Internal API Reference¶
This page documents PyFreeform's private and internal APIs -- the implementation details that power the public API Reference. This is for contributors who need to understand how things work under the hood, not for end users.
Stability
Internal APIs are not covered by semantic versioning and may change between releases. If you find yourself reaching for something documented here from user code, consider whether the public API already provides what you need.
Resolution Pipeline¶
Every entity's position is determined by a priority-based resolution system. When you access entity.x or entity.y, the position is lazily computed:
_resolve_position() -> Coord¶
Returns the entity's resolved position based on the highest-priority binding:
- Path mode (
_along_pathis set): Callsalong.point_at(t)to get a position on the path - Relative mode (
_relative_atis set): Calls_resolve_relative(rx, ry)against the reference frame - Pixel mode (fallback): Returns the stored
_positiondirectly
_resolve_relative(rx, ry) -> Coord | None¶
Converts relative fractions (0.0-1.0) to absolute pixel coordinates using the entity's reference frame:
- If
_referenceis set (fromwithin=), uses that entity's or surface's bounds - Otherwise, uses the containing surface's bounds (
_surface) - Returns
Noneif no reference frame is available
_resolve_to_absolute()¶
"Bakes" the entity — converts all relative properties to absolute pixel values and clears the relative state. After baking, the entity no longer reacts to container changes and entity.is_relative becomes False.
The framework never calls this automatically. It exists as an explicit escape hatch for user code that needs fixed pixel values.
Subclasses override this to bake entity-specific relative properties (e.g., _relative_vertices for Polygon, _relative_end for Line/Curve).
_resolve_size(fraction, dimension) -> float | None¶
Converts a relative size fraction to pixels. The dimension argument is a string: "width", "height", or "min" (default). For example, _resolve_size(0.05, "min") returns 5% of min(surface_width, surface_height).
Private Movement¶
_move_to(x, y) -> Entity¶
Move the entity to absolute pixel coordinates. Clears relative tracking — sets _relative_at = None, _along_path = None. An absolute pixel position cannot be back-computed into a fraction (the reference frame may not exist), so setting one is always a mode switch.
_move_by(dx, dy) -> Entity¶
Move the entity by a pixel offset. Preserves what it can express as a delta:
- Relative mode: converts the pixel delta to a fraction adjustment against the reference frame. The entity stays relative.
- Along-path mode: resolves the current path position, applies the offset, and exits to pixel mode. A 2D pixel offset cannot be expressed as a 1D path parameter
t. - Pixel mode: shifts
_positiondirectly.
Subclasses override _move_by to shift their geometry in tandem — Line/Curve shift their end fractions, Polygon shifts all vertex fractions.
Why these are private¶
The public API for positioning is:
.at = (rx, ry)for relative positioning.position = Coord(x, y)for pixel positioningmove_to_surface(surface, at=)for moving between surfaces
_move_to and _move_by are used internally by transforms (_orbit_around, _scale_around) and fitting algorithms (fit_within). Because _move_by preserves relative state, orbiting or fitting an entity does not destroy its container-reactive behavior.
Binding Internals¶
The Binding dataclass (core/binding.py) is a frozen, immutable snapshot of an entity's positioning configuration:
@dataclass(frozen=True, slots=True)
class Binding:
at: RelCoord | None = None
reference: Surface | Entity | None = None
along: Pathable | None = None
t: float = 0.5
along_offset: float | None = None
How builders create bindings¶
When a Surface builder method (e.g., add_dot(at=..., within=..., along=...)) is called:
- Builder resolves the
atparameter to aRelCoord(or uses the default) - Builder creates the entity with pixel constructor
- Builder sets
entity.binding = Binding(at=relcoord, reference=within_entity, along=path, t=t, along_offset=offset) - The
bindingsetter unpacks the dataclass into the entity's internal attributes: _relative_at = binding.at_reference = binding.reference_along_path = binding.along_along_t = binding.t_along_offset = binding.along_offset
Why Binding is not exported¶
Binding is not in __init__.py's exports because users don't need to construct it. They set positioning through builder method parameters (at=, within=, along=, t=). The .binding property exists for inspection, not for user construction.
ref_frame() Contract¶
Both Entity and Surface implement ref_frame() -> tuple[float, float, float, float], returning (x, y, width, height).
This method exists so the within= system can resolve relative coordinates uniformly against either an entity or a surface without isinstance checks:
# Inside Surface._get_ref_frame():
if within is not None:
return within.ref_frame() # Works for both Entity and Surface
return self.ref_frame() # Fallback to this surface
For entities, ref_frame() returns the bounding box. For surfaces, it returns (x, y, width, height).
Registration Lifecycle¶
_register_entity(entity)¶
Called by every builder method and by add()/place(). Performs:
- Appends entity to
_entitieslist - Sets
entity.surface = self(back-reference)
add_connection(connection) / remove_connection(connection)¶
Bookkeeping methods called by Connection.__init__() and Connection.disconnect(). They add/remove the connection from the surface's _connections dict (used as an insertion-ordered set). Users never call these directly -- they use connect() to create connections and disconnect() to remove them.
Abstract Method Contracts¶
These methods must be implemented by every Entity subclass. They are listed in the Architecture Overview but documented in detail here.
to_svg() -> str¶
Return an SVG element string. Must:
- Use model-space coordinates (not world-space)
- Include
_build_svg_transform()in the element'stransformattribute for rotation/scale - Return an empty string for invisible entities (like Point)
bounds(*, visual=False) -> tuple[float, float, float, float]¶
Return (min_x, min_y, max_x, max_y) in world space (after applying rotation and scale). When visual=True, include stroke width in the bounds.
_named_anchor(name: str) -> Coord¶
Return the anchor point for a given name in world space. Called by the concrete anchor(spec) method on the base class. Must handle all names listed in anchor_names.
inner_bounds() -> tuple[float, float, float, float]¶
Return the largest inscribed rectangle as (min_x, min_y, max_x, max_y). Used by fit_within() to determine the target region. Default: same as bounds().
rotated_bounds(angle, *, visual=False) -> tuple[float, float, float, float]¶
Return the tight axis-aligned bounding box of this entity rotated by angle degrees around the origin. Used by EntityGroup.bounds() to compute tight group bounds from its children.
Entity Internal State¶
Key internal attributes on every entity:
| Attribute | Type | Description |
|---|---|---|
_position |
Coord |
Absolute pixel position (fallback when no relative binding) |
_surface |
Surface \| None |
Containing surface back-reference |
_relative_at |
RelCoord \| None |
Relative position within reference frame |
_reference |
Surface \| Entity \| None |
Reference frame override (from within=) |
_along_path |
Pathable \| None |
Path to follow (from along=) |
_along_t |
float |
Parameter on path (0.0-1.0) |
_resolving |
bool |
Guard against circular reference loops |
_rotation |
float |
Accumulated rotation in degrees |
_scale_factor |
float |
Accumulated scale multiplier |
_z_index |
int |
Layer ordering |
_connections |
dict[Connection, None] |
Connections involving this entity (insertion-ordered) |
_data |
dict[str, Any] |
Custom user data |
Entity-specific relative sizing¶
| Entity | Attribute | Description |
|---|---|---|
| Dot | _relative_radius |
Fraction of min(surface width, height) |
| Line, Curve | _relative_end |
End position as RelCoord |
| Rect | _relative_width, _relative_height |
Fractions of surface dimensions |
| Ellipse | _relative_rx, _relative_ry |
Fractions of surface dimensions |
| Text | _relative_font_size |
Fraction of surface height |
| Polygon | _relative_vertices |
List of RelCoord vertex positions |
Surface internal state¶
| Attribute | Type | Description |
|---|---|---|
_x, _y |
float |
Top-left corner position |
_width, _height |
float |
Dimensions in pixels |
_entities |
list[Entity] |
Contained entities |
_connections |
dict[Connection, None] |
Connections with this surface as endpoint (insertion-ordered) |
_data |
dict[str, Any] |
Custom user data |
SVG Transform Helpers¶
_build_svg_transform() -> str¶
Builds the SVG transform attribute string from the entity's accumulated rotation and scale:
Where (cx, cy) is the entity's rotation_center.
_to_world_space(model_point) -> Coord¶
Transforms a point from model space to world space by applying scale then rotation around rotation_center. Used by anchor() and bounds() to return world-space coordinates.
_orbit_around(angle, origin) -> None¶
Rotates the entity's position around an external origin point. Used by rotate(angle, origin=...).
_scale_around(factor, origin) -> None¶
Scales the entity's distance from an external origin point. Used by scale(factor, origin=...).
Renderer Protocol (core/protocols.py)¶
Rather than working against the full Entity surface, the renderer declares exactly what it needs via Animatable — a @runtime_checkable structural protocol in core/protocols.py:
@runtime_checkable
class Animatable(Protocol):
_animations: list
def surface_position_at(self, rx: float, ry: float) -> tuple[float, float]: ...
@property
def position(self) -> Coord: ...
def anchor(self, spec: AnchorSpec = "center") -> Coord: ...
This makes the renderer's dependency on entities explicit and minimal: the four members above are precisely what animation synthesis needs. Every concrete Entity subclass satisfies the protocol automatically — no registration required. Surface objects do not (they have no _animations), so isinstance(obj, Animatable) is the clean runtime check for distinguishing entity endpoints from surface endpoints in connections.
entity.surface_position_at(rx, ry) -> tuple[float, float]¶
Converts surface-relative coordinates (fractions 0.0–1.0) to absolute pixel coordinates using the entity's containing surface. Returns (rx, ry) unchanged if the entity has no surface. Declared on the protocol so renderers can resolve binding positions without accessing _surface directly.