refactor(pdf): share placed-PDF rendering (PDFR) (#876)

This commit is contained in:
skylar taylor-barrick
2026-10-20 19:52:28 -06:00
committed by GitHub
parent 8dff7ecdb0
commit 1420d8c79b
32 changed files with 1791 additions and 80 deletions
+25
View File
@@ -69,3 +69,28 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra
| `docs/brand/LICENSE-brand.txt` | (licence text) | ArtCraft Team | — | Terms for the ArtCraft Marks in `docs/brand/` | | `docs/brand/LICENSE-brand.txt` | (licence text) | ArtCraft Team | — | Terms for the ArtCraft Marks in `docs/brand/` |
| `packaging/macos/dmg/background.svg` | @XusBadia | original work for this repository, derived from the DesignCraft app icon (`assets/app-icon/designcraft.svg`, referenced via `<image>`, not copied); text is outlined Inter / JetBrains Mono glyph paths | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | DesignCraft macOS DMG window background for the Finder window; `packaging/macos/dmg/generate.py` renders it to `background.tiff` (2026-10-08) | | `packaging/macos/dmg/background.svg` | @XusBadia | original work for this repository, derived from the DesignCraft app icon (`assets/app-icon/designcraft.svg`, referenced via `<image>`, not copied); text is outlined Inter / JetBrains Mono glyph paths | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | DesignCraft macOS DMG window background for the Finder window; `packaging/macos/dmg/generate.py` renders it to `background.tiff` (2026-10-08) |
| `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 1x + 2x HiDPI TIFF for the macOS DMG window; includes the app icon (see its row) and LittleCMS's built-in sRGB profile ("No copyright, use freely") | | `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 1x + 2x HiDPI TIFF for the macOS DMG window; includes the app icon (see its row) and LittleCMS's built-in sRGB profile ("No copyright, use freely") |
## Assets bundled by the PDF backend
These assets were already enabled by Hayro’s default font and CMap features. The pinned
backend keeps the same files; hashes and upstream licence notices are retained here.
| Crate / file | Author | Source | Licence | SHA-256 | Use |
| --- | --- | --- | --- | --- | --- |
| `hayro-interpret 0.7.0 / assets/FoxitDingbats.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitDingbats.pfb | BSD-3-Clause | `845c752392b6c914fb989c75a08b7792b88f542d2499042ef2889f8c814a16ed` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixed.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitFixed.pfb | BSD-3-Clause | `b6c8fe53f134b8b6d4578cd2d544df4cee9624c4efa8d51a560fb40ea296101b` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitFixedBold.pfb | BSD-3-Clause | `f1b7159702973f54fc86254ea38bcf3712b2a736eb3a0995751e9f4bb45ad603` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitFixedBoldItalic.pfb | BSD-3-Clause | `8a000945843bd31add06aee63bf9fd41b7578b4f3242a4b7cd46349ad9d24d4d` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitFixedItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitFixedItalic.pfb | BSD-3-Clause | `5007faf8320fa1fcc08b8894b356ad976f60b992ba29ee5272578bbdebaf3876` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSans.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSans.pfb | BSD-3-Clause | `a02339e95d20db14cbdfc1c6baf359cd36a091d5434925c3a4040f60e1a1af9e` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSansBold.pfb | BSD-3-Clause | `7bdfa36de1d5757dcfaf8b8a4be95c6e41c40fa70dbed9d78bf979a7b9959054` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSansBoldItalic.pfb | BSD-3-Clause | `555ce45924338e2e4b32821910221ce85da691fd59a9723c4c971a7c5627fc8b` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSansItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSansItalic.pfb | BSD-3-Clause | `7751cbefeda351708dae8c4a88db043411bc39ab2ae2097685da845d4d16f307` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerif.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSerif.pfb | BSD-3-Clause | `4f57d2b9d884af8f907bf22df6019b52d86cbf6214fdbd02b1ae05472a543f35` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifBold.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSerifBold.pfb | BSD-3-Clause | `0bdf4b04e964139818d51eda03d566ba999fc3ba2421b1c6c51f9dc969022e80` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifBoldItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSerifBoldItalic.pfb | BSD-3-Clause | `a406cac82583bf98175cb62c87ed5e95c45fbb34eece63a10b0a13e793cb2e10` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSerifItalic.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSerifItalic.pfb | BSD-3-Clause | `610ae0687198045c4db5d1a6650fd5e92536631706382eb8b72e38df578d0ae9` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/FoxitSymbol.pfb` | Foxit Software Inc. / PDFium Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/FoxitSymbol.pfb | BSD-3-Clause | `47967d055530e7357088a08403115425643ec2cdfd6201ba8af0fbd7116c1539` | Renders non-embedded standard-14 fonts (pinned hayro-interpret) |
| `hayro-interpret 0.7.0 / assets/CGATS001Compat-v2-micro.icc` | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc | CC0-1.0 | `73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0` | Default CMYK colour conversion (pinned hayro-interpret) |
| `hayro-cmap 0.1.0 / assets/cmaps.brotli` | Adobe | https://crates.io/crates/hayro-cmap/0.1.0 | BSD-3-Clause | `ddf37af521d904d5646c2fc37b2b7e912dc1b56f0d210190b61f598154758e46` | Character-code mapping tables required to read CJK PDFs. Non-visual data, allowed by the PDF compatibility exception |
| `hayro-interpret 0.7.0 / src/font/generated/metrics.rs` | Adobe (Core 14 AFM metrics, PDF specification encodings); tables by The Hayro Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/src/font/generated/metrics.rs | MIT OR Apache-2.0 | `2ec9e608573eda459de1e1d9bda3bb494315e36bee885acc88912a11fe0caa41` | Glyph widths and encodings for non-embedded standard fonts. Non-visual data, allowed by the PDF compatibility exception |
Generated
+12 -7
View File
@@ -832,8 +832,12 @@ source = "git+https://github.com/storytold/craft-libs?rev=82a24b006eefa036b43c09
[[package]] [[package]]
name = "craft-pdf" name = "craft-pdf"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c3487320b2d1de657b860b3c8abb44e1f2b32bd8#c3487320b2d1de657b860b3c8abb44e1f2b32bd8" source = "git+https://github.com/storytold/craft-libs?rev=89dcbb3060377a26c99a6d80caa16084366704c4#89dcbb3060377a26c99a6d80caa16084366704c4"
dependencies = [ dependencies = [
"bytemuck",
"hayro",
"hayro-interpret",
"hayro-syntax",
"krilla", "krilla",
"thiserror 2.0.21", "thiserror 2.0.21",
] ]
@@ -1181,16 +1185,17 @@ dependencies = [
name = "designcraft-render" name = "designcraft-render"
version = "0.10.0" version = "0.10.0"
dependencies = [ dependencies = [
"craft-pdf",
"designcraft-color", "designcraft-color",
"designcraft-compose", "designcraft-compose",
"designcraft-doc", "designcraft-doc",
"designcraft-fonts", "designcraft-fonts",
"designcraft-geom", "designcraft-geom",
"designcraft-images", "designcraft-images",
"hayro",
"image", "image",
"log", "log",
"serde", "serde",
"thiserror 2.0.21",
"vello_common 0.2.0", "vello_common 0.2.0",
"vello_cpu 0.2.0", "vello_cpu 0.2.0",
] ]
@@ -2105,7 +2110,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro" name = "hayro"
version = "0.7.1" version = "0.7.1"
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" source = "git+https://github.com/storytold/craft-libs?rev=89dcbb3060377a26c99a6d80caa16084366704c4#89dcbb3060377a26c99a6d80caa16084366704c4"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"hayro-interpret", "hayro-interpret",
@@ -2135,7 +2140,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro-interpret" name = "hayro-interpret"
version = "0.7.0" version = "0.7.0"
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" source = "git+https://github.com/storytold/craft-libs?rev=89dcbb3060377a26c99a6d80caa16084366704c4#89dcbb3060377a26c99a6d80caa16084366704c4"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"hayro-cmap", "hayro-cmap",
@@ -2153,7 +2158,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro-jbig2" name = "hayro-jbig2"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" source = "git+https://github.com/storytold/craft-libs?rev=89dcbb3060377a26c99a6d80caa16084366704c4#89dcbb3060377a26c99a6d80caa16084366704c4"
dependencies = [ dependencies = [
"fearless_simd", "fearless_simd",
"hayro-ccitt", "hayro-ccitt",
@@ -2177,7 +2182,7 @@ checksum = "885c5ef0654933139a9b9546fc2c69e18d37f38aa2520f079092b1be18f1fcaa"
[[package]] [[package]]
name = "hayro-syntax" name = "hayro-syntax"
version = "0.7.2" version = "0.7.2"
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" source = "git+https://github.com/storytold/craft-libs?rev=89dcbb3060377a26c99a6d80caa16084366704c4#89dcbb3060377a26c99a6d80caa16084366704c4"
dependencies = [ dependencies = [
"flate2", "flate2",
"hayro-ccitt", "hayro-ccitt",
@@ -2541,7 +2546,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla" name = "krilla"
version = "0.8.2" version = "0.8.2"
source = "git+https://github.com/storytold/craft-libs?rev=c3487320b2d1de657b860b3c8abb44e1f2b32bd8#c3487320b2d1de657b860b3c8abb44e1f2b32bd8" source = "git+https://github.com/storytold/craft-libs?rev=89dcbb3060377a26c99a6d80caa16084366704c4#89dcbb3060377a26c99a6d80caa16084366704c4"
dependencies = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
+7 -9
View File
@@ -11,8 +11,8 @@ rust-version = "1.95"
repository = "https://github.com/storytold/designcraft" repository = "https://github.com/storytold/designcraft"
[workspace.dependencies] [workspace.dependencies]
# Shared writer; the immutable revision must be published before this consumer. # Shared reader and writer; publish the immutable revision before this consumer.
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "c3487320b2d1de657b860b3c8abb44e1f2b32bd8", version = "=0.1.0", default-features = false } craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "89dcbb3060377a26c99a6d80caa16084366704c4", version = "=0.1.0", default-features = false }
# internal # internal
designcraft-geom = { path = "crates/geom" } designcraft-geom = { path = "crates/geom" }
designcraft-color = { path = "crates/color" } designcraft-color = { path = "crates/color" }
@@ -66,7 +66,6 @@ krilla-svg = "=0.8.1"
flo_curves = "0.8" flo_curves = "0.8"
# QR codes (Object › Generate QR Code); MIT. # QR codes (Object › Generate QR Code); MIT.
qrcodegen = "1.8" qrcodegen = "1.8"
hayro = "0.7"
quick-xml = "0.41" quick-xml = "0.41"
zip = { version = "3", default-features = false, features = ["deflate"] } zip = { version = "3", default-features = false, features = ["deflate"] }
# INDD/INDT → IDML conversion for File › Open: storytold/indd-utils, tracking its main branch # INDD/INDT → IDML conversion for File › Open: storytold/indd-utils, tracking its main branch
@@ -116,10 +115,9 @@ debug = 0
# Opt-in cancelable macOS termination on the pinned winit 0.30 line. # Opt-in cancelable macOS termination on the pinned winit 0.30 line.
[patch.crates-io] [patch.crates-io]
krilla = { git = "https://github.com/storytold/craft-libs", rev = "89dcbb3060377a26c99a6d80caa16084366704c4", version = "=0.8.2" }
winit = { git = "https://github.com/skylartaylor/winit.git", rev = "3d51077fd1a966f84df9ef287839dc84fd5b4ca7" } winit = { git = "https://github.com/skylartaylor/winit.git", rev = "3d51077fd1a966f84df9ef287839dc84fd5b4ca7" }
krilla = { git = "https://github.com/storytold/craft-libs", rev = "c3487320b2d1de657b860b3c8abb44e1f2b32bd8", version = "=0.8.2" } hayro = { git = "https://github.com/storytold/craft-libs", rev = "89dcbb3060377a26c99a6d80caa16084366704c4", version = "=0.7.1" }
# The hayro crates from pdfcraft's vendor/ directory, with the patches its README lists. hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "89dcbb3060377a26c99a6d80caa16084366704c4", version = "=0.7.0" }
hayro = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" } hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "89dcbb3060377a26c99a6d80caa16084366704c4", version = "=0.7.2" }
hayro-interpret = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" } hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "89dcbb3060377a26c99a6d80caa16084366704c4", version = "=0.3.0" }
hayro-syntax = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
hayro-jbig2 = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
+26 -3
View File
@@ -81,12 +81,35 @@ list (author, source, license) is in ASSETS.md.
packages as OFL-<family>.txt. Builds without CRAFT_FONTS_DIR do not include them. packages as OFL-<family>.txt. Builds without CRAFT_FONTS_DIR do not include them.
- All UI icons are drawn in code (crates/ui-egui/src/icons.rs) and are original work, MIT OR Apache-2.0. - All UI icons are drawn in code (crates/ui-egui/src/icons.rs) and are original work, MIT OR Apache-2.0.
- hayro PDF crates (placed PDFs): hayro, hayro-interpret, hayro-syntax and hayro-jbig2 - hayro PDF crates (placed PDFs): hayro, hayro-interpret, hayro-syntax and hayro-jbig2
(Apache-2.0 OR MIT) are pdfcraft's patched copies, fetched by git from its vendor/ directory at (Apache-2.0 OR MIT) are fetched from the pinned craft-libs source documented below.
the commit that Cargo.toml pins (https://github.com/storytold/pdfcraft, MIT OR Apache-2.0).
hayro-interpret embeds PDFium's Foxit standard-14 fonts under PDFium's BSD-style license hayro-interpret embeds PDFium's Foxit standard-14 fonts under PDFium's BSD-style license
(vendor/hayro-interpret/assets/LICENSE_FOXIT) and a CMYK profile under CC0-1.0. (vendor/hayro-interpret/assets/LICENSE_FOXIT) and a CMYK profile under CC0-1.0.
Rust dependencies (vello_cpu, kurbo, skrifa, harfrust, image, egui, eframe, …) are permissively Rust dependencies (vello_cpu, kurbo, skrifa, harfrust, image, egui, eframe, …) are permissively
licensed (MIT/Apache-2.0/BSD/ISC/Zlib) and fetched through crates.io. licensed (MIT/Apache-2.0/BSD/ISC/Zlib), fetched through crates.io or the pinned source below.
No Adobe icons, images or artwork are included. No Adobe icons, images or artwork are included.
PDF backend source
The patched hayro 0.7.1, hayro-interpret 0.7.0, hayro-syntax 0.7.2 and hayro-jbig2 0.3.0
are fetched from https://github.com/storytold/craft-libs at revision
89dcbb3060377a26c99a6d80caa16084366704c4. The four source snapshots were transferred
from https://github.com/storytold/pdfcraft at revision
7437bde378f43856eada40af25b983812e615e8a, then patched in craft-libs. Their upstream project is
https://github.com/LaurenzV/hayro, licensed MIT OR Apache-2.0. The snapshot retains each
crate's upstream licence files. The patch ledger is at
https://github.com/storytold/craft-libs/blob/89dcbb3060377a26c99a6d80caa16084366704c4/vendor/README.craft-reader.md.
Per-crate vendor/SOURCE.json files record transferred and local source hashes; asset
origins and unchanged per-file hashes are recorded separately in ASSETS.md.
Assets already bundled through Hayro's default features retain their licences:
- Foxit standard-14 substitutes: Copyright 2014 PDFium Authors. All rights reserved.
Original code copyright 2014 Foxit Software Inc. BSD-3-Clause; see
licenses/pdf-backend/hayro-interpret/assets/LICENSE_FOXIT.
- CGATS001Compat-v2-micro.icc: CC0-1.0, Clinton Ingram; see
licenses/pdf-backend/hayro-interpret/assets/CGATS_LICENSE.txt.
- Predefined CMaps: Copyright 1990-2023 Adobe. All rights reserved. BSD-3-Clause,
non-visual character-mapping data; see licenses/pdf-backend/hayro-cmap/LICENSE.txt.
Adobe and contributor names do not endorse or promote this product.
Per-file hashes and sources are recorded in ASSETS.md.
+2
View File
@@ -638,3 +638,5 @@ Newest first.
The shared craft-installer UI preserves this revision's install options and provides persistent installation, repair and Windows Settings removal completion screens. Silent modes stay unattended. Earlier Windows acceptance is recorded in packaging/windows/README.md; normal CI and production-profile upgrade qualification against pinned current releases are in progress. No release or PR has been published. The shared craft-installer UI preserves this revision's install options and provides persistent installation, repair and Windows Settings removal completion screens. Silent modes stay unattended. Earlier Windows acceptance is recorded in packaging/windows/README.md; normal CI and production-profile upgrade qualification against pinned current releases are in progress. No release or PR has been published.
- **2026-10-15** · Windows recovery folders validate the identity of the live locked file without reading it through a second handle. Missing or replaced locks disable writes and cleanup; regression tests cover retained exclusive locking and replacement-file protection. No readiness percentage change. - **2026-10-15** · Windows recovery folders validate the identity of the live locked file without reading it through a second handle. Missing or replaced locks disable writes and cleanup; regression tests cover retained exclusive locking and replacement-file protection. No readiness percentage change.
- PDF reader infrastructure: pinned shared parsing/rasterization, transformed page-box geometry and explicit failures for known incomplete layered-PDF exports. Application ownership and parity targets are unchanged.
+6 -5
View File
@@ -659,11 +659,11 @@ fn wrap_stand_in(it: &designcraft_doc::Item, depth: usize) -> designcraft_doc::I
} }
/// A copy of `d` where placed PDFs with Object Layer Options hidden layers are 300 ppi images. /// A copy of `d` where placed PDFs with Object Layer Options hidden layers are 300 ppi images.
fn rasterize_layered(d: &designcraft_doc::Document) -> (Option<designcraft_doc::Document>, usize) { fn rasterize_layered(d: &designcraft_doc::Document) -> Result<(Option<designcraft_doc::Document>, usize)> {
let ids: Vec<designcraft_doc::ItemId> = let ids: Vec<designcraft_doc::ItemId> =
d.all_items().into_iter().filter(|id| d.item(*id).is_some_and(|it| !it.pdf_hidden_layers.is_empty() && it.graphic().is_some())).collect(); d.all_items().into_iter().filter(|id| d.item(*id).is_some_and(|it| !it.pdf_hidden_layers.is_empty() && it.graphic().is_some())).collect();
if ids.is_empty() { if ids.is_empty() {
return (None, 0); return Ok((None, 0));
} }
let mut out = d.clone(); let mut out = d.clone();
let mut n = 0; let mut n = 0;
@@ -678,7 +678,8 @@ fn rasterize_layered(d: &designcraft_doc::Document) -> (Option<designcraft_doc::
let [l, t, r, b] = shown.unwrap_or([0.0, 0.0, 1.0, 1.0]); let [l, t, r, b] = shown.unwrap_or([0.0, 0.0, 1.0, 1.0]);
let page_side = (g.size.0 / (r - l)).max(g.size.1 / (b - t)); let page_side = (g.size.0 / (r - l)).max(g.size.1 / (b - t));
let side = (page_side * 300.0 / 72.0).clamp(64.0, 8000.0) as u32; let side = (page_side * 300.0 / 72.0).clamp(64.0, 8000.0) as u32;
let Some(png) = designcraft_render::pdf_page_png(&a.data, a.page as usize, side, &hidden, shown) else { continue }; let png = designcraft_render::pdf_page_png_checked(&a.data, a.page as usize, side, &hidden, shown)
.map_err(|e| EngineError::Other(format!("{}: {e}", a.name)))?;
let px = designcraft_render::image_size(&png); let px = designcraft_render::image_size(&png);
let aid = designcraft_doc::AssetId(out.alloc()); let aid = designcraft_doc::AssetId(out.alloc());
out.assets.insert( out.assets.insert(
@@ -697,7 +698,7 @@ fn rasterize_layered(d: &designcraft_doc::Document) -> (Option<designcraft_doc::
} }
n += 1; n += 1;
} }
(Some(out), n) Ok((Some(out), n))
} }
/// Object number of the first page of a PDF (its page tree's first kid). /// Object number of the first page of a PDF (its page tree's first kid).
@@ -724,7 +725,7 @@ fn export_pdf(s: &mut Session, p: &Value) -> Result<Value> {
let flattened = ppi.map(|ppi| flatten(&st.doc, &s.cache, ppi)); let flattened = ppi.map(|ppi| flatten(&st.doc, &s.cache, ppi));
// Placed PDFs with hidden layers go out as images showing just their visible layers. // Placed PDFs with hidden layers go out as images showing just their visible layers.
let base: &designcraft_doc::Document = flattened.as_ref().map_or(&st.doc, |f| &f.0); let base: &designcraft_doc::Document = flattened.as_ref().map_or(&st.doc, |f| &f.0);
let (layered, layer_count) = rasterize_layered(base); let (layered, layer_count) = rasterize_layered(base)?;
let base2: &designcraft_doc::Document = layered.as_ref().unwrap_or(base); let base2: &designcraft_doc::Document = layered.as_ref().unwrap_or(base);
let (fx_doc, fx_count) = rasterize_effects(base2, &s.cache); let (fx_doc, fx_count) = rasterize_effects(base2, &s.cache);
let doc: &designcraft_doc::Document = fx_doc.as_ref().unwrap_or(base2); let doc: &designcraft_doc::Document = fx_doc.as_ref().unwrap_or(base2);
+3
View File
@@ -629,6 +629,9 @@ fn file_place(s: &mut Session, p: &Value) -> Result<Value> {
}) })
.transpose()?; .transpose()?;
// Image Import Options: which page of a PDF (1-based). // Image Import Options: which page of a PDF (1-based).
if designcraft_render::is_pdf(&bytes) {
designcraft_render::check_pdf_input_size(bytes.len()).map_err(|e| bad("file.place", e.to_string()))?;
}
let pdf_page = match p.get("pdfPage").and_then(Value::as_u64) { let pdf_page = match p.get("pdfPage").and_then(Value::as_u64) {
Some(n) if designcraft_render::is_pdf(&bytes) => { Some(n) if designcraft_render::is_pdf(&bytes) => {
let count = designcraft_render::pdf_page_count(&bytes).unwrap_or(1); let count = designcraft_render::pdf_page_count(&bytes).unwrap_or(1);
+3
View File
@@ -398,6 +398,9 @@ fn reimport(d: &mut Document, sid: StoryId, path: &str, bytes: &[u8], before: Fi
fn reload(path: impl AsRef<Path>) -> Result<(Vec<u8>, (u32, u32))> { fn reload(path: impl AsRef<Path>) -> Result<(Vec<u8>, (u32, u32))> {
let path = path.as_ref(); let path = path.as_ref();
let b = crate::io::read_file(path).map_err(EngineError::Other)?; let b = crate::io::read_file(path).map_err(EngineError::Other)?;
if designcraft_render::is_pdf(&b) {
designcraft_render::check_pdf_input_size(b.len()).map_err(|e| bad("links", format!("{}: {e}", path.display())))?;
}
let px = designcraft_render::image_size(&b).ok_or_else(|| bad("links", format!("{}: unsupported or corrupt image", path.display())))?; let px = designcraft_render::image_size(&b).ok_or_else(|| bad("links", format!("{}: unsupported or corrupt image", path.display())))?;
Ok((b, px)) Ok((b, px))
} }
+349
View File
@@ -0,0 +1,349 @@
//! Synthetic placement/export regressions for PDF backend updates.
use serde_json::json;
use designcraft_engine::Session;
use designcraft_engine::cmd::{base64_decode, base64_encode};
fn pdf(objects: &[String]) -> Vec<u8> {
let mut bytes = b"%PDF-1.7\n".to_vec();
let mut offsets = vec![0];
for (i, object) in objects.iter().enumerate() {
offsets.push(bytes.len());
bytes.extend_from_slice(format!("{} 0 obj\n{object}\nendobj\n", i + 1).as_bytes());
}
let xref = bytes.len();
bytes.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f\r\n", offsets.len()).as_bytes());
for offset in offsets.iter().skip(1) {
bytes.extend_from_slice(format!("{offset:010} 00000 n\r\n").as_bytes());
}
bytes.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", offsets.len()).as_bytes());
bytes
}
fn layered_pdf(second_name: &str) -> Vec<u8> {
layered_pdf_with_box(second_name, "")
}
fn layered_pdf_with_box(second_name: &str, page_box: &str) -> Vec<u8> {
let content = "/OC /a BDC 0 0 1 rg 10 10 35 80 re f EMC /OC /b BDC 1 0 0 rg 55 10 35 80 re f EMC";
pdf(&[
"<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [5 0 R 6 0 R] /D << /Order [5 0 R 6 0 R] >> >> >>".into(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
format!(
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] {page_box} /Resources << /Properties << /a 5 0 R /b 6 0 R >> >> /Contents 4 0 R >>"
),
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
"<< /Type /OCG /Name (Blue) >>".into(),
format!("<< /Type /OCG /Name ({second_name}) >>"),
])
}
fn rendered(bytes: &[u8]) -> designcraft_render::Rendered {
let pm = designcraft_render::render_pdf_page(bytes, 0, 100).expect("render synthetic PDF");
designcraft_render::Rendered {
width: pm.width().into(),
height: pm.height().into(),
pixels: pm.data().iter().flat_map(|p| [p.r, p.g, p.b, p.a]).collect(),
}
}
// Optional local evidence output; no binary fixtures are checked in.
fn evidence(name: &str, bytes: &[u8]) {
if let Some(dir) = std::env::var_os("CRAFT_PDF_EVIDENCE_DIR") {
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(std::path::Path::new(&dir).join(name), bytes).unwrap();
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn placed_pdf_preserves_transparent_background_and_layer_choices() {
let bytes = layered_pdf("Red");
let img = rendered(&bytes);
assert_eq!(img.pixel(2, 2), [0, 0, 0, 0]);
assert_eq!(img.pixel(25, 50), [0, 0, 255, 255]);
assert_eq!(img.pixel(75, 50), [255, 0, 0, 255]);
let hidden = designcraft_render::pdf_hide_layers(&bytes, &["Red".into()]).unwrap();
let img = rendered(&hidden);
assert_eq!(img.pixel(75, 50), [0, 0, 0, 0]);
assert_eq!(img.pixel(25, 50), [0, 0, 255, 255]);
evidence("layers-hidden.png", &img.to_png());
evidence("layers-source.pdf", &bytes);
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn duplicate_layer_names_keep_name_based_hiding() {
let bytes = layered_pdf("Blue");
assert_eq!(designcraft_render::pdf_layers(&bytes).len(), 2);
let hidden = designcraft_render::pdf_hide_layers(&bytes, &["Blue".into()]).unwrap();
let img = rendered(&hidden);
assert_eq!(img.pixel(25, 50), [0, 0, 0, 0]);
assert_eq!(img.pixel(75, 50), [0, 0, 0, 0]);
// A second render of the original must not inherit the hidden configuration.
assert_eq!(rendered(&bytes).pixel(25, 50), [0, 0, 255, 255]);
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn crop_box_and_rotation_keep_placed_dimensions() {
let bytes = pdf(&[
"<< /Type /Catalog /Pages 2 0 R >>".into(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 120 100] /CropBox [10 20 110 80] /Rotate 90 >>".into(),
]);
assert_eq!(designcraft_render::pdf_page_count(&bytes), Some(1));
assert_eq!(designcraft_render::pdf_page_size(&bytes, 0), Some((60.0, 100.0)));
let img = rendered(&bytes);
assert_eq!((img.width, img.height), (60, 100));
assert_eq!(img.pixel(20, 20), [0, 0, 0, 0]);
}
fn exported(hidden: bool) -> Vec<u8> {
let mut session = Session::new();
session.execute("file.new", &json!({"width": 100, "height": 100, "pages": 1})).unwrap();
session.execute("edit.transparencyBlendSpace", &json!({"space": "rgb"})).unwrap();
session.execute("frame.create", &json!({"rect": [0, 0, 100, 100]})).unwrap();
session.execute("object.fill", &json!({"swatch": "[Black]"})).unwrap();
let placed = session
.execute(
"file.place",
&json!({
"base64": base64_encode(&layered_pdf("Red")), "name": "layers.pdf", "x": 0, "y": 0, "width": 100
}),
)
.unwrap();
if hidden {
session.execute("object.layerOptions", &json!({"ids": [placed["id"]], "hidden": ["Red"]})).unwrap();
}
let report = session.execute("file.exportPdf", &json!({"compress": false})).unwrap();
let bytes = base64_decode(report["base64"].as_str().unwrap());
if hidden {
assert!(report["warnings"].to_string().contains("hidden layers"));
}
bytes
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn hidden_layer_export_composites_over_underlying_artwork() {
let bytes = exported(true);
let img = rendered(&bytes);
let right = img.pixel(75, 50);
assert!(right[..3].iter().all(|c| *c < 45), "hidden area must show the black backdrop, not opaque white: {right:?}");
let left = img.pixel(25, 50);
assert!(left[2] > 180 && left[0] < 80, "visible blue layer: {left:?}");
evidence("hidden-export.pdf", &bytes);
evidence("hidden-export.png", &img.to_png());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn ordinary_placed_pages_still_embed_as_vectors() {
use hayro_syntax::object::Name;
let bytes = exported(false);
let parsed = hayro_syntax::Pdf::new(bytes.clone()).unwrap();
let subtypes: Vec<_> = parsed
.objects()
.into_iter()
.filter_map(|o| o.into_stream())
.filter_map(|s| s.dict().get::<Name>("Subtype").map(|n| n.as_str().to_owned()))
.collect();
assert!(subtypes.iter().any(|s| s == "Form"), "placed PDF form: {subtypes:?}");
assert!(!subtypes.iter().any(|s| s == "Image"), "no rasterization: {subtypes:?}");
let img = rendered(&bytes);
let right = img.pixel(75, 50);
assert!(right[0] > 180 && right[1] < 80, "visible red vector layer: {right:?}");
evidence("vector-export.pdf", &bytes);
evidence("vector-export.png", &img.to_png());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn oversized_image_does_not_silently_disappear_from_layered_export() {
// Just above the backend's 2^28-pixel cap. The stream is one byte; no large
// fixture or large image allocation is needed to exercise the refusal.
let content = "/OC /a BDC 0 0 1 rg 10 10 35 80 re f EMC q 100 0 0 100 0 0 cm /Im Do Q";
let bytes = pdf(&[
"<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [6 0 R] /D << /Order [6 0 R] >> >> >>".into(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Resources << /Properties << /a 6 0 R >> /XObject << /Im 5 0 R >> >> /Contents 4 0 R >>".into(),
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
"<< /Type /XObject /Subtype /Image /Width 32769 /Height 8192 /ColorSpace /DeviceGray /BitsPerComponent 8 /Length 1 >>\nstream\nX\nendstream".into(),
"<< /Type /OCG /Name (Blue) >>".into(),
]);
let mut session = Session::new();
session.execute("file.new", &json!({"width": 100, "height": 100, "pages": 1})).unwrap();
let placed = session
.execute(
"file.place",
&json!({
"base64": base64_encode(&bytes), "name": "oversized-scan.pdf", "x": 0, "y": 0, "width": 100
}),
)
.unwrap();
session.execute("object.layerOptions", &json!({"ids": [placed["id"]], "hidden": ["Blue"]})).unwrap();
let before = session.execute("file.serialize", &json!({})).unwrap();
let result = session.execute("file.exportPdf", &json!({}));
assert!(result.is_err(), "export must report the image omitted by the backend's resource limit");
assert_eq!(session.execute("file.serialize", &json!({})).unwrap(), before, "a failed export must leave the document unchanged");
let error = result.unwrap_err().to_string();
assert!(error.contains("oversized-scan.pdf") && error.contains("image"), "{error}");
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn failed_layer_rewrite_aborts_export_instead_of_revealing_hidden_content() {
// A stored layer choice can outlive relinking to a PDF with no layers.
let bytes = pdf(&[
"<< /Type /Catalog /Pages 2 0 R >>".into(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>".into(),
]);
assert!(designcraft_render::render_pdf_page(&bytes, 0, 100).is_some());
assert!(designcraft_render::pdf_hide_layers(&bytes, &["Red".into()]).is_none());
let mut session = Session::new();
session.execute("file.new", &json!({"width": 100, "height": 100, "pages": 1})).unwrap();
let placed =
session.execute("file.place", &json!({"base64": base64_encode(&bytes), "name": "missing-layers.pdf", "x": 0, "y": 0, "width": 100})).unwrap();
session.execute("object.layerOptions", &json!({"ids": [placed["id"]], "hidden": ["Red"]})).unwrap();
let before = session.execute("file.serialize", &json!({})).unwrap();
let result = session.execute("file.exportPdf", &json!({}));
assert!(result.is_err(), "failed layer hiding must not export the original visible layers");
let error = result.unwrap_err().to_string();
assert!(error.contains("missing-layers.pdf") && error.contains("layer"), "{error}");
assert_eq!(session.execute("file.serialize", &json!({})).unwrap(), before);
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn placed_standard_font_text_renders_ink_on_a_transparent_background() {
let content = "BT /F1 18 Tf 10 40 Td (PDF text) Tj ET";
let bytes = pdf(&[
"<< /Type /Catalog /Pages 2 0 R >>".into(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>".into(),
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".into(),
]);
let img = rendered(&bytes);
let ink = (40..65).flat_map(|y| (5..95).map(move |x| (x, y))).filter(|(x, y)| img.pixel(*x, *y)[3] > 100).count();
assert!(ink > 100, "standard-font glyphs must paint visible ink: {ink}");
assert_eq!(img.pixel(2, 2), [0, 0, 0, 0]);
assert_eq!(img.pixel(50, 80), [0, 0, 0, 0]);
assert!(designcraft_render::pdf_page_png_checked(&bytes, 0, 100, &[], None).is_ok());
evidence("standard-font-text.png", &img.to_png());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn unavailable_placed_page_reports_the_page_and_recovery_action() {
let error = designcraft_render::pdf_page_png_checked(&layered_pdf("Red"), 2, 100, &["Red".into()], None).unwrap_err();
assert!(matches!(error, designcraft_render::PdfRasterError::MissingPage { page: 3 }));
assert!(error.to_string().contains("place the replacement PDF again"));
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn hidden_layers_work_with_an_xref_stream_and_compressed_catalog() {
// Reuse the tiny synthetic objects, but put the catalog in an object stream
// and index everything with a binary cross-reference stream.
let classic = String::from_utf8(layered_pdf("Red")).unwrap();
let objects: Vec<&str> = (1..=6).map(|i| classic.split_once(&format!("{i} 0 obj\n")).unwrap().1.split_once("\nendobj").unwrap().0).collect();
let mut bytes = b"%PDF-1.7\n".to_vec();
let mut offsets = [0u32; 9];
for (i, object) in objects.iter().enumerate().skip(1) {
offsets[i + 1] = u32::try_from(bytes.len()).unwrap();
bytes.extend_from_slice(format!("{} 0 obj\n{object}\nendobj\n", i + 1).as_bytes());
}
offsets[7] = u32::try_from(bytes.len()).unwrap();
let catalog = format!("1 0 {}", objects[0]);
bytes.extend_from_slice(
format!("7 0 obj\n<< /Type /ObjStm /N 1 /First 4 /Length {} >>\nstream\n{catalog}\nendstream\nendobj\n", catalog.len()).as_bytes(),
);
offsets[8] = u32::try_from(bytes.len()).unwrap();
let mut entries = Vec::new();
for (i, offset) in offsets.iter().enumerate() {
let (kind, field, generation): (u8, u32, u16) = match i {
0 => (0, 0, 65535),
1 => (2, 7, 0),
_ => (1, *offset, 0),
};
entries.push(kind);
entries.extend_from_slice(&field.to_be_bytes());
entries.extend_from_slice(&generation.to_be_bytes());
}
bytes.extend_from_slice(format!("8 0 obj\n<< /Type /XRef /Size 9 /Root 1 0 R /W [1 4 2] /Length {} >>\nstream\n", entries.len()).as_bytes());
bytes.extend_from_slice(&entries);
bytes.extend_from_slice(format!("\nendstream\nendobj\nstartxref\n{}\n%%EOF\n", offsets[8]).as_bytes());
assert_eq!(rendered(&bytes).pixel(75, 50), [255, 0, 0, 255]);
let hidden = designcraft_render::pdf_hide_layers(&bytes, &["Red".into()]).unwrap();
let img = rendered(&hidden);
assert_eq!(img.pixel(75, 50), [0, 0, 0, 0]);
assert_eq!(img.pixel(25, 50), [0, 0, 255, 255]);
assert!(designcraft_render::pdf_page_png_checked(&bytes, 0, 100, &["Red".into()], None).is_ok());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn trailing_whitespace_does_not_prevent_layer_hiding() {
let mut bytes = layered_pdf("Red");
bytes.extend_from_slice(&[b' '; 96]);
let hidden = designcraft_render::pdf_hide_layers(&bytes, &["Red".into()]).expect("trailing whitespace is accepted by the layer rewriter");
let img = rendered(&hidden);
assert_eq!(img.pixel(75, 50), [0, 0, 0, 0]);
assert_eq!(img.pixel(25, 50), [0, 0, 255, 255]);
assert!(designcraft_render::pdf_page_png_checked(&bytes, 0, 100, &["Red".into()], None).is_ok());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn failed_content_decode_aborts_layered_export_without_changing_the_document() {
for filter in ["FlateDecode", "DCTDecode"] {
let bytes = pdf(&[
"<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [5 0 R] /D << /Order [5 0 R] >> >> >>".into(),
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Resources << /Properties << /a 5 0 R >> >> /Contents 4 0 R >>".into(),
format!("<< /Filter /{filter} /Length 1 >>\nstream\nx\nendstream"),
"<< /Type /OCG /Name (Blue) >>".into(),
]);
let mut session = Session::new();
session.execute("file.new", &json!({"width": 100, "height": 100, "pages": 1})).unwrap();
let placed = session
.execute("file.place", &json!({"base64": base64_encode(&bytes), "name": "broken-content.pdf", "x": 0, "y": 0, "width": 100}))
.unwrap();
session.execute("object.layerOptions", &json!({"ids": [placed["id"]], "hidden": ["Blue"]})).unwrap();
let before = session.execute("file.serialize", &json!({})).unwrap();
let error = session.execute("file.exportPdf", &json!({})).unwrap_err().to_string();
assert!(error.contains("broken-content.pdf") && error.contains("content"), "{filter}: {error}");
assert_eq!(session.execute("file.serialize", &json!({})).unwrap(), before, "{filter}");
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn hidden_layer_export_keeps_the_placed_trim_box() {
let bytes = layered_pdf_with_box("Red", "/TrimBox [0 0 50 100]");
let mut session = Session::new();
session.execute("file.new", &json!({"width": 50, "height": 100, "pages": 1})).unwrap();
session.execute("edit.transparencyBlendSpace", &json!({"space": "rgb"})).unwrap();
let placed = session
.execute(
"file.place",
&json!({"base64": base64_encode(&bytes), "name": "trimmed-layers.pdf", "pdfCrop": "trim", "x": 0, "y": 0, "width": 100}),
)
.unwrap();
session.execute("object.layerOptions", &json!({"ids": [placed["id"]], "hidden": ["Red"]})).unwrap();
let before = session.execute("file.serialize", &json!({})).unwrap();
let report = session.execute("file.exportPdf", &json!({"compress": false})).unwrap();
let exported = base64_decode(report["base64"].as_str().unwrap());
let img = rendered(&exported);
assert_eq!((img.width, img.height), (50, 100));
// Omitting the shown box squeezes the whole page into the trimmed graphic,
// moving the blue rectangle's right edge from x=45 to x=22.5.
let blue = img.pixel(35, 50);
assert!(blue[2] > 180 && blue[0] < 80, "the trim box keeps the visible layer at its placed coordinates: {blue:?}");
assert_eq!(session.execute("file.serialize", &json!({})).unwrap(), before);
evidence("trimmed-layer-export.pdf", &exported);
}
+2 -1
View File
@@ -17,9 +17,10 @@ designcraft-compose = { workspace = true }
vello_cpu = { workspace = true } vello_cpu = { workspace = true }
vello_common = { workspace = true } vello_common = { workspace = true }
image = { workspace = true } image = { workspace = true }
hayro = { workspace = true } craft-pdf = { workspace = true, features = ["raster"] }
designcraft-images = { workspace = true } designcraft-images = { workspace = true }
log = { workspace = true } log = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+113 -24
View File
@@ -1235,29 +1235,93 @@ pub fn is_pdf(bytes: &[u8]) -> bool {
/// Page count of a PDF. /// Page count of a PDF.
pub fn pdf_page_count(bytes: &[u8]) -> Option<usize> { pub fn pdf_page_count(bytes: &[u8]) -> Option<usize> {
let pdf = hayro::hayro_syntax::Pdf::new(std::sync::Arc::new(bytes.to_vec())).ok()?; let pdf = craft_pdf::open_slice(bytes, None).ok()?;
Some(pdf.pages().len()) Some(pdf.pages().len())
} }
/// Size of PDF page `page` (0-based), in points, as it shows (crop box, rotation applied). /// Size of PDF page `page` (0-based), in points, as it shows (crop box, rotation applied).
pub fn pdf_page_size(bytes: &[u8], page: usize) -> Option<(f64, f64)> { pub fn pdf_page_size(bytes: &[u8], page: usize) -> Option<(f64, f64)> {
let pdf = hayro::hayro_syntax::Pdf::new(std::sync::Arc::new(bytes.to_vec())).ok()?; let pdf = craft_pdf::open_slice(bytes, None).ok()?;
let p = pdf.pages().get(page)?; let p = pdf.pages().get(page)?;
let (w, h) = p.render_dimensions(); let (w, h) = p.render_dimensions();
Some((w as f64, h as f64)) Some((w as f64, h as f64))
} }
/// PDF page `page` as PNG, its longer side about `max_side` pixels, with the `hidden` layers off; /// A placed page could not be rasterized completely for export.
/// only the `shown` part of it ([`designcraft_doc::Asset::shown_box`]) when given. #[derive(Debug, thiserror::Error)]
pub enum PdfRasterError {
#[error("{0}")]
Input(#[source] craft_pdf::OpenError),
#[error("{0}")]
Render(#[from] craft_pdf::raster::Error),
#[error("the PDF page could not be read or rendered")]
InvalidPage,
#[error("PDF page {page} is unavailable; choose an existing page in Image Import Options or place the replacement PDF again")]
MissingPage { page: usize },
#[error("the selected PDF layers could not be hidden")]
LayerRewrite,
#[error("an image could not be decoded or exceeded the PDF renderer's resource limits; repair or replace this placed PDF before exporting again")]
ImageDecode,
#[error("the PDF renderer encountered an unsupported font")]
UnsupportedFont,
#[error(
"the PDF renderer could not read all page content or reached its resource limits; repair or replace this placed PDF before exporting again"
)]
ContentTruncated,
}
/// Reject oversized PDFs before placement, layer rewriting or output allocation.
pub fn check_pdf_input_size(size: usize) -> Result<(), PdfRasterError> {
craft_pdf::check_input_size(size).map_err(PdfRasterError::Input)
}
/// PDF page `page` as PNG, its longer side about `max_side` pixels, with the `hidden` layers off.
/// Only the `shown` part ([`designcraft_doc::Asset::shown_box`]) is kept when given.
/// Returns `None` on the same failures as [`pdf_page_png_checked`].
pub fn pdf_page_png(bytes: &[u8], page: usize, max_side: u32, hidden: &[String], shown: Option<[f64; 4]>) -> Option<Vec<u8>> { pub fn pdf_page_png(bytes: &[u8], page: usize, max_side: u32, hidden: &[String], shown: Option<[f64; 4]>) -> Option<Vec<u8>> {
let data = if hidden.is_empty() { bytes.to_vec() } else { pdf_hide_layers(bytes, hidden).unwrap_or_else(|| bytes.to_vec()) }; pdf_page_png_checked(bytes, page, max_side, hidden, shown).ok()
let pm = render_pdf_page(&data, page, max_side)?; }
/// Rasterize a placed page for export, preserving available interpreter diagnostics.
/// This cannot detect backend approximations that do not emit an interpreter warning.
pub fn pdf_page_png_checked(bytes: &[u8], page: usize, max_side: u32, hidden: &[String], shown: Option<[f64; 4]>) -> Result<Vec<u8>, PdfRasterError> {
check_pdf_input_size(bytes.len())?;
let data = if hidden.is_empty() {
std::borrow::Cow::Borrowed(bytes)
} else {
std::borrow::Cow::Owned(pdf_hide_layers(bytes, hidden).ok_or(PdfRasterError::LayerRewrite)?)
};
let raster = render_pdf_raster(&data, page, max_side)?;
if raster.diagnostics.image_decode_failed {
return Err(PdfRasterError::ImageDecode);
}
if raster.diagnostics.unsupported_font {
return Err(PdfRasterError::UnsupportedFont);
}
if raster.diagnostics.content_truncated {
return Err(PdfRasterError::ContentTruncated);
}
let pm = raster_pixmap(&raster);
let pm = match shown { let pm = match shown {
Some(frac) => crop_pixmap(&pm, frac)?, Some(frac) => crop_pixmap(&pm, frac).ok_or(PdfRasterError::InvalidPage)?,
None => pm, None => pm,
}; };
let pixels: Vec<u8> = pm.data().iter().flat_map(|p| [p.r, p.g, p.b, p.a]).collect(); let pixels = pm.data().iter().flat_map(|p| [p.r, p.g, p.b, p.a]).collect();
Some(Rendered { width: pm.width() as u32, height: pm.height() as u32, pixels }.to_png()) Ok(Rendered { width: u32::from(pm.width()), height: u32::from(pm.height()), pixels }.to_png())
}
/// Rasterize PDF page `page` so its longer side is about `max_side` pixels (screen display).
/// Export callers that need failure diagnostics use [`pdf_page_png_checked`].
pub fn render_pdf_page(bytes: &[u8], page: usize, max_side: u32) -> Option<Pixmap> {
#[cfg(test)]
PDF_RENDERS.with(|n| n.set(n.get() + 1));
let raster = render_pdf_raster(bytes, page, max_side).ok()?;
Some(raster_pixmap(&raster))
}
fn raster_pixmap(raster: &craft_pdf::raster::Raster) -> Pixmap {
let data = raster.bytes().as_chunks::<4>().0.iter().map(|c| vello_cpu::color::PremulRgba8 { r: c[0], g: c[1], b: c[2], a: c[3] }).collect();
Pixmap::from_parts(data, raster.width as u16, raster.height as u16)
} }
#[cfg(test)] #[cfg(test)]
@@ -1266,21 +1330,46 @@ thread_local! {
static PDF_RENDERS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) }; static PDF_RENDERS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
} }
/// Rasterize PDF page `page` so its longer side is about `max_side` pixels (screen display; PDF fn render_pdf_raster(bytes: &[u8], page: usize, max_side: u32) -> Result<craft_pdf::raster::Raster, PdfRasterError> {
/// export embeds the page as vectors). let pdf = craft_pdf::open_slice(bytes, None).map_err(|e| match e {
pub fn render_pdf_page(bytes: &[u8], page: usize, max_side: u32) -> Option<Pixmap> { craft_pdf::OpenError::Load(_) => PdfRasterError::InvalidPage,
#[cfg(test)] other => PdfRasterError::Input(other),
PDF_RENDERS.with(|n| n.set(n.get() + 1)); })?;
let pdf = hayro::hayro_syntax::Pdf::new(std::sync::Arc::new(bytes.to_vec())).ok()?; let p = pdf.pages().get(page).ok_or(PdfRasterError::MissingPage { page: page.saturating_add(1) })?;
let p = pdf.pages().get(page)?;
let (w, h) = p.render_dimensions(); let (w, h) = p.render_dimensions();
let scale = (max_side as f32 / w.max(h).max(1.0)).min(8.0); let scale = (max_side as f32 / w.max(h).max(1.0)).min(8.0);
let rs = hayro::RenderSettings { x_scale: scale, y_scale: scale, ..Default::default() }; craft_pdf::raster::render(
let pm = hayro::render(p, &hayro::RenderCache::new(), &hayro::hayro_interpret::InterpreterSettings::default(), &rs); p,
let (pw, ph) = (pm.width(), pm.height()); &craft_pdf::raster::RenderCache::new(),
let data: Vec<vello_cpu::color::PremulRgba8> = &craft_pdf::interpret::InterpreterSettings::default(),
pm.data_as_u8_slice().as_chunks::<4>().0.iter().map(|c| vello_cpu::color::PremulRgba8 { r: c[0], g: c[1], b: c[2], a: c[3] }).collect(); &craft_pdf::raster::Options { scale: [scale; 2], ..Default::default() },
Some(Pixmap::from_parts(data, pw, ph)) )
.map_err(PdfRasterError::Render)
}
/// A page box of a PDF page (`crop`, `trim`, `bleed`, `art`, `media`) as (x, y, w, h) in points
/// from the top-left of the page as rendered (its visible crop box); missing boxes fall back to the
/// crop box, as PDF readers do.
pub fn pdf_page_box(bytes: &[u8], page: usize, kind: &str) -> Option<(f64, f64, f64, f64)> {
use craft_pdf::syntax::object::{Rect as PRect, dict::keys};
let pdf = craft_pdf::open_slice(bytes, None).ok()?;
let p = pdf.pages().get(page)?;
let visible = p.intersected_crop_box();
let page_box = match kind {
"media" => p.media_box(),
"trim" | "bleed" | "art" => {
let key: &[u8] = match kind {
"trim" => keys::TRIM_BOX,
"bleed" => keys::BLEED_BOX,
_ => keys::ART_BOX,
};
p.raw().get::<PRect>(key).map(|b| b.intersect(p.media_box())).unwrap_or(visible)
}
_ => visible,
};
let transform = Affine::new(p.initial_transform(true).as_coeffs());
let b = transform.transform_rect_bbox(Rect::new(page_box.x0, page_box.y0, page_box.x1, page_box.y1));
b.is_finite().then_some((b.x0, b.y0, b.width(), b.height()))
} }
/// The part of PDF page `page` that `crop` shows: its left, top, right and bottom edges as /// The part of PDF page `page` that `crop` shows: its left, top, right and bottom edges as
@@ -1289,10 +1378,10 @@ pub fn render_pdf_page(bytes: &[u8], page: usize, max_side: u32) -> Option<Pixma
/// are the bounds of what the page draws, found from its drawing operations without rendering it /// are the bounds of what the page draws, found from its drawing operations without rendering it
/// (the whole page when it draws nothing). /// (the whole page when it draws nothing).
pub fn pdf_crop_box(bytes: &[u8], page: usize, crop: designcraft_doc::PdfCrop) -> Option<([f64; 4], (f64, f64))> { pub fn pdf_crop_box(bytes: &[u8], page: usize, crop: designcraft_doc::PdfCrop) -> Option<([f64; 4], (f64, f64))> {
use craft_pdf::syntax::object::{Rect as PRect, dict::keys};
use designcraft_doc::PdfCrop; use designcraft_doc::PdfCrop;
use hayro::hayro_syntax::object::{Rect as PRect, dict::keys};
const WHOLE: [f64; 4] = [0.0, 0.0, 1.0, 1.0]; const WHOLE: [f64; 4] = [0.0, 0.0, 1.0, 1.0];
let pdf = hayro::hayro_syntax::Pdf::new(std::sync::Arc::new(bytes.to_vec())).ok()?; let pdf = craft_pdf::open_slice(bytes, None).ok()?;
let p = pdf.pages().get(page)?; let p = pdf.pages().get(page)?;
let (w, h) = p.render_dimensions(); let (w, h) = p.render_dimensions();
let (w, h) = (f64::from(w), f64::from(h)); let (w, h) = (f64::from(w), f64::from(h));
+3 -3
View File
@@ -2,8 +2,8 @@
//! of every painted path, glyph and image, each cut to the clip in force when it's painted. //! of every painted path, glyph and image, each cut to the clip in force when it's painted.
//! Nothing is rasterized, so measuring a page costs a fraction of drawing it. //! Nothing is rasterized, so measuring a page costs a fraction of drawing it.
use hayro::hayro_interpret::font::Glyph; use craft_pdf::interpret::font::Glyph;
use hayro::hayro_interpret::{ use craft_pdf::interpret::{
BlendMode, ClipPath, Context, Device, FillRule, GlyphDrawMode, Image, InterpreterCache, InterpreterSettings, Paint, PathDrawMode, RectExt, BlendMode, ClipPath, Context, Device, FillRule, GlyphDrawMode, Image, InterpreterCache, InterpreterSettings, Paint, PathDrawMode, RectExt,
SoftMask, TransformExt, interpret_page, SoftMask, TransformExt, interpret_page,
}; };
@@ -12,7 +12,7 @@ use vello_cpu::kurbo::{self, Affine, BezPath, Rect, Shape};
/// The bounds of what PDF page `page` draws, as fractions of the page as rendered (its visible /// The bounds of what PDF page `page` draws, as fractions of the page as rendered (its visible
/// crop box, rotated as it displays): left, top, right, bottom. `None` when it draws nothing. /// crop box, rotated as it displays): left, top, right, bottom. `None` when it draws nothing.
pub(crate) fn page_content_bounds(bytes: &[u8], page: usize) -> Option<[f64; 4]> { pub(crate) fn page_content_bounds(bytes: &[u8], page: usize) -> Option<[f64; 4]> {
let pdf = hayro::hayro_syntax::Pdf::new(std::sync::Arc::new(bytes.to_vec())).ok()?; let pdf = craft_pdf::open_slice(bytes, None).ok()?;
let p = pdf.pages().get(page)?; let p = pdf.pages().get(page)?;
let (w, h) = p.render_dimensions(); let (w, h) = p.render_dimensions();
let (w, h) = (f64::from(w), f64::from(h)); let (w, h) = (f64::from(w), f64::from(h));
+7 -7
View File
@@ -5,8 +5,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use hayro::hayro_syntax::Pdf; use craft_pdf::syntax::Pdf;
use hayro::hayro_syntax::object::{Array, Dict, Name, ObjectIdentifier}; use craft_pdf::syntax::object::{Array, Dict, Name, ObjectIdentifier};
/// (object number, name, on by default) of each layer. /// (object number, name, on by default) of each layer.
fn ocgs(pdf: &Pdf) -> Vec<(i32, String, bool)> { fn ocgs(pdf: &Pdf) -> Vec<(i32, String, bool)> {
@@ -28,7 +28,7 @@ fn ocgs(pdf: &Pdf) -> Vec<(i32, String, bool)> {
.filter_map(|r| { .filter_map(|r| {
let d = xref.get::<Dict<'_>>(ObjectIdentifier::from(r))?; let d = xref.get::<Dict<'_>>(ObjectIdentifier::from(r))?;
let name = d let name = d
.get::<hayro::hayro_syntax::object::String<'_>>(b"Name") .get::<craft_pdf::syntax::object::String<'_>>(b"Name")
.map(|s| String::from_utf8_lossy(s.as_bytes()).to_string()) .map(|s| String::from_utf8_lossy(s.as_bytes()).to_string())
.unwrap_or_default(); .unwrap_or_default();
let visible = if base_off { on.contains(&r.obj_number) } else { !off.contains(&r.obj_number) }; let visible = if base_off { on.contains(&r.obj_number) } else { !off.contains(&r.obj_number) };
@@ -39,13 +39,13 @@ fn ocgs(pdf: &Pdf) -> Vec<(i32, String, bool)> {
/// The layers of a placed PDF: (name, visible by default). /// The layers of a placed PDF: (name, visible by default).
pub fn pdf_layers(bytes: &[u8]) -> Vec<(String, bool)> { pub fn pdf_layers(bytes: &[u8]) -> Vec<(String, bool)> {
Pdf::new(Arc::new(bytes.to_vec())).map(|p| ocgs(&p).into_iter().map(|(_, n, v)| (n, v)).collect()).unwrap_or_default() craft_pdf::open_slice(bytes, None).map(|p| ocgs(&p).into_iter().map(|(_, n, v)| (n, v)).collect()).unwrap_or_default()
} }
/// `bytes` with exactly the layers named in `hidden` off (the others on). `None` when the file /// `bytes` with exactly the layers named in `hidden` off (the others on). `None` when the file
/// has no such layers or can't be read. /// has no such layers or can't be read.
pub fn pdf_hide_layers(bytes: &[u8], hidden: &[String]) -> Option<Vec<u8>> { pub fn pdf_hide_layers(bytes: &[u8], hidden: &[String]) -> Option<Vec<u8>> {
let pdf = Pdf::new(Arc::new(bytes.to_vec())).ok()?; let pdf = craft_pdf::open_slice(bytes, None).ok()?;
let layers = ocgs(&pdf); let layers = ocgs(&pdf);
if layers.is_empty() { if layers.is_empty() {
return None; return None;
@@ -74,8 +74,8 @@ pub fn pdf_hide_layers(bytes: &[u8], hidden: &[String]) -> Option<Vec<u8>> {
); );
let xref_at = out.len(); let xref_at = out.len();
let prev = { let prev = {
let tail = String::from_utf8_lossy(&bytes[bytes.len().saturating_sub(64)..]).to_string(); let tail = String::from_utf8_lossy(&bytes[bytes.len().saturating_sub(1024)..]).to_string();
tail.rsplit("startxref").next().and_then(|t| t.split_whitespace().next()).and_then(|n| n.parse::<usize>().ok())? tail.rsplit_once("startxref").and_then(|(_, t)| t.split_whitespace().next()).and_then(|n| n.parse::<usize>().ok())?
}; };
out.extend_from_slice( out.extend_from_slice(
format!("xref\n{root} 1\n{at:010} 00000 n\r\ntrailer\n<</Size {}/Root {root} 0 R/Prev {prev}>>\nstartxref\n{xref_at}\n%%EOF\n", root + 1) format!("xref\n{root} 1\n{at:010} 00000 n\r\ntrailer\n<</Size {}/Root {root} 0 R/Prev {prev}>>\nstartxref\n{xref_at}\n%%EOF\n", root + 1)
+61
View File
@@ -0,0 +1,61 @@
fn page(width: f32, height: f32) -> Vec<u8> {
let content = format!("1 0 0 rg 0 0 {width} {height} re f");
format!(
"%PDF-1.7\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 {width} {height}] /Contents 4 0 R >> endobj\n4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\ntrailer << /Root 1 0 R >>\n%%EOF",
content.len()
)
.into_bytes()
}
#[test]
fn thin_pdf_page_has_visible_preview_and_export_pixels() {
let bytes = page(1000.0, 0.2);
let preview = designcraft_render::render_pdf_page(&bytes, 0, 700).unwrap();
assert_eq!((preview.width(), preview.height()), (700, 1));
assert!(preview.data().iter().any(|pixel| pixel.a > 0));
let png = designcraft_render::pdf_page_png_checked(&bytes, 0, 700, &[], None).unwrap();
let image = image::load_from_memory(&png).unwrap().to_rgba8();
assert_eq!(image.dimensions(), (700, 1));
assert!(image.pixels().any(|pixel| pixel[3] > 0));
}
#[test]
fn empty_media_box_preserves_the_parsers_default_page_repair() {
let bytes = page(0.0, 0.0);
let missing = String::from_utf8(bytes.clone()).unwrap().replace("/MediaBox [0 0 0 0]", "");
let size = designcraft_render::pdf_page_size(&bytes, 0).unwrap();
assert!(size.0 > 0.0 && size.1 > 0.0);
assert_eq!(Some(size), designcraft_render::pdf_page_size(missing.as_bytes(), 0));
let preview = designcraft_render::render_pdf_page(&bytes, 0, 700).unwrap();
let png = designcraft_render::pdf_page_png_checked(&bytes, 0, 700, &[], None).unwrap();
let image = image::load_from_memory(&png).unwrap();
assert_eq!((image.width(), image.height()), (u32::from(preview.width()), u32::from(preview.height())));
}
#[test]
fn pdf_input_limit_is_actionable_before_placement() {
let error = designcraft_render::check_pdf_input_size(craft_pdf::MAX_INPUT_BYTES + 1).unwrap_err();
assert!(matches!(error, designcraft_render::PdfRasterError::Input(_)));
assert!(error.to_string().contains("input limit"));
}
#[test]
fn pdf_boxes_match_scaled_and_rotated_page_coordinates() {
for (rotation, size, trim, media) in [
(0, (200.0, 120.0), (20.0, 40.0, 140.0, 60.0), (-20.0, -40.0, 400.0, 200.0)),
(90, (120.0, 200.0), (20.0, 20.0, 60.0, 140.0), (-40.0, -20.0, 200.0, 400.0)),
(180, (200.0, 120.0), (40.0, 20.0, 140.0, 60.0), (-180.0, -40.0, 400.0, 200.0)),
(270, (120.0, 200.0), (40.0, 40.0, 60.0, 140.0), (-40.0, -180.0, 200.0, 400.0)),
] {
let bytes = String::from_utf8(page(200.0, 100.0)).unwrap().replace(
"/MediaBox [0 0 200 100]",
&format!("/MediaBox [0 0 200 100] /CropBox [10 20 110 80] /TrimBox [20 30 90 60] /UserUnit 2 /Rotate {rotation}"),
);
let bytes = bytes.as_bytes();
assert_eq!(designcraft_render::pdf_page_size(bytes, 0), Some(size), "rotation {rotation}");
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "crop"), Some((0.0, 0.0, size.0, size.1)), "rotation {rotation}");
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "trim"), Some(trim), "rotation {rotation}");
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "media"), Some(media), "rotation {rotation}");
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "art"), Some((0.0, 0.0, size.0, size.1)), "missing box, rotation {rotation}");
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
//! Placed PDFs are drawn through pdfcraft's patched hayro crates. Calculator functions whose `idiv` //! Placed PDFs are drawn through craft-pdf's patched Hayro crates. Calculator functions whose `idiv`
//! divides by zero or overflows, stitching functions nested a hundred thousand deep, and mesh //! divides by zero or overflows, stitching functions nested a hundred thousand deep, and mesh
//! shadings with points far off the page, ten thousand overlapping triangles or forty thousand //! shadings with points far off the page, ten thousand overlapping triangles or forty thousand
//! patches do not panic, overflow the stack or take more than a minute, and the rest of the page is //! patches do not panic, overflow the stack or take more than a minute, and the rest of the page is
@@ -9,14 +9,14 @@ use std::sync::Arc;
use std::sync::mpsc::{self, RecvTimeoutError}; use std::sync::mpsc::{self, RecvTimeoutError};
use std::time::Duration; use std::time::Duration;
use craft_pdf::interpret::Function;
use craft_pdf::syntax::Pdf;
use craft_pdf::syntax::object::{Object, ObjectIdentifier};
use designcraft_compose::Cache; use designcraft_compose::Cache;
use designcraft_doc::build::NewDocument; use designcraft_doc::build::NewDocument;
use designcraft_doc::{Asset, AssetId, Content, Document, Graphic, Item, ItemId, PdfCrop, Shape, SpreadRef}; use designcraft_doc::{Asset, AssetId, Content, Document, Graphic, Item, ItemId, PdfCrop, Shape, SpreadRef};
use designcraft_geom::{Affine, Rect, shapes}; use designcraft_geom::{Affine, Rect, shapes};
use designcraft_render::{Rendered, Renderer, pdf_crop_box, pdf_page_size}; use designcraft_render::{Rendered, Renderer, pdf_crop_box, pdf_page_size};
use hayro::hayro_interpret::Function;
use hayro::hayro_syntax::Pdf;
use hayro::hayro_syntax::object::{Object, ObjectIdentifier};
const WHITE: [u8; 4] = [255, 255, 255, 255]; const WHITE: [u8; 4] = [255, 255, 255, 255];
const RED: [u8; 4] = [255, 0, 0, 255]; const RED: [u8; 4] = [255, 0, 0, 255];
+2
View File
@@ -113,3 +113,5 @@ builds (on manifest changes), packaging lint and the release workflow; `xtask ci
| 2026-10-12 | minor | Documented cancelable Dock Quit through the native event-loop boundary | | 2026-10-12 | minor | Documented cancelable Dock Quit through the native event-loop boundary |
| 2026-10-12 | minor | Documented orderly native exit recovery and the remaining cancelable Dock Quit gap | | 2026-10-12 | minor | Documented orderly native exit recovery and the remaining cancelable Dock Quit gap |
| 2026-10-10 | major | Created from the code on main (92649ff) and `plan/architecture.md` | | 2026-10-10 | major | Created from the code on main (92649ff) and `plan/architecture.md` |
PDF parsing and placed-page rasterization use the pinned `craft-pdf` reader. DesignCraft retains document, placement, layer and export policy; dependency checks and bounds are described in [pdf-backend.md](pdf-backend.md).
+35 -5
View File
@@ -1,9 +1,12 @@
# PDF writing boundary # Shared PDF backend
DesignCraft pins `craft-pdf` and its patched Krilla 0.8.2 backend to craft-libs revision DesignCraft pins `craft-pdf`, patched Krilla 0.8.2 and the four Hayro reader crates to
`c3487320b2d1de657b860b3c8abb44e1f2b32bd8`. The `write-pdf` capability is required for craft-libs revision `89dcbb3060377a26c99a6d80caa16084366704c4`. Existing `craft-ui` and `craft-print` revisions
placed PDF pages and book merging. The ordinary reader dependencies, `craft-ui` and remain unchanged. The reader migration is reviewed on top of the separate writer change.
`craft-print` revisions remain unchanged by this writer migration.
## Writing
The `write-pdf` capability is required for placed PDF pages and book merging.
The shared library supplies fallible font copying/preparation, metadata setup, version and The shared library supplies fallible font copying/preparation, metadata setup, version and
archival configuration, final serialization and a native unwind guard. DesignCraft retains archival configuration, final serialization and a native unwind guard. DesignCraft retains
@@ -32,3 +35,30 @@ Tests cover metadata key presence, hostile page selection, successful PDF export
subprocess recovery for all three export entry points. Native/WASM export comparisons and subprocess recovery for all three export entry points. Native/WASM export comparisons and
independent PDF parsing/rendering supplement the repository gates. Physical printer output, independent PDF parsing/rendering supplement the repository gates. Physical printer output,
Windows/Linux runtime and PDF/A/PDF/X certification remain separate acceptance work. Windows/Linux runtime and PDF/A/PDF/X certification remain separate acceptance work.
## Reading
Placed-PDF geometry, layers and raster output use `craft-pdf` with its explicit `raster`
feature. The facade and four patched Hayro crates are pinned to one immutable craft-libs
revision. The backend source was transferred from PdfCraft revision
`7437bde378f43856eada40af25b983812e615e8a`; the per-crate `vendor/SOURCE.json` manifests and `vendor/README.craft-reader.md`
record the transfer and subsequent craft-libs patches. Asset provenance and hashes
are recorded separately in ASSETS.md.
`cargo xtask pdf-deps` checks unique resolved packages, exact versions and sources.
DesignCraft retains placement, layer-name policy, document state and the application render
cache. Render output is premultiplied RGBA8. Page boxes use the same crop, rotation and
UserUnit transform as raster output, including fractional placed-PDF dimensions.
Layered-PDF export stops on failed layer rewriting, missing pages or reported incomplete
content rather than saving an unchanged or incomplete placed page. Shared diagnostics only
report known backend warnings; they do not certify all PDF fidelity.
DesignCraft uses a 512 MiB reader input bound, checked before parsing or copying.
Raster output has shared geometry/allocation bounds. Native unwind guards turn escaped
backend panics into errors; WASM aborts and process-wide allocation failures are not recoverable.
Application fonts, image assets, editing and export presets remain app-owned. Existing
craft-ui/craft-print versions are preserved. The reader migration builds on the independently qualified writer change.
The shared commit must be published before a fresh public checkout can resolve these pins.
No sibling checkout is required. Licences and existing backend asset notices are recorded in
NOTICE, ASSETS.md and licenses/pdf-backend. The backend's removed LAB.icc is not restored.
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
@@ -0,0 +1,7 @@
Copyright (c) The Hayro Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
Copyright 1990-2023 Adobe. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Neither the name of Adobe nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
@@ -0,0 +1,7 @@
Copyright (c) The Hayro Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,116 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
http://creativecommons.org/publicdomain/zero/1.0/
@@ -0,0 +1,27 @@
// Copyright 2014 PDFium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
@@ -0,0 +1,7 @@
Copyright (c) The Hayro Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
@@ -0,0 +1,7 @@
Copyright (c) The Hayro Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+7
View File
@@ -0,0 +1,7 @@
Copyright (c) The Hayro Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+3 -3
View File
@@ -20,12 +20,12 @@ usage: cargo xtask <command>
commands: commands:
assets check that every icon/image/font/asset is attributed in ASSETS.md assets check that every icon/image/font/asset is attributed in ASSETS.md
layers enforce the crate dependency layering (plan/architecture.md §3) layers enforce the crate dependency layering (plan/architecture.md §3)
pdf-deps verify the pinned PDF backend, including Krilla dependencies
wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates
ci fmt --check, clippy -D warnings, test, assets, layers, wasm (stops at first failure) ci fmt --check, clippy -D warnings, test, assets, layers, wasm (stops at first failure)
corpus [--download] corpus [--download]
show where test corpora live; --download fetches PngSuite into corpus/pngsuite show where test corpora live; --download fetches PngSuite into corpus/pngsuite
stats [--exact] count tests and lines per crate (--exact: ask the test harness via `-- --list`) stats [--exact] count tests and lines per crate (--exact: ask the test harness via `-- --list`)
pdf-deps verify the pinned shared PDF writer and patched Krilla
parity recompute the InDesign feature-parity summary in docs/parity-checklist.md parity recompute the InDesign feature-parity summary in docs/parity-checklist.md
ico <out.ico> <in.png>... ico <out.ico> <in.png>...
pack square PNGs (<= 256 px) into a Windows .ico (see packaging/icons.sh) pack square PNGs (<= 256 px) into a Windows .ico (see packaging/icons.sh)
@@ -37,11 +37,11 @@ fn main() -> ExitCode {
let result = match args.first().map(String::as_str) { let result = match args.first().map(String::as_str) {
Some("assets") => assets::run(&root()), Some("assets") => assets::run(&root()),
Some("layers") => cmd_layers(), Some("layers") => cmd_layers(),
Some("pdf-deps") => pdf_deps::run(&root()),
Some("wasm") => cmd_wasm(), Some("wasm") => cmd_wasm(),
Some("ci") => cmd_ci(), Some("ci") => cmd_ci(),
Some("corpus") => cmd_corpus(rest.contains(&"--download")), Some("corpus") => cmd_corpus(rest.contains(&"--download")),
Some("stats") => stats::run(&root(), rest.contains(&"--exact")), Some("stats") => stats::run(&root(), rest.contains(&"--exact")),
Some("pdf-deps") => pdf_deps::run(&root()),
Some("parity") => parity::run(&root()), Some("parity") => parity::run(&root()),
Some("ico") => ico::run(&rest), Some("ico") => ico::run(&rest),
Some("version") => version::run(&root(), &rest), Some("version") => version::run(&root(), &rest),
@@ -162,6 +162,7 @@ fn cmd_ci() -> Result<(), String> {
run(c, "cargo fmt --all -- --check") run(c, "cargo fmt --all -- --check")
}), }),
), ),
("pdf-deps", Box::new(|| pdf_deps::run(&root()))),
( (
"clippy", "clippy",
Box::new(|| { Box::new(|| {
@@ -178,7 +179,6 @@ fn cmd_ci() -> Result<(), String> {
run(c, "cargo test --workspace") run(c, "cargo test --workspace")
}), }),
), ),
("pdf-deps", Box::new(|| pdf_deps::run(&root()))),
("assets", Box::new(|| assets::run(&root()))), ("assets", Box::new(|| assets::run(&root()))),
("layers", Box::new(cmd_layers)), ("layers", Box::new(cmd_layers)),
("wasm", Box::new(cmd_wasm)), ("wasm", Box::new(cmd_wasm)),
+37 -9
View File
@@ -1,11 +1,18 @@
//! Check the shared writer source, including transitive users such as krilla-svg. //! Check the shared reader source, including the parser reached through Krilla.
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use serde_json::Value; use serde_json::Value;
const REV: &str = "c3487320b2d1de657b860b3c8abb44e1f2b32bd8"; const REV: &str = "89dcbb3060377a26c99a6d80caa16084366704c4";
const PACKAGES: &[(&str, &str)] = &[("craft-pdf", "0.1.0"), ("krilla", "0.8.2")]; const PACKAGES: &[(&str, &str)] = &[
("craft-pdf", "0.1.0"),
("krilla", "0.8.2"),
("hayro", "0.7.1"),
("hayro-interpret", "0.7.0"),
("hayro-syntax", "0.7.2"),
("hayro-jbig2", "0.3.0"),
];
pub fn run(root: &Path) -> Result<(), String> { pub fn run(root: &Path) -> Result<(), String> {
let output = Command::new(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into())) let output = Command::new(std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into()))
@@ -18,7 +25,7 @@ pub fn run(root: &Path) -> Result<(), String> {
} }
let metadata: Value = serde_json::from_slice(&output.stdout).map_err(|e| format!("cargo metadata: {e}"))?; let metadata: Value = serde_json::from_slice(&output.stdout).map_err(|e| format!("cargo metadata: {e}"))?;
validate(&metadata)?; validate(&metadata)?;
println!("PDF writer: facade and patched Krilla resolve once to {REV}"); println!("PDF backend: facade, Krilla and Hayro resolve once to {REV}");
Ok(()) Ok(())
} }
@@ -34,6 +41,15 @@ fn validate(metadata: &Value) -> Result<(), String> {
return Err(format!("{name}: expected {version} from {source}; got {package}")); return Err(format!("{name}: expected {version} from {source}; got {package}"));
} }
} }
let nodes = metadata.pointer("/resolve/nodes").and_then(Value::as_array).ok_or("cargo metadata has no resolved nodes")?;
let facade = packages.iter().find(|p| p["name"] == "craft-pdf").ok_or("craft-pdf: missing package")?;
let node = nodes.iter().find(|n| n["id"] == facade["id"]).ok_or("craft-pdf: missing resolved node")?;
let features = node["features"].as_array().ok_or("craft-pdf: missing features")?;
for required in ["read", "interpret", "raster", "write", "write-pdf"] {
if !features.iter().any(|f| f.as_str() == Some(required)) {
return Err(format!("craft-pdf: missing required {required} feature"));
}
}
Ok(()) Ok(())
} }
@@ -43,17 +59,17 @@ mod tests {
use serde_json::json; use serde_json::json;
fn metadata() -> Value { fn metadata() -> Value {
json!({"packages": PACKAGES.iter().map(|(name, version)| json!({"name":name,"version":version, json!({"packages": PACKAGES.iter().map(|(name, version)| json!({"id":name,"name":name,"version":version,
"source":format!("git+https://github.com/storytold/craft-libs?rev={REV}#{REV}")})).collect::<Vec<_>>()}) "source":format!("git+https://github.com/storytold/craft-libs?rev={REV}#{REV}")})).collect::<Vec<_>>(), "resolve": {"nodes": [{"id": "craft-pdf", "features": ["read", "interpret", "raster", "write", "write-pdf"]}]}})
} }
#[test] #[test]
fn accepts_one_pinned_facade_and_writer() { fn accepts_one_pinned_facade_and_reader() {
assert!(validate(&metadata()).is_ok()); assert!(validate(&metadata()).is_ok());
} }
#[test] #[test]
fn rejects_unpatched_and_local_writer_sources() { fn rejects_unpatched_and_local_reader_sources() {
for source in [Value::Null, json!("registry+https://github.com/rust-lang/crates.io-index")] { for source in [Value::Null, json!("registry+https://github.com/rust-lang/crates.io-index")] {
let mut m = metadata(); let mut m = metadata();
m["packages"][1]["source"] = source; m["packages"][1]["source"] = source;
@@ -63,7 +79,7 @@ mod tests {
#[test] #[test]
fn rejects_missing_duplicate_and_wrong_version_packages() { fn rejects_missing_duplicate_and_wrong_version_packages() {
for index in [0, 1] { for index in 0..PACKAGES.len() {
let mut m = metadata(); let mut m = metadata();
m["packages"][index]["version"] = json!("99.0.0"); m["packages"][index]["version"] = json!("99.0.0");
assert!(validate(&m).is_err()); assert!(validate(&m).is_err());
@@ -77,6 +93,18 @@ mod tests {
} }
} }
#[test]
fn rejects_missing_reader_or_writer_features() {
for index in 0..5 {
let mut m = metadata();
m["resolve"]["nodes"][0]["features"].as_array_mut().unwrap().remove(index);
assert!(validate(&m).is_err());
}
let mut m = metadata();
m["resolve"] = Value::Null;
assert!(validate(&m).is_err());
}
#[test] #[test]
fn rejects_facade_revision_drift_and_malformed_metadata() { fn rejects_facade_revision_drift_and_malformed_metadata() {
let mut m = metadata(); let mut m = metadata();