glyph_path 1.0.0 copy "glyph_path: ^1.0.0" to clipboard
glyph_path: ^1.0.0 copied to clipboard

retracted[pending analysis]

A pure Dart OpenType/TrueType parser and text-to-path converter.

Changelog #

All notable changes to this project will be documented in this file.

This project adheres to Semantic Versioning.

1.0.0 #

Added #

Core parsing

  • TrueType glyph parsingglyf/loca table support for .ttf files
  • CFF/OpenType glyph parsing — compact font format outline support for .otf files, including flex drawing operators (hflex, flex, hflex1, flex1)
  • CFF hhcurveto/vvcurveto — multi-group sequences with a leading delta value are correctly rendered
  • cmap format 4 & 12 — Unicode character-to-glyph mapping with priority-based subtable selection
  • OS/2 table parsing — typographic metrics (sTypoAscender, sTypoDescender, sTypoLineGap) resolved from the USE_TYPO_METRICS flag when set, falling back to hhea values; exposed as Font.ascender, Font.descender, and Font.lineGap
  • advanceWidthForGlyph out-of-range fallback — glyph IDs beyond numberOfHMetrics, and negative glyph IDs, fall back to the .notdef advance width per the OpenType spec instead of throwing RangeError
  • GSUB ligature set isolation — each glyph's ligature rule set is independently allocated, preventing cross-glyph interference
  • GPOS PairPos Format 2 cellSize = 0 — subtables where both value formats are empty are correctly skipped

Layout & measurement

  • TextMeasurement value class — immutable result of measureText() with width, ascent, and descent (all in scaled pixels at the requested font size)
  • measureText — returns a TextMeasurement without generating path data
  • Font.metrics — returns a FontMetrics (unitsPerEm, ascender, descender) without generating paths
  • Kerningkern table (format 0) and GPOS pair-adjustment support
  • Ligature substitution — GSUB liga feature (e.g. fi → fi-ligature)
  • Multi-line layout with word-wrapTextLayout wraps text at a configurable maxWidth; word-wrap decisions use full-string kerning measurement
  • Text alignmentTextAlign.left, TextAlign.center, and TextAlign.right
  • TextLayout metric scalingascender and descender are scaled to pixel units at the requested font size

Path data

  • Sealed PathCommand hierarchyMoveTo, LineTo, QuadTo, CubicTo, ClosePath
  • normalizeToCubic — converts all QuadTo to CubicTo for uniform TTF/OTF output; enables cross-format glyph morphing
  • Per-glyph accessgenerateGlyphPaths() returns one GlyphPathResult per rendered glyph with glyph-local commands, tight bounds (computed from Bézier curve extrema), advanceWidth, positionX, contours, and sourceText
  • Font.generateRawGlyphPaths — returns TextPathResult in design-unit coordinates (scale = 1.0); intended for layout engines and morphing workflows
  • Contour decompositionGlyphPathResult.contours is a List<Contour> where each Contour holds its commands, WindingDirection, and signedArea (shoelace / 2)
  • splitIntoContours — top-level function that splits any List<PathCommand> into List<Contour>, supporting both closed and open sub-paths

Contour manipulation

  • Contour.flipY() — returns a new Contour with Y coordinates negated and winding flipped; use instead of PathCommandListTransform.flipY() to keep winding and signed area consistent
  • Contour.reversed() — returns a new Contour tracing the same closed outline in the opposite direction; swaps CubicTo control points and flips winding
  • Contour.rotated(int shift) — shifts the start vertex of a closed outline by shift on-curve vertices without changing shape or winding
  • PathCommandListTransform — extension on List<PathCommand> exposing translate(dx, dy), scale(factor), and flipY()

