Rendering a string is not a direct lookup from one Unicode character to one image. A text engine must choose fonts, segment runs, shape contextual glyphs, position them, rasterize outlines or color glyphs, then composite pixels. Understanding these boundaries prevents an encoding bug from being misdiagnosed as a font problem.
Separate abstract characters, glyph IDs and pixels
A Unicode scalar is a number in a coded character repertoire; a glyph ID is an index meaningful only within a particular font face. A cluster associates some portion of source text with one or more shaped glyphs. An image of the glyph is produced later and may differ by scale, hinting, graphics backend or color-font support. It is therefore invalid to infer the stored Unicode string from the number of visible shapes.
The string fi consists of U+0066 and U+0069. A font and shaping configuration may display the pair using a single ligature glyph, yet the underlying text generally remains two characters. Conversely, one code point can be rendered by multiple glyphs in some fonts and scripts.
Define the stages and their ownership
Start with decoded scalar values, Unicode segmentation and paragraph-level bidirectional resolution. Itemization then groups compatible direction, script, language and font runs. Shaping maps characters to font-specific glyph sequences and positions. Line layout resolves widths, wrapping and baselines. Rasterization converts outlines or paint graphs to drawing primitives; compositing combines those results with the display.
No single library necessarily implements every stage. HarfBuzz primarily shapes runs, whereas platform layout and graphics libraries decide run boundaries, line breaking, font fallback and drawing. A browser additionally applies CSS font selection, feature settings and language information. Recording the stage where output first diverges is more productive than simply noting that two browsers look different.
Unicode normalization is not a shaping operation
Canonical-equivalent sequences may visually converge after shaping, but shaping is not a general-purpose data-normalization contract. For instance, U+00E9 and U+0065 U+0301 may render similarly in suitable fonts. The two strings remain different sequences until an application performs an explicit normalization operation.
A renderer is also not required to make mathematically styled symbols semantically equal to ASCII letters. The character U+1D400 MATHEMATICAL BOLD CAPITAL A and the ordinary U+0041 A are distinct scalars despite similar letter identity. Visual equivalence cannot safely replace application-level equality, search policy or accessibility text.
Shaping decisions depend on script, language and direction
Arabic joining, Indic reordering, mark placement and Latin ligatures demonstrate different shaping requirements. Script tags and language-system data select appropriate lookups; direction establishes glyph ordering and advances. A forced left-to-right direction can produce incorrect shaping for an otherwise perfectly valid Arabic string.
A font's OpenType GSUB table can substitute contextual forms or ligatures. Its GPOS table may place marks and adjust pair positioning. GDEF can classify glyphs and define ligature carets. These tables describe font behavior; the text engine must still apply script-specific shaping rules and select applicable lookups.
Why raster output changes without changing text
After shaping, identical glyph IDs and advances may be drawn with different hinting modes, grayscale or subpixel antialiasing, color-font technologies and pixel densities. Variation axes and synthetic bolding can alter geometry. Device scaling and fractional positioning also affect apparent crispness.
A screenshot difference is therefore not evidence that either implementation changed the Unicode sequence. Compare shaping output at fixed font versions and feature settings before investigating rasterization. If glyph IDs and advances match but edges differ, look downstream; if they already differ, inspect fonts, language, shaper configuration and fallbacks.
Font fallback interrupts simplistic one-run models
Fallback is not necessarily a per-character replacement operation. A renderer may select a fallback font for a run or sequence to preserve combining marks or emoji presentation. Splitting a grapheme cluster across unrelated fonts can cause misplaced marks or broken emoji composition.
The fallback policy is platform-specific, and glyph coverage alone does not establish ability to shape a sequence correctly. For example, finding isolated coverage for all characters in a ZWJ emoji sequence does not prove that the selected font can produce the intended combined presentation.
Measure clusters and caret positions carefully
Text interfaces need mappings between source offsets and positioned glyph clusters to support cursor navigation, hit-testing and selections. Ligature substitutions may combine multiple source characters into a single glyph; a high-quality editor still needs caret placement within or around that cluster. Visual positions must not be treated as UTF-16 indices.
Extended grapheme clusters from UAX #29 are useful default editing boundaries, but shaping clusters are not guaranteed to align one-for-one with them. Do not use output glyph count to calculate number of user-perceived characters or to reconstruct logical order in bidirectional text.
A differential debugging strategy
Capture the exact Unicode scalar sequence, normalization form if relevant, script/language/direction, selected font file and version, shaping engine and features, glyph IDs/advances, and final screenshot. Change one variable at a time. If the scalar sequence is already wrong, stop investigating the rasterizer.
When glyph substitution differs, compare font tables and shaping configuration. When glyph positions differ but IDs match, inspect GPOS and scaling. When only painted pixels differ, investigate the raster backend and display characteristics. This layered evidence makes a bug report reproducible across toolkits.
Reproducible experiment: preserve the input while comparing the output
The following specimen is deliberately small enough to audit manually. For any test involving rendering, also record the exact font files, shaping configuration, user locale and platform versions. The code prints or illustrates source-level facts; it does not claim that JavaScript can identify a platform's selected font or glyph IDs without the relevant layout instrumentation.
const text = 'fi e\u0301 ๐';
const scalars = [...text].map(ch => 'U+' + ch.codePointAt(0).toString(16).toUpperCase());
console.log(scalars); // input scalars, not the final number of glyphs
console.log(text.normalize('NFC'));
// Keep font file, feature tags, script, language and direction fixed
// when comparing actual glyph IDs and advances from a shaping engine.A useful report includes both positive and negative evidence. Record what stayed identical as well as what changed: identical scalar values with different glyphs indicate a presentation-layer divergence, whereas differing scalars reveal a transformation, selection or interchange problem. Preserve the original code points before applying normalization or conversion.
Evidence matrix for shaping and rasterization
| Layer or specimen | Controlled observation | Correct interpretation |
|---|---|---|
| Input | U+0066 U+0069 | ligature may substitute one font glyph; source remains two scalars |
| Canonical forms | U+00E9 vs U+0065 U+0301 | same-looking output does not imply identical source bytes |
| Presentation | U+2764 U+FE0F | emoji selection depends on font and platform support |
| Positioning | base + combining accent | mark anchoring needs layout support |
| Pixels | same glyph at 1x vs 2x | hinting and antialiasing can differ |
This matrix is a diagnostic aid, not a substitute for conformance testing. Some differences are allowed by Unicode or CSS; others indicate a bug in an implementation, a font, a font-subsetting pipeline or an application that edits text. Be explicit about the relevant standard and the end-user expectation.
Validation protocol for applications and browser integrations
- Input: Reproduce the observed behavior using U+0066 U+0069 as a controlled input. Preserve exact source data, capture an expected outcome, and explain whether the measured difference is located in font selection, shaping, layout or copying. The useful observation is ligature may substitute one font glyph; source remains two scalars; do not extrapolate from a screenshot alone.
- Canonical forms: Reproduce the observed behavior using U+00E9 vs U+0065 U+0301 as a controlled input. Preserve exact source data, capture an expected outcome, and explain whether the measured difference is located in font selection, shaping, layout or copying. The useful observation is same-looking output does not imply identical source bytes; do not extrapolate from a screenshot alone.
- Presentation: Reproduce the observed behavior using U+2764 U+FE0F as a controlled input. Preserve exact source data, capture an expected outcome, and explain whether the measured difference is located in font selection, shaping, layout or copying. The useful observation is emoji selection depends on font and platform support; do not extrapolate from a screenshot alone.
- Positioning: Reproduce the observed behavior using base + combining accent as a controlled input. Preserve exact source data, capture an expected outcome, and explain whether the measured difference is located in font selection, shaping, layout or copying. The useful observation is mark anchoring needs layout support; do not extrapolate from a screenshot alone.
- Pixels: Reproduce the observed behavior using same glyph at 1x vs 2x as a controlled input. Preserve exact source data, capture an expected outcome, and explain whether the measured difference is located in font selection, shaping, layout or copying. The useful observation is hinting and antialiasing can differ; do not extrapolate from a screenshot alone.
Keep regression fixtures in UTF-8 with explicit scalar annotations when invisible characters, combining sequences or non-BMP characters are present. Test a stable baseline and at least one deliberately adversarial input. Store observed environment data alongside results so later font or browser updates can be interpreted rather than mistaken for content edits.
Failure patterns, false positives and interpretation
- Overinterpreting input. A result associated with U+0066 U+0069 does not, by itself, establish the integrity of the whole text pipeline. ligature may substitute one font glyph; source remains two scalars. Compare the input and an independently observed intermediate state before assigning the defect to a component.
- Overinterpreting canonical forms. A result associated with U+00E9 vs U+0065 U+0301 does not, by itself, establish the integrity of the whole text pipeline. same-looking output does not imply identical source bytes. Compare the input and an independently observed intermediate state before assigning the defect to a component.
- Overinterpreting presentation. A result associated with U+2764 U+FE0F does not, by itself, establish the integrity of the whole text pipeline. emoji selection depends on font and platform support. Compare the input and an independently observed intermediate state before assigning the defect to a component.
- Overinterpreting positioning. A result associated with base + combining accent does not, by itself, establish the integrity of the whole text pipeline. mark anchoring needs layout support. Compare the input and an independently observed intermediate state before assigning the defect to a component.
- Overinterpreting pixels. A result associated with same glyph at 1x vs 2x does not, by itself, establish the integrity of the whole text pipeline. hinting and antialiasing can differ. Compare the input and an independently observed intermediate state before assigning the defect to a component.
Do not correct a visual artifact by silently rewriting the user's Unicode string, stripping default-ignorable characters without context, replacing unknown scalars with question marks, or flattening rich text without consent. Data integrity, rendering aesthetics, selection behavior and search policy have different constraints. A fix at one layer can create regressions in another.
Standards and primary implementation references
Primary references are linked below. A standard describes normative requirements or terminology, while an implementation manual describes a particular engine. Where behavior is unspecified or depends on fonts and applications, treat observations as environment-specific rather than universal guarantees.
- Unicode Standard: Conformance โ use this normative reference or implementation manual to verify the applicable definitions and requirements for shaping and rasterization. Distinguish what the source specifies from what a particular font or platform happens to implement.
- HarfBuzz: Getting started โ use this normative reference or implementation manual to verify the applicable definitions and requirements for shaping and rasterization. Distinguish what the source specifies from what a particular font or platform happens to implement.
- OpenType Layout overview โ use this normative reference or implementation manual to verify the applicable definitions and requirements for shaping and rasterization. Distinguish what the source specifies from what a particular font or platform happens to implement.
- CSS Fonts Module Level 4 โ use this normative reference or implementation manual to verify the applicable definitions and requirements for shaping and rasterization. Distinguish what the source specifies from what a particular font or platform happens to implement.
Related research and boundaries
This article belongs to the Fonts, Shaping & Text Rendering virtual silo. Related published technical notes explain adjacent layers so the original problem can be isolated without conflating scalar identity, shaping output, font support and editor behavior.
When reproducing a finding, cite the relevant standard, the exact Unicode sequence and the environment in which it occurred. This preserves the value of a technical observation even after browsers, fonts and rendering libraries change.