From 0621bb32bf0284e458ad9a77bfb13460ad61ba66 Mon Sep 17 00:00:00 2001 From: cadons Date: Sun, 30 Aug 2026 22:57:51 +0200 Subject: [PATCH 1/4] fix(craft): delete copy ops on classes holding unique_ptr maps MSVC's __declspec(dllexport) (DOCRAFT_LIB, active when DOCRAFT_BUILD_SHARED_LIBS is set) forces instantiation of every public member of an exported class, including implicitly-defined copy constructor/assignment. std::unordered_map>'s own copy assignment isn't SFINAE-disabled based on value_type (a pre-C++20 container quirk), so the implicit copy ops on DocraftLoomTagHandlerRegistry and DocraftCraftLanguageParser were never deleted at the type level -- only broken when actually instantiated. GCC/Clang's static-lib builds never instantiate unused implicit members, so this only surfaces on a Windows/MSVC shared-lib build (error C2679 trying to copy-assign the unique_ptr-valued map). Neither class is ever copied (registry is a singleton; the parser is always used as a local) -- explicitly deleting copy ctor/assignment on both fixes the DLL export instantiation without changing any call site. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015RjTSLobQWMZq8DcFyDZoG --- docraft/include/docraft/craft/docraft_craft_language_parser.h | 3 +++ .../docraft/loom/craft/docraft_loom_tag_handler_registry.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index cbdfb2b..08b0771 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -49,6 +49,9 @@ namespace docraft::craft { ~DocraftCraftLanguageParser() = default; + DocraftCraftLanguageParser(const DocraftCraftLanguageParser&) = delete; + DocraftCraftLanguageParser& operator=(const DocraftCraftLanguageParser&) = delete; + /** * @brief Parses craft language source (a single root element) as a string. * @param craft_language_source XML source as string. diff --git a/docraft/include/docraft/loom/craft/docraft_loom_tag_handler_registry.h b/docraft/include/docraft/loom/craft/docraft_loom_tag_handler_registry.h index 186db06..d4f9eaf 100644 --- a/docraft/include/docraft/loom/craft/docraft_loom_tag_handler_registry.h +++ b/docraft/include/docraft/loom/craft/docraft_loom_tag_handler_registry.h @@ -37,6 +37,9 @@ namespace docraft::loom::craft { public: static DocraftLoomTagHandlerRegistry& instance(); + DocraftLoomTagHandlerRegistry(const DocraftLoomTagHandlerRegistry&) = delete; + DocraftLoomTagHandlerRegistry& operator=(const DocraftLoomTagHandlerRegistry&) = delete; + void register_handler(const std::string& tag, std::unique_ptr handler); /** From adb79f8aa843647943f07c70938b51bfc863af6b Mon Sep 17 00:00:00 2001 From: cadons Date: Sun, 30 Aug 2026 23:24:20 +0200 Subject: [PATCH 2/4] fix(build): export DocraftLoomPdfCreator and DocraftValidationResult Both were missing the DOCRAFT_LIB annotation entirely, so on a Windows/MSVC shared-lib build their member functions never made it into docraft.dll's export table. docraft_tool.exe -- linked against docraft.lib as a DLL consumer -- then failed with LNK2019 unresolved external symbol on DocraftLoomPdfCreator::create/render and DocraftValidationResult::has_errors/has_warnings. GCC/Clang builds never surfaced this: the project doesn't set hidden visibility, so every symbol is exported by default there regardless of DOCRAFT_LIB. Only an MSVC dllexport/dllimport build depends on the annotation being present on every class whose methods are called across the DLL boundary (here, from docraft_tool's main.cpp). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015RjTSLobQWMZq8DcFyDZoG --- docraft/include/docraft/loom/docraft_loom_pdf_creator.h | 2 +- docraft/include/docraft/tools/docraft_validator.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docraft/include/docraft/loom/docraft_loom_pdf_creator.h b/docraft/include/docraft/loom/docraft_loom_pdf_creator.h index 50ca666..dbd6fff 100644 --- a/docraft/include/docraft/loom/docraft_loom_pdf_creator.h +++ b/docraft/include/docraft/loom/docraft_loom_pdf_creator.h @@ -24,7 +24,7 @@ namespace docraft::loom { * and re-visited by the rendering pass for each physical page. The body is laid out * as one continuous flow and then split across pages by DocraftLoomPaginationProcessor. */ - class DocraftLoomPdfCreator + class DOCRAFT_LIB DocraftLoomPdfCreator { public: /** diff --git a/docraft/include/docraft/tools/docraft_validator.h b/docraft/include/docraft/tools/docraft_validator.h index a938ab9..5621aec 100644 --- a/docraft/include/docraft/tools/docraft_validator.h +++ b/docraft/include/docraft/tools/docraft_validator.h @@ -45,7 +45,7 @@ namespace docraft::tools { * @brief Aggregated result of DocraftValidator::validate(): every diagnostic found, * in the order they were discovered. */ - struct DocraftValidationResult + struct DOCRAFT_LIB DocraftValidationResult { std::vector issues; From e7737337014cfaeb752f42856109e23adba0f717 Mon Sep 17 00:00:00 2001 From: cadons Date: Sun, 30 Aug 2026 23:51:21 +0200 Subject: [PATCH 3/4] fix(backend): fix dash-pattern type deduction on 32-bit MSVC HPDF_EXPORT declares every libharu function __stdcall on Windows (hpdf.h), a distinct function pointer type from the implicit __cdecl the dash_pattern_element_type deduction helper assumed. __stdcall and __cdecl only coincide on x64, so the existing overload only compiled there; the x86-windows triplet failed with C2672 (no matching overloaded function) trying to deduce against &HPDF_Page_SetDash. Add an __stdcall overload of the deduction helper, guarded to 32-bit MSVC only. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015RjTSLobQWMZq8DcFyDZoG --- .../src/docraft/backend/pdf/docraft_haru_line_backend.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc b/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc index ddc528d..238b59c 100644 --- a/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc +++ b/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc @@ -33,6 +33,15 @@ namespace docraft::backend::pdf { template ElemT dash_pattern_element_type(HPDF_STATUS (*)(PageT, const ElemT*, NumT, PhaseT)); +#if defined(_MSC_VER) && defined(_M_IX86) + // On 32-bit MSVC, HPDF_EXPORT declares libharu's API __stdcall, a distinct + // function pointer type from the implicit __cdecl overload above (they only + // coincide on x64) -- without this overload, deducing against + // &HPDF_Page_SetDash fails to compile on x86. + template + ElemT dash_pattern_element_type(HPDF_STATUS(__stdcall*)(PageT, const ElemT*, NumT, PhaseT)); +#endif + using DashPatternElement = decltype(dash_pattern_element_type(&HPDF_Page_SetDash)); void invoke_set_dash(HPDF_Page page, const std::vector& pattern) { From 86b7c9ab8fb1b33664d8a70ac60c404767bbec6a Mon Sep 17 00:00:00 2001 From: cadons Date: Sun, 30 Aug 2026 23:52:05 +0200 Subject: [PATCH 4/4] fix(loom): use std::iota instead of std::ranges::iota Android NDK r29's libc++ (clang targeting armv7-none-linux- androideabi28 in -std=c++23 mode) doesn't yet implement std::ranges::iota, failing DocraftLoomNode::paint_order_indices() with "no member named 'iota' in namespace 'std::ranges'". The classic std::iota is functionally identical here and has been universally available since C++11. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015RjTSLobQWMZq8DcFyDZoG --- docraft/src/docraft/loom/nodes/docraft_loom_node.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docraft/src/docraft/loom/nodes/docraft_loom_node.cc b/docraft/src/docraft/loom/nodes/docraft_loom_node.cc index 0343899..a4b4088 100644 --- a/docraft/src/docraft/loom/nodes/docraft_loom_node.cc +++ b/docraft/src/docraft/loom/nodes/docraft_loom_node.cc @@ -87,7 +87,7 @@ namespace docraft::loom::nodes { std::vector DocraftLoomNode::paint_order_indices() const { std::vector indices(children_.size()); - std::ranges::iota(indices, 0); + std::iota(indices.begin(), indices.end(), 0); std::ranges::stable_sort(indices, [this](int a, int b) { return children_[static_cast(a)]->z_index() < children_[static_cast(b)]->z_index();