Export

  • SVG path dataTextPathResult.toSvgPathData() returns an SVG d attribute string
  • PDF content streamTextPathResult.toPdfContentStream() emits PDF path operators (PDF 1.7 §8.5.2); quadratic curves are automatically elevated to cubic
  • JSONTextPathResult.toJson() serialises all commands to List<Map<String, dynamic>>
  • Flutter ui.PathTextPathResult.toUiPath() via conditional export; stub provided for Pure Dart environments

Caching

  • Dual-cache with independent size controls — glyph-outline cache (maxGlyphCacheSize, default 256) and shape cache (maxShapeCacheSize, default 256) backed by cacherine with LRU eviction; configured via Font.parse(maxGlyphCacheSize: N, maxShapeCacheSize: M)

Examples

  • Six runnable examples covering basic usage, SVG, PDF, multi-line layout, JSON, and Flutter Canvas integration

Fuzz testing

  • test/fuzz/ — self-contained random-mutation fuzz harness with no external dependencies; targets the whole-font entry point (Font.parse), twelve isolated table parsers (cff, cmap, glyf, gpos, gsub, head, hhea, hmtx, kern, loca, maxp, os2), and the text-shaping API (TextLayout.layout)
  • test/fuzz/helpers.dart — shared runFuzzTarget helper with Stopwatch-bounded loop, FontParseException-swallow policy, and automatic crash reproducer saving
  • test/fuzz/corpus/ — seed corpus derived from existing binary test fixtures; crash reproducers are written here and re-checked on every run
  • .github/workflows/fuzz.yml — CI job running all fuzz targets (60 s on PR/push, 600 s on scheduled runs); uploads the corpus as an artifact on failure

