Skip to content

feat: w:cantSplit on table rows and w:keepLines on paragraphs - #3

Merged
dskripchenko merged 3 commits into
dskripchenko:mainfrom
One-Learning-Community:feat/cant-split-keep-lines
Oct 20, 2026
Merged

dskripchenko merged 3 commits into
dskripchenko:mainfrom
One-Learning-Community:feat/cant-split-keep-lines

Conversation

@serpentblade

Copy link
Copy Markdown
Contributor

feat: w:cantSplit on table rows and w:keepLines on paragraphs

Summary

Three commits, each separable (each passes the full suite on its own, so they can be reviewed or cherry-picked alone):

  1. fix: place numPr by its position in w:pPr for list items: numPr is written at its CT_PPrBase position instead of being prefixed onto the finished string. No output change today.
  2. fix: emit w:keepNext in DOCX output: ParagraphStyle::$keepWithNext was read and sent to HTML but never written to DOCX.
  3. feat: w:cantSplit on table rows and w:keepLines on paragraphs.

Adds two pagination properties that the library did not support:

  • w:cantSplit on table rows (w:trPr): the row never breaks across pages. This is Word's "Allow row to break across pages" switched off.
  • w:keepLines on paragraphs (w:pPr): all lines of a paragraph stay on one page. This is Word's "Keep lines together".

Both are written by the Word2007 writer, read back by the reader, and serialized to HTML as break-inside: avoid, the same way keepNext is serialized as break-after: avoid.

Motivation

Generated documents need to control where pages break: a table row with a signature block or a short address should not be cut in half, and a paragraph that is a unit (a clause, a caption) should move to the next page whole. keepNext and tblHeader existed; these two were the missing counterparts.

API

use Dskripchenko\PhpDocx\Build\TableRowBuilder;
use Dskripchenko\PhpDocx\Element\TableRow;
use Dskripchenko\PhpDocx\Style\ParagraphStyle;

$table->row(fn (TableRowBuilder $r) => $r->cantSplit()->cell('Signed by ...'));

new TableRow($cells, cantSplit: true);
new ParagraphStyle(keepLines: true);
$style->copy(keepLines: true);

Output: <w:trPr><w:cantSplit/></w:trPr> and <w:pPr><w:keepLines/></w:pPr>.

Notes

  • New constructor parameters are appended at the end (TableRow::$cantSplit, ParagraphStyle::$keepLines), so positional callers are unaffected.
  • w:pPr order follows CT_PPrBase: pStyle, keepLines, numPr, pBdr, ... The list-item path prefixed numPr right after pStyle; commit 1 places it by position, after keepNext/keepLines. In w:trPr (an unordered choice) cantSplit is written first, then tblHeader, trHeight.
  • Commit 2 makes the writer emit keepNext; before this it was dropped on the way into DOCX.
  • Not included: parsing break-inside: avoid from HTML back into the model (break-after: avoid is not parsed either).

Tests

composer install && vendor/bin/phpunit : OK (410 tests, 1050 assertions); before: OK (398 tests, 1009 assertions). After each commit: 399, 402, 410 tests, all OK. composer stan: no errors.

A list item's numPr was prefixed onto the finished w:pPr string, right
after pStyle. CT_PPrBase is a sequence, so numPr must follow keepNext,
keepLines and pageBreakBefore. renderParagraphProperties() now takes the
numPr and writes it at its position. Output is unchanged today; it keeps
the order valid once those elements are written.
ParagraphStyle::$keepWithNext was read from DOCX and serialized to HTML
(break-after: avoid) but never written by the Word2007 writer. It is now
written in CT_PPrBase order: after pStyle, before numPr and pBdr.
TableRow::$cantSplit / TableRowBuilder::cantSplit() and
ParagraphStyle::$keepLines are written by the Word2007 writer (keepLines
after keepNext and before numPr and pBdr; cantSplit first in w:trPr),
read back by TableReader / OoxmlPropertyParser, and serialized to HTML
as break-inside: avoid.
dskripchenko added a commit that referenced this pull request Oct 20, 2026
feat: w:cantSplit on table rows and w:keepLines on paragraphs (#3, conflict resolved)
@dskripchenko
dskripchenko merged commit 47a4210 into dskripchenko:main Oct 20, 2026
8 checks passed
@dskripchenko dskripchenko mentioned this pull request Oct 20, 2026
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.

2 participants