Metadata-Version: 2.4
Name: ani2xcurtk
Version: 0.1.0
Summary: .ani to xcursor conversion toolkit
Author-email: mizma <omoikane@path-works.net>
License: LICENSE
Project-URL: homepage, https://github.com/mizma/ani2xcurtk/
Project-URL: documentation, https://github.com/mizma/ani2xcurtk/blob/main/README.md
Project-URL: repository, https://github.com/mizma/ani2xcurtk.git
Project-URL: changelog, https://github.com/mizma/ani2xcurtk/blob/main/CHANGELOG.md
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click<9,>=8
Requires-Dist: PyYaml<7,>=6
Requires-Dist: pillow<13,>=12.2
Provides-Extra: dev
Requires-Dist: pyinstaller; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# ani2xcurtk

`.ani` to `Xcursor` conversion toolkit.  This toolkit helps convert a
typical Windows cursor themes using `.cur` or `.ani` files with specific
file naming convention to map to a typical `Xcursor` theme and create
the cursor theme package.

This tool provides subcommands to split certain stages of the conversion
process for users to be able to intervene in key steps like making
alternate images of a cursor to be used in cursor types not typically
available in a Windows cursor theme pack.

## Installation

~~~shell
git clone git@github.com:mizma/ani2xcurtk.git
cd ani2xcurtk
pipx install .
~~~

### External Prerequisites

Following cli tools will need to be accessible in the environment.

* `win2xcur`
  * Used to convert `.cur` and `.ani` to `Xcursor` cursor file
* `xcur2png`
  * Used to extract `.png` image files from `Xcursor` cursor files
* `ImageMagick`
  * Used to create different cursor sizes
* `xcursorgen`
  * Used to re-package the extracted and up-converted `.png` files
    back to `Xcursor` cursor files

## Usage

### getpng

extract `.cur` or `.ani` files to `.png` files and the Xcursor theme configs.

Requires `win2xcur` and `xcur2png` to be installed and accessible in PATH

~~~shell
> a2xc getpng [OPTIONS] TARGET
Usage: a2xc getpng [OPTIONS] TARGET

  Extract .cur or .ani files to .png files and the Xcursor theme configs.

Options:
  -o, --output <output>  Output Directory Path (default: ./output)
  -c, --config <cfg>     Configuration File (default: ani2xcurtk.yml)
  -g, --generate-extra   Generates extra copies for manual editing
  -v, --verbose          output in verbose mode
  -h, --help             Show this message and exit.
~~~

* `TARGET`
  * directory containing a standard windows cursor theme pack
* `-o, --output PATH`
  * output directory path to place all converted files
* `-c, --config CFG`
  * CFG: Path to Configuration File (default: ani2xcurtk.yml)
* -g, --generate-extra
  * generates extra copies for manual editing
* `-v, --verbose`
  * verbosity

#### What the getpng command does

Equivalent of doing the following to each `.ani` and `.cur` files (example for 01-Normal.ani)

~~~bash
mkdir $PATH/01-Normal
win2xcur 01-Normal.ani -o $PATH/01-Normal # repeated for each item
# -g will generate extra copies.
cd $PATH/01-Normal # repeated for everything in $PATH (incl. directories generated by -g)
mkdir pngs
xcur2png -d pngs 01-Normal
rm 01-Normal
~~~

Which will generate something like the following for each `.ani` and `.cur` files.

~~~shell
01-Normal
├── 01-Normal.conf
└── pngs
    ├── 01-Normal_000.png
    ├── 01-Normal_001.png
    ├── 01-Normal_002.png
    ├── 01-Normal_003.png
    ├── 01-Normal_004.png
    ├── 01-Normal_005.png
    ├── 01-Normal_006.png
    └── 01-Normal_007.png
~~~

##### Generate Extra

* Will make copies of outputs from the following `win2xcur` outputs based on the `generate`
  configuration in `ani2xcurtk.yml`.
