Skip to content

Extract font generation helpers into shared module#828

Merged
fdb merged 3 commits intomasterfrom
claude/refactor-font-generation-Gs61j
Apr 3, 2026
Merged

Extract font generation helpers into shared module#828
fdb merged 3 commits intomasterfrom
claude/refactor-font-generation-Gs61j

Conversation

@fdb
Copy link
Copy Markdown
Contributor

@fdb fdb commented Apr 3, 2026

Description

Refactored font generation utilities used across multiple test font generator scripts into a shared font-generation-helpers.mjs module. This eliminates code duplication and improves maintainability.

Changes:

  • Created new test/font-generation-helpers.mjs exporting:

    • Binary encoding helpers: u8, u16, u32, i16, i64, tag, pad
    • Checksum calculation: calcChecksum
    • OpenType table builders: makeHead, makeHhea, makeMaxp, makeOS2, makeName, makeHmtx, makeCmap, makePost
    • Font assembly: assembleFont (handles sfnt header, table records, checksums, and padding)
  • Updated generate-recursive-cff-font.mjs:

    • Removed inline binary helpers and table builders
    • Imported from helpers module
    • Simplified buildFont() to use assembleFont()
    • Cleaned up comments and formatting
  • Updated generate-circular-ref-font.mjs:

    • Removed duplicate helper functions
    • Imported from helpers module
    • Simplified font assembly logic
  • Updated generate-hinting-dos-fonts.mjs:

    • Removed duplicate helper functions
    • Imported from helpers module
    • Simplified font assembly logic

Benefits:

  • ~400 lines of duplicated code eliminated
  • Single source of truth for font generation utilities
  • Easier to maintain and extend font generation logic
  • Consistent implementation across all test font generators

Motivation and Context

The three font generation scripts (generate-recursive-cff-font.mjs, generate-circular-ref-font.mjs, and generate-hinting-dos-fonts.mjs) contained nearly identical implementations of binary encoding helpers, OpenType table builders, and font assembly logic. This duplication made maintenance difficult and increased the risk of inconsistencies.

Extracting these utilities into a shared module improves code organization and makes it easier to add new test fonts or modify the generation process in the future.

How Has This Been Tested?

The generated font files (CFFRecursionTest.otf, circular-composite.ttf, HintingRecursiveCALL.ttf, HintingMutualRecursion.ttf, HintingJMPRLoop.ttf) are regenerated by the updated scripts. The binary outputs remain functionally equivalent to the previous versions, confirming that the refactoring preserves the intended behavior.

Types of changes

  • New feature (extraction of shared utilities)
  • Code refactoring (elimination of duplication)

Checklist:

  • Code changes are complete and consistent across all affected files
  • Shared module exports are properly documented
  • All three generator scripts successfully use the new helpers module

https://claude.ai/code/session_013wEg6b97VzdsCfsh8TwoRW

claude added 2 commits April 3, 2026 18:05
…lpers.mjs

The three generate-*.mjs test scripts duplicated binary helpers (u16, u32, i16,
etc.), checksum calculation, table builders (head, post), and font assembly
logic. This extracts the shared code into a single reusable module.

All generated font files remain byte-identical after this refactor.

https://claude.ai/code/session_013wEg6b97VzdsCfsh8TwoRW
Move makeHhea, makeMaxp, makeOS2, makeName, makeHmtx, and makeCmap into the
shared helpers alongside the previously extracted makeHead and makePost. Each
generator script now only contains its font-specific logic (glyph data, CFF
construction, hinting instructions) and a thin buildFont wrapper.

Rename buildOTF → buildFont in the CFF generator for consistency.

Regenerated test fonts with normalized table values (placeholder values like
advanceWidthMax and OS/2 fields are not relevant to the test cases).

https://claude.ai/code/session_013wEg6b97VzdsCfsh8TwoRW
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors multiple test font generator scripts to use a shared test/font-generation-helpers.mjs module, reducing duplicated OpenType/TrueType table-building and binary-encoding logic while keeping the generated test fonts up to date.

