CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-fonttools

Tools to manipulate font files

Pending
Overview
Eval results
Files

drawing-pens.mddocs/

Drawing and Pens

Standardized drawing interface for glyph construction, manipulation, and rendering with specialized pen implementations for different use cases. The pen protocol provides a consistent API for drawing vector graphics that can be rendered to various outputs.

Capabilities

Base Pen Classes

Core pen protocol and base implementations that define the standard drawing interface.

class AbstractPen:
    """Abstract base class defining the pen protocol."""
    
    def moveTo(self, pt):
        """
        Move to point without drawing.

        Parameters:
        - pt: Tuple[float, float], (x, y) coordinates
        """

    def lineTo(self, pt):
        """
        Draw line from current point to specified point.

        Parameters:
        - pt: Tuple[float, float], (x, y) coordinates
        """

    def curveTo(self, *points):
        """
        Draw cubic Bezier curve.

        Parameters:
        - points: Tuple[float, float], sequence of control points and end point
                 Last point is curve endpoint, others are control points
        """

    def qCurveTo(self, *points):
        """
        Draw quadratic Bezier curve(s).

        Parameters:
        - points: Tuple[float, float], sequence of control points and optional end point
                 If no end point given, curves to next oncurve point
        """

    def closePath(self):
        """Close current path with line back to start point."""

    def endPath(self):
        """End current path without closing."""

    def addComponent(self, glyphName, transformation):
        """
        Add component reference to another glyph.

        Parameters:
        - glyphName: str, name of referenced glyph
        - transformation: Transform, transformation matrix
        """

class BasePen(AbstractPen):
    def __init__(self, glyphSet=None):
        """
        Base pen implementation with validation.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component validation
        """
        
    def moveTo(self, pt):
        """Move to point with validation."""
        
    def lineTo(self, pt):
        """Draw line with validation."""
        
    def curveTo(self, *points):
        """Draw cubic curve with validation."""
        
    def qCurveTo(self, *points):
        """Draw quadratic curve with validation."""

class NullPen(AbstractPen):
    """No-operation pen for measurement and testing."""
    
    def moveTo(self, pt): pass
    def lineTo(self, pt): pass  
    def curveTo(self, *points): pass
    def qCurveTo(self, *points): pass
    def closePath(self): pass
    def endPath(self): pass

Basic Pen Usage

from fontTools.pens.basePen import BasePen

class DebugPen(BasePen):
    """Custom pen that prints drawing operations."""
    
    def __init__(self):
        super().__init__()
        self.operations = []
    
    def moveTo(self, pt):
        self.operations.append(f"moveTo{pt}")
        print(f"Move to {pt}")
    
    def lineTo(self, pt):
        self.operations.append(f"lineTo{pt}")
        print(f"Line to {pt}")
    
    def curveTo(self, *points):
        self.operations.append(f"curveTo{points}")
        print(f"Curve to {points}")
    
    def closePath(self):
        self.operations.append("closePath")
        print("Close path")

# Use the pen
pen = DebugPen()
pen.moveTo((100, 100))
pen.lineTo((200, 100))
pen.lineTo((200, 200))
pen.lineTo((100, 200))
pen.closePath()

TrueType Glyph Creation

Pens for creating TrueType glyph data with proper curve conversion and hinting.

class TTGlyphPen(BasePen):
    def __init__(self, glyphSet):
        """
        Pen for creating TrueType glyph objects.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component resolution
        """

    def glyph(self):
        """
        Get the constructed glyph object.

        Returns:
        Glyph: TrueType glyph with quadratic curves
        """

class T2CharStringPen(BasePen):
    def __init__(self, width, glyphSet):
        """
        Pen for creating CFF CharString objects.

        Parameters:
        - width: int, glyph advance width
        - glyphSet: GlyphSet, glyph set for component resolution
        """

    def getCharString(self):
        """
        Get the constructed CharString.

        Returns:
        CharString: CFF CharString with cubic curves
        """

Creating Glyphs with Pens

from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen

# Create TrueType glyph (letter A)
tt_pen = TTGlyphPen(None)

# Draw letter A outline
tt_pen.moveTo((200, 0))      # Bottom left
tt_pen.lineTo((100, 700))    # Top left
tt_pen.lineTo((300, 700))    # Top right  
tt_pen.lineTo((400, 0))      # Bottom right
tt_pen.closePath()

# Add crossbar
tt_pen.moveTo((175, 350))
tt_pen.lineTo((325, 350))
tt_pen.lineTo((325, 400))
tt_pen.lineTo((175, 400))
tt_pen.closePath()