Security #

  • Font.parse() preflight — validates the sfnt magic bytes and all required tables (head, hhea, maxp, cmap, hmtx) before any further parsing; unsupported sfnt versions are rejected immediately
  • BinaryReader.slice() — every table parser receives a reader scoped to the table's declared byte range, making cross-table reads structurally impossible
  • FontParseException — structural errors encountered while parsing the font binary itself (missing/truncated tables, out-of-range offsets, malformed structures) are raised as a single typed exception with a descriptive message and optional byte offset. This is distinct from invalid caller input (oversized text, non-finite/out-of-range fontSize, non-positive cache sizes), which raises ArgumentError directly and is never converted to FontParseException — see SECURITY.md for the full distinction
  • ResourceLimits — abstract class of static const hard caps enforced throughout parsing and text shaping:
    • maxTables (64), maxGlyphs (65535)
    • maxCompositeDepth (8), maxCompositeComponents (64) — composite glyph nesting and component count
    • maxCompositeOutputCommands (65535), maxPathCommandsPerGlyph (131072) — composite glyph output size, per level and cumulative
    • maxContoursPerGlyph (256), maxPointsPerGlyph (32768) — TrueType outline geometry
    • maxCmapIterations (65536) — format 4 segment expansion
    • maxCmapGroups (10000), maxCmapFormat12Span (1114112) — format 12 group count and cumulative code-point span
    • maxKernPairs (65535), maxGposPairs (65535), maxGposMatrixCells (65535), maxGposFeatureLookupRefs (65535) — kern and GPOS pair/feature data
    • maxCoverageGlyphs (65535), maxClassDefEntries (65535) — GPOS Coverage/ClassDef expansion
    • maxLigaRules (10000), maxLigatureComponentCount (16), maxLigatureEvalOps (1000000) — GSUB ligature table size and per-call evaluation budget
    • maxCoverageRangeRecords (20000), maxGsubCoverageScanSteps (1000000), maxGsubLigSetVisits (1000000), maxGsubSubtablesPerLookup (64), maxGsubFeatureLookupRefs (65535) — GSUB coverage-scan, LigatureSet-visit, and lookup/feature-reference budgets
    • maxCffSubrDepth (10), maxCffTotalSubrCalls (10000), maxCffStackDepth (513), maxCffStemCount (96), maxCffOutputCommands (32768) — CFF Type 2 subroutine nesting/fanout, operand stack, and output size
    • maxInputTextLength (4096 UTF-16 code units), maxFontSize (1e6) — public API argument validation; both throw ArgumentError before any parsing or glyph-resolution work begins
  • Composite glyph cycle detectionFont detects circular component references during composite glyph resolution and throws FontParseException; previously a crafted font could cause infinite recursion
  • cmap format 4 DoS guardsegCountX2 validated to be even; length checked against the minimum size for the four segment arrays before any read; cumulative iteration count bounded by maxCmapIterations
  • cmap format 12 DoS guard — group count bounded by maxCmapGroups before allocation; cumulative code-point span bounded by maxCmapFormat12Span (prevents CPU exhaustion from overlapping groups); total character map entries bounded by maxGlyphs
  • CFF INDEX validationoffSize validated to be in [1, 4] before reading any INDEX data; offsets using the invalid 1-based encoding are rejected
  • CFF FDSelect Format 3 sentinel — range boundaries are validated for strict monotonic order; a non-monotonic sentinel triggers FontParseException
  • CFF FDSelect Format 0 bounds check — the per-glyph FD-index byte count is validated against the remaining buffer before reading, raising FontParseException directly instead of a raw RangeError
  • CFF charstring interpreter — multi-byte number encodings (byte 28, byte 255, bytes 247–254) checked for sufficient remaining bytes; byte position verified to advance each iteration (stalled iteration → FontParseException); subroutine call depth capped at maxCffSubrDepth; operand stack depth capped at maxCffStackDepth
  • GPOS Non-Fatal boundary — all GPOS parse errors are caught and swallowed; GPOS kerning is silently disabled on malformed data rather than aborting font load
  • GSUB Non-Fatal boundary — GSUB parse failures disable ligatures without propagating to the caller; FontParseException, RangeError, and internal ArgumentErrors raised while parsing the table's own bytes are all caught (mirrors the GPOS Non-Fatal boundary) — unrelated to the public-API-boundary ArgumentError (see the FontParseException entry above), which is never caught or converted. This covers every limit checked inside parseGsub() itself; maxLigatureEvalOps is checked later, once per text-shaping call, and its FontParseException is not swallowed — see SECURITY.md
  • GSUB LigatureSet-visit budget — cumulative LigatureSet visits across all liga lookups/subtables in a single parse are bounded by maxGsubLigSetVisits, independent of maxGsubCoverageScanSteps; without it, a Coverage Format 1 table makes each visit O(1), so a font of only a few tens of kilobytes could reuse one physical subtable across every offset entry to declare hundreds of billions of visits
  • kern table bounds — at least 4 bytes required before reading the table header; each subtable requires at least 6 bytes; subtables with length < 6 are rejected; cumulative pair count bounded by maxKernPairs
  • kern Apple-format header detection — a table version of 0x00010000 (Apple's Fixed-point kern version, laid out as uint32 version + uint32 nTables) is detected explicitly and treated as unsupported (no kerning), rather than relying on the incidental fact that reinterpreting its header as the Microsoft/OpenType uint16+uint16 layout happens to yield nTables = 0
  • loca offset validation — every offset is checked to fall within the glyf table's byte range; descending offsets (negative glyph size) are rejected
  • glyf SimpleGlyph flag consistency — flag array length and on-curve point consistency validated during outline parsing
  • Dependency auditcacherine 2.4.0 and its transitive dependency synchronized 3.4.0+1 have no known advisories as of 2026-06-24; see SECURITY.md
  • SECURITY.md added — vulnerability reporting instructions and full dependency audit record
0
likes
0
points
77
downloads

Publisher

verified publishercrossapplication.members.co.jp

Weekly Downloads

A pure Dart OpenType/TrueType parser and text-to-path converter.

Repository (GitHub)
View/report issues

Topics

#font #text #svg #opentype #truetype

License

(pending) (license)

Dependencies

cacherine, meta

More

Packages that depend on glyph_path