Skip to main content

Pythonic vector glyph synthesis toolkit

Project description

GlyphSynth: Pythonic vector glyph synthesis toolkit

Python versions PyPI Code style: 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:

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:

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

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 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

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

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

Variants matrix

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

glyphsynth-0.1.2.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

glyphsynth-0.1.2-py3-none-any.whl (25.5 kB view details)

Uploaded Python 3

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

Hashes for glyphsynth-0.1.2.tar.gz
Algorithm Hash digest
SHA256 f5dd840c2348aae313a22ddee03d6d0016bb8cd3f91a06202587e487859c3504
MD5 b519cc1c2d75f9370a36b645742faec7
BLAKE2b-256 68dc8b8347a1f760fa6b063a35118ee10578a11c5834748b2a69f5631ab8e788

See more details on using hashes here.

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

Hashes for glyphsynth-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0bdb8648e46188a15765677b12071f397e0d185beec0fcadc53da47dd0b12d77
MD5 3169c1ebd36776e4b9fd6a44447bdac2
BLAKE2b-256 4c98fac0e9652eaa80b62fee8be26cff63730e9b52403eb7892ac044211dfc09

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page