diff --git a/.gitattributes b/.gitattributes index 72564f2..cdbaf3a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -172,3 +172,6 @@ docs/assets/styling-notebook-alpha10/*.pdf binary examples/styling-notebook-alpha11/** text eol=lf docs/assets/styling-notebook-alpha11/** -text docs/assets/styling-notebook-alpha11/*.pdf binary + +# React starter baselines are verified by exact PDF bytes. +docs/assets/react-starter/baseline/*.pdf binary diff --git a/docs/assets/react-starter/baseline/component-credit-1.png b/docs/assets/react-starter/baseline/component-credit-1.png index fc7e744..e61d057 100644 Binary files a/docs/assets/react-starter/baseline/component-credit-1.png and b/docs/assets/react-starter/baseline/component-credit-1.png differ diff --git a/docs/assets/react-starter/baseline/component-credit.pdf b/docs/assets/react-starter/baseline/component-credit.pdf index fa637d0..e8f0e7d 100644 Binary files a/docs/assets/react-starter/baseline/component-credit.pdf and b/docs/assets/react-starter/baseline/component-credit.pdf differ diff --git a/docs/assets/react-starter/baseline/component-edited-1.png b/docs/assets/react-starter/baseline/component-edited-1.png index 8d08b6b..d2e4c28 100644 Binary files a/docs/assets/react-starter/baseline/component-edited-1.png and b/docs/assets/react-starter/baseline/component-edited-1.png differ diff --git a/docs/assets/react-starter/baseline/component-edited.pdf b/docs/assets/react-starter/baseline/component-edited.pdf index f4a9815..95eb9a7 100644 Binary files a/docs/assets/react-starter/baseline/component-edited.pdf and b/docs/assets/react-starter/baseline/component-edited.pdf differ diff --git a/docs/assets/react-starter/baseline/component-empty-1.png b/docs/assets/react-starter/baseline/component-empty-1.png index 747680e..44d1b6e 100644 Binary files a/docs/assets/react-starter/baseline/component-empty-1.png and b/docs/assets/react-starter/baseline/component-empty-1.png differ diff --git a/docs/assets/react-starter/baseline/component-empty.pdf b/docs/assets/react-starter/baseline/component-empty.pdf index 5597d95..8ec4739 100644 Binary files a/docs/assets/react-starter/baseline/component-empty.pdf and b/docs/assets/react-starter/baseline/component-empty.pdf differ diff --git a/docs/assets/react-starter/baseline/component-invoice-1.png b/docs/assets/react-starter/baseline/component-invoice-1.png index 171f072..2fa09ed 100644 Binary files a/docs/assets/react-starter/baseline/component-invoice-1.png and b/docs/assets/react-starter/baseline/component-invoice-1.png differ diff --git a/docs/assets/react-starter/baseline/component-invoice.pdf b/docs/assets/react-starter/baseline/component-invoice.pdf index e7f787e..8f33b13 100644 Binary files a/docs/assets/react-starter/baseline/component-invoice.pdf and b/docs/assets/react-starter/baseline/component-invoice.pdf differ diff --git a/docs/assets/react-starter/baseline/component-long-1.png b/docs/assets/react-starter/baseline/component-long-1.png index f0f84a1..c526413 100644 Binary files a/docs/assets/react-starter/baseline/component-long-1.png and b/docs/assets/react-starter/baseline/component-long-1.png differ diff --git a/docs/assets/react-starter/baseline/component-long.pdf b/docs/assets/react-starter/baseline/component-long.pdf index baa54aa..e8c531d 100644 Binary files a/docs/assets/react-starter/baseline/component-long.pdf and b/docs/assets/react-starter/baseline/component-long.pdf differ diff --git a/docs/assets/react-starter/baseline/component-paid-1.png b/docs/assets/react-starter/baseline/component-paid-1.png index 77d7a32..24c3050 100644 Binary files a/docs/assets/react-starter/baseline/component-paid-1.png and b/docs/assets/react-starter/baseline/component-paid-1.png differ diff --git a/docs/assets/react-starter/baseline/component-paid.pdf b/docs/assets/react-starter/baseline/component-paid.pdf index ab34344..4b5f373 100644 Binary files a/docs/assets/react-starter/baseline/component-paid.pdf and b/docs/assets/react-starter/baseline/component-paid.pdf differ diff --git a/docs/assets/react-starter/baseline/verification.json b/docs/assets/react-starter/baseline/verification.json index 98bb2b7..b56f099 100644 --- a/docs/assets/react-starter/baseline/verification.json +++ b/docs/assets/react-starter/baseline/verification.json @@ -24,54 +24,54 @@ { "design": "component-invoice", "pages": 1, - "pdfSha256": "7ab921d0f55604f84fd519514586f8d162e2912e925ea4071a59dfbd93993f56", + "pdfSha256": "f2553f220e9d30e26df785a4ed251f07ff1e615afd3972a5c29c19a0c5fd3f6c", "previews": [ - "0fa59b3ea2938c413d8c58a02e4b08bec8349aaf93500c16b17652ba92679199" + "54a931df6a4741745ae651800ff74311cb59f1c74b58a0a67936a497e288648c" ], "repeatedPdfPreviewsEqual": true }, { "design": "component-paid", "pages": 1, - "pdfSha256": "f4efce68a8f64f3f2219b9056cf2d3d345e9622fc9b3dbc355325ddbf62f02f3", + "pdfSha256": "91d8c8360fac37cd413fe669ed8f06650658b9ca01777129879207aac014d4c2", "previews": [ - "d040a9e8e435f7baa1498db9b9571508bd174b745d44eca7fc0ff5416eb46721" + "8028cdd4d9b967e61084f54711072a1cc93a208b557749187dc186eec251d531" ], "repeatedPdfPreviewsEqual": true }, { "design": "component-edited", "pages": 1, - "pdfSha256": "709f3438a1d4b363a86bf71025a8e883670354116c0d703b46b3ff16ae668709", + "pdfSha256": "a664f9fd84af1d2354537645aa6b70dd9abe3ab762b8a9631047aab742b1297e", "previews": [ - "aba217ddb0b2b068a6c649bff1510e6627fbd82ba1febfd745a1f9b722955e52" + "2ebbfde2c38975be8abf5312947a07d6375039f422fef3ad81aaad7d67cfe115" ], "repeatedPdfPreviewsEqual": true }, { "design": "component-empty", "pages": 1, - "pdfSha256": "d4f19a2559476236fe19acf76124f28ea5faa351731c7ee5585dd59f16f07122", + "pdfSha256": "cc1982eaea9dfa05b014cffea44644ecc2f4d33e8eb22ae6f6a501ab6d7f1248", "previews": [ - "7d386f278ce859efde193a30ab3981ddc26e481370fb56bd49f9f703c5f21c65" + "2c7539839be57f85421a98fad5a1abc403d914b87d0539f9128c91691c24f783" ], "repeatedPdfPreviewsEqual": true }, { "design": "component-credit", "pages": 1, - "pdfSha256": "b6b5f473e297c0c5d05f5bd1711c5ce3149dfbcbdb7234d0aeb54d2372622abf", + "pdfSha256": "cd57b56ec26e86365ab0223656d7e21b1543c9fbf404e967f0aef2e9b98024e6", "previews": [ - "6dd0b56df6bf3bdca9c19d01fc0227dd838657c93b4c095de064cf844ce506b8" + "8937d9ac9b42cf13c1dd28fcdb150dabd3f404e8bf7816050824c7905d6898e3" ], "repeatedPdfPreviewsEqual": true }, { "design": "component-long", "pages": 3, - "pdfSha256": "8ea549b4429c7f9b7e7b5d5b7491bbaf7b908b3cdaee63e385e2e979cdedb0d4", + "pdfSha256": "589210d6b5bb0684fdbf5dbf62c5e32742c7166d3a8cfc5ae304c430e08357a3", "previews": [ - "692d7e7ef7d2cafb225b1823fb4646e055fe6fee374f1f00c5c95e8d467ba7d4", + "a22c18101f3303398a17c655b88f005fffde67dddb3f0b28521a78041125f4fe", "61e388cdd7c7d133c647b07886bd818d91382b072323c79281c80c698983b24b", "7648f8591967cf8be2ad40f7743cc95464606687f8bcd14ac48098d9f48560c5" ], diff --git a/docs/assets/react-starter/project.zip b/docs/assets/react-starter/project.zip index dbfd590..d81780a 100644 Binary files a/docs/assets/react-starter/project.zip and b/docs/assets/react-starter/project.zip differ diff --git a/docs/assets/react-starter/source.json b/docs/assets/react-starter/source.json index 0833542..8ef8267 100644 --- a/docs/assets/react-starter/source.json +++ b/docs/assets/react-starter/source.json @@ -1,13 +1,13 @@ { "source_repository": "https://github.com/fullbleed-engine/fullbleed-node", - "source_commit": "294ad01a9492d2e8c27feb4f5ed620b10ee47720", + "source_commit": "0de0e692d22a9058610a262a06a1797b7d892ae3", "source_directory": "examples/react", "package_version": "0.6.0", "engine_version": "2.5.24", "react_version": "19.3.0", "package_integrity": "sha512-8tptrfaBbP4cjHohzPDPD1Lh3NuBFbRyVebn4kQhFsSX2VsMJwR6GURImfr9tbO3+iPjsZBFwZFxRYz238xc8g==", - "zip_bytes": 36270, - "zip_sha256": "a6b74368af73b2c217f9a1f2a0a6f07b0701a62185d3528f8fd6b0d9898561d1", + "zip_bytes": 36304, + "zip_sha256": "ce228650f3a34a7702a8bde10c29143864df11f4c652e10c23b60834e9ceeb8d", "files": { "fullbleed-react-starter/.gitignore": "0c8c1daabb918a54d7e75061883a887e9e0039d533487324a895ebe46afe15d8", "fullbleed-react-starter/LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9", @@ -17,9 +17,9 @@ "fullbleed-react-starter/package.json": "d9c56c1953348c135f190e9b57cf84e54c2f960120c1c47f7959d7e1673244f1", "fullbleed-react-starter/public/favicon.svg": "3eed6dcf1013e48bc64886fed4cf815c609ed06b404742bdacdc44c1df8d3118", "fullbleed-react-starter/src/App.tsx": "be531b4b19f86fe2b56ebb652efca23c3ce5e5c0cc656951e4e6ac13aa738d7e", - "fullbleed-react-starter/src/documents/Invoice.tsx": "df2a087615a31c95094ac8797f17e352b3bac8c0c477476989904429349cff3a", + "fullbleed-react-starter/src/documents/Invoice.tsx": "6737ef4e107935cf0e66291f1ef1576d6aef37e647555e81ce875bf37a5cb589", "fullbleed-react-starter/src/documents/data.ts": "c05c98fb07deaef2140f53df4f83670f138d6cad4a453fe09f423f7adb076f70", - "fullbleed-react-starter/src/documents/invoiceStyles.ts": "1cd818aec8cf2e9def15b51ce043bfdfcfc06626fd9df63372f34f9a43160a59", + "fullbleed-react-starter/src/documents/invoiceStyles.ts": "d9d63ef41fccae67d1a36de315b348c56ab5a680a18b0b2c43ffd63c46f18895", "fullbleed-react-starter/src/invoice.css": "0b310ce0f2964fd6b9b68e4afafa9307b4ff8c6b2f1fc753367dc755620ce427", "fullbleed-react-starter/src/invoice.html": "1f065ad6694021649e61b3f666cba6dc01aacdcbd8e80e97425b383e60d7d2fe", "fullbleed-react-starter/src/main.tsx": "f8f523ef5fc0b1e4095830466e0caa648c01f7b42036694b800c25d43d5e2c84", @@ -31,6 +31,6 @@ "fullbleed-react-starter/vite.config.js": "2a300aafa93df7e0269d06d104960ee5ac4d64989d37d61ca482c4b88db3629c" }, "verification_workflow": "https://github.com/fullbleed-engine/fullbleed-node/actions/workflows/react-starter.yml", - "source_verification_run": "https://github.com/fullbleed-engine/fullbleed-node/actions/runs/38803591833", - "local_ui_evidence": "https://github.com/fullbleed-engine/fullbleed-node/blob/294ad01a9492d2e8c27feb4f5ed620b10ee47720/verification/react-components060.json" + "source_verification_run": "https://github.com/fullbleed-engine/fullbleed-node/actions/runs/39462826180", + "local_ui_evidence": "https://github.com/fullbleed-engine/fullbleed-node/blob/0de0e692d22a9058610a262a06a1797b7d892ae3/verification/react-invoice-references.json" } diff --git a/docs/guides/react-pdf.md b/docs/guides/react-pdf.md index 154832d..2ed0a47 100644 --- a/docs/guides/react-pdf.md +++ b/docs/guides/react-pdf.md @@ -89,6 +89,11 @@ sheet, composes the chosen ink color overrides, and attaches it through ``. `src/style.css` styles the React interface; print rules belong to the document's stylesheet. +The invoice reference has its own `className="invoice-reference"` rule with +`letterSpacing: "0pt"` and `textTransform: "none"`. Keep those overrides when +styling the surrounding label so references remain searchable and retain their +original case in the PDF. + Use **See the document component** to inspect the source while trying the form. Keep your layout and data changes in these project files: @@ -189,7 +194,9 @@ React development build with StrictMode enabled. It downloads real PDFs, independently checks their text and page sizes, and compares six component cases with the installed Node package: default, paid, edited, empty, credit, and a twenty-row invoice across three pages. Every component preview matches -the Node output too. The existing HTML invoice and report keep their byte checks. It also checks automatic and +the Node output too. Default and mixed-case references must also survive exact +text extraction and case-sensitive PDF search without whitespace normalization. +The existing HTML invoice and report keep their byte checks. It also checks automatic and on-demand modes, fast edits, cancellation, recovery, unmount/remount, URL cleanup, and 1440/390/320px layouts. The docs build installs and checks the exact ZIP. A real Vite development-server check edits TSX and stylesheet source, verifies diff --git a/tools/react_component_checks.py b/tools/react_component_checks.py index dcbf9a2..6909aad 100644 --- a/tools/react_component_checks.py +++ b/tools/react_component_checks.py @@ -4,6 +4,25 @@ from playwright.sync_api import expect from pypdf import PdfReader +import pypdfium2 as pdfium + + +def check_reference(path, reference, check): + # Identifiers must survive reader extraction and search without removing + # whitespace or changing case, even when the surrounding label is tracked. + text = PdfReader(path).pages[0].extract_text() + check(path.stem + ': reference extracts with exact spacing and case', reference in text) + with pdfium.PdfDocument(str(path)) as document: + page = document[0] + textpage = page.get_textpage() + search = textpage.search(reference, match_case=True) + try: + found = search.get_next() is not None + finally: + search.close() + textpage.close() + page.close() + check(path.stem + ': reference is searchable in PDFium', found) def check_components(page, out, baseline, check, ready, download, hold_engine, resume_engine, requested): @@ -25,7 +44,8 @@ def compare(name, text): ready() check('components are the initial design', page.locator('#kind').input_value() == 'components') - compare('component-invoice', ['Maple & Finch', 'NS-1042', '8,100.00']) + initial = compare('component-invoice', ['Maple & Finch', 'NS-1042', '8,100.00']) + check_reference(initial, 'NS-1042', check) check('component design needs no HTML editor', page.locator('#html-source').count() == 0) page.locator('#component-source summary').click() source = page.locator('#component-source code').last.inner_text() @@ -57,10 +77,11 @@ def compare(name, text): page.locator('#customer').fill('Obsolete component input') assert requested.wait(8), 'Expected a held component renderer.' page.locator('#customer').fill('Birch & ') - page.locator('#reference').fill('TSX-2042') + page.locator('#reference').fill('Ref-aBc-2042') page.locator('#accent').fill('#233b79') resume_engine() - edited = compare('component-edited', ['Birch & ', 'TSX-2042']) + edited = compare('component-edited', ['Birch & ', 'Ref-aBc-2042']) + check_reference(edited, 'Ref-aBc-2042', check) check('superseded component results cannot replace newer input', 'Obsolete component input' not in PdfReader(edited).pages[0].extract_text()) page.screenshot(path=str(out / 'component-edited.png'), full_page=True) page.locator('#auto-preview').uncheck()