* Default setting will produce the following.
  * `01-normal` -> `01-normal-mirrored`
    * This one will also get all the output `.png` mirrored using ImageMagick.
  * `11-Vertical_Resize` -> `11-Vertical_Resize-top`
  * `11-Vertical_Resize` -> `11-Vertical_Resize-bottom`
  * `12-Horizontal_Resize` -> `12-Horizontal_Resize-left`
  * `12-Horizontal_Resize` -> `12-Horizontal_Resize-right`
  * `13-Diagonal_Resize_1` -> `13-Diagonal_Resize_1-top`
  * `13-Diagonal_Resize_1` -> `13-Diagonal_Resize_1-bottom`
  * `14-Diagonal_Resize_2` -> `14-Diagonal_Resize_2-top`
  * `14-Diagonal_Resize_2` -> `14-Diagonal_Resize_2-bottom`
* File set to "copy" will just be a copy of the source
* File set to "mirror" will be processed with `magick mogrify --flop *.png`
  after copy.

### pack

Package the `.png` files into `Xcursor` theme.

`pack` will create different sizes of the image files using `ImageMagick` and
map the typical Windows cursor package files to relevant `Xcursor` themes.

Requires `Xcursorgen` and `ImageMagick` to be installed and accessible in PATH

~~~shell
> a2xc pack [OPTIONS] TARGET
Usage: a2xc pack [OPTIONS] TARGET

  Package the .png files into Xcursor theme.

Options:
  -o, --output <output>  Output Directory Path (default: ./NAME)
  -n, --name <name>      name of the cursor theme
  -c, --config <cfg>     Configuration File (default: ani2xcurtk.yml)
  -v, --verbose          output in verbose mode
  -h, --help             Show this message and exit.

~~~

* `TARGET`
  * directory containg directories with `Xcursor` input png and config files
* `-o, --output PATH`
  * output directory path to place the `Xcursor` theme
* `-n, --name NAME`
  * name of the cursor theme
* `-c, --config CFG`
  * CFG: Path to Configuration File (default: `ani2xcurtk.yml`)
* `-v, --verbose`
  * verbosity

#### What the pack command does

Assumes TARGET is the same structure as the output from `getpng` subcommand
as shown in example below (parts omitted shown as `...`)

~~~shell
├── 01-Normal/
│   ├── 01-Normal.conf
│   └── pngs/
│       ├── 01-Normal_000.png
│       ├── 01-Normal_001.png
│       ├── 01-Normal_002.png
│       ├── 01-Normal_003.png
│       ├── 01-Normal_004.png
│       ├── 01-Normal_005.png
│       ├── 01-Normal_006.png
│       └── 01-Normal_007.png
├── 02-Link/
│   ├── ...
├── 03-Loading/
│   ├── ...
├── 04-Help/
│   ├── ...
...
~~~

The `pack` subcommand parses through the directory and does the following

1. For each `.png` in the directory, generate the different sizes of the image
   and name them with a postfix of the size.
    * Size generated will follow the size defined in `sizes` key of the `ani2xcurtk.yml`.
    * Example: `01-Normal_000_40.png` for 40 x 40 version.
2. Edit the `.conf` file to include all the size variants accordingly
    * Maintains the animation frame numbers for all size variants.
3. Use `xcursorgen` on each `.conf` file
4. Copy the resulting cursor file to output `PATH/NAME/cursors/` renaming them
   accordingly based on the mappings configuration in the `ani2xcurtk.yml` file.
    * First entry for each mappings is copied, while all others will become symbolic links
      of the copied file.
5. Generate the `cursor.theme` file with the NAME as cursor theme name

### conv

Chains getpng and pack in one command.  Will not create any extra copy folders
for manual editing (-g option for `getpng` is not available).

~~~shell
> a2xc conv [OPTIONS] TARGET
Usage: a2xc conv [OPTIONS] TARGET

  Chains getpng and pack in one command. Will not create any extra copy
  folders for manual editing (-g option for getpng is not available).

Options:
  -p, --pngout <pngout>  Output Directory Path to place all converted png
                         files (default: ./pngout)
  -o, --output <output>  Output Directory Path (default: ./NAME)
  -n, --name <name>      name of the cursor theme
  -c, --config <cfg>     Configuration File (default: ani2xcurtk.yml)
  -v, --verbose          output in verbose mode
  -h, --help             Show this message and exit.
~~~

* TARGET
  * Directory containing a standard windows cursor theme pack
* `-p, --pngout PATH`
  * output directory path to place all converted `.png` files
    (same as -o for `getpng` subcommand)