# Get the glyph
glyph_a = tt_pen.glyph()

# Create CFF CharString (letter O)
cff_pen = T2CharStringPen(500, None)

# Draw letter O outline (with curves)
cff_pen.moveTo((250, 0))
cff_pen.curveTo((100, 0), (50, 100), (50, 350))    # Left curve
cff_pen.curveTo((50, 600), (100, 700), (250, 700)) # Top curve
cff_pen.curveTo((400, 700), (450, 600), (450, 350)) # Right curve
cff_pen.curveTo((450, 100), (400, 0), (250, 0))    # Bottom curve
cff_pen.closePath()

# Inner counter
cff_pen.moveTo((250, 100))
cff_pen.curveTo((350, 100), (350, 150), (350, 350))
cff_pen.curveTo((350, 550), (350, 600), (250, 600))
cff_pen.curveTo((150, 600), (150, 550), (150, 350))
cff_pen.curveTo((150, 150), (150, 100), (250, 100))
cff_pen.closePath()

char_string_o = cff_pen.getCharString()

Analysis and Measurement Pens

Pens for analyzing glyph properties without rendering.

class BoundsPen(BasePen):
    def __init__(self, glyphSet):
        """
        Calculate glyph bounding box.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component resolution
        """

    @property
    def bounds(self):
        """
        Get calculated bounds.

        Returns:
        Tuple[float, float, float, float]: (xMin, yMin, xMax, yMax) or None
        """

class AreaPen(BasePen):
    def __init__(self, glyphSet=None):
        """
        Calculate glyph area.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component resolution
        """

    @property  
    def area(self):
        """
        Get calculated area.

        Returns:
        float: Glyph area (positive for clockwise, negative for counter-clockwise)
        """

class StatisticsPen(BasePen):
    def __init__(self, glyphSet=None):
        """
        Gather glyph statistics.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component resolution
        """

    @property
    def area(self):
        """Get glyph area."""

    @property
    def length(self):
        """Get total path length."""

    @property
    def moments(self):
        """Get statistical moments."""

Using Analysis Pens

from fontTools.pens.boundsPen import BoundsPen
from fontTools.pens.areaPen import AreaPen
from fontTools.pens.statisticsPen import StatisticsPen
from fontTools.ttLib import TTFont

# Load font and get glyph
font = TTFont("font.ttf")
glyph_set = font.getGlyphSet()
glyph = glyph_set['A']

# Calculate bounds
bounds_pen = BoundsPen(glyph_set)
glyph.draw(bounds_pen)
bounds = bounds_pen.bounds
print(f"Glyph bounds: {bounds}")  # (xMin, yMin, xMax, yMax)

# Calculate area
area_pen = AreaPen(glyph_set) 
glyph.draw(area_pen)
area = area_pen.area
print(f"Glyph area: {area}")

# Gather statistics
stats_pen = StatisticsPen(glyph_set)
glyph.draw(stats_pen)
print(f"Area: {stats_pen.area}")
print(f"Length: {stats_pen.length}")
print(f"Moments: {stats_pen.moments}")

Transformation Pens

Pens for applying geometric transformations to glyph data.

class TransformPen(BasePen):
    def __init__(self, otherPen, transformation):
        """
        Apply transformation to pen operations.

        Parameters:
        - otherPen: AbstractPen, target pen to receive transformed operations
        - transformation: Transform, transformation matrix to apply
        """

class ReversedContourPen(BasePen):
    def __init__(self, otherPen):
        """
        Reverse contour direction.

        Parameters:
        - otherPen: AbstractPen, target pen to receive reversed operations
        """

Transforming Glyphs

from fontTools.pens.transformPen import TransformPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.misc.transform import Transform

# Create base glyph
base_pen = TTGlyphPen(None)
base_pen.moveTo((100, 100))
base_pen.lineTo((200, 100))
base_pen.lineTo((150, 200))
base_pen.closePath()

# Create transformed version (scaled and rotated)
target_pen = TTGlyphPen(None)
transform = Transform()
transform = transform.scale(1.5, 1.5)  # Scale 150%
transform = transform.rotate(math.radians(45))  # Rotate 45 degrees

transform_pen = TransformPen(target_pen, transform)

# Draw original shape through transform pen
transform_pen.moveTo((100, 100))
transform_pen.lineTo((200, 100))
transform_pen.lineTo((150, 200))
transform_pen.closePath()

