diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 811e536..6edc18d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -132,7 +132,7 @@ jobs: retention-days: 7 - name: Verify downloadable playground projects run: | - python -m pip install fullbleed==2.5.8 + python -m pip install fullbleed==2.5.11 pypdf==6.19.0 node tools/verify_project_export.mjs python -I tools/verify_project_export.py - uses: actions/upload-artifact@v4 diff --git a/docs/assets/playground/LICENSES.txt b/docs/assets/playground/LICENSES.txt index 6370232..c1eb13d 100644 --- a/docs/assets/playground/LICENSES.txt +++ b/docs/assets/playground/LICENSES.txt @@ -1,4 +1,4 @@ -fullbleed 2.5.8 — MIT +fullbleed 2.5.11 — MIT MIT License @@ -126,6 +126,508 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +fullbleed 2.5.11 / src/preview_fonts/LICENSE-Liberation.txt + +Digitized data copyright (c) 2010 Google Corporation + with Reserved Font Arimo, Tinos and Cousine. +Copyright (c) 2012 Red Hat, Inc. + with Reserved Font Name Liberation. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE The goals of the Open Font License (OFL) are to stimulate +worldwide development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to provide +a free and open framework in which fonts may be shared and improved in +partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. +The fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + + + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. +This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components +as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting ? in part or in whole ? +any of the components of the Original Version, by changing formats or +by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer +or other person who contributed to the Font Software. + + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components,in + Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the + corresponding Copyright Holder. This restriction only applies to the + primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + +5) The Font Software, modified or unmodified, in part or in whole, must + be distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under + this license does not apply to any document created using the Font + Software. + + + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + + + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. + + + +fullbleed 2.5.11 / src/preview_fonts/LICENSE-NotoSans.txt + +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) + +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + +fullbleed 2.5.11 / src/preview_fonts/LICENSE-NotoSansMath.txt + +Copyright 2018 Google LLC. All Rights Reserved. + +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/math) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + +fullbleed 2.5.11 / src/preview_fonts/LICENSE-NotoSansSymbols.txt + +Copyright 2016 Google LLC. All Rights Reserved. + +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + +fullbleed 2.5.11 / src/preview_fonts/LICENSE-NotoSansSymbols2.txt + +Copyright 2017 Google Inc. All Rights Reserved. + +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + browser_wasi_shim 0.4.2 — MIT Permission is hereby granted, free of charge, to any diff --git a/docs/assets/playground/app.js b/docs/assets/playground/app.js index edc9d8f..7ad215b 100644 --- a/docs/assets/playground/app.js +++ b/docs/assets/playground/app.js @@ -37,7 +37,7 @@ function clearOutput() { el('placeholder').hidden = false; el('placeholder').textContent = 'Render your document to see it here.'; el('page-count').textContent = '—'; - el('result').textContent = 'Fullbleed 2.5.8'; + el('result').textContent = 'Fullbleed 2.5.11'; el('previous').disabled = el('next').disabled = true; setDownload(false); } diff --git a/docs/getting-started/rust.md b/docs/getting-started/rust.md index e669161..2f47dbe 100644 --- a/docs/getting-started/rust.md +++ b/docs/getting-started/rust.md @@ -15,7 +15,7 @@ With Rust 1.85 or newer and a working native Rust toolchain: ```bash cargo new invoice-demo cd invoice-demo -cargo add fullbleed@=2.5.8 +cargo add fullbleed@=2.5.11 ``` Replace `src/main.rs` with: @@ -67,7 +67,7 @@ cargo run --release --locked --manifest-path examples/rust/Cargo.toml --bin from Open `output/rust-invoice/document.pdf` or `page-1.png` in the same directory. Replace both `invoice` input filenames with `report` for the report design, and choose a new output directory. The report produces three -pages. These examples pin Fullbleed 2.5.8 and include a Cargo lockfile. +pages. These examples pin Fullbleed 2.5.11 and include a Cargo lockfile. For the service notice or your own edits, choose **Download project** in the playground. Extract the ZIP into `my-project` in this repository, then run: @@ -115,7 +115,7 @@ designed example and downloaded playground projects use the same path. Use [the latest hosted Rust API reference](https://docs.rs/fullbleed/latest/fullbleed/) for `FullBleed`, its builder, compiled templates, and document types. The -[2.5.8 source](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.5.8/src/lib.rs) +[2.5.11 source](https://github.com/fullbleed-engine/fullbleed-official/blob/v2.5.11/src/lib.rs) matches the version pinned by these examples. Check [CSS coverage](../css-coverage.md) before adapting a web layout, and inspect your actual output when changing fonts or content. These examples use trusted diff --git a/docs/index.md b/docs/index.md index 485fefb..82e7bed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -153,7 +153,7 @@ Pick your application stack. Generate a PDF locally or download one from the bro ```bash cargo new invoice-demo cd invoice-demo - cargo add fullbleed@=2.5.8 + cargo add fullbleed@=2.5.11 ``` Replace `src/main.rs` with: @@ -265,6 +265,6 @@ Fullbleed uses static HTML/CSS as its layout language. Read the [CSS coverage](c ## Open source, with inspectable evidence -Fullbleed is [MIT licensed](https://github.com/fullbleed-engine/fullbleed-official/blob/master/LICENSE). The [2.5.8 release](https://github.com/fullbleed-engine/fullbleed-official/releases/tag/v2.5.8) includes downloadable wheels and retained engineering evidence. See the [smaller-PDF before/after check](guides/pdf-size.md) and the [performance report](guides/performance.md) for specific measured workloads and their limits. +Fullbleed is [MIT licensed](https://github.com/fullbleed-engine/fullbleed-official/blob/master/LICENSE). The [2.5.11 release](https://github.com/fullbleed-engine/fullbleed-official/releases/tag/v2.5.11) includes downloadable wheels and retained engineering evidence. See the [smaller-PDF before/after check](guides/pdf-size.md) and the [performance report](guides/performance.md) for specific measured workloads and their limits. [Read the Python API](engine/pdf-engine.md) · [Set up a coding agent](guides/ai-agents.md) · [Report an issue](https://github.com/fullbleed-engine/fullbleed-official/issues) · [Support Fullbleed](support.md) diff --git a/docs/playground.md b/docs/playground.md index 3768b8f..8fa7b07 100644 --- a/docs/playground.md +++ b/docs/playground.md @@ -6,7 +6,7 @@ hide: - toc --- - +
@@ -28,7 +28,7 @@ hide:
THE PRINTED PAGE
—

Preparing your first PDF…

- +

Loading the examples…

@@ -51,7 +51,7 @@ python render.py Open `output/document.pdf`; PNG previews are in `output/preview/`. Edit `input.html` or `style.css`, then run the script again. The project uses Fullbleed -2.5.8 to match this demo. Use a Python 3.10 or newer virtual environment. The ZIP +2.5.11 to match this demo. Use a Python 3.10 or newer virtual environment. The ZIP is assembled on your device, using the source present when you click the button. It does not upload your document. @@ -72,12 +72,16 @@ or `notice`. You can keep editing and add `--watch` to rebuild after each save. [Run the Python notebook](getting-started/notebook.md){ .md-button } [CSS support](css-coverage.md){ .md-button } -The playground uses the released **Fullbleed 2.5.8** Rust engine, compiled to +The playground uses the released **Fullbleed 2.5.11** Rust engine, compiled to WebAssembly. Page previews are rendered from the same PDF you download, including the report's gradient chart fills. It accepts static HTML/CSS, with Inter, DM Serif Display, and Bebas Neue embedded fonts. JavaScript inside your document is not executed, and remote assets are not loaded. Use inline SVG for artwork. +Unembedded Helvetica, Times, and Courier variants also produce visible previews. +The [font-preview guide](engine/assets.md#standard-14-native-previews) explains +the bundled substitute fonts used for those faces. + This demo handles up to **6 pages and 200 KB of HTML/CSS**, with a 30-second render limit. These are playground limits; the local Python and Rust packages support larger jobs. Sample organizations and data are @@ -89,7 +93,7 @@ fictional. · [Project download checks](assets/playground/project-verification.json) · [Third-party licenses](assets/playground/LICENSES.txt) - + Build this into your own web app: [download the browser SDK starter](guides/browser-pdf.md), with editable templates and local PDF downloads. diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock index f6616aa..49c8cc8 100644 --- a/examples/rust/Cargo.lock +++ b/examples/rust/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "fullbleed" -version = "2.5.8" +version = "2.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443024fa33d7f7d9b3115d884120bfba2b43590ce3b89fc38fe3b8ec9b7b97d4" +checksum = "a672e0401fb42234b2e5a619adbe3a431db646780bf3398a44c70d8855c63270" dependencies = [ "fullbleed_audit_contract", "subsetter", diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 5dbdf97..0e74c4c 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT" publish = false [dependencies] -fullbleed = "=2.5.8" +fullbleed = "=2.5.11" [[bin]] name = "hello" diff --git a/examples/rust/README.md b/examples/rust/README.md index 3f3f84c..9563c13 100644 --- a/examples/rust/README.md +++ b/examples/rust/README.md @@ -1,6 +1,6 @@ # Fullbleed from Rust -These examples use the published `fullbleed` 2.5.8 crate. They require Rust +These examples use the published `fullbleed` 2.5.11 crate. They require Rust 1.85 or newer and a working native Rust toolchain; they do not require Python. From the root of this documentation repository: diff --git a/playground/README.md b/playground/README.md index 7f6103b..569a72f 100644 --- a/playground/README.md +++ b/playground/README.md @@ -1,6 +1,6 @@ # Fullbleed browser playground -An optional static website adapter for the unchanged published `fullbleed = 2.5.8` +An optional static website adapter for the unchanged published `fullbleed = 2.5.11` Rust crate. It compiles to `wasm32-wasip1` and runs inside a Web Worker using `@bjorn3/browser_wasi_shim` 0.4.2. It adds no dependencies to the Python wheel or the core Rust crate. The engine produces the PDF, then renders PNG page previews @@ -28,8 +28,9 @@ python tools/check_site.py site The build uses both lockfiles, publishes a toolchain/hash record, and retains the generated verification report next to the browser assets. The verification script compares native and WASI PDF and PNG bytes for the invoice, report, and -notice, an edited invoice, and three gradient cases; checks expected gradient -colors and that an edit changes the output; and exercises source/page limits and +notice, an edited invoice, three gradient cases, and all 12 unembedded Latin +standard font faces; checks expected gradient colors and visible text in every +standard-font row; and exercises source/page limits and recovery. The browser UI is additionally reviewed with actual downloaded PDFs. These checks establish the recorded fixtures only, not universal platform parity or ISO conformance. @@ -49,14 +50,16 @@ are checked against the existing attribution manifest before export. After building the playground, verify the complete handoff with: ```sh -python -m pip install fullbleed==2.5.8 +python -m pip install fullbleed==2.5.11 pypdf==6.19.0 node tools/verify_project_export.mjs python -I tools/verify_project_export.py ``` The verifier checks ZIP integrity, exact source and font/license bytes, export replay, failure recovery, and Python/WASI PDF and PNG equality for the invoice, -report, notice, an edited invoice, and three gradient cases. Extracted projects run from paths with +report, notice, an edited invoice, three gradient cases, and the standard-font +fixture. An independent PDF reader verifies that all 12 font faces remain +unembedded and their text is intact. Extracted projects run from paths with spaces and a different current working directory. Results are retained in `playground/project-verification`. @@ -68,4 +71,6 @@ recover; no server process is created. `docs/assets/playground/fonts` contains the licensed fonts used by the showcase. Their upstream attribution is retained in `LICENSES.txt` and `font-sources.json`. +`LICENSES.txt` also includes the licenses for substitute fonts compiled into the +engine's standard-font preview renderer. The demo's limits and embedded assets are not the full local engine feature set. diff --git a/playground/engine/Cargo.lock b/playground/engine/Cargo.lock index 5c01b35..9f0fbf1 100644 --- a/playground/engine/Cargo.lock +++ b/playground/engine/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "fullbleed" -version = "2.5.8" +version = "2.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443024fa33d7f7d9b3115d884120bfba2b43590ce3b89fc38fe3b8ec9b7b97d4" +checksum = "a672e0401fb42234b2e5a619adbe3a431db646780bf3398a44c70d8855c63270" dependencies = [ "fullbleed_audit_contract", "subsetter", diff --git a/playground/engine/Cargo.toml b/playground/engine/Cargo.toml index 5c31252..0ddd9cd 100644 --- a/playground/engine/Cargo.toml +++ b/playground/engine/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" publish = false [dependencies] -fullbleed = "=2.5.8" +fullbleed = "=2.5.11" [profile.release] lto = true diff --git a/playground/engine/src/main.rs b/playground/engine/src/main.rs index 53e60f5..3b5c573 100644 --- a/playground/engine/src/main.rs +++ b/playground/engine/src/main.rs @@ -36,7 +36,7 @@ fn render() -> Result<(), Box> { std::fs::write( "result.json", format!( - "{{\"engine\":\"2.5.8\",\"pages\":{},\"missing_glyphs\":{}}}", + "{{\"engine\":\"2.5.11\",\"pages\":{},\"missing_glyphs\":{}}}", pages.len(), glyphs.missing().len() ), diff --git a/playground/standard-font-preview-verification.json b/playground/standard-font-preview-verification.json new file mode 100644 index 0000000..a86c639 --- /dev/null +++ b/playground/standard-font-preview-verification.json @@ -0,0 +1,233 @@ +{ + "checkedAt": "2026-10-07T04:45:54.191594+00:00", + "scope": "Local Windows verification of the 2.5.11 Playground upgrade; fixture-specific results, not universal parity or standards conformance.", + "baselineCiRun": "https://github.com/fullbleed-engine/docs/actions/runs/37571033471", + "fontPreview": { + "before": { + "engine": "2.5.8", + "pdfSha256": "1b8c17ab56c4dbd073b4b108528d617d272c735ffd14056cac2cd5394e988cbc", + "pngSha256": "2cc00648b3ae8e45f95e8a92a750972137f2a268329c734b2f26375435a536dc", + "inkPixels": 0, + "fonts": [ + "/Courier", + "/Courier-Bold", + "/Courier-BoldOblique", + "/Courier-Oblique", + "/Helvetica", + "/Helvetica-Bold", + "/Helvetica-BoldOblique", + "/Helvetica-Oblique", + "/Times-Bold", + "/Times-BoldItalic", + "/Times-Italic", + "/Times-Roman" + ], + "allFontsUnembedded": true + }, + "after": { + "engine": "2.5.11", + "pdfSha256": "1b8c17ab56c4dbd073b4b108528d617d272c735ffd14056cac2cd5394e988cbc", + "pngSha256": "40dc4face028f04b148ea5016b0c64498fbeb0847c61d09099ab9eb36107759a", + "inkPixels": 15281, + "fonts": [ + "/Courier", + "/Courier-Bold", + "/Courier-BoldOblique", + "/Courier-Oblique", + "/Helvetica", + "/Helvetica-Bold", + "/Helvetica-BoldOblique", + "/Helvetica-Oblique", + "/Times-Bold", + "/Times-BoldItalic", + "/Times-Italic", + "/Times-Roman" + ], + "allFontsUnembedded": true + }, + "pdfUnchanged": true, + "oldPreviewRejectedByNewRegression": true + }, + "nativeWasiFixtures": 8, + "pythonWasiProjectFixtures": 8, + "downloadedZipMatchesVerifiedPythonProject": true, + "downloadedZipSha256": "3c454dd4a1d43989a3c31ce47afc89591f67f515315c98a0f94a82f70b072ae0", + "galleryComparison": [ + { + "name": "invoice", + "oldBytes": 40289, + "newBytes": 40386, + "oldPages": 1, + "newPages": 1, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 3296, + "totalPixels": 891662, + "differenceBox": [ + 88, + 384, + 739, + 1075 + ] + } + ] + }, + { + "name": "invoice-edited", + "oldBytes": 40288, + "newBytes": 40392, + "oldPages": 1, + "newPages": 1, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 3314, + "totalPixels": 891662, + "differenceBox": [ + 88, + 384, + 739, + 1075 + ] + } + ] + }, + { + "name": "report", + "oldBytes": 46259, + "newBytes": 46044, + "oldPages": 3, + "newPages": 3, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 820, + "totalPixels": 891662, + "differenceBox": [ + 110, + 59, + 739, + 1075 + ] + }, + { + "page": 2, + "changedPixels": 1675, + "totalPixels": 891662, + "differenceBox": [ + 132, + 557, + 735, + 1075 + ] + }, + { + "page": 3, + "changedPixels": 1985, + "totalPixels": 891662, + "differenceBox": [ + 105, + 435, + 739, + 1075 + ] + } + ] + }, + { + "name": "notice", + "oldBytes": 34067, + "newBytes": 33990, + "oldPages": 1, + "newPages": 1, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 541, + "totalPixels": 891662, + "differenceBox": [ + 116, + 1066, + 735, + 1075 + ] + } + ] + }, + { + "name": "linear-gradient", + "oldBytes": 961, + "newBytes": 961, + "oldPages": 1, + "newPages": 1, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 0, + "totalPixels": 17689, + "differenceBox": null + } + ] + }, + { + "name": "translucent-gradient", + "oldBytes": 20485, + "newBytes": 20485, + "oldPages": 1, + "newPages": 1, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 0, + "totalPixels": 17689, + "differenceBox": null + } + ] + }, + { + "name": "hard-radial-gradient", + "oldBytes": 1292, + "newBytes": 1292, + "oldPages": 1, + "newPages": 1, + "textTokensEqual": true, + "whitespaceNormalizedCharactersEqual": true, + "images": [ + { + "page": 1, + "changedPixels": 0, + "totalPixels": 17689, + "differenceBox": null + } + ] + } + ], + "browser": "154.0.8037.98", + "viewports": [ + 1440, + 390, + 320 + ], + "sampleBrowserChecks": 9, + "keyboardSourceTabAndRender": true, + "pageErrors": [], + "writeRequests": [], + "visualReview": "Reviewed before/after invoice, report and notice previews and the desktop/narrow editor. Text, tables, artwork and controls remain legible without clipping; small text alignment shifts were retained.", + "wasmMemoryMaximum": 268435456, + "rustGuideFixtures": 8, + "rustGuideMatchesPlaygroundPdfAndPng": true, + "homepageRustSnippetCompiled": true +} diff --git a/tools/build_playground.py b/tools/build_playground.py index d3bd030..3e8e7ee 100644 --- a/tools/build_playground.py +++ b/tools/build_playground.py @@ -36,6 +36,11 @@ def run(args, cwd): if not license_file.exists(): raise SystemExit(f"Missing bundled license for {package['name']}") license_parts.append(f"{package['name']} {package['version']} — MIT\n\n" + license_file.read_text(encoding='utf-8')) +for name in ['Liberation', 'NotoSans', 'NotoSansMath', 'NotoSansSymbols', 'NotoSansSymbols2']: + # Font programs are compiled into the engine; retain their own licenses. + relative = f'src/preview_fonts/LICENSE-{name}.txt' + notice = Path(core['manifest_path']).parent / relative + license_parts.append(f"fullbleed {core['version']} / {relative}\n\n" + notice.read_text(encoding='utf-8')) license_parts.append('browser_wasi_shim 0.4.2 — MIT\n\n' + (shim / 'LICENSE-MIT').read_text(encoding='utf-8')) for path in sorted(ASSETS.glob('fonts/*-OFL.txt')): license_parts.append(path.name + '\n\n' + path.read_text(encoding='utf-8')) @@ -56,8 +61,8 @@ def run(args, cwd): 'limits': {'source_bytes': 200000, 'pages': 6, 'wasm_memory_bytes': 268435456, 'render_seconds': 30}, 'artifacts': {}, } -for path in sorted([ASSETS / 'fullbleed.wasm', ASSETS / 'fonts.zip', *ASSETS.glob('fonts/*.ttf')]): +for path in sorted([ASSETS / 'fullbleed.wasm', ASSETS / 'fonts.zip', ASSETS / 'LICENSES.txt', *ASSETS.glob('fonts/*.ttf')]): raw = path.read_bytes() record['artifacts'][path.relative_to(ASSETS).as_posix()] = {'bytes': len(raw), 'sha256': hashlib.sha256(raw).hexdigest()} -(ASSETS / 'build.json').write_text(json.dumps(record, indent=2) + '\n', encoding='utf-8') +(ASSETS / 'build.json').write_text(json.dumps(record, indent=2) + '\n', encoding='utf-8', newline='\n') print(f"Built playground: {record['artifacts']['fullbleed.wasm']['bytes']:,} bytes of WebAssembly") diff --git a/tools/playground-fixtures/standard-fonts.mjs b/tools/playground-fixtures/standard-fonts.mjs new file mode 100644 index 0000000..5cf5464 --- /dev/null +++ b/tools/playground-fixtures/standard-fonts.mjs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +import assert from 'node:assert/strict'; +import { decodePng } from './png.mjs'; + +export const standardFontFaces = [ + 'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 'Helvetica-BoldOblique', + 'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic', + 'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique', +]; + +export const standardFontFixture = { + name: 'standard-fonts', pages: 1, + html: standardFontFaces.map(face => `

${face} preview

`).join(''), + css: '@page { size: A5; margin: 15mm } body { color: #111; font-size: 14pt } p { margin: 0 0 6pt; line-height: 1.3 }', +}; + +// Each line needs real ink. Equality between native and WASI blank images is insufficient. +export function checkStandardFonts(bytes) { + const image = decodePng(Buffer.from(bytes)); + assert.equal(image.width, 559); + assert.equal(image.height, 794); + const top = 15 * 96 / 25.4, line = (14 * 1.3 + 6) * 96 / 72; + return standardFontFaces.map((face, index) => { + const firstY = Math.floor(top + index * line), lastY = Math.floor(top + (index + 1) * line); + let inkPixels = 0; + for (let y = firstY; y < lastY; y++) { + for (let x = 0; x < image.width; x++) { + const [r, g, b, a] = image.pixel(x, y); + if (a > 0 && Math.min(r, g, b) < 245) inkPixels++; + } + } + assert(inkPixels > 200, `${face}: preview text is missing (${inkPixels} ink pixels)`); + return { face, firstY, lastY, inkPixels }; + }); +} diff --git a/tools/verify_playground.mjs b/tools/verify_playground.mjs index be96d2a..aeca8e8 100644 --- a/tools/verify_playground.mjs +++ b/tools/verify_playground.mjs @@ -8,6 +8,7 @@ import { join } from 'node:path'; import { render } from '../docs/assets/playground/renderer.js'; import { prepareExample } from '../docs/assets/playground/examples.js'; import { gradientFixtures, checkGradient } from './playground-fixtures/gradients.mjs'; +import { standardFontFixture, checkStandardFonts } from './playground-fixtures/standard-fonts.mjs'; const root = fileURLToPath(new URL('../', import.meta.url)); const engine = join(root, 'playground/engine'); @@ -46,7 +47,7 @@ const fonts = Object.fromEntries(await Promise.all(['Inter-Variable.ttf', 'DMSer const manifest = JSON.parse(await readFile(join(assets, 'build.json'), 'utf8')); const report = { engine: manifest.engine.version, preview_source: 'finalized_pdf', platform: process.platform, node: process.version, wasm_sha256: hash(wasmBytes), wasm_memory_maximum: memoryMaximum, fixtures: [], checks: ['Module declares a 256 MiB memory ceiling and no socket imports'] }; -async function fixture(name, html, css, expectedPages, gradient) { +async function fixture(name, html, css, expectedPages, gradient, standardFonts = false) { const files = { ...fonts, 'input.html': Buffer.from(html), 'style.css': Buffer.from(css) }; // Retain the exact prepared inputs for other language bindings to replay. await writeFile(join(evidence, `${name}.html`), html); @@ -73,8 +74,9 @@ async function fixture(name, html, css, expectedPages, gradient) { assert.equal(hash(rerun.outputs[file]), expectedHash, `${name}/${file}: nondeterministic rerender`); } const color_probes = gradient ? checkGradient(gradient, result.outputs['page-1.png']) : []; + const standard_font_rows = standardFonts ? checkStandardFonts(result.outputs['page-1.png']) : []; report.fixtures.push({ name, pages: expectedPages, missing_glyphs: 0, native_wasi_equal: true, repeat_equal: true, hashes, - color_probes, memory_bytes: result.memory, + color_probes, standard_font_rows, memory_bytes: result.memory, source_files: { html: `${name}.html`, css: `${name}.css`, html_sha256: hash(Buffer.from(html)), css_sha256: hash(Buffer.from(css)) } }); return { files, pdfHash: hash(pdf), memory: result.memory }; } @@ -96,6 +98,8 @@ for (const gradient of gradientFixtures) { await fixture(gradient.name, gradient.html, gradient.css, gradient.pages, gradient); } report.checks.push('Linear, translucent, and hard radial gradient interiors match expected colors'); +await fixture(standardFontFixture.name, standardFontFixture.html, standardFontFixture.css, 1, null, true); +report.checks.push('All 12 unembedded Latin standard font faces have visible preview text'); await assert.rejects(render(module, { ...fonts, 'input.html': Buffer.from('x'.repeat(200001)), 'style.css': Buffer.from('') }), /200 KB/); report.checks.push('Oversized source rejected by the Rust adapter'); await assert.rejects(render(module, { ...fonts, 'input.html': Buffer.from('
Page
'.repeat(7)), 'style.css': Buffer.from('@page {size:A4} div{break-after:page}') }), /1 to 6 pages/); diff --git a/tools/verify_project_export.mjs b/tools/verify_project_export.mjs index 1a9101b..778c824 100644 --- a/tools/verify_project_export.mjs +++ b/tools/verify_project_export.mjs @@ -10,6 +10,7 @@ import { storeZip } from '../docs/assets/playground/zip-store.js'; import { render } from '../docs/assets/playground/renderer.js'; import { prepareExample } from '../docs/assets/playground/examples.js'; import { gradientFixtures, checkGradient } from './playground-fixtures/gradients.mjs'; +import { standardFontFixture, checkStandardFonts } from './playground-fixtures/standard-fonts.mjs'; const root = fileURLToPath(new URL('../', import.meta.url)); const assets = join(root, 'docs/assets/playground'); @@ -24,11 +25,13 @@ const fonts = Object.fromEntries(await Promise.all([ const cases = []; const fixtures = [['invoice', 1], ['report', 3], ['notice', 1], ['invoice-edited', 1]].map(([name, pages]) => ({ name, pages })); fixtures.push(...gradientFixtures); +fixtures.push(standardFontFixture); for (const fixture of fixtures) { const { name, pages } = fixture; const gradient = Boolean(fixture.probes); - const example = gradient ? 'report' : name === 'invoice-edited' ? 'invoice' : name; - let { html, css } = gradient ? fixture : prepareExample(example, + const standardFonts = name === standardFontFixture.name; + const example = gradient ? 'report' : (name === 'invoice-edited' || standardFonts) ? 'invoice' : name; + let { html, css } = gradient || standardFonts ? fixture : prepareExample(example, await readFile(join(root, `docs/assets/showcase/${example}.html`), 'utf8'), await readFile(join(root, `docs/assets/showcase/${example}.css`), 'utf8')); if (name === 'invoice-edited') { @@ -48,8 +51,9 @@ for (const fixture of fixtures) { assert.equal(inspection.pages, pages); assert.equal(inspection.missing_glyphs, 0, `${name}: missing glyphs`); const color_probes = gradient ? checkGradient(fixture, result.outputs['page-1.png']) : []; + const standard_font_rows = standardFonts ? checkStandardFonts(result.outputs['page-1.png']) : []; for (const [file, bytes] of Object.entries(result.outputs)) await writeFile(join(expected, file), bytes); - cases.push({ name, pages, missing_glyphs: 0, color_probes, zip_sha256: hash(zip), pdf_sha256: hash(result.outputs['output.pdf']) }); + cases.push({ name, pages, missing_glyphs: 0, color_probes, standard_font_rows, zip_sha256: hash(zip), pdf_sha256: hash(result.outputs['output.pdf']) }); } for (const name of ['../escape', '/absolute', 'C:/drive', 'fonts/../escape', 'fonts\\escape', 'bad\0name']) { diff --git a/tools/verify_project_export.py b/tools/verify_project_export.py index dc4f699..3222864 100644 --- a/tools/verify_project_export.py +++ b/tools/verify_project_export.py @@ -9,6 +9,7 @@ import zipfile import fullbleed +from pypdf import PdfReader ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / 'playground/project-verification' @@ -53,18 +54,33 @@ assert b'DOCUMENT DESIGN EXAMPLE' in (project / 'input.html').read_bytes() assert b'TAGGED DOCUMENT EXAMPLE' not in (project / 'input.html').read_bytes() assert not fullbleed.inspect_pdf(str(pdf))['profile']['struct_tree_root_present'] + standard_font_faces = [] + if name == 'standard-fonts': + reader = PdfReader(pdf) + assert len(reader.pages) == 1 + fonts = [font.get_object() for font in reader.pages[0]['/Resources']['/Font'].values()] + expected_faces = { + 'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 'Helvetica-BoldOblique', + 'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic', + 'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique', + } + standard_font_faces = sorted(str(font['/BaseFont']).removeprefix('/') for font in fonts) + assert len(fonts) == 12 and set(standard_font_faces) == expected_faces + assert all('/FontDescriptor' not in font for font in fonts), 'Fixture must use unembedded fonts' + assert all(face + ' preview' in reader.pages[0].extract_text() for face in expected_faces) for number, preview in enumerate(rendered['previews'], 1): assert Path(preview).read_bytes() == (expected / f'page-{number}.png').read_bytes(), name + ': PNG mismatch' records.append(dict(name=name, pages=case['pages'], files=len(required), zip_crc_valid=True, sources_preserved=True, manifest_hashes_valid=True, fonts_and_licenses_match=True, python_wasi_pdf_equal=True, python_wasi_png_equal=True, + standard_font_faces=standard_font_faces, pdf_sha256=rendered['sha256'])) result = dict(ok=True, platform=sys.platform, python=sys.version.split()[0], engine=metadata.version('fullbleed'), cases=records, source_revision=os.environ.get('GITHUB_SHA'), ci_run_id=os.environ.get('GITHUB_RUN_ID'), archive_checks=json.loads((OUT / 'export-verification.json').read_text())['checks'], preview_source='finalized_pdf', - scope='These seven fixtures and bundled assets; no universal parity or standards-conformance claim.') + scope=f'These {len(records)} fixtures and bundled assets; no universal parity or standards-conformance claim.') (OUT / 'python-verification.json').write_text(json.dumps(result, indent=2) + '\n', encoding='utf-8') (assets / 'project-verification.json').write_text(json.dumps(result, indent=2) + '\n', encoding='utf-8') print(json.dumps(result)) diff --git a/tools/verify_rust_examples.py b/tools/verify_rust_examples.py index 3500ad1..6be8419 100644 --- a/tools/verify_rust_examples.py +++ b/tools/verify_rust_examples.py @@ -83,6 +83,6 @@ def sha256(path): "name": name, "pages": expected_pages, "matches_playground_pdf_and_pngs": True, "sha256": hashes, }) -assert len(report["fixtures"]) == len(playground["fixtures"]) == 7 +assert len(report["fixtures"]) == len(playground["fixtures"]) == 8 (EVIDENCE / "verification.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") print(json.dumps(report, indent=2))