diff --git a/Cargo.lock b/Cargo.lock
index 846b85418e..1bf47f30d0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1178,17 +1178,17 @@ dependencies = [
[[package]]
name = "craft-docpath"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
[[package]]
name = "craft-fonts-build"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
[[package]]
name = "craft-i18n"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"fixed_decimal",
"icu_decimal",
@@ -1211,7 +1211,7 @@ dependencies = [
[[package]]
name = "craft-keymap"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"egui",
"serde",
@@ -1221,7 +1221,7 @@ dependencies = [
[[package]]
name = "craft-menu"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"craft-keymap",
"craft-quit",
@@ -1234,7 +1234,7 @@ dependencies = [
[[package]]
name = "craft-print"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"craft-winprint-gdi",
"log",
@@ -1248,7 +1248,7 @@ dependencies = [
[[package]]
name = "craft-quit"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"dispatch2",
"egui",
@@ -1259,7 +1259,7 @@ dependencies = [
[[package]]
name = "craft-ui"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"craft-fonts-build",
"craft-i18n",
@@ -1280,7 +1280,7 @@ dependencies = [
[[package]]
name = "craft-update"
version = "0.1.0"
-source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
+source = "git+https://github.com/storytold/craft-libs?rev=787a7a0f972fd1d7a7618fdbeca3a5267a0407ff#787a7a0f972fd1d7a7618fdbeca3a5267a0407ff"
dependencies = [
"chrono",
"dunce",
@@ -4208,7 +4208,7 @@ dependencies = [
[[package]]
name = "pdfcraft"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"chrono",
"craft-update",
@@ -4232,7 +4232,7 @@ dependencies = [
[[package]]
name = "pdfcraft-a11y"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"pdfcraft-content",
"pdfcraft-cos",
@@ -4244,7 +4244,7 @@ dependencies = [
[[package]]
name = "pdfcraft-annot"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"hayro-syntax",
"pdfcraft-cos",
@@ -4255,7 +4255,7 @@ dependencies = [
[[package]]
name = "pdfcraft-audit"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"pdfcraft-content",
"pdfcraft-cos",
@@ -4266,7 +4266,7 @@ dependencies = [
[[package]]
name = "pdfcraft-automation"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"base64 0.23.1",
"flate2",
@@ -4288,7 +4288,7 @@ dependencies = [
[[package]]
name = "pdfcraft-cli"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"base64 0.23.1",
"pdfcraft-audit",
@@ -4306,11 +4306,11 @@ dependencies = [
[[package]]
name = "pdfcraft-compare"
-version = "0.16.1"
+version = "0.17.0"
[[package]]
name = "pdfcraft-content"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"hayro-syntax",
"pdfcraft-cos",
@@ -4318,7 +4318,7 @@ dependencies = [
[[package]]
name = "pdfcraft-cos"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"chrono",
"hayro-syntax",
@@ -4333,11 +4333,10 @@ dependencies = [
[[package]]
name = "pdfcraft-create"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"encoding_rs",
"hayro-ccitt",
- "hayro-jbig2",
"hayro-syntax",
"image",
"pdfcraft-cos",
@@ -4353,7 +4352,7 @@ dependencies = [
[[package]]
name = "pdfcraft-crypt"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"aes",
"md-5",
@@ -4365,7 +4364,7 @@ dependencies = [
[[package]]
name = "pdfcraft-edit"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"hayro-syntax",
"kurbo 0.13.1",
@@ -4374,6 +4373,8 @@ dependencies = [
"pdfcraft-cos",
"pdfcraft-fonts",
"pdfcraft-model",
+ "pdfcraft-render",
+ "proptest",
"sha2 0.11.1",
"thiserror 2.0.21",
"unicode-bidi",
@@ -4381,7 +4382,7 @@ dependencies = [
[[package]]
name = "pdfcraft-engine"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"chrono",
"craft-docpath",
@@ -4393,10 +4394,12 @@ dependencies = [
"pdfcraft-annot",
"pdfcraft-audit",
"pdfcraft-compare",
+ "pdfcraft-content",
"pdfcraft-cos",
"pdfcraft-create",
"pdfcraft-edit",
"pdfcraft-export",
+ "pdfcraft-filters",
"pdfcraft-fonts",
"pdfcraft-forms",
"pdfcraft-geom",
@@ -4423,14 +4426,14 @@ dependencies = [
[[package]]
name = "pdfcraft-export"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"flate2",
]
[[package]]
name = "pdfcraft-filters"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"fax",
"flate2",
@@ -4443,7 +4446,7 @@ dependencies = [
[[package]]
name = "pdfcraft-fonts"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"harfrust",
"pdfcraft-cos",
@@ -4453,7 +4456,7 @@ dependencies = [
[[package]]
name = "pdfcraft-forms"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"hayro-syntax",
"pdfcraft-content",
@@ -4466,11 +4469,11 @@ dependencies = [
[[package]]
name = "pdfcraft-geom"
-version = "0.16.1"
+version = "0.17.0"
[[package]]
name = "pdfcraft-js"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"boa_engine",
"chrono",
@@ -4478,7 +4481,7 @@ dependencies = [
[[package]]
name = "pdfcraft-measure"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"pdfcraft-annot",
"pdfcraft-content",
@@ -4491,14 +4494,14 @@ dependencies = [
[[package]]
name = "pdfcraft-model"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"pdfcraft-cos",
]
[[package]]
name = "pdfcraft-ocr"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"ocrs",
"pdfcraft-fonts",
@@ -4509,7 +4512,7 @@ dependencies = [
[[package]]
name = "pdfcraft-optimize"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"image",
"pdfcraft-annot",
@@ -4525,11 +4528,11 @@ dependencies = [
[[package]]
name = "pdfcraft-oracle"
-version = "0.16.1"
+version = "0.17.0"
[[package]]
name = "pdfcraft-organize"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"hayro-syntax",
"pdfcraft-content",
@@ -4540,7 +4543,7 @@ dependencies = [
[[package]]
name = "pdfcraft-platform"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"async-channel",
"futures-lite",
@@ -4550,7 +4553,7 @@ dependencies = [
[[package]]
name = "pdfcraft-preflight"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"flate2",
"pdfcraft-content",
@@ -4561,7 +4564,7 @@ dependencies = [
[[package]]
name = "pdfcraft-print"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"craft-print",
"pdfcraft-content",
@@ -4573,7 +4576,7 @@ dependencies = [
[[package]]
name = "pdfcraft-redact"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"pdfcraft-annot",
"pdfcraft-content",
@@ -4592,7 +4595,7 @@ dependencies = [
[[package]]
name = "pdfcraft-render"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"bytemuck",
"flate2",
@@ -4611,7 +4614,7 @@ dependencies = [
[[package]]
name = "pdfcraft-sign"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"aes",
"aws-lc-rs",
@@ -4647,11 +4650,11 @@ dependencies = [
[[package]]
name = "pdfcraft-testkit"
-version = "0.16.1"
+version = "0.17.0"
[[package]]
name = "pdfcraft-ui-egui"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"arboard",
"base64 0.23.1",
@@ -4675,6 +4678,7 @@ dependencies = [
"objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
+ "pdfcraft-content",
"pdfcraft-cos",
"pdfcraft-engine",
"pdfcraft-fonts",
@@ -4685,6 +4689,7 @@ dependencies = [
"plist",
"png 0.18.1",
"pollster 1.0.1",
+ "proptest",
"rfd",
"rustix 1.1.5",
"serde",
@@ -4703,7 +4708,7 @@ dependencies = [
[[package]]
name = "pdfcraft-web"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"eframe",
"egui",
@@ -4715,7 +4720,7 @@ dependencies = [
[[package]]
name = "pdfcraft-xfa"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"base64 0.23.1",
"image",
@@ -4729,7 +4734,7 @@ dependencies = [
[[package]]
name = "pdfcraft-xfdf"
-version = "0.16.1"
+version = "0.17.0"
dependencies = [
"pdfcraft-annot",
"pdfcraft-cos",
diff --git a/Cargo.toml b/Cargo.toml
index 85d02570ba..edd9e682b7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ exclude = ["vendor"]
default-members = ["crates/*", "apps/pdfcraft", "apps/pdfcraft-cli", "xtask"]
[workspace.package]
-version = "0.16.1"
+version = "0.17.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -13,52 +13,52 @@ repository = "https://github.com/storytold/pdfcraft"
[workspace.dependencies]
# Shared frontend components; engines remain independent of egui.
-craft-ui = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["serde"] }
+craft-ui = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff", features = ["serde"] }
# Quit the macOS way (#1136): ⌘Q asks about unsaved changes, and closing quits through `terminate:`.
-craft-quit = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
+craft-quit = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
# One menu definition for the native macOS menu bar and the in-window bar elsewhere.
-craft-menu = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
-craft-keymap = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", default-features = false }
+craft-menu = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
+craft-keymap = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff", default-features = false }
# Shared system spooler; document composition remains in pdfcraft-print.
-craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
+craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
# Whether a path stored in a document may be opened (GoToR link targets).
-craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
+craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
# craft-update: app-specific pretend versions apply to startup and manual checks.
-craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] }
+craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff", features = ["egui"] }
# internal
-pdfcraft-geom = { path = "crates/geom", version = "0.16.1" }
-pdfcraft-filters = { path = "crates/filters", version = "0.16.1" }
-pdfcraft-cos = { path = "crates/cos", version = "0.16.1" }
-pdfcraft-crypt = { path = "crates/crypt", version = "0.16.1" }
-pdfcraft-organize = { path = "crates/organize", version = "0.16.1" }
-pdfcraft-measure = { path = "crates/measure", version = "0.16.1" }
-pdfcraft-annot = { path = "crates/annot", version = "0.16.1" }
-pdfcraft-audit = { path = "crates/audit", version = "0.16.1" }
-pdfcraft-fonts = { path = "crates/fonts", version = "0.16.1" }
-pdfcraft-model = { path = "crates/model", version = "0.16.1" }
-pdfcraft-content = { path = "crates/content", version = "0.16.1" }
-pdfcraft-redact = { path = "crates/redact", version = "0.16.1" }
-pdfcraft-print = { path = "crates/print", version = "0.16.1" }
-pdfcraft-xfdf = { path = "crates/xfdf", version = "0.16.1" }
-pdfcraft-sign = { path = "crates/sign", version = "0.16.1" }
-pdfcraft-optimize = { path = "crates/optimize", version = "0.16.1" }
-pdfcraft-a11y = { path = "crates/a11y", version = "0.16.1" }
-pdfcraft-oracle = { path = "crates/oracle", version = "0.16.1" }
-pdfcraft-testkit = { path = "crates/testkit", version = "0.16.1" }
-pdfcraft-ocr = { path = "crates/ocr", version = "0.16.1" }
-pdfcraft-js = { path = "crates/js", version = "0.16.1" }
-pdfcraft-compare = { path = "crates/compare", version = "0.16.1" }
-pdfcraft-preflight = { path = "crates/preflight", version = "0.16.1" }
-pdfcraft-export = { path = "crates/export", version = "0.16.1" }
-pdfcraft-edit = { path = "crates/edit", version = "0.16.1" }
-pdfcraft-create = { path = "crates/create", version = "0.16.1" }
-pdfcraft-forms = { path = "crates/forms", version = "0.16.1" }
-pdfcraft-xfa = { path = "crates/xfa", version = "0.16.1" }
-pdfcraft-render = { path = "crates/render", version = "0.16.1" }
-pdfcraft-engine = { path = "crates/engine", version = "0.16.1" }
-pdfcraft-platform = { path = "crates/platform", version = "0.16.1" }
-pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.16.1" }
-pdfcraft-automation = { path = "crates/automation", version = "0.16.1", default-features = false }
+pdfcraft-geom = { path = "crates/geom", version = "0.17.0" }
+pdfcraft-filters = { path = "crates/filters", version = "0.17.0" }
+pdfcraft-cos = { path = "crates/cos", version = "0.17.0" }
+pdfcraft-crypt = { path = "crates/crypt", version = "0.17.0" }
+pdfcraft-organize = { path = "crates/organize", version = "0.17.0" }
+pdfcraft-measure = { path = "crates/measure", version = "0.17.0" }
+pdfcraft-annot = { path = "crates/annot", version = "0.17.0" }
+pdfcraft-audit = { path = "crates/audit", version = "0.17.0" }
+pdfcraft-fonts = { path = "crates/fonts", version = "0.17.0" }
+pdfcraft-model = { path = "crates/model", version = "0.17.0" }
+pdfcraft-content = { path = "crates/content", version = "0.17.0" }
+pdfcraft-redact = { path = "crates/redact", version = "0.17.0" }
+pdfcraft-print = { path = "crates/print", version = "0.17.0" }
+pdfcraft-xfdf = { path = "crates/xfdf", version = "0.17.0" }
+pdfcraft-sign = { path = "crates/sign", version = "0.17.0" }
+pdfcraft-optimize = { path = "crates/optimize", version = "0.17.0" }
+pdfcraft-a11y = { path = "crates/a11y", version = "0.17.0" }
+pdfcraft-oracle = { path = "crates/oracle", version = "0.17.0" }
+pdfcraft-testkit = { path = "crates/testkit", version = "0.17.0" }
+pdfcraft-ocr = { path = "crates/ocr", version = "0.17.0" }
+pdfcraft-js = { path = "crates/js", version = "0.17.0" }
+pdfcraft-compare = { path = "crates/compare", version = "0.17.0" }
+pdfcraft-preflight = { path = "crates/preflight", version = "0.17.0" }
+pdfcraft-export = { path = "crates/export", version = "0.17.0" }
+pdfcraft-edit = { path = "crates/edit", version = "0.17.0" }
+pdfcraft-create = { path = "crates/create", version = "0.17.0" }
+pdfcraft-forms = { path = "crates/forms", version = "0.17.0" }
+pdfcraft-xfa = { path = "crates/xfa", version = "0.17.0" }
+pdfcraft-render = { path = "crates/render", version = "0.17.0" }
+pdfcraft-engine = { path = "crates/engine", version = "0.17.0" }
+pdfcraft-platform = { path = "crates/platform", version = "0.17.0" }
+pdfcraft-ui-egui = { path = "crates/ui-egui", version = "0.17.0" }
+pdfcraft-automation = { path = "crates/automation", version = "0.17.0", default-features = false }
# pdf
hayro = "0.7.1"
diff --git a/README.md b/README.md
index ce9e71d4a9..3c1b67936a 100644
--- a/README.md
+++ b/README.md
@@ -547,9 +547,18 @@ stand on its own.
|
| **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
| **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
| **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
-|
| **PdfCraft** | **Reading, organizing and protecting PDFs · you are here** | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
+|
| **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
| **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
| **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
+|
| **FigCraft** | Interface design and prototyping | [GitHub](https://github.com/storytold/figcraft) | [Website](https://getartcraft.com/apps/figcraft) |
+|
| **WordCraft** | Word processing and documents | [GitHub](https://github.com/storytold/wordcraft) | [Website](https://getartcraft.com/apps/wordcraft) |
+|
| **SoundCraft** | Audio recording and editing | [GitHub](https://github.com/storytold/soundcraft) | [Website](https://getartcraft.com/apps/soundcraft) |
+|
| **DeckCraft** | Presentations and slide decks | [GitHub](https://github.com/storytold/deckcraft) | [Website](https://getartcraft.com/apps/deckcraft) |
+|
| **CADCraft** | Computer-aided design | [GitHub](https://github.com/storytold/cadcraft) | [Website](https://getartcraft.com/apps/cadcraft) |
+|
| **GridCraft** | Spreadsheets and data | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
+|
| **TextureCraft** | 3D texture painting | [GitHub](https://github.com/storytold/texturecraft) | [Website](https://getartcraft.com/apps/texturecraft) |
+|
| **Craft Anything** | See how any software works, then build your own | [GitHub](https://github.com/storytold/craft-anything) | [Website](https://getartcraft.com/apps/craft-anything) |
+|
| **SolidCraft** | Parametric 3D CAD by Tangency Labs (community app) | [GitHub](https://github.com/tangency-labs/solidcraft) | |
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
diff --git a/ROADMAP.md b/ROADMAP.md
index 5a8f9ab294..c0f1823c7f 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,5 +1,11 @@
# PdfCraft roadmap
+> **Last reviewed:** 2026-11-01 | **Last updated:** 2026-11-01 | **Change:** patch (M7, PR #2155 further review: inherited drawing/text state, affine image gestures, paste/selection ownership, no-op scripts and menu permissions; estimates unchanged)
+
+> **Last reviewed:** 2026-11-01 | **Last updated:** 2026-11-01 | **Change:** patch (M7, PR #2155 latest comments: explicit image-paste events, mixed-size field refresh, and documented mixed-script normalization with Undo; estimates unchanged)
+
+> **Last reviewed:** 2026-11-01 | **Last updated:** 2026-11-01 | **Change:** patch (M7, PR #2155 review follow-up: preserve image crop/tag/stream ownership, resize in page orientation, harden clipboard and text formatting, and reload installed fonts; estimates unchanged)
+
> **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M2, PR #4172: GBK rasters compared with explicit Unicode and independently selected installed faces; wrong-glyph and wrong-family negative controls fail; estimates unchanged)
> **Last reviewed:** 2026-10-31 | **Last updated:** 2026-10-31 | **Change:** patch (M2, PR #4172: integrate main fa5f9f8c; retain CJK viewer and upstream raster regressions, restore explicit display_config imports; estimates unchanged)
> **Last reviewed:** 2026-10-28 · **Last updated:** 2026-10-28 · **Change:** patch (M2, PR #4172: integrate main 5af10276; retain both roadmap histories and viewer-only CJK font policy; estimates unchanged)
@@ -213,6 +219,18 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
## Progress log
+- **2026-11-01 (M7, #5657):** Header/footer, watermark and background additions and replacements stage the complete call before committing. Synthetic later-page failures preserve exact incremental bytes, object count, dirty state and allocation cursor; success controls retain other marks and shared/inherited resources. Estimates unchanged.
+
+- **2026-11-01 (M14, #5572):** Preferences ▸ Documents and view sets how many recent files to keep, from 0 to 50 with 12 by default. At 0 Home has no Recent section and File ▸ Open Recent is disabled. The list is cut when Preferences closes, so the number can pass through 0 on its way to another.
+
+- **2026-11-01 (M5/M14, customizable hotbar, freehand highlight, and moveable navigation rail):** Added Freehand-Highlight (`/Subtype /Ink`, `/IT /FreehandHighlight`, Multiply blending, 100 pt max line width) inside the standard Highlight flyout and headless `comment_add`; added a customizable floating or side-docked hotbar with a full-side chrome strip mode, settings gear icon anchored at the bar end, and 24–64 px sizing; and added Left/Right docking for the navigation rail (`rail_side`). Focused unit and integration tests pass across `annot`, `automation`, and `ui-egui`. Estimates unchanged.
+- 2026-11-01: PR #2155 further review preserves inherited Form text state and soft-mask chronology during Arrange, composes crop-group moves in the proper scope, and uses affine/press-relative image resize plus a shared rotation pivot. No-op script buttons preserve hidden source text; paste coordinates, visible selection ownership, and Snapshot/Export permissions have dedicated regressions. Candidate only; estimates unchanged.
+
+- 2026-11-01: PR #2155 latest follow-up handles explicit image-paste events without release-time guesses, refreshes the font-size field when a selection becomes mixed, and specifies mixed-script normalization with exact Undo restoration. Candidate only; estimates unchanged.
+
+- 2026-11-01: PR #2155 review follow-up preserves editor crops through group moves and keeps arranged artwork outside added-item streams; unsafe nested-tag extraction is refused. Image handles and flips follow page orientation, added images accept general transforms, and clipboard/text/Home/font-discovery edge cases have focused regressions. Candidate only; estimates unchanged.
+- 2026-11-01: PR #2155 maintainer edits remove redundant image/text helpers and add regressions for crop ownership, arrange scope/state fidelity, clipboard precedence, crop-drag bounds, and font scanning. Transform and Unicode-script tools complete the headless paths.
+
- **2026-10-18 (M3, #1354, local candidate):** Integrated --two-paged with current main, retaining upstream modal/picker ownership and single-tab reuse. Native launch/opening/password regressions, negative checks against the old wrapper, visual inspection, formatting, strict scoped Clippy, layers, assets, parity and scoped WASM compilation pass. Three existing socket tests are unavailable in this workspace; one existing network test remains ignored. No push or GitHub merge; estimates unchanged.
- **2026-10-31 (M2, PR #4172 review):** Added same-machine pixel oracles for GBK Song, Hei, Kai and FangSong, including all four subset-prefixed Founder aliases. Reference PDFs use explicit Unicode mappings and independently selected installed faces; font data and rasters remain in memory. All eight cases ran; wrong-glyph and wrong-family production mutations each failed the new assertion and were restored. Validation: 422 renderer unit tests, renderer all-target Clippy with -D warnings, formatting, layers/assets/parity and renderer WASM checks passed; existing optional-patch/WASM warnings and parity notes remain. Estimates unchanged.
@@ -728,6 +746,7 @@ Newest first: the date, what moved, and the new overall estimate.
| Date | Change | Summary |
|---|---|---|
+| 2026-11-01 | patch | M7 #5657: header/footer, watermark and background operations commit all selected pages together; later-page errors preserve marks, content, shared/inherited resources and pending edits; estimates unchanged. |
| 2026-10-28 | patch | M2, PR #4172: integrate main 5af10276 and retain both roadmap histories; viewer-only CJK and print/output font policies and estimates unchanged. |
| 2026-10-27 | minor | M10: OCR stages SHA-256-verified ONNX models after the upstream RTEN URL brownout; discovery prefers a complete ONNX pair per directory, falls back to a complete RTEN pair, and never mixes formats or directories; language/web limits and estimates unchanged. |
| 2026-10-23 | minor | M8, #3205 follow-up: phone-pattern redaction includes the +39 country code on labelled Italian geographic numbers with a slash; dates, protocol identifiers and whole-run guards remain. Estimates unchanged. |
diff --git a/apps/pdfcraft/src/main.rs b/apps/pdfcraft/src/main.rs
index d2a75c5941..5bc5c5c372 100644
--- a/apps/pdfcraft/src/main.rs
+++ b/apps/pdfcraft/src/main.rs
@@ -10,7 +10,7 @@
//! --theme light|dark|system --language auto| --mode all|read|edit|convert|sign --tool --left open|closed
//! --organize on --fields on --dialog properties|shortcuts|about --palette --home on
//! --cover on|off --default-layout single|continuous|two-up|two-page --default-continuous on|off --default-zoom fit-width|fit-page| --always-default-view on|off
-//! --page-gaps on|off --default-page-gaps on|off
+//! --page-gaps on|off --default-page-gaps on|off --recent-files 0..50
//! --combine-view grid|list --combine-zoom 60..200`
//!
//! `--new-window` opens a window of its own. Without it, on Windows, a launch that only names files
diff --git a/contributors/people.toml b/contributors/people.toml
index a5a47f5c52..fecfb68c90 100644
--- a/contributors/people.toml
+++ b/contributors/people.toml
@@ -180,3 +180,6 @@ sync_github_name = true
[people.dylanmtaylor]
sync_github_name = true
+
+[people.Combiler01]
+sync_github_name = true
diff --git a/crates/annot/src/appearance.rs b/crates/annot/src/appearance.rs
index abc746c0dd..909250ef26 100644
--- a/crates/annot/src/appearance.rs
+++ b/crates/annot/src/appearance.rs
@@ -392,9 +392,13 @@ struct AppearanceDrawing {
}
impl AppearanceDrawing {
+ /// Initialize an appearance drawing buffer and configure its graphics state for opacity and blend mode.
fn new(context: &AppearanceContext<'_>) -> Self {
let mut drawing = Self::default();
- if let Some(gs) = ext_gstate(context.opacity, context.subtype == b"Highlight") {
+ if let Some(gs) = ext_gstate(
+ context.opacity,
+ context.subtype == b"Highlight" || (context.subtype == b"Ink" && context.dict.name(b"IT") == Some(b"FreehandHighlight")),
+ ) {
drawing.resources.set(b"ExtGState".to_vec(), Object::Dict(gs));
drawing.content.push_str("/GS0 gs\n");
}
diff --git a/crates/annot/src/create.rs b/crates/annot/src/create.rs
index a6db5b940b..1904745e95 100644
--- a/crates/annot/src/create.rs
+++ b/crates/annot/src/create.rs
@@ -33,6 +33,16 @@ pub(crate) fn grow(r: [f64; 4], by: f64) -> [f64; 4] {
[r[0] - by, r[1] - by, r[2] + by, r[3] + by]
}
+/// Tight bounding box of ink strokes, including Bezier control points for strokes with three or
+/// more points (matching [`appearance::smooth_segments`]).
+pub(crate) fn ink_bounds(strokes: &[Vec<[f64; 2]>]) -> Option<[f64; 4]> {
+ let controls = strokes.iter().filter(|s| s.len() > 2).flat_map(|s| {
+ let pts: Vec<(f64, f64)> = s.iter().map(|p| (p[0], p[1])).collect();
+ appearance::smooth_segments(&pts).into_iter().flat_map(|[a, b, _]| [[a.0, a.1], [b.0, b.1]])
+ });
+ bounds(strokes.iter().flatten().copied().chain(controls))
+}
+
pub(crate) fn ending_draws(ending: LineEnding) -> bool {
ending != LineEnding::None
}
@@ -87,17 +97,11 @@ pub(crate) fn rect_for(shape: &Shape, style: &Style) -> Result<[f64; 4], AnnotEr
let pad = half + if ending_draws(*start) || ending_draws(*end) { appearance::arrow_size(style.width) } else { 0.0 };
grow(bounds([*from, *to].into_iter()).unwrap_or_default(), pad + 1.0)
}
- Shape::Ink { strokes } | Shape::Signature { strokes } => {
+ Shape::Ink { strokes } | Shape::FreehandHighlight { strokes } | Shape::Signature { strokes } => {
if strokes.iter().all(Vec::is_empty) || !strokes.iter().flatten().all(|p| finite(p)) {
return Err(bad("drawing (no points)"));
}
- // Strokes of three or more points are drawn as curves, which stay within their
- // points and control points.
- let controls = strokes.iter().filter(|s| s.len() > 2).flat_map(|s| {
- let pts: Vec<(f64, f64)> = s.iter().map(|p| (p[0], p[1])).collect();
- appearance::smooth_segments(&pts).into_iter().flat_map(|[a, b, _]| [[a.0, a.1], [b.0, b.1]])
- });
- grow(bounds(strokes.iter().flatten().copied().chain(controls)).unwrap_or_default(), half + 1.0)
+ grow(ink_bounds(strokes).unwrap_or_default(), half + 1.0)
}
Shape::Polygon { vertices, cloud } => {
let b = bounds(vertices.iter().copied())
@@ -164,6 +168,7 @@ fn subject(shape: &Shape) -> &'static str {
Shape::Oval { .. } => "Oval",
Shape::Line { start: LineEnding::None, end: LineEnding::OpenArrow, .. } => "Arrow",
Shape::Line { .. } => "Line",
+ Shape::FreehandHighlight { .. } => "Freehand Highlight",
Shape::Ink { .. } => "Pencil",
Shape::TextBox { .. } => "Text Box",
Shape::Typewriter { .. } => "Typewriter",
@@ -292,10 +297,13 @@ pub fn add_annotation(doc: &mut Document, new: &NewAnnotation, meta: &Meta) -> R
}
}
}
- Shape::Ink { strokes } | Shape::Signature { strokes } => {
+ Shape::Ink { strokes } | Shape::FreehandHighlight { strokes } | Shape::Signature { strokes } => {
d.set(b"C".to_vec(), rgb(style.color));
let list = strokes.iter().filter(|s| !s.is_empty()).map(|s| num_array(&s.concat())).collect();
d.set(b"InkList".to_vec(), Object::Array(list));
+ if matches!(&new.shape, Shape::FreehandHighlight { .. }) {
+ d.set(b"IT".to_vec(), Object::name("FreehandHighlight"));
+ }
border(&mut d);
}
Shape::Polygon { vertices, cloud } => {
diff --git a/crates/annot/src/modify.rs b/crates/annot/src/modify.rs
index 2e3ae0b7d0..a615b8e013 100644
--- a/crates/annot/src/modify.rs
+++ b/crates/annot/src/modify.rs
@@ -1,5 +1,5 @@
use super::create::{
- add_annotation, base_dict, bounds, callout_attach, ending_draws, ending_pair, finite, grow, normalize, num_array, rect_for, rgb,
+ add_annotation, base_dict, bounds, callout_attach, ending_draws, ending_pair, finite, grow, ink_bounds, normalize, num_array, rect_for, rgb,
};
use super::draw::{draw_appearance, redraw_or_restore, set_appearance};
use super::info::{line_endings_of, text_value};
@@ -529,7 +529,33 @@ fn apply_text_box(doc: &mut Document, r: ObjRef, rect: [f64; 4], meta: &Meta, ne
}
fn border_width_of(d: &Dict) -> f64 {
- d.get(b"BS").and_then(|b| b.as_dict()).and_then(|b| b.get(b"W")).and_then(|w| w.as_f64()).unwrap_or(1.0).max(0.0)
+ appearance::border_width(d)
+}
+
+/// Recompute `/Rect` for an `Ink` annotation when its stroke width changes from `old_w` to `w`.
+/// Preserves any baseline margin beyond `old_w / 2.0` (at least `1.0` pt) without accumulating
+/// padding across repeated width increases and decreases.
+fn ink_rect_for_width(d: &Dict, old_w: f64, w: f64) -> Option<[f64; 4]> {
+ let list = d.get(b"InkList")?.as_array()?;
+ let mut strokes: Vec> = Vec::with_capacity(list.len());
+ for s in list {
+ let pts: Vec = s.as_array()?.iter().map(Object::as_f64).collect::