Skip to content

Inherit from the parent element's computed values in the standard CSS mode - #683

Merged
jakejackson1 merged 1 commit into
gravitypdffrom
feature/547-computed-values
Oct 6, 2026
Merged

jakejackson1 merged 1 commit into
gravitypdffrom
feature/547-computed-values

Conversation

@gravitypdf-bot

@gravitypdf-bot gravitypdf-bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Step 26 of #520. Under the standard cssMode, every element now inherits from its parent's computed values, as in a browser. The legacy cascade is unchanged.

What was wrong

mPDF passed inherited values along several separate channels:

  • a block's saved text state, to its children;
  • base_table_properties and PARENTCELL, to a table's cells;
  • row-group and row merges;
  • the properties WriteFixedPosHTML() puts on the <div> that stands in for a positioned block.

Each channel carried its own set of properties in its own form. So:

  • An em length was read again at the child's size. With letter-spacing: 0.1em on a 20pt block, a 10pt paragraph inside it spaced its letters 1pt apart, not 2pt. inherit on a length had the same problem.
  • A positioned block handed its content line-height: normal as the number 1.33. That content was drawn with different spacing from the rest of the document.
  • A block closed the bidirectional embedding of the inline element it was opened in, and the text after the block was drawn outside that embedding.

The change

  • Computed values on each frame: each frame on the stack of open elements (Track the stack of open elements while parsing HTML #533) holds its element's computed values of the inherited properties. An element starts from its parent's frame, then the cascade (Add an opt-in cascade that orders rules by specificity and source order #535) and the CSS-wide keywords (Support the CSS-wide keywords inherit, initial, unset and revert #546) apply. ComputedValues::compute() then turns the result into computed values:
    • font-size becomes absolute, through the size converter's existing convertFontSize();
    • font-weight becomes a number;
    • a line-height length or percentage becomes a length, and a number or normal stays as written;
    • every other em/ex/ch/rem length becomes the absolute length.
  • Channels replaced: in standard mode the frames replace the old channels. Blocks, tables, row groups, rows, cells, nested tables and positioned blocks all read their parent's frame. inherit on a property that is not inherited reads the same frame, so a row written straight into a table reads the implied tbody's values.
  • Links and decorations: the frames also carry the link and the decorations (A descendant can remove a text decoration it should only add to, because text-decoration is inherited instead of propagated #544) that an element's content is drawn in.
  • Shared helpers: the letter and word spacing of the text state live in Css\TextSpacing, which restoreInlineProperties() and printbuffer() both use. The font autoLangToFont chooses for a language is Mpdf::fontForLanguage(), shared by setCSS(), the table handler and the computed values. The thead and tfoot handlers share Tag::keepLegacyRowGroupFont().

Behaviour changes, standard mode only

  • An em/ex/ch/rem length is inherited as the length it came to, and inherit takes that length.
  • A positioned block's content draws line-height: normal the same way the flow does. Before, it was a fixed 1.33, which with DejaVu Sans or Times sets lines slightly further apart. To keep the old spacing, add line-height: 1.33 to the positioned block.
  • A bidirectional embedding is opened again for the inline element's text after a block.
  • The text before a block opened in an inline element is drawn in the block's own state, including its letter and word spacing, not the inline element's.

The changelog entry is under Breaking Changes, next to #543 and #544.

Snapshots

  • No existing page changes. Ten documents with a positioned block changed only through the line-height above. I confirmed that by forcing the positioned block's frame back to 1.33, which made every existing fixture match again. Those documents now set line-height: 1.33 on their positioned blocks.
  • Three fixtures refreshed, pages unchanged: css-wide-keywords, inherited-channels and unicode-zero-width-space. Text moves by hundredths of a point because em lengths are now computed once on the parent, and one font is renumbered.
  • Let a table inherit from the block around it #656's test: TableInheritsFromBlockTest no longer special-cases a table in a positioned block. That table now draws normal.

Tests

  • InheritanceMatrixTest: the matrix from Inherit from the parent element's computed values under the opt-in cascade #547, run under both cascades, through the shared DrawnStyles trait.
  • ComputedValueInheritanceTest:
    • relative font sizes three levels deep, in every context;
    • em lengths and inherit;
    • the positioned block's line-height;
    • the bidi embedding;
    • the text state before a block, including its letter and word spacing;
    • a table in an inline element.
  • OpenElementStackTest: what a frame carries.
  • TextSpacingTest: the spacing helper.
  • Snapshots computed-values and computed-values-brochure-{standard,legacy}.

Found along the way

letter-spacing: normal on an inline element is ignored when its text starts a block. That is pre-existing, in both modes, and is #684.

Gates

  • Full suite: 12,581 tests, 31 skipped, of which 211 are snapshots, all passing.
  • composer cs: clean.
  • PHPStan: no errors.
  • php -l on PHP 5.6: clean.
  • Measured cost: about +2–3% render time in standard mode on a 2,000-paragraph document and a 2,000-row table. Legacy is unchanged within noise.

Fixes #547. Part of #520.

🤖 Generated with Claude Code

jakejackson1 pushed a commit that referenced this pull request Oct 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… mode

Under the standard cssMode every element inherits from its parent's computed values, as in a browser. Each frame on the stack of open elements holds its element's computed values of the inherited properties: font-size made absolute, font-weight a number, a line-height length or percentage made a length, and every other em, ex, ch or rem length made absolute. Blocks, tables, row groups, rows, cells, nested tables and positioned blocks all start from their parent's frame, which replaces the saved text state, base_table_properties, the row and row group merges and the properties put on a positioned block's stand-in div. The frames also carry the link and the decorations an element's content is drawn in. The legacy cascade is unchanged.

The letter and word spacing of the text state live in TextSpacing, and the font autoLangToFont chooses for a language in Mpdf::fontForLanguage(), which setCSS(), Table and the computed values share.

Fixes #547. Part of #520.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jakejackson1
jakejackson1 force-pushed the feature/547-computed-values branch from a3e044b to a42c233 Compare October 6, 2026 01:24
@jakejackson1
jakejackson1 merged commit 971bd4d into gravitypdf Oct 6, 2026
@jakejackson1
jakejackson1 deleted the feature/547-computed-values branch October 6, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

create-upstream-pr enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inherit from the parent element's computed values under the opt-in cascade

2 participants