Release v0.3.69 — language bindings, registry publishing, regression examples - #777
Merged
Conversation
2067 maturin build artifacts under target-wheel/ (the CARGO_TARGET_DIR used by the python wheel build) were accidentally tracked and merged into main. Add /target-wheel/ to .gitignore (alongside /target/) and remove the files from the index (kept on disk). No source changes. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic header-only C++17 RAII bindings over the C ABI (include/pdf_oxide_c/pdf_oxide.h). Establishes the binding + CI pattern the other v0.3.68 languages reuse. - pdf_oxide::Document: open / open_from_bytes / open_with_password, page_count, version, is_encrypted, has_structure_tree, extract_text, to_plain_text, to_markdown, to_html, to_markdown_all, extract_structured_json. - pdf_oxide::Pdf builder: from_markdown / from_html / from_text, save, save_to_bytes. C strings/buffers freed via free_string; errors -> Error. - CMake build (links the default-feature cdylib, NOT the python wheel), runnable example (asserted), tests/test_api_coverage.cpp (one check per method), README, .clang-format. - CI: .github/workflows/cpp.yml (Linux+macOS): build cdylib -> cmake -> ctest -> run example with assertion -> clang-format. Same verification set as the other bindings. Verified locally: cmake build + ctest green (api-coverage 100% pass) against a freshly built default-feature cdylib. Editor/builder/OCR/signatures/barcodes/ search/fonts/images/annotations/tables coverage to follow. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Dart bindings over the C ABI via dart:ffi (no C shim). Loads the default-feature cdylib at runtime; NativeFinalizer + close() free handles; free_string frees returned strings/buffers; errors -> PdfOxideError. - PdfDocument: open / openFromBytes / openWithPassword, pageCount, version, isEncrypted, hasStructureTree, extractText, toPlainText, toMarkdown, toHtml, toMarkdownAll, extractStructuredJson, close. - Pdf builder: fromMarkdown / fromHtml / fromText, save, saveToBytes, close. - example/basic_extraction.dart (asserted in CI), test/api_coverage_test.dart (one test per method), README, pubspec. - CI: .github/workflows/dart.yml (Linux+macOS): build cdylib -> pub get -> analyze -> format check -> dart test -> run example with assertion. Verified locally (Dart 3.11.5): dart analyze clean, dart format clean, `dart test` 16/16 pass against the default-feature cdylib. Editor/builder/OCR/ signatures/barcodes/search/fonts/images coverage to follow. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…b/ rule) The Python-artifacts `lib/` ignore rule also matched dart/lib/, so the Dart binding's main wrapper file was omitted from the previous commit. Add a `!dart/lib/` exception (alongside go/lib and ruby/lib) and commit the wrapper. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic R package (pdfoxide) wrapping the C ABI via a small C shim and R's .Call interface. Handles are external pointers freed by the GC; returned strings/buffers copied into R and freed via free_string; C-ABI errors raised as R errors. Page indices 0-based. - Document: pdf_open / pdf_open_bytes (+ password), pdf_page_count, pdf_version, pdf_is_encrypted, pdf_has_structure_tree, pdf_extract_text, pdf_to_plain_text, pdf_to_markdown, pdf_to_html, pdf_to_markdown_all, pdf_extract_structured_json. - Builder: pdf_from_markdown / pdf_from_html / pdf_from_text, pdf_save, pdf_save_to_bytes. - inst/examples/basic_extraction.R (asserted in CI), inst/tinytest api-coverage (one test per function), DESCRIPTION/NAMESPACE/Makevars, README. - CI: .github/workflows/r.yml (Linux+macOS): build cdylib -> R CMD INSTALL -> tinytest -> run example with assertion. Verified locally (R 4.5.3): R CMD INSTALL clean, tinytest 15/15 pass against the default-feature cdylib. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Kotlin/JVM (Android-ready) via JNA — pure Kotlin, no native compile. JNA loads libpdf_oxide; AutoCloseable handles; free_string for returned strings/buffers; errors -> PdfOxideException; suspending coroutine extensions on Dispatchers.Default. KGP 2.2.20 (Gradle 9 + JDK 25 compat); jvmToolchain 17. ApiCoverageTest (one test per method incl. coroutine helpers) green locally. Julia via ccall (direct, no shim). Finalizer-freed handles; free_string; errors -> PdfOxideError. test/runtests.jl api-coverage; example asserted in CI. Both: wrapper + example (asserted) + api-coverage test + CI workflow (kotlin.yml / julia.yml, Linux+macOS, same verification set) + README. Also: author -> "Yury Fedoseev" in r/DESCRIPTION and julia/Project.toml. Kotlin verified locally (gradle test BUILD SUCCESSFUL, all coverage tests pass). Julia CI-verified (toolchain not installed locally). Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Zig bindings over the C ABI via @cImport of the cbindgen header (no shim). Document/Pdf structs with deinit; returned strings/buffers copied into a caller allocator and the C buffer freed via free_string; C-ABI errors map to error.PdfOxide. Pinned to Zig 0.14.0 (pre-1.0 API drift). - build.zig (links default-feature cdylib), build.zig.zon, lib/pdf_oxide.zig (wrapper + api-coverage tests, one per method), examples/basic_extraction.zig (asserted in CI), README. - CI: .github/workflows/zig.yml (Linux+macOS, pinned Zig): build cdylib -> zig build test -> zig build example with assertion. Same verification set. Zig toolchain not installed locally -> CI-verified. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
… rule) The Python-artifacts `lib/` ignore rule also matched zig/lib/ (same as the earlier dart/lib fix), omitting the Zig wrapper from the previous commit. Add a `!zig/lib/` exception and commit the wrapper. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Scala 3 bindings over the C ABI via JNA (same proven mechanism as the Kotlin binding). AutoCloseable handles (Using-friendly); free_string for returned strings/buffers; errors -> PdfOxideException; case-class PdfVersion. - src/main/scala/fyi/oxide/pdf/PdfOxide.scala (PdfDocument, Pdf), example (asserted in CI), ApiCoverageSpec (ScalaTest, one test per method), build.sbt, README. - CI: .github/workflows/scala.yml (Linux+macOS): build cdylib -> JDK 17 + sbt -> sbt test -> run example with assertion. Same verification set. Scala/sbt not installed locally -> CI-verified. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Clojure bindings over the C ABI via JNA (same mechanism as
Kotlin/Scala). Document/Pdf are Closeable deftypes (with-open friendly);
returned strings/buffers freed via free_string; C-ABI errors -> ex-info {:code}.
- src/pdf_oxide/core.clj (open/open-bytes/page-count/version/extract-text/
to-markdown/to-html/to-markdown-all/extract-structured-json + builder
from-markdown/from-html/from-text/save/save-to-bytes), example (asserted in
CI), test/pdf_oxide/core_test.clj (clojure.test, one test per fn), deps.edn,
README.
- CI: .github/workflows/clojure.yml (Linux+macOS): build cdylib -> JDK 17 +
Clojure CLI -> clojure -M:test -> run example with assertion.
Clojure CLI not installed locally -> CI-verified.
Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Objective-C bindings over the C ABI (header is C-callable from ObjC). POXDocument/POXPdf NSObject wrappers own the handles, free in -dealloc (ARC); returned strings/buffers -> NSString/NSData freed via free_string; C-ABI errors -> NSError (POXErrorDomain). - include/POXPdfOxide.h + src/POXPdfOxide.m (open/openData/openPassword, pageCount, getVersion, isEncrypted, hasStructureTree, extractText/toPlainText/ toMarkdown/toHtml/toMarkdownAll/extractStructuredJson + builder fromMarkdown/ fromHtml/fromText/saveToPath/saveToBytes), example (asserted in CI), tests/test_api_coverage.m (one check per method, exit-code), Makefile, README. - CI: .github/workflows/objc.yml (macOS): build cdylib -> make (clang/ARC) -> test_api_coverage -> run example with assertion. ObjC toolchain not local -> CI-verified (macOS runner). Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Swift bindings over the C ABI. CPdfOxide system-library target exposes the cbindgen header via a module map; PdfOxide is the Swift wrapper. Class-owned handles freed in deinit; returned strings/buffers -> String/[UInt8] freed via free_string; C-ABI errors -> PdfOxideError. - Package.swift, Sources/CPdfOxide (module.modulemap + shim.h), Sources/PdfOxide (Document, Pdf), Sources/Example (asserted in CI), Tests/PdfOxideTests (XCTest, one test per method), README. - CI: .github/workflows/swift.yml (macOS): build cdylib -> swift test (api-coverage) -> swift run example with assertion. Same verification set. Swift toolchain not local -> CI-verified (macOS runner). Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Idiomatic Elixir bindings via a NIF over the C ABI. Every CPU-bound text NIF
runs on a dirty CPU scheduler (ERL_NIF_DIRTY_JOB_CPU_BOUND) so it never blocks
the BEAM. Handles are NIF resources freed by the GC; returned strings/buffers ->
binaries freed via free_string; results are {:ok, v}/{:error, code}.
- lib/pdf_oxide.ex (PdfOxide + Document/Pdf structs + Error), lib/pdf_oxide/
native.ex (NIF loader), c_src/pdf_oxide_nif.c (dirty NIF), Makefile
(elixir_make), example (asserted in CI), test/pdf_oxide_test.exs (ExUnit, one
test per function), mix.exs, README.
- CI: .github/workflows/elixir.yml (Linux+macOS): build cdylib -> OTP/Elixir ->
mix compile (builds NIF) -> mix test -> run example with assertion.
Elixir/OTP not installed locally -> CI-verified.
Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…o 0.3.68 Centralize the release version in ONE place (Cargo.toml [package] version) and propagate it everywhere: - scripts/sync_version.py — reads the canonical Cargo.toml version and writes it into every binding manifest + version/parity assert (sub-crates cli/mcp/jni, pyproject, js/wasm package.json, csharp .csproj, java pom version+scm-tag+ header, go fallbackVersion, ruby version.rb + 2 specs, php Pdf.php + installer (x2) + parity test, dart, r, julia, kotlin, scala, zig, cpp, elixir). `--check` verifies (exit 1 on drift), `--set X.Y.Z` bumps the source then syncs. First-match-only by default (so pom.xml dep versions are untouched); `multi` for legitimately-repeated strings. Idempotent. - .github/workflows/version-consistency.yml — CI fails if any binding drifts. - Bumped workspace + every binding 0.3.67 -> 0.3.68 (incl. Cargo.lock, uv.lock). - CHANGELOG: 0.3.68 language-bindings release entry. Verified: sync_version.py --check passes; cargo metadata resolves; full-repo sweep shows no stray 0.3.67. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Every binding now has wrapper + example + api-coverage test + README + CI workflow. (Zig's api-coverage tests are inline test blocks in lib/pdf_oxide.zig per Zig convention.) Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…ig build) The fingerprint's high 32 bits must match the package-name hash; the made-up value was rejected by Zig 0.14. Verified locally: zig build test passes, example prints expected output. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…cal Pkg.test) Julia's ccall forbids a variable function name; the _str_page/_from helpers passed the C symbol as an argument, failing precompile. Generate the page extractors and builders with @eval so each ccall references its C function as a literal symbol. Verified locally: Pkg.test 15/15 pass against the cdylib. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…quality bar) Apply JuliaFormatter and add a format-check step to julia.yml (mirrors the gofmt/rubocop/cs-fixer format gates the existing bindings enforce). Verified locally: format check clean, Pkg.test 15/15 still pass. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Match the existing-binding quality bar (gofmt+golangci-lint, php-cs-fixer+phpstan, spotless+spotbugs): add blocking clang-format checks and, for C++, clang-tidy static analysis (bugprone/performance/modernize/readability, WarningsAsErrors). Applied clang-format to all C++/ObjC sources; added cpp/.clang-tidy and objc/.clang-format. Verified locally: clang-format clean, clang-tidy exit 0, C++ rebuild + ctest pass after reformat. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Match the binding quality bar: add .formatter.exs, Credo (.credo.exs + dep), and CI steps for mix format --check-formatted, mix credo --strict, and compile --warnings-as-errors. Verified locally: NIF compiles clean (warnings-as-errors), 8/8 tests pass, format clean, credo no issues. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Local clojure -M:test caught two real bugs: (1) definterface can't extend com.sun.jna.Library (JNA rejected it) -> rewrote to JNA NativeLibrary + Function direct invocation (no interface); (2) deftype mutable fields aren't readable externally -> store the handle in an atom field (Closeable resets it). Also add clj-kondo lint (config-free, 0 warnings) to clojure.yml — matches the binding quality bar. Verified locally: 15 assertions pass, clj-kondo clean. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Add .scalafmt.conf + sbt-scalafmt plugin and a scalafmtCheckAll CI gate. Local sbt run caught a scalafmt/Scala-3 significant-indentation pitfall (the Native_ object body got de-indented) — converted that object to braces. Verified locally: scalafmt check clean, 8/8 ScalaTest pass against the cdylib. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
Apply ktlint (autofix) and add an .editorconfig that disables only function-naming + class-naming (the JNA CLib interface must use snake_case to map C symbols; the Native_ object mirrors the C namespace) — all other ktlint standard rules enforced. CI installs ktlint 1.8.0 and runs the check before tests. Verified locally: ktlint clean, gradle test BUILD SUCCESSFUL. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…rity hardening Bring every new binding's CI to the existing-binding quality bar (format check + lint/static analysis), and harden the workflows: - R: add lintr (r/.lintr) + lint step in r.yml. Verified locally: "No lints found". - Swift: add .swift-format + `swift format lint --strict` step (macOS CI; the local brew Swift has a glibc incompatibility so Swift is CI-verified). - Security (flagged by commit review on kotlin.yml): the ktlint download is now SHA256-pinned + verified (sha256sum -c) instead of an unverified curl. - Least privilege: add `permissions: contents: read` to all 12 new binding/ version workflows. Lint/format gate now per binding: cpp clang-format+clang-tidy, dart analyze+ format, r lintr, julia JuliaFormatter, zig fmt, scala scalafmt, clojure clj-kondo, objc clang-format, swift swift-format, kotlin ktlint, elixir mix format+credo+warnings-as-errors. All verified locally except swift/objc (macOS-only). Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…tion + Julia Aqua From the 2026 SOTA tooling research workflow (adversarially verified): - R (real defect): useDynLib(.registration = TRUE) had NO R_registerRoutines / R_init_pdfoxide in the C shim — registration was absent (R CMD check NOTE). Add an R_CallMethodDef table + R_init_pdfoxide (R_useDynamicSymbols FALSE, R_forceSymbols TRUE); NAMESPACE .fixes = "C_"; switch all 18 .Call sites to the registered symbol objects (C_*). Verified locally: reinstall + tinytest 15/15 pass. - Julia (real gap): Project.toml only bounded julia, not Test. Add [compat] for Aqua + Test and an Aqua.test_all(PdfOxide; persistent_tasks=false) testset (stale-deps / compat-coverage / undefined-exports / piracy QA). Verified locally: Aqua 10/10 + api coverage pass. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…es (all bindings) The C++ ASan/UBSan sanitizer lane (added per the SOTA research) caught a heap-buffer-overflow: save_to_bytes freed the raw byte buffer returned by pdf_save_to_bytes with free_string(), which runs strlen() on a buffer that is NOT NUL-terminated → read past the allocation. The C header explicitly mandates free_bytes() for uint8_t* buffers. This latent bug was present in EVERY binding's save_to_bytes (tests passed without a sanitizer because the over-read was usually benign). Fixed in all 11: C++, Dart (added free_bytes ffi binding), R, Julia, Zig, Scala (+CLib decl), Kotlin (+CLib decl), Clojure (+helper), Objective-C, Elixir, Swift. Re-verified locally after the fix: C++ ASan clean, Dart 16/16, R 15/15, Julia Aqua+15, Zig, Scala 8/8, Kotlin, Clojure 15/15, Elixir 8/8. ObjC/Swift CI-only. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…OTA research) Top-rated findings from the 2026 tooling research workflow (high value/low risk): - cpp.yml: a Linux ASan+UBSan job that compiles the api-coverage test with -fsanitize=address,undefined and runs it — this is what caught the free_bytes heap-overflow. Memory/lifetime coverage at the C-ABI boundary that clang-tidy can't provide. - objc.yml: Clang Static Analyzer via `scan-build --status-bugs make build` (exit 1 on any potential bug) — zero new deps, high value for the ARC/pointer wrapper layer. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…s gen SOTA research recommended R CMD check as the R gate. Running it locally caught a real defect: DESCRIPTION lacked Author/Maintainer (Authors@R alone didn't expand under R CMD check). Fixed. Plus: - .Rbuildignore for dev files (.lintr, .Rcheck, build artifacts). - @useDynLib gains .fixes = "C_" so roxygen regenerates the symbol-based NAMESPACE. - r.yml: install roxygen2/rcmdcheck, generate man/ docs (roxygenise), then rcmdcheck(error_on="warning") + lintr + tinytest + example. (Air formatter deferred — pending a SHA-pinned setup action.) Verified locally: DESCRIPTION ERROR cleared (R CMD check), tinytest 15/15. man/ docs generated in CI (local roxygen2 install is broken on this box — pkgload C-compile failure, environment not code). Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
…ned toolchain) Workflow research flagged the 0.14-only pin as outdated (0.15.x is current stable in 2026). Migrated to Zig 0.15.1: - build.zig: new module-based API (b.createModule + addTest/addExecutable with .root_module; include/link settings moved onto Module). - example: Zig 0.15 "Writergate" — stdout via std.fs.File.stdout().writer(&buf) + .interface + flush(); page_allocator (GeneralPurposeAllocator renamed). - build.zig.zon minimum_zig_version 0.14.0 → 0.15.0; CI setup-zig pinned 0.15.1; README updated. Verified locally against Zig 0.15.1: zig build test passes, example prints expected output, zig fmt --check clean. Claude-Session: https://claude.ai/code/session_01XhP8c4j3bAPsXKme22WX4Q
- clojure.yml: the pinned DeLaGuardo/setup-clojure SHA did not exist in the action repo (jobs failed at 'Set up job'); repin to the real 13.4 commit. - cpp.yml: clang-format is apt-installed on Linux only, but the format-check step ran on all OSes -> exit 127 on macOS. Gate it to Linux (matches the Linux-only clang-tidy step; formatting is platform-independent).
- cpp/pdf_oxide.hpp: drop no-op std::move on trivially-copyable Path (performance-move-const-arg); make discarded unique_ptr::release() an explicit assignment (bugprone-unused-return-value). Verified clang-tidy + clang-format clean locally. - objc examples: clang-format the new example sources (were unformatted). - objc.yml: install clang-format on the macOS runner (was exiting 127).
roxygen2 (and other deps) were silently failing to compile from source on
the Ubuntu runner, leaving them absent for the 'Generate man/ docs' step
('there is no package called roxygen2'). Enable use-public-rspm for prebuilt
binaries and verify all packages installed (install.packages only warns).
PdfOxide.swift and ApiCoverageTests.swift had never been run through swift-format; the swift.yml strict format check flagged 265 violations on first CI run. Auto-formatted with swift-format 6.0.3 and moved 3 over-long trailing comments to their own lines. Verified 0 violations locally.
PHPStan 2.2.2 flags assertNotNull() on a non-null FFI\CData as always-true (the FFI stub types the render handle as non-null). Assert the instance type instead — meaningful and version-robust. Verified: phpstan 2.2.2 reports 'No errors'. (Pre-existing; surfaced by the unpinned phpstan minor bump.)
…s, UselessAssignment) RuboCop 1.88.0 flagged 3 pre-existing offenses (surfaced by the unpinned rubocop minor bump): use preserve.zero?, omit ternary-condition parens, and drop a redundant 'str_ptrs = []' pre-init (the functional str_ptrs that anchors the FFI string pointers is unaffected). Verified: no offenses.
- ruby: my earlier rubocop autocorrect changed 'preserve == 0' to 'preserve.zero?', which raises NoMethodError on a Boolean arg (true.zero?). Use [false,nil,0].include?(preserve) — type-safe AND rubocop-clean. Verified: rubocop clean + phase4 spec passes. - cpp tests: 4 intentional empty catches (tolerating optional-feature errors) tripped bugprone-empty-catch on first CI run; suppress with NOLINT/NOLINTNEXTLINE. Verified clang-tidy + clang-format clean. - r/.lintr: disable object_usage_linter — it false-positives on the useDynLib(.registration) native symbols (C_r_*).
The README still advertised only the original ~7 bindings ('Java is the 8th
binding', Ruby/PHP/Swift as 'roadmap'). Refresh the title, add a v0.3.69
callout for the 11 new bindings, list all 19 bindings + install snippets in
'Other languages' (incl. Ruby/PHP which already shipped), and update the
feature line, citation, and footer. Links to docs/RELEASING-bindings.md.
erlef/setup-beam failed on macos-latest (now macos26): 'failed to map a target OS from ImageOS macos26'. Pin to macos-15 until setup-beam supports the newer image.
uv.lock still pinned the editable pdf-oxide package at 0.3.68 (sync_version didn't track it). Regenerated via 'uv lock' and added a sync_version rule so the Version Consistency CI catches uv.lock drift on future bumps.
Version-numbered test/binary names (coverage_v0368, binding_parity_v0350/ v0351, api_coverage_phase4, debug_v0356, v0365_targets_and_locks, phase1-xfa-forms, test_phase2_results) read as noise and don't say what they test. Rename by subject: - *_v0368 / phase4 coverage -> api_coverage_extended - binding/purego parity v0350 -> *_sanitize_sign (doc sanitization + PAdES reader) - binding/purego parity v0351 -> *_ocr_models (OCR model provisioning) - debug_v0356 -> debug_extract_text - v0365_targets_and_locks -> regression_targets_and_locks - phase1-xfa-forms -> xfa-forms - test_phase2_results -> check_span_spacing Renames the C#/Go/JS internal symbols to match and de-versions the header comments. Pure renames — no test logic changed; filenames keep their test-runner patterns; no references elsewhere (grep-verified).
- r/.lintr: disable indentation_linter (opinionated; flags the conventional structure(\n list(...)) 2-space style); split an over-100-char roxygen line into one @PARAM per line (also fixes params that weren't documented). - zig.yml: pin macos-15 — Zig 0.15.1's build runner fails to link against the macos26 SDK (macos-latest advanced; undefined libSystem symbols).
The Swift wrapper never compiled — swift.yml's build never ran on a real macOS runner, so a systematic set of C-ABI type mismatches went unnoticed: - 43× Int->UInt: every size_t/UInt C arg was passed a Swift Int (.count, page indices, etc.); wrapped each in UInt(...). - 1× OpaquePointer: pdf_document_get_dss returns UnsafeMutableRawPointer but Dss.init takes OpaquePointer -> Dss(OpaquePointer(h)). - Removed 6 dead 'try' on rethrows withByteArrayArray calls (no nested throw). Verified with swift 6.0.3: 'swift build' = Build complete (0 errors/warnings), 'swift format lint' = 0 violations.
…FIPS wheel) - lint: sort imports + ruff-format scripts/sync_version.py (I001) - R: split packed `@param`/`@return` roxygen tags onto their own lines — roxygen2 only parses a tag at line start, so trailing tags became description text and left ~50 functions' args "undocumented" (R CMD check WARNING, error_on="warning"). Fixes both R on ubuntu + macos. - Swift: pass the C header dir to the Clang module importer via `-Xcc -I` (swiftc's plain `-I` is a Swift-module path only), on every target that imports PdfOxide → fixes "pdf_oxide_c/pdf_oxide.h not found". - ObjC: install + invoke scan-build from the keg-only `llvm` formula (not on the macOS runner PATH) → fixes "scan-build: command not found". - FIPS python wheel: bump the cargo cache key to v2 so the stale macOS-26-SDK aws-lc-fips crypto dylib is rebuilt under MACOSX_DEPLOYMENT_TARGET=11.0 → fixes delocate's 11.0-floor rejection.
…ngs)
- lint: ruff-format cpp/conanfile.py (ruff format --check . runs repo-wide;
the prior commit only covered scripts/).
- ObjC scan-build (now that it actually runs): fix the two analyzer bugs it
surfaced.
* POXPdfOxide.m pageTable: guard `cells` on (nRows && nCols) — the exact
conditions under which the fill loops run — so the analyzer can prove it
non-NULL (decoupled `nCells` left a spurious null path). Behaviour-identical.
* test_api_coverage.m: the bogus cert loaders always yield nil, so the
signing calls deliberately pass nil to a `nonnull` `certificate:` to test
graceful failure. Exclude that block from the analyzer via
`#ifndef __clang_analyzer__`; it still builds and runs.
(The Python 3.8 Windows failure was a transient `curl`/schannel error fetching
the flate2 crate — a fresh run re-attempts the download.)
…e crash The binding build/config fixes unblocked these api-coverage tests, which now RUN for the first time and surface two pre-existing issues: - Swift `testPdfPageCount`: `XCTAssertGreaterThanOrEqual(try pdf.pageCount(), 1)` swallows the thrown PdfOxideError and records its own failure, so the `expectReturnOrPdfError` wrapper (which is meant to tolerate code-1 on a freshly-built Pdf) never sees it. Pull the `try` out of the assert so the throw propagates to the wrapper. (52 tests, was the only failure.) - ObjC `test_api_coverage` segfaults (exit 139) on first run. Add a SIGSEGV/SIGABRT/SIGBUS backtrace handler + per-section `g_phase` markers so the next CI run reports which API faults instead of a bare 139. Diagnostic only — kept as a permanent aid; the actual fix follows once the frame is known.
The crash diagnostic localised the ObjC `test_api_coverage` segfault to `-[POXDocument pageCountAliasError:]`, which called the Pdf-*builder* C ABI `pdf_get_page_count((Pdf*)_handle, …)` with a `PdfDocument*` handle. Opaque handles are indistinguishable to the C ABI, so it read the PdfDocument as a `Pdf` and dereferenced garbage → SIGSEGV. The method was redundant as well as wrong: `pdf_get_page_count` is already covered correctly via `POXPdf.pageCountError:` (a real `Pdf*`), and document page count via `POXDocument.pageCountError:` (`pdf_document_get_page_count`). Remove the broken alias (header + impl) and its test call — zero coverage loss. Keep the SIGSEGV/SIGABRT/SIGBUS backtrace handler added for diagnosis as a permanent aid.
…774) Brings the #774 fix (non-Identity-ordered Type0 fonts emit U+FFFD for CIDs missing from /ToUnicode instead of a wrong char) into the release, plus its tests. Moves its CHANGELOG entry under [0.3.69] → Fixed and credits @schelip (reporter of #773 and author of the fix). Claude-Session: https://claude.ai/code/session_01UvsYzR5sjaMeamU35eGFsC
Mirrors Dependabot #764 onto the release branch so release/v0.3.69 and main stay in sync on the workflow action pins (identical SHA bump). CI-tooling only; no effect on shipped artifacts. Claude-Session: https://claude.ai/code/session_01UvsYzR5sjaMeamU35eGFsC
The blockquote announced Go/JS/C# as new in v0.3.24 and linked only 6 language guides — obsolete at v0.3.69 (20 languages, already listed in the intro line). Claude-Session: https://claude.ai/code/session_01UvsYzR5sjaMeamU35eGFsC
Match the project's identity (PDFOxide), as used in the CHANGELOG. The `pdf_oxide` package/crate name is unchanged everywhere it's an actual identifier. Claude-Session: https://claude.ai/code/session_01UvsYzR5sjaMeamU35eGFsC
Brand/product mentions (H1, intro, comparison tables, citation title, license prose) now read PDFOxide, matching the CHANGELOG and our identity. URLs, package/crate names, imports, and file paths keep the pdf_oxide identifier. Claude-Session: https://claude.ai/code/session_01UvsYzR5sjaMeamU35eGFsC
The release-fips Python wheel job tags wheels macosx_11_0 via MACOSX_DEPLOYMENT_TARGET but reused a cached aws-lc-fips dylib built against the macOS-26 SDK (min target 15.0). cargo/cmake don't rebuild the dylib on an env-var change, so delocate rejected the 15.0 dylib in the 11.0 wheel. - Bump the FIPS cargo cache key v2 -> v3 to force a clean rebuild that honors the 11.0 deployment target. - Set CMAKE_OSX_DEPLOYMENT_TARGET=11.0 so aws-lc-fips's CMake build back-deploys its dylib to 11.0 explicitly. - Mirror the same pins in ci-fips.yml so CI builds/delocates the same 11.0 wheel the release ships instead of masking the regression. Also bump the 0.3.69 CHANGELOG date to 2026-06-27.
# Conflicts: # CHANGELOG.md
aws-lc-fips-sys ignores MACOSX_DEPLOYMENT_TARGET/CMAKE_OSX_DEPLOYMENT_TARGET and links its arm64 dylib against the runner SDK (min macOS 15.0), so a macosx_11_0-tagged wheel bundling that dylib is rejected by delocate. The earlier 11.0 pin + cache bust could not lower the dylib. Tag the arm64 FIPS wheel macosx_15_0 to match the bundled dylib; x86_64 still builds at the portable 11.0 floor (it passes). Drop the inert CMAKE_OSX pin.
aws-lc-fips links its arm64 dylib against the runner's build SDK (now macOS 26) and ignores MACOSX_/CMAKE_OSX_DEPLOYMENT_TARGET, so the bundled dylib's minimum keeps rising with the runner image and delocate rejects any portable wheel tag. Drop the aarch64-apple-darwin entry from the FIPS Python wheel matrix (release-fips.yml + ci-fips.yml); ship Linux x86_64/ aarch64, macOS x86_64, and Windows FIPS wheels. FIPS is predominantly Linux/x86_64; macOS arm64 FIPS wheel tracked as a follow-up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.3.69 — Language-bindings release
Brings the 11 new language bindings to production readiness.
What's in it
release.yml: Maven Central (Kotlin/Scala), Clojars (Clojure), Hex.pm (Elixir), pub.dev (Dart, OIDC). Objective-C ships as a Trunk-free CocoaPods binary pod (xcframework + podspec as release assets) — CocoaPods Trunk goes read-only 2026-12-02. C++ (vcpkg/Conan), R (CRAN), Julia (General registry), Swift/Zig (git tag) documented indocs/RELEASING-bindings.md.files:so the NIF ships, objc podspec license, LICENSE for all 11, C++ CMake install/export + Conan), SOTA-2026 secondary linters (scalafix, cljfmt, dialyxir, dart analysis_options), version bump to 0.3.69 across all manifests,.gitignorefixes.Verification
Release gate
Do not merge/tag until: pub.dev OIDC enabled (+ first manual Dart publish), Clojars creds rotated, and CI fully green. See
docs/RELEASING-bindings.md.Closes
Delivers the idiomatic-binding requests: