mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 18:23:47 +10:00
fix(pdf): consume oversized list marker presence hints
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderToBuffer } from "@react-pdf/renderer";
|
||||
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
|
||||
@@ -54,6 +59,54 @@ async function listPages(
|
||||
}
|
||||
|
||||
describe("list marker pagination (#3344)", () => {
|
||||
it("terminates when an authored marker presence hint exceeds a whole page", async () => {
|
||||
if (process.env.RR_LIST_PRESENCE_PROBE !== "1") {
|
||||
// The renderer's pagination loop is synchronous. A test timeout cannot
|
||||
// interrupt it, so run this probe in a killable process with thread workers.
|
||||
const vitest = join(dirname(createRequire(import.meta.url).resolve("vitest/package.json")), "vitest.mjs");
|
||||
await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
vitest,
|
||||
"run",
|
||||
"src/templates/shared/list-pagination.test.tsx",
|
||||
"-t",
|
||||
"terminates when an authored marker presence hint exceeds a whole page",
|
||||
"--pool=threads",
|
||||
"--maxWorkers=1",
|
||||
"--passWithNoTests=false",
|
||||
],
|
||||
{
|
||||
cwd: fileURLToPath(new URL("../../../", import.meta.url)),
|
||||
env: { ...process.env, RR_LIST_PRESENCE_PROBE: "1" },
|
||||
timeout: 30000,
|
||||
killSignal: "SIGKILL",
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const rtl of [false, true]) {
|
||||
for (const first of ["list-item-content", "list-marker"]) {
|
||||
const result = await listPages(
|
||||
180,
|
||||
30,
|
||||
`${first} { order: -1; } list-marker { -resume-min-presence-ahead: 1000pt; }`,
|
||||
{ rtl },
|
||||
);
|
||||
expect(result.first).toBe(1);
|
||||
expect(result.marker).toBe(result.first);
|
||||
expect(result.last).toBeGreaterThan(result.first);
|
||||
expect(result.pages.length).toBeLessThanOrEqual(4);
|
||||
expect(result.pages.flat().join(" ").match(/•/g)).toHaveLength(1);
|
||||
expect(
|
||||
result.pages
|
||||
.flat()
|
||||
.join(" ")
|
||||
.match(/\bSome\b/g),
|
||||
).toHaveLength(30);
|
||||
}
|
||||
}
|
||||
}, 40000);
|
||||
it("moves a bullet with its first paragraph when the paragraph cannot start on this page", async () => {
|
||||
const result = await listPages(194);
|
||||
expect(result.first).toBe(1);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/lib/index.js b/lib/index.js
|
||||
index 18c2618f4630b3c1296c8c3cac317b8d7666bbe8..d4471eda64ee99695bedf01f7a0098806d2fc961 100644
|
||||
index 18c2618f4630b3c1296c8c3cac317b8d7666bbe8..79e1898aadcbbfc78a206e235d868cb178683a07 100644
|
||||
--- a/lib/index.js
|
||||
+++ b/lib/index.js
|
||||
@@ -3163,7 +3163,10 @@ const splitNodes = (height, contentArea, nodes) => {
|
||||
@@ -14,7 +14,7 @@ index 18c2618f4630b3c1296c8c3cac317b8d7666bbe8..d4471eda64ee99695bedf01f7a009880
|
||||
// But if the current page is empty then we can just include the parent on the current page
|
||||
if (currentChildren.length === 0) {
|
||||
currentChildren.push(child, ...futureFixedNodes);
|
||||
@@ -3196,7 +3199,45 @@ const splitChildren = (height, contentArea, node) => {
|
||||
@@ -3196,7 +3199,52 @@ const splitChildren = (height, contentArea, node) => {
|
||||
};
|
||||
const splitView = (node, height, contentArea) => {
|
||||
const [currentNode, nextNode] = splitNode(node, height);
|
||||
@@ -32,6 +32,13 @@ index 18c2618f4630b3c1296c8c3cac317b8d7666bbe8..d4471eda64ee99695bedf01f7a009880
|
||||
+ const contentIndex = node.children.findIndex((child) => child.props['data-resume-list-content']);
|
||||
+ const hasMarker = (children) => children.some((child) => child.props['data-resume-list-marker']);
|
||||
+ const deferredMarker = nextChildren.find((child) => child.props['data-resume-list-marker']);
|
||||
+ // Presence is advisory once the whole row moves to a fresh page. Consume
|
||||
+ // the hint on continuation so an impossible window cannot rewind the
|
||||
+ // same unsplit content indefinitely (including content-first rows).
|
||||
+ const continuedMarkerProps = (marker) => ({
|
||||
+ ...(deferredMarker?.props || marker.props),
|
||||
+ minPresenceAhead: 0,
|
||||
+ });
|
||||
+ if (markerIndex !== -1 && !hasMarker(currentChilds) && hasMarker(nextChildren) && hasText(content(currentChilds))) {
|
||||
+ // A presence hint or page break can defer a marker after content.
|
||||
+ // Defer unsplit content while retaining the marker's consumed flags.
|
||||
@@ -39,7 +46,7 @@ index 18c2618f4630b3c1296c8c3cac317b8d7666bbe8..d4471eda64ee99695bedf01f7a009880
|
||||
+ nextChildren = node.children
|
||||
+ .filter((child) => child.props['data-resume-list-content'] || child.props['data-resume-list-marker'])
|
||||
+ .map((child) => child.props['data-resume-list-marker']
|
||||
+ ? { ...child, props: deferredMarker.props }
|
||||
+ ? { ...child, props: continuedMarkerProps(child) }
|
||||
+ : child);
|
||||
+ } else if (markerIndex !== -1 && !hasText(content(currentChilds)) && hasText(content(nextChildren))) {
|
||||
+ // The first text did not fit. Preserve the authored marker/content
|
||||
@@ -49,7 +56,7 @@ index 18c2618f4630b3c1296c8c3cac317b8d7666bbe8..d4471eda64ee99695bedf01f7a009880
|
||||
+ // page break; restoring the original props would replay it.
|
||||
+ const nextMarker = {
|
||||
+ ...marker,
|
||||
+ props: deferredMarker?.props || marker.props,
|
||||
+ props: continuedMarkerProps(marker),
|
||||
+ box: { ...marker.box, top: 0 },
|
||||
+ };
|
||||
+ currentChilds = currentChilds.filter((child) => !child.props['data-resume-list-marker']);
|
||||
|
||||
Generated
+3
-3
@@ -12,7 +12,7 @@ overrides:
|
||||
uuid@<11.1.1: ^11.1.1
|
||||
|
||||
patchedDependencies:
|
||||
'@react-pdf/layout@5.2.0': add03fa9efd8cf4ab004a49c4c37bdf11f6d82109bfd3f6a2c48b7473aad29ae
|
||||
'@react-pdf/layout@5.2.0': 75dac7cb8260f9c96cd9ab5522d095a3aa047881a9e9d1d2a597409b45944494
|
||||
'@react-pdf/textkit': 092a6fe8baf3c472a81cdf2ab52a00ec98ef3364e1b8e744005dbcf219a8a213
|
||||
|
||||
importers:
|
||||
@@ -11198,7 +11198,7 @@ snapshots:
|
||||
jay-peg: 1.1.1
|
||||
png-js: 2.0.0
|
||||
|
||||
'@react-pdf/layout@5.2.0(patch_hash=add03fa9efd8cf4ab004a49c4c37bdf11f6d82109bfd3f6a2c48b7473aad29ae)':
|
||||
'@react-pdf/layout@5.2.0(patch_hash=75dac7cb8260f9c96cd9ab5522d095a3aa047881a9e9d1d2a597409b45944494)':
|
||||
dependencies:
|
||||
'@react-pdf/fns': 3.1.3
|
||||
'@react-pdf/image': 3.1.2
|
||||
@@ -11239,7 +11239,7 @@ snapshots:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@react-pdf/fns': 3.1.3
|
||||
'@react-pdf/font': 4.1.2
|
||||
'@react-pdf/layout': 5.2.0(patch_hash=add03fa9efd8cf4ab004a49c4c37bdf11f6d82109bfd3f6a2c48b7473aad29ae)
|
||||
'@react-pdf/layout': 5.2.0(patch_hash=75dac7cb8260f9c96cd9ab5522d095a3aa047881a9e9d1d2a597409b45944494)
|
||||
'@react-pdf/primitives': 4.4.0
|
||||
'@react-pdf/reconciler': 2.0.0(react@19.2.8)
|
||||
'@react-pdf/render': 4.7.0
|
||||
|
||||
Reference in New Issue
Block a user