Name | glyphsynth JSON |
Version |
0.8.0
JSON |
| download |
home_page | https://github.com/mm21/glyphsynth |
Summary | Pythonic vector graphics synthesis toolkit |
upload_time | 2025-05-24 23:39:00 |
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.
|
<p align="center">
<img src="./assets/logo.svg" alt="Logo" />
</p>
# GlyphSynth
Pythonic vector graphics synthesis toolkit
[](https://pypi.org/project/glyphsynth)
[](https://pypi.org/project/glyphsynth)
[]()
[]()
[](https://github.com/psf/black)
- [GlyphSynth](#glyphsynth)
- [Motivation](#motivation)
- [Getting started](#getting-started)
- [Drawing interface](#drawing-interface)
- [Exporting](#exporting)
- [Programmatically](#programmatically)
- [CLI](#cli)
- [Examples](#examples)
- [Glyphs](#glyphs)
- [Runic alphabet](#runic-alphabet)
- [GlyphSynth logo](#glyphsynth-logo)
- [Letter combination variants](#letter-combination-variants)
- [Drawings](#drawings)
- [Sunset gradients](#sunset-gradients)
- [Multi-square](#multi-square)
- [Multi-square fractal](#multi-square-fractal)
## Motivation
This project provides a Pythonic mechanism to construct SVG drawings. Drawings can be parameterized and leverage composition and inheritance to promote reuse. The ability to construct many variations of drawings programmatically can be a powerful tool for creativity.
This project's goal is to specialize in the creation of glyphs — symbols conveying some meaning. The unique Pythonic approach can be ideal for anything from logos to artwork.
Nonetheless it evolved to become a more general-purpose vector graphics framework, essentially providing a layer of abstraction on top of `svgwrite`. The underlying graphics synthesis capability is planned to be split off into a separate project, with GlyphSynth continuing to offer a more specialized interface for glyphs specifically.
## Getting started
First, install using pip:
```bash
pip install glyphsynth
```
The user is intended to develop graphics using their own Python modules. A typical workflow might be to create a number of `BaseDrawing` subclasses, set them in `__all__`, and invoke `glyphsynth-export` passing in the module and output path. See below for more details.
## Drawing interface
The drawing interface largely borrows the structure and terminology of `svgwrite`, with some enhancements along with type safety. The top-level graphics element is therefore the "drawing". Drawings can be constructed in two ways, or a combination of both:
- Subclass `BaseDrawing` and implement `draw()`
- Parameterize with a subclass of `BaseParams` corresponding to the `BaseDrawing` subclass
- Create an instance of `Drawing` (or any other `BaseDrawing` subclass) and invoke draw APIs
In its `draw()` method, a `BaseDrawing` subclass can invoke drawing APIs which create corresponding SVG objects. SVG properties are automatically propagated to SVG objects from the drawing's properties, `BaseDrawing.properties`, which can be provided upon creation with defaults specified by the subclass.
A simple example of implementing `draw()` to draw a blue square:
<p align="center">
<img src="./assets/examples/blue-square.png" alt="Blue square" />
</p>
```python
from glyphsynth import BaseDrawing, BaseParams, ShapeProperties
# drawing params
class MySquareParams(BaseParams):
color: str
# drawing subclass
class MySquareDrawing(BaseDrawing[MySquareParams]):
# canonical size for drawing construction, can be rescaled upon creation
canonical_size = (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),
properties=ShapeProperties(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),
],
properties=ShapeProperties(
stroke="black",
fill="none",
stroke_width="5",
),
)
# create drawing instance
blue_square = MySquareDrawing(
drawing_id="blue-square", params=MySquareParams(color="blue")
)
# render as image
blue_square.export_png(Path("my-drawings"))
```
Equivalently, the same drawing can be constructed from a `Drawing`:
```python
from glyphsynth import Drawing
blue_square = Drawing(drawing_id="blue-square", size=(100, 100))
# draw a centered square
blue_square.draw_rect(
(25.0, 25.0), (50.0, 50.0), properties=ShapeProperties(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)],
properties=ShapeProperties(
stroke="black",
fill="none",
stroke_width="5",
),
)
```
## Exporting
A drawing is primarily 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
```
### Programmatically
A drawing can be exported using `BaseDrawing.export()`, `BaseDrawing.export_svg()`, or `BaseDrawing.export_png()`. If a folder is passed as the output path, the drawing's `drawing_id` will be used to derive the filename.
```python
from pathlib import Path
my_drawings = Path("my-drawings")
# export to specific file, format auto-detected
blue_square.export(my_drawings / "blue-square.svg")
blue_square.export(my_drawings / "blue-square.png")
# export to folder using drawing_id as filename
blue_square.export_svg(my_drawings) # blue-square.svg
blue_square.export_png(my_drawings) # blue-square.png
```
### CLI
The CLI tool `glyphsynth-export` exports drawings 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__`
- `BaseDrawing` subclass
- `BaseDrawing` instance
- Iterable
- Callable
Any `BaseDrawing` subclasses found will be instantiated using their respective default parameters. For `Iterable` and `Callable`, the object is traversed or invoked recursively until drawing subclasses or instances are found.
Assuming the above code containing the `blue_square` is placed in `my_drawings.py`, the drawing can be exported to `my-drawings/` via the following command:
`glyphsynth-export my_drawings.blue_square my-drawings --svg --png`
## Examples
### Glyphs
#### Runic alphabet
As part of `glyphsynth.lib`, an alphabet of rune-style glyphs is provided. These are designed to be overlayed and form geometric shapes.
<p align="center">
<img src="./assets/examples/runic-alphabet.svg" alt="Runic letter matrix" />
</p>
```python
from glyphsynth import MatrixDrawing
from glyphsynth.lib.alphabets.latin.runic import (
LETTER_CLASSES,
BaseRunicGlyph,
)
# instantiate letters and split into 2 rows
rows: list[list[BaseRunicGlyph]] = [
[letter_cls() for letter_cls in LETTER_CLASSES[:13]],
[letter_cls() for letter_cls in LETTER_CLASSES[13:]],
]
# create matrix of letters
matrix = MatrixDrawing.new(rows, drawing_id="runic-alphabet", spacing=10)
```
#### GlyphSynth logo
This project's logo is formed by combining the runic glyphs `G` and `S`:
<p align="center">
<img src="./assets/examples/glyphsynth-logo.svg" alt="Project logo" />
</p>
```python
from glyphsynth import Glyph
class GlyphSynthLogo(Glyph):
def draw(self):
self.draw_glyph(G)
self.draw_glyph(S, scale=0.5)
glyphsynth_logo = GlyphSynthLogo(drawing_id="glyphsynth-logo")
```
Note the `S` glyph is scaled by one half, remaining centered in the parent glyph. While its size is reduced, its stroke width is increased accordingly to match the parent glyph.
#### Letter combination variants
This illustrates the use of runic letter glyphs to create parameterized geometric designs. Combinations of pairs of letters `A`, `M`, and `Y` are selected for a range of stroke widths, with the second letter being rotated 180 degrees.
<p align="center">
<img src="./assets/examples/letter-combination-variants.png" alt="Letter variant matrix" width="300" />
</p>
```python
from glyphsynth.glyph import UNIT, BaseGlyph, GlyphParams
from glyphsynth.lib.alphabets.latin.runic import A, M, Y
# letters to combine
LETTERS = [
A,
M,
Y,
]
# stroke widths (in percents) to iterate over
STROKE_PCTS = [2.5, 5, 7.5]
class LetterComboParams(GlyphParams):
letter1: type[BaseGlyph]
letter2: type[BaseGlyph]
class LetterComboGlyph(BaseGlyph[LetterComboParams]):
def draw(self):
# draw letters given by params, rotating letter2
self.draw_glyph(self.params.letter1)
self.draw_glyph(self.params.letter2).rotate(180)
```
A subclass of `BaseVariantFactory` can be used as a convenience for generating variants:
```python
import itertools
from typing import Generator
from glyphsynth.lib.variants import BaseVariantFactory
# factory to produce variants of LetterComboGlyph with different params
class LetterVariantFactory(BaseVariantFactory[LetterComboGlyph]):
MATRIX_WIDTH = len(STROKE_PCTS)
SPACING = UNIT / 10
# generate variants of stroke widths and letter combinations
def get_params_variants(
self,
) -> Generator[LetterComboParams, None, None]:
for letter1, letter2, stroke_pct in itertools.product(
LETTERS, LETTERS, STROKE_PCTS
):
yield LetterComboParams(
stroke_pct=stroke_pct,
letter1=letter1,
letter2=letter2,
)
```
The fully-qualified class name of `LetterVariantFactory` 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.
### Drawings
The following examples illustrate the use of the generic drawing capability developed for this project.
#### Sunset gradients
This illustrates the use of gradients and drawing composition to create a simple ocean sunset scene.
<p align="center">
<img src="./assets/examples/sunset-gradients.png" alt="Sunset gradients" />
</p>
```python
from glyphsynth import BaseDrawing, BaseParams, StopColor
WIDTH = 800
HEIGHT = 600
class BackgroundParams(BaseParams):
sky_colors: list[str]
water_colors: list[str]
class BackgroundDrawing(BaseDrawing[BackgroundParams]):
canonical_size = (WIDTH, HEIGHT)
def draw(self):
sky_insert, sky_size = (0.0, 0.0), (self.width, self.center_y)
water_insert, water_size = (0.0, self.center_y), (
self.width,
self.center_y,
)
# draw sky
self.draw_rect(sky_insert, sky_size).fill(
gradient=self.create_linear_gradient(
start=(self.center_x, 0),
end=(self.center_x, self.center_y),
colors=self.params.sky_colors,
)
)
# draw water
self.draw_rect(water_insert, water_size).fill(
gradient=self.create_linear_gradient(
start=(self.center_x, self.center_y),
end=(self.center_x, self.height),
colors=self.params.water_colors,
)
)
class SunParams(BaseParams):
colors: list[StopColor]
focal_scale: float
class SunDrawing(BaseDrawing[SunParams]):
canonical_size = (WIDTH, HEIGHT / 2)
def draw(self):
insert, size = (0.0, 0.0), (self.width, self.height)
self.draw_rect(insert, size).fill(
gradient=self.create_radial_gradient(
center=(self.center_x, self.height),
radius=self.center_x,
focal=(
self.center_x,
self.height * self.params.focal_scale,
),
colors=self.params.colors,
)
)
class SceneParams(BaseParams):
background_params: BackgroundParams
sun_params: SunParams
class SunsetDrawing(BaseDrawing[SceneParams]):
canonical_size = (WIDTH, HEIGHT)
def draw(self):
# background
self.insert_drawing(
BackgroundDrawing(params=self.params.background_params),
insert=(0, 0),
)
# sunset
self.insert_drawing(
SunDrawing(params=self.params.sun_params),
insert=(0, 0),
)
# sunset reflection
self.insert_drawing(
SunDrawing(params=self.params.sun_params)
.rotate(180)
.fill(opacity_pct=50.0),
insert=(0, self.center_y),
)
sunset = SunsetDrawing(
drawing_id="sunset-gradients",
params=SceneParams(
background_params=BackgroundParams(
sky_colors=["#1a2b4c", "#9b4e6c"],
water_colors=["#2d3d5e", "#0f1c38"],
),
sun_params=SunParams(
colors=[
StopColor("#ffd700", 0.0, 100.0),
StopColor("#ff7f50", 50.0, 90.0),
StopColor("#ff6b6b", 100.0, 25.0),
],
focal_scale=1.2,
),
),
)
```
#### Multi-square
This drawing is composed of 4 nested squares, each with a color parameter.
<p align="center">
<img src="./assets/examples/multi-square.png" alt="Multi-square" width="300" />
</p>
```python
from glyphsynth import BaseDrawing, BaseParams, ShapeProperties
# definitions
ZERO = 0.0
UNIT = 1000
HALF = 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 drawing class
class MultiSquareDrawing(BaseDrawing[MultiSquareParams]):
canonical_size = 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,
properties=ShapeProperties(fill=self.params.color_upper_left),
)
# draw upper right
self.draw_rect(
(HALF, ZERO),
size,
properties=ShapeProperties(fill=self.params.color_upper_right),
)
# draw lower left
self.draw_rect(
(ZERO, HALF),
size,
properties=ShapeProperties(fill=self.params.color_lower_left),
)
# draw lower right
self.draw_rect(
(HALF, HALF),
size,
properties=ShapeProperties(fill=self.params.color_lower_right),
)
# create parameters
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)",
)
# create drawing
multi_square = MultiSquareDrawing(
drawing_id="multi-square", params=multi_square_params
)
```
#### Multi-square fractal
This drawing nests a multi-square drawing recursively up to a certain depth.
<p align="center">
<img src="./assets/examples/multi-square-fractal.png" alt="Multi-square fractal" width="300" />
</p>
```python
# maximum recursion depth for creating fractal
FRACTAL_DEPTH = 10
class SquareFractalParams(BaseParams):
square_params: MultiSquareParams
depth: int = FRACTAL_DEPTH
class SquareFractalDrawing(BaseDrawing[SquareFractalParams]):
canonical_size = UNIT_SIZE
def draw(self):
# draw square
self.insert_drawing(
MultiSquareDrawing(params=self.params.square_params)
)
if self.params.depth > 1:
# draw another fractal drawing, half the size and rotated 90 degrees
child_params = SquareFractalParams(
square_params=self.params.square_params,
depth=self.params.depth - 1,
)
child_drawing = SquareFractalDrawing(
params=child_params, size=(HALF, HALF)
)
# rotate and insert in center
child_drawing.rotate(90.0)
self.insert_drawing(child_drawing, insert=(HALF / 2, HALF / 2))
multi_square_fractal = SquareFractalDrawing(
drawing_id="multi-square-fractal",
params=SquareFractalParams(square_params=multi_square_params),
)
```
Raw data
{
"_id": null,
"home_page": "https://github.com/mm21/glyphsynth",
"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/2e/81/c4a3256f46327ded998e6b1cf2da18cdab32629cb91fc6de0a994556b274/glyphsynth-0.8.0.tar.gz",
"platform": null,
"description": "<p align=\"center\">\n <img src=\"./assets/logo.svg\" alt=\"Logo\" />\n</p>\n\n# GlyphSynth\nPythonic vector graphics synthesis toolkit\n\n[](https://pypi.org/project/glyphsynth)\n[](https://pypi.org/project/glyphsynth)\n[]()\n[]()\n[](https://github.com/psf/black)\n\n- [GlyphSynth](#glyphsynth)\n - [Motivation](#motivation)\n - [Getting started](#getting-started)\n - [Drawing interface](#drawing-interface)\n - [Exporting](#exporting)\n - [Programmatically](#programmatically)\n - [CLI](#cli)\n - [Examples](#examples)\n - [Glyphs](#glyphs)\n - [Runic alphabet](#runic-alphabet)\n - [GlyphSynth logo](#glyphsynth-logo)\n - [Letter combination variants](#letter-combination-variants)\n - [Drawings](#drawings)\n - [Sunset gradients](#sunset-gradients)\n - [Multi-square](#multi-square)\n - [Multi-square fractal](#multi-square-fractal)\n\n\n## Motivation\n\nThis project provides a Pythonic mechanism to construct SVG drawings. Drawings can be parameterized and leverage composition and inheritance to promote reuse. The ability to construct many variations of drawings programmatically can be a powerful tool for creativity.\n\nThis project's goal is to specialize in the creation of glyphs — symbols conveying some meaning. The unique Pythonic approach can be ideal for anything from logos to artwork.\n\nNonetheless it evolved to become a more general-purpose vector graphics framework, essentially providing a layer of abstraction on top of `svgwrite`. The underlying graphics synthesis capability is planned to be split off into a separate project, with GlyphSynth continuing to offer a more specialized interface for glyphs specifically.\n\n## Getting started\n\nFirst, install using pip:\n\n```bash\npip install glyphsynth\n```\n\nThe user is intended to develop graphics using their own Python modules. A typical workflow might be to create a number of `BaseDrawing` subclasses, set them in `__all__`, and invoke `glyphsynth-export` passing in the module and output path. See below for more details.\n\n## Drawing interface\n\nThe drawing interface largely borrows the structure and terminology of `svgwrite`, with some enhancements along with type safety. The top-level graphics element is therefore the \"drawing\". Drawings can be constructed in two ways, or a combination of both:\n\n- Subclass `BaseDrawing` and implement `draw()`\n - Parameterize with a subclass of `BaseParams` corresponding to the `BaseDrawing` subclass\n- Create an instance of `Drawing` (or any other `BaseDrawing` subclass) and invoke draw APIs\n\nIn its `draw()` method, a `BaseDrawing` subclass can invoke drawing APIs which create corresponding SVG objects. SVG properties are automatically propagated to SVG objects from the drawing's properties, `BaseDrawing.properties`, which can be provided upon creation with defaults specified by the subclass.\n\nA simple example of implementing `draw()` to draw a blue square:\n\n<p align=\"center\">\n <img src=\"./assets/examples/blue-square.png\" alt=\"Blue square\" />\n</p>\n\n```python\nfrom glyphsynth import BaseDrawing, BaseParams, ShapeProperties\n\n# drawing params\nclass MySquareParams(BaseParams):\n color: str\n\n# drawing subclass\nclass MySquareDrawing(BaseDrawing[MySquareParams]):\n # canonical size for drawing construction, can be rescaled upon creation\n canonical_size = (100.0, 100.0)\n\n def draw(self):\n # draw a centered square using the provided color\n self.draw_rect(\n (25.0, 25.0),\n (50.0, 50.0),\n properties=ShapeProperties(fill=self.params.color),\n )\n\n # draw a black border around the perimeter\n self.draw_polyline(\n [\n (0.0, 0.0),\n (0.0, 100.0),\n (100.0, 100.0),\n (100.0, 0),\n (0.0, 0.0),\n ],\n properties=ShapeProperties(\n stroke=\"black\",\n fill=\"none\",\n stroke_width=\"5\",\n ),\n )\n\n# create drawing instance\nblue_square = MySquareDrawing(\n drawing_id=\"blue-square\", params=MySquareParams(color=\"blue\")\n)\n\n# render as image\nblue_square.export_png(Path(\"my-drawings\"))\n```\n\nEquivalently, the same drawing can be constructed from a `Drawing`:\n\n```python\nfrom glyphsynth import Drawing\n\nblue_square = Drawing(drawing_id=\"blue-square\", size=(100, 100))\n\n# draw a centered square\nblue_square.draw_rect(\n (25.0, 25.0), (50.0, 50.0), properties=ShapeProperties(fill=\"blue\")\n)\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 properties=ShapeProperties(\n stroke=\"black\",\n fill=\"none\",\n stroke_width=\"5\",\n ),\n)\n```\n\n## Exporting\n\nA drawing is primarily 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\n### Programmatically\n\nA drawing can be exported using `BaseDrawing.export()`, `BaseDrawing.export_svg()`, or `BaseDrawing.export_png()`. If a folder is passed as the output path, the drawing's `drawing_id` will be used to derive the filename.\n\n```python\nfrom pathlib import Path\n\nmy_drawings = Path(\"my-drawings\")\n\n# export to specific file, format auto-detected\nblue_square.export(my_drawings / \"blue-square.svg\")\nblue_square.export(my_drawings / \"blue-square.png\")\n\n# export to folder using drawing_id as filename\nblue_square.export_svg(my_drawings) # blue-square.svg\nblue_square.export_png(my_drawings) # blue-square.png\n```\n\n### CLI\n\nThe CLI tool `glyphsynth-export` exports drawings 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- `BaseDrawing` subclass\n- `BaseDrawing` instance\n- Iterable\n- Callable\n\nAny `BaseDrawing` subclasses found will be instantiated using their respective default parameters. For `Iterable` and `Callable`, the object is traversed or invoked recursively until drawing subclasses or instances are found.\n\nAssuming the above code containing the `blue_square` is placed in `my_drawings.py`, the drawing can be exported to `my-drawings/` via the following command:\n\n`glyphsynth-export my_drawings.blue_square my-drawings --svg --png`\n\n## Examples\n\n### Glyphs\n\n#### Runic alphabet\n\nAs part of `glyphsynth.lib`, an alphabet of rune-style glyphs is provided. These are designed to be overlayed and form geometric shapes.\n\n<p align=\"center\">\n <img src=\"./assets/examples/runic-alphabet.svg\" alt=\"Runic letter matrix\" />\n</p>\n\n```python\nfrom glyphsynth import MatrixDrawing\nfrom glyphsynth.lib.alphabets.latin.runic import (\n LETTER_CLASSES,\n BaseRunicGlyph,\n)\n\n# instantiate letters and split into 2 rows\nrows: list[list[BaseRunicGlyph]] = [\n [letter_cls() for letter_cls in LETTER_CLASSES[:13]],\n [letter_cls() for letter_cls in LETTER_CLASSES[13:]],\n]\n\n# create matrix of letters\nmatrix = MatrixDrawing.new(rows, drawing_id=\"runic-alphabet\", spacing=10)\n```\n\n#### GlyphSynth logo\n\nThis project's logo is formed by combining the runic glyphs `G` and `S`:\n\n<p align=\"center\">\n <img src=\"./assets/examples/glyphsynth-logo.svg\" alt=\"Project logo\" />\n</p>\n\n```python\nfrom glyphsynth import Glyph\n\nclass GlyphSynthLogo(Glyph):\n def draw(self):\n self.draw_glyph(G)\n self.draw_glyph(S, scale=0.5)\n\nglyphsynth_logo = GlyphSynthLogo(drawing_id=\"glyphsynth-logo\")\n```\n\nNote the `S` glyph is scaled by one half, remaining centered in the parent glyph. While its size is reduced, its stroke width is increased accordingly to match the parent glyph.\n\n#### Letter combination variants\n\nThis illustrates the use of runic letter glyphs to create parameterized geometric designs. Combinations of pairs of letters `A`, `M`, and `Y` are selected for a range of stroke widths, with the second letter being rotated 180 degrees.\n\n<p align=\"center\">\n <img src=\"./assets/examples/letter-combination-variants.png\" alt=\"Letter variant matrix\" width=\"300\" />\n</p>\n\n```python\nfrom glyphsynth.glyph import UNIT, BaseGlyph, GlyphParams\nfrom glyphsynth.lib.alphabets.latin.runic import A, M, Y\n\n# letters to combine\nLETTERS = [\n A,\n M,\n Y,\n]\n\n# stroke widths (in percents) to iterate over\nSTROKE_PCTS = [2.5, 5, 7.5]\n\nclass LetterComboParams(GlyphParams):\n letter1: type[BaseGlyph]\n letter2: type[BaseGlyph]\n\nclass LetterComboGlyph(BaseGlyph[LetterComboParams]):\n def draw(self):\n # draw letters given by params, rotating letter2\n self.draw_glyph(self.params.letter1)\n self.draw_glyph(self.params.letter2).rotate(180)\n```\n\nA subclass of `BaseVariantFactory` can be used as a convenience for generating variants:\n\n```python\nimport itertools\nfrom typing import Generator\n\nfrom glyphsynth.lib.variants import BaseVariantFactory\n\n# factory to produce variants of LetterComboGlyph with different params\nclass LetterVariantFactory(BaseVariantFactory[LetterComboGlyph]):\n MATRIX_WIDTH = len(STROKE_PCTS)\n SPACING = UNIT / 10\n\n # generate variants of stroke widths and letter combinations\n def get_params_variants(\n self,\n ) -> Generator[LetterComboParams, None, None]:\n for letter1, letter2, stroke_pct in itertools.product(\n LETTERS, LETTERS, STROKE_PCTS\n ):\n yield LetterComboParams(\n stroke_pct=stroke_pct,\n letter1=letter1,\n letter2=letter2,\n )\n```\n\nThe fully-qualified class name of `LetterVariantFactory` 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\n### Drawings\n\nThe following examples illustrate the use of the generic drawing capability developed for this project.\n\n#### Sunset gradients\n\nThis illustrates the use of gradients and drawing composition to create a simple ocean sunset scene.\n\n<p align=\"center\">\n <img src=\"./assets/examples/sunset-gradients.png\" alt=\"Sunset gradients\" />\n</p>\n\n```python\nfrom glyphsynth import BaseDrawing, BaseParams, StopColor\n\nWIDTH = 800\nHEIGHT = 600\n\nclass BackgroundParams(BaseParams):\n sky_colors: list[str]\n water_colors: list[str]\n\nclass BackgroundDrawing(BaseDrawing[BackgroundParams]):\n canonical_size = (WIDTH, HEIGHT)\n\n def draw(self):\n sky_insert, sky_size = (0.0, 0.0), (self.width, self.center_y)\n water_insert, water_size = (0.0, self.center_y), (\n self.width,\n self.center_y,\n )\n\n # draw sky\n self.draw_rect(sky_insert, sky_size).fill(\n gradient=self.create_linear_gradient(\n start=(self.center_x, 0),\n end=(self.center_x, self.center_y),\n colors=self.params.sky_colors,\n )\n )\n\n # draw water\n self.draw_rect(water_insert, water_size).fill(\n gradient=self.create_linear_gradient(\n start=(self.center_x, self.center_y),\n end=(self.center_x, self.height),\n colors=self.params.water_colors,\n )\n )\n\nclass SunParams(BaseParams):\n colors: list[StopColor]\n focal_scale: float\n\nclass SunDrawing(BaseDrawing[SunParams]):\n canonical_size = (WIDTH, HEIGHT / 2)\n\n def draw(self):\n insert, size = (0.0, 0.0), (self.width, self.height)\n\n self.draw_rect(insert, size).fill(\n gradient=self.create_radial_gradient(\n center=(self.center_x, self.height),\n radius=self.center_x,\n focal=(\n self.center_x,\n self.height * self.params.focal_scale,\n ),\n colors=self.params.colors,\n )\n )\n\nclass SceneParams(BaseParams):\n background_params: BackgroundParams\n sun_params: SunParams\n\nclass SunsetDrawing(BaseDrawing[SceneParams]):\n canonical_size = (WIDTH, HEIGHT)\n\n def draw(self):\n # background\n self.insert_drawing(\n BackgroundDrawing(params=self.params.background_params),\n insert=(0, 0),\n )\n\n # sunset\n self.insert_drawing(\n SunDrawing(params=self.params.sun_params),\n insert=(0, 0),\n )\n\n # sunset reflection\n self.insert_drawing(\n SunDrawing(params=self.params.sun_params)\n .rotate(180)\n .fill(opacity_pct=50.0),\n insert=(0, self.center_y),\n )\n\nsunset = SunsetDrawing(\n drawing_id=\"sunset-gradients\",\n params=SceneParams(\n background_params=BackgroundParams(\n sky_colors=[\"#1a2b4c\", \"#9b4e6c\"],\n water_colors=[\"#2d3d5e\", \"#0f1c38\"],\n ),\n sun_params=SunParams(\n colors=[\n StopColor(\"#ffd700\", 0.0, 100.0),\n StopColor(\"#ff7f50\", 50.0, 90.0),\n StopColor(\"#ff6b6b\", 100.0, 25.0),\n ],\n focal_scale=1.2,\n ),\n ),\n)\n```\n\n#### Multi-square\n\nThis drawing is composed of 4 nested squares, each with a color parameter.\n\n<p align=\"center\">\n <img src=\"./assets/examples/multi-square.png\" alt=\"Multi-square\" width=\"300\" />\n</p>\n\n```python\nfrom glyphsynth import BaseDrawing, BaseParams, ShapeProperties\n\n# definitions\nZERO = 0.0\nUNIT = 1000\nHALF = 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 drawing class\nclass MultiSquareDrawing(BaseDrawing[MultiSquareParams]):\n canonical_size = UNIT_SIZE\n\n def draw(self):\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(\n ORIGIN,\n size,\n properties=ShapeProperties(fill=self.params.color_upper_left),\n )\n\n # draw upper right\n self.draw_rect(\n (HALF, ZERO),\n size,\n properties=ShapeProperties(fill=self.params.color_upper_right),\n )\n\n # draw lower left\n self.draw_rect(\n (ZERO, HALF),\n size,\n properties=ShapeProperties(fill=self.params.color_lower_left),\n )\n\n # draw lower right\n self.draw_rect(\n (HALF, HALF),\n size,\n properties=ShapeProperties(fill=self.params.color_lower_right),\n )\n\n# create parameters\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\n# create drawing\nmulti_square = MultiSquareDrawing(\n drawing_id=\"multi-square\", params=multi_square_params\n)\n```\n\n#### Multi-square fractal\n\nThis drawing nests a multi-square drawing recursively up to a certain depth.\n\n<p align=\"center\">\n <img src=\"./assets/examples/multi-square-fractal.png\" alt=\"Multi-square fractal\" width=\"300\" />\n</p>\n\n```python\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 SquareFractalDrawing(BaseDrawing[SquareFractalParams]):\n canonical_size = UNIT_SIZE\n\n def draw(self):\n # draw square\n self.insert_drawing(\n MultiSquareDrawing(params=self.params.square_params)\n )\n\n if self.params.depth > 1:\n # draw another fractal drawing, 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_drawing = SquareFractalDrawing(\n params=child_params, size=(HALF, HALF)\n )\n\n # rotate and insert in center\n child_drawing.rotate(90.0)\n self.insert_drawing(child_drawing, insert=(HALF / 2, HALF / 2))\n\nmulti_square_fractal = SquareFractalDrawing(\n drawing_id=\"multi-square-fractal\",\n params=SquareFractalParams(square_params=multi_square_params),\n)\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Pythonic vector graphics synthesis toolkit",
"version": "0.8.0",
"project_urls": {
"Homepage": "https://github.com/mm21/glyphsynth"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d364cc53db97dd2f87237e3f14d60d0a88a6f2187349feab6e57e3e8aa5f8adf",
"md5": "27aac66ce18c49cea27680de01292cae",
"sha256": "67a3b477f55dd09610b4d232f9a4aad5a0e96f4ce0633c52e38eebaa8653dc70"
},
"downloads": -1,
"filename": "glyphsynth-0.8.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "27aac66ce18c49cea27680de01292cae",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.12",
"size": 37651,
"upload_time": "2025-05-24T23:38:58",
"upload_time_iso_8601": "2025-05-24T23:38:58.622970Z",
"url": "https://files.pythonhosted.org/packages/d3/64/cc53db97dd2f87237e3f14d60d0a88a6f2187349feab6e57e3e8aa5f8adf/glyphsynth-0.8.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "2e81c4a3256f46327ded998e6b1cf2da18cdab32629cb91fc6de0a994556b274",
"md5": "c0621de8e1966955e679d71715e0d3bf",
"sha256": "e622cdcb934f17596963129281fc08f8e5117dadc087efc14c2f02572bb23217"
},
"downloads": -1,
"filename": "glyphsynth-0.8.0.tar.gz",
"has_sig": false,
"md5_digest": "c0621de8e1966955e679d71715e0d3bf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.12",
"size": 31657,
"upload_time": "2025-05-24T23:39:00",
"upload_time_iso_8601": "2025-05-24T23:39:00.129837Z",
"url": "https://files.pythonhosted.org/packages/2e/81/c4a3256f46327ded998e6b1cf2da18cdab32629cb91fc6de0a994556b274/glyphsynth-0.8.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-05-24 23:39:00",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "mm21",
"github_project": "glyphsynth",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "glyphsynth"
}