Skip to content

feat: add subtitle field to XMP metadata parser, refactor for modern Java#70

Merged
balazs-szucs merged 10 commits into
grimmory-tools:mainfrom
balazs-szucs:modern-stuff
May 12, 2026
Merged

feat: add subtitle field to XMP metadata parser, refactor for modern Java#70
balazs-szucs merged 10 commits into
grimmory-tools:mainfrom
balazs-szucs:modern-stuff

Conversation

@balazs-szucs
Copy link
Copy Markdown
Member

@balazs-szucs balazs-szucs commented May 12, 2026

Mostly done via automated tools. Notable changes: volatilite -> StableValue, remove unused, Intellij's "cleanup", adding more fields to XMP, bump C++, minor modernization there.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added XMP metadata extraction using QPDF backend
    • Added ability to save PDFs with embedded metadata directly to file
    • Added configurable global XMP padding for metadata preservation
    • Added character bounding box information API
    • Added book subtitle support in metadata
  • Improvements

    • Enhanced metadata caching and parsing performance
    • Improved PDF rendering memory safety
    • Upgraded C++ compiler standard support
    • Optimized native library initialization

@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Warning

Rate limit exceeded

@balazs-szucs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 33 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 928d0839-8a77-4d4c-9949-c31814dd17d9

📥 Commits

Reviewing files that changed from the base of the PR and between d2e701c and d1d9752.

