Pythonic vector glyph synthesis toolkit
Project description
GlyphSynth: Pythonic vector glyph synthesis toolkit
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:
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 implementdraw()
- Parameterize with a subclass of
BaseParams
corresponding to the glyph
- Parameterize with a subclass of
- Create an instance of
EmptyGlyph
(or any otherBaseGlyph
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:
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:
Equivalently, the same glyph can be constructed from an EmptyGlyph
:
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:
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.
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
subclassBaseGlyph
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
This glyph is composed of 4 nested squares, each with a color parameter.
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
This glyph nests a square glyph recursively up to a certain depth.
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
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.
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:
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file glyphsynth-0.1.2.tar.gz
.
File metadata
- Download URL: glyphsynth-0.1.2.tar.gz
- Upload date:
- Size: 22.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.12.6 Linux/5.15.0-119-generic
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f5dd840c2348aae313a22ddee03d6d0016bb8cd3f91a06202587e487859c3504 |
|
MD5 | b519cc1c2d75f9370a36b645742faec7 |
|
BLAKE2b-256 | 68dc8b8347a1f760fa6b063a35118ee10578a11c5834748b2a69f5631ab8e788 |
Provenance
File details
Details for the file glyphsynth-0.1.2-py3-none-any.whl
.
File metadata
- Download URL: glyphsynth-0.1.2-py3-none-any.whl
- Upload date:
- Size: 25.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.12.6 Linux/5.15.0-119-generic
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0bdb8648e46188a15765677b12071f397e0d185beec0fcadc53da47dd0b12d77 |
|
MD5 | 3169c1ebd36776e4b9fd6a44447bdac2 |
|
BLAKE2b-256 | 4c98fac0e9652eaa80b62fee8be26cff63730e9b52403eb7892ac044211dfc09 |