transformed_glyph = target_pen.glyph()

Recording and Replay Pens

Pens for capturing and replaying drawing operations.

class RecordingPen(BasePen):
    def __init__(self):
        """Pen that records all drawing operations."""

    @property
    def value(self):
        """
        Get recorded operations.

        Returns:
        List[Tuple]: List of (operation, args) tuples
        """

    def replay(self, pen):
        """
        Replay recorded operations to another pen.

        Parameters:
        - pen: AbstractPen, target pen for replay
        """

class DecomposingRecordingPen(RecordingPen):
    def __init__(self, glyphSet):
        """
        Recording pen that decomposes components.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component decomposition
        """

Recording and Replaying Operations

from fontTools.pens.recordingPen import RecordingPen

# Record drawing operations
recording_pen = RecordingPen()
recording_pen.moveTo((0, 0))
recording_pen.lineTo((100, 0))
recording_pen.lineTo((100, 100))
recording_pen.lineTo((0, 100))
recording_pen.closePath()

# Get recorded operations
operations = recording_pen.value
print("Recorded operations:")
for op, args in operations:
    print(f"  {op}{args}")

# Replay to different pen
target_pen = TTGlyphPen(None)
recording_pen.replay(target_pen)
replayed_glyph = target_pen.glyph()

Output Format Pens

Pens for generating various output formats from glyph data.

class SVGPathPen(BasePen):
    def __init__(self, glyphSet=None):
        """
        Generate SVG path data.

        Parameters:
        - glyphSet: GlyphSet, glyph set for component resolution
        """

    def getCommands(self):
        """
        Get SVG path commands.

        Returns:
        List[str]: SVG path command strings
        """

    def d(self):
        """
        Get SVG path 'd' attribute value.

        Returns:
        str: Complete SVG path data
        """

Generating SVG Paths

from fontTools.pens.svgPathPen import SVGPathPen

# Create SVG path from glyph
svg_pen = SVGPathPen()

# Draw a simple shape
svg_pen.moveTo((100, 100))
svg_pen.curveTo((150, 50), (250, 50), (300, 100))
svg_pen.curveTo((350, 150), (350, 250), (300, 300))
svg_pen.curveTo((250, 350), (150, 350), (100, 300))
svg_pen.curveTo((50, 250), (50, 150), (100, 100))
svg_pen.closePath()

# Get SVG path data
path_data = svg_pen.d()
print(f"SVG path: {path_data}")

# Use in SVG
svg_content = f'''
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
  <path d="{path_data}" fill="black"/>
</svg>
'''

Error Handling

class PenError(Exception):
    """Base pen exception."""

class OpenContourError(PenError):
    """Raised when path operations are invalid due to open contour."""

Custom Pen Development

class CustomPen(BasePen):
    """Example custom pen implementation."""
    
    def __init__(self):
        super().__init__()
        self.paths = []
        self.current_path = []
    
    def moveTo(self, pt):
        if self.current_path:
            self.paths.append(self.current_path)
        self.current_path = [('moveTo', pt)]
    
    def lineTo(self, pt):
        self.current_path.append(('lineTo', pt))
    
    def curveTo(self, *points):
        self.current_path.append(('curveTo', points))
    
    def closePath(self):
        self.current_path.append(('closePath',))
        self.paths.append(self.current_path)
        self.current_path = []
    
    def endPath(self):
        if self.current_path:
            self.paths.append(self.current_path)
            self.current_path = []

# Usage patterns for drawing glyphs
def draw_rectangle(pen, x, y, width, height):
    """Helper function to draw rectangle."""
    pen.moveTo((x, y))
    pen.lineTo((x + width, y))
    pen.lineTo((x + width, y + height))
    pen.lineTo((x, y + height))
    pen.closePath()

def draw_circle(pen, cx, cy, radius, segments=32):
    """Helper function to draw circle approximation."""
    import math
    
    # Calculate points for circle
    points = []
    for i in range(segments):
        angle = 2 * math.pi * i / segments
        x = cx + radius * math.cos(angle)
        y = cy + radius * math.sin(angle)
        points.append((x, y))
    
    pen.moveTo(points[0])
    for point in points[1:]:
        pen.lineTo(point)
    pen.closePath()

Install with Tessl CLI

npx tessl i tessl/pypi-fonttools

docs

core-font-operations.md

drawing-pens.md

font-building.md

font-processing.md

index.md

utilities-tools.md

variable-fonts.md

tile.json