⛔ Files ignored due to path filters (8)
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_raw_ptr.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libchrome_zlib.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libicuuc.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libpdfium.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libthird_party_abseil-cpp_absl.so is excluded by !**/*.so
📒 Files selected for processing (42)
  • .github/workflows/ci.yml
  • .github/workflows/publish.yml
  • build.gradle.kts
  • pdfium4j-natives-linux-x64/build.gradle.kts
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/native-libs.txt
  • shim/CMakeLists.txt
  • shim/include/pdfium4j_shim.h
  • shim/src/pdfium4j_read.cpp
  • shim/src/pdfium4j_utils.h
  • shim/src/pdfium4j_write.cpp
  • src/main/java/org/grimmory/pdfium4j/PdfDocument.java
  • src/main/java/org/grimmory/pdfium4j/PdfPage.java
  • src/main/java/org/grimmory/pdfium4j/PdfSaver.java
  • src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java
  • src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java
  • src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java
  • src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java
  • src/main/java/org/grimmory/pdfium4j/internal/Generators.java
  • src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java
  • src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java
  • src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java
  • src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java
  • src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java
  • src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java
  • src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java
  • src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java
  • src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java
  • src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java
  • src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java
  • src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java
  • src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java
  • src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java
  • src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java
  • src/test/java/org/grimmory/pdfium4j/internal/TrigramTokenizerAllocationTest.java
📝 Walkthrough

Walkthrough

Introduces version 1.2.0 with Windows 2025 runner support, enhanced host/platform detection in Gradle, C++23 shim compilation with Windows-specific headers, new QPDF-backed XMP/metadata extraction functions, systematic FFM binding refactoring from volatile+synchronized to StableValue caching, configurable XMP padding, improved metadata caching in PdfDocument, and refactored ScratchBuffer with ScopedValue-based lifecycle management.

Changes

CI/Build & Platform Infrastructure

Layer / File(s) Summary
Runner version updates
.github/workflows/ci.yml, .github/workflows/publish.yml
Windows runner matrix updated from windows-2022 to windows-2025 in both CI and publish workflows.
Host platform detection and C++ standard upgrades
build.gradle.kts, shim/CMakeLists.txt
Added host OS/architecture detection with platformFilter logic; version bumped to 1.2.0; Java preview features enabled; test heap size increased to 2GB; C++ standard raised to C++23 with Windows-specific compiler flags (NOMINMAX, WIN32_LEAN_AND_MEAN); corpus processor tasks now depend on generateNativeIndex instead of buildShim; removed linux-x64 native module build script and native-libs resource file.

PDFium Shim XMP/Metadata Extraction APIs

Layer / File(s) Summary
Shim header declarations
shim/include/pdfium4j_shim.h
Added pdfium4j_char_info_t struct for character bounds/font-size metadata; declared pdfium4j_save_with_metadata_mem_to_file_native for in-memory PDF input with file output; declared QPDF-backed XMP readers (pdfium4j_get_xmp_qpdf, pdfium4j_get_xmp_qpdf_mem) and optional symbol resolver (pdfium4j_resolve_optional_symbols).
Shim C++ implementation
shim/src/pdfium4j_read.cpp, shim/src/pdfium4j_write.cpp, shim/src/pdfium4j_utils.h
Implemented optional symbol resolution via std::once_flag for FPDF_GetXMPMetadata; refactored XMP extraction to use thread-local UTF-16 staging and std::span/std::string_view for efficient buffer handling; added QPDF-based /Metadata stream extraction with error mapping; introduced pdfium4j_save_with_metadata_mem_to_file_native using centralized inject_metadata helper; added RAII deleter structs and Scoped* type aliases for PDFium handle lifetime management; added ABI size/alignment compile-time checks for pdfium4j_char_info_t.
Java FFM bindings for new shim APIs
src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java
Added public MethodHandle accessors for pdfium4jGetXmpQpdf(), pdfium4jGetXmpQpdfMem(), and pdfium4jResolveOptionalSymbols() backed by StableValue caches; updated PdfiumLibrary.initialize() to conditionally invoke optional symbol resolution after core init.

FFM Binding Cache Modernization

Layer / File(s) Summary
Systematic StableValue refactoring across all bindings
src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java, src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java, src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java, src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java, src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java, src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java, src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java, src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java
Replaced private static volatile MethodHandle fields + synchronized double-checked locking with private static final StableValue<MethodHandle> + orElseSet(...) lazy initialization across all PDFium FFM bindings; added @SuppressWarnings("preview") annotations to preview-feature-using classes; public accessor method signatures unchanged.
ShimBindings binding unification
src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java
Applied StableValue refactoring to all existing shim binding accessors (page count, metadata, save operations, etc.) alongside the new XMP/metadata bindings.
FfmHelper layout caching modernization
src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java
Converted CANONICAL_LAYOUTS and C_LONG to StableValue-backed caches; added explicit imports for OfInt/OfLong type aliases; updated readCLong/writeCLong to use pattern matching on OfLong for platform-dependent layout selection.

Java Metadata APIs & PDF Save Flow

Layer / File(s) Summary
Extended metadata model
src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java, src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java, src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java
BookMetadata interface now declares subtitle() returning Optional<String>; PdfBookMetadata record adds subtitle component; XmpMetadata adds toBuilder(), findField(String), and findListField(String) search helpers; calibre series index changed from OptionalDouble to Optional<Double>; builder internals refactored to use nullable fields with late Optional wrapping.
PdfDocument metadata caching & XMP handling
src/main/java/org/grimmory/pdfium4j/PdfDocument.java
Added setGlobalXmpPadding(int) to configure XMP serialization padding; converted cachedFallbackMeta from nullable to StableValue-backed lazy cache; introduced cachedXmp for memoized parsed XMP; made bookMetadata() synchronized and use memoized XMP; updated metadata fallback paths to use the new lazy-cache infrastructure; changed openPages to SequencedCollection.
XMP padding configuration
src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java
Added configurable paddingEnabled/paddingLines state with synchronized fluent setters; converted WriterSink and OutputStreamSink from static classes to record types; replaced fixed 20-line padding with instance-driven writePadding() method; adjusted custom-field grouping so xmp:-prefixed keys follow normal grouped-per-prefix logic instead of being treated as unprefixed.
PDF save path refactoring
src/main/java/org/grimmory/pdfium4j/PdfSaver.java
Eliminated intermediate temporary source file creation for file-to-file saves; saveNative now handles source as separate parameters (path/bytes/segment) and forwards each to the appropriate native save binding; refactored metadata pair array filling to use explicit index increments; updated native bindings to match three save-source forms (path, bytes, segment).

Internal Infrastructure Refactoring

Layer / File(s) Summary
ScratchBuffer scoped lifecycle management
src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java
Refactored buffer lifecycle from ThreadLocal + counter to ScopedValue-bound state with ThreadLocal bridge fallback; added configurable sizing via system properties (STEADY_STATE_SIZE, MAX_SIZE, WARN_THRESHOLD); removed getWide(String) and wrap(MemorySegment, long); added withScratch(Runnable) and callWithScratch(Callable<T>) for scoped contexts; updated SegmentInputStream to acquire immediately during construction; simplified internal State structure to core arenas/segments, metadata buffer, and KeyValueSlots.
Supporting refactors & test alignment
src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java, src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java, src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java, src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java, src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java
NativeLoader uses StableValue for loaded state and shim lookup; SegmentOutputStream.write(int) refactored to explicit post-increment; PdfDocumentFallbackMeta.unescape() uses toString(StandardCharsets.ISO_8859_1); InternalLogger uses imported Logger.Level; TrigramTokenizer.generateTrigramHashes() rewritten as Stream/Gatherers pipeline; test assertions updated for calibre series index type change and allocation thresholds; PdfPage.renderSafe fixed to promote width to long before multiplication to prevent overflow.

Sequence Diagram(s)

sequenceDiagram
    participant Java as PdfDocument
    participant NativeInit as PdfiumLibrary.initialize()
    participant Shim as Native Shim
    participant QPDF as QPDF Library
    
    Java->>NativeInit: PdfiumLibrary.initialize()
    NativeInit->>Shim: Resolve required symbols
    Shim-->>NativeInit: Required symbols ready
    NativeInit->>Shim: pdfium4jResolveOptionalSymbols()
    Shim->>Shim: One-time resolve FPDF_GetXMPMetadata
    Shim-->>NativeInit: Optional symbols resolved
    NativeInit-->>Java: Initialization complete
    
    Note over Java: PDF operations enabled
    Java->>Java: bookMetadata() [synchronized]
    Java->>Shim: pdfium4jGetXmpQpdf(path)
    Shim->>QPDF: Extract /Metadata stream
    QPDF-->>Shim: XMP bytes
    Shim-->>Java: XMP document
    Java->>Java: Parse XMP once, cache in cachedXmp
    Java-->>Java: Return PdfBookMetadata with title/subtitle
Loading
sequenceDiagram
    participant Client as PdfSaver.save()
    participant Java as saveNative()
    participant Shim as Native pdfium4j_save_*
    participant File as File System
    
    Client->>Java: save(SaveParams with source, dest, xmp, metadata)
    Java->>Java: Determine source type (path/bytes/segment)
    alt Source is file path
        Java->>Shim: pdfium4j_save_with_metadata_native(srcPath, dstPath, xmp, metadata)
    else Source is byte array
        Java->>Shim: pdfium4j_save_with_metadata_mem_native(srcBytes, dstPath, xmp, metadata)
    else Source is MemorySegment
        Java->>Shim: pdfium4j_save_with_metadata_mem_to_file_native(srcSegment, dstPath, xmp, metadata)
    end
    Shim->>Shim: Inject XMP /Metadata
    Shim->>Shim: Inject Info dictionary key/value pairs
    Shim->>File: Write PDF file with updated metadata
    File-->>Shim: Write complete
    Shim-->>Java: Return status
    Java-->>Client: Complete or throw IOException
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • grimmory-tools/PDFium4j#51: Overlapping shim-level changes for QPDF-based metadata readers/writers and new save-with-metadata APIs with corresponding ShimBindings methods.
  • grimmory-tools/PDFium4j#40: Related ScratchBuffer and FfmHelper refactoring patterns used across PdfDocument and metadata extraction.
  • grimmory-tools/PDFium4j#42: Directly related XmpMetadataWriter serialization logic and custom-field grouping modifications.

Suggested labels

backend, feature, refactoring

Poem

🐰 A rabbit hops through streams and scopes,
With caches bound and shim-layer hopes,
From volatile locks to values stable—
Native threads now sip from one clear table!
PDF metadata flows swift and bright,
And padding options feel just right! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

@balazs-szucs
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java (1)

363-406: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove SegmentInputStream and its acquire()/release() calls.

SegmentInputStream (lines 363–406) is dead code. The public factory wrap(MemorySegment, long) that created instances was removed, and no remaining code references this class. The SegmentInputStream instantiation found at PdfDocument.java:1024 uses a different class (PdfDocument.SegmentInputStream) with a different constructor signature. Delete the unused inner class and its resource plumbing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java` around lines
363 - 406, Remove the unused inner class SegmentInputStream and its resource
plumbing: delete the entire private static final class SegmentInputStream
(including its constructor, read/available/close methods) and remove the calls
to acquire() in the constructor and release() in close(); this class was
orphaned after the public factory wrap(MemorySegment,long) was removed and is
distinct from PdfDocument.SegmentInputStream, so simply deleting
SegmentInputStream and its acquire()/release() invocations is the correct
cleanup.
src/main/java/org/grimmory/pdfium4j/PdfDocument.java (1)

922-957: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Subtitle is hard-coded to Optional.empty() despite PR adding subtitle parsing.

The PR adds subtitle parsing to XmpMetadataParser (stored as booklore:subtitle in custom fields), and PdfBookMetadata now has a subtitle field (position 2), but this constructor call passes Optional.empty() instead of extracting the parsed subtitle. The newly parsed subtitle is never surfaced to the caller, nullifying the user-visible feature.

The subtitle is accessible via XmpMetadata.findField("subtitle"), which searches custom namespaces and returns Optional<String>:

xmp.findField("subtitle")

Alternatively, if a fallback to Info dictionary is desired (consistent with title/description handling):

xmp.findField("subtitle").or(() -> metadata(MetadataTag.SUBTITLE))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/grimmory/pdfium4j/PdfDocument.java` around lines 922 - 957,
The constructor call creating PdfBookMetadata currently passes a hard-coded
Optional.empty() for the subtitle (position 2) so the parsed subtitle from
XmpMetadata is never returned; update that argument to use the parsed subtitle
from XmpMetadata by replacing Optional.empty() with xmp.findField("subtitle")
or, if you want the same fallback pattern used elsewhere,
xmp.findField("subtitle").or(() -> metadata(MetadataTag.SUBTITLE)); locate the
return in PdfDocument where PdfBookMetadata(...) is constructed and modify that
second parameter accordingly (use XmpMetadata.findField and the
metadata(MetadataTag.SUBTITLE) helper).
🧹 Nitpick comments (5)
src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java (1)

47-51: ⚡ Quick win

C_LONG_V StableValue is redundant — the fallback is evaluated eagerly.

Map.getOrDefault(key, default) always evaluates its second argument, so C_LONG_V.orElseSet(FfmHelper::detectCLongLayout) is invoked unconditionally on every class-init, regardless of whether "long" is present in the canonical layouts. Combined with the fact that C_LONG is itself a static final initialized exactly once, wrapping detectCLongLayout() in a StableValue provides no laziness or caching benefit here — it only adds indirection.

If the intent was to call detectCLongLayout() only when the canonical layouts don't supply "long", switch to a conditional lookup. Otherwise, just inline the detection call and drop C_LONG_V entirely.

♻️ Proposed simplification (truly lazy fallback)
-  private static final StableValue<ValueLayout> C_LONG_V = StableValue.of();
-
-  public static final ValueLayout C_LONG =
-      (ValueLayout)
-          layouts().getOrDefault("long", C_LONG_V.orElseSet(FfmHelper::detectCLongLayout));
+  public static final ValueLayout C_LONG = resolveCLong();
+
+  private static ValueLayout resolveCLong() {
+    MemoryLayout fromLinker = layouts().get("long");
+    return fromLinker instanceof ValueLayout vl ? vl : detectCLongLayout();
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java` around lines 47
- 51, C_LONG_V is unnecessary because layouts().getOrDefault(...) eagerly
evaluates the fallback; change the static init for C_LONG to avoid calling
detectCLongLayout() unconditionally: perform a conditional lookup (e.g., check
layouts().containsKey("long") and use layouts().get("long") if present,
otherwise call FfmHelper::detectCLongLayout) and remove the C_LONG_V StableValue
field; reference C_LONG, C_LONG_V, detectCLongLayout, and layouts() when making
the change.
src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java (1)

209-218: ⚡ Quick win

Add coverage for withScratch(Runnable) and exception propagation.

The new callWithScratch happy path is covered, but the sibling withScratch(Runnable) API is entirely untested, and there is no assertion that exceptions thrown inside the action propagate correctly (and that the State is still cleaned up). Both are cheap to add and exercise the most failure-prone parts of the new lifecycle.

🧪 Suggested additions
   `@Test`
   void callWithScratchReturnsResult() throws Exception {
     String result =
         ScratchBuffer.callWithScratch(
             () -> {
               ScratchBuffer.get(8);
               return "success";
             });
     assertEquals("success", result);
   }
+
+  `@Test`
+  void withScratchRunsActionWithImplicitAcquire() {
+    AtomicLong captured = new AtomicLong();
+    ScratchBuffer.withScratch(() -> captured.set(ScratchBuffer.get(8).byteSize()));
+    assertTrue(captured.get() >= 8);
+  }
+
+  `@Test`
+  void callWithScratchPropagatesExceptions() {
+    assertThrows(
+        IllegalStateException.class,
+        () ->
+            ScratchBuffer.callWithScratch(
+                () -> {
+                  throw new IllegalStateException("boom");
+                }));
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java` around
lines 209 - 218, Add unit tests that cover the void API and exception
propagation: add a test that calls ScratchBuffer.withScratch(() -> {
ScratchBuffer.get(8); /* no return */ }) and asserts no exception and that State
is cleaned up (e.g., ScratchBuffer.get(...) no longer returns a retained state
or ScratchBuffer.isActive() false after call); and add a test that calls
ScratchBuffer.withScratch(() -> { throw new RuntimeException("boom"); }) (or use
callWithScratch variant) and assert the RuntimeException propagates out and that
the ScratchBuffer State is still cleaned up afterwards (no lingering active
state). Reference the methods withScratch(Runnable), callWithScratch(Callable),
and ScratchBuffer.get(...) to locate where to add these assertions.
src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java (3)