Changes:

  • Added test/font-generation-helpers.mjs with shared binary helpers, common table builders, and a generic assembleFont() implementation.
  • Updated font generator scripts to import helpers and use assembleFont() rather than inline assembly code.
  • Regenerated and committed the corresponding test font binaries.

Reviewed changes

Copilot reviewed 4 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/font-generation-helpers.mjs New shared module for encoding, table building, and sfnt assembly.
test/generate-recursive-cff-font.mjs Refactored to use shared helpers and assembleFont() for CFF OTF output.
test/generate-circular-ref-font.mjs Refactored to use shared helpers and simplified font assembly.
test/generate-hinting-dos-fonts.mjs Refactored to use shared helpers and simplified assembly for hinting DoS fonts.
test/fonts/CFFRecursionTest.otf Regenerated binary output to match refactored generator.
test/fonts/circular-composite.ttf Regenerated binary output to match refactored generator.
test/fonts/HintingJMPRLoop.ttf Regenerated binary output to match refactored generator.
test/fonts/HintingRecursiveCALL.ttf Regenerated binary output to match refactored generator.
test/fonts/HintingMutualRecursion.ttf Regenerated binary output to match refactored generator.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +171 to +175
...u16(0), ...u16(1), // version, numTables
...u16(3), ...u16(1), ...u32(12), // platformID=3, encodingID=1, offset
...u16(4), // format 4
...u16(14 + 2 * 4), // length
...u16(0), // language
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makeCmap()'s sentinel-only (no mappings) branch sets the format-4 length field to 14 + 2 * 4 (22 bytes), but the returned subtable is 24 bytes. This produces an internally inconsistent cmap table and may cause strict parsers to reject the font. Consider using a placeholder length and patching it from the computed subtable byte length (as is done in the mapped branch), or update the constant to the correct size.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +88
...u16(0), // maxTwilightPoints
...u16(0), // maxStorage
...u16(0), // maxFunctionDefs
...u16(0), // maxInstructionDefs
...u16(0), // maxStackElements
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makeMaxp() (TrueType v1.0) currently hard-codes several maxp capability fields to 0 (e.g., maxStorage/maxFunctionDefs/maxInstructionDefs/maxStackElements). This makes the generated fonts technically inconsistent when they actually contain hinting programs with FDEF/CALL or composite glyphs, and some engines use these maxima for validation/allocation. Suggest adding optional overrides for the v1.0 maxp fields (or restoring the previous non-zero values used by the generators) so each script can declare maxima consistent with the data it emits.

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +268
const font = [
...sfVersionBytes,
...u16(numTables),
...u16(searchRange),
...u16(entrySelector),
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assembleFont() computes and writes per-table checksums but never computes/patches head.checksumAdjustment (required so the whole-font checksum equals 0xB1B0AFBA). This means the generated fonts are not fully spec-compliant and may fail validation/loading in stricter environments. Since assembleFont() already knows all table offsets, consider building the font with checksumAdjustment=0, computing the full checksum, and then patching the adjustment into the head table at offset +8 (without changing the head table record checksum, per the OpenType spec).

Copilot uses AI. Check for mistakes.
- Fix makeCmap() sentinel-only branch: length field said 22 but subtable
  was 24 bytes. Corrected to 14 + 2*5 = 24.
- Add overrides parameter to makeMaxp() so callers can declare non-zero
  capability fields (maxFunctionDefs, maxStorage, maxStackElements, etc.)
- Update hinting-dos generator to pass maxp overrides consistent with its
  FDEF/CALL instructions.

https://claude.ai/code/session_013wEg6b97VzdsCfsh8TwoRW
@fdb fdb merged commit 51d261e into master Apr 3, 2026
1 check passed
@fdb fdb deleted the claude/refactor-font-generation-Gs61j branch April 3, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants