Name | glyphsynth JSON |
Version |
0.1.2
JSON |
| download |
home_page | None |
Summary | Pythonic vector glyph synthesis toolkit |
upload_time | 2024-10-28 04:18:48 |
maintainer | None |
docs_url | None |
author | mm21 |
requires_python | <4.0,>=3.12 |
license | None |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# GlyphSynth: Pythonic vector glyph synthesis toolkit
[![Python versions](https://img.shields.io/pypi/pyversions/glyphsynth.svg)](https://pypi.org/project/glyphsynth)
[![PyPI](https://img.shields.io/pypi/v/glyphsynth?color=%2334D058&label=pypi%20package)](https://pypi.org/project/glyphsynth)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
## Motivation
This project provides a Pythonic mechanism to construct SVG graphics, termed as "glyphs". Glyphs can be parameterized and leverage inheritance to promote reuse. The ability to construct many variations of glyphs programmatically is a powerful tool for creativity.
## Getting started
First, install using pip:
```bash
pip install glyphsynth
```
The user is intended to develop glyphs using their own Python modules. A typical workflow might be to create a number of `BaseGlyph` subclasses, set them in `__all__`, and invoke `glyphsynth-export` passing in the module and output path. See below for more details.
## Interface
Glyphs can be constructed in two ways, or a combination of both:
- Subclass `BaseGlyph` and implement `draw()`
- Parameterize with a subclass of `BaseParams` corresponding to the glyph
- Create an instance of `EmptyGlyph` (or any other `BaseGlyph` subclass) and invoke draw APIs
In its `draw()` method, a `BaseGlyph` subclass can invoke drawing APIs which create corresponding SVG objects. SVG properties are automatically propagated to SVG objects from the glyph's properties, `BaseGlyph.properties`, which can be provided at runtime with defaults being specified by the subclass.
A simple example of implementing `draw()` to draw a blue square:
```python
from glyphsynth import BaseParams, BaseGlyph
# Glyph params
class MySquareParams(BaseParams):
color: str
# Glyph subclass
class MySquareGlyph(BaseGlyph[MySquareParams]):
# Canonical size for glyph construction, can be rescaled upon creation
size_canon = (100.0, 100.0)
def draw(self):
# Draw a centered square using the provided color
self.draw_rect((25.0, 25.0), (50.0, 50.0), fill=self.params.color)
# Draw a black border around the perimeter
self.draw_polyline(
[(0.0, 0.0), (0.0, 100.0), (100.0, 100.0), (100.0, 0), (0.0, 0.0)],
stroke="black",
fill="none",
stroke_width="5",
)
# Create glyph instance
blue_square = MySquareGlyph(
glyph_id="blue-square", params=MySquareParams(color="blue")
)
# Render as image
blue_square.export_png(Path("my_glyph_renders"))
```
This is rendered as:
[![Blue-square](./examples/blue-square.png)]()
Equivalently, the same glyph can be constructed from an `EmptyGlyph`:
```python
from glyphsynth import EmptyGlyph
blue_square = EmptyGlyph(glyph_id="blue-square", size=(100, 100))
# Draw a centered square
blue_square.draw_rect((25.0, 25.0), (50.0, 50.0), fill="blue")
# Draw a black border around the perimeter
blue_square.draw_polyline(
[(0.0, 0.0), (0.0, 100.0), (100.0, 100.0), (100.0, 0), (0.0, 0.0)],
stroke="black",
fill="none",
stroke_width="5",
)
```
## Exporting
### Programmatically
A glyph is exported as an `.svg` file. Rasterizing to `.png` is supported on Linux and requires the following packages:
```bash
sudo apt install librsvg2-bin libmagickwand-dev
```
A glyph can be exported using `BaseGlyph.export()`, `BaseGlyph.export_svg()`, or `BaseGlyph.export_png()`. If a folder is passed as the output path, the glyph's `glyph_id` will be used to derive the filename.
```python
from pathlib import Path
# Export to specific path
blue_square.export(Path("my_glyph_renders/blue-square.svg"))
blue_square.export(Path("my_glyph_renders/blue-square.png"))
# Export using class name as filename
blue_square.export_svg(Path("my_glyph_renders")) # blue-square.svg
blue_square.export_png(Path("my_glyph_renders")) # blue-square.png
```
### CLI
The CLI tool `glyphsynth-export` exports glyphs by importing a Python object. See `glyphsynth-export --help` for full details.
The object can be any of the following:
- Module, from which objects will be extracted via `__all__`
- `BaseGlyph` subclass
- `BaseGlyph` instance
- Iterable
- Callable
Any `BaseGlyph` subclasses found will be instantiated using their respective default parameters. For `Iterable` and `Callable`, the object is traversed or invoked recursively until glyph subclasses or instances are found.
Assuming the above code containing the `blue_square` is placed in `my_glyphs.py`, the glyph can be exported to `my_glyph_renders/` via the following command:
`glyphsynth-export my_glyphs.blue_square my_glyph_renders --svg --png`
## Examples
### Multi-square
[![Multi-square](./examples/multi-square.png)]()
This glyph is composed of 4 nested squares, each with a color parameter.
```python
from glyphsynth import BaseParams, BaseGlyph
# Definitions
ZERO: float = 0.0
UNIT: float = 100.0
HALF: float = UNIT / 2
UNIT_SIZE: tuple[float, float] = (UNIT, UNIT)
ORIGIN: tuple[float, float] = (ZERO, ZERO)
# Multi-square parameters
class MultiSquareParams(BaseParams):
color_upper_left: str
color_upper_right: str
color_lower_left: str
color_lower_right: str
# Multi-square glyph class
class MultiSquareGlyph(BaseGlyph[MultiSquareParams]):
size_canon = UNIT_SIZE
def draw(self):
# Each nested square should occupy 1/4 of the area
size: tuple[float, float] = (HALF, HALF)
# Draw upper left
self.draw_rect(ORIGIN, size, fill=self.params.color_upper_left)
# Draw upper right
self.draw_rect((HALF, ZERO), size, fill=self.params.color_upper_right)
# Draw lower left
self.draw_rect((ZERO, HALF), size, fill=self.params.color_lower_left)
# Draw lower right
self.draw_rect((HALF, HALF), size, fill=self.params.color_lower_right)
# Create parameters
multi_square_params = MultiSquareParams(
color_upper_left="red",
color_upper_right="orange",
color_lower_right="green",
color_lower_left="blue",
)
# Create glyph
multi_square = MultiSquareGlyph(glyph_id="multi-square", params=multi_square_params)
```
### Multi-square fractal
[![Multi-square fractal](./examples/multi-square-fractal.png)]()
This glyph nests a square glyph recursively up to a certain depth.
```python
from glyphsynth import BaseParams, BaseGlyph
# Maximum recursion depth for creating fractal
FRACTAL_DEPTH = 10
class SquareFractalParams(BaseParams):
square_params: MultiSquareParams
depth: int = FRACTAL_DEPTH
class SquareFractalGlyph(BaseGlyph[SquareFractalParams]):
size_canon = UNIT_SIZE
def draw(self):
# Draw square
self.insert_glyph(MultiSquareGlyph(params=self.params.square_params))
if self.params.depth > 1:
# Draw another fractal glyph, half the size and rotated 90 degrees
child_params = SquareFractalParams(
square_params=self.params.square_params,
depth=self.params.depth - 1,
)
child_glyph = SquareFractalGlyph(
params=child_params, size=(HALF, HALF)
)
# Rotate and insert in center
child_glyph.rotate(90.0)
self.insert_glyph(child_glyph, insert=(HALF / 2, HALF / 2))
multi_square_params = MultiSquareParams(
color_upper_left="rgb(250, 50, 0)",
color_upper_right="rgb(250, 250, 0)",
color_lower_right="rgb(0, 250, 50)",
color_lower_left="rgb(0, 50, 250)",
)
fractal = SquareFractalGlyph(
glyph_id="multi-square-fractal",
params=SquareFractalParams(square_params=multi_square_params),
)
```
### Letter combination variants
[![Variants matrix](./examples/variants-matrix-pad.png)]()
This illustrates the use of letter glyphs, provided by this package as a library, to create parameterized geometric designs. Permutations of pairs of letters `A`, `M`, and `T` are selected for a range of color variants, with the second letter being rotated 180 degrees.
```python
from glyphsynth.lib.alphabet.minimal import (
UNIT,
LetterParams,
BaseLetterGlyph,
LetterComboParams,
BaseLetterComboGlyph,
A,
M,
T,
)
# Letters to use for variants
LETTERS = [
A,
M,
T,
]
# Colors to use for variants
COLORS = [
"black",
"red",
"green",
"blue",
]
# Params containing 2 letters which are overlayed
class AMTComboParams(LetterComboParams):
letter1: type[BaseLetterGlyph]
letter2: type[BaseLetterGlyph]
# Glyph class
class AMTComboGlyph(BaseLetterComboGlyph[AMTComboParams]):
def draw(self):
# draw letters given by params
self.draw_letter(self.params.letter1)
letter2 = self.draw_letter(self.params.letter2)
# additionally rotate letter2
letter2.rotate(180)
```
A subclass of `BaseVariantExportFactory` can be used as a convenience for generating variants:
```python
from typing import Generator
import itertools
from glyphsynth.lib.variants import BaseVariantExportFactory
# Factory to create variants of AMTComboGlyph
class VariantFactory(BaseVariantExportFactory[AMTComboGlyph]):
# Width of resulting matrix
MATRIX_WIDTH = len(COLORS)
# Top-level padding and space between glyph variants
SPACING = UNIT / 10
# Glyph subclass from which to generate variants
glyph_cls = AMTComboGlyph
# Generate variants of colors and letter combinations
def get_params_variants(self) -> Generator[AMTComboParams, None, None]:
for color, letter1, letter2 in itertools.product(
COLORS, LETTERS, LETTERS
):
yield AMTComboParams(
letter_params=LetterParams(color=color),
letter1=letter1,
letter2=letter2,
)
```
The fully-qualified class name of `VariantFactory` can be passed as an argument to `glyphsynth-export`. This will result in a folder structure containing each variant individually, as well as the variant matrix and each individual row/column.
Raw data
{
"_id": null,
"home_page": null,
"name": "glyphsynth",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.12",
"maintainer_email": null,
"keywords": null,
"author": "mm21",
"author_email": "mm21.dev@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/68/dc/8b8347a1f760fa6b063a35118ee10578a11c5834748b2a69f5631ab8e788/glyphsynth-0.1.2.tar.gz",
"platform": null,
"description": "# GlyphSynth: Pythonic vector glyph synthesis toolkit\n\n[![Python versions](https://img.shields.io/pypi/pyversions/glyphsynth.svg)](https://pypi.org/project/glyphsynth)\n[![PyPI](https://img.shields.io/pypi/v/glyphsynth?color=%2334D058&label=pypi%20package)](https://pypi.org/project/glyphsynth)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\n## Motivation\n\nThis project provides a Pythonic mechanism to construct SVG graphics, termed as \"glyphs\". Glyphs can be parameterized and leverage inheritance to promote reuse. The ability to construct many variations of glyphs programmatically is a powerful tool for creativity.\n\n## Getting started\n\nFirst, install using pip:\n\n```bash\npip install glyphsynth\n```\n\nThe user is intended to develop glyphs using their own Python modules. A typical workflow might be to create a number of `BaseGlyph` subclasses, set them in `__all__`, and invoke `glyphsynth-export` passing in the module and output path. See below for more details.\n\n## Interface\n\nGlyphs can be constructed in two ways, or a combination of both:\n\n- Subclass `BaseGlyph` and implement `draw()`\n - Parameterize with a subclass of `BaseParams` corresponding to the glyph\n- Create an instance of `EmptyGlyph` (or any other `BaseGlyph` subclass) and invoke draw APIs\n\nIn its `draw()` method, a `BaseGlyph` subclass can invoke drawing APIs which create corresponding SVG objects. SVG properties are automatically propagated to SVG objects from the glyph's properties, `BaseGlyph.properties`, which can be provided at runtime with defaults being specified by the subclass.\n\nA simple example of implementing `draw()` to draw a blue square:\n\n```python\nfrom glyphsynth import BaseParams, BaseGlyph\n\n# Glyph params\nclass MySquareParams(BaseParams):\n color: str\n\n# Glyph subclass\nclass MySquareGlyph(BaseGlyph[MySquareParams]):\n\n # Canonical size for glyph construction, can be rescaled upon creation\n size_canon = (100.0, 100.0)\n\n def draw(self):\n\n # Draw a centered square using the provided color\n self.draw_rect((25.0, 25.0), (50.0, 50.0), fill=self.params.color)\n\n # Draw a black border around the perimeter\n self.draw_polyline(\n [(0.0, 0.0), (0.0, 100.0), (100.0, 100.0), (100.0, 0), (0.0, 0.0)],\n stroke=\"black\",\n fill=\"none\",\n stroke_width=\"5\",\n )\n\n\n# Create glyph instance\nblue_square = MySquareGlyph(\n glyph_id=\"blue-square\", params=MySquareParams(color=\"blue\")\n)\n\n# Render as image\nblue_square.export_png(Path(\"my_glyph_renders\"))\n```\n\nThis is rendered as:\n\n[![Blue-square](./examples/blue-square.png)]()\n\nEquivalently, the same glyph can be constructed from an `EmptyGlyph`:\n\n```python\nfrom glyphsynth import EmptyGlyph\n\nblue_square = EmptyGlyph(glyph_id=\"blue-square\", size=(100, 100))\n\n# Draw a centered square\nblue_square.draw_rect((25.0, 25.0), (50.0, 50.0), fill=\"blue\")\n\n# Draw a black border around the perimeter\nblue_square.draw_polyline(\n [(0.0, 0.0), (0.0, 100.0), (100.0, 100.0), (100.0, 0), (0.0, 0.0)],\n stroke=\"black\",\n fill=\"none\",\n stroke_width=\"5\",\n)\n```\n\n## Exporting\n\n### Programmatically\n\nA glyph is exported as an `.svg` file. Rasterizing to `.png` is supported on Linux and requires the following packages:\n\n```bash\nsudo apt install librsvg2-bin libmagickwand-dev\n```\n\nA glyph can be exported using `BaseGlyph.export()`, `BaseGlyph.export_svg()`, or `BaseGlyph.export_png()`. If a folder is passed as the output path, the glyph's `glyph_id` will be used to derive the filename.\n\n```python\nfrom pathlib import Path\n\n# Export to specific path\nblue_square.export(Path(\"my_glyph_renders/blue-square.svg\"))\nblue_square.export(Path(\"my_glyph_renders/blue-square.png\"))\n\n# Export using class name as filename\nblue_square.export_svg(Path(\"my_glyph_renders\")) # blue-square.svg \nblue_square.export_png(Path(\"my_glyph_renders\")) # blue-square.png\n```\n\n### CLI\n\nThe CLI tool `glyphsynth-export` exports glyphs by importing a Python object. See `glyphsynth-export --help` for full details.\n\nThe object can be any of the following:\n\n- Module, from which objects will be extracted via `__all__`\n- `BaseGlyph` subclass\n- `BaseGlyph` instance\n- Iterable\n- Callable\n\nAny `BaseGlyph` subclasses found will be instantiated using their respective default parameters. For `Iterable` and `Callable`, the object is traversed or invoked recursively until glyph subclasses or instances are found.\n\nAssuming the above code containing the `blue_square` is placed in `my_glyphs.py`, the glyph can be exported to `my_glyph_renders/` via the following command:\n\n`glyphsynth-export my_glyphs.blue_square my_glyph_renders --svg --png`\n\n## Examples\n\n### Multi-square\n\n[![Multi-square](./examples/multi-square.png)]()\n\nThis glyph is composed of 4 nested squares, each with a color parameter.\n\n```python\nfrom glyphsynth import BaseParams, BaseGlyph\n\n# Definitions\nZERO: float = 0.0\nUNIT: float = 100.0\nHALF: float = UNIT / 2\nUNIT_SIZE: tuple[float, float] = (UNIT, UNIT)\nORIGIN: tuple[float, float] = (ZERO, ZERO)\n\n# Multi-square parameters\nclass MultiSquareParams(BaseParams):\n color_upper_left: str\n color_upper_right: str\n color_lower_left: str\n color_lower_right: str\n\n# Multi-square glyph class\nclass MultiSquareGlyph(BaseGlyph[MultiSquareParams]):\n\n size_canon = UNIT_SIZE\n\n def draw(self):\n\n # Each nested square should occupy 1/4 of the area\n size: tuple[float, float] = (HALF, HALF)\n\n # Draw upper left\n self.draw_rect(ORIGIN, size, fill=self.params.color_upper_left)\n\n # Draw upper right\n self.draw_rect((HALF, ZERO), size, fill=self.params.color_upper_right)\n\n # Draw lower left\n self.draw_rect((ZERO, HALF), size, fill=self.params.color_lower_left)\n\n # Draw lower right\n self.draw_rect((HALF, HALF), size, fill=self.params.color_lower_right)\n\n# Create parameters\nmulti_square_params = MultiSquareParams(\n color_upper_left=\"red\",\n color_upper_right=\"orange\",\n color_lower_right=\"green\",\n color_lower_left=\"blue\",\n)\n\n# Create glyph\nmulti_square = MultiSquareGlyph(glyph_id=\"multi-square\", params=multi_square_params)\n```\n\n### Multi-square fractal\n\n[![Multi-square fractal](./examples/multi-square-fractal.png)]()\n\nThis glyph nests a square glyph recursively up to a certain depth.\n\n```python\nfrom glyphsynth import BaseParams, BaseGlyph\n\n# Maximum recursion depth for creating fractal\nFRACTAL_DEPTH = 10\n\nclass SquareFractalParams(BaseParams):\n square_params: MultiSquareParams\n depth: int = FRACTAL_DEPTH\n\nclass SquareFractalGlyph(BaseGlyph[SquareFractalParams]):\n\n size_canon = UNIT_SIZE\n\n def draw(self):\n\n # Draw square\n self.insert_glyph(MultiSquareGlyph(params=self.params.square_params))\n\n if self.params.depth > 1:\n # Draw another fractal glyph, half the size and rotated 90 degrees\n\n child_params = SquareFractalParams(\n square_params=self.params.square_params,\n depth=self.params.depth - 1,\n )\n child_glyph = SquareFractalGlyph(\n params=child_params, size=(HALF, HALF)\n )\n\n # Rotate and insert in center\n child_glyph.rotate(90.0)\n self.insert_glyph(child_glyph, insert=(HALF / 2, HALF / 2))\n\nmulti_square_params = MultiSquareParams(\n color_upper_left=\"rgb(250, 50, 0)\",\n color_upper_right=\"rgb(250, 250, 0)\",\n color_lower_right=\"rgb(0, 250, 50)\",\n color_lower_left=\"rgb(0, 50, 250)\",\n)\n\nfractal = SquareFractalGlyph(\n glyph_id=\"multi-square-fractal\",\n params=SquareFractalParams(square_params=multi_square_params),\n)\n```\n\n### Letter combination variants\n\n[![Variants matrix](./examples/variants-matrix-pad.png)]()\n\nThis illustrates the use of letter glyphs, provided by this package as a library, to create parameterized geometric designs. Permutations of pairs of letters `A`, `M`, and `T` are selected for a range of color variants, with the second letter being rotated 180 degrees.\n\n```python\nfrom glyphsynth.lib.alphabet.minimal import (\n UNIT,\n LetterParams,\n BaseLetterGlyph,\n LetterComboParams,\n BaseLetterComboGlyph,\n A,\n M,\n T,\n)\n\n# Letters to use for variants\nLETTERS = [\n A,\n M,\n T,\n]\n\n# Colors to use for variants\nCOLORS = [\n \"black\",\n \"red\",\n \"green\",\n \"blue\",\n]\n\n# Params containing 2 letters which are overlayed\nclass AMTComboParams(LetterComboParams):\n letter1: type[BaseLetterGlyph]\n letter2: type[BaseLetterGlyph]\n\n# Glyph class\nclass AMTComboGlyph(BaseLetterComboGlyph[AMTComboParams]):\n def draw(self):\n \n # draw letters given by params\n self.draw_letter(self.params.letter1)\n letter2 = self.draw_letter(self.params.letter2)\n\n # additionally rotate letter2\n letter2.rotate(180)\n```\n\nA subclass of `BaseVariantExportFactory` can be used as a convenience for generating variants:\n\n```python\nfrom typing import Generator\nimport itertools\n\nfrom glyphsynth.lib.variants import BaseVariantExportFactory\n\n# Factory to create variants of AMTComboGlyph\nclass VariantFactory(BaseVariantExportFactory[AMTComboGlyph]):\n\n # Width of resulting matrix\n MATRIX_WIDTH = len(COLORS)\n\n # Top-level padding and space between glyph variants\n SPACING = UNIT / 10\n\n # Glyph subclass from which to generate variants\n glyph_cls = AMTComboGlyph\n\n # Generate variants of colors and letter combinations\n def get_params_variants(self) -> Generator[AMTComboParams, None, None]:\n for color, letter1, letter2 in itertools.product(\n COLORS, LETTERS, LETTERS\n ):\n yield AMTComboParams(\n letter_params=LetterParams(color=color),\n letter1=letter1,\n letter2=letter2,\n )\n```\n\nThe fully-qualified class name of `VariantFactory` can be passed as an argument to `glyphsynth-export`. This will result in a folder structure containing each variant individually, as well as the variant matrix and each individual row/column.\n",
"bugtrack_url": null,
"license": null,
"summary": "Pythonic vector glyph synthesis toolkit",
"version": "0.1.2",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4c98fac0e9652eaa80b62fee8be26cff63730e9b52403eb7892ac044211dfc09",
"md5": "3169c1ebd36776e4b9fd6a44447bdac2",
"sha256": "0bdb8648e46188a15765677b12071f397e0d185beec0fcadc53da47dd0b12d77"
},
"downloads": -1,
"filename": "glyphsynth-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "3169c1ebd36776e4b9fd6a44447bdac2",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.12",
"size": 25483,
"upload_time": "2024-10-28T04:18:46",
"upload_time_iso_8601": "2024-10-28T04:18:46.264053Z",
"url": "https://files.pythonhosted.org/packages/4c/98/fac0e9652eaa80b62fee8be26cff63730e9b52403eb7892ac044211dfc09/glyphsynth-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "68dc8b8347a1f760fa6b063a35118ee10578a11c5834748b2a69f5631ab8e788",
"md5": "b519cc1c2d75f9370a36b645742faec7",
"sha256": "f5dd840c2348aae313a22ddee03d6d0016bb8cd3f91a06202587e487859c3504"
},
"downloads": -1,
"filename": "glyphsynth-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "b519cc1c2d75f9370a36b645742faec7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.12",
"size": 22874,
"upload_time": "2024-10-28T04:18:48",
"upload_time_iso_8601": "2024-10-28T04:18:48.311416Z",
"url": "https://files.pythonhosted.org/packages/68/dc/8b8347a1f760fa6b063a35118ee10578a11c5834748b2a69f5631ab8e788/glyphsynth-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-28 04:18:48",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "glyphsynth"
}