Auto-generated from JavaScript object model introspection
- Overview
- Class Reference
- Font - The main font class representing a complete font
- Glyph - Glyph in the font
- Layer - Layer in a glyph representing a master or intermediate design
- Shape - Shape wrapper that can contain either a Component or a Path
- Path - Path (contour) in a layer
- Node - Point in a path
- Component - Component reference to another glyph
- Anchor - Anchor point in a layer
- Guide - Guideline in a layer or master
- Axis - Variation axis in a variable font
- Master - Master/source in a design space
- Instance - Named instance in a variable font
- Complete Examples
- Tips and Best Practices
The Font Object Model provides an object-oriented interface for manipulating font data. All objects are lightweight facades over the underlying JSON data - changes are immediately reflected in the font structure.
# Get the current font (fonteditor module is pre-loaded)
font = Font()Dictionary-like object model fields are wrapped as live Python mappings. Use normal Python dictionary access for both reading and writing.
font = Font()
master = font.masters[0]
# Nested kerning dictionary (live two-way view)
master.kerning["A"]["V"] = -80
# Internationalized naming dictionaries
font.names.familyName["dflt"] = "My Family"
font.names.familyName["de"] = "Meine Familie"
font.names.familyName["fr"] = "Ma Famille"
font.names.familyName["ar"] = "عائلتي"
master.name["dflt"] = "Standard"
master.name["de"] = "Standard"
master.name["fr"] = "Standard"
master.name["ar"] = "قياسي"
# Optional snapshot copy when needed
kerning_snapshot = master.kerning.as_dict()ctx = Context()
ctx.runCount = getattr(ctx, "runCount", 0) + 1
SetContextPatch({"lastRun": {"count": ctx.runCount}})All objects in the hierarchy have a parent() method that returns their parent object,
allowing navigation up the object tree to the root Font object.
Example:
# Navigate from node up to font
node = font.glyphs[0].layers[0].paths[0].nodes[0]
path = node.parent() # Path object
shape = path.parent() # Shape object
layer = shape.parent() # Layer object
glyph = layer.parent() # Glyph object
font = glyph.parent() # Font objectThe main font class representing a complete font
Access:
# fonteditor module is pre-loaded
font = Font()upm(float | int)version([number, number])note(str | None)date(str)names(dict[str, dict[str, str] | None])custom_ot_values(list[Unsafe] | None)variation_sequences( | dict | None)features(dict[str, Any])first_kern_groups(dict | None)second_kern_groups(dict | None)format_specific(dict | None)source(str | None)
axes(list[Axis] | None)instances(list[Instance] | None)masters(list[Master] | None)glyphs(list[Glyph])
Find a glyph by name
Example:
glyph = font.findGlyph("A")
if glyph:
print(glyph.name)Find a glyph by codepoint
Example:
glyph = font.findGlyphByCodepoint(0x0041) # Find 'A'Find all glyphs that reference a given glyph as a component This recursively finds glyphs at each nesting level
Example:
glyphs = font.findGlyphsUsingComponent("o")
# Returns ["ö", "õ", "ø", ...] if they use "o" as a componentDuplicate a glyph with a new name
Example:
new_glyph = font.duplicateGlyph(glyph, "A.alt")Find an axis by ID
Find an axis by tag
Example:
weight_axis = font.findAxisByTag("wght")Find a master by ID
Add a new glyph to the font
Example:
glyph = font.addGlyph("myGlyph", "Base")Remove a glyph by name
Example:
font.removeGlyph("oldGlyph")Serialize the font back to JSON string
Create a Font instance from JSON string
Create a Font instance from parsed JSON data
Analyze a feature's code to determine if it contains GSUB and/or GPOS rules
Example:
const analysis = font.analyzeFeatureTables("liga")
if (analysis.hasGSUB) console.log("Feature has substitution rules")Analyze OpenType feature code to determine if it contains GSUB and/or GPOS rules This is a general-purpose method that can analyze code from features, prefixes, or other sources
Example:
const analysis = font.analyzeOpenTypeCode("substitute a by b;")
if (analysis.hasGSUB) console.log("Code contains substitution rules")Analyze a prefix's code to determine if it contains GSUB and/or GPOS rules
Example:
const analysis = font.analyzePrefix("myLookup")
if (analysis.hasGSUB) console.log("Prefix contains substitution rules")Glyph in the font
Access:
glyph = font.glyphs[0]
# or
glyph = font.findGlyph("A")leftMetricsKey(str | None)rightMetricsKey(str | None)name(str)production_name(str | None)category(Babelfont.GlyphCategory)codepoints(list[float | int] | None)exported(bool | None)direction(Babelfont.Direction | None)format_specific(dict | None)
BUILTIN_CATEGORIES(Any)layers(list[Layer] | None)isCompatible(bool): Returns True/False based on whether the outline structure (components + paths + anchors) is compatible across all main layers of this glyph.
Add a new layer to the glyph
Example:
layer = glyph.addLayer(500) # 500 units wideRemove a layer at the specified index
Find a layer by ID
Find a layer by master ID
calculateOutlineCompatibility() -> { compatible: boolean; layerCount: number; referenceLayerId?: string; incompatibleLayerIds: string[]; }
Compare outline structure across main layers (the same list shown in the UI).
For compatibility checks, mixed shape sequences are normalized by moving components before paths while preserving their relative order inside each type.
Layer in a glyph representing a master or intermediate design
Access:
layer = glyph.layers[0]leftMetricsKey(str | None)rightMetricsKey(str | None)width(float | int)lsb(float | int): Get the left sidebearing (LSB) - the distance from x=0 to the left edge of the bounding boxrsb(float | int): Get the right sidebearing (RSB) - the distance from the right edge of the bounding box to the advance widthlinked(bool): Whether this layer is linked for editor multi-layer operations. This is editor-only runtime state keyed by glyph and layer ID; it is not persisted into font data.name(str | None)id(str | None)master(Babelfont.LayerType | None)smart_component_location(UserspaceLocation | None)selection(list[SelectableLayerObject]): Current UI selection on this layer. Assign a node, anchor, component, guide, or a list of them to replace the selection.color(Babelfont.Color | None)layer_index(float | int | None)is_background(bool | None)background_layer_id(str | None)location(DesignspaceLocation | None)format_specific(dict | None)
guides(list[Guide] | None)paths(list[Path]): Direct path objects in this layer, ready to use without Shape.asPath()components(list[Component]): Direct component objects in this layer, ready to use without Shape.asComponent()anchors(list[Anchor] | None)fingerprint(str): Returns a normalized outline-compatibility fingerprint for this layer. The fingerprint includes components, paths, and anchors, with anchors sorted by name and guides excluded.
Get the resolved master object for this layer. Returns a Master only when this layer is a DefaultForMaster layer.
Add a new shape to the layer
Add a new path to the layer
Example:
path = layer.addPath(closed=True)addComponent(reference: str, transform: list[float | int] | Babelfont.DecomposedAffine | None = None) -> [Component](#component)
Add a new component to the layer
Example:
component = layer.addComponent("A")
# With transformation matrix (legacy 6-element format converted to DecomposedAffine)
component = layer.addComponent("acutecomb", [1, 0, 0, 1, 250, 500])removeShape(shapeOrIndex: float | int | [Shape](#shape) | [Path](#path) | [Component](#component)) -> None
Remove a shape at the specified index
Add a new anchor to the layer
Example:
anchor = layer.addAnchor(250, 700, "top")addGuide(pos: Babelfont.Position, name: str | None = None, color: Babelfont.Color | None = None) -> [Guide](#guide)
Remove an anchor at the specified index
processPathSegments(pathData: { nodes: Unsafe[]; closed?: boolean; }) -> Array<{ points: Array<{ x: number; y: number }>; type: 'line' | 'quadratic' | 'cubic'; }>
Process a path into Bezier curve segments Handles the babelfont node format where:
- Nodes can have 'type' (lowercase: o, c, l, q, etc.) or 'nodetype' (capitalized: OffCurve, Curve, Line, etc.)
- Segments are sequences: [oncurve] [offcurve*] [oncurve]
- For closed paths, the path can start with offcurve nodes
calculatePathBounds(pathData: list[{ nodes?: Unsafe] | string; closed?: boolean; }) -> { minX: number; minY: number; maxX: number; maxY: number; width: number; height: number; } | None
calculateShapeBounds(shapes: list[Unsafe] | None, parentTransform: list[float | int]) -> { minX: number; minY: number; maxX: number; maxY: number; width: number; height: number; } | None
calculateSvgPathBounds(pathData: str) -> { minX: number; minY: number; maxX: number; maxY: number; width: number; height: number; } | None
Get all paths in this layer including transformed paths from components (recursively flattened)
calculateBoundingBox(layerData: Unsafe, includeAnchors: bool, font: [Font](#font) | None = None, masterId: str | None = None) -> { minX: number; minY: number; maxX: number; maxY: number; width: number; height: number; } | None
Calculate bounding box for layer data
getBoundingBox(includeAnchors: bool) -> { minX: number; minY: number; maxX: number; maxY: number; width: number; height: number; } | None
Calculate bounding box for this layer
getIntersectionsOnLine(p1: { x: number; y: number }, p2: { x: number; y: number }, includeComponents: bool) -> Array<{ x: number; y: number; t: number }>
Calculate intersections between a line segment and all paths in this layer
Calculate sidebearings at a given Y height by measuring distance from glyph edges to first/last outline intersections
Find the exact matching stored layer on another glyph for this layer's effective designspace location.
Shape wrapper that can contain either a Component or a Path
Access:
path = layer.paths[0]
shape = path.parent()Check if this shape is a component
Check if this shape is a path
Get as Component (throws if not a component)
Get as Path (throws if not a path)
Path (contour) in a layer
Access:
path = layer.paths[0]All properties are read/write:
nodes(list[Node])closed(bool)format_specific(dict | None)
Parse nodes from babelfont-rs string format Format: "x1 y1 type x2 y2 type ..." Types: m, l, o, c, q (with optional 's' suffix for smooth)
Map short node type to Babelfont.NodeType
Convert nodes array back to compact string format for serialization
insertNode(index: float | int, x: float | int, y: float | int, nodetype: Babelfont.NodeType, smooth: bool | None = None) -> [Node](#node)
Insert a node at the specified index
Example:
path.insertNode(1, 150, 250, "Line") # Insert at index 1Remove a node at the specified index
Example:
path.removeNode(0) # Remove first nodeappendNode(x: float | int, y: float | int, nodetype: Babelfont.NodeType, smooth: bool | None = None) -> [Node](#node)
Append a node to the end of the path
Example:
path.appendNode(100, 200, "Line")
path.appendNode(300, 400, "Curve", smooth=True)Point in a path
Access:
node = path.nodes[0]All properties are read/write:
selected(bool): Whether this node is selected in the active outline editor.x(float | int)y(float | int)nodetype(Babelfont.NodeType)smooth(bool | None)
Component reference to another glyph
Access:
component = layer.components[0]All properties are read/write:
selected(bool): Whether this component is selected in the active outline editor.reference(str)transform(Babelfont.DecomposedAffine)location(DesignspaceLocation | None)format_specific(dict | None)
Convert transform to affine matrix array [a, b, c, d, e, f] Uses the proper DecomposedAffineTransform utility
Get all paths from this component with transforms applied recursively Automatically determines the correct master by walking up the parent chain
Anchor point in a layer
Access:
anchor = layer.anchors[0]All properties are read/write:
selected(bool): Whether this anchor is selected in the active outline editor.x(float | int)y(float | int)name(str | None)format_specific(dict | None)
Guideline in a layer or master
Access:
guide = layer.guides[0]
# or
guide = master.guides[0]All properties are read/write:
selected(bool): Whether this guide is selected in the active outline editor.pos(Babelfont.Position)name(str | None)color(Babelfont.Color | None)format_specific(dict | None)
Variation axis in a variable font
Access:
axis = font.axes[0]
# or
axis = font.findAxisByTag("wght")All properties are read/write:
name(dict[str, str])tag(str)id(str)min(float | int | None)max(float | int | None)default(float | int | None)map(list[[number, number]] | None)hidden(bool | None)values(list[float | int] | None)format_specific(dict | None)
Master/source in a design space
Access:
master = font.masters[0]
# or
master = font.findMaster("master-id")name(dict[str, str])id(str)location(DesignspaceLocation | None)metrics(dict)kerning(dict)custom_ot_values(list[Unsafe] | None)format_specific(dict | None)
guides(list[Guide] | None)
addGuide(pos: Babelfont.Position, name: str | None = None, color: Babelfont.Color | None = None) -> [Guide](#guide)
Named instance in a variable font
Access:
instance = font.instances[0]All properties are read/write:
id(str)name(dict[str, str])location(DesignspaceLocation | None)custom_names(dict[str, dict[str, str] | None])variable(bool | None)linked_style(str | None)format_specific(dict | None)
# Get the font
font = Font()
# Create a new glyph
glyph = font.addGlyph("myGlyph", "Base")
# Add a layer
layer = glyph.addLayer(500) # 500 units wide
# Create a rectangle path
path = layer.addPath(closed=True)
path.appendNode(100, 0, "Line")
path.appendNode(400, 0, "Line")
path.appendNode(400, 700, "Line")
path.appendNode(100, 700, "Line")
print(f"Created glyph: {glyph.name}")font = Font()
# Find glyph A
glyph_a = font.findGlyph("A")
if glyph_a:
layer = glyph_a.layers[0]
# Modify all nodes
for path in layer.paths:
for node in path.nodes:
node.x += 10 # Shift 10 units right
node.y += 5 # Shift 5 units up
# Add an anchor
layer.addAnchor(250, 700, "top")
print(f"Modified {glyph_a.name}")font = Font()
# Create a glyph with a component
glyph = font.addGlyph("Aacute", "Base")
layer = glyph.addLayer(600)
# Add base letter component
base = layer.addComponent("A")
# Add accent component with transformation
# Transform: [scaleX, skewX, skewY, scaleY, translateX, translateY]
accent = layer.addComponent("acutecomb", [1, 0, 0, 1, 250, 500])
print(f"Created {glyph.name} with components")font = Font()
# Count nodes across all glyphs
total_nodes = 0
for glyph in font.glyphs:
if glyph.layers:
for layer in glyph.layers:
for path in layer.paths:
total_nodes += len(path.nodes)
print(f"Total nodes in font: {total_nodes}")font = Font()
# Check if font has axes
if font.axes:
print("Variable font axes:")
for axis in font.axes:
print(f" {axis.tag}: {axis.min} - {axis.max} (default: {axis.default})")
# Check masters
if font.masters:
print(f"\nFont has {len(font.masters)} masters:")
for master in font.masters:
location_str = ", ".join(f"{k}={v}" for k, v in (master.location or {}).items())
print(f" Master: {location_str}")font = Font()
# Scale all glyphs by 1.5x
scale_factor = 1.5
for glyph in font.glyphs:
if glyph.layers:
for layer in glyph.layers:
# Scale width
layer.width *= scale_factor
# Scale all outline paths
for path in layer.paths:
for node in path.nodes:
node.x *= scale_factor
node.y *= scale_factor
# Scale anchors
if layer.anchors:
for anchor in layer.anchors:
anchor.x *= scale_factor
anchor.y *= scale_factor
print(f"Scaled {len(font.glyphs)} glyphs by {scale_factor}x")font = Font()
master = font.masters[0]
# Ensure nested kerning bucket exists
if "A" not in master.kerning:
master.kerning["A"] = {}
master.kerning["A"]["V"] = -90
master.kerning["A"]["W"] = -70
# Read values with standard dict APIs
av_value = master.kerning["A"].get("V")
print(f"A/V kerning: {av_value}")
# Update localized names
font.names.familyName["dflt"] = "Counterpunch Sans"
font.names.familyName["de"] = "Counterpunch Sans DE"
font.names.familyName["fr"] = "Counterpunch Sans FR"
font.names.familyName["ar"] = "كاونتربنش سانس"font = Font()
# features is a live list-like wrapper
feature_items = font.features.features
# Append a new feature tuple: [tag, code-record]
feature_items.append(["liga", {"code": "sub f i by fi;"}])
# Insert at the top
feature_items.insert(0, ["kern", {"code": "pos A V -80;"}])
# Remove entries with normal list operations
if len(feature_items) > 5:
feature_items.pop()
del feature_items[0]- Changes to properties are immediately reflected in the underlying JSON data
- No need to "save" or "commit" changes - they are live
- Dictionary-like fields are live Python mappings (no routine
.to_py()needed) - Array fields (for example
font.features.features) are live list-like wrappers - For batch operations, group changes together to minimize redraws
# Use the filtered convenience collections when you know what you want
for path in layer.paths:
# Work with path
for component in layer.components:
# Work with component# Check for optional properties
if glyph.layers:
for layer in glyph.layers:
for path in layer.paths:
for node in path.nodes:
print(f"Node at ({node.x}, {node.y})")Dictionary-like fields reject scalar overwrite assignments to prevent broken model state.
# ❌ Avoid replacing a language dictionary with a string
# font.names.familyName = "My Font"
# ✅ Set a language value inside the dictionary
font.names.familyName["dflt"] = "My Font"
# ✅ Or replace with a full mapping
font.names.familyName = {
"dflt": "My Font",
"de": "Meine Schrift"
}# Direct access (may fail if properties are None)
# glyph.layers[0].paths[0].nodes # DON'T DO THIS
# Safe access with checks:
layer = glyph.layers[0] if glyph.layers else None
if layer and layer.paths:
path = layer.paths[0]
nodes = path.nodes
print(f"Path has {len(nodes)} nodes")- Origin (0, 0) is at the baseline on the left
- Y-axis points upward
- All coordinates are in font units (1/upm of the em square)
Q: Why does glyph.layers[0].paths[0].nodes fail?
A: Optional properties may be None. Use safe access:
# Check each step
if glyph.layers and len(glyph.layers) > 0:
layer = glyph.layers[0]
if layer.paths and len(layer.paths) > 0:
path = layer.paths[0]
nodes = path.nodes # Now safe to accessQ: How do I access only paths or only components in a layer?
A: Use layer.paths and layer.components directly:
for path in layer.paths:
print(len(path.nodes))
for component in layer.components:
print(component.reference)Generated by generate-api-docs.mjs