A desktop color font editor for COLRv0 and COLRv1 fonts
All notable changes to Colr Pak and its components are documented here. Colr Pak is a fork of Fontra Pak, built on Fontra and fontra-compile.
.ttf and otf sources.UFO Color Layer Mapping: Added support for the com.github.googlei18n.ufo2ft.colorLayerMapping protocol. This ensures that color layers are correctly associated with palette indices when exporting to UFO, enabling a seamless roundtrip between OpenType and UFO formats.
.color.0 glyphs) are now hidden from the main glyph list to reduce clutter, while remaining fully accessible through the parent glyph’s Layers panel.VarStoreInstancer logic to handle color-specific variations (deltas) in paints and clip boxes more accurately across different axis locations.colrPaintGraphs and colrGlyphPaintEntries through a unified processing pipeline.Color GraphClip BoxTransform GlyphAdd keyframe at current axis locationopentype.py that caused initialization failures when loading fonts with specific COLR versions.color.n) was not being correctly applied during glyph loading.fontra-color-support
fix(paint-tool): correct PaintTransform bounding box origin and eliminate fallback flash
Added direct on-canvas editing for COLRv1 transform paints, including PaintTranslate, PaintRotate, PaintScale, PaintSkew, and matrix-based PaintTransform.
Added transform-aware handle rendering with dedicated visuals such as translate crosshairs, rotation arcs, scale arms, skew guides, and PaintTransform parallelograms.
Added bounds-aware PaintTransform editing by fetching child glyph bounds and falling back to computed path bounds when needed.
Expanded the unified paint tool from gradient-only editing into a general COLRv1 editor that dispatches behavior by handle role instead of a narrower handle map.
Updated cursor behavior so different handle types display context-specific cursors such as move, resize, alias, cell, and crosshair.
Reworked drag handling to properly separate direct coordinate edits, angle edits, and custom transform commits (such as scale, skew, and matrix adjustments).
Fixed PaintTransform handle placement so transform wrappers are treated as top-level paint nodes instead of incorrectly reading only layer.paint.
Fixed transform-handle sizing by using actual child glyph bounds when available, with a safe fallback when bounds cannot be resolved.
Fixed palette cycling to successfully read palette data from the active font controller path used by the scene model.
Fixed highlight and live-preview updates so transformed handle movement is redrawn smoothly and consistently during drag operations.
fontra-compile(color-support)
DecomposedTransform. The fix is applied to both static paint generation and variable font merging.Reorganized paint-tool role handling into distinct direct-role, angle-role, and custom-role dispatch maps for better maintainability.
Added internal helper utilities for raw path-bound calculation and transform-matrix point application.
Updated COLRv1 test glyph fixtures to exercise the new transform and skew workflows, adjusting metrics and outline coordinates accordingly.
featcolrv1 improve PaintComposite authoring in panel-color-layers.js
PaintTransform, PaintComposite, PaintSkew, and PaintRotate in the COLRv1 Color Layers UI.Add Paint to insert paint references to other glyphs.fix(builder): cu2qu conversion, name table, and OpenType feature compilation
Three significant fixes to the font compilation pipeline:
feat(colrv1): add CPAL palette names + fix variable paint compilation
org.colrpak.colorPaletteLabels lib key to store per-palette
name strings in the .fontra sourcebuildFont via _normalizePaletteLabels()paletteLabels and corresponding nameIDs
in the name table when any palette has a label, using
fontTools.colorLib.builder.buildCPAL_palettesHaveLabels() guard so static fonts without labels
continue to emit CPAL version 0 unchangedorg.colrpak.colorPaletteLabelspb.varstorebuilder = OnlineVarStoreBuilder(...) to before
the colorGlyphs loop so make_var_scalar always has a valid store;
previously a variable glyph encountered first would crash with
ValueError: dictionary update sequence element #0 has length 1userSpaceLocs construction to use only fvar axis tags known
to PythonBuilder.axes, preventing bare axis name strings from
leaking into VariableScalar.add_value as invalid location keysglyphInfo.model (already validated in prepareGlyphs)
instead of constructing a second VariationModel from raw
user-space locations, avoiding index mismatches between the model’s
reverseMapping and the locations listAdd full COLRv1 round-trip support and fix variation handling
This major update enhances the OTF backend’s ability to read compiled COLRv1 color fonts and seamlessly convert them into Fontra’s native format, preserving crucial data like clip boxes and exact master metrics for editing and exporting.
COLRv1 Structural & ClipBox Support
_convertPaintGraphToFontra recursive converter.ClipList definitions from the COLR table during initialization.ClipBox VarIndexBase definitions and correctly
add them to the location discovery loop.ClipBox coordinates per master layer and preserve them
inside layer.glyph.customData["fontra.colrv1.clipBox"].Variation & Indexing Fixes
_collectVarIndicesFromPaint was gathering raw
variation indices without applying varIndexMap, causing locations to detach
from instancer mapping.NO_VARIATION_INDEX None guards to axis mapping resolution.VarStore and VarIndexMap directly into
customData for flawless variable font re-compilation.General Glyph & Font Source Updates
PaintColrLayers when a base glyph maps to
multiple color layers or nested elements.avar map segments.axisValuesVarIndex or
transformVarIndex in VARC table.ColorLine and color palette values natively to RGBA format.fix(colrv1): restore layer-nested data structure for compiler and tool compatibility
In commit eae66e7, the COLRv1 data path was simplified to write to the root glyph’s customData. This caused a structural mismatch:
This change reverts the “simplification” and enforces the previous standard (nested under layers[id].glyph.customData).
Changes:
Fixes: Invisible paint handles and compiler rendering errors.
fix(export): block OTF export for .fontra sources with user-facing error
COLRv1 fonts require TrueType (glyf) outlines — CFF2 (OTF) is incompatible with COLR/CPAL tables per the OpenType spec. .fontra is Colr Pak’s native COLRv1 format and must always compile to TTF or WOFF2.
Previously, selecting OTF as the export format for a .fontra source would silently pass the request to fontra_compile, which crashed deep in fontTools with AssertionError: assert not self.isTTF.
Note: .ufo and .designspace sources exporting to OTF are unaffected — COLRv0 with CFF outlines is valid per spec and fontmake handles it correctly. This guard is intentionally scoped to .fontra sources only.
Both nsis setup exe and msi installer for Windows Platform are now available
Modified Version scheme and organisaion in Pyinstaller Spec to avoid confusion with Upstream fontra
fix(builder): force TTF mode for .fontra sources to prevent CFF2/COLR collision
COLRv1 fonts require TrueType (glyf) outlines — the OpenType spec does not permit CFF2 in a font with COLR/CPAL tables. When a .fontra source contained cubic curves, buildCFF2 was being set to True by the caller, causing:
Two guards added in builder.py:
build() — early guard before prepareGlyphs():
buildFont() — final guard before FontBuilder is instantiated:
cu2qu handles cubic→quadratic conversion transparently during glyf table construction, so forcing TTF mode does not lose any outline fidelity.
Reproducer: open any .fontra font with cubic outlines → Export As → TTF Previously: AssertionError crash in fontra_compile.main.main() Now: compiles cleanly with correct glyf + COLR/CPAL tables
fix(colrv1): correct PaintSweepGradient angle units in dataToPaint — scale startAngle/endAngle by 360.0 to convert Fontra turn fractions (0–1) to degrees expected by paintcompiler. Regression introduced when the same fix was applied to the mergenode variation path in v0.2.4 but missed the static dataToPaint path.
fix(colrv1-renderer): correct PaintComposite color rendering
COLRv1 glyphs with PaintComposite nodes rendered with incorrect colors
due to backdrop paint bleeding directly onto the main canvas context.
Root cause: Canvas 2D globalCompositeOperation composites against
existing canvas content, not against a local backdrop. PaintComposite
requires isolated rendering per the COLRv1 spec.
Fix: render backdrop and source into OffscreenCanvas buffers, apply
composite mode between them, blit result to main canvas.
fixes issue #2
Tested with: knobs, ticket, boarding-pass emoji glyphs.
feat(colrv1): implement async clip glyph rendering for COLRv1 canvas
- Read paint graph from customData["colorv1"] (new backend format)
- Resolve self-referencing PaintGlyph clips from positionedGlyph directly
- Use getGlyphInstance to fetch external clip glyphs asynchronously
- Add module-level _resolvedPathCache to persist paths across render frames
- Add _pendingGlyphs guard to prevent duplicate fetch requests
- Move COLRv1 pre-fetch to positionedLines listener in scene-controller
- Use loadGlyphs for bulk pre-fetching referenced component glyphs
- Canvas now renders COLRv1 glyphs with correct colors and clipping
Clip glyph transform/positioning and axis-aware instantiation are
still to be addressed in a future release.
fix: static COLRv1 font compilation
.woff2) export for both v0 and v1 color fontsRebrand: replace Fontra references with ColrPak
refactor: simplify CompileFontMakeAction to invoke fontmake directly from source path Remove intermediate UFO/designspace export step and helper functions (addInstances, addGlyphOrder, addMinimalGaspTable, _fixColorLibKeys). Instead, unwrap the backend chain to find the original source path and pass it directly to fontmake_main.
fix: execute fontra_compile natively to resolve PyInstaller PATH issues
Previously, exportFontToPathCompile used subprocess.run(["fontra-compile"]).
When packaged with PyInstaller, this caused the application to search the system
$PATH (e.g., ~/.local/bin) for the executable rather than using the bundled module,
causing the compilation to fail.
This replaces the subprocess call with a direct python import of the bundled
fontra_compile.__main__.main function. To maintain exact compatibility with
the existing UI log parser, this commit also:
sys.argv and os.chdir to simulate the external command environmentio.StringIOSystemExit to accurately report the return code in the log filefix1: post hhea and os2 metrics not transmitted from fontra format font-info
Derive hhea and OS/2 metrics from shared source lineMetricsHorizontalLayout and customData instead of letting fontTools default to zeros
startAngle and endAngle keys in Fontra JSON by
providing a 0.0 fallback, preventing silent failures when defaults
are omitted._merge_node to convert Fontra’s turn
fractions (0-1) into the degrees expected by fontTools/paintcompiler.
This fixes the issue where variation deltas in the VarStore were
calculated with the wrong magnitude.
fix(colrv1): correct PaintLinearGradient P2 projection, radial transform, and sweep gradient arc
Three bugs in the COLRv1 canvas renderer caused gradient paints to render incorrectly relative to what the font specifies.
— PaintLinearGradient —
Canvas 2D createLinearGradient takes two points, but COLRv1 defines three: P0 (start), P1 (end), and P2 (rotation anchor). The renderer was passing P0→P1 directly and ignoring P2, producing wrong or reversed gradient axes.
Fix: project P1 onto the perpendicular of (P2−P0) to derive the correct effective end point P1eff before calling createLinearGradient. When P2 coincides with P0 (degenerate case) fall back to P1 unchanged.
— PaintRadialGradient —
COLRv1 radial gradients support an affine transform on the gradient cone, allowing elliptical or rotated radials. The renderer silently discarded paint.transform, so any non-circular radial gradient rendered as a plain symmetric cone.
Fix: wrap the paint in ctx.save()/ctx.restore() and apply paint.transform via ctx.transform() before calling createRadialGradient, so the cone is correctly skewed/rotated by the context matrix.
— PaintSweepGradient —
Three separate errors:
endAngle ignored — createConicGradient was called with only startAngle; endAngle was never read. Partial arc sweeps always filled the full 360°.
Wrong sweep direction — COLRv1 sweep angles are counter-clockwise (font Y-up). Canvas 2D createConicGradient is clockwise (Y-down). The scene transform already flips Y, so the angle must be negated to preserve the correct sweep direction. Without this, all sweeps were mirrored.
Color stops not remapped to arc — stops are defined by the font author relative to the [startAngle, endAngle] arc (0→1 across that arc), but were being passed directly to the conic gradient which interprets them relative to a full 360° turn. Fix: scale each stop offset by arcSpan / (2π) before calling _applyColorLine.
Previews color v1 paint actually what is going to shipped in final font
fontra_compile fix : COLRv1 multi-source variation VarStore population
Exports working variable COLR TTF.
COLRv1 variable font compiles and renders correctly with colour palette and variable axes intact
mitradranirban/colr-pak)edit-tools-paint) for interactive COLRv1 editing:
paletteIndex for PaintSolid layersFontra Pak to Colr Pak in fontra-menuscolr.py — use absolute instead of relative importsmitradranirban/fontra, branch fontra-color-support)2026.3.4paletteIndex in gradient colorStops — _convertColorLine was reading stop.Color?.PaletteIndex but raw fontTools stores PaletteIndex directly on the stop object; all gradient palette indexes were defaulting to 0_setV1ArrayField, _writeV1Paint) and correct colorStops nesting structuremitradranirban/fontra-compile branch fontra-color-support )copyFont stripping color palette data from temporary UFO before compiling through fontmake, which caused color variable fonts to export as monochromelib.plist in variable font compilationcolr-pak 0.1.3
Bugfix: Removed drop-unreachable-glyphs from the export workflow and drop-unused-sources-and-layers from the fontmake compile action in fontra-compile — both filters were silently stripping color layer glyphs before fontmake could build the COLR/CPAL tables, resulting in monochrome output.
mitradranirban/colr-pak).fontra to .ttf/.otf via fontra-compile — resolves
TypeError: run_original() takes 0 positional arguments but 4 were given
crash when exporting from a loaded TTF fontdoExportAs to use top-level exportFontToPath and new
exportFontToPathCompile functions as multiprocessing targets, matching
upstream Fontra Pak structure and preventing future breakageCOLR_PAK_VERSION constant for single-point version managementmitradranirban/fontra, branch fontra-color-support)fix(colrv1): Restore paletteIndex in gradient colorStops —
_convertColorLine was reading stop.Color?.PaletteIndex but raw
fontTools stores PaletteIndex directly on the stop object; all gradient
palette indexes were defaulting to 0fix(colrv1): Fix “add stop” button for COLRv1 gradients — fix method
name references (_setV1ArrayField, _writeV1Paint) and correct
colorStops nesting structurefix(colrv1): TTF COLRv1 paint loading, panel detection and rendering —
.bak file from bisectrelease/0.2.0mitradranirban/fontra-compile)feat: Add COLRv1 compile support via PythonBuilder — reads paint data
and palettes directly from font-data.json/glyph JSON; implements full
_dataToPaint() covering solid, gradients, transforms, composite, with
varscalar() for Fontra keyframe variation specscompile_colorv1_action.py entry point — COLRv1
compilation now handled entirely in build.pycolorV1_export_helper from fontra-compile integration.fontra files exclusively to fontra-compile for proper
COLR and CPAL table compilation2026.3.2add convertPaintGraph/convertColorLine for fontTools raw format
conversion; fix COLRv0 TTF panel detection
fix: Add color palette support for UFO backend.ufo/.designspace (COLRv0), .fontra (COLRv1).glyphs/.glyphspackage (without COLR data).ttf filesfeat(colrv1): Variable font support for .fontra sources + full paint
graph fixes — add getTagLocation(), getPaintGraph(), resolveVal();
fix all 32 paint format handlers including composite modes, PaintGlyph,
bezier curvesfeat(color-layers): Add COLRv1 type-aware parameter UI with
PAINT_PARAM_SCHEMA, paired field rendering, _setV1PaintParam() mutatorfeat(color-palettes): Enhance palette panel — alpha slider, palette tab
strip, usage badges, remove buttons, PALETTES_KEY exportpaintcompiler base COLRv1 builder backendufo2ft working