diff --git a/.vscode/launch.json b/.vscode/launch.json index fe27067..277d84d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "module": "emblematic", "justMyCode": true, - "args": ["--output-file=output.svg", "--background-file=/store/Documents/Workspaces/RYGhub/logos/current/bg.svg", "--foreground-file=/home/steffo/Pictures/Font Awesome/svgs/solid/apple-whole.svg"] + "args": ["basic", "--background=/store/Documents/Workspaces/RYGhub/logos/current/watermark.svg", "--icon=/home/steffo/Pictures/Font Awesome/svgs", "--fill=#feedb4", "--output-dir=/home/steffo/Workspaces/Steffo99/emblematic-1/output"] } ] } diff --git a/Dockerfile b/Dockerfile index 9fa3d3b..e1dcff3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,8 @@ -# TODO: If you're building a library, remove this file! - FROM python:3-alpine AS system -# TODO: Add whatever dependency your image may require RUN apk add --update build-base python3-dev py-pip musl-dev RUN pip install "poetry" FROM system AS workdir -# TODO: Use the name of your project WORKDIR /usr/src/emblematic FROM workdir AS dependencies @@ -20,9 +16,8 @@ RUN poetry install FROM package AS entrypoint ENV PYTHONUNBUFFERED=1 -ENTRYPOINT ["poetry", "run", "python", "-m"] -# TODO: Set the name of your Python module -CMD ["emblematic"] +ENTRYPOINT ["poetry", "run", "python", "-m", "emblematic"] +CMD [] FROM entrypoint AS labels # TODO: Set a Docker image title diff --git a/README.md b/README.md index 7aa6d32..7e57598 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Simple icon generator -\[ **[Documentation]** | **[Available on PyPI]** | [Repository] \] +\[ **[Documentation]** | **[PyPI]** | [Repository] \] [Documentation]: https://emblematic.readthedocs.io/latest/ -[Available on PyPI]: https://pypi.org/project/emblematic +[PyPI]: https://pypi.org/project/emblematic [Repository]: https://github.com/Steffo99/emblematic/ diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 991d280..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: "3.9" - -services: - emblematic: - image: "ghcr.io/Steffo99/emblematic:latest" - restart: unless-stopped - env_file: - - "stack.env" diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..a9a5104 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,17 @@ +******************************************************************************* +API reference +******************************************************************************* + + +============ +``.compose`` +============ + +.. automodule:: emblematic.compose + + +========== +``.files`` +========== + +.. automodule:: emblematic.files diff --git a/docs/source/conf.py b/docs/source/conf.py index d23d71f..3289a86 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -96,7 +96,6 @@ nitpicky = False # Intersphinx URLs intersphinx_mapping = { "python": ("https://docs.python.org/3.10", None), - "poetry": } # Manpages URL manpages_url = "https://man.archlinux.org/" diff --git a/docs/source/index.rst b/docs/source/index.rst index 18cf377..3bc8457 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,7 +15,8 @@ Table of contents .. toctree:: installation - + usage + api ============ diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 55e4000..79c42a3 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -40,7 +40,7 @@ Using pip $ # On Fish $ source .venv/bin/activate.fish - .. code-block:: wincon + .. code-block:: doscon > ; On Windows > .venv/Scripts/activate.ps1 @@ -50,7 +50,7 @@ Using pip To install |this| using :mod:`pip`: -#. Add |this| to your `requirements.txt` file: +#. Add |this| to your ``requirements.txt`` file: .. code-block:: text @@ -75,6 +75,7 @@ To install |this| using :mod:`pipx`: $ pipx install emblematic + =========== From source =========== @@ -133,7 +134,7 @@ To contribute to |this|, you need to setup the project's environment using :mod: .. hint:: - Setting ``virtualenvs.in-project`` to :data:`True` is recommended! + It is recommended to set ``virtualenvs.in-project`` to :data:`True`! .. code-block:: console diff --git a/docs/source/ryg6-ice-cream.png b/docs/source/ryg6-ice-cream.png new file mode 100644 index 0000000..355adbc Binary files /dev/null and b/docs/source/ryg6-ice-cream.png differ diff --git a/docs/source/usage.rst b/docs/source/usage.rst new file mode 100644 index 0000000..41d9baa --- /dev/null +++ b/docs/source/usage.rst @@ -0,0 +1,55 @@ +******************************************************************************* +Usage +******************************************************************************* + +:mod:`emblematic` currently supports only a single mode of operation. + + +======================= +Generate a basic emblem +======================= + +A *basic emblem* can be generated by running: + +.. code-block:: console + + $ emblematic basic --background="./bg.svg" --icon="./icon.svg" --fill="#feedb4" --output-dir="./output/" + +It is composed by: + +1. taking the SVG background image contained in the file given as the ``--background`` option, such as the following: + + .. figure:: ryg6-bg.svg + :width: 150 + :height: 150 + +2. overlaying a rescaled version of the SVG foreground icon contained in the file given as the ``--icon`` option, filled with the color given in the ``--fill`` option, such as the following: + + .. figure:: fontawesome-ice-cream.svg + :width: 150 + :height: 150 + +3. converting the resulting document to a 2000x2000 PNG file for better compatibility with applications (very few support correctly the ``preserveAspectRatio`` property): + + .. figure:: ryg6-ice-cream.png + :width: 150 + :height: 150 + + +--------------------------------- +Multiple emblems with one command +--------------------------------- + +Multiple emblem files can be generated at once. + +* Pass the ``--icon`` parameter multiple times to generate emblems with the same settings but different icons: + + .. code-block:: console + + $ emblematic basic --background="./bg.svg" --icon="./icon1.svg" --icon="./icon2.svg" --icon="./icon3.svg" --fill="#feedb4" --output-dir="./output/" + +* Pass a directory as the ``--icon`` parameter to render all contained files matched by the ``**/*.svg`` glob: + + .. code-block:: console + + $ emblematic basic --background="./bg.svg" --icon="./fontawesome/" --fill="#feedb4" --output-dir="./output/" diff --git a/emblematic/__init__.py b/emblematic/__init__.py index 8c571c4..7785212 100644 --- a/emblematic/__init__.py +++ b/emblematic/__init__.py @@ -1,5 +1,8 @@ -# If you are building a **library**, use this file to export objects! +from . import compose +from . import files + __all__ = ( - # "", + "compose", + "files", ) diff --git a/emblematic/__main__.py b/emblematic/__main__.py index f0f149f..5a78c5c 100644 --- a/emblematic/__main__.py +++ b/emblematic/__main__.py @@ -1,37 +1,84 @@ +""" +Command-line interface for :mod:`emblematic`. + +Implemented with :mod:`click`. +""" + import click import bs4 +import pathlib +import cairosvg + +from .files import get_svgs +from .compose import compose_basic -@click.command() +@click.group() +def main(): + pass + + +@main.command("basic") @click.option( - "-o", "--output-file", "output_file", - help="File to output the icon to.", - required=True, - type=click.File(mode="w"), -) -@click.option( - "-b", "--background-file", "background_file", - help="File to use as the background of the icon.", - required=True, + "-b", "--background", "bg_file", type=click.File(mode="r"), + required=True, + help="SVG file to be used as background.", ) @click.option( - "-f", "--foreground-file", "foreground_file", - help="File to use as the foreground of the icon.", + "-i", "--icon", "icon_paths", + type=click.Path(exists=True), required=True, - type=click.File(mode="r"), + multiple=True, + help="SVG files or directories of files to be used as foreground.", ) -def main(output_file, background_file, foreground_file): - background_soup = bs4.BeautifulSoup(background_file, "lxml-xml") - foreground_soup = bs4.BeautifulSoup(foreground_file, "lxml-xml") +@click.option( + "-f", "--fill", "icon_fill", + type=str, + required=True, + help="The color to fill icons with." +) +@click.option( + "-o", "--output-dir", "output_dir", + type=click.Path(exists=True, file_okay=False, dir_okay=True), + required=True, + help="The directory where output files should be placed in." +) +def basic(bg_file, icon_paths, icon_fill, output_dir): + icon_paths = map(pathlib.Path, icon_paths) + icon_paths = map(get_svgs, icon_paths) + icon_paths = sum(icon_paths, start=[]) - foreground_tag = foreground_soup.path - foreground_scaled_tag = bs4.BeautifulSoup("""""", "lxml-xml") - foreground_scaled_tag.g.append(foreground_tag) + output_dir = pathlib.Path(output_dir) - background_soup.svg.append(foreground_scaled_tag) + bg_doc = bs4.BeautifulSoup(bg_file, features="lxml-xml") + bg = bg_doc.svg + + for icon_path in icon_paths: + icon_path: pathlib.Path + output_path = output_dir.joinpath(f"{icon_path.stem}.png") + + with open(icon_path) as icon_file: + click.echo(icon_path, nl=False) + icon_doc = bs4.BeautifulSoup(icon_file, features="lxml-xml") + icon = icon_doc.svg + icon.path.attrs["fill"] = icon_fill + + click.echo(" → ", nl=False) + svg_doc = compose_basic(background=bg, icon=icon) + + click.echo(" → ", nl=False) + svg_bytes = bytes(svg_doc.prettify(), encoding="utf8") + + click.echo(" → ", nl=False) + png_bytes = cairosvg.svg2png(bytestring=svg_bytes) + + with open(output_path, mode="wb") as output_file: + click.echo(output_path, nl=False) + output_file.write(png_bytes) + + click.echo() - output_file.write(background_soup.prettify()) if __name__ == "__main__": diff --git a/emblematic/compose.py b/emblematic/compose.py index 5b9615d..5a35062 100644 --- a/emblematic/compose.py +++ b/emblematic/compose.py @@ -1,36 +1,43 @@ +""" +Module containing the functions used to generate ```` icons from other ```` :class:`bs4.Tag`. +""" + import bs4 -def compose_basic(bg: bs4.Tag, fg: bs4.Tag) -> bs4.Tag: +def compose_basic(background: bs4.Tag, icon: bs4.Tag) -> bs4.Tag: """ - Create a nice icon from the given background ```` and the given foreground ````. + Create a new and nice ```` icon from the given background ```` and the given foreground ````. """ - if bg.name != "svg": + if background.name != "svg": raise ValueError("bg is not a tag.") - if fg.name != "svg": + if icon.name != "svg": raise ValueError("fg is not a tag.") - bg = bg.__copy__() - bg.attrs["id"] = "emblematic-bg" - bg.attrs["width"] = "100%" - bg.attrs["height"] = "100%" + background = background.__copy__() + background.attrs["id"] = "emblematic-background" + background.attrs["width"] = "100%" + background.attrs["height"] = "100%" - fg = fg.__copy__() - fg.attrs["id"] = "emblematic-fg" - fg.attrs["width"] = "63%" - fg.attrs["height"] = "63%" - fg.attrs["preserveAspectRation"] = "xMidYMid meet" - fg.attrs["transform"] = "translate(185, 185)" + icon = icon.__copy__() + icon.attrs["id"] = "emblematic-icon" + icon.attrs["width"] = "63%" + icon.attrs["height"] = "63%" + icon.attrs["preserveAspectRatio"] = "xMidYMid meet" + icon.attrs["transform"] = "translate(370, 370)" doc = bs4.BeautifulSoup(""" - - + - """) + """, features="lxml-xml") container: bs4.Tag = doc.svg - container.append(bg) - container.append(fg) + container.append(background) + container.append(icon) return doc + +__all__ = ( + "compose_basic", +) diff --git a/emblematic/files.py b/emblematic/files.py new file mode 100644 index 0000000..6c13320 --- /dev/null +++ b/emblematic/files.py @@ -0,0 +1,22 @@ +""" +Module containing functions to operate on files or directories. +""" + +import pathlib + + +def get_svgs(path: pathlib.Path): + """ + Find all SVGs in the given path. + """ + if not path.exists(): + raise ValueError("Given path does not exist.") + if not path.is_dir(): + return [path] + + return list(path.glob("**/*.svg")) + + +__all__ = ( + "get_svgs", +) diff --git a/emblematic/fontawesome.py b/emblematic/fontawesome.py deleted file mode 100644 index 0a6abcf..0000000 --- a/emblematic/fontawesome.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -Module containing function to process `Font Awesome `_ SVG icons. -""" - -import typing as t -import bs4 -import pathlib - - -def get_icons(path: pathlib.Path): - if not path.exists(): - raise ValueError("Given path does not exist.") - if not path.is_dir(): - raise ValueError("Given path is not a directory.") - - svgs = path.joinpath("svgs") - return svgs.glob("**/*.svg") - - -__all__ = ( - "get_icons", -) diff --git a/pyproject.toml b/pyproject.toml index 87887e1..704a526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,19 +57,24 @@ documentation = "https://emblematic.readthedocs.io/latest/" # Up to five keywords related to your project. # See also: https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#keywords -#keywords = [ -# "", -# "", -# "", -# "", -# "", -#] +keywords = [ + "icon", + "avatar", + "emblem", + "logo", + "symbol", +] # Any number of trove classifiers that apply to your project. # See the list at: https://pypi.org/classifiers/ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", + "Development Status :: 3 - Alpha", + "Environment :: Console", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Topic :: Multimedia :: Graphics", + "Typing :: Typed", ] # ADVANCED: specify the packages exported by your project