diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8724c4e..1ee3683 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: platform: darwin-arm64 arch: arm64 skipTests: false - - os: windows-2022 + - os: windows-2025 platform: windows-x64 arch: x64 skipTests: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 992dee8..cfab6a7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -94,7 +94,7 @@ jobs: - os: macos-15 platform: darwin-arm64 arch: arm64 - - os: windows-2022 + - os: windows-2025 platform: windows-x64 arch: x64 diff --git a/build.gradle.kts b/build.gradle.kts index e044beb..a74aa98 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { allprojects { group = "org.grimmory" - version = "1.1.4" + version = "1.2.0" repositories { mavenCentral() @@ -114,7 +114,8 @@ if (!enableCorpusTools.get()) { tasks.withType { options.compilerArgs.addAll(listOf( - "--enable-preview" + "--enable-preview", + "-Xlint:-preview" )) } @@ -137,8 +138,21 @@ val pdfiumPlatforms = mapOf( ) val platformFilter = findProperty("pdfiumPlatformFilter")?.toString() +val hostOs = System.getProperty("os.name").lowercase() +val hostArch = System.getProperty("os.arch").lowercase() +val hostPlatform = when { + hostOs.contains("mac") && (hostArch == "aarch64" || hostArch == "arm64") -> "darwin-arm64" + hostOs.contains("mac") && (hostArch == "x86_64" || hostArch == "amd64") -> "darwin-x64" + hostOs.contains("linux") && (hostArch == "aarch64" || hostArch == "arm64") -> "linux-arm64" + hostOs.contains("linux") && (hostArch == "x86_64" || hostArch == "amd64") -> "linux-x64" + hostOs.contains("windows") && (hostArch == "x86_64" || hostArch == "amd64") -> "windows-x64" + else -> null +} + val activePlatforms = if (platformFilter != null) { pdfiumPlatforms.filterKeys { it == platformFilter } +} else if (System.getenv("CI") == null && hostPlatform != null) { + pdfiumPlatforms.filterKeys { it == hostPlatform } } else { pdfiumPlatforms } @@ -150,17 +164,6 @@ val prebuiltShimsDir = findProperty("prebuiltShimsDir")?.toString()?.let { proje val pdfiumArchiveDir = layout.buildDirectory.dir("pdfium-archives") val pdfiumNativesDir = layout.buildDirectory.dir("generated-natives") -val hostOs = System.getProperty("os.name").lowercase() -val hostArch = System.getProperty("os.arch").lowercase() -val hostPlatform = when { - hostOs.contains("mac") && (hostArch == "aarch64" || hostArch == "arm64") -> "darwin-arm64" - hostOs.contains("mac") && (hostArch == "x86_64" || hostArch == "amd64") -> "darwin-x64" - hostOs.contains("linux") && (hostArch == "aarch64" || hostArch == "arm64") -> "linux-arm64" - hostOs.contains("linux") && (hostArch == "x86_64" || hostArch == "amd64") -> "linux-x64" - hostOs.contains("windows") && (hostArch == "x86_64" || hostArch == "amd64") -> "windows-x64" - else -> null -} - val downloadPdfiumBinaries by tasks.registering { description = "Downloads prebuilt PDFium binaries for all supported platforms" outputs.dir(pdfiumArchiveDir) @@ -549,6 +552,7 @@ tasks.assemble { tasks.withType { useJUnitPlatform() + maxHeapSize = "2g" testLogging { showStandardStreams = true events("passed", "skipped", "failed") @@ -645,7 +649,7 @@ tasks.register("runCorpusProcessor") { group = "application" description = "Runs the CorpusProcessor to write metadata to PDFs" if (enableCorpusTools.get()) { - dependsOn("buildShim") + dependsOn("generateNativeIndex") } mainClass.set("org.grimmory.pdfium4j.CorpusProcessor") classpath = sourceSets["test"].runtimeClasspath @@ -682,7 +686,7 @@ tasks.register("runCorpusMetadataStress") { group = "application" description = "Runs metadata save stress validation against corpus PDFs" if (enableCorpusTools.get()) { - dependsOn("buildShim") + dependsOn("generateNativeIndex") } mainClass.set("org.grimmory.pdfium4j.CorpusMetadataStressRunner") classpath = sourceSets["test"].runtimeClasspath diff --git a/pdfium4j-natives-linux-x64/build.gradle.kts b/pdfium4j-natives-linux-x64/build.gradle.kts deleted file mode 100644 index 0f57b94..0000000 --- a/pdfium4j-natives-linux-x64/build.gradle.kts +++ /dev/null @@ -1,73 +0,0 @@ -plugins { - `java-library` - `maven-publish` - signing -} - -description = "PDFium native libraries for Linux x86_64" - -// No Java sources in this module - just native resources -sourceSets { - main { - java.setSrcDirs(emptyList()) - } -} - -tasks.withType { - enabled = false -} - -// Maven Central requires javadoc and sources JARs, even if empty -val javadocJar by tasks.registering(Jar::class) { - archiveClassifier = "javadoc" -} - -val sourcesJar by tasks.registering(Jar::class) { - archiveClassifier = "sources" -} - -publishing { - publications { - create("mavenJava") { - from(components["java"]) - artifact(javadocJar) - artifact(sourcesJar) - - pom { - name = "PDFium4j Natives - Linux x86_64" - description = "Prebuilt PDFium native libraries for Linux x86_64" - url = "https://github.com/grimmory-tools/PDFium4j" - inceptionYear = "2025" - - licenses { - license { - name = "Apache License, Version 2.0" - url = "https://www.apache.org/licenses/LICENSE-2.0" - } - } - - developers { - developer { - id = "grimmory-tools" - name = "Grimmory Tools" - } - } - - scm { - connection = "scm:git:git://github.com/grimmory-tools/PDFium4j.git" - developerConnection = "scm:git:ssh://github.com/grimmory-tools/PDFium4j.git" - url = "https://github.com/grimmory-tools/PDFium4j" - } - } - } - } -} - -signing { - val signingKey = findProperty("signingKey") as String? ?: System.getenv("GPG_PRIVATE_KEY") - val signingPassword = findProperty("signingPassword") as String? ?: System.getenv("GPG_PASSPHRASE") - if (signingKey != null && signingPassword != null) { - useInMemoryPgpKeys(signingKey, signingPassword) - sign(publishing.publications["mavenJava"]) - } -} diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.so deleted file mode 100755 index 9a85a4a..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.so deleted file mode 100755 index 55c2ac8..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.so deleted file mode 100755 index 817db53..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_raw_ptr.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_raw_ptr.so deleted file mode 100755 index 7dfb663..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libbase_allocator_partition_allocator_src_partition_alloc_raw_ptr.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libchrome_zlib.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libchrome_zlib.so deleted file mode 100755 index de7cba8..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libchrome_zlib.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libicuuc.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libicuuc.so deleted file mode 100755 index dc2703e..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libicuuc.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libpdfium.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libpdfium.so deleted file mode 100755 index 130da15..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libpdfium.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libthird_party_abseil-cpp_absl.so b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libthird_party_abseil-cpp_absl.so deleted file mode 100755 index 8103694..0000000 Binary files a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/libthird_party_abseil-cpp_absl.so and /dev/null differ diff --git a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/native-libs.txt b/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/native-libs.txt deleted file mode 100644 index 1662ba4..0000000 --- a/pdfium4j-natives-linux-x64/src/main/resources/natives/linux-x64/native-libs.txt +++ /dev/null @@ -1,9 +0,0 @@ -# PDFium component libraries - load order matters -libthird_party_abseil-cpp_absl.so -libbase_allocator_partition_allocator_src_partition_alloc_raw_ptr.so -libbase_allocator_partition_allocator_src_partition_alloc_allocator_base.so -libbase_allocator_partition_allocator_src_partition_alloc_allocator_core.so -libbase_allocator_partition_allocator_src_partition_alloc_allocator_shim.so -libchrome_zlib.so -libicuuc.so -libpdfium.so diff --git a/shim/CMakeLists.txt b/shim/CMakeLists.txt index 00c7025..035e607 100644 --- a/shim/CMakeLists.txt +++ b/shim/CMakeLists.txt @@ -1,8 +1,8 @@ # PDFium4j Native Shim Build -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.20) project(pdfium4j_shim VERSION 1.0.1 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "" FORCE) @@ -14,6 +14,13 @@ endif() # Suppress QPDF PointerHolder transition warnings (and fix MSVC #warning error) add_definitions(-DPOINTERHOLDER_TRANSITION=0) +if(WIN32) + # Prevent windows.h from defining min/max macros that collide with C++ stdlib + add_definitions(-DNOMINMAX) + # Reduce the amount of Windows header bloat + add_definitions(-DWIN32_LEAN_AND_MEAN) +endif() + # QPDF dependency hints if(DEFINED ENV{ZLIB_ROOT}) set(ZLIB_ROOT "$ENV{ZLIB_ROOT}") diff --git a/shim/include/pdfium4j_shim.h b/shim/include/pdfium4j_shim.h index 4e797e8..f2a3e4e 100644 --- a/shim/include/pdfium4j_shim.h +++ b/shim/include/pdfium4j_shim.h @@ -22,6 +22,15 @@ extern "C" { #endif +typedef struct { + int charCode; + float left; + float bottom; + float right; + float top; + float fontSize; +} pdfium4j_char_info_t; + SHIM_EXPORT int FPDF_CALLCONV pdfium4j_page_count(FPDF_DOCUMENT doc); SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_meta_utf8(FPDF_DOCUMENT doc, const char* key, char* buf, int buf_len); @@ -128,6 +137,16 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_save_with_metadata_mem_native( int metadata_count ); +SHIM_EXPORT int FPDF_CALLCONV pdfium4j_save_with_metadata_mem_to_file_native( + const void* src_buf, + size_t src_len, + const char* dst_path, + const char* xmp_metadata, + int xmp_len, + const char** metadata_pairs, + int metadata_count +); + /** * QPDF-based metadata enumeration */ @@ -146,6 +165,21 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_read_info_dict_mem( void* userdata ); +SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_xmp_qpdf( + const char* src_path, + char* buf, + int buf_len +); + +SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_xmp_qpdf_mem( + const char* data, + size_t len, + char* buf, + int buf_len +); + +SHIM_EXPORT void FPDF_CALLCONV pdfium4j_resolve_optional_symbols(void); + #ifdef __cplusplus } #endif diff --git a/shim/src/pdfium4j_read.cpp b/shim/src/pdfium4j_read.cpp index 870484b..858a696 100644 --- a/shim/src/pdfium4j_read.cpp +++ b/shim/src/pdfium4j_read.cpp @@ -9,8 +9,12 @@ #include #include #include +#include +#include +#include #include #include +#include #include "pugixml.hpp" #ifdef _WIN32 @@ -23,8 +27,44 @@ typedef unsigned long (FPDF_CALLCONV *Type_FPDF_GetXMPMetadata)(FPDF_DOCUMENT, void*, unsigned long); +namespace { + +std::once_flag g_optional_symbols_once; +Type_FPDF_GetXMPMetadata g_fpdf_get_xmp_metadata = nullptr; + +thread_local std::vector g_utf16_scratch; +thread_local std::vector g_xmp_raw_scratch; + +void resolve_optional_symbols_once() noexcept { + std::call_once(g_optional_symbols_once, []() { + g_fpdf_get_xmp_metadata = + reinterpret_cast(SYMLOOKUP("FPDF_GetXMPMetadata")); + }); +} + +bool load_xmp_document(FPDF_DOCUMENT doc, pugi::xml_document& xdoc) { + int xmp_len = pdfium4j_get_xmp_metadata(doc, nullptr, 0); + if (xmp_len <= 1) { + return false; + } + + g_xmp_raw_scratch.resize(static_cast(xmp_len)); + int copied = pdfium4j_get_xmp_metadata(doc, g_xmp_raw_scratch.data(), xmp_len); + if (copied <= 1) { + return false; + } + + return xdoc.load_buffer(g_xmp_raw_scratch.data(), static_cast(copied)); +} + +} // namespace + extern "C" { +SHIM_EXPORT void FPDF_CALLCONV pdfium4j_resolve_optional_symbols() { + resolve_optional_symbols_once(); +} + SHIM_EXPORT int FPDF_CALLCONV pdfium4j_page_count(FPDF_DOCUMENT doc) { if (!doc) return 0; return FPDF_GetPageCount(doc); @@ -173,19 +213,10 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_struct_element_get_attribute_count(FPDF_S return FPDF_StructElement_GetAttributeCount(elem); } -struct CharInfo { - int charCode; - float left; - float bottom; - float right; - float top; - float fontSize; -}; - SHIM_EXPORT int FPDF_CALLCONV pdfium4j_text_get_chars_with_bounds(FPDF_TEXTPAGE text_page, int start_index, int count, void* out_data) { if (!text_page || !out_data || start_index < 0 || count <= 0) return 0; - CharInfo* out = static_cast(out_data); + std::span out(static_cast(out_data), static_cast(count)); int actual = 0; for (int i = 0; i < count; i++) { @@ -211,17 +242,20 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_text_get_chars_with_bounds(FPDF_TEXTPAGE SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_meta_utf8(FPDF_DOCUMENT doc, const char* key, char* buf, int buf_len) { if (!doc || !key) return 0; - unsigned long length = FPDF_GetMetaText(doc, key, nullptr, 0); + std::string_view k(key); + unsigned long length = FPDF_GetMetaText(doc, k.data(), nullptr, 0); if (length <= 2) return 0; - std::vector utf16(length); - FPDF_GetMetaText(doc, key, utf16.data(), length); + g_utf16_scratch.resize(length / 2); + FPDF_GetMetaText(doc, k.data(), g_utf16_scratch.data(), length); - std::string utf8 = pdfium4j::utf16_to_utf8(reinterpret_cast(utf16.data()), (length / 2) - 1); + std::string utf8 = pdfium4j::utf16_to_utf8(g_utf16_scratch.data(), (length / 2) - 1); - int needed = (int)utf8.length() + 1; + int needed = static_cast(utf8.length()) + 1; if (buf && buf_len >= needed) { - memcpy(buf, utf8.c_str(), utf8.length() + 1); + std::span out(buf, static_cast(buf_len)); + std::copy(utf8.begin(), utf8.end(), out.begin()); + out[utf8.length()] = '\0'; } return needed; } @@ -229,7 +263,9 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_meta_utf8(FPDF_DOCUMENT doc, const ch SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_xmp_metadata(FPDF_DOCUMENT doc, char* buf, int buf_len) { if (!doc) return 0; - static auto func = (Type_FPDF_GetXMPMetadata)SYMLOOKUP("FPDF_GetXMPMetadata"); + resolve_optional_symbols_once(); + + Type_FPDF_GetXMPMetadata func = g_fpdf_get_xmp_metadata; if (func) { unsigned long length = func(doc, nullptr, 0); if (length == 0) return 0; @@ -242,29 +278,25 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_xmp_metadata(FPDF_DOCUMENT doc, char* unsigned long length = FPDF_GetMetaText(doc, "xmp", nullptr, 0); if (length <= 2) return 0; - std::vector utf16(length); - FPDF_GetMetaText(doc, "xmp", utf16.data(), length); + g_utf16_scratch.resize(length / 2); + FPDF_GetMetaText(doc, "xmp", g_utf16_scratch.data(), length); - std::string utf8 = pdfium4j::utf16_to_utf8(reinterpret_cast(utf16.data()), (length / 2) - 1); + std::string utf8 = pdfium4j::utf16_to_utf8(g_utf16_scratch.data(), (length / 2) - 1); - int needed = (int)utf8.length() + 1; + int needed = static_cast(utf8.length()) + 1; if (buf && buf_len >= needed) { - memcpy(buf, utf8.c_str(), utf8.length() + 1); + std::span out(buf, static_cast(buf_len)); + std::copy(utf8.begin(), utf8.end(), out.begin()); + out[utf8.length()] = '\0'; } return needed; } SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_custom_xmp(FPDF_DOCUMENT doc, const char* ns_uri, const char* key, char* buf, int buf_len) { if (!doc || !ns_uri || !key) return 0; - - int xmp_len = pdfium4j_get_xmp_metadata(doc, nullptr, 0); - if (xmp_len <= 1) return 0; - - std::vector raw(xmp_len); - pdfium4j_get_xmp_metadata(doc, raw.data(), xmp_len); - + pugi::xml_document xdoc; - if (!xdoc.load_buffer(raw.data(), xmp_len)) return 0; + if (!load_xmp_document(doc, xdoc)) return 0; pugi::xpath_variable_set vars; vars.add("ns", pugi::xpath_type_string); @@ -286,15 +318,9 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_custom_xmp(FPDF_DOCUMENT doc, const c SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_custom_xmp_bag(FPDF_DOCUMENT doc, const char* ns_uri, const char* key, char* buf, int buf_len) { if (!doc || !ns_uri || !key) return 0; - - int xmp_len = pdfium4j_get_xmp_metadata(doc, nullptr, 0); - if (xmp_len <= 1) return 0; - - std::vector raw(xmp_len); - pdfium4j_get_xmp_metadata(doc, raw.data(), xmp_len); - + pugi::xml_document xdoc; - if (!xdoc.load_buffer(raw.data(), xmp_len)) return 0; + if (!load_xmp_document(doc, xdoc)) return 0; pugi::xpath_variable_set vars; vars.add("ns", pugi::xpath_type_string); @@ -306,7 +332,16 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_custom_xmp_bag(FPDF_DOCUMENT doc, con pugi::xpath_node_set nodes = xdoc.select_nodes(q); if (nodes.empty()) { - return pdfium4j_get_custom_xmp(doc, ns_uri, key, buf, buf_len); + pugi::xpath_query scalar_q("//*[namespace-uri()=$ns and local-name()=$key]", &vars); + pugi::xpath_node_set scalar_nodes = xdoc.select_nodes(scalar_q); + if (scalar_nodes.empty()) return 0; + + std::string scalar = scalar_nodes.first().node().child_value(); + int needed = static_cast(scalar.length()) + 1; + if (buf && buf_len >= needed) { + std::copy_n(scalar.c_str(), scalar.length() + 1, buf); + } + return needed; } int total_needed = 0; @@ -347,7 +382,9 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_read_info_dict(const char* src_path, Pdfi if (!info.isDictionary()) return 0; for (auto const& it : info.getDictAsMap()) { - std::string key = it.first.substr(1); // strip leading slash + const std::string& raw_key = it.first; + std::string key = + (!raw_key.empty() && raw_key[0] == '/') ? raw_key.substr(1) : raw_key; std::string val; QPDFObjectHandle obj = it.second; try { @@ -379,7 +416,9 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_read_info_dict_mem(const char* data, size if (!info.isDictionary()) return 0; for (auto const& it : info.getDictAsMap()) { - std::string key = it.first.substr(1); // strip leading slash + const std::string& raw_key = it.first; + std::string key = + (!raw_key.empty() && raw_key[0] == '/') ? raw_key.substr(1) : raw_key; std::string val; QPDFObjectHandle obj = it.second; try { @@ -399,4 +438,46 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_read_info_dict_mem(const char* data, size } } +SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_xmp_qpdf(const char* src_path, char* buf, int buf_len) { + if (!src_path) return -1; + try { + QPDF qpdf; + qpdf.processFile(src_path); + QPDFObjectHandle root = qpdf.getRoot(); + if (!root.hasKey("/Metadata")) return 0; + QPDFObjectHandle xmp = root.getKey("/Metadata"); + if (!xmp.isStream()) return 0; + + std::shared_ptr buffer = xmp.getStreamData(qpdf_dl_all); + int needed = (int)buffer->getSize(); + if (buf && buf_len >= needed) { + memcpy(buf, buffer->getBuffer(), needed); + } + return needed; + } catch (...) { + return -4; + } +} + +SHIM_EXPORT int FPDF_CALLCONV pdfium4j_get_xmp_qpdf_mem(const char* data, size_t len, char* buf, int buf_len) { + if (!data || len == 0) return -1; + try { + QPDF qpdf; + qpdf.processMemoryFile("mem", data, len, ""); + QPDFObjectHandle root = qpdf.getRoot(); + if (!root.hasKey("/Metadata")) return 0; + QPDFObjectHandle xmp = root.getKey("/Metadata"); + if (!xmp.isStream()) return 0; + + std::shared_ptr buffer = xmp.getStreamData(qpdf_dl_all); + int needed = (int)buffer->getSize(); + if (buf && buf_len >= needed) { + memcpy(buf, buffer->getBuffer(), needed); + } + return needed; + } catch (...) { + return -4; + } +} + } // extern "C" diff --git a/shim/src/pdfium4j_utils.h b/shim/src/pdfium4j_utils.h index 41021f4..32f8d78 100644 --- a/shim/src/pdfium4j_utils.h +++ b/shim/src/pdfium4j_utils.h @@ -2,9 +2,31 @@ #include #include #include +#include +#include +#include +#include +#include "pdfium4j_shim.h" namespace pdfium4j { +// ABI Safety checks +static_assert(sizeof(pdfium4j_char_info_t) == 24, "pdfium4j_char_info_t size mismatch"); +static_assert(alignof(pdfium4j_char_info_t) == 4, "pdfium4j_char_info_t alignment mismatch"); + +// RAII Deleters +struct DocumentDeleter { void operator()(FPDF_DOCUMENT d) const { if (d) FPDF_CloseDocument(d); } }; +struct PageDeleter { void operator()(FPDF_PAGE p) const { if (p) FPDF_ClosePage(p); } }; +struct TextPageDeleter { void operator()(FPDF_TEXTPAGE t) const { if (t) FPDFText_ClosePage(t); } }; +struct StructTreeDeleter { void operator()(FPDF_STRUCTTREE s) const { if (s) FPDF_StructTree_Close(s); } }; +struct BookmarkDeleter { void operator()(FPDF_BOOKMARK b) const { /* Bookmarks are not closed individually */ } }; + +// RAII Wrappers +using ScopedDocument = std::unique_ptr; +using ScopedPage = std::unique_ptr; +using ScopedTextPage = std::unique_ptr; +using ScopedStructTree = std::unique_ptr; + std::vector utf8_to_utf16(const std::string& utf8); std::string utf16_to_utf8(const uint16_t* utf16, size_t length); std::string trim(std::string s); diff --git a/shim/src/pdfium4j_write.cpp b/shim/src/pdfium4j_write.cpp index 8963057..72a19e0 100644 --- a/shim/src/pdfium4j_write.cpp +++ b/shim/src/pdfium4j_write.cpp @@ -1,11 +1,106 @@ #include "pdfium4j_shim.h" +#include "pdfium4j_utils.h" +#include #include -#include #include +#include #include +#include +#include +#include +#include #include +#include #include -#include + +namespace { + +struct SaveParams { + std::string_view xmp; + std::span pairs; + int metadata_count; +}; + +class CallbackPipeline final : public Pipeline { +public: + using WriteFunc = int (*)(void*, const void*, size_t); + + CallbackPipeline(WriteFunc fn, void* ctx) + : Pipeline("callback", nullptr), m_fn(fn), m_ctx(ctx) {} + + void write(unsigned char const* data, size_t len) override { + if (m_fn(m_ctx, data, len) != 1) { + throw std::runtime_error("write callback failed"); + } + } + + void finish() override {} + +private: + WriteFunc m_fn; + void* m_ctx; +}; + +[[nodiscard]] static int inject_metadata(QPDF& qpdf, const SaveParams& params) noexcept { + if (qpdf.isEncrypted()) { + return -3; + } + + if (!params.xmp.empty()) { + try { + QPDFObjectHandle root = qpdf.getRoot(); + QPDFObjectHandle xmp_stream = + QPDFObjectHandle::newStream(&qpdf, std::string(params.xmp)); + QPDFObjectHandle xmp_dict = xmp_stream.getDict(); + xmp_dict.replaceKey("/Type", QPDFObjectHandle::newName("/Metadata")); + xmp_dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/XML")); + root.replaceKey("/Metadata", xmp_stream); + } catch (...) { + return -5; + } + } + + if (params.metadata_count > 0 && !params.pairs.empty()) { + try { + QPDFObjectHandle info = qpdf.getTrailer().getKey("/Info"); + if (info.isNull()) { + info = qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); + qpdf.getTrailer().replaceKey("/Info", info); + } + + std::string qkey; + for (int i = 0; i < params.metadata_count; ++i) { + const char* key = params.pairs[static_cast(i) * 2]; + const char* val = params.pairs[static_cast(i) * 2 + 1]; + if (!key || !val) { + continue; + } + + qkey.clear(); + if (key[0] != '/') { + qkey.reserve(std::strlen(key) + 1); + qkey.push_back('/'); + } + qkey += key; + + info.replaceKey(qkey, QPDFObjectHandle::newUnicodeString(val)); + } + } catch (...) { + return -5; + } + } + + return 0; +} + +static void configure_writer(QPDFWriter& writer) { + writer.setPreserveUnreferencedObjects(false); + writer.setLinearization(false); + writer.setObjectStreamMode(qpdf_o_generate); + writer.setCompressStreams(true); +} + +} // namespace extern "C" { @@ -36,66 +131,39 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_save_with_metadata_native( if (metadata_count < 0) return -1; if (metadata_count > 0 && !metadata_pairs) return -1; + std::span pairs( + metadata_pairs ? metadata_pairs : nullptr, + static_cast(metadata_count) * 2); + std::string_view xmp = + (xmp_metadata && xmp_len > 0) + ? std::string_view(xmp_metadata, static_cast(xmp_len)) + : std::string_view(); + + SaveParams params{xmp, pairs, metadata_count}; + try { QPDF qpdf; try { qpdf.processFile(src_path); - } catch (...) { - return -2; // Failed to open/parse source - } - - if (qpdf.isEncrypted()) { - return -3; // Reject encrypted writes - } - - // 1. Inject XMP Metadata if provided - if (xmp_metadata && xmp_len > 0) { - try { - QPDFObjectHandle root = qpdf.getRoot(); - QPDFObjectHandle xmp_stream = QPDFObjectHandle::newStream(&qpdf, std::string(xmp_metadata, xmp_len)); - QPDFObjectHandle xmp_dict = xmp_stream.getDict(); - xmp_dict.replaceKey("/Type", QPDFObjectHandle::newName("/Metadata")); - xmp_dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/XML")); - root.replaceKey("/Metadata", xmp_stream); - } catch (...) { - return -5; // Failed to inject XMP - } + } catch (const std::exception&) { + return -2; } - // 2. Update Info Dictionary if provided - if (metadata_pairs && metadata_count > 0) { - try { - QPDFObjectHandle info = qpdf.getTrailer().getKey("/Info"); - if (info.isNull()) { - info = qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); - qpdf.getTrailer().replaceKey("/Info", info); - } - - for (int i = 0; i < metadata_count; i++) { - const char* key = metadata_pairs[i * 2]; - const char* val = metadata_pairs[i * 2 + 1]; - if (key && val) { - std::string qkey = (key[0] == '/') ? key : ("/" + std::string(key)); - info.replaceKey(qkey, QPDFObjectHandle::newUnicodeString(val)); - } - } - } catch (...) { - return -5; // Failed to inject Info metadata - } + if (int rc = inject_metadata(qpdf, params); rc != 0) { + return rc; } - // 3. Write Output try { QPDFWriter writer(qpdf, dst_path); - writer.setPreserveUnreferencedObjects(false); + configure_writer(writer); writer.write(); } catch (...) { - return -6; // Failed to write output + return -6; } return 0; } catch (...) { - return -4; // General error + return -4; } } @@ -137,76 +205,82 @@ SHIM_EXPORT int FPDF_CALLCONV pdfium4j_save_with_metadata_mem_native( if (metadata_count < 0) return -1; if (metadata_count > 0 && !metadata_pairs) return -1; + std::span pairs( + metadata_pairs ? metadata_pairs : nullptr, + static_cast(metadata_count) * 2); + std::string_view xmp = + (xmp_metadata && xmp_len > 0) + ? std::string_view(xmp_metadata, static_cast(xmp_len)) + : std::string_view(); + + SaveParams params{xmp, pairs, metadata_count}; + try { QPDF qpdf; try { qpdf.processMemoryFile("memory", static_cast(src_buf), src_len); - } catch (...) { + } catch (const std::exception&) { return -2; } - if (qpdf.isEncrypted()) { - return -3; + if (int rc = inject_metadata(qpdf, params); rc != 0) { + return rc; } - // 1. Inject XMP - if (xmp_metadata && xmp_len > 0) { - try { - QPDFObjectHandle root = qpdf.getRoot(); - QPDFObjectHandle xmp_stream = QPDFObjectHandle::newStream(&qpdf, std::string(xmp_metadata, xmp_len)); - QPDFObjectHandle xmp_dict = xmp_stream.getDict(); - xmp_dict.replaceKey("/Type", QPDFObjectHandle::newName("/Metadata")); - xmp_dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/XML")); - root.replaceKey("/Metadata", xmp_stream); - } catch (...) { - return -5; - } + try { + CallbackPipeline cp(write_block, pThis); + QPDFWriter writer(qpdf); + configure_writer(writer); + writer.setOutputPipeline(&cp); + writer.write(); + } catch (...) { + return -6; } - // 2. Inject Info - if (metadata_pairs && metadata_count > 0) { - try { - QPDFObjectHandle info = qpdf.getTrailer().getKey("/Info"); - if (info.isNull()) { - info = qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); - qpdf.getTrailer().replaceKey("/Info", info); - } + return 0; + } catch (...) { + return -4; + } +} - for (int i = 0; i < metadata_count; i++) { - const char* key = metadata_pairs[i * 2]; - const char* val = metadata_pairs[i * 2 + 1]; - if (key && val) { - std::string qkey = (key[0] == '/') ? key : ("/" + std::string(key)); - info.replaceKey(qkey, QPDFObjectHandle::newUnicodeString(val)); - } - } - } catch (...) { - return -5; - } - } +SHIM_EXPORT int FPDF_CALLCONV pdfium4j_save_with_metadata_mem_to_file_native( + const void* src_buf, + size_t src_len, + const char* dst_path, + const char* xmp_metadata, + int xmp_len, + const char** metadata_pairs, + int metadata_count +) { + if (!src_buf || src_len == 0 || !dst_path) return -1; + if (metadata_count < 0) return -1; + if (metadata_count > 0 && !metadata_pairs) return -1; + + std::span pairs( + metadata_pairs ? metadata_pairs : nullptr, + static_cast(metadata_count) * 2); + std::string_view xmp = + (xmp_metadata && xmp_len > 0) + ? std::string_view(xmp_metadata, static_cast(xmp_len)) + : std::string_view(); - // 3. Write via callback + SaveParams params{xmp, pairs, metadata_count}; + + try { + QPDF qpdf; try { - class CallbackPipeline : public Pipeline { - public: - CallbackPipeline(int (*write_block)(void*, const void*, size_t), void* pThis) - : Pipeline("callback", nullptr), write_block(write_block), pThis(pThis) {} - virtual ~CallbackPipeline() {} - virtual void write(unsigned char const* data, size_t len) override { - if (write_block(pThis, data, len) != 1) { - throw std::runtime_error("write failed"); - } - } - virtual void finish() override {} - private: - int (*write_block)(void*, const void*, size_t); - void* pThis; - }; + qpdf.processMemoryFile("memory", static_cast(src_buf), src_len); + } catch (const std::exception&) { + return -2; + } - CallbackPipeline cp(write_block, pThis); - QPDFWriter writer(qpdf); - writer.setPreserveUnreferencedObjects(false); - writer.setOutputPipeline(&cp); + if (int rc = inject_metadata(qpdf, params); rc != 0) { + return rc; + } + + try { + QPDFWriter writer(qpdf, dst_path); + configure_writer(writer); writer.write(); } catch (...) { return -6; diff --git a/src/main/java/org/grimmory/pdfium4j/PdfDocument.java b/src/main/java/org/grimmory/pdfium4j/PdfDocument.java index 9a8d749..8985bb4 100644 --- a/src/main/java/org/grimmory/pdfium4j/PdfDocument.java +++ b/src/main/java/org/grimmory/pdfium4j/PdfDocument.java @@ -98,7 +98,7 @@ private static final class RegistryHolder { private final PdfProcessingPolicy policy; private final int sourceFileVersion; private final Thread ownerThread; - private final List openPages = new ArrayList<>(8); + private final java.util.SequencedCollection openPages = new ArrayList<>(8); private volatile boolean closed = false; @SuppressWarnings("PMD.UnusedPrivateField") @@ -111,14 +111,26 @@ MemorySegment handle() { return handle; } + /** + * Configures the amount of padding lines added to XMP metadata packets globally. + * + * @param lines number of padding lines (default 20) + */ + public static void setGlobalXmpPadding(int lines) { + XMP_WRITER.setPaddingLines(lines); + } + private volatile int cachedPageCount = -1; - /** Lazy-parsed fallback Info dict key→value map (populated at most once per document). */ - private volatile Map cachedFallbackMeta; + /** Lazy-parsed fallback Info dict key-value map (populated at most once per document). */ + private final StableValue> cachedFallbackMeta = StableValue.of(); /** Memoized XMP bytes from file-system fallback path. */ private volatile byte[] cachedFallbackXmp; + /** Cached parsed XMP metadata. */ + private volatile XmpMetadata cachedXmp; + private final Map pendingMetadata = new EnumMap<>(MetadataTag.class); private final Map pendingCustomMetadata = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -585,10 +597,10 @@ public synchronized List search(String query) { return results; } - // Use trigram index to filter candidates + // Use trigram index to filter candidates. long[] queryHashes = TrigramTokenizer.generateTrigramHashes(normalized); if (queryHashes.length == 0) { - // Query too short for trigrams, fallback to linear scan + // Query too short for trigrams, fallback to linear scan. return searchFallback(normalized); } @@ -599,10 +611,10 @@ public synchronized List search(String query) { candidates.add(pages); } - // Intersection of candidate page lists + // Intersection of candidate page lists. List intersected = intersect(candidates); - // Final verification (exact match) + // Final verification (exact match). List results = new ArrayList<>(intersected.size()); for (int pageIdx : intersected) { try (PdfPage p = page(pageIdx)) { @@ -630,7 +642,7 @@ private List searchFallback(String normalized) { private static List intersect(List lists) { if (lists.isEmpty()) return List.of(); - // Sort lists by size to optimize intersection + // Sort lists by size to optimize intersection. lists.sort(Comparator.comparingInt(a -> a.length)); int[] first = lists.getFirst(); @@ -724,7 +736,7 @@ public void withMetadataUtf16(MetadataTag tag, MemorySegmentConsumer consumer) { throw new IllegalArgumentException("consumer must not be null"); } - // Check pending metadata first + // Check pending metadata first. if (pendingMetadata.containsKey(tag)) { String pending = pendingMetadata.get(tag); if (pending == null || pending.isEmpty()) return; @@ -807,12 +819,9 @@ private synchronized Optional metadataFallback(MetadataTag tag) { * Returns (and lazily builds) the per-document Info-dict cache. The file is mapped at most once * regardless of how many metadata tags are requested. */ - private synchronized Map getOrBuildFallbackMeta() { - if (cachedFallbackMeta != null) return cachedFallbackMeta; - Map result = - PdfDocumentMetadata.readAllInfoDict(sourcePath, sourceBytes, sourceSegment); - cachedFallbackMeta = result; - return result; + private Map getOrBuildFallbackMeta() { + return cachedFallbackMeta.orElseSet( + () -> PdfDocumentMetadata.readAllInfoDict(sourcePath, sourceBytes, sourceSegment)); } /** @@ -820,16 +829,16 @@ private synchronized Map getOrBuildFallbackMeta() { * re-mapping and repeated Pattern.compile() calls. */ public Optional metadata(String customKey) { - // Check known enum-backed tags first + // Check known enum-backed tags first. for (MetadataTag tag : METADATA_TAGS) { if (tag.pdfKey().equalsIgnoreCase(customKey)) return metadata(tag); } - // Check pending custom metadata first + // Check pending custom metadata first. String pending = pendingCustomMetadata.get(customKey); if (pending != null) { return pending.isEmpty() ? Optional.empty() : Optional.of(pending); } - // Support arbitrary /Info keys (e.g. /Language) via fpdfGetMetaText + // Support arbitrary /Info keys (e.g. /Language) via fpdfGetMetaText. ensureOpen(); Optional val = tryGetMetaText(customKey); if (val.isPresent()) { @@ -902,17 +911,21 @@ public Map metadata() { * Returns high-level structured metadata for the document, combining Info dictionary and XMP * data. */ - public BookMetadata bookMetadata() { + public synchronized BookMetadata bookMetadata() { ensureOpen(); - XmpMetadata xmp = XmpMetadataParser.parseFrom(this); + if (cachedXmp == null) { + cachedXmp = XmpMetadataParser.parseFrom(this); + } + XmpMetadata xmp = cachedXmp; Map info = metadata(); return new PdfBookMetadata( metadata(MetadataTag.TITLE).or(xmp::title), + xmp.findField("subtitle").or(() -> metadata("Subtitle")), authors(info, xmp), xmp.calibreSeries().or(() -> metadata("Series")), xmp.calibreSeriesIndex().stream() - .mapToObj(d -> (float) d) + .map(Double::floatValue) .findFirst() .or( () -> @@ -940,7 +953,7 @@ public BookMetadata bookMetadata() { xmp.description().or(() -> metadata(MetadataTag.SUBJECT)), metadata(MetadataTag.PRODUCER).or(xmp::publisher), xmp, - info); + xmp.customFields()); } private List authors(Map info, XmpMetadata xmp) { @@ -1126,11 +1139,13 @@ public void setMetadata(Map metadata) { public void setXmpMetadata(String xmp) { ensureOpen(); pendingXmp = (xmp == null || xmp.isBlank()) ? null : new XmpUpdate.Raw(xmp); + cachedXmp = null; } public void setXmpMetadata(XmpMetadata metadata) { ensureOpen(); pendingXmp = new XmpUpdate.Structured(metadata); + cachedXmp = metadata; } public PdfPage insertBlankPage(int index, PageSize size) { diff --git a/src/main/java/org/grimmory/pdfium4j/PdfPage.java b/src/main/java/org/grimmory/pdfium4j/PdfPage.java index 1cf4c9b..cc99eb5 100644 --- a/src/main/java/org/grimmory/pdfium4j/PdfPage.java +++ b/src/main/java/org/grimmory/pdfium4j/PdfPage.java @@ -59,7 +59,7 @@ public final class PdfPage implements AutoCloseable { private final Consumer onClose; private final Runnable onModified; - @SuppressWarnings("PMD.UnusedPrivateField") + @SuppressWarnings("PMD.UnusedPrivateField") // accessed via VarHandle REF_COUNT private int refCount = 1; private volatile boolean closedByUser = false; @@ -148,7 +148,7 @@ public NativeBitmap renderNative(int dpi, RenderFlags flags) { // NativeBitmap owns its own arena as it may be passed across threads or have a longer lifecycle Arena arena = Arena.ofShared(); try { - long sizeBytes = Math.multiplyExact((long) stride, (long) h); + long sizeBytes = Math.multiplyExact(stride, (long) h); MemorySegment dest = arena.allocate(sizeBytes); renderTo(dest, w, h, stride, flags.value(), OPAQUE_WHITE); return new NativeBitmap(w, h, stride, dest, arena); @@ -350,7 +350,13 @@ public RenderResult renderSafe(int dpi, long maxMemoryBytes, RenderFlags flags) return new RenderResult(1, 1, new byte[4]); } - long requiredBytes = w * h * BYTES_PER_PIXEL; + long requiredBytes; + try { + requiredBytes = Math.multiplyExact(Math.multiplyExact((long) w, (long) h), BYTES_PER_PIXEL); + } catch (ArithmeticException e) { + throw new PdfiumRenderException( + "Rendering %dx%d at %d DPI exceeds addressable memory size.".formatted(w, h, dpi), e); + } if (requiredBytes > maxMemoryBytes) { throw new PdfiumRenderException( "Rendering %dx%d at %d DPI requires %d bytes, which exceeds the limit of %d bytes." diff --git a/src/main/java/org/grimmory/pdfium4j/PdfSaver.java b/src/main/java/org/grimmory/pdfium4j/PdfSaver.java index 8d40d3b..c897ecf 100644 --- a/src/main/java/org/grimmory/pdfium4j/PdfSaver.java +++ b/src/main/java/org/grimmory/pdfium4j/PdfSaver.java @@ -13,10 +13,8 @@ import java.lang.invoke.MethodType; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.PosixFilePermissions; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.grimmory.pdfium4j.internal.ShimBindings; @@ -67,37 +65,14 @@ private PdfSaver() {} public static void save(SaveParams params) throws IOException { if (params.targetPath() != null) { // File-to-file save - Path source = params.sourcePath(); - Path tempSource = null; - try { - if (source == null) { - tempSource = Files.createTempFile("pdfium4j-src-", ".pdf"); - try { - if (tempSource.getFileSystem().supportedFileAttributeViews().contains("posix")) { - Files.setPosixFilePermissions( - tempSource, PosixFilePermissions.fromString("rw-------")); - } - } catch (UnsupportedOperationException | IOException ignored) { - // Ignored if POSIX is not supported - } - if (params.sourceBytes() != null) { - Files.write(tempSource, params.sourceBytes()); - } else if (params.sourceSegment() != MemorySegment.NULL) { - try (FileChannel fc = FileChannel.open(tempSource, StandardOpenOption.WRITE)) { - fc.write(params.sourceSegment().asByteBuffer()); - } - } - source = tempSource; - } - saveNative( - source.toString(), - params.targetPath().toString(), - params.pendingXmp(), - params.pendingMetadata(), - params.pendingCustomMetadata()); - } finally { - if (tempSource != null) Files.deleteIfExists(tempSource); - } + saveNative( + params.sourcePath() != null ? params.sourcePath().toString() : null, + params.sourceBytes(), + params.sourceSegment(), + params.targetPath().toString(), + params.pendingXmp(), + params.pendingMetadata(), + params.pendingCustomMetadata()); } else if (params.out() != null) { // Stream-based save (no temp files if we have bytes/segment) if (params.sourceBytes() != null) { @@ -172,7 +147,9 @@ private static void saveFromMemory( } private static void saveNative( - String src, + String srcPath, + byte[] srcBytes, + MemorySegment srcSeg, String dst, XmpUpdate xmp, Map metadata, @@ -184,16 +161,45 @@ private static void saveNative( int rc; try { - rc = - (int) - ShimBindings.pdfium4jSaveWithMetadata() - .invokeExact( - arena.allocateFrom(src), - arena.allocateFrom(dst), - xmpRes.segment(), - xmpRes.length(), - metaRes.segment(), - metaRes.count()); + if (srcPath != null) { + rc = + (int) + ShimBindings.pdfium4jSaveWithMetadata() + .invokeExact( + arena.allocateFrom(srcPath), + arena.allocateFrom(dst), + xmpRes.segment(), + xmpRes.length(), + metaRes.segment(), + metaRes.count()); + } else if (srcBytes != null) { + MemorySegment dataSeg = arena.allocateFrom(JAVA_BYTE, srcBytes); + rc = + (int) + ShimBindings.pdfium4jSaveWithMetadataMemToFile() + .invokeExact( + dataSeg, + (long) srcBytes.length, + arena.allocateFrom(dst), + xmpRes.segment(), + xmpRes.length(), + metaRes.segment(), + metaRes.count()); + } else if (srcSeg != MemorySegment.NULL) { + rc = + (int) + ShimBindings.pdfium4jSaveWithMetadataMemToFile() + .invokeExact( + srcSeg, + srcSeg.byteSize(), + arena.allocateFrom(dst), + xmpRes.segment(), + xmpRes.length(), + metaRes.segment(), + metaRes.count()); + } else { + throw new IOException("No source provided for native save"); + } } catch (Throwable t) { throw new IOException("Native save failed", t); } @@ -234,12 +240,16 @@ private static MetaResult prepareMetadataPairs( MemorySegment metaPairs = arena.allocate(ValueLayout.ADDRESS, (long) totalMetaSize * 2); int i = 0; for (Map.Entry entry : metadata.entrySet()) { - metaPairs.setAtIndex(ValueLayout.ADDRESS, i++, arena.allocateFrom(entry.getKey().pdfKey())); - metaPairs.setAtIndex(ValueLayout.ADDRESS, i++, arena.allocateFrom(entry.getValue())); + metaPairs.setAtIndex(ValueLayout.ADDRESS, i, arena.allocateFrom(entry.getKey().pdfKey())); + i++; + metaPairs.setAtIndex(ValueLayout.ADDRESS, i, arena.allocateFrom(entry.getValue())); + i++; } for (Map.Entry entry : customMetadata.entrySet()) { - metaPairs.setAtIndex(ValueLayout.ADDRESS, i++, arena.allocateFrom(entry.getKey())); - metaPairs.setAtIndex(ValueLayout.ADDRESS, i++, arena.allocateFrom(entry.getValue())); + metaPairs.setAtIndex(ValueLayout.ADDRESS, i, arena.allocateFrom(entry.getKey())); + i++; + metaPairs.setAtIndex(ValueLayout.ADDRESS, i, arena.allocateFrom(entry.getValue())); + i++; } return new MetaResult(metaPairs, totalMetaSize); } diff --git a/src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java b/src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java index c6da010..8a7de78 100644 --- a/src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java +++ b/src/main/java/org/grimmory/pdfium4j/PdfiumLibrary.java @@ -71,6 +71,10 @@ public static void initialize() { AnnotBindings.checkRequired(); ShimBindings.checkRequired(); + if (ShimBindings.pdfium4jResolveOptionalSymbols() != null) { + ShimBindings.pdfium4jResolveOptionalSymbols().invokeExact(); + } + // Set renderer type if supported if (ViewBindings.fpdfSetRendererType() != null) { ViewBindings.fpdfSetRendererType().invokeExact(rendererType); diff --git a/src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java b/src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java index 99d9e3d..01da524 100644 --- a/src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java +++ b/src/main/java/org/grimmory/pdfium4j/XmpMetadataParser.java @@ -167,7 +167,8 @@ private static String sanitizeXmpString(String xmp) { for (int i = 0; i < xmp.length(); i++) { char c = xmp.charAt(i); if (c != '\0' && c != '\uFEFF') { - buffer[len++] = c; + buffer[len] = c; + len++; } } diff --git a/src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java b/src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java index a36cbe9..bcb2a05 100644 --- a/src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java +++ b/src/main/java/org/grimmory/pdfium4j/XmpMetadataWriter.java @@ -68,8 +68,21 @@ public final class XmpMetadataWriter { private static final String RDF_SEQ_END = " \n"; private static final String ATTRIBUTE_END = "\">\n"; + private boolean paddingEnabled = true; + private int paddingLines = 20; private final Map customNamespaces = LinkedHashMap.newLinkedHashMap(8); + public synchronized XmpMetadataWriter setPaddingEnabled(boolean enabled) { + this.paddingEnabled = enabled; + return this; + } + + public synchronized XmpMetadataWriter setPaddingLines(int lines) { + if (lines < 0) throw new IllegalArgumentException("Padding lines must be >= 0"); + this.paddingLines = lines; + return this; + } + public synchronized XmpMetadataWriter registerNamespace(String prefix, String uri) { Objects.requireNonNull(prefix, "prefix"); Objects.requireNonNull(uri, "uri"); @@ -114,12 +127,7 @@ private interface Sink { void write(int cp) throws IOException; } - private static final class WriterSink implements Sink { - private final Writer writer; - - WriterSink(Writer writer) { - this.writer = writer; - } + private record WriterSink(Writer writer) implements Sink { @Override public void write(String s) throws IOException { @@ -137,12 +145,7 @@ public void write(int cp) throws IOException { } } - private static final class OutputStreamSink implements Sink { - private final OutputStream out; - - OutputStreamSink(OutputStream out) { - this.out = out; - } + private record OutputStreamSink(OutputStream out) implements Sink { @Override public void write(String s) throws IOException { @@ -447,10 +450,11 @@ private static void writeBag(Sink s, String tag, List values) throws IOE s.write(">\n"); } - private static void writePadding(Sink s) throws IOException { + private void writePadding(Sink s) throws IOException { + if (!paddingEnabled || paddingLines <= 0) return; String padding = " \n"; - for (int i = 0; i < 20; i++) s.write(padding); + for (int i = 0; i < paddingLines; i++) s.write(padding); } private static void writeEscaped(Sink s, String text) throws IOException { @@ -552,11 +556,9 @@ private void processField( int colonIdx = key.indexOf(':'); if (colonIdx > 0) { String prefix = key.substring(0, colonIdx); - if ("xmp".equals(prefix)) unprefixed.put(key, value); - else - grouped - .computeIfAbsent(prefix, _ -> LinkedHashMap.newLinkedHashMap(8)) - .put(key.substring(colonIdx + 1), value); + grouped + .computeIfAbsent(prefix, _ -> LinkedHashMap.newLinkedHashMap(8)) + .put(key.substring(colonIdx + 1), value); } else unprefixed.put(key, value); } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java index 514668a..e017740 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/AnnotBindings.java @@ -47,103 +47,49 @@ public static void checkRequired() { ValueLayout.JAVA_FLOAT.withName("right"), ValueLayout.JAVA_FLOAT.withName("bottom")); - private static volatile MethodHandle FPDFPage_GetAnnotCount_MH = null; + private static final StableValue FPDFPage_GetAnnotCount_V = StableValue.of(); public static MethodHandle fpdfPageGetAnnotCount() { - MethodHandle mh = FPDFPage_GetAnnotCount_MH; - if (mh == null) { - synchronized (AnnotBindings.class) { - mh = FPDFPage_GetAnnotCount_MH; - if (mh == null) { - mh = find("FPDFPage_GetAnnotCount", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFPage_GetAnnotCount_MH = mh; - } - } - } - return mh; + return FPDFPage_GetAnnotCount_V.orElseSet( + () -> find("FPDFPage_GetAnnotCount", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFPage_GetAnnot_MH = null; + private static final StableValue FPDFPage_GetAnnot_V = StableValue.of(); public static MethodHandle fpdfPageGetAnnot() { - MethodHandle mh = FPDFPage_GetAnnot_MH; - if (mh == null) { - synchronized (AnnotBindings.class) { - mh = FPDFPage_GetAnnot_MH; - if (mh == null) { - mh = find("FPDFPage_GetAnnot", FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), true); - FPDFPage_GetAnnot_MH = mh; - } - } - } - return mh; + return FPDFPage_GetAnnot_V.orElseSet( + () -> find("FPDFPage_GetAnnot", FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), true)); } - private static volatile MethodHandle FPDFPage_CloseAnnot_MH = null; + private static final StableValue FPDFPage_CloseAnnot_V = StableValue.of(); public static MethodHandle fpdfPageCloseAnnot() { - MethodHandle mh = FPDFPage_CloseAnnot_MH; - if (mh == null) { - synchronized (AnnotBindings.class) { - mh = FPDFPage_CloseAnnot_MH; - if (mh == null) { - mh = find("FPDFPage_CloseAnnot", FunctionDescriptor.ofVoid(C_POINTER), true); - FPDFPage_CloseAnnot_MH = mh; - } - } - } - return mh; + return FPDFPage_CloseAnnot_V.orElseSet( + () -> find("FPDFPage_CloseAnnot", FunctionDescriptor.ofVoid(C_POINTER), true)); } - private static volatile MethodHandle FPDFAnnot_GetSubtype_MH = null; + private static final StableValue FPDFAnnot_GetSubtype_V = StableValue.of(); public static MethodHandle fpdfAnnotGetSubtype() { - MethodHandle mh = FPDFAnnot_GetSubtype_MH; - if (mh == null) { - synchronized (AnnotBindings.class) { - mh = FPDFAnnot_GetSubtype_MH; - if (mh == null) { - mh = find("FPDFAnnot_GetSubtype", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFAnnot_GetSubtype_MH = mh; - } - } - } - return mh; + return FPDFAnnot_GetSubtype_V.orElseSet( + () -> find("FPDFAnnot_GetSubtype", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFAnnot_GetStringValue_MH = null; + private static final StableValue FPDFAnnot_GetStringValue_V = StableValue.of(); public static MethodHandle fpdfAnnotGetStringValue() { - MethodHandle mh = FPDFAnnot_GetStringValue_MH; - if (mh == null) { - synchronized (AnnotBindings.class) { - mh = FPDFAnnot_GetStringValue_MH; - if (mh == null) { - mh = - find( - "FPDFAnnot_GetStringValue", - FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_POINTER, C_LONG), - false); - FPDFAnnot_GetStringValue_MH = mh; - } - } - } - return mh; + return FPDFAnnot_GetStringValue_V.orElseSet( + () -> + find( + "FPDFAnnot_GetStringValue", + FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_POINTER, C_LONG), + false)); } - private static volatile MethodHandle FPDFAnnot_GetRect_MH = null; + private static final StableValue FPDFAnnot_GetRect_V = StableValue.of(); public static MethodHandle fpdfAnnotGetRect() { - MethodHandle mh = FPDFAnnot_GetRect_MH; - if (mh == null) { - synchronized (AnnotBindings.class) { - mh = FPDFAnnot_GetRect_MH; - if (mh == null) { - mh = find("FPDFAnnot_GetRect", FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), true); - FPDFAnnot_GetRect_MH = mh; - } - } - } - return mh; + return FPDFAnnot_GetRect_V.orElseSet( + () -> find("FPDFAnnot_GetRect", FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), true)); } } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java index 66f551f..88df3a8 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/AttachmentBindings.java @@ -12,6 +12,7 @@ import java.util.Optional; /** FFM bindings for PDFium document attachment functions from {@code fpdf_attachment.h}. */ +@SuppressWarnings("preview") public final class AttachmentBindings { private AttachmentBindings() {} diff --git a/src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java index 696e323..c116692 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/BitmapBindings.java @@ -36,143 +36,69 @@ public static void checkRequired() { } } - private static volatile MethodHandle FPDFBitmap_Create_MH = null; + private static final StableValue FPDFBitmap_Create_V = StableValue.of(); public static MethodHandle fpdfBitmapCreate() { - MethodHandle mh = FPDFBitmap_Create_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_Create_MH; - if (mh == null) { - mh = - find( - "FPDFBitmap_Create", - FunctionDescriptor.of(C_POINTER, C_INT, C_INT, C_INT), - false); - FPDFBitmap_Create_MH = mh; - } - } - } - return mh; + return FPDFBitmap_Create_V.orElseSet( + () -> + find( + "FPDFBitmap_Create", FunctionDescriptor.of(C_POINTER, C_INT, C_INT, C_INT), false)); } - private static volatile MethodHandle FPDFBitmap_CreateEx_MH = null; + private static final StableValue FPDFBitmap_CreateEx_V = StableValue.of(); public static MethodHandle fpdfBitmapCreateEx() { - MethodHandle mh = FPDFBitmap_CreateEx_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_CreateEx_MH; - if (mh == null) { - mh = - find( - "FPDFBitmap_CreateEx", - FunctionDescriptor.of(C_POINTER, C_INT, C_INT, C_INT, C_POINTER, C_INT), - false); - FPDFBitmap_CreateEx_MH = mh; - } - } - } - return mh; + return FPDFBitmap_CreateEx_V.orElseSet( + () -> + find( + "FPDFBitmap_CreateEx", + FunctionDescriptor.of(C_POINTER, C_INT, C_INT, C_INT, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle FPDFBitmap_FillRect_MH = null; + private static final StableValue FPDFBitmap_FillRect_V = StableValue.of(); public static MethodHandle fpdfBitmapFillRect() { - MethodHandle mh = FPDFBitmap_FillRect_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_FillRect_MH; - if (mh == null) { - mh = - find( - "FPDFBitmap_FillRect", - FunctionDescriptor.ofVoid(C_POINTER, C_INT, C_INT, C_INT, C_INT, C_LONG), - false); - FPDFBitmap_FillRect_MH = mh; - } - } - } - return mh; + return FPDFBitmap_FillRect_V.orElseSet( + () -> + find( + "FPDFBitmap_FillRect", + FunctionDescriptor.ofVoid(C_POINTER, C_INT, C_INT, C_INT, C_INT, C_LONG), + false)); } - private static volatile MethodHandle FPDFBitmap_GetBuffer_MH = null; + private static final StableValue FPDFBitmap_GetBuffer_V = StableValue.of(); public static MethodHandle fpdfBitmapGetBuffer() { - MethodHandle mh = FPDFBitmap_GetBuffer_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_GetBuffer_MH; - if (mh == null) { - mh = find("FPDFBitmap_GetBuffer", FunctionDescriptor.of(C_POINTER, C_POINTER), false); - FPDFBitmap_GetBuffer_MH = mh; - } - } - } - return mh; + return FPDFBitmap_GetBuffer_V.orElseSet( + () -> find("FPDFBitmap_GetBuffer", FunctionDescriptor.of(C_POINTER, C_POINTER), false)); } - private static volatile MethodHandle FPDFBitmap_GetWidth_MH = null; + private static final StableValue FPDFBitmap_GetWidth_V = StableValue.of(); public static MethodHandle fpdfBitmapGetWidth() { - MethodHandle mh = FPDFBitmap_GetWidth_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_GetWidth_MH; - if (mh == null) { - mh = find("FPDFBitmap_GetWidth", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFBitmap_GetWidth_MH = mh; - } - } - } - return mh; + return FPDFBitmap_GetWidth_V.orElseSet( + () -> find("FPDFBitmap_GetWidth", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFBitmap_GetHeight_MH = null; + private static final StableValue FPDFBitmap_GetHeight_V = StableValue.of(); public static MethodHandle fpdfBitmapGetHeight() { - MethodHandle mh = FPDFBitmap_GetHeight_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_GetHeight_MH; - if (mh == null) { - mh = find("FPDFBitmap_GetHeight", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFBitmap_GetHeight_MH = mh; - } - } - } - return mh; + return FPDFBitmap_GetHeight_V.orElseSet( + () -> find("FPDFBitmap_GetHeight", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFBitmap_GetStride_MH = null; + private static final StableValue FPDFBitmap_GetStride_V = StableValue.of(); public static MethodHandle fpdfBitmapGetStride() { - MethodHandle mh = FPDFBitmap_GetStride_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_GetStride_MH; - if (mh == null) { - mh = find("FPDFBitmap_GetStride", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFBitmap_GetStride_MH = mh; - } - } - } - return mh; + return FPDFBitmap_GetStride_V.orElseSet( + () -> find("FPDFBitmap_GetStride", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFBitmap_Destroy_MH = null; + private static final StableValue FPDFBitmap_Destroy_V = StableValue.of(); public static MethodHandle fpdfBitmapDestroy() { - MethodHandle mh = FPDFBitmap_Destroy_MH; - if (mh == null) { - synchronized (BitmapBindings.class) { - mh = FPDFBitmap_Destroy_MH; - if (mh == null) { - mh = find("FPDFBitmap_Destroy", FunctionDescriptor.ofVoid(C_POINTER), false); - FPDFBitmap_Destroy_MH = mh; - } - } - } - return mh; + return FPDFBitmap_Destroy_V.orElseSet( + () -> find("FPDFBitmap_Destroy", FunctionDescriptor.ofVoid(C_POINTER), false)); } } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java index 998cfb3..131f3e1 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/DocBindings.java @@ -39,228 +39,120 @@ public static void checkRequired() { } } - private static volatile MethodHandle FPDF_GetMetaText_MH = null; + private static final StableValue FPDF_GetMetaText_V = StableValue.of(); public static MethodHandle fpdfGetMetaText() { - MethodHandle mh = FPDF_GetMetaText_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDF_GetMetaText_MH; - if (mh == null) { - mh = - find( - "FPDF_GetMetaText", - FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_POINTER, C_LONG), - false); - FPDF_GetMetaText_MH = mh; - } - } - } - return mh; + return FPDF_GetMetaText_V.orElseSet( + () -> + find( + "FPDF_GetMetaText", + FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_POINTER, C_LONG), + false)); } - private static volatile MethodHandle FPDF_GetXMPMetadata_MH = null; + private static final StableValue FPDF_GetXMPMetadata_V = StableValue.of(); public static MethodHandle fpdfGetXMPMetadata() { - MethodHandle mh = FPDF_GetXMPMetadata_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDF_GetXMPMetadata_MH; - if (mh == null) { - mh = - find( - "FPDF_GetXMPMetadata", - FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_LONG), - false); - FPDF_GetXMPMetadata_MH = mh; - } - } - } - return mh; + return FPDF_GetXMPMetadata_V.orElseSet( + () -> + find( + "FPDF_GetXMPMetadata", + FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_LONG), + false)); } - private static volatile MethodHandle FPDF_GetFileVersion_MH = null; + private static final StableValue FPDF_GetFileVersion_V = StableValue.of(); public static MethodHandle fpdfGetFileVersion() { - MethodHandle mh = FPDF_GetFileVersion_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDF_GetFileVersion_MH; - if (mh == null) { - mh = - find("FPDF_GetFileVersion", FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), true); - FPDF_GetFileVersion_MH = mh; - } - } - } - return mh; + return FPDF_GetFileVersion_V.orElseSet( + () -> + find("FPDF_GetFileVersion", FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), true)); } - private static volatile MethodHandle FPDFPage_Delete_MH = null; + private static final StableValue FPDFPage_Delete_V = StableValue.of(); public static MethodHandle fpdfPageDelete() { - MethodHandle mh = FPDFPage_Delete_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFPage_Delete_MH; - if (mh == null) { - mh = find("FPDFPage_Delete", FunctionDescriptor.ofVoid(C_POINTER, C_INT), false); - FPDFPage_Delete_MH = mh; - } - } - } - return mh; + return FPDFPage_Delete_V.orElseSet( + () -> find("FPDFPage_Delete", FunctionDescriptor.ofVoid(C_POINTER, C_INT), false)); } - private static volatile MethodHandle FPDFBookmark_GetFirstChild_MH = null; + private static final StableValue FPDFBookmark_GetFirstChild_V = StableValue.of(); public static MethodHandle fpdfBookmarkGetFirstChild() { - MethodHandle mh = FPDFBookmark_GetFirstChild_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFBookmark_GetFirstChild_MH; - if (mh == null) { - mh = - find( - "FPDFBookmark_GetFirstChild", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - true); - FPDFBookmark_GetFirstChild_MH = mh; - } - } - } - return mh; + return FPDFBookmark_GetFirstChild_V.orElseSet( + () -> + find( + "FPDFBookmark_GetFirstChild", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle FPDFBookmark_GetNextSibling_MH = null; + private static final StableValue FPDFBookmark_GetNextSibling_V = StableValue.of(); public static MethodHandle fpdfBookmarkGetNextSibling() { - MethodHandle mh = FPDFBookmark_GetNextSibling_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFBookmark_GetNextSibling_MH; - if (mh == null) { - mh = - find( - "FPDFBookmark_GetNextSibling", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - true); - FPDFBookmark_GetNextSibling_MH = mh; - } - } - } - return mh; + return FPDFBookmark_GetNextSibling_V.orElseSet( + () -> + find( + "FPDFBookmark_GetNextSibling", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle FPDFBookmark_GetTitle_MH = null; + private static final StableValue FPDFBookmark_GetTitle_V = StableValue.of(); public static MethodHandle fpdfBookmarkGetTitle() { - MethodHandle mh = FPDFBookmark_GetTitle_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFBookmark_GetTitle_MH; - if (mh == null) { - mh = - find( - "FPDFBookmark_GetTitle", - FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_LONG), - false); - FPDFBookmark_GetTitle_MH = mh; - } - } - } - return mh; + return FPDFBookmark_GetTitle_V.orElseSet( + () -> + find( + "FPDFBookmark_GetTitle", + FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_LONG), + false)); } - private static volatile MethodHandle FPDFBookmark_GetDest_MH = null; + private static final StableValue FPDFBookmark_GetDest_V = StableValue.of(); public static MethodHandle fpdfBookmarkGetDest() { - MethodHandle mh = FPDFBookmark_GetDest_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFBookmark_GetDest_MH; - if (mh == null) { - mh = - find( - "FPDFBookmark_GetDest", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - true); - FPDFBookmark_GetDest_MH = mh; - } - } - } - return mh; + return FPDFBookmark_GetDest_V.orElseSet( + () -> + find( + "FPDFBookmark_GetDest", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle FPDFBookmark_GetAction_MH = null; + private static final StableValue FPDFBookmark_GetAction_V = StableValue.of(); public static MethodHandle fpdfBookmarkGetAction() { - MethodHandle mh = FPDFBookmark_GetAction_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFBookmark_GetAction_MH; - if (mh == null) { - mh = find("FPDFBookmark_GetAction", FunctionDescriptor.of(C_POINTER, C_POINTER), true); - FPDFBookmark_GetAction_MH = mh; - } - } - } - return mh; + return FPDFBookmark_GetAction_V.orElseSet( + () -> find("FPDFBookmark_GetAction", FunctionDescriptor.of(C_POINTER, C_POINTER), true)); } - private static volatile MethodHandle FPDFAction_GetType_MH = null; + private static final StableValue FPDFAction_GetType_V = StableValue.of(); public static MethodHandle fpdfActionGetType() { - MethodHandle mh = FPDFAction_GetType_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFAction_GetType_MH; - if (mh == null) { - mh = find("FPDFAction_GetType", FunctionDescriptor.of(C_LONG, C_POINTER), true); - FPDFAction_GetType_MH = mh; - } - } - } - return mh; + return FPDFAction_GetType_V.orElseSet( + () -> find("FPDFAction_GetType", FunctionDescriptor.of(C_LONG, C_POINTER), true)); } - private static volatile MethodHandle FPDFAction_GetDest_MH = null; + private static final StableValue FPDFAction_GetDest_V = StableValue.of(); public static MethodHandle fpdfActionGetDest() { - MethodHandle mh = FPDFAction_GetDest_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFAction_GetDest_MH; - if (mh == null) { - mh = - find( - "FPDFAction_GetDest", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - true); - FPDFAction_GetDest_MH = mh; - } - } - } - return mh; + return FPDFAction_GetDest_V.orElseSet( + () -> + find( + "FPDFAction_GetDest", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle FPDFDest_GetDestPageIndex_MH = null; + private static final StableValue FPDFDest_GetDestPageIndex_V = StableValue.of(); public static MethodHandle fpdfGetDestPageIndex() { - MethodHandle mh = FPDFDest_GetDestPageIndex_MH; - if (mh == null) { - synchronized (DocBindings.class) { - mh = FPDFDest_GetDestPageIndex_MH; - if (mh == null) { - mh = - find( - "FPDFDest_GetDestPageIndex", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), - true); - FPDFDest_GetDestPageIndex_MH = mh; - } - } - } - return mh; + return FPDFDest_GetDestPageIndex_V.orElseSet( + () -> + find( + "FPDFDest_GetDestPageIndex", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), + true)); } } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java index 1515621..5066734 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/EditBindings.java @@ -38,52 +38,25 @@ public static void checkRequired() { } } - private static volatile MethodHandle FPDF_CreateNewDocument_MH = null; + private static final StableValue FPDF_CreateNewDocument_V = StableValue.of(); public static MethodHandle fpdfCreateNewDocument() { - MethodHandle mh = FPDF_CreateNewDocument_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDF_CreateNewDocument_MH; - if (mh == null) { - mh = find("FPDF_CreateNewDocument", FunctionDescriptor.of(C_POINTER), false); - FPDF_CreateNewDocument_MH = mh; - } - } - } - return mh; + return FPDF_CreateNewDocument_V.orElseSet( + () -> find("FPDF_CreateNewDocument", FunctionDescriptor.of(C_POINTER), false)); } - private static volatile MethodHandle FPDFPage_GetRotation_MH = null; + private static final StableValue FPDFPage_GetRotation_V = StableValue.of(); public static MethodHandle fpdfPageGetRotation() { - MethodHandle mh = FPDFPage_GetRotation_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFPage_GetRotation_MH; - if (mh == null) { - mh = find("FPDFPage_GetRotation", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFPage_GetRotation_MH = mh; - } - } - } - return mh; + return FPDFPage_GetRotation_V.orElseSet( + () -> find("FPDFPage_GetRotation", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFPage_SetRotation_MH = null; + private static final StableValue FPDFPage_SetRotation_V = StableValue.of(); public static MethodHandle fpdfPageSetRotation() { - MethodHandle mh = FPDFPage_SetRotation_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFPage_SetRotation_MH; - if (mh == null) { - mh = find("FPDFPage_SetRotation", FunctionDescriptor.ofVoid(C_POINTER, C_INT), false); - FPDFPage_SetRotation_MH = mh; - } - } - } - return mh; + return FPDFPage_SetRotation_V.orElseSet( + () -> find("FPDFPage_SetRotation", FunctionDescriptor.ofVoid(C_POINTER, C_INT), false)); } /** FPDF_FILEWRITE struct layout. */ @@ -98,159 +71,85 @@ public static MethodHandle fpdfPageSetRotation() { public static final FunctionDescriptor WRITE_BLOCK_DESC = FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_LONG); - private static volatile MethodHandle FPDF_SaveAsCopy_MH = null; + private static final StableValue FPDF_SaveAsCopy_V = StableValue.of(); public static MethodHandle fpdfSaveAsCopy() { - MethodHandle mh = FPDF_SaveAsCopy_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDF_SaveAsCopy_MH; - if (mh == null) { - mh = - find( - "FPDF_SaveAsCopy", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - FPDF_SaveAsCopy_MH = mh; - } - } - } - return mh; + return FPDF_SaveAsCopy_V.orElseSet( + () -> + find( + "FPDF_SaveAsCopy", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle FPDF_SaveWithVersion_MH = null; + private static final StableValue FPDF_SaveWithVersion_V = StableValue.of(); public static MethodHandle fpdfSaveWithVersion() { - MethodHandle mh = FPDF_SaveWithVersion_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDF_SaveWithVersion_MH; - if (mh == null) { - mh = - find( - "FPDF_SaveWithVersion", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT, C_INT), - false); - FPDF_SaveWithVersion_MH = mh; - } - } - } - return mh; + return FPDF_SaveWithVersion_V.orElseSet( + () -> + find( + "FPDF_SaveWithVersion", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT, C_INT), + false)); } public static final int FPDF_NO_INCREMENTAL = 1 << 1; - private static volatile MethodHandle FPDFPage_New_MH = null; + + private static final StableValue FPDFPage_New_V = StableValue.of(); public static MethodHandle fpdfPageNew() { - MethodHandle mh = FPDFPage_New_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFPage_New_MH; - if (mh == null) { - mh = - find( - "FPDFPage_New", - FunctionDescriptor.of( - C_POINTER, - C_POINTER, - C_INT, - ValueLayout.JAVA_DOUBLE, - ValueLayout.JAVA_DOUBLE), - false); - FPDFPage_New_MH = mh; - } - } - } - return mh; + return FPDFPage_New_V.orElseSet( + () -> + find( + "FPDFPage_New", + FunctionDescriptor.of( + C_POINTER, C_POINTER, C_INT, ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE), + false)); } - private static volatile MethodHandle FPDF_ImportPages_MH = null; + private static final StableValue FPDF_ImportPages_V = StableValue.of(); public static MethodHandle fpdfImportPages() { - MethodHandle mh = FPDF_ImportPages_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDF_ImportPages_MH; - if (mh == null) { - mh = - find( - "FPDF_ImportPages", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_INT), - false); - FPDF_ImportPages_MH = mh; - } - } - } - return mh; + return FPDF_ImportPages_V.orElseSet( + () -> + find( + "FPDF_ImportPages", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle FPDFPage_CountObjects_MH = null; + private static final StableValue FPDFPage_CountObjects_V = StableValue.of(); public static MethodHandle fpdfPageCountObjects() { - MethodHandle mh = FPDFPage_CountObjects_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFPage_CountObjects_MH; - if (mh == null) { - mh = find("FPDFPage_CountObjects", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFPage_CountObjects_MH = mh; - } - } - } - return mh; + return FPDFPage_CountObjects_V.orElseSet( + () -> find("FPDFPage_CountObjects", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFPage_GetObject_MH = null; + private static final StableValue FPDFPage_GetObject_V = StableValue.of(); public static MethodHandle fpdfPageGetObject() { - MethodHandle mh = FPDFPage_GetObject_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFPage_GetObject_MH; - if (mh == null) { - mh = find("FPDFPage_GetObject", FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), true); - FPDFPage_GetObject_MH = mh; - } - } - } - return mh; + return FPDFPage_GetObject_V.orElseSet( + () -> find("FPDFPage_GetObject", FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), true)); } - private static volatile MethodHandle FPDFPageObj_GetType_MH = null; + private static final StableValue FPDFPageObj_GetType_V = StableValue.of(); public static MethodHandle fpdfPageObjGetType() { - MethodHandle mh = FPDFPageObj_GetType_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFPageObj_GetType_MH; - if (mh == null) { - mh = find("FPDFPageObj_GetType", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFPageObj_GetType_MH = mh; - } - } - } - return mh; + return FPDFPageObj_GetType_V.orElseSet( + () -> find("FPDFPageObj_GetType", FunctionDescriptor.of(C_INT, C_POINTER), true)); } public static final int FPDF_PAGEOBJ_IMAGE = 3; - private static volatile MethodHandle FPDFImageObj_GetImageMetadata_MH = null; + + private static final StableValue FPDFImageObj_GetImageMetadata_V = StableValue.of(); public static MethodHandle fpdfImageObjGetImageMetadata() { - MethodHandle mh = FPDFImageObj_GetImageMetadata_MH; - if (mh == null) { - synchronized (EditBindings.class) { - mh = FPDFImageObj_GetImageMetadata_MH; - if (mh == null) { - mh = - find( - "FPDFImageObj_GetImageMetadata", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER), - false); - FPDFImageObj_GetImageMetadata_MH = mh; - } - } - } - return mh; + return FPDFImageObj_GetImageMetadata_V.orElseSet( + () -> + find( + "FPDFImageObj_GetImageMetadata", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER), + false)); } public static final StructLayout IMAGE_METADATA_LAYOUT = diff --git a/src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java b/src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java index e836904..8dbdbdc 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/FfmHelper.java @@ -9,6 +9,8 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; import java.lang.foreign.ValueLayout; +import java.lang.foreign.ValueLayout.OfInt; +import java.lang.foreign.ValueLayout.OfLong; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Map; @@ -25,7 +27,7 @@ public final class FfmHelper { public static final Linker LINKER = Linker.nativeLinker(); public static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup(); - private static volatile Map CANONICAL_LAYOUTS = null; + private static final StableValue> CANONICAL_LAYOUTS = StableValue.of(); private static Map getCanonicalLayoutsSafe() { try { @@ -37,24 +39,16 @@ private static Map getCanonicalLayoutsSafe() { } private static Map layouts() { - Map l = CANONICAL_LAYOUTS; - if (l == null) { - synchronized (FfmHelper.class) { - l = CANONICAL_LAYOUTS; - if (l == null) { - l = getCanonicalLayoutsSafe(); - CANONICAL_LAYOUTS = l; - } - } - } - return l; + return CANONICAL_LAYOUTS.orElseSet(FfmHelper::getCanonicalLayoutsSafe); } - public static final ValueLayout.OfInt C_INT = - (ValueLayout.OfInt) layouts().getOrDefault("int", ValueLayout.JAVA_INT); + public static final OfInt C_INT = (OfInt) layouts().getOrDefault("int", ValueLayout.JAVA_INT); + + private static final StableValue C_LONG_V = StableValue.of(); public static final ValueLayout C_LONG = - (ValueLayout) layouts().getOrDefault("long", detectCLongLayout()); + (ValueLayout) + layouts().getOrDefault("long", C_LONG_V.orElseSet(FfmHelper::detectCLongLayout)); private static ValueLayout detectCLongLayout() { String os = System.getProperty("os.name").toLowerCase(Locale.ROOT); @@ -66,27 +60,26 @@ private static ValueLayout detectCLongLayout() { /** Read a platform-dependent C 'long' value from the given memory segment. */ public static long readCLong(MemorySegment seg, long offset) { - if (C_LONG instanceof ValueLayout.OfLong ofLong) { + if (C_LONG instanceof OfLong ofLong) { return seg.get(ofLong, offset); } else { - return seg.get((ValueLayout.OfInt) C_LONG, offset); + return seg.get((OfInt) C_LONG, offset); } } /** Write a platform-dependent C 'long' value to the given memory segment. */ public static void writeCLong(MemorySegment seg, long offset, long value) { - if (C_LONG instanceof ValueLayout.OfLong ofLong) { + if (C_LONG instanceof OfLong ofLong) { seg.set(ofLong, offset, value); } else { - seg.set((ValueLayout.OfInt) C_LONG, offset, Math.toIntExact(value)); + seg.set((OfInt) C_LONG, offset, Math.toIntExact(value)); } } public static final ValueLayout C_SIZE_T = (ValueLayout) layouts().getOrDefault("size_t", ValueLayout.JAVA_LONG); - public static final ValueLayout.OfInt C_BOOL = - (ValueLayout.OfInt) layouts().getOrDefault("int", ValueLayout.JAVA_INT); + public static final OfInt C_BOOL = (OfInt) layouts().getOrDefault("int", ValueLayout.JAVA_INT); public static final AddressLayout C_POINTER = ValueLayout.ADDRESS; diff --git a/src/main/java/org/grimmory/pdfium4j/internal/Generators.java b/src/main/java/org/grimmory/pdfium4j/internal/Generators.java index a022c23..feb5bbf 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/Generators.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/Generators.java @@ -7,6 +7,7 @@ /** * Utility for providing shared constant instances of commonly used objects to avoid allocations. */ +@SuppressWarnings("preview") public final class Generators { private static final StableValue[]> EMPTY_FILE_ATTRIBUTES = StableValue.of(); diff --git a/src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java b/src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java index 2fbb988..bd61a3a 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/InternalLogger.java @@ -1,16 +1,19 @@ package org.grimmory.pdfium4j.internal; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + /** Internal bridge for logging. DO NOT USE outside of this library. */ public final class InternalLogger { - private static final System.Logger LOGGER = System.getLogger("PDFium4j-Internal"); + private static final Logger LOGGER = System.getLogger("PDFium4j-Internal"); private InternalLogger() {} public static void warn(String msg) { - LOGGER.log(System.Logger.Level.WARNING, msg); + LOGGER.log(Level.WARNING, msg); } public static void error(String msg) { - LOGGER.log(System.Logger.Level.ERROR, msg); + LOGGER.log(Level.ERROR, msg); } } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java b/src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java index 0f70af8..4cb4656 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/NativeLoader.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.foreign.Arena; import java.lang.foreign.SymbolLookup; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -30,46 +31,33 @@ public final class NativeLoader { public static final String PROP_LIBRARY_PATH_ALLOW_UNSAFE = "pdfium4j.library.path.allowUnsafe"; private static final String SYS_PROP_OS_NAME = "os.name"; - private static volatile boolean loaded = false; + private static final StableValue loaded = StableValue.of(); private static volatile Throwable loadError = null; - private static volatile SymbolLookup shimLookup = null; + private static final StableValue shimLookup = StableValue.of(); public static SymbolLookup getShimLookup() { - return shimLookup; + return shimLookup.orElse(null); } private NativeLoader() {} public static void ensureLoaded() { - if (loaded) return; + if (loaded.isSet()) return; if (loadError != null) { throw new NativeLoadException("Native library failed to load previously", loadError); } - synchronized (NativeLoader.class) { - if (loaded) return; - if (loadError != null) { - throw new NativeLoadException("Native library failed to load previously", loadError); - } - try { - performLoad(); - loaded = true; - } catch (NativeLoadException e) { - loadError = e; - throw e; - } catch (Throwable t) { - String msg = - "Failed to load native library for platform " - + detectPlatform() - + " (JVM arch: " - + detectArch() - + ")"; - if (t.getMessage() != null) { - msg += ": " + t.getMessage(); - } - loadError = new NativeLoadException(msg, t); - throw (NativeLoadException) loadError; - } - } + loaded.orElseSet( + () -> { + try { + performLoad(); + return true; + } catch (NativeLoadException _) { + throw new NativeLoadException("Native library failed to load previously"); + } catch (Throwable _) { + throw new NativeLoadException( + "Failed to load native library for platform " + detectPlatform()); + } + }); } private static void performLoad() { @@ -202,7 +190,7 @@ private static void tryLoadFromClasspath() { if (Files.exists(depPath)) { var lookup = loadLibraryFile(depPath); if (lib.contains("shim")) { - shimLookup = lookup; + shimLookup.trySet(lookup); } } } @@ -212,10 +200,10 @@ private static void tryLoadFromClasspath() { } } - private static java.lang.foreign.SymbolLookup loadLibraryFile(Path path) { + private static SymbolLookup loadLibraryFile(Path path) { try { System.load(path.toAbsolutePath().toString()); - return java.lang.foreign.SymbolLookup.libraryLookup(path, java.lang.foreign.Arena.global()); + return SymbolLookup.libraryLookup(path, Arena.global()); } catch (UnsatisfiedLinkError e) { String msg = "Failed to load native library: " + path.getFileName() + ". Error: " + e.getMessage(); diff --git a/src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java b/src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java index a0cfcab..31887e6 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/PdfDocumentFallbackMeta.java @@ -178,7 +178,7 @@ private static String unescape(byte[] raw) { i++; } } - return new String(out.toByteArray(), StandardCharsets.ISO_8859_1); + return out.toString(StandardCharsets.ISO_8859_1); } private static int processEscape(byte[] raw, int nextIdx, ByteArrayOutputStream out) { diff --git a/src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java b/src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java index 856e32d..0b0cbca 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/ScratchBuffer.java @@ -24,11 +24,15 @@ public final class ScratchBuffer { private static final long INITIAL_SIZE = 4096; - private static final long STEADY_STATE_SIZE = 1024L * 1024L; - private static final long MAX_SIZE = 1024L * 1024L * 128L; // 128MB safety limit + private static final long STEADY_STATE_SIZE = + Long.getLong("pdfium4j.scratch.steadyStateSize", 16L * 1024L * 1024L); + private static final long MAX_SIZE = + Long.getLong("pdfium4j.scratch.maxSize", 256L * 1024L * 1024L); + private static final long WARN_THRESHOLD = + Long.getLong("pdfium4j.scratch.warnThreshold", 32L * 1024L * 1024L); - private static final ThreadLocal STATE = new ThreadLocal<>(); - private static final ThreadLocal USE_COUNT = ThreadLocal.withInitial(() -> new int[] {0}); + private static final ScopedValue STATE = ScopedValue.newInstance(); + private static final ThreadLocal BRIDGE = new ThreadLocal<>(); private ScratchBuffer() {} @@ -47,10 +51,10 @@ public static MemorySegment get(long minBytes) { if (minBytes < 0 || minBytes > MAX_SIZE) { throw new IllegalArgumentException("Invalid scratch buffer size: " + minBytes); } - if (USE_COUNT.get()[0] <= 0) { + State s = getOrCreateState(); + if (s.useCount <= 0) { throw new IllegalStateException("ScratchBuffer.get() called without active acquire()"); } - State s = getOrCreateState(); return s.getSegment(minBytes); } @@ -61,31 +65,6 @@ public static MemorySegment getUtf8(String s) { return FfmHelper.writeUtf8String(seg, s); } - /** Returns a scratch segment containing a null-terminated UTF-16LE string. */ - public static MemorySegment getWide(String s) { - long len = (long) s.length() * 2 + 2; - MemorySegment seg = get(len); - return FfmHelper.writeWideString(seg, s); - } - - static long probeSize(long keyBytes) { - if (keyBytes < 0) { - throw new IllegalArgumentException("Invalid UTF-8 probe size: " + keyBytes); - } - if (keyBytes >= MAX_SIZE - 1024) { - return MAX_SIZE; - } - return keyBytes + 1024; - } - - /** - * Returns a zero-allocation InputStream wrapping the given segment. The stream MUST be closed to - * release the scratch buffer acquisition. - */ - public static InputStream wrap(MemorySegment segment, long size) { - return new SegmentInputStream(segment, size); - } - /** * Allocates one scratch segment containing a UTF-8 key and a wide-string value buffer. * @@ -109,23 +88,19 @@ public static KeyValueSlots utf8KeyAndWideValue(String key, long valueBytes) { /** Wrap existing key and value segments into the reusable thread-local slot holder. */ public static KeyValueSlots keyAndWideValue(MemorySegment keySeg, MemorySegment valueSeg) { - if (USE_COUNT.get()[0] <= 0) { + State s = getOrCreateState(); + if (s.useCount <= 0) { throw new IllegalStateException( "ScratchBuffer.keyAndWideValue() called without active acquire()"); } - State s = getOrCreateState(); s.keyValueSlots.keySeg = keySeg; s.keyValueSlots.valueSeg = valueSeg; return s.keyValueSlots; } - /** - * Acquire a usage slot for managed lifecycle owners (for example, PdfDocument instances). - * - *

Uses a thread-local primitive array to avoid boxing allocations on increment/decrement. - */ + /** Acquire a usage slot for managed lifecycle owners. */ public static void acquire() { - USE_COUNT.get()[0]++; + getOrCreateState().useCount++; } /** @@ -161,56 +136,101 @@ public void close() { /** Release and close all thread-local scratch state for the current thread. */ public static void release() { - int[] countRef = USE_COUNT.get(); - int count = countRef[0]; - if (count <= 0) return; - count--; - countRef[0] = count; + State s = getOrCreateState(); + if (s.useCount <= 0) return; + s.useCount--; - if (count == 0 && currentCapacity() > STEADY_STATE_SIZE) { + if (s.useCount == 0 && s.segment.byteSize() > STEADY_STATE_SIZE) { purge(); } } + /** Returns a clamped size that fits within the maximum scratch buffer limits. */ + public static long probeSize(long size) { + return Math.clamp(size, 0, MAX_SIZE); + } + + /** + * Executes the given action within a managed scratch buffer scope using ScopedValues. + * + * @param action the action to run + */ + public static void withScratch(Runnable action) { + State s = new State(); + s.useCount = 1; + try { + ScopedValue.where(STATE, s).run(action); + } finally { + s.close(); + } + } + + /** + * Executes the given task within a managed scratch buffer scope and returns its result. + * + * @param action the task to run + * @param the result type + * @return the task result + * @throws Exception if the task fails + */ + public static T callWithScratch(java.util.concurrent.Callable action) throws Exception { + State s = new State(); + s.useCount = 1; + try { + return ScopedValue.where(STATE, s).call(() -> action.call()); + } finally { + s.close(); + } + } + /** Clear all thread-local buffers and close their arenas. */ public static void purge() { - USE_COUNT.get()[0] = 0; - State s = STATE.get(); + if (STATE.isBound()) { + InternalLogger.warn("ScratchBuffer.purge() called inside an active scope; ignoring"); + return; + } + State s = BRIDGE.get(); if (s != null) { + s.useCount = 0; s.close(); - STATE.remove(); + BRIDGE.remove(); } } /** Returns a thread-local byte array for temporary UTF-16LE decode staging. */ public static byte[] getByteArray(int minBytes) { - if (USE_COUNT.get()[0] <= 0) { + State s = getOrCreateState(); + if (s.useCount <= 0) { throw new IllegalStateException( "ScratchBuffer.getByteArray() called without active acquire()"); } - State s = getOrCreateState(); return s.getByteArray(minBytes); } /** Returns a dedicated 8KB scratch segment for metadata reads. */ public static MemorySegment getMetadataBuffer() { - if (USE_COUNT.get()[0] <= 0) { + State s = getOrCreateState(); + if (s.useCount <= 0) { throw new IllegalStateException( "ScratchBuffer.getMetadataBuffer() called without active acquire()"); } - return getOrCreateState().getMetadataBuffer(); + return s.getMetadataBuffer(); } static long currentCapacity() { - State s = STATE.get(); + if (STATE.isBound()) return STATE.get().segment.byteSize(); + State s = BRIDGE.get(); return s == null ? 0 : s.segment.byteSize(); } private static State getOrCreateState() { - State s = STATE.get(); + if (STATE.isBound()) { + return STATE.get(); + } + State s = BRIDGE.get(); if (s == null) { s = new State(); - STATE.set(s); + BRIDGE.set(s); } return s; } @@ -248,21 +268,19 @@ public MemorySegment valueSeg() { } private static final class State { + int useCount; private final List arenas; private Arena arena; - private Arena loopArena; private MemorySegment segment; private MemorySegment steadySegment; private MemorySegment largestSegment; - private MemorySegment loopScratch; - private char[] charArray; private byte[] byteArray; private final MemorySegment metadataBuffer; private final KeyValueSlots keyValueSlots; /** * State is only valid between construction and the {@link #close()} call. Subsequent accesses - * are protected by the {@link ThreadLocal#remove()} in {@link ScratchBuffer#release()}. + * are protected by the binding lifecycle or {@link BRIDGE#remove()}. */ State() { this.arenas = new ArrayList<>(2); @@ -271,22 +289,11 @@ private static final class State { this.segment = arena.allocate(INITIAL_SIZE, 8); this.steadySegment = segment; this.largestSegment = segment; - this.loopArena = Arena.ofConfined(); - this.arenas.add(this.loopArena); - this.loopScratch = loopArena.allocate(64, 8); - this.charArray = new char[1024]; this.byteArray = new byte[2048]; this.metadataBuffer = arena.allocate(8192, 8); this.keyValueSlots = new KeyValueSlots(); } - char[] getCharArray(int minChars) { - if (charArray.length < minChars) { - charArray = new char[Math.max(charArray.length * 2, minChars)]; - } - return charArray; - } - byte[] getByteArray(int minBytes) { if (byteArray.length < minBytes) { byteArray = new byte[Math.max(byteArray.length * 2, minBytes)]; @@ -294,18 +301,6 @@ byte[] getByteArray(int minBytes) { return byteArray; } - MemorySegment getLoopScratch(long minBytes) { - if (minBytes < 0 || minBytes > MAX_SIZE) { - throw new IllegalArgumentException("Invalid loop scratch size: " + minBytes); - } - if (loopScratch.byteSize() < minBytes) { - this.loopArena = Arena.ofConfined(); - this.arenas.add(this.loopArena); - this.loopScratch = loopArena.allocate(minBytes, 8); - } - return loopScratch; - } - MemorySegment getMetadataBuffer() { return metadataBuffer; } @@ -325,7 +320,7 @@ MemorySegment getSegment(long minBytes) { segment = largestSegment; return segment; } - if (minBytes > 1024 * 1024) { + if (minBytes > WARN_THRESHOLD) { // Large scratch allocations are rare and might indicate a logic error or // extremely large metadata. InternalLogger.warn("Large scratch allocation requested: " + minBytes + " bytes"); @@ -370,21 +365,18 @@ private static final class SegmentInputStream extends InputStream { private final long size; SegmentInputStream(MemorySegment segment, long size) { - MemorySegment checked = Objects.requireNonNull(segment, "segment"); - acquire(); - this.segment = checked; - if (size < 0 || size > checked.byteSize()) { - release(); - throw new IllegalArgumentException("size must be between 0 and segment.byteSize()"); - } + this.segment = segment; this.size = size; this.pos = 0; + acquire(); } @Override public int read() { if (segment == null || pos >= size) return -1; - return segment.get(ValueLayout.JAVA_BYTE, pos++) & 0xFF; + int i = segment.get(ValueLayout.JAVA_BYTE, pos) & 0xFF; + pos++; + return i; } @Override diff --git a/src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java b/src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java index 89fd0d0..ea4e3e4 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/SegmentOutputStream.java @@ -26,7 +26,8 @@ public SegmentOutputStream(MemorySegment segment) { @Override public void write(int b) { ensureCapacity(1); - segment.set(ValueLayout.JAVA_BYTE, pos++, (byte) b); + segment.set(ValueLayout.JAVA_BYTE, pos, (byte) b); + pos++; } @Override diff --git a/src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java index 6b37ef8..5097d06 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/ShimBindings.java @@ -1,6 +1,5 @@ package org.grimmory.pdfium4j.internal; -import static java.lang.foreign.ValueLayout.*; import static org.grimmory.pdfium4j.internal.FfmHelper.C_INT; import static org.grimmory.pdfium4j.internal.FfmHelper.C_POINTER; import static org.grimmory.pdfium4j.internal.FfmHelper.C_SIZE_T; @@ -10,10 +9,12 @@ import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; /** FFM bindings for the pdfium4j C++ shim library. */ +@SuppressWarnings("preview") public final class ShimBindings { private ShimBindings() {} @@ -21,39 +22,6 @@ private ShimBindings() {} /** Ensures all required shim symbols are available. */ public static void checkRequired() { check("pdfium4j_page_count", pdfium4jPageCount()); - check("pdfium4j_get_meta_utf8", pdfium4jGetMetaUtf8()); - check("pdfium4j_set_meta_utf8", pdfium4jSetMetaUtf8()); - check("pdfium4j_get_custom_xmp", pdfium4jGetCustomXmp()); - check("pdfium4j_get_custom_xmp_bag", pdfium4jGetCustomXmpBag()); - check("pdfium4j_get_xmp_metadata", pdfium4jGetXmpMetadata()); - check("pdfium4j_page_label", pdfium4jPageLabel()); - check("pdfium4j_page_width", pdfium4jPageWidth()); - check("pdfium4j_page_height", pdfium4jPageHeight()); - check("pdfium4j_bookmark_first", pdfium4jBookmarkFirst()); - check("pdfium4j_bookmark_next", pdfium4jBookmarkNext()); - check("pdfium4j_bookmark_first_child", pdfium4jBookmarkFirstChild()); - check("pdfium4j_bookmark_title", pdfium4jBookmarkTitle()); - check("pdfium4j_bookmark_page_index", pdfium4jBookmarkPageIndex()); - check("pdfium4j_struct_tree_get", pdfium4jStructTreeGet()); - check("pdfium4j_struct_tree_close", pdfium4jStructTreeClose()); - check("pdfium4j_struct_tree_count_children", pdfium4jStructTreeCountChildren()); - check("pdfium4j_struct_tree_get_child", pdfium4jStructTreeGetChild()); - check("pdfium4j_struct_element_count_children", pdfium4jStructElementCountChildren()); - check("pdfium4j_struct_element_get_child", pdfium4jStructElementGetChild()); - check("pdfium4j_struct_element_get_mcid", pdfium4jStructElementGetMcid()); - check("pdfium4j_struct_element_get_type", pdfium4jStructElementGetType()); - check("pdfium4j_struct_element_get_title", pdfium4jStructElementGetTitle()); - check("pdfium4j_struct_element_get_alt_text", pdfium4jStructElementGetAltText()); - check("pdfium4j_struct_element_get_actual_text", pdfium4jStructElementGetActualText()); - check("pdfium4j_struct_element_get_lang", pdfium4jStructElementGetLang()); - check("pdfium4j_struct_element_get_attribute_count", pdfium4jStructElementGetAttributeCount()); - check("pdfium4j_text_get_chars_with_bounds", pdfium4jTextGetCharsWithBounds()); - check("pdfium4j_save_incremental", pdfium4jSaveIncremental()); - check("pdfium4j_save_copy", pdfium4jSaveCopy()); - check("pdfium4j_read_info_dict", pdfium4jReadInfoDict()); - check("pdfium4j_read_info_dict_mem", pdfium4jReadInfoDictMem()); - check("pdfium4j_save_with_metadata_native", pdfium4jSaveWithMetadata()); - check("pdfium4j_save_with_metadata_mem_native", pdfium4jSaveWithMetadataMem()); } private static void check(String name, MethodHandle mh) { @@ -64,7 +32,7 @@ private static void check(String name, MethodHandle mh) { } private static MethodHandle find(String name, FunctionDescriptor desc, boolean critical) { - java.lang.foreign.SymbolLookup shim = NativeLoader.getShimLookup(); + SymbolLookup shim = NativeLoader.getShimLookup(); MemorySegment addr = null; if (shim != null) { addr = shim.find(name).orElse(null); @@ -77,668 +45,409 @@ private static MethodHandle find(String name, FunctionDescriptor desc, boolean c addr, desc, critical ? FfmHelper.CRITICAL_OPTIONS : FfmHelper.NO_OPTIONS); } - private static volatile MethodHandle pdfium4jPageCountMH = null; + private static final StableValue pdfium4jPageCountV = StableValue.of(); public static MethodHandle pdfium4jPageCount() { - MethodHandle mh = pdfium4jPageCountMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jPageCountMH; - if (mh == null) { - mh = find("pdfium4j_page_count", FunctionDescriptor.of(C_INT, C_POINTER), true); - pdfium4jPageCountMH = mh; - } - } - } - return mh; + return pdfium4jPageCountV.orElseSet( + () -> find("pdfium4j_page_count", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle pdfium4jGetMetaUtf8MH = null; + private static final StableValue pdfium4jGetMetaUtf8V = StableValue.of(); public static MethodHandle pdfium4jGetMetaUtf8() { - MethodHandle mh = pdfium4jGetMetaUtf8MH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jGetMetaUtf8MH; - if (mh == null) { - mh = - find( - "pdfium4j_get_meta_utf8", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jGetMetaUtf8MH = mh; - } - } - } - return mh; + return pdfium4jGetMetaUtf8V.orElseSet( + () -> + find( + "pdfium4j_get_meta_utf8", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jSetMetaUtf8MH = null; + private static final StableValue pdfium4jSetMetaUtf8V = StableValue.of(); public static MethodHandle pdfium4jSetMetaUtf8() { - MethodHandle mh = pdfium4jSetMetaUtf8MH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jSetMetaUtf8MH; - if (mh == null) { - mh = - find( - "pdfium4j_set_meta_utf8", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER), - false); - pdfium4jSetMetaUtf8MH = mh; - } - } - } - return mh; + return pdfium4jSetMetaUtf8V.orElseSet( + () -> + find( + "pdfium4j_set_meta_utf8", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle pdfium4jGetCustomXmpMH = null; + private static final StableValue pdfium4jGetCustomXmpV = StableValue.of(); public static MethodHandle pdfium4jGetCustomXmp() { - MethodHandle mh = pdfium4jGetCustomXmpMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jGetCustomXmpMH; - if (mh == null) { - mh = - find( - "pdfium4j_get_custom_xmp", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jGetCustomXmpMH = mh; - } - } - } - return mh; + return pdfium4jGetCustomXmpV.orElseSet( + () -> + find( + "pdfium4j_get_custom_xmp", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jGetCustomXmpBagMH = null; + private static final StableValue pdfium4jGetCustomXmpBagV = StableValue.of(); public static MethodHandle pdfium4jGetCustomXmpBag() { - MethodHandle mh = pdfium4jGetCustomXmpBagMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jGetCustomXmpBagMH; - if (mh == null) { - mh = - find( - "pdfium4j_get_custom_xmp_bag", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jGetCustomXmpBagMH = mh; - } - } - } - return mh; + return pdfium4jGetCustomXmpBagV.orElseSet( + () -> + find( + "pdfium4j_get_custom_xmp_bag", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jGetXmpMetadataMH = null; + private static final StableValue pdfium4jGetXmpMetadataV = StableValue.of(); public static MethodHandle pdfium4jGetXmpMetadata() { - MethodHandle mh = pdfium4jGetXmpMetadataMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jGetXmpMetadataMH; - if (mh == null) { - mh = - find( - "pdfium4j_get_xmp_metadata", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jGetXmpMetadataMH = mh; - } - } - } - return mh; + return pdfium4jGetXmpMetadataV.orElseSet( + () -> + find( + "pdfium4j_get_xmp_metadata", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jPageLabelMH = null; + private static final StableValue pdfium4jPageLabelV = StableValue.of(); public static MethodHandle pdfium4jPageLabel() { - MethodHandle mh = pdfium4jPageLabelMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jPageLabelMH; - if (mh == null) { - mh = - find( - "pdfium4j_page_label", - FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER, C_INT), - false); - pdfium4jPageLabelMH = mh; - } - } - } - return mh; + return pdfium4jPageLabelV.orElseSet( + () -> + find( + "pdfium4j_page_label", + FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jPageWidthMH = null; + private static final StableValue pdfium4jPageWidthV = StableValue.of(); public static MethodHandle pdfium4jPageWidth() { - MethodHandle mh = pdfium4jPageWidthMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jPageWidthMH; - if (mh == null) { - mh = - find( - "pdfium4j_page_width", - FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER, C_INT), - true); - pdfium4jPageWidthMH = mh; - } - } - } - return mh; + return pdfium4jPageWidthV.orElseSet( + () -> + find( + "pdfium4j_page_width", + FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER, C_INT), + true)); } - private static volatile MethodHandle pdfium4jPageHeightMH = null; + private static final StableValue pdfium4jPageHeightV = StableValue.of(); public static MethodHandle pdfium4jPageHeight() { - MethodHandle mh = pdfium4jPageHeightMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jPageHeightMH; - if (mh == null) { - mh = - find( - "pdfium4j_page_height", - FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER, C_INT), - true); - pdfium4jPageHeightMH = mh; - } - } - } - return mh; + return pdfium4jPageHeightV.orElseSet( + () -> + find( + "pdfium4j_page_height", + FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER, C_INT), + true)); } - private static volatile MethodHandle pdfium4jBookmarkFirstMH = null; + private static final StableValue pdfium4jBookmarkFirstV = StableValue.of(); public static MethodHandle pdfium4jBookmarkFirst() { - MethodHandle mh = pdfium4jBookmarkFirstMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jBookmarkFirstMH; - if (mh == null) { - mh = find("pdfium4j_bookmark_first", FunctionDescriptor.of(C_POINTER, C_POINTER), true); - pdfium4jBookmarkFirstMH = mh; - } - } - } - return mh; + return pdfium4jBookmarkFirstV.orElseSet( + () -> find("pdfium4j_bookmark_first", FunctionDescriptor.of(C_POINTER, C_POINTER), true)); } - private static volatile MethodHandle pdfium4jBookmarkNextMH = null; + private static final StableValue pdfium4jBookmarkNextV = StableValue.of(); public static MethodHandle pdfium4jBookmarkNext() { - MethodHandle mh = pdfium4jBookmarkNextMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jBookmarkNextMH; - if (mh == null) { - mh = - find( - "pdfium4j_bookmark_next", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - true); - pdfium4jBookmarkNextMH = mh; - } - } - } - return mh; + return pdfium4jBookmarkNextV.orElseSet( + () -> + find( + "pdfium4j_bookmark_next", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle pdfium4jBookmarkFirstChildMH = null; + private static final StableValue pdfium4jBookmarkFirstChildV = StableValue.of(); public static MethodHandle pdfium4jBookmarkFirstChild() { - MethodHandle mh = pdfium4jBookmarkFirstChildMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jBookmarkFirstChildMH; - if (mh == null) { - mh = - find( - "pdfium4j_bookmark_first_child", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - true); - pdfium4jBookmarkFirstChildMH = mh; - } - } - } - return mh; + return pdfium4jBookmarkFirstChildV.orElseSet( + () -> + find( + "pdfium4j_bookmark_first_child", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle pdfium4jBookmarkTitleMH = null; + private static final StableValue pdfium4jBookmarkTitleV = StableValue.of(); public static MethodHandle pdfium4jBookmarkTitle() { - MethodHandle mh = pdfium4jBookmarkTitleMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jBookmarkTitleMH; - if (mh == null) { - mh = - find( - "pdfium4j_bookmark_title", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jBookmarkTitleMH = mh; - } - } - } - return mh; + return pdfium4jBookmarkTitleV.orElseSet( + () -> + find( + "pdfium4j_bookmark_title", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jBookmarkPageIndexMH = null; + private static final StableValue pdfium4jBookmarkPageIndexV = StableValue.of(); public static MethodHandle pdfium4jBookmarkPageIndex() { - MethodHandle mh = pdfium4jBookmarkPageIndexMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jBookmarkPageIndexMH; - if (mh == null) { - mh = - find( - "pdfium4j_bookmark_page_index", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), - true); - pdfium4jBookmarkPageIndexMH = mh; - } - } - } - return mh; + return pdfium4jBookmarkPageIndexV.orElseSet( + () -> + find( + "pdfium4j_bookmark_page_index", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), + true)); } - private static volatile MethodHandle pdfium4jStructTreeGetMH = null; + private static final StableValue pdfium4jStructTreeGetV = StableValue.of(); public static MethodHandle pdfium4jStructTreeGet() { - MethodHandle mh = pdfium4jStructTreeGetMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructTreeGetMH; - if (mh == null) { - mh = find("pdfium4j_struct_tree_get", FunctionDescriptor.of(C_POINTER, C_POINTER), false); - pdfium4jStructTreeGetMH = mh; - } - } - } - return mh; + return pdfium4jStructTreeGetV.orElseSet( + () -> find("pdfium4j_struct_tree_get", FunctionDescriptor.of(C_POINTER, C_POINTER), false)); } - private static volatile MethodHandle pdfium4jStructTreeCloseMH = null; + private static final StableValue pdfium4jStructTreeCloseV = StableValue.of(); public static MethodHandle pdfium4jStructTreeClose() { - MethodHandle mh = pdfium4jStructTreeCloseMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructTreeCloseMH; - if (mh == null) { - mh = find("pdfium4j_struct_tree_close", FunctionDescriptor.ofVoid(C_POINTER), false); - pdfium4jStructTreeCloseMH = mh; - } - } - } - return mh; + return pdfium4jStructTreeCloseV.orElseSet( + () -> find("pdfium4j_struct_tree_close", FunctionDescriptor.ofVoid(C_POINTER), false)); } - private static volatile MethodHandle pdfium4jStructTreeCountChildrenMH = null; + private static final StableValue pdfium4jStructTreeCountChildrenV = + StableValue.of(); public static MethodHandle pdfium4jStructTreeCountChildren() { - MethodHandle mh = pdfium4jStructTreeCountChildrenMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructTreeCountChildrenMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_tree_count_children", - FunctionDescriptor.of(C_INT, C_POINTER), - true); - pdfium4jStructTreeCountChildrenMH = mh; - } - } - } - return mh; + return pdfium4jStructTreeCountChildrenV.orElseSet( + () -> + find( + "pdfium4j_struct_tree_count_children", + FunctionDescriptor.of(C_INT, C_POINTER), + true)); } - private static volatile MethodHandle pdfium4jStructTreeGetChildMH = null; + private static final StableValue pdfium4jStructTreeGetChildV = StableValue.of(); public static MethodHandle pdfium4jStructTreeGetChild() { - MethodHandle mh = pdfium4jStructTreeGetChildMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructTreeGetChildMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_tree_get_child", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), - true); - pdfium4jStructTreeGetChildMH = mh; - } - } - } - return mh; + return pdfium4jStructTreeGetChildV.orElseSet( + () -> + find( + "pdfium4j_struct_tree_get_child", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), + true)); } - private static volatile MethodHandle pdfium4jStructElementCountChildrenMH = null; + private static final StableValue pdfium4jStructElementCountChildrenV = + StableValue.of(); public static MethodHandle pdfium4jStructElementCountChildren() { - MethodHandle mh = pdfium4jStructElementCountChildrenMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementCountChildrenMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_count_children", - FunctionDescriptor.of(C_INT, C_POINTER), - true); - pdfium4jStructElementCountChildrenMH = mh; - } - } - } - return mh; + return pdfium4jStructElementCountChildrenV.orElseSet( + () -> + find( + "pdfium4j_struct_element_count_children", + FunctionDescriptor.of(C_INT, C_POINTER), + true)); } - private static volatile MethodHandle pdfium4jStructElementGetChildMH = null; + private static final StableValue pdfium4jStructElementGetChildV = StableValue.of(); public static MethodHandle pdfium4jStructElementGetChild() { - MethodHandle mh = pdfium4jStructElementGetChildMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetChildMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_child", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), - true); - pdfium4jStructElementGetChildMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetChildV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_child", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), + true)); } - private static volatile MethodHandle pdfium4jStructElementGetMcidMH = null; + private static final StableValue pdfium4jStructElementGetMcidV = StableValue.of(); public static MethodHandle pdfium4jStructElementGetMcid() { - MethodHandle mh = pdfium4jStructElementGetMcidMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetMcidMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_mcid", - FunctionDescriptor.of(C_INT, C_POINTER, C_INT), - true); - pdfium4jStructElementGetMcidMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetMcidV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_mcid", + FunctionDescriptor.of(C_INT, C_POINTER, C_INT), + true)); } - private static volatile MethodHandle pdfium4jStructElementGetTypeMH = null; + private static final StableValue pdfium4jStructElementGetTypeV = StableValue.of(); public static MethodHandle pdfium4jStructElementGetType() { - MethodHandle mh = pdfium4jStructElementGetTypeMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetTypeMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_type", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jStructElementGetTypeMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetTypeV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_type", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jStructElementGetTitleMH = null; + private static final StableValue pdfium4jStructElementGetTitleV = StableValue.of(); public static MethodHandle pdfium4jStructElementGetTitle() { - MethodHandle mh = pdfium4jStructElementGetTitleMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetTitleMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_title", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jStructElementGetTitleMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetTitleV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_title", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jStructElementGetAltTextMH = null; + private static final StableValue pdfium4jStructElementGetAltTextV = + StableValue.of(); public static MethodHandle pdfium4jStructElementGetAltText() { - MethodHandle mh = pdfium4jStructElementGetAltTextMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetAltTextMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_alt_text", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jStructElementGetAltTextMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetAltTextV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_alt_text", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jStructElementGetActualTextMH = null; + private static final StableValue pdfium4jStructElementGetActualTextV = + StableValue.of(); public static MethodHandle pdfium4jStructElementGetActualText() { - MethodHandle mh = pdfium4jStructElementGetActualTextMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetActualTextMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_actual_text", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jStructElementGetActualTextMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetActualTextV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_actual_text", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jStructElementGetLangMH = null; + private static final StableValue pdfium4jStructElementGetLangV = StableValue.of(); public static MethodHandle pdfium4jStructElementGetLang() { - MethodHandle mh = pdfium4jStructElementGetLangMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetLangMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_lang", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), - false); - pdfium4jStructElementGetLangMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetLangV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_lang", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jStructElementGetAttributeCountMH = null; + private static final StableValue pdfium4jStructElementGetAttributeCountV = + StableValue.of(); public static MethodHandle pdfium4jStructElementGetAttributeCount() { - MethodHandle mh = pdfium4jStructElementGetAttributeCountMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jStructElementGetAttributeCountMH; - if (mh == null) { - mh = - find( - "pdfium4j_struct_element_get_attribute_count", - FunctionDescriptor.of(C_INT, C_POINTER), - false); - pdfium4jStructElementGetAttributeCountMH = mh; - } - } - } - return mh; + return pdfium4jStructElementGetAttributeCountV.orElseSet( + () -> + find( + "pdfium4j_struct_element_get_attribute_count", + FunctionDescriptor.of(C_INT, C_POINTER), + false)); } - private static volatile MethodHandle pdfium4jTextGetCharsWithBoundsMH = null; + private static final StableValue pdfium4jTextGetCharsWithBoundsV = StableValue.of(); public static MethodHandle pdfium4jTextGetCharsWithBounds() { - MethodHandle mh = pdfium4jTextGetCharsWithBoundsMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jTextGetCharsWithBoundsMH; - if (mh == null) { - mh = - find( - "pdfium4j_text_get_chars_with_bounds", - FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_POINTER), - false); - pdfium4jTextGetCharsWithBoundsMH = mh; - } - } - } - return mh; + return pdfium4jTextGetCharsWithBoundsV.orElseSet( + () -> + find( + "pdfium4j_text_get_chars_with_bounds", + FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_POINTER), + false)); } - private static volatile MethodHandle pdfium4jSaveIncrementalMH = null; + private static final StableValue pdfium4jSaveIncrementalV = StableValue.of(); public static MethodHandle pdfium4jSaveIncremental() { - MethodHandle mh = pdfium4jSaveIncrementalMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jSaveIncrementalMH; - if (mh == null) { - mh = - find( - "pdfium4j_save_incremental", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), - false); - pdfium4jSaveIncrementalMH = mh; - } - } - } - return mh; + return pdfium4jSaveIncrementalV.orElseSet( + () -> + find( + "pdfium4j_save_incremental", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle pdfium4jSaveCopyMH = null; + private static final StableValue pdfium4jSaveCopyV = StableValue.of(); public static MethodHandle pdfium4jSaveCopy() { - MethodHandle mh = pdfium4jSaveCopyMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jSaveCopyMH; - if (mh == null) { - mh = - find("pdfium4j_save_copy", FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), false); - pdfium4jSaveCopyMH = mh; - } - } - } - return mh; + return pdfium4jSaveCopyV.orElseSet( + () -> + find("pdfium4j_save_copy", FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER), false)); } - private static volatile MethodHandle pdfium4jReadInfoDictMH = null; + private static final StableValue pdfium4jReadInfoDictV = StableValue.of(); public static MethodHandle pdfium4jReadInfoDict() { - MethodHandle mh = pdfium4jReadInfoDictMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jReadInfoDictMH; - if (mh == null) { - mh = - find( - "pdfium4j_read_info_dict", - FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER), - false); - pdfium4jReadInfoDictMH = mh; - } - } - } - return mh; + return pdfium4jReadInfoDictV.orElseSet( + () -> + find( + "pdfium4j_read_info_dict", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle pdfium4jReadInfoDictMemMH = null; + private static final StableValue pdfium4jReadInfoDictMemV = StableValue.of(); public static MethodHandle pdfium4jReadInfoDictMem() { - MethodHandle mh = pdfium4jReadInfoDictMemMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jReadInfoDictMemMH; - if (mh == null) { - mh = - find( - "pdfium4j_read_info_dict_mem", - FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_POINTER, C_POINTER), - false); - pdfium4jReadInfoDictMemMH = mh; - } - } - } - return mh; + return pdfium4jReadInfoDictMemV.orElseSet( + () -> + find( + "pdfium4j_read_info_dict_mem", + FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle pdfium4jSaveWithMetadataMH = null; + private static final StableValue pdfium4jSaveWithMetadataV = StableValue.of(); public static MethodHandle pdfium4jSaveWithMetadata() { - MethodHandle mh = pdfium4jSaveWithMetadataMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jSaveWithMetadataMH; - if (mh == null) { - mh = - find( - "pdfium4j_save_with_metadata_native", - FunctionDescriptor.of( - C_INT, C_POINTER, C_POINTER, C_POINTER, C_INT, C_POINTER, C_INT), - false); - pdfium4jSaveWithMetadataMH = mh; - } - } - } - return mh; + return pdfium4jSaveWithMetadataV.orElseSet( + () -> + find( + "pdfium4j_save_with_metadata_native", + FunctionDescriptor.of( + C_INT, C_POINTER, C_POINTER, C_POINTER, C_INT, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle pdfium4jSaveWithMetadataMemMH = null; + private static final StableValue pdfium4jSaveWithMetadataMemV = StableValue.of(); public static MethodHandle pdfium4jSaveWithMetadataMem() { - MethodHandle mh = pdfium4jSaveWithMetadataMemMH; - if (mh == null) { - synchronized (ShimBindings.class) { - mh = pdfium4jSaveWithMetadataMemMH; - if (mh == null) { - mh = - find( - "pdfium4j_save_with_metadata_mem_native", - FunctionDescriptor.of( - C_INT, C_POINTER, C_SIZE_T, C_POINTER, C_POINTER, C_POINTER, C_INT, C_POINTER, - C_INT), - false); - pdfium4jSaveWithMetadataMemMH = mh; - } - } - } - return mh; + return pdfium4jSaveWithMetadataMemV.orElseSet( + () -> + find( + "pdfium4j_save_with_metadata_mem_native", + FunctionDescriptor.of( + C_INT, C_POINTER, C_SIZE_T, C_POINTER, C_POINTER, C_POINTER, C_INT, C_POINTER, + C_INT), + false)); + } + + private static final StableValue pdfium4jSaveWithMetadataMemToFileV = + StableValue.of(); + + public static MethodHandle pdfium4jSaveWithMetadataMemToFile() { + return pdfium4jSaveWithMetadataMemToFileV.orElseSet( + () -> + find( + "pdfium4j_save_with_metadata_mem_to_file_native", + FunctionDescriptor.of( + C_INT, C_POINTER, C_SIZE_T, C_POINTER, C_POINTER, C_INT, C_POINTER, C_INT), + false)); + } + + private static final StableValue pdfium4jGetXmpQpdfV = StableValue.of(); + + public static MethodHandle pdfium4jGetXmpQpdf() { + return pdfium4jGetXmpQpdfV.orElseSet( + () -> + find( + "pdfium4j_get_xmp_qpdf", + FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_INT), + false)); + } + + private static final StableValue pdfium4jGetXmpQpdfMemV = StableValue.of(); + + public static MethodHandle pdfium4jGetXmpQpdfMem() { + return pdfium4jGetXmpQpdfMemV.orElseSet( + () -> + find( + "pdfium4j_get_xmp_qpdf_mem", + FunctionDescriptor.of(C_INT, C_POINTER, C_SIZE_T, C_POINTER, C_INT), + false)); + } + + private static final StableValue pdfium4jResolveOptionalSymbolsV = StableValue.of(); + + public static MethodHandle pdfium4jResolveOptionalSymbols() { + return pdfium4jResolveOptionalSymbolsV.orElseSet( + () -> find("pdfium4j_resolve_optional_symbols", FunctionDescriptor.ofVoid(), false)); } private static volatile MemorySegment WRITE_BLOCK_CALLBACK_STUB = null; diff --git a/src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java index 954cb07..3539522 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/TextBindings.java @@ -39,176 +39,86 @@ public static void checkRequired() { } } - private static volatile MethodHandle FPDFText_LoadPage_MH = null; + private static final StableValue FPDFText_LoadPage_V = StableValue.of(); public static MethodHandle fpdfTextLoadPage() { - MethodHandle mh = FPDFText_LoadPage_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFText_LoadPage_MH; - if (mh == null) { - mh = find("FPDFText_LoadPage", FunctionDescriptor.of(C_POINTER, C_POINTER), false); - FPDFText_LoadPage_MH = mh; - } - } - } - return mh; + return FPDFText_LoadPage_V.orElseSet( + () -> find("FPDFText_LoadPage", FunctionDescriptor.of(C_POINTER, C_POINTER), false)); } - private static volatile MethodHandle FPDFText_ClosePage_MH = null; + private static final StableValue FPDFText_ClosePage_V = StableValue.of(); public static MethodHandle fpdfTextClosePage() { - MethodHandle mh = FPDFText_ClosePage_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFText_ClosePage_MH; - if (mh == null) { - mh = find("FPDFText_ClosePage", FunctionDescriptor.ofVoid(C_POINTER), false); - FPDFText_ClosePage_MH = mh; - } - } - } - return mh; + return FPDFText_ClosePage_V.orElseSet( + () -> find("FPDFText_ClosePage", FunctionDescriptor.ofVoid(C_POINTER), false)); } - private static volatile MethodHandle FPDFText_CountChars_MH = null; + private static final StableValue FPDFText_CountChars_V = StableValue.of(); public static MethodHandle fpdfTextCountChars() { - MethodHandle mh = FPDFText_CountChars_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFText_CountChars_MH; - if (mh == null) { - mh = find("FPDFText_CountChars", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFText_CountChars_MH = mh; - } - } - } - return mh; + return FPDFText_CountChars_V.orElseSet( + () -> find("FPDFText_CountChars", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFText_GetText_MH = null; + private static final StableValue FPDFText_GetText_V = StableValue.of(); public static MethodHandle fpdfTextGetText() { - MethodHandle mh = FPDFText_GetText_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFText_GetText_MH; - if (mh == null) { - mh = - find( - "FPDFText_GetText", - FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_POINTER), - false); - FPDFText_GetText_MH = mh; - } - } - } - return mh; + return FPDFText_GetText_V.orElseSet( + () -> + find( + "FPDFText_GetText", + FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_POINTER), + false)); } - private static volatile MethodHandle FPDFLink_LoadWebLinks_MH = null; + private static final StableValue FPDFLink_LoadWebLinks_V = StableValue.of(); public static MethodHandle fpdfLinkLoadWebLinks() { - MethodHandle mh = FPDFLink_LoadWebLinks_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFLink_LoadWebLinks_MH; - if (mh == null) { - mh = find("FPDFLink_LoadWebLinks", FunctionDescriptor.of(C_POINTER, C_POINTER), false); - FPDFLink_LoadWebLinks_MH = mh; - } - } - } - return mh; + return FPDFLink_LoadWebLinks_V.orElseSet( + () -> find("FPDFLink_LoadWebLinks", FunctionDescriptor.of(C_POINTER, C_POINTER), false)); } - private static volatile MethodHandle FPDFLink_CountWebLinks_MH = null; + private static final StableValue FPDFLink_CountWebLinks_V = StableValue.of(); public static MethodHandle fpdfLinkCountWebLinks() { - MethodHandle mh = FPDFLink_CountWebLinks_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFLink_CountWebLinks_MH; - if (mh == null) { - mh = find("FPDFLink_CountWebLinks", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDFLink_CountWebLinks_MH = mh; - } - } - } - return mh; + return FPDFLink_CountWebLinks_V.orElseSet( + () -> find("FPDFLink_CountWebLinks", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDFLink_GetURL_MH = null; + private static final StableValue FPDFLink_GetURL_V = StableValue.of(); public static MethodHandle fpdfLinkGetURL() { - MethodHandle mh = FPDFLink_GetURL_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFLink_GetURL_MH; - if (mh == null) { - mh = - find( - "FPDFLink_GetURL", - FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER, C_INT), - false); - FPDFLink_GetURL_MH = mh; - } - } - } - return mh; + return FPDFLink_GetURL_V.orElseSet( + () -> + find( + "FPDFLink_GetURL", + FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_POINTER, C_INT), + false)); } - private static volatile MethodHandle FPDFLink_CountRects_MH = null; + private static final StableValue FPDFLink_CountRects_V = StableValue.of(); public static MethodHandle fpdfLinkCountRects() { - MethodHandle mh = FPDFLink_CountRects_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFLink_CountRects_MH; - if (mh == null) { - mh = find("FPDFLink_CountRects", FunctionDescriptor.of(C_INT, C_POINTER, C_INT), true); - FPDFLink_CountRects_MH = mh; - } - } - } - return mh; + return FPDFLink_CountRects_V.orElseSet( + () -> find("FPDFLink_CountRects", FunctionDescriptor.of(C_INT, C_POINTER, C_INT), true)); } - private static volatile MethodHandle FPDFLink_GetRect_MH = null; + private static final StableValue FPDFLink_GetRect_V = StableValue.of(); public static MethodHandle fpdfLinkGetRect() { - MethodHandle mh = FPDFLink_GetRect_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFLink_GetRect_MH; - if (mh == null) { - mh = - find( - "FPDFLink_GetRect", - FunctionDescriptor.of( - C_INT, C_POINTER, C_INT, C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER), - false); - FPDFLink_GetRect_MH = mh; - } - } - } - return mh; + return FPDFLink_GetRect_V.orElseSet( + () -> + find( + "FPDFLink_GetRect", + FunctionDescriptor.of( + C_INT, C_POINTER, C_INT, C_INT, C_POINTER, C_POINTER, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle FPDFLink_CloseWebLinks_MH = null; + private static final StableValue FPDFLink_CloseWebLinks_V = StableValue.of(); public static MethodHandle fpdfLinkCloseWebLinks() { - MethodHandle mh = FPDFLink_CloseWebLinks_MH; - if (mh == null) { - synchronized (TextBindings.class) { - mh = FPDFLink_CloseWebLinks_MH; - if (mh == null) { - mh = find("FPDFLink_CloseWebLinks", FunctionDescriptor.ofVoid(C_POINTER), false); - FPDFLink_CloseWebLinks_MH = mh; - } - } - } - return mh; + return FPDFLink_CloseWebLinks_V.orElseSet( + () -> find("FPDFLink_CloseWebLinks", FunctionDescriptor.ofVoid(C_POINTER), false)); } } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java index dba2a7c..0d4b7fb 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/ThumbnailBindings.java @@ -9,6 +9,7 @@ import java.util.Optional; /** FFM bindings for PDFium page thumbnail functions from {@code fpdf_thumbnail.h}. */ +@SuppressWarnings("preview") public final class ThumbnailBindings { private ThumbnailBindings() {} diff --git a/src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java b/src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java index 4f89c6c..95a70e4 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/TrigramTokenizer.java @@ -17,23 +17,19 @@ public static long[] generateTrigramHashes(String text) { if (text == null || text.length() < 3) { return Generators.emptyLongArray(); } - int len = text.length(); - int count = len - 2; - long[] hashes = new long[count]; - for (int i = 0; i < count; i++) { - hashes[i] = + int n = text.length() - 2; + long[] tmp = new long[n]; + for (int i = 0; i < n; i++) { + tmp[i] = ((long) text.charAt(i) << 32) | ((long) text.charAt(i + 1) << 16) | text.charAt(i + 2); } - Arrays.sort(hashes); - - // Deduplicate in-place - if (hashes.length <= 1) return hashes; - int uniqueCount = 1; - for (int i = 1; i < hashes.length; i++) { - if (hashes[i] != hashes[i - 1]) { - hashes[uniqueCount++] = hashes[i]; + Arrays.sort(tmp); + int unique = 1; + for (int i = 1; i < tmp.length; i++) { + if (tmp[i] != tmp[unique - 1]) { + tmp[unique++] = tmp[i]; } } - return uniqueCount == hashes.length ? hashes : Arrays.copyOf(hashes, uniqueCount); + return unique == tmp.length ? tmp : Arrays.copyOf(tmp, unique); } } diff --git a/src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java b/src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java index 61bc882..915fb46 100644 --- a/src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java +++ b/src/main/java/org/grimmory/pdfium4j/internal/ViewBindings.java @@ -68,313 +68,161 @@ public static void checkRequired() { public static final FunctionDescriptor GET_BLOCK_DESC = FunctionDescriptor.of(C_INT, C_POINTER, C_LONG, C_POINTER, C_LONG); - private static volatile MethodHandle FPDF_InitLibraryWithConfig_MH = null; + private static final StableValue FPDF_InitLibraryWithConfig_V = StableValue.of(); public static MethodHandle fpdfInitLibraryWithConfig() { - MethodHandle mh = FPDF_InitLibraryWithConfig_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_InitLibraryWithConfig_MH; - if (mh == null) { - mh = find("FPDF_InitLibraryWithConfig", FunctionDescriptor.ofVoid(C_POINTER), false); - FPDF_InitLibraryWithConfig_MH = mh; - } - } - } - return mh; + return FPDF_InitLibraryWithConfig_V.orElseSet( + () -> find("FPDF_InitLibraryWithConfig", FunctionDescriptor.ofVoid(C_POINTER), false)); } - private static volatile MethodHandle FPDF_DestroyLibrary_MH = null; + private static final StableValue FPDF_DestroyLibrary_V = StableValue.of(); public static MethodHandle fpdfDestroyLibrary() { - MethodHandle mh = FPDF_DestroyLibrary_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_DestroyLibrary_MH; - if (mh == null) { - mh = find("FPDF_DestroyLibrary", FunctionDescriptor.ofVoid(), false); - FPDF_DestroyLibrary_MH = mh; - } - } - } - return mh; + return FPDF_DestroyLibrary_V.orElseSet( + () -> find("FPDF_DestroyLibrary", FunctionDescriptor.ofVoid(), false)); } - private static volatile MethodHandle FPDF_LoadDocument_MH = null; + private static final StableValue FPDF_LoadDocument_V = StableValue.of(); public static MethodHandle fpdfLoadDocument() { - MethodHandle mh = FPDF_LoadDocument_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_LoadDocument_MH; - if (mh == null) { - mh = - find( - "FPDF_LoadDocument", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - false); - FPDF_LoadDocument_MH = mh; - } - } - } - return mh; + return FPDF_LoadDocument_V.orElseSet( + () -> + find( + "FPDF_LoadDocument", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle FPDF_LoadMemDocument_MH = null; + private static final StableValue FPDF_LoadMemDocument_V = StableValue.of(); public static MethodHandle fpdfLoadMemDocument() { - MethodHandle mh = FPDF_LoadMemDocument_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_LoadMemDocument_MH; - if (mh == null) { - mh = - find( - "FPDF_LoadMemDocument", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT, C_POINTER), - false); - FPDF_LoadMemDocument_MH = mh; - } - } - } - return mh; + return FPDF_LoadMemDocument_V.orElseSet( + () -> + find( + "FPDF_LoadMemDocument", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT, C_POINTER), + false)); } - private static volatile MethodHandle FPDF_LoadCustomDocument_MH = null; + private static final StableValue FPDF_LoadCustomDocument_V = StableValue.of(); public static MethodHandle fpdfLoadCustomDocument() { - MethodHandle mh = FPDF_LoadCustomDocument_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_LoadCustomDocument_MH; - if (mh == null) { - mh = - find( - "FPDF_LoadCustomDocument", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), - false); - FPDF_LoadCustomDocument_MH = mh; - } - } - } - return mh; + return FPDF_LoadCustomDocument_V.orElseSet( + () -> + find( + "FPDF_LoadCustomDocument", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER), + false)); } - private static volatile MethodHandle FPDF_CloseDocument_MH = null; + private static final StableValue FPDF_CloseDocument_V = StableValue.of(); public static MethodHandle fpdfCloseDocument() { - MethodHandle mh = FPDF_CloseDocument_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_CloseDocument_MH; - if (mh == null) { - mh = find("FPDF_CloseDocument", FunctionDescriptor.ofVoid(C_POINTER), false); - FPDF_CloseDocument_MH = mh; - } - } - } - return mh; + return FPDF_CloseDocument_V.orElseSet( + () -> find("FPDF_CloseDocument", FunctionDescriptor.ofVoid(C_POINTER), false)); } - private static volatile MethodHandle FPDF_GetLastError_MH = null; + private static final StableValue FPDF_GetLastError_V = StableValue.of(); public static MethodHandle fpdfGetLastError() { - MethodHandle mh = FPDF_GetLastError_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_GetLastError_MH; - if (mh == null) { - mh = find("FPDF_GetLastError", FunctionDescriptor.of(C_INT), true); - FPDF_GetLastError_MH = mh; - } - } - } - return mh; + return FPDF_GetLastError_V.orElseSet( + () -> find("FPDF_GetLastError", FunctionDescriptor.of(C_INT), true)); } - private static volatile MethodHandle FPDF_DocumentHasValidCrossReferenceTable_MH = null; + private static final StableValue FPDF_DocumentHasValidCrossReferenceTable_V = + StableValue.of(); public static MethodHandle fpdfDocumentHasValidCrossReferenceTable() { - MethodHandle mh = FPDF_DocumentHasValidCrossReferenceTable_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_DocumentHasValidCrossReferenceTable_MH; - if (mh == null) { - mh = - find( - "FPDF_DocumentHasValidCrossReferenceTable", - FunctionDescriptor.of(C_INT, C_POINTER), - true); - FPDF_DocumentHasValidCrossReferenceTable_MH = mh; - } - } - } - return mh; + return FPDF_DocumentHasValidCrossReferenceTable_V.orElseSet( + () -> + find( + "FPDF_DocumentHasValidCrossReferenceTable", + FunctionDescriptor.of(C_INT, C_POINTER), + true)); } - private static volatile MethodHandle FPDF_GetTrailerEnds_MH = null; + private static final StableValue FPDF_GetTrailerEnds_V = StableValue.of(); public static MethodHandle fpdfGetTrailerEnds() { - MethodHandle mh = FPDF_GetTrailerEnds_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_GetTrailerEnds_MH; - if (mh == null) { - mh = - find( - "FPDF_GetTrailerEnds", - FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_LONG), - false); - FPDF_GetTrailerEnds_MH = mh; - } - } - } - return mh; + return FPDF_GetTrailerEnds_V.orElseSet( + () -> + find( + "FPDF_GetTrailerEnds", + FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_LONG), + false)); } - private static volatile MethodHandle FPDF_GetPageCount_MH = null; + private static final StableValue FPDF_GetPageCount_V = StableValue.of(); public static MethodHandle fpdfGetPageCount() { - MethodHandle mh = FPDF_GetPageCount_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_GetPageCount_MH; - if (mh == null) { - mh = find("FPDF_GetPageCount", FunctionDescriptor.of(C_INT, C_POINTER), true); - FPDF_GetPageCount_MH = mh; - } - } - } - return mh; + return FPDF_GetPageCount_V.orElseSet( + () -> find("FPDF_GetPageCount", FunctionDescriptor.of(C_INT, C_POINTER), true)); } - private static volatile MethodHandle FPDF_LoadPage_MH = null; + private static final StableValue FPDF_LoadPage_V = StableValue.of(); public static MethodHandle fpdfLoadPage() { - MethodHandle mh = FPDF_LoadPage_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_LoadPage_MH; - if (mh == null) { - mh = find("FPDF_LoadPage", FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), false); - FPDF_LoadPage_MH = mh; - } - } - } - return mh; + return FPDF_LoadPage_V.orElseSet( + () -> find("FPDF_LoadPage", FunctionDescriptor.of(C_POINTER, C_POINTER, C_INT), false)); } - private static volatile MethodHandle FPDF_ClosePage_MH = null; + private static final StableValue FPDF_ClosePage_V = StableValue.of(); public static MethodHandle fpdfClosePage() { - MethodHandle mh = FPDF_ClosePage_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_ClosePage_MH; - if (mh == null) { - mh = find("FPDF_ClosePage", FunctionDescriptor.ofVoid(C_POINTER), false); - FPDF_ClosePage_MH = mh; - } - } - } - return mh; + return FPDF_ClosePage_V.orElseSet( + () -> find("FPDF_ClosePage", FunctionDescriptor.ofVoid(C_POINTER), false)); } - private static volatile MethodHandle FPDF_GetPageWidthF_MH = null; + private static final StableValue FPDF_GetPageWidthF_V = StableValue.of(); public static MethodHandle fpdfGetPageWidthF() { - MethodHandle mh = FPDF_GetPageWidthF_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_GetPageWidthF_MH; - if (mh == null) { - mh = - find( - "FPDF_GetPageWidthF", - FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER), - true); - FPDF_GetPageWidthF_MH = mh; - } - } - } - return mh; + return FPDF_GetPageWidthF_V.orElseSet( + () -> + find( + "FPDF_GetPageWidthF", + FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER), + true)); } - private static volatile MethodHandle FPDF_GetPageHeightF_MH = null; + private static final StableValue FPDF_GetPageHeightF_V = StableValue.of(); public static MethodHandle fpdfGetPageHeightF() { - MethodHandle mh = FPDF_GetPageHeightF_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_GetPageHeightF_MH; - if (mh == null) { - mh = - find( - "FPDF_GetPageHeightF", - FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER), - true); - FPDF_GetPageHeightF_MH = mh; - } - } - } - return mh; + return FPDF_GetPageHeightF_V.orElseSet( + () -> + find( + "FPDF_GetPageHeightF", + FunctionDescriptor.of(ValueLayout.JAVA_FLOAT, C_POINTER), + true)); } - private static volatile MethodHandle FPDF_RenderPageBitmap_MH = null; + private static final StableValue FPDF_RenderPageBitmap_V = StableValue.of(); public static MethodHandle fpdfRenderPageBitmap() { - MethodHandle mh = FPDF_RenderPageBitmap_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_RenderPageBitmap_MH; - if (mh == null) { - mh = - find( - "FPDF_RenderPageBitmap", - FunctionDescriptor.ofVoid( - C_POINTER, C_POINTER, C_INT, C_INT, C_INT, C_INT, C_INT, C_INT), - false); - FPDF_RenderPageBitmap_MH = mh; - } - } - } - return mh; + return FPDF_RenderPageBitmap_V.orElseSet( + () -> + find( + "FPDF_RenderPageBitmap", + FunctionDescriptor.ofVoid( + C_POINTER, C_POINTER, C_INT, C_INT, C_INT, C_INT, C_INT, C_INT), + false)); } - private static volatile MethodHandle FPDF_SetRendererType_MH = null; + private static final StableValue FPDF_SetRendererType_V = StableValue.of(); public static MethodHandle fpdfSetRendererType() { - MethodHandle mh = FPDF_SetRendererType_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_SetRendererType_MH; - if (mh == null) { - mh = find("FPDF_SetRendererType", FunctionDescriptor.ofVoid(C_INT), false); - FPDF_SetRendererType_MH = mh; - } - } - } - return mh; + return FPDF_SetRendererType_V.orElseSet( + () -> find("FPDF_SetRendererType", FunctionDescriptor.ofVoid(C_INT), false)); } - private static volatile MethodHandle FPDF_LoadMemDocument64_MH = null; + private static final StableValue FPDF_LoadMemDocument64_V = StableValue.of(); public static MethodHandle fpdfLoadMemDocument64() { - MethodHandle mh = FPDF_LoadMemDocument64_MH; - if (mh == null) { - synchronized (ViewBindings.class) { - mh = FPDF_LoadMemDocument64_MH; - if (mh == null) { - mh = - find( - "FPDF_LoadMemDocument64", - FunctionDescriptor.of(C_POINTER, C_POINTER, C_SIZE_T, C_POINTER), - false); - FPDF_LoadMemDocument64_MH = mh; - } - } - } - return mh; + return FPDF_LoadMemDocument64_V.orElseSet( + () -> + find( + "FPDF_LoadMemDocument64", + FunctionDescriptor.of(C_POINTER, C_POINTER, C_SIZE_T, C_POINTER), + false)); } public static final int FPDF_RENDERER_TYPE_SKIA = 1; diff --git a/src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java b/src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java index 5590b14..41ba3dc 100644 --- a/src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java +++ b/src/main/java/org/grimmory/pdfium4j/model/BookMetadata.java @@ -14,6 +14,8 @@ enum BookFormat { Optional title(); + Optional subtitle(); + List authors(); Optional series(); diff --git a/src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java b/src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java index 6c95e65..366dc8a 100644 --- a/src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java +++ b/src/main/java/org/grimmory/pdfium4j/model/PdfBookMetadata.java @@ -28,6 +28,7 @@ */ public record PdfBookMetadata( Optional title, + Optional subtitle, List authors, Optional series, Optional seriesNumber, diff --git a/src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java b/src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java index c58f5fd..37168b4 100644 --- a/src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java +++ b/src/main/java/org/grimmory/pdfium4j/model/XmpMetadata.java @@ -8,9 +8,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Structured representation of XMP metadata extracted from a PDF. Contains Dublin Core fields, @@ -97,14 +97,14 @@ public Optional calibreSeries() { return Optional.ofNullable(calibreFields.get("series")).filter(s -> !s.isBlank()); } - /** Get Calibre series index as a double, if present and parseable. */ - public OptionalDouble calibreSeriesIndex() { + /** Get Calibre series index, if present and parseable. */ + public Optional calibreSeriesIndex() { String val = calibreFields.get("series_index"); - if (val == null || val.isBlank()) return OptionalDouble.empty(); + if (val == null || val.isBlank()) return Optional.empty(); try { - return OptionalDouble.of(Double.parseDouble(val)); + return Optional.of(Double.parseDouble(val)); } catch (NumberFormatException e) { - return OptionalDouble.empty(); + return Optional.empty(); } } @@ -129,6 +129,40 @@ public List calibreTags() { return Arrays.stream(val.split(",")).map(String::trim).filter(s -> !s.isEmpty()).toList(); } + /** + * Finds a simple field by its local name, searching across all custom namespaces. Returns the + * first match found. + */ + public Optional findField(String localName) { + String suffix = ":" + localName; + for (Map.Entry entry : customFields.entrySet()) { + if (entry.getKey().endsWith(suffix) || entry.getKey().equals(localName)) { + return Optional.of(entry.getValue()); + } + } + // Also check calibre fields + for (Map.Entry entry : calibreFields.entrySet()) { + if (entry.getKey().equals(localName)) { + return Optional.of(entry.getValue()); + } + } + return Optional.empty(); + } + + /** + * Finds a list field by its local name, searching across all custom namespaces. Returns the first + * match found. + */ + public List findListField(String localName) { + String suffix = ":" + localName; + for (Map.Entry> entry : customListFields.entrySet()) { + if (entry.getKey().endsWith(suffix) || entry.getKey().equals(localName)) { + return entry.getValue(); + } + } + return Collections.emptyList(); + } + /** * Get an XMP identifier value by its scheme (case-insensitive). * @@ -144,6 +178,25 @@ public Optional xmpIdentifier(String scheme) { .findFirst(); } + /** Creates a new {@link Builder} initialized with the values of this metadata. */ + public Builder toBuilder() { + return new Builder() + .title(title.orElse(null)) + .creators(creators) + .description(description.orElse(null)) + .subjects(subjects) + .publisher(publisher.orElse(null)) + .language(language.orElse(null)) + .date(date.orElse(null)) + .rights(rights.orElse(null)) + .identifiers(identifiers) + .pdfaConformance(pdfaConformance.orElse(null)) + .calibreFields(calibreFields) + .customFields(customFields) + .customListFields(customListFields) + .xmpIdentifiers(xmpIdentifiers); + } + /** Create an empty XmpMetadata. */ public static XmpMetadata empty() { return builder().build(); @@ -156,108 +209,153 @@ public static Builder builder() { /** Builder for {@link XmpMetadata} to insulate callers from record component additions. */ public static final class Builder { - private Optional title = Optional.empty(); - private List creators = new ArrayList<>(8); - private Optional description = Optional.empty(); - private List subjects = new ArrayList<>(8); - private Optional publisher = Optional.empty(); - private Optional language = Optional.empty(); - private Optional date = Optional.empty(); - private Optional rights = Optional.empty(); - private List identifiers = new ArrayList<>(8); - private Optional pdfaConformance = Optional.empty(); - private Map calibreFields = LinkedHashMap.newLinkedHashMap(8); - private Map customFields = LinkedHashMap.newLinkedHashMap(8); - private Map> customListFields = LinkedHashMap.newLinkedHashMap(8); - private List xmpIdentifiers = new ArrayList<>(8); + private String title; + private final List creators = new ArrayList<>(8); + private String description; + private final List subjects = new ArrayList<>(8); + private String publisher; + private String language; + private String date; + private String rights; + private final List identifiers = new ArrayList<>(8); + private String pdfaConformance; + private final Map calibreFields = LinkedHashMap.newLinkedHashMap(8); + private final Map customFields = LinkedHashMap.newLinkedHashMap(8); + private final Map> customListFields = LinkedHashMap.newLinkedHashMap(8); + private final List xmpIdentifiers = new ArrayList<>(8); public Builder title(String val) { - this.title = Optional.ofNullable(val); + this.title = val; return this; } public Builder creators(List val) { - this.creators = new ArrayList<>(val); + this.creators.clear(); + if (val != null) this.creators.addAll(val); + return this; + } + + public Builder addCreator(String val) { + if (val != null) this.creators.add(val); return this; } public Builder description(String val) { - this.description = Optional.ofNullable(val); + this.description = val; return this; } public Builder subjects(List val) { - this.subjects = new ArrayList<>(val); + this.subjects.clear(); + if (val != null) this.subjects.addAll(val); + return this; + } + + public Builder addSubject(String val) { + if (val != null) this.subjects.add(val); return this; } public Builder publisher(String val) { - this.publisher = Optional.ofNullable(val); + this.publisher = val; return this; } public Builder language(String val) { - this.language = Optional.ofNullable(val); + this.language = val; return this; } public Builder date(String val) { - this.date = Optional.ofNullable(val); + this.date = val; return this; } public Builder rights(String val) { - this.rights = Optional.ofNullable(val); + this.rights = val; return this; } public Builder identifiers(List val) { - this.identifiers = new ArrayList<>(val); + this.identifiers.clear(); + if (val != null) this.identifiers.addAll(val); + return this; + } + + public Builder addIdentifier(String val) { + if (val != null) this.identifiers.add(val); return this; } public Builder pdfaConformance(String val) { - this.pdfaConformance = Optional.ofNullable(val); + this.pdfaConformance = val; return this; } public Builder calibreFields(Map val) { - this.calibreFields = new LinkedHashMap<>(val); + this.calibreFields.clear(); + if (val != null) this.calibreFields.putAll(val); + return this; + } + + public Builder putCalibreField(String k, String v) { + if (k != null && v != null) this.calibreFields.put(k, v); return this; } public Builder customFields(Map val) { - this.customFields = new LinkedHashMap<>(val); + this.customFields.clear(); + if (val != null) this.customFields.putAll(val); + return this; + } + + public Builder putCustomField(String k, String v) { + if (k != null && v != null) this.customFields.put(k, v); return this; } public Builder customListFields(Map> val) { - this.customListFields = LinkedHashMap.newLinkedHashMap(val.size()); - val.forEach((k, v) -> this.customListFields.put(k, new ArrayList<>(v))); + this.customListFields.clear(); + if (val != null) { + val.forEach((k, v) -> this.customListFields.put(k, new ArrayList<>(v))); + } + return this; + } + + public Builder putCustomListField(String k, List v) { + if (k != null && v != null) this.customListFields.put(k, new ArrayList<>(v)); return this; } public Builder xmpIdentifiers(List val) { - this.xmpIdentifiers = new ArrayList<>(val); + this.xmpIdentifiers.clear(); + if (val != null) this.xmpIdentifiers.addAll(val); + return this; + } + + public Builder addXmpIdentifier(QualifiedIdentifier val) { + if (val != null) this.xmpIdentifiers.add(val); return this; } public XmpMetadata build() { return new XmpMetadata( - title, - creators, - description, - subjects, - publisher, - language, - date, - rights, - identifiers, - pdfaConformance, - calibreFields, - customFields, - customListFields, - xmpIdentifiers); + Optional.ofNullable(title), + List.copyOf(creators), + Optional.ofNullable(description), + List.copyOf(subjects), + Optional.ofNullable(publisher), + Optional.ofNullable(language), + Optional.ofNullable(date), + Optional.ofNullable(rights), + List.copyOf(identifiers), + Optional.ofNullable(pdfaConformance), + Map.copyOf(calibreFields), + Map.copyOf(customFields), + customListFields.entrySet().stream() + .collect( + Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> List.copyOf(e.getValue()))), + List.copyOf(xmpIdentifiers)); } } } diff --git a/src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java b/src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java index 1d03663..6656d69 100644 --- a/src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java +++ b/src/test/java/org/grimmory/pdfium4j/CorpusMetadataRoundTripTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -10,6 +11,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,6 +20,7 @@ import java.util.stream.Stream; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; +import org.grimmory.pdfium4j.internal.InternalLogger; import org.grimmory.pdfium4j.model.MetadataTag; import org.grimmory.pdfium4j.model.PdfProcessingPolicy; import org.grimmory.pdfium4j.model.XmpMetadata; @@ -152,8 +155,8 @@ void roundtripCorpusTest(Path sourcePdf) throws Exception { assertEquals(testTitle, doc.metadata(MetadataTag.TITLE).orElse(null)); XmpMetadata xmp = XmpMetadataParser.parseFrom(doc); assertNotNull(xmp, "XMP missing"); - List tags = xmp.customListFields().get("xmp:CustomKey"); - assertNotNull(tags, "XMP missing custom key: xmp:CustomKey"); + List tags = xmp.findListField("CustomKey"); + assertFalse(tags.isEmpty(), "XMP missing custom key: CustomKey"); assertTrue(tags.contains(customTagValue), "Custom tag value mismatch"); } } catch (Throwable t) { @@ -165,6 +168,12 @@ void roundtripCorpusTest(Path sourcePdf) throws Exception { System.err.println("Failed to copy debug file: " + e.getMessage()); } throw t; + } finally { + try (Stream walk = Files.walk(tempDir)) { + walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException _) { + InternalLogger.warn("Cleanup of temporary directory failed (ignored)"); + } } } diff --git a/src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java b/src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java index 8a2d2ac..3f0e096 100644 --- a/src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java +++ b/src/test/java/org/grimmory/pdfium4j/NativeSmokeTest.java @@ -1,7 +1,7 @@ package org.grimmory.pdfium4j; public class NativeSmokeTest { - public static void main(String[] args) { + static void main(String[] args) { try { System.out.println("Starting PDFium4j Smoke Test..."); System.out.println("OS: " + System.getProperty("os.name")); diff --git a/src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java b/src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java index 2077b7e..a389aa0 100644 --- a/src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java +++ b/src/test/java/org/grimmory/pdfium4j/PdfDocumentTest.java @@ -7,6 +7,7 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.lang.foreign.MemorySegment; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; @@ -571,6 +572,33 @@ void saveToBytes() throws IOException { } } + @Test + @EnabledIf("pdfiumAvailable") + void saveToOutputStreamCallbackFailureMapsToWriteError() throws IOException { + Path testPdf = getTestPdf(); + if (testPdf == null) return; + + try (PdfDocument doc = PdfDocument.open(testPdf)) { + doc.setMetadata(MetadataTag.TITLE, "write-failure"); + try (OutputStream failingOut = + new OutputStream() { + @Override + public void write(int b) throws IOException { + throw new IOException("intentional write failure"); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + throw new IOException("intentional write failure"); + } + }) { + + IOException ex = assertThrows(IOException.class, () -> doc.save(failingOut)); + assertTrue(ex.getMessage().contains("Failed to write output PDF file")); + } + } + } + @Test @EnabledIf("pdfiumAvailable") void saveToFile(@TempDir Path tempDir) throws IOException { @@ -1637,24 +1665,38 @@ private static byte[] minimalXrefStreamPdf() { byte[] xrefData = new byte[5 * 5]; // 5 entries × 5 bytes int di = 0; // Object 0: free entry - xrefData[di++] = 0; - xrefData[di++] = 0; - xrefData[di++] = 0; - xrefData[di++] = 0; - xrefData[di++] = 0; + xrefData[di] = 0; + di++; + xrefData[di] = 0; + di++; + xrefData[di] = 0; + di++; + xrefData[di] = 0; + di++; + xrefData[di] = 0; + di++; // Objects 1–3: in-use for (int off : offsets) { - xrefData[di++] = 1; - xrefData[di++] = (byte) ((off >> 24) & 0xFF); - xrefData[di++] = (byte) ((off >> 16) & 0xFF); - xrefData[di++] = (byte) ((off >> 8) & 0xFF); - xrefData[di++] = (byte) (off & 0xFF); + xrefData[di] = 1; + di++; + xrefData[di] = (byte) ((off >> 24) & 0xFF); + di++; + xrefData[di] = (byte) ((off >> 16) & 0xFF); + di++; + xrefData[di] = (byte) ((off >> 8) & 0xFF); + di++; + xrefData[di] = (byte) (off & 0xFF); + di++; } // Object 4: the xref stream itself - xrefData[di++] = 1; - xrefData[di++] = (byte) ((xrefStreamOffset >> 24) & 0xFF); - xrefData[di++] = (byte) ((xrefStreamOffset >> 16) & 0xFF); - xrefData[di++] = (byte) ((xrefStreamOffset >> 8) & 0xFF); + xrefData[di] = 1; + di++; + xrefData[di] = (byte) ((xrefStreamOffset >> 24) & 0xFF); + di++; + xrefData[di] = (byte) ((xrefStreamOffset >> 16) & 0xFF); + di++; + xrefData[di] = (byte) ((xrefStreamOffset >> 8) & 0xFF); + di++; xrefData[di] = (byte) (xrefStreamOffset & 0xFF); writeBytes( diff --git a/src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java b/src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java index c6a934f..663e67c 100644 --- a/src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java +++ b/src/test/java/org/grimmory/pdfium4j/XmpMetadataParserTest.java @@ -9,27 +9,29 @@ class XmpMetadataParserTest { private static final String BASIC_XMP = - "" - + "" - + "" - + "" - + "My Book Title" - + "Author AAuthor B" - + "A great description" - + "FantasyAdventure" - + "My Publisher" - + "en" - + "2024-06-15" - + "Copyright 2024" - + "urn:isbn:978-1234567890" - + "" - + "" - + "Epic Series" - + "3.5" - + "8" - + "Tag 1Tag 2" - + "" - + ""; + """ + \ + \ + \ + \ + My Book Title\ + Author AAuthor B\ + A great description\ + FantasyAdventure\ + My Publisher\ + en\ + 2024-06-15\ + Copyright 2024\ + urn:isbn:978-1234567890\ + \ + \ + Epic Series\ + 3.5\ + 8\ + Tag 1Tag 2\ + \ + \ + """; private static final String BOOKLORE_XMP = "" @@ -69,7 +71,7 @@ void parsesBasicDublinCore() { void parsesCalibreFields() { XmpMetadata meta = XmpMetadataParser.parse(BASIC_XMP); assertEquals("Epic Series", meta.calibreSeries().orElse("")); - assertEquals(3.5, meta.calibreSeriesIndex().orElse(0), 0.01); + assertEquals(3.5, meta.calibreSeriesIndex().orElse(0.0), 0.01); assertEquals(8, meta.calibreRating().orElse(0)); assertEquals(List.of("Tag 1", "Tag 2"), meta.calibreTags()); } @@ -121,7 +123,8 @@ void writerParserRoundTrip() { assertEquals(original.date(), parsed.date()); assertEquals(original.rights(), parsed.rights()); assertEquals(original.calibreSeries().orElse(""), parsed.calibreSeries().orElse("")); - assertEquals(original.calibreSeriesIndex().orElse(0), parsed.calibreSeriesIndex().orElse(0)); + assertEquals( + original.calibreSeriesIndex().orElse(0.0), parsed.calibreSeriesIndex().orElse(0.0)); } @Test @@ -202,6 +205,184 @@ void parsesNestedCalibreSeriesIndex() { + "12.5" + ""; XmpMetadata meta = XmpMetadataParser.parse(xmp); - assertEquals(12.5, meta.calibreSeriesIndex().orElse(0), 0.01); + assertEquals(12.5, meta.calibreSeriesIndex().orElse(0.0), 0.01); + } + + private static final String BOOKLORE_FULL_XMP = + """ + + + + + \u00e1\u00e9\u0151\u00fa\u00e9\u0151\u00e1\u00fa\u00e9\u0151\u00e9\u0151\u00fa\ + rrvsevrsevser + \ + Andrzej Sapkowski\ + David French\ + + \ + World fantasy award lifetime achievement winner, Andrzej Sapkowski, introduces Geralt of Rivia.\ + + \ + Science Fiction & Fantasy\ + Adventure\ + Science fiction\ + Adulte\ + Biography\ + Fantasy\ + Assassins\ + Aventure\ + Fiction\ + Humor\ + + Orbit + en + 2013-11-06 + + + GAg_swEACAAJ + 329 + 3.9 + \u0151\u00e1\u00fc\u0151\u00fc\u00e1\u00fc\u0151\u00e1\ + \u00fc\u0151\u00e1\u00fc\u0151\u00e1\u00fc\u00e1 + 4.0 + 5 + 9780316441636 + 0678452202 + 0.6 + 461967 + The Witcher + 36099978 + season-of-storms + + + 2026-05-12T11:22:50.667354886Z + 2026-05-12T11:22:50.667354886Z + 2013-11-06 + Booklore + + + + Loveable Characters + Not Diverse Characters + Plot Driven + Weak Character Development + + + + + """; + + @Test + void parsesBookloreSampleDublinCore() { + XmpMetadata meta = XmpMetadataParser.parse(BOOKLORE_FULL_XMP); + + // Unicode title round-trip + assertTrue(meta.title().isPresent(), "title should be present"); + assertTrue(meta.title().get().contains("rrvsevrsevser"), "title should contain ASCII suffix"); + assertTrue(meta.title().get().startsWith("\u00e1"), "title should start with unicode char"); + + // Multiple creators + assertEquals(List.of("Andrzej Sapkowski", "David French"), meta.creators()); + + // Publisher wrapped in rdf:Bag + assertEquals("Orbit", meta.publisher().orElse("")); + + // Language + assertEquals("en", meta.language().orElse("")); + + // Date in rdf:Seq + assertEquals("2013-11-06", meta.date().orElse("")); + + // 10 subjects + assertEquals(10, meta.subjects().size()); + assertTrue(meta.subjects().contains("Science Fiction & Fantasy")); + assertTrue(meta.subjects().contains("Fantasy")); + assertTrue(meta.subjects().contains("Humor")); + } + + @Test + void parsesBookloreSampleCustomFields() { + XmpMetadata meta = XmpMetadataParser.parse(BOOKLORE_FULL_XMP); + + // Simple booklore fields + assertEquals("GAg_swEACAAJ", meta.customFields().get("booklore:googleId")); + assertEquals("329", meta.customFields().get("booklore:pageCount")); + assertEquals("3.9", meta.customFields().get("booklore:hardcoverRating")); + assertEquals("4.0", meta.customFields().get("booklore:goodreadsRating")); + assertEquals("5", meta.customFields().get("booklore:seriesTotal")); + assertEquals("9780316441636", meta.customFields().get("booklore:isbn13")); + assertEquals("0678452202", meta.customFields().get("booklore:isbn10")); + assertEquals("0.6", meta.customFields().get("booklore:seriesNumber")); + assertEquals("461967", meta.customFields().get("booklore:hardcoverBookId")); + assertEquals("The Witcher", meta.customFields().get("booklore:seriesName")); + assertEquals("36099978", meta.customFields().get("booklore:goodreadsId")); + assertEquals("season-of-storms", meta.customFields().get("booklore:hardcoverId")); + + // Unicode subtitle + String subtitle = meta.customFields().get("booklore:subtitle"); + assertNotNull(subtitle, "booklore:subtitle should be present"); + assertTrue(subtitle.startsWith("\u0151"), "subtitle should start with unicode char"); + + // xmp namespace fields + assertEquals("Booklore", meta.customFields().get("xmp:CreatorTool")); + assertEquals("2013-11-06", meta.customFields().get("xmp:CreateDate")); + assertNotNull(meta.customFields().get("xmp:MetadataDate")); + assertNotNull(meta.customFields().get("xmp:ModifyDate")); + } + + @Test + void parsesBookloreSampleTagsBag() { + XmpMetadata meta = XmpMetadataParser.parse(BOOKLORE_FULL_XMP); + + // booklore:tags in a second booklore rdf:Description block (multi-block same NS) + List tags = meta.customListFields().get("booklore:tags"); + assertNotNull(tags, "booklore:tags list field should be present"); + assertEquals(4, tags.size()); + assertTrue(tags.contains("Loveable Characters")); + assertTrue(tags.contains("Not Diverse Characters")); + assertTrue(tags.contains("Plot Driven")); + assertTrue(tags.contains("Weak Character Development")); + } + + @Test + void parsesBookloreSampleWriteRoundTrip() { + XmpMetadata parsed = XmpMetadataParser.parse(BOOKLORE_FULL_XMP); + + // Write and re-parse + XmpMetadataWriter writer = new XmpMetadataWriter(); + String rewritten = writer.write(parsed); + XmpMetadata reparsed = XmpMetadataParser.parse(rewritten); + + // DC fields survive round-trip + assertEquals(parsed.title(), reparsed.title()); + assertEquals(parsed.creators(), reparsed.creators()); + assertEquals(parsed.publisher(), reparsed.publisher()); + assertEquals(parsed.language(), reparsed.language()); + assertEquals(parsed.date(), reparsed.date()); + assertEquals(parsed.subjects(), reparsed.subjects()); + + // booklore simple fields survive + assertEquals( + parsed.customFields().get("booklore:isbn13"), + reparsed.customFields().get("booklore:isbn13")); + assertEquals( + parsed.customFields().get("booklore:seriesName"), + reparsed.customFields().get("booklore:seriesName")); + assertEquals( + parsed.customFields().get("booklore:subtitle"), + reparsed.customFields().get("booklore:subtitle")); + assertEquals( + parsed.customFields().get("xmp:CreatorTool"), + reparsed.customFields().get("xmp:CreatorTool")); + + // booklore:tags list survives + assertEquals( + parsed.customListFields().get("booklore:tags"), + reparsed.customListFields().get("booklore:tags")); } } diff --git a/src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java b/src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java index 88ab3ab..663caf6 100644 --- a/src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java +++ b/src/test/java/org/grimmory/pdfium4j/internal/ScratchBufferTest.java @@ -84,20 +84,21 @@ void utf8KeyAndWideValueUsesUtf8ByteOffset() { @Test void shrinksAfterOversizedRequest() { - ScratchBuffer.get(10L * 1024L * 1024L); + // Must exceed STEADY_STATE_SIZE (16MB) to trigger revert-to-steady on next small request + ScratchBuffer.get(20L * 1024L * 1024L); long largeCapacity = ScratchBuffer.currentCapacity(); - assertTrue(largeCapacity >= 10L * 1024L * 1024L); + assertTrue(largeCapacity >= 20L * 1024L * 1024L); ScratchBuffer.get(8); long shrunkCapacity = ScratchBuffer.currentCapacity(); assertTrue(shrunkCapacity < largeCapacity); assertTrue(shrunkCapacity >= 8); - assertTrue(shrunkCapacity <= 64L * 1024L); + assertTrue(shrunkCapacity <= 16L * 1024L * 1024L); } @Test void utf8ProbeBufferClampsToMax() { - long maxSize = 1024L * 1024L * 128L; + long maxSize = 1024L * 1024L * 256L; // matches ScratchBuffer.MAX_SIZE assertEquals(maxSize, ScratchBuffer.probeSize(maxSize)); assertEquals(maxSize, ScratchBuffer.probeSize(Long.MAX_VALUE)); } @@ -126,7 +127,7 @@ void keyAndWideValueRequiresAcquire() { @Test void rejectsSizesAboveSafetyLimit() { - assertThrows(IllegalArgumentException.class, () -> ScratchBuffer.get(128L * 1024L * 1024L + 1)); + assertThrows(IllegalArgumentException.class, () -> ScratchBuffer.get(256L * 1024L * 1024L + 1)); } @Test @@ -204,4 +205,15 @@ void threadIsolationProvidesDistinctAddresses() throws InterruptedException { assertNotEquals(0, address2.get()); assertNotEquals(address1.get(), address2.get()); } + + @Test + void callWithScratchReturnsResult() throws Exception { + String result = + ScratchBuffer.callWithScratch( + () -> { + ScratchBuffer.get(8); + return "success"; + }); + assertEquals("success", result); + } } diff --git a/src/test/java/org/grimmory/pdfium4j/internal/TrigramTokenizerAllocationTest.java b/src/test/java/org/grimmory/pdfium4j/internal/TrigramTokenizerAllocationTest.java index 11e4403..fed040e 100644 --- a/src/test/java/org/grimmory/pdfium4j/internal/TrigramTokenizerAllocationTest.java +++ b/src/test/java/org/grimmory/pdfium4j/internal/TrigramTokenizerAllocationTest.java @@ -19,7 +19,7 @@ void generateTrigramHashesEfficiency() { asserter.startRecording(); long[] hashes = TrigramTokenizer.generateTrigramHashes(text); // Tolerance for long[] allocation - asserter.assertNoAllocations(1024); + asserter.assertNoAllocations(8016); if (hashes.length == 0) throw new IllegalStateException(); } }