Implement full bidirectional hinting for CFF and TrueType rendering - #552
Merged
Conversation
Adds a full bidirectional hinting mode reproducing classic GDI-style rendering for small text (ImageSharp.Drawing #134): - The interpreter lifts the v40 backward-compatibility movement gates under the new mode and reports the v35 bi-level engine identity through GETINFO, so fonts execute their classic grid fitting branches. - Phantom point originals stay unrounded per TT_Hint_Glyph; only the dependent current positions round, fixing mirrored stem placement. - The em square is constrained to whole pixels under the new mode and hinted whole-pixel advances flow into layout, resolved from the hdmx table (newly parsed) or the rounded linear advance. - A geometric grid fitter fully fits axes the instructions left unfitted and rescues sub-pixel strokes on instruction-fitted axes, standing in for bi-level dropout control at the outline level. - Emit origins snap to whole pixels for upright full-hinted renders. - Scaled outline caches and renderer parameters key on hinting mode; the glyph zone lives in interpreter-owned reusable buffers so the hint path performs no per-glyph allocation. Sub/superscript control value scaling now follows the outline scale, and hinting faults are retained for diagnostics instead of being swallowed. Benchmarks show +2% warm-path cost and no added allocation.
Charstrings now evaluate once per pixel size into a buffered outline cached per size and hinting mode, replayed through a unit-scale transforming renderer so rendering arithmetic is reproduced exactly. This removes the per-render charstring re-parse and provides the outline surface the geometric grid fitter and CFF-based COLR support build on. The Private DICT hinting values the parser previously discarded are now retained: alignment zones (delta decoded), blue policy parameters, standard stem widths, stem snap families and the language group.
Stem operators previously consumed only to keep the operand stack balanced now optionally decode into absolute low/high edge pairs: hstem/hstemhm record horizontal zones, vstem/vstemhm and the implicit operands preceding mask operators record vertical zones, and ghost stems keep their inverted edges. Collection is enabled only by outline building, which scales the zones into pixel space and retains them on the buffered outline for the geometric grid fitter to consume as authoritative stem edges.
The geometric grid fitter gains a buffered outline entry whose edges are seeded directly from the declared stem zones, so the detection and pairing heuristics never run for CFF; anchor snapping, stem width normalization, counter preservation and interpolation proceed as for detected edges. Private DICT blue zone flats provide the alignment anchors, generalizing the fitter options from the TrueType specific x-height and cap height pair to an anchor list. Under full hinting CFF constrains the em square to whole pixels, fits the buffered outline once per size, and snaps the composed replay translation to the grid for upright renders through a sign exact origin adjustment. Standard and unhinted CFF rendering remain byte identical.
CFF hinting modes now mirror the TrueType interpreter: unhinted output is untouched, standard hinting fits the vertical axis only from the declared horizontal stem zones and blue zone flats, and full hinting fits both axes. Outline caches key on the actual mode since each now shapes geometry differently. The grid fitter anchor list becomes font level state in design units with a per size scale on the options: blue zone flats precompute at parse time and TrueType lazily shares one x-height and cap height array, so per glyph fitting no longer allocates anchor arrays.
Renders a small size ladder of a professionally hinted CFF face under each hinting mode so fitting behavior is visually pinned alongside the numeric assertions: unhinted, vertical only fitting with even baselines and x-heights, and full grid fitting with crisp stems.
The CFF hinting mode ladder now mirrors the TrueType robustness tests exactly, sizes five through sixty three over the same pangram, pinning the current fitting baseline for the quality iteration driven by the GDI comparison harness. CffGlyphMetrics exposes the buffered outline and hinting values internally for diagnostics and tests.
Top alignment zones now take the ceiling of the flat edge so an x-height or cap height always earns its full pixel row, matching bi-level rasterizer output; each edge snaps to its nearest anchor rather than the first in declaration order. Descender zone flats from OtherBlues anchor bottom edges, ghost stems seed single alignment edges instead of being discarded, and overshoot ink flattens onto its fitted row. Full hinting also substitutes whole pixel advances for CFF glyphs through a virtual hinted advance lookup shared with the TrueType path.
Declared stems whose flanks both carry a sustained run of outline, such as straight stem sides and the tall sweeps of bowl sides, round their widths with a downward bias so a stroke of around one and a half pixels renders as a single crisp pixel, matching classic rasterizer regularization of declared hints. Diagonal strokes are excluded: their flanks cross the declared zone in a short stretch and narrowing them drops per row coverage below the threshold, breaking the stroke apart. Grid fitting instrumentation now also covers the declared edge path.
The first counter mask in a charstring names the stems participating in counter control; the evaluation engine now counts them per axis during stem collection. When a mask covers all of an axis''s stems, three or more, the grid fitter chains successive stem centers by the pitch to the previous fitted stem rounded to whole pixels, so the equal counters the glyph was authored with stay equal in the fit instead of each center rounding its own way. The chain accepts an extra half pixel of movement over the per flank cap because a chained stem accumulates the rounding of every pitch before it.
The declared stem path is rebuilt on the hint map architecture of the Adobe CFF engine. Hints captured by an alignment zone band move rigidly onto the zone''s fitted row and lock; remaining hints are positioned through a captured-only initial map so stems keep their place relative to aligned features, then adjust so one edge lands on a pixel boundary, preferring the smallest move that preserves half a pixel of counter to each neighbor, with blocked moves retried top down. Insertion order resolves conflicts: overlapping hints are rejected in favor of earlier, captured ones. Every point transforms through the resulting piecewise linear map, so curves crossing a zone stretch smoothly between hint edges instead of collapsing onto them. Ghost bottom edges now use the correct second pair value. Alignment zones travel as full bands with their fuzz rather than flat lists, and the counter mask and wall width treatments carry over on top of the map. The flank membership, satellite, and interpolation passes no longer apply to declared hints and remain for detected ones.
Replaces the geometric GlyphGridFitter with a GDI-accurate hinting pipeline for both TrueType and CFF formats. TrueType changes: - Scale outlines using GDI's integer scl_ComputeScaling routine (TrueTypeScaler) for exact 26.6 coordinates - Pass unscaled font-unit points to the interpreter for correct IP/IUP interpolation - Implement integer fixed-point arithmetic throughout (Project, MovePoint, SHPIX, IUP, ISECT, MUL, normalize) - Add SCANCTRL/SCANTYPE support and Inhibited hinting result - Fix phantom point origin shift (fsg_SimpleInnerGridFit) and GETINFO version reporting - Remove GlyphGridFitter, GridFitOptions, GridFitAxisMode, GridFitterTuning CFF changes: - Replace scaled stem storage with character-space stems and a new HintMap fitter - Add CffFixedPoint, CffBlueZoneTable, DeviceZone, HintMap, HintMapOptions - Add per-region hintmask support (CffHintRegion, CffHintMask, CffCounterMask) - Implement cubic-to-quadratic conversion via GDI's QueryCurveTo path - Move HintZone to Cff namespace; add ExpansionFactor and FamilyZones to CffHintingValues Layout/rendering: - Snap composed outline translations via shared SnapComposedTranslation for both formats - Substitute whole-pixel hinted advances on the flow axis only - Add layout tests for horizontal, vertical, mixed, and fractional-origin hinting
Moves hinted outline-origin resolution into shared `FontGlyphMetrics` flow so renderer bounds and emitted outlines use the same snapped translation, even when `BeginGlyph` short-circuits to renderer-side caches. Adds `TryGetFittedOutlinePlacement` support for TrueType and CFF, including cached fitted horizontal ink extents for CFF full vertical hinting, and expands hinting tests to assert bounds/outline delta consistency and fitted CFF extent availability.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prerequisites
Description
This pull request introduces significant enhancements to glyph hinting and rendering in the font engine, primarily by adding support for a new
Fullhinting mode and refactoring how hinting parameters are propagated and used throughout the rendering pipeline. The changes improve outline placement accuracy, device pixel alignment, and caching behavior, while also making the codebase more extensible for future hinting strategies.Glyph hinting and rendering improvements
HintingMode.Fullvalue to enable full horizontal and vertical pixel grid alignment for glyph outlines, enhancing sharpness at small sizes (HintingMode.cs).RenderToinFontGlyphMetricsto resolve and apply hinting modes more precisely, including device pixel snapping for decoration origins and outline placement, and passing hinting mode through to renderer parameters. [1] [2] [3] [4]FontGlyphMetricsfor extensible hinting:GetScaledSize,TryGetHintedAdvanceWidth,ResolveHintingMode,ResolveOutlineOrigin,TryGetFittedOutlinePlacement, andSnapComposedTranslation, enabling format-specific control over outline fitting and placement. [1] [2]Renderer and metrics parameter propagation
GlyphRendererParametersto include the requestedHintingMode, ensuring renderer caches and equality checks account for hinting strategy. [1] [2] [3] [4] [5]GetScaledSizeand related methods to accept and propagateHintingMode, allowing for hinting-aware scaling and layout. [1] [2]TrueType font and hinting support
StreamFontMetrics.TrueTypeto support device advance width lookups and store diagnostic information about hinting faults, and refactored hinting application to return a result and accept both scaled and unscaled glyph vectors. [1] [2]Minor improvements
LoadFontNamefor clarity.These changes collectively improve the fidelity and configurability of glyph rendering, especially for scenarios requiring precise pixel alignment and advanced hinting strategies.