diff --git a/.changeset/repeat-row-fixed.md b/.changeset/repeat-row-fixed.md new file mode 100644 index 000000000..ec40b908e --- /dev/null +++ b/.changeset/repeat-row-fixed.md @@ -0,0 +1,5 @@ +--- +'@react-pdf/paginate': patch +--- + +Repeat fixed children of a row on the row's continuation, as the legacy engine does diff --git a/packages/paginate/src/fit/row.ts b/packages/paginate/src/fit/row.ts index 3fcbc07f8..eb2b3bc16 100644 --- a/packages/paginate/src/fit/row.ts +++ b/packages/paginate/src/fit/row.ts @@ -1,11 +1,13 @@ import isRow from '../item/isRow'; import isColumn from '../item/isColumn'; import isLeaf from '../item/isLeaf'; +import isRepeat from '../item/isRepeat'; import hasContentAbove from '../fill/hasContentAbove'; import fill from '../fill/fill'; import fragmentHeight from '../fragment/height'; import reach from '../fragment/reach'; import repeatFragments from '../fragment/repeatFragments'; +import toFragments from '../fragment/toFragments'; import { FillResult, Fragment, @@ -18,7 +20,8 @@ import { CONTINUE, DECLINE, DONE } from '../step'; // Place every row child side by side at y=0. A break is one horizontal cut // through all children at `height`: each keeps what fits above the line, and -// only children with leftovers reappear in the continuation. Returns null +// only children with leftovers reappear in the continuation, along with fresh +// copies of fully placed repeat children in their original order. Returns null // when a child can't fit or break — the whole row moves together. export const place = ( rowFragment: Fragment, @@ -29,6 +32,7 @@ export const place = ( ): FillResult | null => { const placed: PlacedItem[] = []; const remaining: Fragment[] = []; + let continues = false; for (const childFragment of rowFragment.children) { const child = childFragment.item; @@ -64,6 +68,9 @@ export const place = ( isFirst: false, children: [...repeats, ...inner.remaining], }); + continues = true; + } else if (isRepeat(child)) { + remaining.push(...toFragments([child])); } continue; @@ -83,6 +90,7 @@ export const place = ( part: { isFirst, isLast: false }, }); remaining.push({ item: split.next, isFirst: false, children: [] }); + continues = true; continue; } @@ -93,9 +101,11 @@ export const place = ( height: fragmentHeight({ item: child, isFirst, children: [] }), part: { isFirst, isLast: true }, }); + + if (isRepeat(child)) remaining.push(...toFragments([child])); } - return { placed, remaining }; + return { placed, remaining: continues ? remaining : [] }; }; // Place the row's children into the space left on the page. Declines when diff --git a/packages/paginate/tests/paginate.test.ts b/packages/paginate/tests/paginate.test.ts index da7479c7d..673b5c574 100644 --- a/packages/paginate/tests/paginate.test.ts +++ b/packages/paginate/tests/paginate.test.ts @@ -1888,6 +1888,66 @@ describe('paginate', () => { ); }); + test('a fully placed repeat child of a row re-emits on the row continuation', () => { + const body = column( + [leaf(20, 'a'), leaf(20, 'b'), leaf(20, 'c'), leaf(20, 'd')], + 'body', + ); + const items: Item[] = [row([repeatLeaf(10, 'label'), body])]; + const pages = paginateFlow(items, 50); + + const cells = (i: number) => pages[i][0].children?.map((c) => c.item.id); + const lines = (i: number) => + pages[i][0].children?.[1].children?.map((c) => c.item.id); + + expect(pages).toHaveLength(2); + expect(cells(0)).toEqual(['label', 'body']); + expect(cells(1)).toEqual(['label', 'body']); + expect(lines(0)).toEqual(['a', 'b']); + expect(lines(1)).toEqual(['c', 'd']); + + snapshotPages(paginate(column(items), 50), region(50), 'repeat-row'); + }); + + test('a row repeat copy taller than the carried cells sets the continuation height', () => { + const body = column( + [leaf(20, 'a'), leaf(20, 'b'), leaf(20, 'c')], + 'body', + ); + const items: Item[] = [row([repeatLeaf(30, 'label'), body])]; + const pages = paginateFlow(items, 50); + + expect(pages).toHaveLength(2); + expect(pages[1][0].children?.map((c) => c.item.id)).toEqual([ + 'label', + 'body', + ]); + expect(pages[1][0].height).toBe(30); + + snapshotPages( + paginate(column(items), 50), + region(50), + 'repeat-row-tall-copy', + ); + }); + + test('a row that does not continue adds no repeat copies', () => { + const items: Item[] = [ + row([repeatLeaf(10, 'label'), leaf(20, 'a')], 'row'), + leaf(40, 'next'), + ]; + const pages = paginateFlow(items, 50); + + expect(pages.map((p) => p.map((c) => c.item.id))).toEqual([ + ['row'], + ['next'], + ]); + expect(pages[0][0].children?.map((c) => c.item.id)).toEqual([ + 'label', + 'a', + ]); + }); + test('a repeat item does not appear before its flow position', () => { const items: Item[] = [ leaf(40, 'a'), diff --git a/packages/paginate/tests/snapshots/repeat-row-tall-copy.png b/packages/paginate/tests/snapshots/repeat-row-tall-copy.png new file mode 100644 index 000000000..1a43258ac Binary files /dev/null and b/packages/paginate/tests/snapshots/repeat-row-tall-copy.png differ diff --git a/packages/paginate/tests/snapshots/repeat-row.png b/packages/paginate/tests/snapshots/repeat-row.png new file mode 100644 index 000000000..f9f8ee13b Binary files /dev/null and b/packages/paginate/tests/snapshots/repeat-row.png differ