Text & Typography¶
Text in PyFreeform supports font families, bold/italic, rotation, and text-along-path warping.
Basic Text¶
Font Families¶
Bold & Italic¶
cell.add_text("Art", font_size=0.25, color=colors.primary, bold=True)
cell.add_text("Art", font_size=0.25, color=colors.primary, italic=True)
cell.add_text("Art", font_size=0.25, color=colors.primary, bold=True, italic=True)
ASCII Art from Images¶
Map brightness to characters for a classic ASCII art effect:
chars = " .:-=+*#%@"
scene = Scene.from_image("MonaLisa.jpg", grid_size=40, cell_size=10)
for cell in scene.grid:
idx = int(cell.brightness * (len(chars) - 1))
char = chars[idx]
if char != " ":
cell.add_text(char, at="center", font_size=0.90, color=cell.color, font_family="monospace")
Rotating Text¶
for cell in scene.grid:
nx, ny = cell.normalized_position
rotation = (nx + ny) * 90
cell.add_text("AB", at="center", font_size=0.40, color=colors.primary, rotation=rotation)
Text Along Paths¶
Use along= with a path to warp text along its shape:
Along a Curve¶
curve = cell.add_curve(start=(0.05, 0.7), end=(0.95, 0.3), curvature=0.5, ...)
cell.add_text("Text warps along curves beautifully", along=curve, font_size=0.05, color=colors.accent)
Along an Ellipse¶
ellipse = cell.add_ellipse(at="center", rx=0.4, ry=0.25, fill="none", stroke=colors.line)
cell.add_text("Text flows around an ellipse path", along=ellipse, font_size=0.05, color=colors.primary)
How it works
When you pass along= without t, PyFreeform uses SVG <textPath> to warp text. The font size auto-adjusts to fill the path length.
Fitting Text to Cells¶
font_size=0.25 sets the em-height to 25% of cell height, but says nothing about width. Long strings can overflow horizontally. Use fit=True to prevent this:
cell.add_text("Short", font_size=0.25, color="white", fit=True) # keeps 0.25
cell.add_text("A much longer label", font_size=0.25, color="white", fit=True) # shrinks to fit width
fit=True is a containment guard — it only shrinks the font when the text would exceed the cell width. It never upsizes. font_size acts as the ceiling.
When to use fit=True
Any time you display variable-length text (labels, filenames, data values) where overflow would look wrong. Single-character art and path text don't need it.
fit_to_cell() for Text¶
If you want text to fill a cell (scaling up or down), use fit_to_cell() after creation:
t = cell.add_text("TITLE", at="center", font_size=0.25, color="white", bold=True)
t.fit_to_cell(0.8) # scale to 80% of cell — may grow or shrink
This is the Text equivalent of EntityGroup.fit_to_cell().
Title Overlays¶
Merge cells and overlay text on an artwork:
scene = Scene.from_image("FrankMonster.png", grid_size=30, cell_size=12)
for cell in scene.grid:
cell.add_dot(radius=cell.brightness * 0.42, color=cell.color, opacity=0.7)
# Merge bottom rows for a title bar
title = scene.grid.merge((scene.grid.rows - 3, 0), (scene.grid.rows - 1, scene.grid.cols - 1))
title.add_fill(color="#000000", opacity=0.5)
title.add_text("FRANK", at="center", font_size=0.55, color="#ffffff", bold=True)
What's Next?¶
Master transforms, fitting, connections, and layout: