diff --git a/.gitattributes b/.gitattributes index 4f75624..8c6f6be 100644 --- a/.gitattributes +++ b/.gitattributes @@ -182,3 +182,9 @@ docs/assets/cloudflare-starter/** -text docs/assets/cloudflare-starter/*.pdf binary docs/assets/cloudflare-starter/*.png binary docs/assets/cloudflare-starter/*.zip binary + +# Keep the linked-report downloads and verification bytes immutable. +docs/assets/pdf-links-alpha14/** -text +docs/assets/pdf-links-alpha14/*.pdf binary +docs/assets/pdf-links-alpha14/*.png binary +docs/assets/pdf-links-alpha14/*.zip binary diff --git a/docs/assets/pdf-links-alpha14/linked-report.pdf b/docs/assets/pdf-links-alpha14/linked-report.pdf new file mode 100644 index 0000000..444394b Binary files /dev/null and b/docs/assets/pdf-links-alpha14/linked-report.pdf differ diff --git a/docs/assets/pdf-links-alpha14/manifest.json b/docs/assets/pdf-links-alpha14/manifest.json new file mode 100644 index 0000000..320557d --- /dev/null +++ b/docs/assets/pdf-links-alpha14/manifest.json @@ -0,0 +1,34 @@ +{ + "ok": true, + "at": "2026-10-23T14:23:20.103968+00:00", + "version": "2.6.0a14", + "source_commit": "0e418e174a0d1a4cfe44bc29828c48e635829d90", + "guide_sha256": "39282c56abfac46d68b76c9031ff56aecb62ed3b50a97e9bd4886b1d88409d37", + "public_pdf_identical": true, + "public_vs_local_native_previews_identical": true, + "public_vs_local_pdfium_previews_identical": true, + "website_preview_source": "PDFium raster of the downloadable PDF", + "files": { + "linked-report.pdf": { + "sha256": "1ad53b6d97dc3ad6f605e33c82325b1b0e75a805f5b06069cf0553fe1b3b1274", + "bytes": 160746 + }, + "page-1.png": { + "sha256": "8d6a014be2a686cc37fcea4a866dcebd5a6671dfee0520ce4b0531bbfee3ea15", + "bytes": 80497 + }, + "page-2.png": { + "sha256": "bf1b28c24516e2e765116558fcc6b36d9282f018fee6fe074f2185563e9467a1", + "bytes": 126640 + }, + "project.zip": { + "sha256": "a3ae4e0ea1a49c32d7c34788010b1757d70b99820afeaf6291f514dce2d4577a", + "bytes": 213884 + }, + "verification.json": { + "sha256": "62797ba48caf667be3d414f67203c3ad20ae7478f325f1880658d686d0f08d52", + "bytes": 4068 + } + }, + "snippets_receipt_sha256": "34a79d65ce8d82b61f970de75e94f136cd0b814f27ebf443bd9fbeda5764bdf3" +} diff --git a/docs/assets/pdf-links-alpha14/page-1.png b/docs/assets/pdf-links-alpha14/page-1.png new file mode 100644 index 0000000..07a0840 Binary files /dev/null and b/docs/assets/pdf-links-alpha14/page-1.png differ diff --git a/docs/assets/pdf-links-alpha14/page-2.png b/docs/assets/pdf-links-alpha14/page-2.png new file mode 100644 index 0000000..bcb688e Binary files /dev/null and b/docs/assets/pdf-links-alpha14/page-2.png differ diff --git a/docs/assets/pdf-links-alpha14/project.zip b/docs/assets/pdf-links-alpha14/project.zip new file mode 100644 index 0000000..38398dd Binary files /dev/null and b/docs/assets/pdf-links-alpha14/project.zip differ diff --git a/docs/assets/pdf-links-alpha14/verification.json b/docs/assets/pdf-links-alpha14/verification.json new file mode 100644 index 0000000..8ad2d21 --- /dev/null +++ b/docs/assets/pdf-links-alpha14/verification.json @@ -0,0 +1,121 @@ +{ + "ok": true, + "at": "2026-10-23T14:23:19.622531+00:00", + "version": "2.6.0a14", + "pdf_sha256": "1ad53b6d97dc3ad6f605e33c82325b1b0e75a805f5b06069cf0553fe1b3b1274", + "pages": 2, + "authored_links": 5, + "annotation_fragments": 6, + "links": [ + { + "page": 1, + "rect": [ + 98.352996, + 197.929016, + 160.666994, + 210.029015 + ], + "target_page": 2, + "target_y": 495.591004 + }, + { + "page": 1, + "rect": [ + 98.352996, + 161.929017, + 198.196992, + 174.029017 + ], + "target_page": 2, + "target_y": 226.591015 + }, + { + "page": 2, + "rect": [ + 55.684998, + 183.364016, + 227.956991, + 195.464016 + ], + "uri": "https://docs.fullbleed.dev/" + }, + { + "page": 2, + "rect": [ + 55.684998, + 162.364017, + 160.796994, + 174.464017 + ], + "uri": "mailto:hello@example.com" + }, + { + "page": 2, + "rect": [ + 39.659998, + 105.60502, + 60.682998, + 116.495019 + ], + "target_page": 1, + "target_y": 555.591002 + }, + { + "page": 2, + "rect": [ + 60.707998, + 105.60502, + 112.939995, + 116.495019 + ], + "target_page": 1, + "target_y": 555.591002 + } + ], + "diagnostics_ok": true, + "component_snippet_verified": true, + "component_annotation_fragments": 3, + "visual_review_pending": false, + "source_sha256": { + "report.html": "532fe583e0ca173443e0bfc55d7f20712c83d04cea63072d38c088cd61760354", + "report.css": "c3c2d6f4d1ff17fb0604cf049ba096c7deb19bd4b0d1f9201799797e3cdb9183", + "render.py": "26bc3dbc4a0107cbaccda75469a57be831ffe5fc69908ad1c7ffcac6b6ce023c" + }, + "files": { + "component-links.pdf": "d8eb3d846ca2c71a72a69bee5b806dffb74f69f643dda223b16185054b149a89", + "component-styled.html": "12f8304efbde609e039d08fb3aa480cde3fb85643e7b096d4050c5958497bb60", + "component-unstyled.html": "ace387d8d59fd5e56e09e1d1f1ceda50fbae79fdb3fa7f9aee281b415acc62d5", + "diagnostics.json": "63e1c38dc9bfd16e1307c69cf823387235240398d2017c6a882e5cf0395542a2", + "diagnostics.stderr.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "pdfium-page-1.png": "8d6a014be2a686cc37fcea4a866dcebd5a6671dfee0520ce4b0531bbfee3ea15", + "pdfium-page-2.png": "bf1b28c24516e2e765116558fcc6b36d9282f018fee6fe074f2185563e9467a1" + }, + "scope": "Actual two-page report, all five authored links in six native annotation fragments, page content and bounds, independent PDFium renders, runtime layout/font diagnostics, and the component snippet exports. The back link has two adjacent fragments with the same destination. Visual review is separate. No universal viewer or accessibility claim.", + "visual_review": { + "at": "2026-10-23T13:23:50.683692+00:00", + "reviewed_pages": [ + 1, + 2 + ], + "native_png_pages": [ + 1, + 2 + ], + "independent_reader": "PDFium", + "observations": [ + "Two complete A5 pages; headings, metrics, index and footer fit without overlap or clipping.", + "All content and both page headers are present; the initial preview concern was not reproduced.", + "Link text is readable and underlined; both section destinations and the return link resolve to the intended pages.", + "Native width is 839 pixels; PDFium width is 840 at 144dpi because of page-size rounding. No pixel-parity assertion is made." + ], + "reproduced_from_public_wheel": true, + "matching_pdf_and_native_pngs": true, + "matching_independent_pdfium_pngs": true, + "local_receipt_sha256": "9e0eac8907af7d286afc9b4f348738ad6c1fd95e7190c038b77e28c10602d4e8" + }, + "public_package": { + "version": "2.6.0a14", + "source_commit": "0e418e174a0d1a4cfe44bc29828c48e635829d90", + "release_verification_sha256": "e373f26fa3636e7e117d129161e31a0cb03d4e0688db1bdf6507495232b8db68" + } +} diff --git a/docs/examples.md b/docs/examples.md index 2e93d88..09b6c8e 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -14,8 +14,8 @@ Common Ground report, Riverton notice, and Hillside statements were generated with the public **Fullbleed 2.4.0** wheel; the pandas report below uses **2.5.2**, the bilingual invoice uses **2.5.8**, and the Markdown brief uses **2.5.10**. The numbered report uses **2.5.18**, and the Streamlit report uses -**2.5.23**. All sample data and organizations -are fictional. +**2.5.23**. The linked project handoff uses the opt-in **2.6.0a14** +prerelease. All sample data and organizations are fictional. [Download showcase sources](assets/showcase/source.zip){ .md-button .md-button--primary } [Browse the source](https://github.com/fullbleed-engine/fullbleed-official/tree/b57e8ea5bb8315f04f7daac41a3489d21a2be158/examples/design_showcase){ .md-button } @@ -107,6 +107,28 @@ project outline a clear visual structure. +## A connected project handoff { #linked-report } + +
Keep the handoff clear, connected, and easy to use.
+ +""" +css = """ +@page { size: A5; margin: 18mm; } +body { font-family: Inter; font-size: 11pt; line-height: 16pt; } +a { color: #12665c; text-decoration: underline; } +""" + +font = files("fullbleed_assets").joinpath("fonts/Inter-Variable.ttf") +engine = fullbleed.PdfEngine(font_files=[str(font)]) +Path("report.pdf").write_bytes(engine.render_pdf(html, css)) +``` + +The renderer does not fetch the linked page. It stores the destination in +the PDF; the reader handles activation. PDF-reader security settings can +affect opening external URLs. + +| Destination | HTML | +| --- | --- | +| Web page | `Open the source report` | +| Email address | `Email the project team` | +| Document section | `Project notes` with an element whose `id` is `findings` | + +Email links use the reader's configured mail application. The example address +is illustrative. Use descriptive labels so the destination is clear before +someone selects a link. + +## Adapt the complete report + +Unzip the project, then run: + +```sh +python -m pip install -r requirements.txt +python render.py +``` + +Edit `report.html` for the content and destinations. Edit `report.css` for +the color palette, spacing, typography, and page layout. The script writes +`output/linked-report.pdf` and two PNG previews. Open the PDF to try the links; +PNG previews show the layout and do not contain interactive links. + +Use unique IDs. Missing destinations produce no active internal link, and +duplicate IDs resolve to the first occurrence. Link text may span multiple +activation regions when it wraps or contains separate shaped fragments. + +## Use the same links in components + +The component authoring path emits HTML anchors with the same behavior. +Pass native HTML attributes through `attrs`, including a heading's `id`: + +```python +from pathlib import Path +from fullbleed.components import Document, Heading, Renderer +from fullbleed.components.bootstrap import Link + +document = Document( + Link("Project notes", href="#findings"), + Heading("Project notes", level=2, attrs={"id": "findings"}), + Link("Documentation", href="https://docs.fullbleed.dev/"), + title="Component link example", +) +Path("component-links.pdf").write_bytes(document.render_pdf(Renderer())) +styled_html = document.to_html(include_styles=True) +unstyled_html = document.to_html(include_styles=False) +``` + +Both HTML exports keep the `href` values. The new component API remains alpha; +see its [release status](component-status.md) and +[styling and export guide](component-preview-12.md). + +## What was checked + +The report was checked for two complete pages and working section, web, and +email destinations. Its layout was reviewed in native previews and an +independent PDF reader; layout/font checks reported no overflow or missing +glyphs. Both Python examples were executed, and the component example's +styled and unstyled HTML exports were inspected. + +[Verification record](../assets/pdf-links-alpha14/verification.json) · +[Release evidence](https://github.com/fullbleed-engine/fullbleed-official/releases/tag/v2.6.0a14) + +Keep links rectangular when precise activation boundaries matter. Some readers +expand the clickable area of rotated or diagonally clipped links. This example +does not establish keyboard, screen-reader, or PDF/UA conformance. The +[navigation reference and reader observations](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.6.0a14/docs/pdf-links.md) +describe the tested geometry and remaining limits. diff --git a/mkdocs.yml b/mkdocs.yml index a418d0b..8151d4d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -91,6 +91,7 @@ nav: - JavaScript components and JSX: guides/javascript-components.md - C# components and styles: guides/csharp-components.md - Complete HTML documents: guides/complete-html.md + - Clickable PDF links: guides/pdf-links.md - Invoices from data: guides/invoices.md - Numbered reports: guides/numbered-reports.md - Markdown to PDF: guides/markdown-pdf.md