158-184: 💤 Low value

Document the implicit acquire on withScratch/callWithScratch.

Both helpers pre-set useCount = 1 so the action can use get(...) immediately. That means an unbalanced release() (or extra Scope.close()) inside the action will drop useCount to 0 and surprise the next get(...) with IllegalStateException. A short note in the JavaDoc clarifying that callers should not call release() / use acquireScope() symmetrically would prevent surprises.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java` around lines
158 - 184, Update the JavaDoc for withScratch and callWithScratch to document
that they implicitly set State.useCount = 1 (i.e., they acquire a scratch for
the duration of the action), and therefore callers must not call release(),
acquireScope(), or close the surrounding Scope (e.g., Scope.close()) inside the
provided action or expect to symmetrically release—doing so may drop useCount to
0 and cause get(...) to throw IllegalStateException; reference withScratch,
callWithScratch, State.useCount, get(...), release(), acquireScope(), and
Scope.close() in the comment so callers know the exact behavior to avoid.

12-23: ⚡ Quick win

Class JavaDoc no longer matches the implementation.

The header still describes a "thread-local scratch buffer" with a "reusable thread-local slab", but the new design centers on ScopedValue with ThreadLocal only as a bridge fallback, and introduces withScratch / callWithScratch as preferred entry points. Please refresh the doc to describe the scope-based lifecycle and the bridge semantics so callers can pick the right entry point.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java` around lines
12 - 23, The ScratchBuffer JavaDoc is outdated: update it to explain the new
scope-based lifecycle using ScopedValue with ThreadLocal only as a bridge
fallback, and document the preferred entry points withScratch and
callWithScratch (which establish the scratch scope) instead of implying a purely
thread-local reusable slab; state that returned segments are short-lived
temporaries valid only for the duration of the scope or until release(),
describe that growth is non-destructive across nested scopes, and note
ThreadLocal is used only when ScopedValue is unavailable so callers can choose
the correct entry point.

