Pass the inherited properties of rows and row groups to their cells - #655
Merged
Merged
Conversation
This was referenced Sep 30, 2026
jakejackson1
force-pushed
the
fix/542-row-properties-to-cells
branch
from
September 30, 2026 10:38
d044058 to
dcca660
Compare
jakejackson1
force-pushed
the
fix/539-inherited-channels
branch
2 times, most recently
from
September 30, 2026 11:47
d7baa1a to
4e5b37c
Compare
jakejackson1
force-pushed
the
fix/542-row-properties-to-cells
branch
from
September 30, 2026 11:48
dcca660 to
090dfc9
Compare
jakejackson1
force-pushed
the
fix/539-inherited-channels
branch
from
September 30, 2026 17:51
4e5b37c to
a4f8701
Compare
jakejackson1
force-pushed
the
fix/542-row-properties-to-cells
branch
from
October 4, 2026 21:21
090dfc9 to
d7bcd60
Compare
Under the standard cssMode, a tr, thead, tbody or tfoot hands every inherited property to its cells, over the table's and under the cell's own. A tbody rule reaches rows written straight into a table, a row's relative font size is taken of its row group's, and a th is centred only when it inherits no text-align, as HTML's rendering rules have it. Legacy output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jakejackson1
force-pushed
the
fix/542-row-properties-to-cells
branch
from
October 4, 2026 21:28
d7bcd60 to
b31d661
Compare
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.
Closes #542. Part of #520 (step 21).
Follows #648, now merged.
Problem
Measured on #648's head,
4fd4c2bf. Whencolor,font-*,letter-spacing,word-spacing,text-transform,text-shadow,text-align,line-heightordirectionis set on atr,thead,tbodyortfoot, whether by a rule or bystyle="", it does not reach the text of the cells. Only the row background works. The one exception is atheadortfoot'stext-align, which mPDF v7 passed totdcells throughthead_textalign_default. It also sets the cells' weight and style, butReset()then clears them.<tr style="color:#f00; font-size:20pt"><tbody style="font-size:150%"><tr style="font-size:50%"><style>tbody{color:#f00}</style>, rows written straight into the table<thead style="text-align:right">with athChange
Under
CssMode::STANDARD:THead,TBodyandTFootkeep what their cells inherit on the table's state asrowGroupInherited. That isInheritedProperties::names()from the row group's merged CSS, laid overbase_table_properties.Trlays its own properties over the row group's, asrowInherited.Trmerges the rules for thatTBODYthrough a newCssManager::previewTableCss(), which merges without side effects, aspreviewBlockCss()does.TracksOpenElements::opensInImpliedTbody()shares the stack's own test for it.font-sizeis resolved against the table's. A row's is resolved against its row group's, and a cell's own is resolved against its row's, sincesetCSS()reads a cell's size against the table's.Tdpasses the row's set toCssMerger::merge()as a new$inheritedlayer, under everything else. It replaces theArrays::uniqueRecursiveMerge()withbase_table_properties. The cell's defaults, rules andstylethen go over it, so a rule on the cell still wins.th. It keeps its bold default, since that is the cell's own UA rule, as in a browser. HTML's rendering rules centre athonly when thetext-alignof its parent row is the initial value. Firefox (-moz-center-or-inherit) and Chrome (-internal-center) both do this. So under standard, aththat inherits atext-aligntakes it over the default centring. To let that include the table's owntext-align,Table::open()addstext-aligntobase_table_properties. For atd, that gives the same alignment as the table'stxtafield. A rule that setstext-alignon thethstill wins.This changes output wherever a standard-mode document styles rows or row groups. Their text now looks as it does in a browser.
<tr align="right">isn't covered here. #641 turns it into aTEXT-ALIGNhint among the row's merged properties, which this change then carries to the cells. #641 isn't on this branch, so the tests cover the CSS path.Compatibility
Every change is gated. Under
CssMode::LEGACY,Tdmerges as before and the row parts store nothing.To prove legacy is unchanged, I rendered all 144 snapshot documents under legacy, with the mode forced on every document, from #648's head and from this branch. They are byte-identical. The existing fixtures, which run in their own modes, all pass unchanged.
Tests
InheritedPropertiesTest: 12 new contexts in Carry every inherited text property through every channel that hands inherited values on #648's matrix, each checking everyTEXTproperty under both modes:styleand by a class rule;tbody,theadandtfoot;page-break-inside: avoidblock laid out again on the next page;font-kerningandhyphens, which the default table style resets (Let a table inherit from the block around it, not only from the document defaults #543).RowPropertiesToCellsTest:text-align,line-heightanddirectionfrom each row part, compared by where the cell's lines are drawn. Under legacy only thethead/tfoottext-alignis carried, as before.tbody, and atable > tbodyrule on the implied tbody.theadbefore the implied tbody;tbodybefore anothertbody;tbodyrule and atheadcell;th:font-weight: normal;tdbold in a bold row;theador table;text-align, and ath { text-align: center }rule, still winning.%through the table, row group, row and cell;emon a row group; a cell'semand%of its row; a cell in points; and the near miss of a sibling row's size.theadrepeated on the second page, and atbodyrunning onto it, keep their colours on both pages.RowPropertiesToCellsTestfail, all under standard.Snapshots
row-properties-to-cells(standard), one case per caption:tbody's font and transform;thead's alignment and weight against ath's defaults;tfoot's weight and colour;tbodyrule on rows with notbodytag;150%row group and an80%row;row-styled-invoice-standardandrow-styled-invoice-legacy: an invoice with a darktheadin white capitals, striped rows by class, an overdue row, a discount row holding a nested breakdown, atfootof totals, and payment terms whose rows sit straight in the table. The captions say what each mode draws.I rendered each fixture and looked at it.
Interactions with #651 and #652
CssMerger::merge()'s result as a frame's cascaded values. A cell's merge under standard now includes what it inherits from the table and its row, soinheriton a cell reads its row's value.color: currentColorfrombase_table_properties['COLOR'], which is the table's colour, not the row's.inheritedColor()now reads the inherited layer the cell was merged over first, so a cell'scurrentColoris its row's or row group's colour.CurrentColorTest::testACellsCurrentColorIsItsRowsColourcovers a row, atbody, atheadand the implied tbody, forcolorand for a border. Borders already took the row's colour, because the cell's mergedCOLORholds it.trwithcolor: currentColor, or a row border incurrentColor, took the table's colour, not its row group's.Trnow merges the row over what its row group hands it, asTdmerges a cell over its row, soinheriton a row also reads its row group.CurrentColorTest::testARowsCurrentColorIsItsRowGroupsColourcovers atbody, atheadand the implied tbody.Checks
composer test: OK. 7203 tests, 31 skipped for missing optional packages or ungenerated data. The snapshot suite ran with Imagick and Ghostscript, with no skips.composer cs: clean.Rebased after #633 to #646
#648 and this PR were rebased onto
gravitypdf.CssMerger: Honour !important in stylesheets and inline styles in cssMode standard (mpdf/mpdf#1010) #638 gives the default stylesheet's rules as[normal, important]. Thethalignment this PR takes from$inheritednow followsmergeEach()of the normal ones.TracksOpenElements:opensInImpliedTbody()sits beside Match :root, html, :link and :any-link, and read rem against html's font size #642'sgetDocumentPath().No test changed. Gates:
composer test9112 tests, 31 skipped;composer csclean; PHPStan no errors.Rebased after #648, #651, #652 and #653
The stacked #648 commit is dropped, since #648 was squash-merged.
CssMerger: Resolve inherit, initial, unset and revert in the standard CSS mode #652 merges the defaults on their own forrevertto read. Thethalignment it inherits now goes into those defaults, sotext-align: reverton athalso gives the inherited alignment, as in a browser.Table: theTEXT-ALIGNadded tobase_table_propertiessits beside Compute font-weight numbers, bolder and lighter, and font-size larger and smaller from the parent's values #647's resolution of the table'sfont-weight.currentColor: see the Draw currentColor, and a border or shadow with no colour, in the element's colour #651 note above, for cells and for rows. These are the only code changes beyond the conflicts, and they add one changelog sentence.Gates:
composer test11505 tests, 30 skipped; no snapshot fixture changed;composer csclean; PHPStan no errors; the changed files lint under PHP 5.6.🤖 Generated with Claude Code