* `-o, --output PATH`
  * Output directory path to place the `Xcursor` theme
* `-n, --name NAME`
  * Name of the cursor theme
* `-c, --config CFG`
  * CFG: Path to Configuration File (default: `ani2xcurtk.yml`)
* `-v, --verbose`
  * verbosity

### Configuration file

YAML based configuration file (default file name: `ani2xcurtk.yaml`)
is used to store default settings for the tool.
The command line options will take precedence over the configuration parameters.

#### Parameters

* `sizes` key
  * Extra cursor sizes (in pixels) to generate as an array.
* `mappings` key
  * Array of keys with the Windows cursor filename.
  * Each Windows cursor filename key has an array of `Xcursor` cursor
    file name associated to the Windows version.
    * The cursor generated from this `.cur/.ani` will be copied
      to the first element in the array, and the rest will become
      symbolic links to the first element.
    * If no mappings are available, keys can be omitted or use `null`
      to clarify that there is no current mapping.
  * Mappings are processed in the order it is defined.
    Any mapping appearing later will overwrite previous mappings.
    * This is to allow manually edited images to overwrite existing
      non-ideal mappings.
    * Make sure you do not override the top mapping for each file,
      Since the top file is the file copied over and all other mappings
      link to this

#### Example config

Following shows the default settings of the configuration

~~~yaml
sizes:
  - 40
  - 48
  - 56
  - 64
generate:
  - "01-normal":
    - "01-normal-mirrored": "mirror"
  - "11-Vertical Resize":
    - "11-Vertical_Resize-top": "copy"
    - "11-Vertical_Resize-bottom": "copy"
  - "12-Horizontal_Resize":
    - "12-Horizontal_Resize-left": "copy"
    - "12-Horizontal_Resize-right": "copy"
  - "13-Diagonal_Resize_1":
    - "13-Diagonal_Resize_1-top": "copy"
    - "13-Diagonal_Resize_1-bottom": "copy"
  - "14-Diagonal Resize 2":
    - "14-Diagonal_Resize_2-top": "copy"
    - "14-Diagonal_Resize_2-bottom": "copy"