176-184: 💤 Low value

Minor: simplify the Callable wrapping and import Callable directly.

() -> action.call() is a redundant lambda wrapper around a Callable; passing the method reference is cleaner. Also add import java.util.concurrent.Callable; instead of using the fully-qualified type in the signature.

♻️ Suggested tweak
-  public static <T> T callWithScratch(java.util.concurrent.Callable<T> action) throws Exception {
+  public static <T> T callWithScratch(Callable<T> action) throws Exception {
     State s = new State();
     s.useCount = 1;
     try {
-      return ScopedValue.where(STATE, s).call(() -> action.call());
+      return ScopedValue.where(STATE, s).call(action::call);
     } finally {
       s.close();
     }
   }

The method reference action::call is fully type-safe with ScopedValue.Carrier.call(CallableOp op) in Java 25, where the CallableOp throws clause is inferred as Exception.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java` around lines
176 - 184, callWithScratch currently uses a redundant lambda (() ->
action.call()) and a fully-qualified Callable type; simplify by importing
java.util.concurrent.Callable and passing the method reference action::call to
ScopedValue.where(STATE, s).call(...). Locate the callWithScratch method and
replace the parameter type with Callable (after adding the import) and change
the lambda to use action::call, leaving State s creation and s.close() in the
finally unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shim/CMakeLists.txt`:
- Around line 2-5: Update the CMake minimum required version to match the C++23
standard: change the cmake_minimum_required(...) value from 3.15 to 3.20 so it
supports set(CMAKE_CXX_STANDARD 23) and the CMAKE_CXX_STANDARD_REQUIRED ON
behavior; locate the cmake_minimum_required call and bump its version to 3.20.

In `@src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java`:
- Around line 49-60: The lambda passed to loaded.orElseSet in NativeLoader
swallows original exceptions when rethrowing; update the two catch blocks inside
the lambda (the one catching NativeLoadException and the one catching Throwable
around performLoad()) to preserve the original exception as the cause when
throwing a new NativeLoadException (e.g., throw new NativeLoadException("...",
caughtException) or call initCause on the new exception). This ensures
performLoad() failures and detectPlatform() details remain available in the
thrown NativeLoadException's stack trace.

In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java`:
- Around line 186-199: The purge() implementation must not zombify a currently
bound scoped State: do not close or zero a State while STATE.isBound() is true.
Change purge() to check STATE.isBound() and refuse (throw IllegalStateException
with a clear message) instead of mutating the bound State; leave BRIDGE handling
for non-scoped States as-is. Reference symbols: purge(), STATE, BRIDGE, State,
useCount, close(), and the fields segment/steadySegment/metadataBuffer.

In `@src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java`:
- Around line 20-27: The current TrigramTokenizer implementation builds a boxed
stream and uses Gatherers.windowSliding which causes heavy allocation; replace
that pipeline with a primitive, low-allocation loop inside the same method in
TrigramTokenizer: convert the input String to a char[] or use text.charAt(i) in
a for-loop, compute each trigram long via ((long) c0 << 32) | ((long) c1 << 16)
| c2, store into a primitive long[] (or an Int-to-long deduplicating routine
using primitive buffers), then sort and deduplicate the primitive array before
returning; ensure you eliminate Stream.boxed()/Gatherers and use only primitive
arrays and loops to minimize allocations while preserving the distinct/sorted
semantics.

In `@src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java`:
- Around line 100-109: The method calibreSeriesIndex() was changed from
returning OptionalDouble to Optional<Double>, which is an API-breaking change;
update the release notes and migration guide to document this change, mention
the exact method name calibreSeriesIndex() and the previous return type
OptionalDouble plus the new return type Optional<Double>, describe how callers
should adapt (unwrap Optional<Double> or use Double::isPresent and get()), and
include the version (1.2.0) and a short example migration snippet so integrators
can update their code.

In `@src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java`:
- Around line 74-76: Wrap the optional symbol invocation
ShimBindings.pdfium4jResolveOptionalSymbols().invokeExact() in a try-catch so
that exceptions from the optional resolution are caught, logged, and do not
abort library initialization; specifically, call
ShimBindings.pdfium4jResolveOptionalSymbols(), if non-null attempt invokeExact()
inside a catch (Throwable) that logs the error (using the existing logger used
by PdfiumLibrary or a suitable logger) and continues without rethrowing so the
rest of initialization proceeds.

In `@src/main/java/org/grimmory/pdfium4j/PdfPage.java`:
- Line 353: The calculation of requiredBytes in PdfPage may overflow long for
extreme w/h, bypassing the memory guard; replace the simple cast multiplication
with overflow-checked math using Math.multiplyExact: compute requiredBytes =
Math.multiplyExact(Math.multiplyExact((long) w, (long) h), (long)
BYTES_PER_PIXEL) and catch ArithmeticException (or otherwise handle overflow) to
treat it as an overflow condition (e.g., set requiredBytes to Long.MAX_VALUE or
fail the allocation) so the max-memory check cannot be bypassed.

In `@src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java`:
- Line 4: The main method in NativeSmokeTest is declared without public
visibility so the Java launcher can't use it; change the method signature of
NativeSmokeTest.main to be public static void main(String[] args) (i.e., restore
public visibility on the main method) so the class can be executed as a standard
Java application entrypoint.

---

Outside diff comments:
In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java`:
- Around line 363-406: Remove the unused inner class SegmentInputStream and its
resource plumbing: delete the entire private static final class
SegmentInputStream (including its constructor, read/available/close methods) and
remove the calls to acquire() in the constructor and release() in close(); this
class was orphaned after the public factory wrap(MemorySegment,long) was removed
and is distinct from PdfDocument.SegmentInputStream, so simply deleting
SegmentInputStream and its acquire()/release() invocations is the correct
cleanup.

In `@src/main/java/org/grimmory/pdfium4j/PdfDocument.java`:
- Around line 922-957: The constructor call creating PdfBookMetadata currently
passes a hard-coded Optional.empty() for the subtitle (position 2) so the parsed
subtitle from XmpMetadata is never returned; update that argument to use the
parsed subtitle from XmpMetadata by replacing Optional.empty() with
xmp.findField("subtitle") or, if you want the same fallback pattern used
elsewhere, xmp.findField("subtitle").or(() -> metadata(MetadataTag.SUBTITLE));
locate the return in PdfDocument where PdfBookMetadata(...) is constructed and
modify that second parameter accordingly (use XmpMetadata.findField and the
metadata(MetadataTag.SUBTITLE) helper).

---

Nitpick comments:
In `@src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java`:
- Around line 47-51: C_LONG_V is unnecessary because layouts().getOrDefault(...)
eagerly evaluates the fallback; change the static init for C_LONG to avoid
calling detectCLongLayout() unconditionally: perform a conditional lookup (e.g.,
check layouts().containsKey("long") and use layouts().get("long") if present,
otherwise call FfmHelper::detectCLongLayout) and remove the C_LONG_V StableValue
field; reference C_LONG, C_LONG_V, detectCLongLayout, and layouts() when making
the change.

In `@src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java`:
- Around line 158-184: Update the JavaDoc for withScratch and callWithScratch to
document that they implicitly set State.useCount = 1 (i.e., they acquire a
scratch for the duration of the action), and therefore callers must not call
release(), acquireScope(), or close the surrounding Scope (e.g., Scope.close())
inside the provided action or expect to symmetrically release—doing so may drop
useCount to 0 and cause get(...) to throw IllegalStateException; reference
withScratch, callWithScratch, State.useCount, get(...), release(),
acquireScope(), and Scope.close() in the comment so callers know the exact
behavior to avoid.
- Around line 12-23: The ScratchBuffer JavaDoc is outdated: update it to explain
the new scope-based lifecycle using ScopedValue with ThreadLocal only as a
bridge fallback, and document the preferred entry points withScratch and
callWithScratch (which establish the scratch scope) instead of implying a purely
thread-local reusable slab; state that returned segments are short-lived
temporaries valid only for the duration of the scope or until release(),
describe that growth is non-destructive across nested scopes, and note
ThreadLocal is used only when ScopedValue is unavailable so callers can choose
the correct entry point.
- Around line 176-184: callWithScratch currently uses a redundant lambda (() ->
action.call()) and a fully-qualified Callable type; simplify by importing
java.util.concurrent.Callable and passing the method reference action::call to
ScopedValue.where(STATE, s).call(...). Locate the callWithScratch method and
replace the parameter type with Callable (after adding the import) and change
the lambda to use action::call, leaving State s creation and s.close() in the
finally unchanged.

In `@src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java`:
- Around line 209-218: Add unit tests that cover the void API and exception
propagation: add a test that calls ScratchBuffer.withScratch(() -> {
ScratchBuffer.get(8); /* no return */ }) and asserts no exception and that State
is cleaned up (e.g., ScratchBuffer.get(...) no longer returns a retained state
or ScratchBuffer.isActive() false after call); and add a test that calls
ScratchBuffer.withScratch(() -> { throw new RuntimeException("boom"); }) (or use
callWithScratch variant) and assert the RuntimeException propagates out and that
the ScratchBuffer State is still cleaned up afterwards (no lingering active
state). Reference the methods withScratch(Runnable), callWithScratch(Callable),
and ScratchBuffer.get(...) to locate where to add these assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d755be9-d342-43fe-9216-b98feb746d45

📥 Commits

Reviewing files that changed from the base of the PR and between 73d7c9b and d2e701c.

⛔ Files ignored due to path filters (8)
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_raw_ptr.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libchrome_zlib.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libicuuc.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libpdfium.so is excluded by !**/*.so
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libthird_party_abseil-cpp_absl.so is excluded by !**/*.so
📒 Files selected for processing (42)
  • .github/workflows/ci.yml
  • .github/workflows/publish.yml
  • build.gradle.kts
  • pdfium4j-natives-linux-x64/build.gradle.kts
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/native-libs.txt
  • shim/CMakeLists.txt
  • shim/include/pdfium4j_shim.h
  • shim/src/pdfium4j_read.cpp
  • shim/src/pdfium4j_utils.h
  • shim/src/pdfium4j_write.cpp
  • src/main/java/org/grimmory/pdfium4j/PdfDocument.java
  • src/main/java/org/grimmory/pdfium4j/PdfPage.java
  • src/main/java/org/grimmory/pdfium4j/PdfSaver.java
  • src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java
  • src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java
  • src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java
  • src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java
  • src/main/java/org/grimmory/pdfium4j/internal/Generators.java
  • src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java
  • src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java
  • src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java
  • src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java
  • src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java
  • src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java
  • src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java
  • src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java
  • src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java
  • src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java
  • src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java
  • src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java
  • src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java
  • src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java
  • src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java
  • src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java
  • src/test/java/org/grimmory/pdfium4j/internal/TrigramTokenizerAllocationTest.java
💤 Files with no reviewable changes (2)
  • pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/native-libs.txt
  • pdfium4j-natives-linux-x64/build.gradle.kts
📜 Review details
🧰 Additional context used
🔀 Multi-repo context grimmory-tools/grimmory-docs

Findings for grimmory-tools/grimmory-docs [::grimmory-tools/grimmory-docs::]

  • docs/magic-shelf.md

    • Line ~60: documents a "Subtitle" metadata field (subtitle) described as "Book subtitle".
    • Line ~205: includes subtitle in metadata presence dropdown list.
    • Lines ~1256–1263: mentions filtering/books that have subtitle metadata and example filter { "field": "subtitle", "operator": "is_not_empty", "value": null }.
  • docs/metadata/file-naming-patterns.md

    • Line ~32 / ~80 / ~87 / ~90 / ~129 / ~136: multiple examples and pattern entries referencing {subtitle} and examples showing how subtitles appear/are omitted in filenames.
  • docs/metadata/sidecar-files.md

    • Line ~37: lists subtitle among sidecar metadata fields (Title, subtitle, ...).
  • docs/metadata/metadata-settings.md

    • Line ~61: lists subtitle in "Standard fields".
  • docs/metadata/metadata-fetch-configuration.md

    • Line ~63: documents "Subtitle | Book subtitle".

Relevance: the repo contains user-facing documentation that references a "subtitle" metadata field. The PR adds subtitle handling in the PDFium4j XMP metadata model, so these docs align with that field; no code-level consumers were found in this repository.

🔇 Additional comments (61)
src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java (1)

27-31: LGTM!

src/main/java/org/grimmory/pdfium4j/PdfPage.java (1)

62-63: LGTM!

Also applies to: 151-151

src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java (1)

30-30: ⚡ Quick win

The --enable-preview configuration is properly wired up for both compilation and runtime.

The build configuration correctly enables preview features: build.gradle.kts includes --enable-preview in the JavaCompile task (lines 117) and across all JVM execution contexts (benchmarks, tests, Javadoc). Compiler warnings are suppressed globally via -Xlint:-preview (line 118).

One note: unlike similar binding classes (ShimBindings, ThumbnailBindings, Generators, AttachmentBindings), FfmHelper lacks the @SuppressWarnings("preview") annotation at the class level, though the build-level suppression covers this and prevents compilation warnings.

src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java (1)

87-96: LGTM!

Also applies to: 100-104, 129-131

.github/workflows/publish.yml (1)

97-97: Verify that the windows-2025 runner image is available.

Same verification needed as in ci.yml line 103.

src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java (1)

15-15: LGTM!

src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java (1)

12-12: LGTM!

src/main/java/org/grimmory/pdfium4j/internal/Generators.java (1)

10-10: LGTM!

src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java (1)

181-181: LGTM!

.github/workflows/ci.yml (1)

103-103: ⚡ Quick win

No action neededwindows-2025 is a documented, available GitHub Actions runner image provided and maintained by GitHub.

			> Likely an incorrect or invalid review comment.
src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java (1)

17-18: ⚡ Quick win

No action needed. PdfBookMetadata is a Java record with subtitle as a component, which automatically generates the required subtitle() method to satisfy the BookMetadata interface contract. All implementations are properly updated.

src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java (1)

31-31: ⚡ Quick win

The subtitle parameter is correctly provided in the only PdfBookMetadata construction site. All 13 record fields are properly accounted for in PdfDocument.java line 922, with subtitle passed as Optional.empty() at the correct position. No other construction sites exist that need updating.

src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java (1)

3-4: LGTM!

Also applies to: 8-8, 13-13, 17-17

src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java (1)

50-55: LGTM!

Also applies to: 57-62, 64-69, 71-76, 78-87, 89-94

src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java (1)

41-46: LGTM!

Also applies to: 48-53, 55-60, 74-83, 85-94, 98-108, 110-119, 121-126, 128-133, 135-140, 144-153

src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java (1)

42-47: StableValue class implementation is missing from the codebase.

The review comment raises a valid concern about null-handling semantics, but cannot be verified because StableValue is used throughout the code (in TextBindings, FfmHelper, Generators, and all binding files) yet the class definition does not exist in the repository.

The concern itself is legitimate: if orElseSet() caches null values returned by the supplier (when find() fails to locate a symbol), the binding would not retry the lookup on subsequent calls. However, without access to the StableValue implementation, the actual behavior cannot be confirmed. The code may already handle this correctly (as suggested by patterns in AttachmentBindings where results are wrapped in Optional.ofNullable().orElse(null)), or it may indeed have the caching issue described.

build.gradle.kts (5)

18-18: LGTM!


117-118: LGTM!


141-150: LGTM!


555-555: LGTM!


652-652: LGTM!

Also applies to: 689-689

src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java (1)

42-50: LGTM!

Also applies to: 53-61, 64-69, 72-76, 79-87, 90-98, 101-109, 112-120, 123-127, 130-134, 137-145, 148-156

src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java (1)

39-45: LGTM!

Also applies to: 48-56, 59-67, 70-74, 77-81, 84-88, 91-95, 98-102

src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java (2)

9-9: LGTM!

Also applies to: 203-206


34-34: LGTM!

Also applies to: 36-36, 39-39, 45-45, 193-193

src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java (1)

71-75: LGTM!

Also applies to: 78-82, 85-93, 96-104, 107-115, 118-122, 125-129, 132-141, 144-152, 155-159, 162-166, 169-173, 176-184, 187-195, 198-207, 210-214, 217-225

src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java (4)

71-84: LGTM!


130-146: LGTM!

Also applies to: 148-188


453-458: LGTM!


559-562: LGTM!

src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java (4)

132-164: LGTM!


181-198: LGTM!


212-225: LGTM!


227-358: LGTM!

src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java (1)

170-171: LGTM!

src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java (4)

12-34: LGTM!


74-74: LGTM!

Also applies to: 126-127, 208-208


211-278: LGTM!


281-387: LGTM!

src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java (2)

158-160: LGTM!


171-176: LGTM!

src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java (2)

575-600: LGTM!


1668-1700: LGTM!

src/main/java/org/grimmory/pdfium4j/PdfSaver.java (3)

68-75: LGTM!


149-202: LGTM!


243-252: LGTM!

src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java (1)

48-53: LGTM!

Also applies to: 56-64, 66-75, 77-86, 88-97, 99-108, 110-119, 121-130, 132-141, 143-148, 150-159, 161-170, 172-181, 183-192, 194-199, 201-206, 208-218, 220-229, 231-241, 243-252, 254-263, 265-274, 276-285, 287-297, 299-309, 311-320, 322-332, 334-343, 345-354, 356-362, 364-373, 375-384, 386-396, 398-409, 411-422, 424-433, 435-444, 446-451

shim/src/pdfium4j_utils.h (2)

13-15: LGTM!


17-28: LGTM!

shim/src/pdfium4j_write.cpp (4)

16-103: LGTM!


122-168: LGTM!


194-244: LGTM!


246-293: LGTM!

src/main/java/org/grimmory/pdfium4j/PdfDocument.java (2)

914-919: LGTM!


126-126: ⚡ Quick win

No action required. The build configuration already correctly enables preview features for StableValue. The Gradle build file (build.gradle.kts) sets languageVersion = JavaLanguageVersion.of(25) and includes --enable-preview in both the JavaCompile compiler arguments (line 117) and all JavaExec and Test runtime JVM arguments. This configuration is sufficient and correct; the code will compile and run without issues.

shim/include/pdfium4j_shim.h (2)

140-148: LGTM!

Also applies to: 168-181


25-32: ⚡ Quick win

ABI layout is already verified—static assertions and Java implementation are correct.

The struct pdfium4j_char_info_t (24 bytes: int + 5×float) already has explicit static_assert checks for size and alignment in shim/include/pdfium4j_shim.h (lines 14–15). The Java-side memory layout in PdfPage.java (lines 482–488) correctly reads the struct fields at offsets 0, 4, 8, 12, 16, 20 with the expected 24-byte stride. Integration tests in PdfDocumentTest.java and PdfPageTextAllocationTest.java exercise this path and confirm correctness.

			> Likely an incorrect or invalid review comment.
shim/src/pdfium4j_read.cpp (4)

30-66: LGTM!


216-293: LGTM!


295-364: LGTM!


385-387: LGTM!

Also applies to: 419-421

Comment thread shim/CMakeLists.txt Outdated
Comment thread src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java
Comment thread src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java
Comment thread src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java Outdated
Comment thread src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java
Comment thread src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java
Comment thread src/main/java/org/grimmory/pdfium4j/PdfPage.java Outdated
Comment thread src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java
@balazs-szucs balazs-szucs merged commit 9fdb7b1 into grimmory-tools:main May 12, 2026
12 checks passed
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant