Tools to manipulate font files
—
Miscellaneous utilities for text processing, geometric transformations, XML handling, and various font-related calculations. These utilities provide foundational support for font manipulation and processing workflows.
2D affine transformation matrix operations for scaling, rotation, translation, and skewing.
class Transform:
def __init__(self, a=1, b=0, c=0, d=1, e=0, f=0):
"""
Create 2D affine transformation matrix.
Parameters:
- a, b, c, d, e, f: float, transformation matrix components
[a c e] [x] [a*x + c*y + e]
[b d f] × [y] = [b*x + d*y + f]
[0 0 1] [1] [1]
"""
def transformPoint(self, point):
"""
Apply transformation to point.
Parameters:
- point: Tuple[float, float], input coordinates
Returns:
Tuple[float, float]: Transformed coordinates
"""
def transformPoints(self, points):
"""
Apply transformation to list of points.
Parameters:
- points: List[Tuple[float, float]], input points
Returns:
List[Tuple[float, float]]: Transformed points
"""
def scale(self, x, y=None):
"""
Create scaling transformation.
Parameters:
- x: float, horizontal scale factor
- y: float, vertical scale factor (default: same as x)
Returns:
Transform: New scaling transformation
"""
def rotate(self, angle):
"""
Create rotation transformation.
Parameters:
- angle: float, rotation angle in radians
Returns:
Transform: New rotation transformation
"""
def translate(self, x, y):
"""
Create translation transformation.
Parameters:
- x, y: float, translation offsets
Returns:
Transform: New translation transformation
"""
def skew(self, x, y):
"""
Create skew transformation.
Parameters:
- x, y: float, skew angles in radians
Returns:
Transform: New skew transformation
"""
def inverse(self):
"""
Get inverse transformation.
Returns:
Transform: Inverse transformation matrix
"""
def __mul__(self, other):
"""
Multiply transformations.
Parameters:
- other: Transform, transformation to multiply with
Returns:
Transform: Combined transformation
"""
Identity = Transform() # Identity transformation constantfrom fontTools.misc.transform import Transform, Identity
import math
# Create basic transformations
scale_transform = Transform().scale(2.0, 1.5) # Scale 2x horizontally, 1.5x vertically
rotate_transform = Transform().rotate(math.radians(45)) # Rotate 45 degrees
translate_transform = Transform().translate(100, 50) # Move 100 right, 50 up
# Combine transformations
combined = scale_transform * rotate_transform * translate_transform
# Apply to points
original_points = [(0, 0), (100, 0), (100, 100), (0, 100)]
transformed_points = combined.transformPoints(original_points)
print("Original:", original_points)
print("Transformed:", transformed_points)
# Create complex transformation
complex_transform = (Transform()
.translate(-50, -50) # Center at origin
.rotate(math.radians(30)) # Rotate 30 degrees
.scale(1.2) # Scale up 20%
.translate(200, 300)) # Move to final position
# Apply to single point
x, y = complex_transform.transformPoint((100, 100))
print(f"Point (100, 100) transformed to ({x}, {y})")Functions for working with coordinate arrays and bounding box calculations.
def calcBounds(array):
"""
Calculate bounding box of coordinate array.
Parameters:
- array: List[Tuple[float, float]], coordinate points
Returns:
Tuple[float, float, float, float]: (xMin, yMin, xMax, yMax) or None if empty
"""
def updateBounds(bounds, p):
"""
Update bounding box to include point.
Parameters:
- bounds: Tuple[float, float, float, float], current bounds (xMin, yMin, xMax, yMax)
- p: Tuple[float, float], point to include
Returns:
Tuple[float, float, float, float]: Updated bounds
"""
def pointInRect(p, rect):
"""
Test if point is inside rectangle.
Parameters:
- p: Tuple[float, float], point coordinates
- rect: Tuple[float, float, float, float], rectangle (xMin, yMin, xMax, yMax)
Returns:
bool: True if point is inside rectangle
"""
def rectCenter(rect):
"""
Get center point of rectangle.
Parameters:
- rect: Tuple[float, float, float, float], rectangle (xMin, yMin, xMax, yMax)
Returns:
Tuple[float, float]: Center coordinates
"""
def intRect(rect):
"""
Convert rectangle to integer coordinates.
Parameters:
- rect: Tuple[float, float, float, float], rectangle with float coordinates
Returns:
Tuple[int, int, int, int]: Rectangle with integer coordinates
"""from fontTools.misc.arrayTools import calcBounds, updateBounds, rectCenter
# Calculate bounds of point array
points = [(10, 20), (50, 10), (30, 60), (80, 40)]
bounds = calcBounds(points)
print(f"Bounds: {bounds}") # (10, 10, 80, 60)
# Update bounds incrementally
current_bounds = None
for point in points:
if current_bounds is None:
current_bounds = (point[0], point[1], point[0], point[1])
else:
current_bounds = updateBounds(current_bounds, point)
print(f"Incremental bounds: {current_bounds}")
# Get rectangle center
center = rectCenter(bounds)
print(f"Center: {center}") # (45.0, 35.0)Functions for handling hexadecimal data, safe evaluation, and text conversion.
def safeEval(data):
"""
Safely evaluate simple Python expressions.
Parameters:
- data: str, Python expression to evaluate
Returns:
Any: Evaluation result
"""
def readHex(content):
"""
Read hexadecimal string and return bytes.
Parameters:
- content: str, hexadecimal string
Returns:
bytes: Decoded binary data
"""
def writeHex(data):
"""
Convert bytes to hexadecimal string.
Parameters:
- data: bytes, binary data
Returns:
str: Hexadecimal representation
"""
def hexStr(value):
"""
Convert integer to hexadecimal string.
Parameters:
- value: int, integer value
Returns:
str: Hexadecimal string (e.g., "0x1a2b")
"""from fontTools.misc.textTools import safeEval, readHex, writeHex, hexStr
# Safe evaluation
result = safeEval("2 + 3 * 4")
print(f"Safe eval result: {result}") # 14
# Hex conversion
hex_string = "48656c6c6f20576f726c64" # "Hello World" in hex
binary_data = readHex(hex_string)
print(f"Decoded: {binary_data}") # b'Hello World'
# Convert back to hex
hex_result = writeHex(binary_data)
print(f"Re-encoded: {hex_result}")
# Integer to hex
print(f"0x{255:02x}") # 0xff
print(hexStr(255)) # 0xffFunctions for various rounding operations commonly used in font processing.
def roundFunc(value, func=round):
"""
Apply rounding function safely.
Parameters:
- value: float, value to round
- func: callable, rounding function (default: round)
Returns:
int: Rounded value
"""
def noRound(value):
"""
No-op rounding function (returns input unchanged).
Parameters:
- value: float, input value
Returns:
float: Unchanged input value
"""
def otRound(value):
"""
OpenType-style rounding (rounds 0.5 up).
Parameters:
- value: float, value to round
Returns:
int: Rounded value using OpenType rules
"""from fontTools.misc.roundTools import roundFunc, noRound, otRound
values = [1.2, 1.5, 1.7, 2.5, -1.5]
print("Standard rounding:")
for val in values:
print(f" {val} -> {roundFunc(val)}")
print("OpenType rounding:")
for val in values:
print(f" {val} -> {otRound(val)}")
print("No rounding:")
for val in values:
print(f" {val} -> {noRound(val)}")Utilities for working with fixed-point numbers used in font tables.
def fixedToFloat(value, precisionBits=16):
"""
Convert fixed-point integer to floating-point.
Parameters:
- value: int, fixed-point value
- precisionBits: int, number of fractional bits (default: 16)
Returns:
float: Floating-point representation
"""
def floatToFixed(value, precisionBits=16):
"""
Convert floating-point to fixed-point integer.
Parameters:
- value: float, floating-point value
- precisionBits: int, number of fractional bits (default: 16)
Returns:
int: Fixed-point representation
"""
def ensureVersionIsLong(value):
"""
Ensure version number is in correct format.
Parameters:
- value: int or float, version number
Returns:
int: Properly formatted version number
"""from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
# Convert between fixed-point and float
float_val = 1.5
fixed_val = floatToFixed(float_val)
print(f"Float {float_val} -> Fixed {fixed_val}") # 98304 (1.5 * 65536)
back_to_float = fixedToFloat(fixed_val)
print(f"Fixed {fixed_val} -> Float {back_to_float}") # 1.5
# Different precision
fixed_8bit = floatToFixed(1.5, precisionBits=8)
print(f"8-bit fixed: {fixed_8bit}") # 384 (1.5 * 256)Mathematical functions for working with Bezier curves and path calculations.
def calcQuadraticBounds(pt1, pt2, pt3):
"""
Calculate bounding box of quadratic Bezier curve.
Parameters:
- pt1, pt2, pt3: Tuple[float, float], curve control points
Returns:
Tuple[float, float, float, float]: Bounding box (xMin, yMin, xMax, yMax)
"""
def calcCubicBounds(pt1, pt2, pt3, pt4):
"""
Calculate bounding box of cubic Bezier curve.
Parameters:
- pt1, pt2, pt3, pt4: Tuple[float, float], curve control points
Returns:
Tuple[float, float, float, float]: Bounding box (xMin, yMin, xMax, yMax)
"""
def splitLine(pt1, pt2, where):
"""
Split line segment at parameter t.
Parameters:
- pt1, pt2: Tuple[float, float], line endpoints
- where: float, parameter t (0.0 to 1.0)
Returns:
Tuple[Tuple, Tuple]: Two line segments
"""
def splitQuadratic(pt1, pt2, pt3, where):
"""
Split quadratic Bezier curve at parameter t.
Parameters:
- pt1, pt2, pt3: Tuple[float, float], curve control points
- where: float, parameter t (0.0 to 1.0)
Returns:
Tuple[Tuple, Tuple]: Two quadratic curve segments
"""
def splitCubic(pt1, pt2, pt3, pt4, where):
"""
Split cubic Bezier curve at parameter t.
Parameters:
- pt1, pt2, pt3, pt4: Tuple[float, float], curve control points
- where: float, parameter t (0.0 to 1.0)
Returns:
Tuple[Tuple, Tuple]: Two cubic curve segments
"""from fontTools.misc.bezierTools import calcCubicBounds, splitCubic
# Calculate bounds of cubic curve
pt1 = (0, 0)
pt2 = (50, 100)
pt3 = (150, 100)
pt4 = (200, 0)
bounds = calcCubicBounds(pt1, pt2, pt3, pt4)
print(f"Curve bounds: {bounds}")
# Split curve at midpoint
left_curve, right_curve = splitCubic(pt1, pt2, pt3, pt4, 0.5)
print(f"Left curve: {left_curve}")
print(f"Right curve: {right_curve}")Utilities for reading and writing XML data in font processing contexts.
class XMLWriter:
def __init__(self, outFile, encoding="utf-8"):
"""
XML output writer.
Parameters:
- outFile: file-like, output file object
- encoding: str, character encoding
"""
def begintag(self, tag, **kwargs):
"""
Write opening XML tag.
Parameters:
- tag: str, tag name
- kwargs: tag attributes
"""
def endtag(self, tag):
"""
Write closing XML tag.
Parameters:
- tag: str, tag name
"""
def simpletag(self, tag, **kwargs):
"""
Write self-closing XML tag.
Parameters:
- tag: str, tag name
- kwargs: tag attributes
"""
def data(self, data):
"""
Write XML character data.
Parameters:
- data: str, character data
"""
class XMLReader:
def __init__(self, inFile):
"""
XML input reader.
Parameters:
- inFile: file-like, input file object
"""
def read(self, handler):
"""
Parse XML with content handler.
Parameters:
- handler: object, content handler with start/end/data methods
"""Utilities for configuring FontTools logging output.
def configLogger(level="INFO", format=None, logger=None):
"""
Configure FontTools logging.
Parameters:
- level: str, logging level ("DEBUG", "INFO", "WARNING", "ERROR")
- format: str, log message format string
- logger: Logger, specific logger to configure (default: fontTools logger)
"""from fontTools.misc.loggingTools import configLogger
import logging
# Configure FontTools logging
configLogger(level="DEBUG")
# Use FontTools logger
from fontTools import log
log.info("Processing font file")
log.warning("Unusual glyph structure detected")
log.error("Font validation failed")
# Configure with custom format
configLogger(
level="INFO",
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)Cross-platform file system operations for font processing.
from fontTools.misc.filesystem import open_file, ensure_path
# Platform-aware file opening
with open_file("font.ttf", "rb") as f:
font_data = f.read()
# Ensure directory exists
ensure_path("output/fonts/")def optimize_glyph_coordinates(glyph_coords, transform=None):
"""Example combining multiple utilities."""
from fontTools.misc.arrayTools import calcBounds
from fontTools.misc.roundTools import otRound
from fontTools.misc.transform import Transform
# Apply transformation if provided
if transform:
coords = transform.transformPoints(glyph_coords)
else:
coords = glyph_coords
# Round coordinates using OpenType rules
rounded_coords = [(otRound(x), otRound(y)) for x, y in coords]
# Calculate optimized bounds
bounds = calcBounds(rounded_coords)
return rounded_coords, bounds
def create_web_font_transform(target_upm=1000):
"""Create transformation for web font optimization."""
from fontTools.misc.transform import Transform
# Common web font optimizations
transform = Transform()
# Scale to target units per em
current_upm = 2048 # Assume source UPM
scale_factor = target_upm / current_upm
transform = transform.scale(scale_factor)
# Optional: slight compression for better rendering
transform = transform.scale(1.0, 0.95)
return transformInstall with Tessl CLI
npx tessl i tessl/pypi-fonttools