mappings:
  - "01-normal":
    - "arrow"
    - "default"
    - "left_ptr"
    - "top_left_arrow"
  - "02-link":
    - "alias"
    - "dnd-link"
    - "hand"
    - "hand1"
    - "hand2"
    - "link"
    - "openhand"
    - "pointer"
    - "pointing_hand"
    - "3085a0e285430894940527032f8b26df"
    - "640fb0e74195791501fd1ed57b41487f"
    - "9d800788f1b08800ae810202380a0822"
    - "a2a266d0498c3104214a47bd64ab0fc8"
    - "b66166c04f8c3109214a4fbd64a50fc8"
    - "e29285e634086352946a0e7090d73106"
  - "03-loading":
    - "half-busy"
    - "left_ptr_watch"
    - "progress"
    - "wait"
    - "watch"
    - "00000000000000020006000e7e9ffc3f"
    - "08e8e1c95fe2fc01f976f1e063a24ccd"
    - "3ecb610c1bf2410f44200f48c40d3599"
  - "04-help":
    - "dnd-ask"
    - "help"
    - "left_ptr_help"
    - "question_arrow"
    - "whats_this"
    - "5c6cd98b3f3ebcb1f9c7f1c204630408"
    - "d9ce0ab605698f320427677b458ad60b"
  - "05-text_select":
    - "ibeam"
    - "text"
    - "xterm"
    - "vertical-text"
  - "06-handwriting":
    - "draft"
    - "pencil"
  - "07-precision":
    - "cell"
    - "color-picker"
    - "cross_reverse"
    - "cross"
    - "crosshair"
    - "diamond_cross"
    - "plus"
    - "size_all"
    - "tcross"
  - "08-unavailable":
    - "circle"
    - "crossed_circle"
    - "dnd-no-drop"
    - "forbidden"
    - "not-allowed"
    - "no-drop"
    - "pirate"
    - "03b6e0fcb3499374a867c041f52298f0"
  - "09-Location_Select": null
  - "10-Person_Select": null
  - "11-Vertical_Resize":
    - "v_double_arrow"
    - "bottom_side"
    - "top_side"
    - "up-arrow"
    - "down-arrow"
    - "n-resize"
    - "ns-resize"
    - "row-resize"
    - "s-resize"
    - "sb_v_double_arrow"
    - "size_ver"
    - "split_v"
    - "sb_down_arrow"
    - "sb_up_arrow"
    - "00008160000006810000408080010102"
    - "2870a09082c103050810ffdffffe0204"
  - "12-Horizontal_Resize":
    - "col-resize"
    - "e-resize"
    - "ew-resize"
    - "h_double_arrow"
    - "left_side"
    - "left-arrow"
    - "right_side"
    - "right-arrow"
    - "sb_right_arrow"
    - "sb_left_arrow"
    - "sb_h_double_arrow"
    - "size_hor"
    - "split_h"
    - "w-resize"
    - "14fef782d02440884392942c11205230"
    - "028006030e0e7ebffc7f7070c0600140"
  - "13-Diagonal_Resize_1":
    - "bd_double_arrow"
    - "bottom_right_corner"
    - "lr_angle"
    - "nw-resize"
    - "nwse-resize"
    - "se-resize"
    - "size_fdiag"
    - "top_left_corner"
    - "ul_angle"
    - "c7088f0f3e6c8088236ef8e1e3e70000"
  - "14-Diagonal_Resize_2":
    - "fd_double_arrow"
    - "bottom_left_corner"
    - "ll_angle"
    - "ne-resize"
    - "nesw-resize"
    - "size_bdiag"
    - "sw-resize"
    - "top_right_corner"
    - "ur_angle"
    - "fcf1c3c7cd4491d801f1e1c78f100000"
  - "15-Move":
    - "all-scroll"
    - "closedhand"
    - "dnd-move"
    - "dnd-none"
    - "fleur"
    - "grab"
    - "grabbing"
    - "move"
    - "4498f0e0c1937ffe01fd06f973665830"
    - "9081237383d90e509aa00f00170e968f"
  - "16-Alternate_Select":
    - "center_ptr"
    - "right_ptr"
    - "draft_large"
    - "draft_small"
  # Following are for manual edit of existing images.
  # These will overwrite existing ones above.
  - "01-normal-mirrored": # auto-generated
    - "right_ptr"
    - "draft_large"
    - "draft_small"
  - "11-Vertical_Resize-top": # manually generate
    - "top_side"
    - "up-arrow"
    - "n-resize"
    - "sb_up_arrow"
  - "11-Vertical_Resize-bottom": # manually generate
    - "bottom_side"
    - "down-arrow"
    - "s-resize"
    - "sb_down_arrow"
  - "12-Horizontal_Resize-left": # manually generate
    - "left_side"
    - "left-arrow"
    - "sb_left_arrow"
    - "w-resize"
  - "12-Horizontal_Resize-right": # manually generate
    - "right_side"
    - "right-arrow"
    - "sb_right_arrow"
    - "e-resize"
  - "13-Diagonal_Resize_1-top": # manually generate
    - "top_left_corner"
    - "nw-resize"
    - "ul_angle"
  - "13-Diagonal_Resize_1-bottom": # manually generate
    - "bottom_right_corner"
    - "se-resize"
    - "lr_angle"
  - "14-Diagonal_Resize_2-top": # manually generate
    - "top_right_corner"
    - "ne-resize"
    - "ur_angle"
  - "14-Diagonal_Resize_2-bottom": # manually generate
    - "bottom_left_corner"
    - "sw-resize"
    - "ll_angle"
~~~

## Known Issues

* Currently does not support partial busy cursor image.

## Development

### Versioning

The project will follow the [semver2.0](http://semver.org/) versioning scheme.
With initial development phase starting at 0.1.0 and increasing
minor/patch versions until we deploy the tool to production
(and reach 1.0.0).
The interface relevant to versioning is whatever defined in this
document's "Usage" section (includes all (sub)commands, their cli arguments,
and the format of the configuration file "ani2xcurtk.yaml").

## Acknowledgement

* [win2xcur-batch](https://github.com/khayalhus/win2xcur-batch)
  * Base inspiration for the tool.  Initial mapping are based on their [map.json](https://github.com/khayalhus/win2xcur-batch/blob/main/map.json)
