diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 747db112..940d9d18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: include: - platform: 'ubuntu-22.04' - platform: 'windows-latest' - - platform: 'macos-latest' + - platform: 'macos-26' runs-on: ${{ matrix.platform }} defaults: @@ -37,7 +37,7 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Install Rust targets (macOS universal) - if: matrix.platform == 'macos-latest' + if: matrix.platform == 'macos-26' run: rustup target add aarch64-apple-darwin x86_64-apple-darwin - name: Install dependencies (Ubuntu only) @@ -47,7 +47,13 @@ jobs: sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - name: Install frontend dependencies - run: npm install + run: npm ci + + - name: Run quality regression tests + run: npm run test:quality + + - name: Prepare native runtime + run: npm run prepare:native-runtime - name: Run unit tests run: npm run test:unit @@ -76,7 +82,7 @@ jobs: cp target/release/pdfium-worker open-pdf-studio/src-tauri/binaries/pdfium-worker-x86_64-unknown-linux-gnu - name: Build pdfium-worker sidecar (macOS universal) - if: matrix.platform == 'macos-latest' + if: matrix.platform == 'macos-26' working-directory: . run: | cargo build --release -p pdfium-worker --target aarch64-apple-darwin @@ -90,13 +96,19 @@ jobs: target/aarch64-apple-darwin/release/pdfium-worker \ target/x86_64-apple-darwin/release/pdfium-worker - - name: Fetch libpdfium.dylib (macOS) - if: matrix.platform == 'macos-latest' - run: | - mkdir -p src-tauri/binaries/macos-universal - curl -L "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F7834/pdfium-mac-univ.tgz" -o /tmp/pdfium-mac.tgz - tar xzf /tmp/pdfium-mac.tgz -C /tmp lib/libpdfium.dylib - cp /tmp/lib/libpdfium.dylib src-tauri/binaries/macos-universal/libpdfium.dylib - - name: Build Tauri app + if: matrix.platform != 'macos-26' run: npm run tauri build -- --no-bundle + + - name: Build ad-hoc signed macOS app + if: matrix.platform == 'macos-26' + env: + APPLE_SIGNING_IDENTITY: '-' + run: npm run tauri build -- --target universal-apple-darwin --bundles app --config '{"bundle":{"createUpdaterArtifacts":false}}' + + - name: Verify and launch macOS app + if: matrix.platform == 'macos-26' + run: | + APP_PATH="../target/universal-apple-darwin/release/bundle/macos/Open PDF Studio.app" + codesign --verify --deep --strict --verbose=2 "$APP_PATH" + bash scripts/macos-startup-smoke.sh "$APP_PATH" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b9549679..e5058146 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -92,7 +92,7 @@ jobs: - platform: 'ubuntu-22.04' args: '' target: 'linux' - - platform: 'macos-latest' + - platform: 'macos-26' args: '--target universal-apple-darwin' target: 'macos' - platform: 'windows-latest' @@ -136,7 +136,7 @@ jobs: sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - name: Install frontend dependencies - run: npm install + run: npm ci # The PDFium worker pool runs as a Tauri sidecar (bundle.externalBin). # Tauri requires binaries/pdfium-worker- to exist at build @@ -199,14 +199,9 @@ jobs: # verwijst naar binaries/macos-universal/libpdfium.dylib. Universal # (arm64+x86_64) via bblanchon/pdfium-binaries (zelfde chromium-release als # Linux/Windows), met SHA-256-verificatie. - - name: Fetch libpdfium.dylib (macOS) + - name: Prepare libpdfium.dylib (macOS) if: matrix.target == 'macos' - run: | - mkdir -p src-tauri/binaries/macos-universal - curl -L "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F7834/pdfium-mac-univ.tgz" -o /tmp/pdfium-mac.tgz - echo "659e2f647ffd667b36487375165563e58f961db9cf75a45104dc59b9407ccbdf /tmp/pdfium-mac.tgz" | shasum -a 256 -c - - tar xzf /tmp/pdfium-mac.tgz -C /tmp lib/libpdfium.dylib - cp /tmp/lib/libpdfium.dylib src-tauri/binaries/macos-universal/libpdfium.dylib + run: npm run prepare:native-runtime - name: Build Tauri app if: matrix.target != 'windows-user' @@ -227,6 +222,18 @@ jobs: releaseId: ${{ needs.prepare-release.outputs.release_id }} includeUpdaterJson: false + - name: Verify macOS signature and notarization + if: matrix.target == 'macos' + run: | + APP_PATH="../target/universal-apple-darwin/release/bundle/macos/Open PDF Studio.app" + DMG_PATH="$(find ../target/universal-apple-darwin/release/bundle/dmg -name '*.dmg' -print -quit)" + test -n "$DMG_PATH" + codesign --verify --deep --strict --verbose=2 "$APP_PATH" + spctl --assess --type execute --verbose=4 "$APP_PATH" + xcrun stapler validate "$APP_PATH" + xcrun stapler validate "$DMG_PATH" + bash scripts/macos-startup-smoke.sh "$APP_PATH" + - name: Build Tauri app (Windows user installer) if: matrix.target == 'windows-user' uses: tauri-apps/tauri-action@action-v0.6.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2104a320..3a17f956 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ on: version: description: 'Version tag (e.g., v1.0.0)' required: true - default: 'v1.71.0' + default: 'v1.78.0' jobs: create-release: @@ -64,7 +64,7 @@ jobs: - platform: 'ubuntu-22.04' args: '' target: 'linux' - - platform: 'macos-latest' + - platform: 'macos-26' args: '--target universal-apple-darwin' target: 'macos' - platform: 'windows-latest' @@ -104,7 +104,7 @@ jobs: sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - name: Install frontend dependencies - run: npm install + run: npm ci # The PDFium worker pool runs as a Tauri sidecar (bundle.externalBin). # Tauri requires binaries/pdfium-worker- to exist at @@ -181,14 +181,9 @@ jobs: # via bblanchon/pdfium-binaries (zelfde chromium-release als de Linux- # .so en de win-x64 pdfium.dll), met SHA-256-verificatie zodat een # gewijzigde upstream-asset de build laat falen i.p.v. stil door te gaan. - - name: Fetch libpdfium.dylib (macOS) + - name: Prepare libpdfium.dylib (macOS) if: matrix.target == 'macos' - run: | - mkdir -p src-tauri/binaries/macos-universal - curl -L "https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F7834/pdfium-mac-univ.tgz" -o /tmp/pdfium-mac.tgz - echo "659e2f647ffd667b36487375165563e58f961db9cf75a45104dc59b9407ccbdf /tmp/pdfium-mac.tgz" | shasum -a 256 -c - - tar xzf /tmp/pdfium-mac.tgz -C /tmp lib/libpdfium.dylib - cp /tmp/lib/libpdfium.dylib src-tauri/binaries/macos-universal/libpdfium.dylib + run: npm run prepare:native-runtime - name: Set user install mode if: matrix.target == 'windows-user' @@ -232,6 +227,18 @@ jobs: releaseId: ${{ needs.create-release.outputs.release_id }} includeUpdaterJson: true + - name: Verify macOS signature and notarization + if: matrix.target == 'macos' + run: | + APP_PATH="../target/universal-apple-darwin/release/bundle/macos/Open PDF Studio.app" + DMG_PATH="$(find ../target/universal-apple-darwin/release/bundle/dmg -name '*.dmg' -print -quit)" + test -n "$DMG_PATH" + codesign --verify --deep --strict --verbose=2 "$APP_PATH" + spctl --assess --type execute --verbose=4 "$APP_PATH" + xcrun stapler validate "$APP_PATH" + xcrun stapler validate "$DMG_PATH" + bash scripts/macos-startup-smoke.sh "$APP_PATH" + - name: Build Tauri app (user installer) if: matrix.target == 'windows-user' uses: tauri-apps/tauri-action@action-v0.6.1 diff --git a/Cargo.lock b/Cargo.lock index 4444b586..981128c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,27 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -782,6 +803,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.58" @@ -1263,6 +1293,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dpi" version = "0.1.2" @@ -1435,6 +1471,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "event-listener" version = "5.4.1" @@ -1462,6 +1504,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fdeflate" version = "0.3.7" @@ -1506,6 +1554,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -1910,6 +1964,24 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "global-hotkey" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c386b0a4a70cb2d39fffd74480f985b6f0bfbcb934b6a6b6b7e630e448f242e" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2", + "objc2-app-kit", + "once_cell", + "serde", + "thiserror 2.0.18", + "windows-sys 0.59.0", + "x11rb", + "xkeysym", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -1973,6 +2045,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2321,6 +2404,7 @@ dependencies = [ "moxcms", "num-traits", "png 0.18.1", + "tiff", "zune-core", "zune-jpeg", ] @@ -2706,7 +2790,7 @@ dependencies = [ "itoa", "log", "md-5", - "nom", + "nom 7.1.3", "rangemap", "rayon", "time", @@ -2929,6 +3013,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -3246,7 +3339,7 @@ dependencies = [ [[package]] name = "open-pdf-studio" -version = "1.71.0" +version = "1.78.0" dependencies = [ "anyhow", "axum", @@ -3268,10 +3361,12 @@ dependencies = [ "sha2", "tauri", "tauri-build", + "tauri-plugin-clipboard-manager", "tauri-plugin-deep-link", "tauri-plugin-dialog", "tauri-plugin-drag", "tauri-plugin-fs", + "tauri-plugin-global-shortcut", "tauri-plugin-log", "tauri-plugin-os", "tauri-plugin-process", @@ -3472,6 +3567,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.14.0", +] + [[package]] name = "phf" version = "0.13.1" @@ -3741,6 +3847,12 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.39.4" @@ -5117,6 +5229,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-clipboard-manager" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf" +dependencies = [ + "arboard", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + [[package]] name = "tauri-plugin-deep-link" version = "2.4.9" @@ -5195,6 +5322,21 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-global-shortcut" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9f4c5136c09cd962da0c86dc4accd4666db2ea591cf16e6597435843bd2b" +dependencies = [ + "global-hotkey", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + [[package]] name = "tauri-plugin-log" version = "2.8.0" @@ -5493,6 +5635,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.47" @@ -5856,6 +6012,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" +dependencies = [ + "memchr", + "nom 8.0.0", + "petgraph", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6260,6 +6427,76 @@ dependencies = [ "semver", ] +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.1", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.11.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.11.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.98" @@ -7143,6 +7380,24 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wl-clipboard-rs" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix", + "thiserror 2.0.18", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "writeable" version = "0.6.3" @@ -7223,6 +7478,23 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xattr" version = "1.6.1" @@ -7233,6 +7505,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "xz2" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index 5253d30b..417686d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,20 @@ opt-level = 3 # gecachet); workspace-crates behouden snelle debug-compiles. [profile.dev.package."*"] opt-level = 2 + +# Rendering hot paths get full optimization in development. These profiles +# must be declared at the workspace root; Cargo ignores them in member crates. +[profile.dev.package.tiny-skia] +opt-level = 3 + +[profile.dev.package.tiny-skia-path] +opt-level = 3 + +[profile.dev.package.lopdf] +opt-level = 3 + +[profile.dev.package.image] +opt-level = 3 + +[profile.dev.package.pdfium-render] +opt-level = 3 diff --git a/README.md b/README.md index 155c0bda..69e8734c 100644 --- a/README.md +++ b/README.md @@ -300,26 +300,30 @@ Download the APK from [Releases](https://github.com/OpenAEC-Foundation/OpenPDFSt ### Prerequisites - [Node.js](https://nodejs.org/) 20+ - [Rust](https://www.rust-lang.org/tools/install) (stable) +- [CMake](https://cmake.org/download/) and a C/C++ toolchain - System dependencies: - **Linux:** `libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf` - - **macOS:** Xcode Command Line Tools + - **macOS:** Xcode Command Line Tools; universal release builds also need the `aarch64-apple-darwin` and `x86_64-apple-darwin` Rust targets - **Windows:** Visual Studio Build Tools with C++ workload ### Build ```bash cd open-pdf-studio -npm install +npm ci npx tauri build ``` -The built application will be in `open-pdf-studio/src-tauri/target/release/bundle/`. +The build automatically downloads the pinned macOS PDFium runtime and verifies +its SHA-256 checksum. Build artifacts are written to the workspace-level +`target/release/bundle/` directory, or `target/universal-apple-darwin/release/bundle/` +for a universal macOS build. ### Development ```bash cd open-pdf-studio -npm install +npm ci npx tauri dev ``` diff --git a/docs/superpowers/specs/2026-07-15-macos-release-quality-design.md b/docs/superpowers/specs/2026-07-15-macos-release-quality-design.md new file mode 100644 index 00000000..3a53e3c3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-15-macos-release-quality-design.md @@ -0,0 +1,185 @@ +# Ontwerp: macOS-releasekwaliteit voor versie 1.78 + +Status: goedgekeurd ontwerp, nog niet geïmplementeerd + +Doelbranch: `codex/mac-quality-178`, gebaseerd op `origin/main` + +Gerelateerde GitHub-items: PR #251 en issues #291, #208, #252 en #276 + +## Aanleiding + +De huidige `main` bevat al meerdere verbeteringen die na de basis van PR #251 zijn toegevoegd: platformcorrecte sidecar-namen, een gebundelde macOS-PDFium-library, Developer ID-signing, notarisatie en een recentere `tao`-versie. PR #251 rechtstreeks mergen zou deze nieuwere oplossingen deels vervangen door oudere buildlogica. De PR roept onder andere Cargo opnieuw aan vanuit `build.rs`, terwijl het huidige buildscript dit bewust vermijdt wegens de target-lock van de bovenliggende Cargo-build. + +De resterende problemen zijn kleiner en duidelijk af te bakenen: + +- een verse macOS-clone mist de genegeerde `libpdfium.dylib`, waardoor lokale dev- en releasebuilds zonder handmatige download falen; +- het hoofdvenster is aanvankelijk verborgen en wordt pas later door de frontend getoond, een patroon dat op macOS 26 eerder tot een onzichtbaar of onresponsief venster leidde; +- de bestaande releasechecks bewijzen nog niet dat de gebouwde app werkelijk start, een zichtbaar venster heeft en na initialisatie blijft draaien; +- aangeleverde 1.67-crashrapporten tonen `SIGABRT` door een Rust-panic in de macOS-launchcallback. Ze tonen geen `CODESIGNING`-termination. De huidige signing- en runtimewijzigingen moeten daarom met een echte macOS-26-starttest worden bewezen; +- een aangeleverde verificatie-PDF bevat een beeldconstructie die op de laatste pagina niet in de app verschijnt. Hiervoor ontbreekt een kleine, herhaalbare regressiefixture; +- diagnostiek bij startup-problemen bevat onvoldoende fase-informatie om build-, native-library-, venster- en frontendproblemen snel te onderscheiden. + +## Doelen + +1. `npm run tauri:dev` en `npx tauri build` werken op een verse macOS-clone zonder handmatige native-library-stap. +2. Lokale builds en CI gebruiken exact dezelfde gepinde native dependency en checksumcontrole. +3. De hoofdwindow wordt zichtbaar aangemaakt en is niet afhankelijk van een latere frontend-`show()` om bruikbaar te worden. +4. Een GitHub Actions-job op macOS 26 bouwt de universal app, controleert de inhoud en voert een echte start-smoketest uit. +5. Nightly- en releasebuilds controleren signing, notarisatie en stapling voordat een artifact als bruikbaar geldt. +6. Startupdiagnostiek registreert lokale, privacyvriendelijke fasemarkers en foutketens zonder documentinhoud of volledige documentpaden. +7. Het ontbrekende-beeldprobleem krijgt een minimale, niet-vertrouwelijke regressiefixture en een automatische renderassertie. +8. Versievelden worden gezamenlijk verhoogd van 1.77.0 naar 1.78.0. +9. Windows- en Linux-releasegedrag blijft intact, inclusief de Windows-sidecar-signing, WebView2-bootstrapper en `WebView2Loader.dll`. + +## Niet-doelen + +- PR #251 integraal rebasen of de duizenden gegenereerde bestanden uit die PR in deze releaseopschoning meenemen. +- Native libraries voor alle platformen opnieuw organiseren. +- De PDFium-versie boven de huidige, al gevalideerde release verhogen. +- Automatische crashtelemetrie of stille verzending van gebruikersdata toevoegen. +- De originele aangeleverde verificatie-PDF in de repository opnemen. +- Android-signing wijzigen. APK- en AAB-signing blijven een afzonderlijke releasewerkstroom met één blijvende productiesleutel. + +## Overwogen aanpakken + +### 1. Gerichte integratie op actuele `main` - gekozen + +Alleen ontbrekende macOS-build-, startup-, diagnostiek- en regressietestonderdelen worden toegevoegd. Bestaande platformfixes blijven behouden. Dit geeft de kleinste reviewbare wijziging en de laagste regressiekans. + +### 2. Universele macOS-library in Git committen + +Dit maakt een verse clone direct bouwbaar en is eenvoudig, maar vergroot iedere clone met ongeveer 14 MB en maakt dependency-updates minder controleerbaar. De al bestaande Linux- en Windows-binaries worden in deze wijziging niet als precedent gebruikt om nog een binary toe te voegen. + +### 3. PR #251 rechtstreeks mergen + +Niet gekozen. De PR is gebaseerd op een oude `main`, bevat inmiddels achterhaalde platformfixes, gebruikt een oudere native-libraryrelease, heeft een onbetrouwbaar pad voor universal builds en verwijdert relevante Windows-signingstappen. + +## Ontwerp + +### Native dependencyvoorbereiding + +Er komt één klein Node-script onder `open-pdf-studio/scripts/` met twee verantwoordelijkheden: + +1. op macOS bepalen of `src-tauri/binaries/macos-universal/libpdfium.dylib` met de verwachte metadata aanwezig is; +2. bij ontbreken de gepinde universal archive downloaden, SHA-256 controleren, alleen het vereiste bestand uitpakken en het resultaat atomair op zijn definitieve plaats zetten. + +De URL, upstreamversie, archive-SHA-256, archivepad en lokale doelnaam staan bij elkaar in een afzonderlijk, importeerbaar manifest. Het script gebruikt een tijdelijke map binnen de buildomgeving en hernoemt pas na succesvolle checksum- en bestandstypecontrole. Na installatie schrijft het atomair een klein markerbestand met versie en geverifieerde archivehash. Alleen de combinatie van dylib en overeenkomende marker geldt als geldige cache; een los of verouderd bestand wordt opnieuw voorbereid. Een mislukte download, checksumafwijking of ontbrekend archive-element levert een korte fout met hersteladvies en een niet-nul exitcode op. Een reeds geldige cache veroorzaakt geen netwerkverkeer. + +`predev` en `prebuild` roepen dit script aan vóór de bestaande worker-build. De macOS-workflow gebruikt dezelfde npm-route; de dubbele handgeschreven `curl`/`tar`-stap verdwijnt. Windows en Linux krijgen een expliciete, geteste no-op en behouden hun huidige binaries en releasepaden. + +Er komt geen Cargo-aanroep in `build.rs`. Dat script blijft uitsluitend de reeds gebouwde sidecar naar de Tauri-naam kopiëren. + +### Vensterstart + +Het hoofdvenster wordt zichtbaar aangemaakt. De bestaande frontend-call die het venster na de eerste paint toont, wordt verwijderd of idempotent gemaakt zodat hij geen noodzakelijke startupstap meer is. De neutrale bestaande achtergrondkleur blijft zichtbaar tijdens de korte frontendinitialisatie, zodat dit geen zwart of transparant startvenster oplevert. + +De startupcode legt de volgende lokale fasen vast: + +- proces gestart; +- Tauri-builder aangemaakt; +- setup-hook gestart; +- resource-directory gevonden; +- PDFium geladen of gecontroleerd gedegradeerd; +- hoofdvenster aanwezig en zichtbaar; +- frontend gereed. + +De frontend meldt gereedheid één keer via een smalle Tauri-command nadat de eerste render is voltooid. Rust logt daarop de fase `frontend-ready`; dezelfde marker is het synchronisatiepunt voor de start-smoketest. Een fout in een optionele renderdependency blijft niet-fataal. Een fout die starten onmogelijk maakt, bevat de laatste voltooide startupfase en de volledige Rust-foutketen. + +### Lokale diagnostiek + +Diagnostiek wordt lokaal opgeslagen in de Tauri-logdirectory met rotatie en een kleine maximale bewaartermijn. Standaard bevat het rapport: + +- appversie en buildtype; +- besturingssysteem en CPU-architectuur; +- startupfasen en tijden; +- aanwezigheid en laadresultaat van de native renderlibrary en worker; +- panic- en foutketeninformatie; +- signingstatus die de app zelf veilig kan waarnemen. + +Documentinhoud, annotaties en volledige bestandspaden worden niet opgeslagen. Als een pad nodig is voor diagnose, wordt alleen de extensie en een eenrichtingshash opgenomen. Export is uitsluitend een bewuste gebruikersactie via Help en produceert een leesbaar tekstbestand of zip met logbestanden en een korte privacyverklaring. + +### PDF-renderregressie + +De aangeleverde verificatie-PDF wordt alleen lokaal gebruikt om de onderliggende objectconstructie te bepalen. Daarna wordt een minimale synthetische PDF-fixture gemaakt met uitsluitend die constructie en niet met de originele tekst, afbeeldingen of metadata. + +De regressietest rendert de relevante pagina en controleert minimaal: + +- dat het verwachte beeldgebied niet volledig wit of transparant is; +- dat het aantal niet-achtergrondpixels boven een stabiele ondergrens ligt; +- dat de fixture zonder panic of ontbrekende-resourcefout opent; +- dat de bestaande renderregressies niet veranderen buiten een kleine, vastgelegde tolerantie. + +Als de oorzaak in Form XObjects, masks, clipping, optionele content of resource-overerving ligt, wordt de test op het kleinste relevante PDF-objectniveau gehouden. Daardoor blijft de fixture klein en verklaart de test precies welke constructie wordt ondersteund. + +### macOS-26 CI en releasecontrole + +Een gerichte PR-job draait op de officiële Apple Silicon `macos-26` runner wanneer native build-, Tauri-, render- of workflowbestanden wijzigen. De job: + +1. installeert Node en Rust met beide Apple-targets; +2. voert de gedeelde native-dependencyvoorbereiding uit; +3. bouwt beide workerarchitecturen en de universal app; +4. controleert met platformtools dat hoofdprogramma, worker en PDFium-library de vereiste architecturen bevatten; +5. controleert dat de gebundelde resourcepaden bestaan; +6. gebruikt voor de PR-smoketest een expliciete ad-hoc signature; +7. start de `.app`, wacht op de frontend-readymarker, verifieert procesoverleving en controleert via een klein Swift/CoreGraphics-hulpprogramma dat minstens één on-screen hoofdvenster van de bundle bestaat; +8. sluit de app gecontroleerd af en faalt bij een nieuw crashrapport. + +Nightly en release blijven de huidige Developer ID-omgeving gebruiken. Direct na bundling controleren zij daarnaast: + +- `codesign --verify --deep --strict` op de `.app`; +- de verwachte signing identity en hardened runtime; +- Gatekeeper-beoordeling; +- stapling/notarisatiestatus van app en DMG; +- de universal architecturen en vereiste resources; +- dezelfde start-smoketest op de gebouwde app. + +De huidige Tauri-interface blijft leidend: `APPLE_CERTIFICATE`, `APPLE_CERTIFICATE_PASSWORD` en `APPLE_SIGNING_IDENTITY` voor signing; `APPLE_ID`, `APPLE_PASSWORD` en `APPLE_TEAM_ID` voor notarisatie. Geheimen worden nooit geprint of beschikbaar gemaakt aan fork-PR's. + +### Bouwdocumentatie + +De README noemt Node 20+, Rust stable, CMake en Xcode Command Line Tools als macOS-prerequisites. De bouwinstructies leggen uit dat native renderlibraries automatisch en checksum-gecontroleerd worden voorbereid, waar de lokale cache staat en hoe een ontwikkelaar die cache veilig opnieuw laat opbouwen. De documentatie bevat geen handmatige downloadstap en geen verwijzing naar een gebruikersspecifiek bestandspad. + +### Kwaliteitsgrenzen + +De integratie-PR blijft beperkt tot bovengenoemde onderdelen. Verwijdering van omvangrijke historische buildartefacten wordt een afzonderlijke onderhouds-PR. Voor gewijzigde code zijn de volgende checks verplicht: + +- Rust-formattering, Clippy op de gewijzigde workspacecrates en gerichte Rust-tests; +- TypeScript-typecheck en bestaande frontendbuild; +- Node-tests voor platformselectie, geldige cache, checksumfout en atomische installatie; +- regressietest voor de synthetische beeldfixture; +- configuratieasserties voor WebView2, platformresources, sidecars en vensterzichtbaarheid; +- macOS-26 build- en startsmoke. + +## Foutafhandeling en herstel + +- Een native downloadfout laat bestaande geldige bestanden ongemoeid. +- Een checksumfout verwijdert uitsluitend de tijdelijke download en meldt de verwachte en ontvangen hash. +- Een ontbrekende PDFium-library laat de app starten in gedegradeerde modus, maar laat build- en releasechecks falen. +- Een ontbrekende sidecar schakelt de workerpool lokaal uit met een duidelijke melding; releasechecks beschouwen dit als fout. +- Een mislukte notarisatie of stapling houdt nightly/release tegen. +- Een startupcrash bewaart de laatst voltooide startupfase en leidt tot een mislukte smoketest. + +## Versie en GitHub-afhandeling + +Alle gezaghebbende versievelden gaan gezamenlijk naar 1.78.0. Na groene lokale checks en groene PR-CI wordt de gerichte integratie-PR gemerged. PR #251 krijgt een onderhoudersreactie die aangeeft welke ideeën al op `main` stonden, welke gericht zijn overgenomen en waarom de PR niet integraal is gemerged; relevante bijdrage-attributie blijft behouden. + +Issues worden alleen gesloten met bewijs. Als code of workflowlogica aantoonbaar uit PR #251 wordt aangepast, krijgt de bijbehorende commit een `Co-authored-by`-regel; anders blijft de bijdrage zichtbaar via de afsluitende PR-reactie en een link vanuit de integratie-PR. + +De issuecriteria zijn: + +- #291 na een groene build vanuit een schone macOS-omgeving zonder handmatige download; +- #208 na een macOS-26-test die een zichtbaar, interactief venster bevestigt; +- #252 en #276 na een gesigneerde en genotariseerde 1.78-nightly die op macOS 26 start zonder nieuw crashrapport. + +## Acceptatiecriteria + +- Een schone macOS-clone doorloopt `npm install` en `npm run tauri:dev` zonder handmatige PDFium-stap. +- `npx tauri build --target universal-apple-darwin` produceert een universal app en DMG met de juiste worker en renderlibrary. +- De app toont op macOS 26 een hoofdvenster, blijft minimaal tien seconden draaien en sluit gecontroleerd af. +- PR-builds gebruiken geen productiesigninggeheimen; nightly/release zijn geldig gesigneerd, genotariseerd en gestapled. +- De synthetische beeldfixture rendert het bedoelde beeldgebied en faalt vóór de bijbehorende rendererfix. +- Startupdiagnostiek bevat fase en foutketen, maar geen documentinhoud of volledige documentpaden. +- Windows-config bevat nog steeds `embedBootstrapper` en `WebView2Loader.dll`; de Windows-sidecar wordt nog vóór bundling gesigneerd. +- Linux-build- en renderchecks blijven groen. +- Alle versievelden zijn 1.78.0 en onderling consistent. +- PR #251 en de vier Mac-issues zijn met toetsbaar bewijs bijgewerkt. diff --git a/open-pdf-render/src/image_decode.rs b/open-pdf-render/src/image_decode.rs deleted file mode 100644 index a6a13d73..00000000 --- a/open-pdf-render/src/image_decode.rs +++ /dev/null @@ -1 +0,0 @@ -pub fn decode_image() {} diff --git a/open-pdf-render/src/interpreter.rs b/open-pdf-render/src/interpreter.rs index 8d4a2b5d..c4c23d3f 100644 --- a/open-pdf-render/src/interpreter.rs +++ b/open-pdf-render/src/interpreter.rs @@ -1252,7 +1252,7 @@ impl Interpreter { let img = image::load_from_memory_with_format(jpeg_data, image::ImageFormat::Jpeg).ok()?; let img = img.to_rgba8(); let (w, h) = (img.width(), img.height()); - let mut rgba = img.into_raw(); + let rgba = img.into_raw(); // Downsample if over budget if max_pixels > 0 && w * h > max_pixels { @@ -2608,17 +2608,6 @@ impl Interpreter { } } - fn handle_do_extract( - operands: &[Object], - buf: &mut DrawCommandBuffer, - state: &mut GraphicsStateStack, - doc: &Document, - resources: &Dictionary, - font_registry: &mut crate::fonts::FontRegistry, - ) { - Self::handle_do_extract_with_text(operands, buf, state, doc, resources, font_registry, None); - } - fn handle_do_extract_with_text( operands: &[Object], buf: &mut DrawCommandBuffer, @@ -3348,7 +3337,8 @@ impl Interpreter { /// Walk a content stream and emit one TextSpan per Tj/TJ run. /// Lighter than extract_commands — only the operators that affect text /// position or content are processed; path/color/image ops are skipped. - pub fn extract_text_only( + #[cfg(test)] + fn extract_text_only( content_bytes: &[u8], spans: &mut Vec, state: &mut GraphicsStateStack, @@ -3497,6 +3487,7 @@ impl Interpreter { /// then computes the final user-space bbox by transforming through the /// current CTM. Decodes bytes to text via the font's ToUnicode CMap /// (falls back to Latin-1 / WinAnsi for fonts without ToUnicode). + #[cfg(test)] fn emit_text_span( bytes: &[u8], text_state: &mut TextState, @@ -3612,6 +3603,7 @@ impl Interpreter { } /// Recurse into a Form XObject for text-only extraction. + #[cfg(test)] fn handle_do_text_only( operands: &[Object], spans: &mut Vec, @@ -3673,6 +3665,27 @@ impl Interpreter { mod inline_image_tests { use super::*; + #[test] + fn text_only_extractor_accepts_an_empty_stream() { + let doc = Document::with_version("1.5"); + let resources = Dictionary::new(); + let mut spans = Vec::new(); + let mut state = crate::graphics_state::GraphicsStateStack::new(); + let mut registry = crate::fonts::FontRegistry::new(); + + Interpreter::extract_text_only( + b"", + &mut spans, + &mut state, + &doc, + &resources, + &mut registry, + ) + .expect("empty text stream"); + + assert!(spans.is_empty()); + } + /// Draai extract_commands over een synthetische content-stream en geef de /// rauwe commandbuffer terug (zonder 16-byte pagina-header). fn extract(content: &[u8]) -> Vec { diff --git a/open-pdf-render/src/lib.rs b/open-pdf-render/src/lib.rs index 2ebcbabe..22ed185c 100644 --- a/open-pdf-render/src/lib.rs +++ b/open-pdf-render/src/lib.rs @@ -5,7 +5,6 @@ mod graphics_state; mod interpreter; mod renderer; mod color; -mod image_decode; pub mod draw_commands; pub mod encoding; pub mod font_parser; diff --git a/open-pdf-render/src/tile_render.rs b/open-pdf-render/src/tile_render.rs index cd962e05..5186f4d7 100644 --- a/open-pdf-render/src/tile_render.rs +++ b/open-pdf-render/src/tile_render.rs @@ -24,7 +24,7 @@ use rayon::prelude::*; use tiny_skia::{ - FillRule, LineCap, LineJoin, Paint, PathBuilder, Pixmap, PixmapPaint, Rect, Stroke, + FillRule, LineCap, LineJoin, Paint, PathBuilder, Pixmap, PixmapPaint, Stroke, StrokeDash, Transform, }; diff --git a/open-pdf-studio/index.html b/open-pdf-studio/index.html index 40953213..212a38e0 100644 --- a/open-pdf-studio/index.html +++ b/open-pdf-studio/index.html @@ -14,13 +14,12 @@