From 54749476e7429cbe33b5a51ac579bcf9c5ef41f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 20:50:06 +0000 Subject: [PATCH 01/15] Initial plan From 2f5c5afd4a2b4ea8e1d2fe65cdd2ce9b6106bd52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 20:58:45 +0000 Subject: [PATCH 02/15] Refactor pointer getters into readonly/edit accessors --- .../contributors/components/model-and-dom.md | 6 +- doc/project-doc/users/craft-language.md | 4 +- .../craft/docraft_craft_language_parser.h | 3 +- docraft/include/docraft/docraft_document.h | 24 ++++-- docraft/include/docraft/docraft_document.hpp | 16 +++- .../docraft/docraft_document_context.h | 31 ++++--- docraft/include/docraft/docraft_lib.h | 9 ++ .../templating/docraft_template_engine.h | 2 +- .../craft/docraft_craft_language_parser.cc | 6 +- docraft/src/docraft/docraft_document.cc | 67 +++++++++++---- .../src/docraft/docraft_document_context.cc | 85 ++++++++++++------- .../docraft/layout/docraft_layout_engine.cc | 4 +- docraft/src/docraft/main.cpp | 2 +- .../templating/docraft_template_engine.cc | 2 +- .../docraft_craft_language_parser_test.cc | 22 +++++ docraft/test/docraft/docraft_document_test.cc | 18 ++++ 16 files changed, 220 insertions(+), 81 deletions(-) diff --git a/doc/project-doc/contributors/components/model-and-dom.md b/doc/project-doc/contributors/components/model-and-dom.md index 01172c0..b6ff14f 100644 --- a/doc/project-doc/contributors/components/model-and-dom.md +++ b/doc/project-doc/contributors/components/model-and-dom.md @@ -36,11 +36,11 @@ All nodes can carry: `DocraftDocument` offers utility APIs to query and mutate the graph: -- `get_by_name`, `get_first_by_name`, `get_last_by_name`, -- `get_by_type()`, +- readonly: `get_by_name`, `get_first_by_name`, `get_last_by_name`, `get_by_type()`, +- mutable: `edit_get_by_name`, `edit_get_first_by_name`, `edit_get_last_by_name`, `edit_get_by_type()`, - `traverse_dom(callback)`. -Because nodes are mutable shared pointers, these APIs are used both by internal stages and by application code for runtime customization. +Readonly methods return pointers to const nodes; use `edit_*` methods when runtime customization is required. ## 5. Clone behavior diff --git a/doc/project-doc/users/craft-language.md b/doc/project-doc/users/craft-language.md index 8dd9a45..8bf0462 100644 --- a/doc/project-doc/users/craft-language.md +++ b/doc/project-doc/users/craft-language.md @@ -230,7 +230,7 @@ A common pattern is: parse `.craft`, then modify nodes programmatically before r ```cpp auto title_node = std::dynamic_pointer_cast( - document->get_first_by_name("report_title")); + document->edit_get_first_by_name("report_title")); if (title_node) { title_node->set_text("Q1 2026 - Final Version"); } @@ -239,7 +239,7 @@ if (title_node) { ### 6.2 Find nodes by type ```cpp -auto texts = document->get_by_type(); +auto texts = document->edit_get_by_type(); for (const auto &text : texts) { if (text->text().empty()) { text->set_visible(false); diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index ece521c..8a2baa7 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -57,7 +57,8 @@ namespace docraft::craft { * @brief Returns the parsed document. * @return Parsed document or nullptr if not parsed. */ - std::shared_ptr get_document() const; + std::shared_ptr get_document() const; + std::shared_ptr edit_document(); /** * @brief Parses a single XML node into a Docraft node. diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index a2b9943..8c02a15 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -91,7 +91,7 @@ namespace docraft { * @brief Returns the current document title. * @return Document title string. */ - std::string document_title(); + std::string document_title() const; /** * @brief Sets the output directory where the rendered file will be saved. @@ -103,7 +103,7 @@ namespace docraft { * @brief Returns the current output directory path. * @return Output directory path. */ - std::string document_path(); + std::string document_path() const; /** * @brief Sets document settings (fonts, etc.). @@ -115,7 +115,7 @@ namespace docraft { * @brief Returns the current settings object. * @return Shared pointer to settings or nullptr if not set. */ - std::shared_ptr settings() const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftSettings, settings) /** * @brief Sets document metadata values. @@ -160,37 +160,43 @@ namespace docraft { void set_document_template_engine(const std::shared_ptr &template_engine); - std::shared_ptr document_template_engine() const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(templating::DocraftTemplateEngine, document_template_engine) /** * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - const std::vector> &nodes() const; + std::vector> nodes() const; + std::vector> &edit_nodes(); /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. * @return Vector of nodes matching the name, or empty vector if none found. */ - std::vector> get_by_name(const std::string &name) const; + std::vector> get_by_name(const std::string &name) const; + std::vector> edit_get_by_name(const std::string &name); /** * @brief Finds the first node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the first matching node, or nullptr if not found. */ - std::shared_ptr get_first_by_name(const std::string &name) const; + std::shared_ptr get_first_by_name(const std::string &name) const; + std::shared_ptr edit_get_first_by_name(const std::string &name); /** * @brief Finds the last node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the last matching node, or nullptr if not found. */ - std::shared_ptr get_last_by_name(const std::string &name) const; + std::shared_ptr get_last_by_name(const std::string &name) const; + std::shared_ptr edit_get_last_by_name(const std::string &name); /** * @brief Finds nodes by type in the document DOM. * @tparam T Node type to search for. * @return Vector of nodes matching the type, or empty vector if none found. */ template - std::vector> get_by_type() const; + std::vector> get_by_type() const; + template + std::vector> edit_get_by_type(); /** * @brief Traverses the document DOM and executes a callback on each node. * @param callback Function called for each node and operation (enter/exit). diff --git a/docraft/include/docraft/docraft_document.hpp b/docraft/include/docraft/docraft_document.hpp index 9ee8301..0306c2b 100644 --- a/docraft/include/docraft/docraft_document.hpp +++ b/docraft/include/docraft/docraft_document.hpp @@ -5,7 +5,21 @@ namespace docraft { template - std::vector> DocraftDocument::get_by_type() const { + std::vector> DocraftDocument::get_by_type() const { + std::vector> result; + traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { + if (op != DocraftDomTraverseOp::kEnter) { + return; + } + if (auto casted = std::dynamic_pointer_cast(node)) { + result.push_back(casted); + } + }); + return result; + } + + template + std::vector> DocraftDocument::edit_get_by_type() { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index be7e10f..00019c9 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -60,7 +60,7 @@ namespace docraft { * @brief Returns the active rendering backend. * @return Shared pointer to the rendering backend. */ - [[nodiscard]] const std::shared_ptr& rendering_backend() const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(backend::IDocraftRenderingBackend, rendering_backend) /** * @brief Returns the mutable layout cursor. * @return Reference to the cursor. @@ -106,7 +106,7 @@ namespace docraft { * @brief Returns the header node. * @return Header node (may be nullptr). */ - [[nodiscard]] const std::shared_ptr& header() const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftHeader, header) /** * @brief Sets the document body node. * @param body Body node. @@ -116,7 +116,7 @@ namespace docraft { * @brief Returns the body node. * @return Body node (may be nullptr). */ - [[nodiscard]] const std::shared_ptr& body() const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftBody, body) /** * @brief Sets the document footer node. * @param footer Footer node. @@ -126,7 +126,7 @@ namespace docraft { * @brief Returns the footer node. * @return Footer node (may be nullptr). */ - [[nodiscard]] const std::shared_ptr& footer() const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftFooter, footer) /** * @brief Sets the font applier used for text nodes. * @param font_applier Font applier instance. @@ -136,32 +136,37 @@ namespace docraft { * @brief Returns the font applier instance. * @return Font applier (may be nullptr). */ - [[nodiscard]] const std::shared_ptr& font_applier()const; + DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(docraft::generic::DocraftFontApplier, font_applier) /** * @brief Returns the line backend (cached). * @return Line rendering backend. */ - [[nodiscard]] const std::shared_ptr& line_backend() const; + [[nodiscard]] std::shared_ptr line_backend() const; + [[nodiscard]] std::shared_ptr edit_line_backend(); /** * @brief Returns the shape backend (cached). * @return Shape rendering backend. */ - [[nodiscard]] const std::shared_ptr& shape_backend() const; + [[nodiscard]] std::shared_ptr shape_backend() const; + [[nodiscard]] std::shared_ptr edit_shape_backend(); /** * @brief Returns the text backend (cached). * @return Text rendering backend. */ - [[nodiscard]] const std::shared_ptr& text_backend() const; + [[nodiscard]] std::shared_ptr text_backend() const; + [[nodiscard]] std::shared_ptr edit_text_backend(); /** * @brief Returns the image backend (cached). * @return Image rendering backend. */ - [[nodiscard]] const std::shared_ptr& image_backend() const; + [[nodiscard]] std::shared_ptr image_backend() const; + [[nodiscard]] std::shared_ptr edit_image_backend(); /** * @brief Returns the page backend (cached). * @return Page rendering backend. */ - [[nodiscard]] const std::shared_ptr& page_backend() const; + [[nodiscard]] std::shared_ptr page_backend() const; + [[nodiscard]] std::shared_ptr edit_page_backend(); /** * @brief Replaces the underlying rendering backend. * @param backend New rendering backend. Pass nullptr to restore the default backend. @@ -174,15 +179,15 @@ namespace docraft { /** * @brief Moves to the first page (index 0). */ - void go_to_first_page() const; + void go_to_first_page(); /** * @brief Moves to the previous page. */ - void go_to_previous_page() const; + void go_to_previous_page(); /** * @brief Moves to the last page. */ - void go_to_last_page() const; + void go_to_last_page(); /** * @brief Sets header/body/footer ratios. */ diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index a93b4e1..65b496b 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -24,12 +24,21 @@ #else #define DOCRAFT_LIB #endif + #elif defined(__GNUC__) && __GNUC__ >= 4 #if defined(DOCRAFT_BUILD_SHARED_LIBS) #define DOCRAFT_LIB __attribute__((visibility("default"))) #else #define DOCRAFT_LIB #endif + +#define DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(TYPE, NAME) \ + [[nodiscard]] std::shared_ptr NAME() const; \ + [[nodiscard]] std::shared_ptr edit_##NAME(); + +#define DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(SCOPE, TYPE, NAME, MEMBER) \ + std::shared_ptr SCOPE::NAME() const { return MEMBER; } \ + std::shared_ptr SCOPE::edit_##NAME() { return MEMBER; } #else #define DOCRAFT_LIB #endif diff --git a/docraft/include/docraft/templating/docraft_template_engine.h b/docraft/include/docraft/templating/docraft_template_engine.h index 8a5eca9..b0f7ef5 100644 --- a/docraft/include/docraft/templating/docraft_template_engine.h +++ b/docraft/include/docraft/templating/docraft_template_engine.h @@ -53,7 +53,7 @@ namespace docraft::templating { * @return Stored value. * @throws std::runtime_error if not found. */ - std::string get_template_variable(const std::string &name); + std::string get_template_variable(const std::string &name) const; /** * @brief Clears all template variables. */ diff --git a/docraft/src/docraft/craft/docraft_craft_language_parser.cc b/docraft/src/docraft/craft/docraft_craft_language_parser.cc index 8dee52a..7f9772d 100644 --- a/docraft/src/docraft/craft/docraft_craft_language_parser.cc +++ b/docraft/src/docraft/craft/docraft_craft_language_parser.cc @@ -665,7 +665,11 @@ void DocraftCraftLanguageParser::load_document() { LOG_INFO("Document loaded successfully with title: " + document_->document_title()); } -std::shared_ptr DocraftCraftLanguageParser::get_document() const { +std::shared_ptr DocraftCraftLanguageParser::get_document() const { + return document_; +} + +std::shared_ptr DocraftCraftLanguageParser::edit_document() { return document_; } diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index bb5a2c9..742066c 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -233,7 +233,7 @@ namespace docraft { layout_engine.compute_document_layout(dom_); // Rendering phase - const auto &page_backend = context_->page_backend(); + const auto page_backend = context_->edit_page_backend(); if (page_backend) { page_backend->go_to_first_page(); } @@ -260,7 +260,7 @@ namespace docraft { } } - context_->rendering_backend()->set_document_metadata(metadata_); + context_->edit_rendering_backend()->set_document_metadata(metadata_); const std::string output_file_name = with_extension( document_title_, context_->rendering_backend()->file_extension()); context_->rendering_backend()->save_to_file(with_directory(document_path_, output_file_name)); @@ -276,7 +276,7 @@ namespace docraft { metadata_.set_title(document_title); } - std::string DocraftDocument::document_title() { + std::string DocraftDocument::document_title() const { return document_title_; } @@ -284,7 +284,7 @@ namespace docraft { document_path_ = document_path; } - std::string DocraftDocument::document_path() { + std::string DocraftDocument::document_path() const { return document_path_; } @@ -292,9 +292,7 @@ namespace docraft { settings_ = settings; } - std::shared_ptr DocraftDocument::settings() const { - return settings_; - } + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocument, model::DocraftSettings, settings, settings_) void DocraftDocument::set_document_metadata(const DocraftDocumentMetadata &metadata) { metadata_ = metadata; @@ -345,15 +343,38 @@ namespace docraft { template_engine_ = template_engine; } - std::shared_ptr DocraftDocument::document_template_engine() const { - return template_engine_; + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocument, + templating::DocraftTemplateEngine, + document_template_engine, + template_engine_) + + std::vector> DocraftDocument::nodes() const { + std::vector> result; + result.reserve(dom_.size()); + for (const auto &node: dom_) { + result.push_back(node); + } + return result; } - const std::vector> &DocraftDocument::nodes() const { + std::vector> &DocraftDocument::edit_nodes() { return dom_; } - std::vector> DocraftDocument::get_by_name(const std::string &name) const { - static std::vector> empty_result; + + std::vector> DocraftDocument::get_by_name(const std::string &name) const { + std::vector> result; + traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { + if (op != DocraftDomTraverseOp::kEnter) { + return; + } + if (node && node->node_name() == name) { + result.push_back(node); + } + }); + return result; + } + + std::vector> DocraftDocument::edit_get_by_name(const std::string &name) { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { @@ -363,15 +384,27 @@ namespace docraft { result.push_back(node); } }); - return result.empty() ? empty_result : result; + return result; + } + + std::shared_ptr DocraftDocument::get_first_by_name(const std::string &name) const { + const auto matches = get_by_name(name); + return matches.empty() ? nullptr : matches.front(); + } + + std::shared_ptr DocraftDocument::edit_get_first_by_name(const std::string &name) { + const auto matches = edit_get_by_name(name); + return matches.empty() ? nullptr : matches.front(); } - std::shared_ptr DocraftDocument::get_first_by_name(const std::string &name) const { - return get_by_name(name).empty() ? nullptr : get_by_name(name).front(); + std::shared_ptr DocraftDocument::get_last_by_name(const std::string &name) const { + const auto matches = get_by_name(name); + return matches.empty() ? nullptr : matches.back(); } - std::shared_ptr DocraftDocument::get_last_by_name(const std::string &name) const { - return get_by_name(name).empty() ? nullptr : get_by_name(name).back(); + std::shared_ptr DocraftDocument::edit_get_last_by_name(const std::string &name) { + const auto matches = edit_get_by_name(name); + return matches.empty() ? nullptr : matches.back(); } void DocraftDocument::traverse_dom( diff --git a/docraft/src/docraft/docraft_document_context.cc b/docraft/src/docraft/docraft_document_context.cc index 38bdf2d..b42556b 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -74,7 +74,7 @@ namespace docraft { void DocraftDocumentContext::set_page_format(model::DocraftPageSize size, model::DocraftPageOrientation orientation) { - const auto &backend = page_backend(); + const auto backend = edit_page_backend(); if (backend) { backend->set_page_format(size, orientation); page_height_ = backend->page_height(); @@ -84,9 +84,10 @@ namespace docraft { } #pragma endregion #pragma region getter - const std::shared_ptr &DocraftDocumentContext::rendering_backend() const { - return backend_; - } + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, + backend::IDocraftRenderingBackend, + rendering_backend, + backend_) DocraftCursor &DocraftDocumentContext::cursor() { return cursor_; @@ -112,74 +113,100 @@ namespace docraft { } - const std::shared_ptr &DocraftDocumentContext::header() const { - return header_; - } - - - const std::shared_ptr &DocraftDocumentContext::body() const { - return body_; - } + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, model::DocraftHeader, header, header_) + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, model::DocraftBody, body, body_) + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, model::DocraftFooter, footer, footer_) + DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, + generic::DocraftFontApplier, + font_applier, + font_applier_) - const std::shared_ptr &DocraftDocumentContext::footer() const { - return footer_; - } - - const std::shared_ptr &DocraftDocumentContext::font_applier() const { - return font_applier_; + std::shared_ptr DocraftDocumentContext::line_backend() const { + if (!line_backend_) { + line_backend_ = std::dynamic_pointer_cast(backend_); + } + return line_backend_; } - const std::shared_ptr &DocraftDocumentContext::line_backend() const { + std::shared_ptr DocraftDocumentContext::edit_line_backend() { if (!line_backend_) { line_backend_ = std::dynamic_pointer_cast(backend_); } return line_backend_; } - const std::shared_ptr &DocraftDocumentContext::shape_backend() const { + std::shared_ptr DocraftDocumentContext::shape_backend() const { if (!shape_backend_) { shape_backend_ = std::dynamic_pointer_cast(backend_); } return shape_backend_; } - const std::shared_ptr &DocraftDocumentContext::text_backend() const { + std::shared_ptr DocraftDocumentContext::edit_shape_backend() { + if (!shape_backend_) { + shape_backend_ = std::dynamic_pointer_cast(backend_); + } + return shape_backend_; + } + + std::shared_ptr DocraftDocumentContext::text_backend() const { if (!text_backend_) { text_backend_ = std::dynamic_pointer_cast(backend_); } return text_backend_; } - const std::shared_ptr &DocraftDocumentContext::image_backend() const { + std::shared_ptr DocraftDocumentContext::edit_text_backend() { + if (!text_backend_) { + text_backend_ = std::dynamic_pointer_cast(backend_); + } + return text_backend_; + } + + std::shared_ptr DocraftDocumentContext::image_backend() const { if (!image_backend_) { image_backend_ = std::dynamic_pointer_cast(backend_); } return image_backend_; } - const std::shared_ptr &DocraftDocumentContext::page_backend() const { + std::shared_ptr DocraftDocumentContext::edit_image_backend() { + if (!image_backend_) { + image_backend_ = std::dynamic_pointer_cast(backend_); + } + return image_backend_; + } + + std::shared_ptr DocraftDocumentContext::page_backend() const { + if (!page_backend_) { + page_backend_ = std::dynamic_pointer_cast(backend_); + } + return page_backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_page_backend() { if (!page_backend_) { page_backend_ = std::dynamic_pointer_cast(backend_); } return page_backend_; } - void DocraftDocumentContext::go_to_first_page() const { - const auto &backend = page_backend(); + void DocraftDocumentContext::go_to_first_page() { + const auto backend = edit_page_backend(); if (backend) { backend->go_to_first_page(); } } - void DocraftDocumentContext::go_to_previous_page() const { - const auto &backend = page_backend(); + void DocraftDocumentContext::go_to_previous_page() { + const auto backend = edit_page_backend(); if (backend) { backend->go_to_previous_page(); } } - void DocraftDocumentContext::go_to_last_page() const { - const auto &backend = page_backend(); + void DocraftDocumentContext::go_to_last_page() { + const auto backend = edit_page_backend(); if (backend) { backend->go_to_last_page(); } diff --git a/docraft/src/docraft/layout/docraft_layout_engine.cc b/docraft/src/docraft/layout/docraft_layout_engine.cc index 72e9c47..951db8d 100644 --- a/docraft/src/docraft/layout/docraft_layout_engine.cc +++ b/docraft/src/docraft/layout/docraft_layout_engine.cc @@ -382,7 +382,7 @@ namespace docraft::layout { if (!sections.body) { throw std::runtime_error("Document must have a body section"); } - if (const auto &page_backend = context()->page_backend()) { + if (const auto page_backend = context()->edit_page_backend()) { page_backend->go_to_first_page(); } const SectionPlan plan = build_section_plan(sections); @@ -469,7 +469,7 @@ namespace docraft::layout { body_cursor.move_to(body->position().x, body_start_y); int current_page = 1; - const auto &page_backend = context()->page_backend(); + const auto page_backend = context()->edit_page_backend(); if (page_backend) { current_page = static_cast(page_backend->current_page_number()); } diff --git a/docraft/src/docraft/main.cpp b/docraft/src/docraft/main.cpp index 3a373f3..e15d799 100644 --- a/docraft/src/docraft/main.cpp +++ b/docraft/src/docraft/main.cpp @@ -325,7 +325,7 @@ int main(int argc, char *argv[]) { docraft::craft::DocraftCraftLanguageParser parser; parser.load_from_file(options.craft_file.string()); - auto document = parser.get_document(); + auto document = parser.edit_document(); if (!document) { throw std::runtime_error("Unable to build document from .craft file"); } diff --git a/docraft/src/docraft/templating/docraft_template_engine.cc b/docraft/src/docraft/templating/docraft_template_engine.cc index ae8ad6b..c1ebc50 100644 --- a/docraft/src/docraft/templating/docraft_template_engine.cc +++ b/docraft/src/docraft/templating/docraft_template_engine.cc @@ -53,7 +53,7 @@ namespace docraft::templating { template_variables_.insert({normalized_name, value}); } - std::string DocraftTemplateEngine::get_template_variable(const std::string &name) { + std::string DocraftTemplateEngine::get_template_variable(const std::string &name) const { auto normalized_name = normalize_name(name); auto it = template_variables_.find(normalized_name); if (it == template_variables_.end()) { diff --git a/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc b/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc index 8047b4d..2225a4d 100644 --- a/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc +++ b/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc @@ -294,3 +294,25 @@ TEST(DocraftCraftLanguageParserTest, AllowsLayoutInBodyWithMultipleText) { EXPECT_EQ(texts[1]->text(), "Second line"); } +TEST(DocraftCraftLanguageParserTest, EditDocumentReturnsMutableDocument) { + const char *xml = R"XML( + + + Body copy + + +)XML"; + + docraft::craft::DocraftCraftLanguageParser parser; + parser.parse(xml); + + const auto readonly_document = parser.get_document(); + ASSERT_TRUE(readonly_document); + EXPECT_EQ(readonly_document->document_title(), "Untitled Document"); + + auto editable_document = parser.edit_document(); + ASSERT_TRUE(editable_document); + editable_document->set_document_title("Edited"); + + EXPECT_EQ(readonly_document->document_title(), "Edited"); +} diff --git a/docraft/test/docraft/docraft_document_test.cc b/docraft/test/docraft/docraft_document_test.cc index c59f885..e070dee 100644 --- a/docraft/test/docraft/docraft_document_test.cc +++ b/docraft/test/docraft/docraft_document_test.cc @@ -133,6 +133,24 @@ namespace docraft::test { EXPECT_EQ(document.get_last_by_name("target"), last); } + TEST(DocraftDocumentTest, EditGettersAllowNodeMutation) { + DocraftDocument document("Test Document"); + + auto text = std::make_shared("Before"); + text->set_name("target"); + document.add_node(text); + + auto editable = std::dynamic_pointer_cast( + document.edit_get_first_by_name("target")); + ASSERT_TRUE(editable); + editable->set_text("After"); + + const auto readonly = document.get_first_by_name("target"); + auto readonly_text = std::dynamic_pointer_cast(readonly); + ASSERT_TRUE(readonly_text); + EXPECT_EQ(readonly_text->text(), "After"); + } + TEST(DocraftDocumentTest, GetByTypeFindsMatchingNodes) { DocraftDocument document("Test Document"); From 93c29744b14209a607b1e381263646edff0c4309 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:01:15 +0000 Subject: [PATCH 03/15] Address review feedback on accessor macros --- docraft/include/docraft/docraft_lib.h | 7 ++++--- docraft/src/docraft/docraft_document.cc | 7 +------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index 65b496b..5c66399 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -32,6 +32,10 @@ #define DOCRAFT_LIB #endif +#else +#define DOCRAFT_LIB +#endif + #define DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(TYPE, NAME) \ [[nodiscard]] std::shared_ptr NAME() const; \ [[nodiscard]] std::shared_ptr edit_##NAME(); @@ -39,6 +43,3 @@ #define DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(SCOPE, TYPE, NAME, MEMBER) \ std::shared_ptr SCOPE::NAME() const { return MEMBER; } \ std::shared_ptr SCOPE::edit_##NAME() { return MEMBER; } -#else -#define DOCRAFT_LIB -#endif diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index 742066c..a4f2176 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -349,12 +349,7 @@ namespace docraft { template_engine_) std::vector> DocraftDocument::nodes() const { - std::vector> result; - result.reserve(dom_.size()); - for (const auto &node: dom_) { - result.push_back(node); - } - return result; + return {dom_.begin(), dom_.end()}; } std::vector> &DocraftDocument::edit_nodes() { From ce982d7b9f3f158248fec795c56aca1fdafe0ceb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:02:54 +0000 Subject: [PATCH 04/15] Refactor get_by_name traversal helper --- docraft/include/docraft/docraft_document.h | 1 + docraft/src/docraft/docraft_document.cc | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 8c02a15..582487f 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -208,6 +208,7 @@ namespace docraft { void traverse_node( const std::shared_ptr &node, const std::function &, DocraftDomTraverseOp)> &callback) const; + std::vector> get_by_name_impl(const std::string &name) const; std::shared_ptr context_; std::shared_ptr settings_; std::string document_title_; diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index a4f2176..9ae2803 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -358,18 +358,17 @@ namespace docraft { std::vector> DocraftDocument::get_by_name(const std::string &name) const { std::vector> result; - traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { - if (op != DocraftDomTraverseOp::kEnter) { - return; - } - if (node && node->node_name() == name) { - result.push_back(node); - } - }); + for (const auto &node: get_by_name_impl(name)) { + result.push_back(node); + } return result; } std::vector> DocraftDocument::edit_get_by_name(const std::string &name) { + return get_by_name_impl(name); + } + + std::vector> DocraftDocument::get_by_name_impl(const std::string &name) const { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { From fa823c700101195c49a84eaf06426d5c20940732 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:10:45 +0000 Subject: [PATCH 05/15] refactor: inline accessor macros and rename find APIs --- .../contributors/components/model-and-dom.md | 4 +-- doc/project-doc/users/craft-language.md | 6 ++-- docraft/include/docraft/docraft_document.h | 22 ++++++------- docraft/include/docraft/docraft_document.hpp | 4 +-- .../docraft/docraft_document_context.h | 10 +++--- docraft/include/docraft/docraft_lib.h | 14 ++++---- docraft/src/docraft/docraft_document.cc | 33 ++++++++----------- .../src/docraft/docraft_document_context.cc | 14 -------- .../docraft_craft_language_parser_test.cc | 6 ++-- docraft/test/docraft/docraft_document_test.cc | 16 ++++----- 10 files changed, 55 insertions(+), 74 deletions(-) diff --git a/doc/project-doc/contributors/components/model-and-dom.md b/doc/project-doc/contributors/components/model-and-dom.md index b6ff14f..9016477 100644 --- a/doc/project-doc/contributors/components/model-and-dom.md +++ b/doc/project-doc/contributors/components/model-and-dom.md @@ -36,8 +36,8 @@ All nodes can carry: `DocraftDocument` offers utility APIs to query and mutate the graph: -- readonly: `get_by_name`, `get_first_by_name`, `get_last_by_name`, `get_by_type()`, -- mutable: `edit_get_by_name`, `edit_get_first_by_name`, `edit_get_last_by_name`, `edit_get_by_type()`, +- readonly: `find_by_name`, `find_first_by_name`, `find_last_by_name`, `find_by_type()`, +- mutable: `edit_find_by_name`, `edit_find_first_by_name`, `edit_find_last_by_name`, `edit_find_by_type()`, - `traverse_dom(callback)`. Readonly methods return pointers to const nodes; use `edit_*` methods when runtime customization is required. diff --git a/doc/project-doc/users/craft-language.md b/doc/project-doc/users/craft-language.md index 8bf0462..c7482d9 100644 --- a/doc/project-doc/users/craft-language.md +++ b/doc/project-doc/users/craft-language.md @@ -48,7 +48,7 @@ int main() { - `DocraftDocument::set_document_path(...)` - to choose output folder while keeping `title` as file name. - DOM APIs on `DocraftDocument` - - to modify parsed content before render (`get_by_name`, `get_by_type`, `traverse_dom`, `add_node`). + - to modify parsed content before render (`find_by_name`, `find_by_type`, `traverse_dom`, `add_node`). Custom backend example: @@ -230,7 +230,7 @@ A common pattern is: parse `.craft`, then modify nodes programmatically before r ```cpp auto title_node = std::dynamic_pointer_cast( - document->edit_get_first_by_name("report_title")); + document->edit_find_first_by_name("report_title")); if (title_node) { title_node->set_text("Q1 2026 - Final Version"); } @@ -239,7 +239,7 @@ if (title_node) { ### 6.2 Find nodes by type ```cpp -auto texts = document->edit_get_by_type(); +auto texts = document->edit_find_by_type(); for (const auto &text : texts) { if (text->text().empty()) { text->set_visible(false); diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 582487f..280d210 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -115,7 +115,7 @@ namespace docraft { * @brief Returns the current settings object. * @return Shared pointer to settings or nullptr if not set. */ - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftSettings, settings) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftSettings, settings, settings_) /** * @brief Sets document metadata values. @@ -160,7 +160,7 @@ namespace docraft { void set_document_template_engine(const std::shared_ptr &template_engine); - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(templating::DocraftTemplateEngine, document_template_engine) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(templating::DocraftTemplateEngine, document_template_engine, template_engine_) /** * @brief Returns the document DOM nodes. * @return Vector of root nodes. @@ -172,31 +172,31 @@ namespace docraft { * @param name Node name to search for. * @return Vector of nodes matching the name, or empty vector if none found. */ - std::vector> get_by_name(const std::string &name) const; - std::vector> edit_get_by_name(const std::string &name); + std::vector> find_by_name(const std::string &name) const; + std::vector> edit_find_by_name(const std::string &name); /** * @brief Finds the first node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the first matching node, or nullptr if not found. */ - std::shared_ptr get_first_by_name(const std::string &name) const; - std::shared_ptr edit_get_first_by_name(const std::string &name); + std::shared_ptr find_first_by_name(const std::string &name) const; + std::shared_ptr edit_find_first_by_name(const std::string &name); /** * @brief Finds the last node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the last matching node, or nullptr if not found. */ - std::shared_ptr get_last_by_name(const std::string &name) const; - std::shared_ptr edit_get_last_by_name(const std::string &name); + std::shared_ptr find_last_by_name(const std::string &name) const; + std::shared_ptr edit_find_last_by_name(const std::string &name); /** * @brief Finds nodes by type in the document DOM. * @tparam T Node type to search for. * @return Vector of nodes matching the type, or empty vector if none found. */ template - std::vector> get_by_type() const; + std::vector> find_by_type() const; template - std::vector> edit_get_by_type(); + std::vector> edit_find_by_type(); /** * @brief Traverses the document DOM and executes a callback on each node. * @param callback Function called for each node and operation (enter/exit). @@ -208,7 +208,7 @@ namespace docraft { void traverse_node( const std::shared_ptr &node, const std::function &, DocraftDomTraverseOp)> &callback) const; - std::vector> get_by_name_impl(const std::string &name) const; + std::vector> find_by_name_impl(const std::string &name) const; std::shared_ptr context_; std::shared_ptr settings_; std::string document_title_; diff --git a/docraft/include/docraft/docraft_document.hpp b/docraft/include/docraft/docraft_document.hpp index 0306c2b..4bcf2d7 100644 --- a/docraft/include/docraft/docraft_document.hpp +++ b/docraft/include/docraft/docraft_document.hpp @@ -5,7 +5,7 @@ namespace docraft { template - std::vector> DocraftDocument::get_by_type() const { + std::vector> DocraftDocument::find_by_type() const { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { @@ -19,7 +19,7 @@ namespace docraft { } template - std::vector> DocraftDocument::edit_get_by_type() { + std::vector> DocraftDocument::edit_find_by_type() { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index 00019c9..15c2af3 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -60,7 +60,7 @@ namespace docraft { * @brief Returns the active rendering backend. * @return Shared pointer to the rendering backend. */ - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(backend::IDocraftRenderingBackend, rendering_backend) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(backend::IDocraftRenderingBackend, rendering_backend, backend_) /** * @brief Returns the mutable layout cursor. * @return Reference to the cursor. @@ -106,7 +106,7 @@ namespace docraft { * @brief Returns the header node. * @return Header node (may be nullptr). */ - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftHeader, header) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftHeader, header, header_) /** * @brief Sets the document body node. * @param body Body node. @@ -116,7 +116,7 @@ namespace docraft { * @brief Returns the body node. * @return Body node (may be nullptr). */ - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftBody, body) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftBody, body, body_) /** * @brief Sets the document footer node. * @param footer Footer node. @@ -126,7 +126,7 @@ namespace docraft { * @brief Returns the footer node. * @return Footer node (may be nullptr). */ - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(model::DocraftFooter, footer) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftFooter, footer, footer_) /** * @brief Sets the font applier used for text nodes. * @param font_applier Font applier instance. @@ -136,7 +136,7 @@ namespace docraft { * @brief Returns the font applier instance. * @return Font applier (may be nullptr). */ - DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(docraft::generic::DocraftFontApplier, font_applier) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(docraft::generic::DocraftFontApplier, font_applier, font_applier_) /** * @brief Returns the line backend (cached). * @return Line rendering backend. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index 5c66399..217ec0b 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -16,6 +16,8 @@ #pragma once +#include + #if defined(_WIN32) || defined(__CYGWIN__) #if defined(DOCRAFT_BUILD_SHARED_LIBS) #define DOCRAFT_LIB __declspec(dllexport) @@ -36,10 +38,10 @@ #define DOCRAFT_LIB #endif -#define DOCRAFT_DECLARE_SHARED_PTR_ACCESSORS(TYPE, NAME) \ - [[nodiscard]] std::shared_ptr NAME() const; \ - [[nodiscard]] std::shared_ptr edit_##NAME(); +#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(TYPE, NAME, MEMBER) \ + [[nodiscard]] const TYPE &NAME() const { return MEMBER; } \ + [[nodiscard]] TYPE &edit_##NAME() { return MEMBER; } -#define DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(SCOPE, TYPE, NAME, MEMBER) \ - std::shared_ptr SCOPE::NAME() const { return MEMBER; } \ - std::shared_ptr SCOPE::edit_##NAME() { return MEMBER; } +#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ + [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; } \ + [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index 9ae2803..fc80425 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -292,8 +292,6 @@ namespace docraft { settings_ = settings; } - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocument, model::DocraftSettings, settings, settings_) - void DocraftDocument::set_document_metadata(const DocraftDocumentMetadata &metadata) { metadata_ = metadata; if (metadata_.title().has_value()) { @@ -343,11 +341,6 @@ namespace docraft { template_engine_ = template_engine; } - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocument, - templating::DocraftTemplateEngine, - document_template_engine, - template_engine_) - std::vector> DocraftDocument::nodes() const { return {dom_.begin(), dom_.end()}; } @@ -356,19 +349,19 @@ namespace docraft { return dom_; } - std::vector> DocraftDocument::get_by_name(const std::string &name) const { + std::vector> DocraftDocument::find_by_name(const std::string &name) const { std::vector> result; - for (const auto &node: get_by_name_impl(name)) { + for (const auto &node: find_by_name_impl(name)) { result.push_back(node); } return result; } - std::vector> DocraftDocument::edit_get_by_name(const std::string &name) { - return get_by_name_impl(name); + std::vector> DocraftDocument::edit_find_by_name(const std::string &name) { + return find_by_name_impl(name); } - std::vector> DocraftDocument::get_by_name_impl(const std::string &name) const { + std::vector> DocraftDocument::find_by_name_impl(const std::string &name) const { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { @@ -381,23 +374,23 @@ namespace docraft { return result; } - std::shared_ptr DocraftDocument::get_first_by_name(const std::string &name) const { - const auto matches = get_by_name(name); + std::shared_ptr DocraftDocument::find_first_by_name(const std::string &name) const { + const auto matches = find_by_name(name); return matches.empty() ? nullptr : matches.front(); } - std::shared_ptr DocraftDocument::edit_get_first_by_name(const std::string &name) { - const auto matches = edit_get_by_name(name); + std::shared_ptr DocraftDocument::edit_find_first_by_name(const std::string &name) { + const auto matches = edit_find_by_name(name); return matches.empty() ? nullptr : matches.front(); } - std::shared_ptr DocraftDocument::get_last_by_name(const std::string &name) const { - const auto matches = get_by_name(name); + std::shared_ptr DocraftDocument::find_last_by_name(const std::string &name) const { + const auto matches = find_by_name(name); return matches.empty() ? nullptr : matches.back(); } - std::shared_ptr DocraftDocument::edit_get_last_by_name(const std::string &name) { - const auto matches = edit_get_by_name(name); + std::shared_ptr DocraftDocument::edit_find_last_by_name(const std::string &name) { + const auto matches = edit_find_by_name(name); return matches.empty() ? nullptr : matches.back(); } diff --git a/docraft/src/docraft/docraft_document_context.cc b/docraft/src/docraft/docraft_document_context.cc index b42556b..2515c8b 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -84,11 +84,6 @@ namespace docraft { } #pragma endregion #pragma region getter - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, - backend::IDocraftRenderingBackend, - rendering_backend, - backend_) - DocraftCursor &DocraftDocumentContext::cursor() { return cursor_; } @@ -112,15 +107,6 @@ namespace docraft { return page_width_; } - - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, model::DocraftHeader, header, header_) - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, model::DocraftBody, body, body_) - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, model::DocraftFooter, footer, footer_) - DOCRAFT_DEFINE_SHARED_PTR_ACCESSORS(DocraftDocumentContext, - generic::DocraftFontApplier, - font_applier, - font_applier_) - std::shared_ptr DocraftDocumentContext::line_backend() const { if (!line_backend_) { line_backend_ = std::dynamic_pointer_cast(backend_); diff --git a/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc b/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc index 2225a4d..d32133a 100644 --- a/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc +++ b/docraft/test/docraft/craft/docraft_craft_language_parser_test.cc @@ -19,7 +19,7 @@ TEST(DocraftCraftLanguageParserTest, ParsesTitleSubtitleAndTextWithPredefinedDef auto document = parser.get_document(); ASSERT_TRUE(document); - const auto texts = document->get_by_type(); + const auto texts = document->find_by_type(); ASSERT_EQ(texts.size(), 3U); EXPECT_EQ(texts[0]->text(), "Main Heading"); @@ -50,7 +50,7 @@ TEST(DocraftCraftLanguageParserTest, HeadingAttributesOverridePredefinedDefaults auto document = parser.get_document(); ASSERT_TRUE(document); - const auto texts = document->get_by_type(); + const auto texts = document->find_by_type(); ASSERT_EQ(texts.size(), 2U); EXPECT_FLOAT_EQ(texts[0]->font_size(), 30.0F); @@ -288,7 +288,7 @@ TEST(DocraftCraftLanguageParserTest, AllowsLayoutInBodyWithMultipleText) { auto document = parser.get_document(); ASSERT_TRUE(document); - const auto texts = document->get_by_type(); + const auto texts = document->find_by_type(); ASSERT_EQ(texts.size(), 2U); EXPECT_EQ(texts[0]->text(), "First line"); EXPECT_EQ(texts[1]->text(), "Second line"); diff --git a/docraft/test/docraft/docraft_document_test.cc b/docraft/test/docraft/docraft_document_test.cc index e070dee..43fb03f 100644 --- a/docraft/test/docraft/docraft_document_test.cc +++ b/docraft/test/docraft/docraft_document_test.cc @@ -56,7 +56,7 @@ namespace docraft::test { document.add_node(rect); document.add_node(list); - const auto matches = document.get_by_name("target"); + const auto matches = document.find_by_name("target"); ASSERT_EQ(matches.size(), 2U); EXPECT_EQ(matches[0], rect); @@ -70,7 +70,7 @@ namespace docraft::test { rect->set_name("root"); document.add_node(rect); - const auto matches = document.get_by_name("missing"); + const auto matches = document.find_by_name("missing"); EXPECT_TRUE(matches.empty()); } @@ -129,8 +129,8 @@ namespace docraft::test { document.add_node(list); document.add_node(last); - EXPECT_EQ(document.get_first_by_name("target"), first); - EXPECT_EQ(document.get_last_by_name("target"), last); + EXPECT_EQ(document.find_first_by_name("target"), first); + EXPECT_EQ(document.find_last_by_name("target"), last); } TEST(DocraftDocumentTest, EditGettersAllowNodeMutation) { @@ -141,11 +141,11 @@ namespace docraft::test { document.add_node(text); auto editable = std::dynamic_pointer_cast( - document.edit_get_first_by_name("target")); + document.edit_find_first_by_name("target")); ASSERT_TRUE(editable); editable->set_text("After"); - const auto readonly = document.get_first_by_name("target"); + const auto readonly = document.find_first_by_name("target"); auto readonly_text = std::dynamic_pointer_cast(readonly); ASSERT_TRUE(readonly_text); EXPECT_EQ(readonly_text->text(), "After"); @@ -164,8 +164,8 @@ namespace docraft::test { document.add_node(list); document.add_node(rect2); - const auto rectangles = document.get_by_type(); - const auto texts = document.get_by_type(); + const auto rectangles = document.find_by_type(); + const auto texts = document.find_by_type(); ASSERT_EQ(rectangles.size(), 2U); EXPECT_EQ(rectangles[0], rect1); From 6e1ee43760db25b45dbb313da071507762b547bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:16:07 +0000 Subject: [PATCH 06/15] test: align renamed find API test naming --- docraft/test/docraft/docraft_document_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docraft/test/docraft/docraft_document_test.cc b/docraft/test/docraft/docraft_document_test.cc index 43fb03f..58a78ce 100644 --- a/docraft/test/docraft/docraft_document_test.cc +++ b/docraft/test/docraft/docraft_document_test.cc @@ -133,7 +133,7 @@ namespace docraft::test { EXPECT_EQ(document.find_last_by_name("target"), last); } - TEST(DocraftDocumentTest, EditGettersAllowNodeMutation) { + TEST(DocraftDocumentTest, EditFindMethodsAllowNodeMutation) { DocraftDocument document("Test Document"); auto text = std::make_shared("Before"); From d8ff7d408e12a2fa8dd80ea2bb37c782e518ec83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:18:03 +0000 Subject: [PATCH 07/15] refactor: use macros for lazy backend accessors and take_by APIs --- .../contributors/components/model-and-dom.md | 2 +- doc/project-doc/users/craft-language.md | 4 +- docraft/include/docraft/docraft_document.h | 8 +-- docraft/include/docraft/docraft_document.hpp | 2 +- .../docraft/docraft_document_context.h | 35 +++++++--- docraft/include/docraft/docraft_lib.h | 14 ++++ docraft/src/docraft/docraft_document.cc | 10 +-- .../src/docraft/docraft_document_context.cc | 70 ------------------- docraft/test/docraft/docraft_document_test.cc | 2 +- 9 files changed, 53 insertions(+), 94 deletions(-) diff --git a/doc/project-doc/contributors/components/model-and-dom.md b/doc/project-doc/contributors/components/model-and-dom.md index 9016477..ba26e6d 100644 --- a/doc/project-doc/contributors/components/model-and-dom.md +++ b/doc/project-doc/contributors/components/model-and-dom.md @@ -37,7 +37,7 @@ All nodes can carry: `DocraftDocument` offers utility APIs to query and mutate the graph: - readonly: `find_by_name`, `find_first_by_name`, `find_last_by_name`, `find_by_type()`, -- mutable: `edit_find_by_name`, `edit_find_first_by_name`, `edit_find_last_by_name`, `edit_find_by_type()`, +- mutable: `take_by_name`, `take_first_by_name`, `take_last_by_name`, `take_by_type()`, - `traverse_dom(callback)`. Readonly methods return pointers to const nodes; use `edit_*` methods when runtime customization is required. diff --git a/doc/project-doc/users/craft-language.md b/doc/project-doc/users/craft-language.md index c7482d9..577b211 100644 --- a/doc/project-doc/users/craft-language.md +++ b/doc/project-doc/users/craft-language.md @@ -230,7 +230,7 @@ A common pattern is: parse `.craft`, then modify nodes programmatically before r ```cpp auto title_node = std::dynamic_pointer_cast( - document->edit_find_first_by_name("report_title")); + document->take_first_by_name("report_title")); if (title_node) { title_node->set_text("Q1 2026 - Final Version"); } @@ -239,7 +239,7 @@ if (title_node) { ### 6.2 Find nodes by type ```cpp -auto texts = document->edit_find_by_type(); +auto texts = document->take_by_type(); for (const auto &text : texts) { if (text->text().empty()) { text->set_visible(false); diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 280d210..f515fe8 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -173,21 +173,21 @@ namespace docraft { * @return Vector of nodes matching the name, or empty vector if none found. */ std::vector> find_by_name(const std::string &name) const; - std::vector> edit_find_by_name(const std::string &name); + std::vector> take_by_name(const std::string &name); /** * @brief Finds the first node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the first matching node, or nullptr if not found. */ std::shared_ptr find_first_by_name(const std::string &name) const; - std::shared_ptr edit_find_first_by_name(const std::string &name); + std::shared_ptr take_first_by_name(const std::string &name); /** * @brief Finds the last node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the last matching node, or nullptr if not found. */ std::shared_ptr find_last_by_name(const std::string &name) const; - std::shared_ptr edit_find_last_by_name(const std::string &name); + std::shared_ptr take_last_by_name(const std::string &name); /** * @brief Finds nodes by type in the document DOM. * @tparam T Node type to search for. @@ -196,7 +196,7 @@ namespace docraft { template std::vector> find_by_type() const; template - std::vector> edit_find_by_type(); + std::vector> take_by_type(); /** * @brief Traverses the document DOM and executes a callback on each node. * @param callback Function called for each node and operation (enter/exit). diff --git a/docraft/include/docraft/docraft_document.hpp b/docraft/include/docraft/docraft_document.hpp index 4bcf2d7..f64bc37 100644 --- a/docraft/include/docraft/docraft_document.hpp +++ b/docraft/include/docraft/docraft_document.hpp @@ -19,7 +19,7 @@ namespace docraft { } template - std::vector> DocraftDocument::edit_find_by_type() { + std::vector> DocraftDocument::take_by_type() { std::vector> result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index 15c2af3..8a2cfa2 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -141,32 +141,47 @@ namespace docraft { * @brief Returns the line backend (cached). * @return Line rendering backend. */ - [[nodiscard]] std::shared_ptr line_backend() const; - [[nodiscard]] std::shared_ptr edit_line_backend(); + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( + backend::IDocraftLineRenderingBackend, + line_backend, + line_backend_, + line_backend_ = std::dynamic_pointer_cast(backend_)) /** * @brief Returns the shape backend (cached). * @return Shape rendering backend. */ - [[nodiscard]] std::shared_ptr shape_backend() const; - [[nodiscard]] std::shared_ptr edit_shape_backend(); + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( + backend::IDocraftShapeRenderingBackend, + shape_backend, + shape_backend_, + shape_backend_ = std::dynamic_pointer_cast(backend_)) /** * @brief Returns the text backend (cached). * @return Text rendering backend. */ - [[nodiscard]] std::shared_ptr text_backend() const; - [[nodiscard]] std::shared_ptr edit_text_backend(); + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( + backend::IDocraftTextRenderingBackend, + text_backend, + text_backend_, + text_backend_ = std::dynamic_pointer_cast(backend_)) /** * @brief Returns the image backend (cached). * @return Image rendering backend. */ - [[nodiscard]] std::shared_ptr image_backend() const; - [[nodiscard]] std::shared_ptr edit_image_backend(); + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( + backend::IDocraftImageRenderingBackend, + image_backend, + image_backend_, + image_backend_ = std::dynamic_pointer_cast(backend_)) /** * @brief Returns the page backend (cached). * @return Page rendering backend. */ - [[nodiscard]] std::shared_ptr page_backend() const; - [[nodiscard]] std::shared_ptr edit_page_backend(); + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( + backend::IDocraftPageRenderingBackend, + page_backend, + page_backend_, + page_backend_ = std::dynamic_pointer_cast(backend_)) /** * @brief Replaces the underlying rendering backend. * @param backend New rendering backend. Pass nullptr to restore the default backend. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index 217ec0b..5b19df4 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -45,3 +45,17 @@ #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; } \ [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } + +#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT(TYPE, NAME, MEMBER, INIT_EXPR) \ + [[nodiscard]] std::shared_ptr NAME() const { \ + if (!(MEMBER)) { \ + INIT_EXPR; \ + } \ + return MEMBER; \ + } \ + [[nodiscard]] std::shared_ptr edit_##NAME() { \ + if (!(MEMBER)) { \ + INIT_EXPR; \ + } \ + return MEMBER; \ + } diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index fc80425..20c69fb 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -357,7 +357,7 @@ namespace docraft { return result; } - std::vector> DocraftDocument::edit_find_by_name(const std::string &name) { + std::vector> DocraftDocument::take_by_name(const std::string &name) { return find_by_name_impl(name); } @@ -379,8 +379,8 @@ namespace docraft { return matches.empty() ? nullptr : matches.front(); } - std::shared_ptr DocraftDocument::edit_find_first_by_name(const std::string &name) { - const auto matches = edit_find_by_name(name); + std::shared_ptr DocraftDocument::take_first_by_name(const std::string &name) { + const auto matches = take_by_name(name); return matches.empty() ? nullptr : matches.front(); } @@ -389,8 +389,8 @@ namespace docraft { return matches.empty() ? nullptr : matches.back(); } - std::shared_ptr DocraftDocument::edit_find_last_by_name(const std::string &name) { - const auto matches = edit_find_by_name(name); + std::shared_ptr DocraftDocument::take_last_by_name(const std::string &name) { + const auto matches = take_by_name(name); return matches.empty() ? nullptr : matches.back(); } diff --git a/docraft/src/docraft/docraft_document_context.cc b/docraft/src/docraft/docraft_document_context.cc index 2515c8b..231200b 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -107,76 +107,6 @@ namespace docraft { return page_width_; } - std::shared_ptr DocraftDocumentContext::line_backend() const { - if (!line_backend_) { - line_backend_ = std::dynamic_pointer_cast(backend_); - } - return line_backend_; - } - - std::shared_ptr DocraftDocumentContext::edit_line_backend() { - if (!line_backend_) { - line_backend_ = std::dynamic_pointer_cast(backend_); - } - return line_backend_; - } - - std::shared_ptr DocraftDocumentContext::shape_backend() const { - if (!shape_backend_) { - shape_backend_ = std::dynamic_pointer_cast(backend_); - } - return shape_backend_; - } - - std::shared_ptr DocraftDocumentContext::edit_shape_backend() { - if (!shape_backend_) { - shape_backend_ = std::dynamic_pointer_cast(backend_); - } - return shape_backend_; - } - - std::shared_ptr DocraftDocumentContext::text_backend() const { - if (!text_backend_) { - text_backend_ = std::dynamic_pointer_cast(backend_); - } - return text_backend_; - } - - std::shared_ptr DocraftDocumentContext::edit_text_backend() { - if (!text_backend_) { - text_backend_ = std::dynamic_pointer_cast(backend_); - } - return text_backend_; - } - - std::shared_ptr DocraftDocumentContext::image_backend() const { - if (!image_backend_) { - image_backend_ = std::dynamic_pointer_cast(backend_); - } - return image_backend_; - } - - std::shared_ptr DocraftDocumentContext::edit_image_backend() { - if (!image_backend_) { - image_backend_ = std::dynamic_pointer_cast(backend_); - } - return image_backend_; - } - - std::shared_ptr DocraftDocumentContext::page_backend() const { - if (!page_backend_) { - page_backend_ = std::dynamic_pointer_cast(backend_); - } - return page_backend_; - } - - std::shared_ptr DocraftDocumentContext::edit_page_backend() { - if (!page_backend_) { - page_backend_ = std::dynamic_pointer_cast(backend_); - } - return page_backend_; - } - void DocraftDocumentContext::go_to_first_page() { const auto backend = edit_page_backend(); if (backend) { diff --git a/docraft/test/docraft/docraft_document_test.cc b/docraft/test/docraft/docraft_document_test.cc index 58a78ce..23f8f46 100644 --- a/docraft/test/docraft/docraft_document_test.cc +++ b/docraft/test/docraft/docraft_document_test.cc @@ -141,7 +141,7 @@ namespace docraft::test { document.add_node(text); auto editable = std::dynamic_pointer_cast( - document.edit_find_first_by_name("target")); + document.take_first_by_name("target")); ASSERT_TRUE(editable); editable->set_text("After"); From ac081d2db8067c2bb1cdf2cb59ae00e8bc6386a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:32:55 +0000 Subject: [PATCH 08/15] refactor: apply macro accessors and rename template lookup API --- .../craft/docraft_craft_language_parser.h | 3 +-- docraft/include/docraft/docraft_document.h | 7 +++---- docraft/include/docraft/docraft_lib.h | 4 ++++ .../docraft/templating/docraft_template_engine.h | 2 +- .../craft/docraft_craft_language_parser.cc | 8 -------- docraft/src/docraft/docraft_document.cc | 16 ---------------- .../templating/docraft_template_engine.cc | 6 +++--- .../templating/docraft_template_engine_test.cc | 8 ++++---- 8 files changed, 16 insertions(+), 38 deletions(-) diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index 8a2baa7..aa4cabf 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -57,8 +57,7 @@ namespace docraft::craft { * @brief Returns the parsed document. * @return Parsed document or nullptr if not parsed. */ - std::shared_ptr get_document() const; - std::shared_ptr edit_document(); + DOCRAFT_CREATE_GET_AND_EDIT_METHOD_SHARED_SMART_POINTER(DocraftDocument, document, document_) /** * @brief Parses a single XML node into a Docraft node. diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index f515fe8..77ccd55 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -91,7 +91,7 @@ namespace docraft { * @brief Returns the current document title. * @return Document title string. */ - std::string document_title() const; + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_title, document_title_) /** * @brief Sets the output directory where the rendered file will be saved. @@ -103,7 +103,7 @@ namespace docraft { * @brief Returns the current output directory path. * @return Output directory path. */ - std::string document_path() const; + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_path, document_path_) /** * @brief Sets document settings (fonts, etc.). @@ -165,8 +165,7 @@ namespace docraft { * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - std::vector> nodes() const; - std::vector> &edit_nodes(); + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::vector>, nodes, dom_) /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index 5b19df4..7eb2305 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -46,6 +46,10 @@ [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; } \ [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } +#define DOCRAFT_CREATE_GET_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ + [[nodiscard]] std::shared_ptr get_##NAME() const { return MEMBER; } \ + [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } + #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT(TYPE, NAME, MEMBER, INIT_EXPR) \ [[nodiscard]] std::shared_ptr NAME() const { \ if (!(MEMBER)) { \ diff --git a/docraft/include/docraft/templating/docraft_template_engine.h b/docraft/include/docraft/templating/docraft_template_engine.h index b0f7ef5..c0a041f 100644 --- a/docraft/include/docraft/templating/docraft_template_engine.h +++ b/docraft/include/docraft/templating/docraft_template_engine.h @@ -53,7 +53,7 @@ namespace docraft::templating { * @return Stored value. * @throws std::runtime_error if not found. */ - std::string get_template_variable(const std::string &name) const; + std::string find_template_variable(const std::string &name) const; /** * @brief Clears all template variables. */ diff --git a/docraft/src/docraft/craft/docraft_craft_language_parser.cc b/docraft/src/docraft/craft/docraft_craft_language_parser.cc index 7f9772d..807d667 100644 --- a/docraft/src/docraft/craft/docraft_craft_language_parser.cc +++ b/docraft/src/docraft/craft/docraft_craft_language_parser.cc @@ -665,12 +665,4 @@ void DocraftCraftLanguageParser::load_document() { LOG_INFO("Document loaded successfully with title: " + document_->document_title()); } -std::shared_ptr DocraftCraftLanguageParser::get_document() const { - return document_; -} - -std::shared_ptr DocraftCraftLanguageParser::edit_document() { - return document_; -} - } // namespace docraft::craft diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index 20c69fb..1a48cac 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -276,18 +276,10 @@ namespace docraft { metadata_.set_title(document_title); } - std::string DocraftDocument::document_title() const { - return document_title_; - } - void DocraftDocument::set_document_path(const std::string &document_path) { document_path_ = document_path; } - std::string DocraftDocument::document_path() const { - return document_path_; - } - void DocraftDocument::set_settings(const std::shared_ptr &settings) { settings_ = settings; } @@ -341,14 +333,6 @@ namespace docraft { template_engine_ = template_engine; } - std::vector> DocraftDocument::nodes() const { - return {dom_.begin(), dom_.end()}; - } - - std::vector> &DocraftDocument::edit_nodes() { - return dom_; - } - std::vector> DocraftDocument::find_by_name(const std::string &name) const { std::vector> result; for (const auto &node: find_by_name_impl(name)) { diff --git a/docraft/src/docraft/templating/docraft_template_engine.cc b/docraft/src/docraft/templating/docraft_template_engine.cc index c1ebc50..78f2373 100644 --- a/docraft/src/docraft/templating/docraft_template_engine.cc +++ b/docraft/src/docraft/templating/docraft_template_engine.cc @@ -53,7 +53,7 @@ namespace docraft::templating { template_variables_.insert({normalized_name, value}); } - std::string DocraftTemplateEngine::get_template_variable(const std::string &name) const { + std::string DocraftTemplateEngine::find_template_variable(const std::string &name) const { auto normalized_name = normalize_name(name); auto it = template_variables_.find(normalized_name); if (it == template_variables_.end()) { @@ -240,7 +240,7 @@ namespace docraft::templating { } } else if (has_template_variable(variable_name)) { // Handle normal template variables if they are used in foreach item templates - variable_value = get_template_variable(variable_name); + variable_value = find_template_variable(variable_name); } else { LOG_WARNING("Template variable '" + variable_name + "' not found in template engine."); variable_value = text; @@ -270,7 +270,7 @@ namespace docraft::templating { std::string variable_value; try { if (has_template_variable(variable_name)) { - variable_value = get_template_variable(variable_name); + variable_value = find_template_variable(variable_name); } else { LOG_WARNING("Template variable '" + variable_name + "' not found in template engine."); variable_value = text; diff --git a/docraft/test/docraft/templating/docraft_template_engine_test.cc b/docraft/test/docraft/templating/docraft_template_engine_test.cc index d406037..f1b2657 100644 --- a/docraft/test/docraft/templating/docraft_template_engine_test.cc +++ b/docraft/test/docraft/templating/docraft_template_engine_test.cc @@ -29,18 +29,18 @@ namespace docraft::test::templating { engine_.add_template_variable("title", "Docraft"); EXPECT_TRUE(engine_.has_template_variable("title")); EXPECT_EQ(engine_.items(), 1); - EXPECT_EQ(engine_.get_template_variable("title"), "Docraft"); + EXPECT_EQ(engine_.find_template_variable("title"), "Docraft"); } TEST_F(DocraftTemplateEngineTest, AddDuplicateVariableThrows) { engine_.add_template_variable("title", "Docraft"); EXPECT_THROW(engine_.add_template_variable("title", "Other"), std::runtime_error); EXPECT_EQ(engine_.items(), 1); - EXPECT_EQ(engine_.get_template_variable("title"), "Docraft"); + EXPECT_EQ(engine_.find_template_variable("title"), "Docraft"); } TEST_F(DocraftTemplateEngineTest, GetMissingVariableThrows) { - EXPECT_THROW(engine_.get_template_variable("missing"), std::runtime_error); + EXPECT_THROW(engine_.find_template_variable("missing"), std::runtime_error); } TEST_F(DocraftTemplateEngineTest, RemoveVariable) { @@ -112,7 +112,7 @@ namespace docraft::test::templating { auto template_engine = std::make_shared(); template_engine->add_template_variable("title", "Docraft"); document.set_document_template_engine(template_engine); - EXPECT_EQ(document.document_template_engine()->get_template_variable("title"), "Docraft"); + EXPECT_EQ(document.document_template_engine()->find_template_variable("title"), "Docraft"); //text std::shared_ptr text_node1 = std::make_shared("${title} is a great library!"); document.add_node(text_node1); From 281ea8da33d6f1c415d54416acc56dabfade63d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:38:10 +0000 Subject: [PATCH 09/15] refactor: preserve const node getter semantics in accessor macro --- .../docraft/craft/docraft_craft_language_parser.h | 3 ++- docraft/include/docraft/docraft_document.h | 2 +- docraft/include/docraft/docraft_lib.h | 11 ++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index aa4cabf..568edd1 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -57,7 +57,8 @@ namespace docraft::craft { * @brief Returns the parsed document. * @return Parsed document or nullptr if not parsed. */ - DOCRAFT_CREATE_GET_AND_EDIT_METHOD_SHARED_SMART_POINTER(DocraftDocument, document, document_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_CUSTOM( + DocraftDocument, get_document, edit_document, document_) /** * @brief Parses a single XML node into a Docraft node. diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 77ccd55..23b2bcc 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -165,7 +165,7 @@ namespace docraft { * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::vector>, nodes, dom_) + DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftNode, nodes, dom_) /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index 7eb2305..be4fbbc 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -17,6 +17,7 @@ #pragma once #include +#include #if defined(_WIN32) || defined(__CYGWIN__) #if defined(DOCRAFT_BUILD_SHARED_LIBS) @@ -46,9 +47,9 @@ [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; } \ [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } -#define DOCRAFT_CREATE_GET_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ - [[nodiscard]] std::shared_ptr get_##NAME() const { return MEMBER; } \ - [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } +#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_CUSTOM(TYPE, GETTER_NAME, EDIT_NAME, MEMBER) \ + [[nodiscard]] std::shared_ptr GETTER_NAME() const { return MEMBER; } \ + [[nodiscard]] std::shared_ptr EDIT_NAME() { return MEMBER; } #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT(TYPE, NAME, MEMBER, INIT_EXPR) \ [[nodiscard]] std::shared_ptr NAME() const { \ @@ -63,3 +64,7 @@ } \ return MEMBER; \ } + +#define DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ + [[nodiscard]] std::vector> NAME() const { return {MEMBER.begin(), MEMBER.end()}; } \ + [[nodiscard]] std::vector> &edit_##NAME() { return MEMBER; } From f4edc51818bece577721afd3e32893b566957fff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 21:53:25 +0000 Subject: [PATCH 10/15] fix: make accessor macro declarations parser-safe for docs --- .../craft/docraft_craft_language_parser.h | 2 +- docraft/include/docraft/docraft_document.h | 10 +++++----- .../docraft/docraft_document_context.h | 20 +++++++++---------- docraft/include/docraft/docraft_lib.h | 20 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index 568edd1..85cf80d 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -58,7 +58,7 @@ namespace docraft::craft { * @return Parsed document or nullptr if not parsed. */ DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_CUSTOM( - DocraftDocument, get_document, edit_document, document_) + DocraftDocument, get_document, edit_document, document_); /** * @brief Parses a single XML node into a Docraft node. diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 23b2bcc..3ceffbd 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -91,7 +91,7 @@ namespace docraft { * @brief Returns the current document title. * @return Document title string. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_title, document_title_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_title, document_title_); /** * @brief Sets the output directory where the rendered file will be saved. @@ -103,7 +103,7 @@ namespace docraft { * @brief Returns the current output directory path. * @return Output directory path. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_path, document_path_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_path, document_path_); /** * @brief Sets document settings (fonts, etc.). @@ -115,7 +115,7 @@ namespace docraft { * @brief Returns the current settings object. * @return Shared pointer to settings or nullptr if not set. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftSettings, settings, settings_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftSettings, settings, settings_); /** * @brief Sets document metadata values. @@ -160,12 +160,12 @@ namespace docraft { void set_document_template_engine(const std::shared_ptr &template_engine); - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(templating::DocraftTemplateEngine, document_template_engine, template_engine_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(templating::DocraftTemplateEngine, document_template_engine, template_engine_); /** * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftNode, nodes, dom_) + DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftNode, nodes, dom_); /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index 8a2cfa2..06f61d8 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -60,7 +60,7 @@ namespace docraft { * @brief Returns the active rendering backend. * @return Shared pointer to the rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(backend::IDocraftRenderingBackend, rendering_backend, backend_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(backend::IDocraftRenderingBackend, rendering_backend, backend_); /** * @brief Returns the mutable layout cursor. * @return Reference to the cursor. @@ -106,7 +106,7 @@ namespace docraft { * @brief Returns the header node. * @return Header node (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftHeader, header, header_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftHeader, header, header_); /** * @brief Sets the document body node. * @param body Body node. @@ -116,7 +116,7 @@ namespace docraft { * @brief Returns the body node. * @return Body node (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftBody, body, body_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftBody, body, body_); /** * @brief Sets the document footer node. * @param footer Footer node. @@ -126,7 +126,7 @@ namespace docraft { * @brief Returns the footer node. * @return Footer node (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftFooter, footer, footer_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftFooter, footer, footer_); /** * @brief Sets the font applier used for text nodes. * @param font_applier Font applier instance. @@ -136,7 +136,7 @@ namespace docraft { * @brief Returns the font applier instance. * @return Font applier (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(docraft::generic::DocraftFontApplier, font_applier, font_applier_) + DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(docraft::generic::DocraftFontApplier, font_applier, font_applier_); /** * @brief Returns the line backend (cached). * @return Line rendering backend. @@ -145,7 +145,7 @@ namespace docraft { backend::IDocraftLineRenderingBackend, line_backend, line_backend_, - line_backend_ = std::dynamic_pointer_cast(backend_)) + line_backend_ = std::dynamic_pointer_cast(backend_)); /** * @brief Returns the shape backend (cached). * @return Shape rendering backend. @@ -154,7 +154,7 @@ namespace docraft { backend::IDocraftShapeRenderingBackend, shape_backend, shape_backend_, - shape_backend_ = std::dynamic_pointer_cast(backend_)) + shape_backend_ = std::dynamic_pointer_cast(backend_)); /** * @brief Returns the text backend (cached). * @return Text rendering backend. @@ -163,7 +163,7 @@ namespace docraft { backend::IDocraftTextRenderingBackend, text_backend, text_backend_, - text_backend_ = std::dynamic_pointer_cast(backend_)) + text_backend_ = std::dynamic_pointer_cast(backend_)); /** * @brief Returns the image backend (cached). * @return Image rendering backend. @@ -172,7 +172,7 @@ namespace docraft { backend::IDocraftImageRenderingBackend, image_backend, image_backend_, - image_backend_ = std::dynamic_pointer_cast(backend_)) + image_backend_ = std::dynamic_pointer_cast(backend_)); /** * @brief Returns the page backend (cached). * @return Page rendering backend. @@ -181,7 +181,7 @@ namespace docraft { backend::IDocraftPageRenderingBackend, page_backend, page_backend_, - page_backend_ = std::dynamic_pointer_cast(backend_)) + page_backend_ = std::dynamic_pointer_cast(backend_)); /** * @brief Replaces the underlying rendering backend. * @param backend New rendering backend. Pass nullptr to restore the default backend. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index be4fbbc..b530a63 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -40,16 +40,16 @@ #endif #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(TYPE, NAME, MEMBER) \ - [[nodiscard]] const TYPE &NAME() const { return MEMBER; } \ - [[nodiscard]] TYPE &edit_##NAME() { return MEMBER; } + [[nodiscard]] const TYPE &NAME() const { return MEMBER; }; \ + [[nodiscard]] TYPE &edit_##NAME() { return MEMBER; }; #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ - [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; } \ - [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; } + [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; }; \ + [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; }; #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_CUSTOM(TYPE, GETTER_NAME, EDIT_NAME, MEMBER) \ - [[nodiscard]] std::shared_ptr GETTER_NAME() const { return MEMBER; } \ - [[nodiscard]] std::shared_ptr EDIT_NAME() { return MEMBER; } + [[nodiscard]] std::shared_ptr GETTER_NAME() const { return MEMBER; }; \ + [[nodiscard]] std::shared_ptr EDIT_NAME() { return MEMBER; }; #define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT(TYPE, NAME, MEMBER, INIT_EXPR) \ [[nodiscard]] std::shared_ptr NAME() const { \ @@ -57,14 +57,14 @@ INIT_EXPR; \ } \ return MEMBER; \ - } \ + }; \ [[nodiscard]] std::shared_ptr edit_##NAME() { \ if (!(MEMBER)) { \ INIT_EXPR; \ } \ return MEMBER; \ - } + }; #define DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ - [[nodiscard]] std::vector> NAME() const { return {MEMBER.begin(), MEMBER.end()}; } \ - [[nodiscard]] std::vector> &edit_##NAME() { return MEMBER; } + [[nodiscard]] std::vector> NAME() const { return {MEMBER.begin(), MEMBER.end()}; }; \ + [[nodiscard]] std::vector> &edit_##NAME() { return MEMBER; }; From 77163512d9a16e7d09ad9d23e22df671323213e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 22:01:22 +0000 Subject: [PATCH 11/15] refactor: unify getter/edit generation into single accessor macro --- .../craft/docraft_craft_language_parser.h | 10 ++- docraft/include/docraft/docraft_document.h | 45 ++++++++-- .../docraft/docraft_document_context.h | 90 ++++++++++++++----- docraft/include/docraft/docraft_lib.h | 45 +++++----- 4 files changed, 139 insertions(+), 51 deletions(-) diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index 85cf80d..710a6b9 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -57,8 +57,14 @@ namespace docraft::craft { * @brief Returns the parsed document. * @return Parsed document or nullptr if not parsed. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_CUSTOM( - DocraftDocument, get_document, edit_document, document_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + get_document, + edit_document, + document_, + document_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Parses a single XML node into a Docraft node. diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 3ceffbd..2a299e1 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -91,7 +91,14 @@ namespace docraft { * @brief Returns the current document title. * @return Document title string. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_title, document_title_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + const std::string&, + std::string&, + document_title, + edit_document_title, + document_title_, + document_title_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Sets the output directory where the rendered file will be saved. @@ -103,7 +110,14 @@ namespace docraft { * @brief Returns the current output directory path. * @return Output directory path. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(std::string, document_path, document_path_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + const std::string&, + std::string&, + document_path, + edit_document_path, + document_path_, + document_path_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Sets document settings (fonts, etc.). @@ -115,7 +129,14 @@ namespace docraft { * @brief Returns the current settings object. * @return Shared pointer to settings or nullptr if not set. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftSettings, settings, settings_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + settings, + edit_settings, + settings_, + settings_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Sets document metadata values. @@ -160,12 +181,26 @@ namespace docraft { void set_document_template_engine(const std::shared_ptr &template_engine); - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(templating::DocraftTemplateEngine, document_template_engine, template_engine_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + document_template_engine, + edit_document_template_engine, + template_engine_, + template_engine_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftNode, nodes, dom_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::vector>, + std::vector>&, + nodes, + edit_nodes, + docraft::to_const_shared_vector(dom_), + dom_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index 06f61d8..49a4cf0 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -60,7 +60,14 @@ namespace docraft { * @brief Returns the active rendering backend. * @return Shared pointer to the rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(backend::IDocraftRenderingBackend, rendering_backend, backend_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + rendering_backend, + edit_rendering_backend, + backend_, + backend_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Returns the mutable layout cursor. * @return Reference to the cursor. @@ -106,7 +113,14 @@ namespace docraft { * @brief Returns the header node. * @return Header node (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftHeader, header, header_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + header, + edit_header, + header_, + header_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Sets the document body node. * @param body Body node. @@ -116,7 +130,14 @@ namespace docraft { * @brief Returns the body node. * @return Body node (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftBody, body, body_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + body, + edit_body, + body_, + body_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Sets the document footer node. * @param footer Footer node. @@ -126,7 +147,14 @@ namespace docraft { * @brief Returns the footer node. * @return Footer node (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(model::DocraftFooter, footer, footer_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + footer, + edit_footer, + footer_, + footer_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Sets the font applier used for text nodes. * @param font_applier Font applier instance. @@ -136,52 +164,74 @@ namespace docraft { * @brief Returns the font applier instance. * @return Font applier (may be nullptr). */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(docraft::generic::DocraftFontApplier, font_applier, font_applier_); + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, + font_applier, + edit_font_applier, + font_applier_, + font_applier_, + DOCRAFT_ACCESSOR_INIT_NONE); /** * @brief Returns the line backend (cached). * @return Line rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( - backend::IDocraftLineRenderingBackend, + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, line_backend, + edit_line_backend, line_backend_, - line_backend_ = std::dynamic_pointer_cast(backend_)); + line_backend_, + docraft::ensure_lazy_backend(line_backend_, backend_)); /** * @brief Returns the shape backend (cached). * @return Shape rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( - backend::IDocraftShapeRenderingBackend, + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, shape_backend, + edit_shape_backend, + shape_backend_, shape_backend_, - shape_backend_ = std::dynamic_pointer_cast(backend_)); + docraft::ensure_lazy_backend(shape_backend_, backend_)); /** * @brief Returns the text backend (cached). * @return Text rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( - backend::IDocraftTextRenderingBackend, + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, text_backend, + edit_text_backend, + text_backend_, text_backend_, - text_backend_ = std::dynamic_pointer_cast(backend_)); + docraft::ensure_lazy_backend(text_backend_, backend_)); /** * @brief Returns the image backend (cached). * @return Image rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( - backend::IDocraftImageRenderingBackend, + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, image_backend, + edit_image_backend, image_backend_, - image_backend_ = std::dynamic_pointer_cast(backend_)); + image_backend_, + docraft::ensure_lazy_backend(image_backend_, backend_)); /** * @brief Returns the page backend (cached). * @return Page rendering backend. */ - DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT( - backend::IDocraftPageRenderingBackend, + DOCRAFT_CREATE_ACCESSOR_METHOD( + std::shared_ptr, + std::shared_ptr, page_backend, + edit_page_backend, + page_backend_, page_backend_, - page_backend_ = std::dynamic_pointer_cast(backend_)); + docraft::ensure_lazy_backend(page_backend_, backend_)); /** * @brief Replaces the underlying rendering backend. * @param backend New rendering backend. Pass nullptr to restore the default backend. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index b530a63..776a3e2 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -39,32 +39,29 @@ #define DOCRAFT_LIB #endif -#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD(TYPE, NAME, MEMBER) \ - [[nodiscard]] const TYPE &NAME() const { return MEMBER; }; \ - [[nodiscard]] TYPE &edit_##NAME() { return MEMBER; }; +namespace docraft { + template + std::vector> to_const_shared_vector( + const std::vector> &values) { + return {values.begin(), values.end()}; + } -#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ - [[nodiscard]] std::shared_ptr NAME() const { return MEMBER; }; \ - [[nodiscard]] std::shared_ptr edit_##NAME() { return MEMBER; }; + template + void ensure_lazy_backend(std::shared_ptr &cache, const std::shared_ptr &backend) { + if (!cache) { + cache = std::dynamic_pointer_cast(backend); + } + } +} // namespace docraft -#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_CUSTOM(TYPE, GETTER_NAME, EDIT_NAME, MEMBER) \ - [[nodiscard]] std::shared_ptr GETTER_NAME() const { return MEMBER; }; \ - [[nodiscard]] std::shared_ptr EDIT_NAME() { return MEMBER; }; +#define DOCRAFT_ACCESSOR_INIT_NONE ((void)0) -#define DOCRAFT_CREATE_GETTER_AND_EDIT_METHOD_SHARED_SMART_POINTER_LAZY_INIT(TYPE, NAME, MEMBER, INIT_EXPR) \ - [[nodiscard]] std::shared_ptr NAME() const { \ - if (!(MEMBER)) { \ - INIT_EXPR; \ - } \ - return MEMBER; \ +#define DOCRAFT_CREATE_ACCESSOR_METHOD(GETTER_RETURN_TYPE, EDIT_RETURN_TYPE, GETTER_NAME, EDIT_NAME, GETTER_EXPR, EDIT_EXPR, INIT_EXPR) \ + [[nodiscard]] GETTER_RETURN_TYPE GETTER_NAME() const { \ + INIT_EXPR; \ + return GETTER_EXPR; \ }; \ - [[nodiscard]] std::shared_ptr edit_##NAME() { \ - if (!(MEMBER)) { \ - INIT_EXPR; \ - } \ - return MEMBER; \ + [[nodiscard]] EDIT_RETURN_TYPE EDIT_NAME() { \ + INIT_EXPR; \ + return EDIT_EXPR; \ }; - -#define DOCRAFT_CREATE_GETTER_CONST_VECTOR_AND_EDIT_METHOD_SHARED_SMART_POINTER(TYPE, NAME, MEMBER) \ - [[nodiscard]] std::vector> NAME() const { return {MEMBER.begin(), MEMBER.end()}; }; \ - [[nodiscard]] std::vector> &edit_##NAME() { return MEMBER; }; From 408f69651a22af0e8c3598367fe9923df1b3b5c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 05:50:07 +0000 Subject: [PATCH 12/15] refactor: replace accessor macros with explicit methods --- .../craft/docraft_craft_language_parser.h | 10 +- docraft/include/docraft/docraft_document.h | 50 ++------- .../docraft/docraft_document_context.h | 100 ++++-------------- docraft/include/docraft/docraft_lib.h | 12 --- .../craft/docraft_craft_language_parser.cc | 8 ++ docraft/src/docraft/docraft_document.cc | 40 +++++++ .../src/docraft/docraft_document_context.cc | 90 ++++++++++++++++ 7 files changed, 170 insertions(+), 140 deletions(-) diff --git a/docraft/include/docraft/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index 710a6b9..1d83f23 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_parser.h +++ b/docraft/include/docraft/craft/docraft_craft_language_parser.h @@ -57,14 +57,8 @@ namespace docraft::craft { * @brief Returns the parsed document. * @return Parsed document or nullptr if not parsed. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - get_document, - edit_document, - document_, - document_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr get_document() const; + [[nodiscard]] std::shared_ptr edit_document(); /** * @brief Parses a single XML node into a Docraft node. diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index 2a299e1..bdc1df5 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -91,14 +91,8 @@ namespace docraft { * @brief Returns the current document title. * @return Document title string. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - const std::string&, - std::string&, - document_title, - edit_document_title, - document_title_, - document_title_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] const std::string &document_title() const; + [[nodiscard]] std::string &edit_document_title(); /** * @brief Sets the output directory where the rendered file will be saved. @@ -110,14 +104,8 @@ namespace docraft { * @brief Returns the current output directory path. * @return Output directory path. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - const std::string&, - std::string&, - document_path, - edit_document_path, - document_path_, - document_path_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] const std::string &document_path() const; + [[nodiscard]] std::string &edit_document_path(); /** * @brief Sets document settings (fonts, etc.). @@ -129,14 +117,8 @@ namespace docraft { * @brief Returns the current settings object. * @return Shared pointer to settings or nullptr if not set. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - settings, - edit_settings, - settings_, - settings_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr settings() const; + [[nodiscard]] std::shared_ptr edit_settings(); /** * @brief Sets document metadata values. @@ -181,26 +163,14 @@ namespace docraft { void set_document_template_engine(const std::shared_ptr &template_engine); - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - document_template_engine, - edit_document_template_engine, - template_engine_, - template_engine_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr document_template_engine() const; + [[nodiscard]] std::shared_ptr edit_document_template_engine(); /** * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::vector>, - std::vector>&, - nodes, - edit_nodes, - docraft::to_const_shared_vector(dom_), - dom_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::vector> nodes() const; + [[nodiscard]] std::vector> &edit_nodes(); /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index 49a4cf0..76b6bdb 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -60,14 +60,8 @@ namespace docraft { * @brief Returns the active rendering backend. * @return Shared pointer to the rendering backend. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - rendering_backend, - edit_rendering_backend, - backend_, - backend_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr rendering_backend() const; + [[nodiscard]] std::shared_ptr edit_rendering_backend(); /** * @brief Returns the mutable layout cursor. * @return Reference to the cursor. @@ -113,14 +107,8 @@ namespace docraft { * @brief Returns the header node. * @return Header node (may be nullptr). */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - header, - edit_header, - header_, - header_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr header() const; + [[nodiscard]] std::shared_ptr edit_header(); /** * @brief Sets the document body node. * @param body Body node. @@ -130,14 +118,8 @@ namespace docraft { * @brief Returns the body node. * @return Body node (may be nullptr). */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - body, - edit_body, - body_, - body_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr body() const; + [[nodiscard]] std::shared_ptr edit_body(); /** * @brief Sets the document footer node. * @param footer Footer node. @@ -147,14 +129,8 @@ namespace docraft { * @brief Returns the footer node. * @return Footer node (may be nullptr). */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - footer, - edit_footer, - footer_, - footer_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr footer() const; + [[nodiscard]] std::shared_ptr edit_footer(); /** * @brief Sets the font applier used for text nodes. * @param font_applier Font applier instance. @@ -164,74 +140,38 @@ namespace docraft { * @brief Returns the font applier instance. * @return Font applier (may be nullptr). */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - font_applier, - edit_font_applier, - font_applier_, - font_applier_, - DOCRAFT_ACCESSOR_INIT_NONE); + [[nodiscard]] std::shared_ptr font_applier() const; + [[nodiscard]] std::shared_ptr edit_font_applier(); /** * @brief Returns the line backend (cached). * @return Line rendering backend. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - line_backend, - edit_line_backend, - line_backend_, - line_backend_, - docraft::ensure_lazy_backend(line_backend_, backend_)); + [[nodiscard]] std::shared_ptr line_backend() const; + [[nodiscard]] std::shared_ptr edit_line_backend(); /** * @brief Returns the shape backend (cached). * @return Shape rendering backend. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - shape_backend, - edit_shape_backend, - shape_backend_, - shape_backend_, - docraft::ensure_lazy_backend(shape_backend_, backend_)); + [[nodiscard]] std::shared_ptr shape_backend() const; + [[nodiscard]] std::shared_ptr edit_shape_backend(); /** * @brief Returns the text backend (cached). * @return Text rendering backend. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - text_backend, - edit_text_backend, - text_backend_, - text_backend_, - docraft::ensure_lazy_backend(text_backend_, backend_)); + [[nodiscard]] std::shared_ptr text_backend() const; + [[nodiscard]] std::shared_ptr edit_text_backend(); /** * @brief Returns the image backend (cached). * @return Image rendering backend. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - image_backend, - edit_image_backend, - image_backend_, - image_backend_, - docraft::ensure_lazy_backend(image_backend_, backend_)); + [[nodiscard]] std::shared_ptr image_backend() const; + [[nodiscard]] std::shared_ptr edit_image_backend(); /** * @brief Returns the page backend (cached). * @return Page rendering backend. */ - DOCRAFT_CREATE_ACCESSOR_METHOD( - std::shared_ptr, - std::shared_ptr, - page_backend, - edit_page_backend, - page_backend_, - page_backend_, - docraft::ensure_lazy_backend(page_backend_, backend_)); + [[nodiscard]] std::shared_ptr page_backend() const; + [[nodiscard]] std::shared_ptr edit_page_backend(); /** * @brief Replaces the underlying rendering backend. * @param backend New rendering backend. Pass nullptr to restore the default backend. diff --git a/docraft/include/docraft/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index 776a3e2..6041740 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -53,15 +53,3 @@ namespace docraft { } } } // namespace docraft - -#define DOCRAFT_ACCESSOR_INIT_NONE ((void)0) - -#define DOCRAFT_CREATE_ACCESSOR_METHOD(GETTER_RETURN_TYPE, EDIT_RETURN_TYPE, GETTER_NAME, EDIT_NAME, GETTER_EXPR, EDIT_EXPR, INIT_EXPR) \ - [[nodiscard]] GETTER_RETURN_TYPE GETTER_NAME() const { \ - INIT_EXPR; \ - return GETTER_EXPR; \ - }; \ - [[nodiscard]] EDIT_RETURN_TYPE EDIT_NAME() { \ - INIT_EXPR; \ - return EDIT_EXPR; \ - }; diff --git a/docraft/src/docraft/craft/docraft_craft_language_parser.cc b/docraft/src/docraft/craft/docraft_craft_language_parser.cc index 807d667..379717d 100644 --- a/docraft/src/docraft/craft/docraft_craft_language_parser.cc +++ b/docraft/src/docraft/craft/docraft_craft_language_parser.cc @@ -491,6 +491,14 @@ void DocraftCraftLanguageParser::load_from_file(const std::string &file_path) { load_document(); } +std::shared_ptr DocraftCraftLanguageParser::get_document() const { + return document_; +} + +std::shared_ptr DocraftCraftLanguageParser::edit_document() { + return document_; +} + void DocraftCraftLanguageParser::print_xml_tree(const pugi::xml_node &node, int /*indent*/) { node.print(std::cout); } diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index 1a48cac..03616e5 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -280,6 +280,30 @@ namespace docraft { document_path_ = document_path; } + const std::string &DocraftDocument::document_title() const { + return document_title_; + } + + std::string &DocraftDocument::edit_document_title() { + return document_title_; + } + + const std::string &DocraftDocument::document_path() const { + return document_path_; + } + + std::string &DocraftDocument::edit_document_path() { + return document_path_; + } + + std::shared_ptr DocraftDocument::settings() const { + return settings_; + } + + std::shared_ptr DocraftDocument::edit_settings() { + return settings_; + } + void DocraftDocument::set_settings(const std::shared_ptr &settings) { settings_ = settings; } @@ -333,6 +357,22 @@ namespace docraft { template_engine_ = template_engine; } + std::shared_ptr DocraftDocument::document_template_engine() const { + return template_engine_; + } + + std::shared_ptr DocraftDocument::edit_document_template_engine() { + return template_engine_; + } + + std::vector> DocraftDocument::nodes() const { + return docraft::to_const_shared_vector(dom_); + } + + std::vector> &DocraftDocument::edit_nodes() { + return dom_; + } + std::vector> DocraftDocument::find_by_name(const std::string &name) const { std::vector> result; for (const auto &node: find_by_name_impl(name)) { diff --git a/docraft/src/docraft/docraft_document_context.cc b/docraft/src/docraft/docraft_document_context.cc index 231200b..eb9549e 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -84,6 +84,14 @@ namespace docraft { } #pragma endregion #pragma region getter + std::shared_ptr DocraftDocumentContext::rendering_backend() const { + return backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_rendering_backend() { + return backend_; + } + DocraftCursor &DocraftDocumentContext::cursor() { return cursor_; } @@ -107,6 +115,88 @@ namespace docraft { return page_width_; } + std::shared_ptr DocraftDocumentContext::header() const { + return header_; + } + + std::shared_ptr DocraftDocumentContext::edit_header() { + return header_; + } + + std::shared_ptr DocraftDocumentContext::body() const { + return body_; + } + + std::shared_ptr DocraftDocumentContext::edit_body() { + return body_; + } + + std::shared_ptr DocraftDocumentContext::footer() const { + return footer_; + } + + std::shared_ptr DocraftDocumentContext::edit_footer() { + return footer_; + } + + std::shared_ptr DocraftDocumentContext::font_applier() const { + return font_applier_; + } + + std::shared_ptr DocraftDocumentContext::edit_font_applier() { + return font_applier_; + } + + std::shared_ptr DocraftDocumentContext::line_backend() const { + docraft::ensure_lazy_backend(line_backend_, backend_); + return line_backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_line_backend() { + docraft::ensure_lazy_backend(line_backend_, backend_); + return line_backend_; + } + + std::shared_ptr DocraftDocumentContext::shape_backend() const { + docraft::ensure_lazy_backend(shape_backend_, backend_); + return shape_backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_shape_backend() { + docraft::ensure_lazy_backend(shape_backend_, backend_); + return shape_backend_; + } + + std::shared_ptr DocraftDocumentContext::text_backend() const { + docraft::ensure_lazy_backend(text_backend_, backend_); + return text_backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_text_backend() { + docraft::ensure_lazy_backend(text_backend_, backend_); + return text_backend_; + } + + std::shared_ptr DocraftDocumentContext::image_backend() const { + docraft::ensure_lazy_backend(image_backend_, backend_); + return image_backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_image_backend() { + docraft::ensure_lazy_backend(image_backend_, backend_); + return image_backend_; + } + + std::shared_ptr DocraftDocumentContext::page_backend() const { + docraft::ensure_lazy_backend(page_backend_, backend_); + return page_backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_page_backend() { + docraft::ensure_lazy_backend(page_backend_, backend_); + return page_backend_; + } + void DocraftDocumentContext::go_to_first_page() { const auto backend = edit_page_backend(); if (backend) { From 3221b710e5356f03e46db0c0515243cdb045d051 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 06:00:25 +0000 Subject: [PATCH 13/15] refactor: move metadata accessors to cpp --- docraft/CMakeLists.txt | 2 + .../docraft/docraft_document_metadata.h | 60 ++++---- .../src/docraft/docraft_document_metadata.cc | 139 ++++++++++++++++++ 3 files changed, 171 insertions(+), 30 deletions(-) create mode 100644 docraft/src/docraft/docraft_document_metadata.cc diff --git a/docraft/CMakeLists.txt b/docraft/CMakeLists.txt index 3e74a93..721d1fb 100644 --- a/docraft/CMakeLists.txt +++ b/docraft/CMakeLists.txt @@ -18,6 +18,8 @@ set(DOCRAFT_SOURCES include/docraft/model/docraft_new_page.h src/docraft/docraft_document.cc include/docraft/docraft_document.h + src/docraft/docraft_document_metadata.cc + include/docraft/docraft_document_metadata.h src/docraft/docraft_document_context.cc include/docraft/docraft_document_context.h src/docraft/docraft_cursor.cc diff --git a/docraft/include/docraft/docraft_document_metadata.h b/docraft/include/docraft/docraft_document_metadata.h index 0d67c39..f1f962b 100644 --- a/docraft/include/docraft/docraft_document_metadata.h +++ b/docraft/include/docraft/docraft_document_metadata.h @@ -45,38 +45,38 @@ namespace docraft { int off_minutes = 0; }; - void set_author(const std::string &author) { author_ = author; } - void set_creator(const std::string &creator) { creator_ = creator; } - void set_producer(const std::string &producer) { producer_ = producer; } - void set_title(const std::string &title) { title_ = title; } - void set_subject(const std::string &subject) { subject_ = subject; } - void set_keywords(const std::string &keywords) { keywords_ = keywords; } - void set_trapped(const std::string &trapped) { trapped_ = trapped; } - void set_gts_pdfx(const std::string >s_pdfx) { gts_pdfx_ = gts_pdfx; } - void set_creation_date(const DateTime &creation_date) { creation_date_ = creation_date; } - void set_modification_date(const DateTime &modification_date) { modification_date_ = modification_date; } + void set_author(const std::string &author); + void set_creator(const std::string &creator); + void set_producer(const std::string &producer); + void set_title(const std::string &title); + void set_subject(const std::string &subject); + void set_keywords(const std::string &keywords); + void set_trapped(const std::string &trapped); + void set_gts_pdfx(const std::string >s_pdfx); + void set_creation_date(const DateTime &creation_date); + void set_modification_date(const DateTime &modification_date); - void clear_author() { author_.reset(); } - void clear_creator() { creator_.reset(); } - void clear_producer() { producer_.reset(); } - void clear_title() { title_.reset(); } - void clear_subject() { subject_.reset(); } - void clear_keywords() { keywords_.reset(); } - void clear_trapped() { trapped_.reset(); } - void clear_gts_pdfx() { gts_pdfx_.reset(); } - void clear_creation_date() { creation_date_.reset(); } - void clear_modification_date() { modification_date_.reset(); } + void clear_author(); + void clear_creator(); + void clear_producer(); + void clear_title(); + void clear_subject(); + void clear_keywords(); + void clear_trapped(); + void clear_gts_pdfx(); + void clear_creation_date(); + void clear_modification_date(); - [[nodiscard]] const std::optional &author() const { return author_; } - [[nodiscard]] const std::optional &creator() const { return creator_; } - [[nodiscard]] const std::optional &producer() const { return producer_; } - [[nodiscard]] const std::optional &title() const { return title_; } - [[nodiscard]] const std::optional &subject() const { return subject_; } - [[nodiscard]] const std::optional &keywords() const { return keywords_; } - [[nodiscard]] const std::optional &trapped() const { return trapped_; } - [[nodiscard]] const std::optional >s_pdfx() const { return gts_pdfx_; } - [[nodiscard]] const std::optional &creation_date() const { return creation_date_; } - [[nodiscard]] const std::optional &modification_date() const { return modification_date_; } + [[nodiscard]] const std::optional &author() const; + [[nodiscard]] const std::optional &creator() const; + [[nodiscard]] const std::optional &producer() const; + [[nodiscard]] const std::optional &title() const; + [[nodiscard]] const std::optional &subject() const; + [[nodiscard]] const std::optional &keywords() const; + [[nodiscard]] const std::optional &trapped() const; + [[nodiscard]] const std::optional >s_pdfx() const; + [[nodiscard]] const std::optional &creation_date() const; + [[nodiscard]] const std::optional &modification_date() const; private: std::optional author_; diff --git a/docraft/src/docraft/docraft_document_metadata.cc b/docraft/src/docraft/docraft_document_metadata.cc new file mode 100644 index 0000000..857f5ef --- /dev/null +++ b/docraft/src/docraft/docraft_document_metadata.cc @@ -0,0 +1,139 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "docraft/docraft_document_metadata.h" + +namespace docraft { + void DocraftDocumentMetadata::set_author(const std::string &author) { + author_ = author; + } + + void DocraftDocumentMetadata::set_creator(const std::string &creator) { + creator_ = creator; + } + + void DocraftDocumentMetadata::set_producer(const std::string &producer) { + producer_ = producer; + } + + void DocraftDocumentMetadata::set_title(const std::string &title) { + title_ = title; + } + + void DocraftDocumentMetadata::set_subject(const std::string &subject) { + subject_ = subject; + } + + void DocraftDocumentMetadata::set_keywords(const std::string &keywords) { + keywords_ = keywords; + } + + void DocraftDocumentMetadata::set_trapped(const std::string &trapped) { + trapped_ = trapped; + } + + void DocraftDocumentMetadata::set_gts_pdfx(const std::string >s_pdfx) { + gts_pdfx_ = gts_pdfx; + } + + void DocraftDocumentMetadata::set_creation_date(const DateTime &creation_date) { + creation_date_ = creation_date; + } + + void DocraftDocumentMetadata::set_modification_date(const DateTime &modification_date) { + modification_date_ = modification_date; + } + + void DocraftDocumentMetadata::clear_author() { + author_.reset(); + } + + void DocraftDocumentMetadata::clear_creator() { + creator_.reset(); + } + + void DocraftDocumentMetadata::clear_producer() { + producer_.reset(); + } + + void DocraftDocumentMetadata::clear_title() { + title_.reset(); + } + + void DocraftDocumentMetadata::clear_subject() { + subject_.reset(); + } + + void DocraftDocumentMetadata::clear_keywords() { + keywords_.reset(); + } + + void DocraftDocumentMetadata::clear_trapped() { + trapped_.reset(); + } + + void DocraftDocumentMetadata::clear_gts_pdfx() { + gts_pdfx_.reset(); + } + + void DocraftDocumentMetadata::clear_creation_date() { + creation_date_.reset(); + } + + void DocraftDocumentMetadata::clear_modification_date() { + modification_date_.reset(); + } + + const std::optional &DocraftDocumentMetadata::author() const { + return author_; + } + + const std::optional &DocraftDocumentMetadata::creator() const { + return creator_; + } + + const std::optional &DocraftDocumentMetadata::producer() const { + return producer_; + } + + const std::optional &DocraftDocumentMetadata::title() const { + return title_; + } + + const std::optional &DocraftDocumentMetadata::subject() const { + return subject_; + } + + const std::optional &DocraftDocumentMetadata::keywords() const { + return keywords_; + } + + const std::optional &DocraftDocumentMetadata::trapped() const { + return trapped_; + } + + const std::optional &DocraftDocumentMetadata::gts_pdfx() const { + return gts_pdfx_; + } + + const std::optional &DocraftDocumentMetadata::creation_date() const { + return creation_date_; + } + + const std::optional &DocraftDocumentMetadata::modification_date() const { + return modification_date_; + } +} // namespace docraft From ae7b8850b729d424812cf80eed5435aff3e4b8e0 Mon Sep 17 00:00:00 2001 From: cadons Date: Tue, 26 May 2026 22:20:56 +0200 Subject: [PATCH 14/15] refactor(context): rename context accessors for clarity and consistency --- docraft/include/docraft/docraft_color.h | 9 ++-- docraft/include/docraft/docraft_document.h | 46 +++++++++++++------ .../docraft/docraft_document_context.h | 16 +++++-- .../handler/abstract_docraft_layout_handler.h | 10 +++- .../docraft/utils/docraft_font_registry.h | 3 +- .../src/docraft/docraft_document_context.cc | 27 +++++------ .../docraft/generic/docraft_font_applier.cc | 8 ++-- .../handler/docraft_basic_layout_handler.cc | 6 +-- .../handler/docraft_layout_blank_line.cc | 2 +- .../layout/handler/docraft_layout_handler.cc | 2 +- .../handler/docraft_layout_list_handler.cc | 10 ++-- .../handler/docraft_layout_table_handler.cc | 4 +- .../handler/docraft_layout_text_handler.cc | 12 ++--- .../docraft/utils/docraft_font_registry.cc | 2 +- 14 files changed, 94 insertions(+), 63 deletions(-) diff --git a/docraft/include/docraft/docraft_color.h b/docraft/include/docraft/docraft_color.h index 3b97524..e8f84fd 100644 --- a/docraft/include/docraft/docraft_color.h +++ b/docraft/include/docraft/docraft_color.h @@ -18,6 +18,7 @@ #include "docraft/docraft_lib.h" #include +#include namespace docraft { /** @@ -49,11 +50,11 @@ namespace docraft { */ std::string to_hex() const { auto clamp_byte = [](int v) -> int { return v < 0 ? 0 : (v > 255 ? 255 : v); }; - int r_int = clamp_byte(static_cast(r * 255.0F + 0.5F));//convert float to int between 0-255 - int g_int = clamp_byte(static_cast(g * 255.0F + 0.5F)); - int b_int = clamp_byte(static_cast(b * 255.0F + 0.5F)); - int a_int = clamp_byte(static_cast(a * 255.0F + 0.5F)); + int r_int = clamp_byte(static_cast(std::lround(static_cast(r) * 255.0))); + int g_int = clamp_byte(static_cast(std::lround(static_cast(g) * 255.0))); + int b_int = clamp_byte(static_cast(std::lround(static_cast(b) * 255.0))); + int a_int = clamp_byte(static_cast(std::lround(static_cast(a) * 255.0))); char buf[10]; // "#RRGGBBAA" + null std::snprintf(buf, sizeof(buf), "#%02X%02X%02X%02X", r_int, g_int, b_int, a_int);//print the string in the buffer return std::string(buf); diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index bdc1df5..d2e8c02 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -33,6 +33,7 @@ namespace docraft { kEnter, kExit }; + /** * @brief High-level document container that owns settings, title, and the DOM node list. * @@ -45,7 +46,7 @@ namespace docraft { * @brief Creates a document with an optional title. * @param document_title Human-readable title for the document metadata. */ - DocraftDocument(std::string document_title = "Untitled Document"); + explicit DocraftDocument(std::string document_title = "Untitled Document"); /** * @brief Virtual destructor. @@ -92,6 +93,7 @@ namespace docraft { * @return Document title string. */ [[nodiscard]] const std::string &document_title() const; + [[nodiscard]] std::string &edit_document_title(); /** @@ -105,6 +107,7 @@ namespace docraft { * @return Output directory path. */ [[nodiscard]] const std::string &document_path() const; + [[nodiscard]] std::string &edit_document_path(); /** @@ -118,11 +121,12 @@ namespace docraft { * @return Shared pointer to settings or nullptr if not set. */ [[nodiscard]] std::shared_ptr settings() const; + [[nodiscard]] std::shared_ptr edit_settings(); /** * @brief Sets document metadata values. - * @param metadata Metadata values supported by libharu. + * @param metadata Metadata values supported by library. */ void set_document_metadata(const DocraftDocumentMetadata &metadata); @@ -164,55 +168,71 @@ namespace docraft { void set_document_template_engine(const std::shared_ptr &template_engine); [[nodiscard]] std::shared_ptr document_template_engine() const; + [[nodiscard]] std::shared_ptr edit_document_template_engine(); + /** * @brief Returns the document DOM nodes. * @return Vector of root nodes. */ - [[nodiscard]] std::vector> nodes() const; - [[nodiscard]] std::vector> &edit_nodes(); + [[nodiscard]] std::vector > nodes() const; + + [[nodiscard]] std::vector > &edit_nodes(); + /** * @brief Finds nodes by name in the document DOM. * @param name Node name to search for. * @return Vector of nodes matching the name, or empty vector if none found. */ - std::vector> find_by_name(const std::string &name) const; - std::vector> take_by_name(const std::string &name); + std::vector > find_by_name(const std::string &name) const; + + std::vector > take_by_name(const std::string &name); + /** * @brief Finds the first node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the first matching node, or nullptr if not found. */ std::shared_ptr find_first_by_name(const std::string &name) const; + std::shared_ptr take_first_by_name(const std::string &name); + /** * @brief Finds the last node by name in the document DOM. * @param name Node name to search for. * @return Shared pointer to the last matching node, or nullptr if not found. */ std::shared_ptr find_last_by_name(const std::string &name) const; + std::shared_ptr take_last_by_name(const std::string &name); + /** * @brief Finds nodes by type in the document DOM. * @tparam T Node type to search for. * @return Vector of nodes matching the type, or empty vector if none found. */ - template - std::vector> find_by_type() const; - template - std::vector> take_by_type(); + template + std::vector > find_by_type() const; + + template + std::vector > take_by_type(); + /** * @brief Traverses the document DOM and executes a callback on each node. * @param callback Function called for each node and operation (enter/exit). */ void traverse_dom( - const std::function &, DocraftDomTraverseOp)> &callback) const; + const std::function &, DocraftDomTraverseOp)> &callback) + const; private: void traverse_node( const std::shared_ptr &node, - const std::function &, DocraftDomTraverseOp)> &callback) const; - std::vector> find_by_name_impl(const std::string &name) const; + const std::function &, DocraftDomTraverseOp)> &callback) + const; + + std::vector > find_by_name_impl(const std::string &name) const; + std::shared_ptr context_; std::shared_ptr settings_; std::string document_title_; diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index 76b6bdb..f94ab44 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -211,6 +211,12 @@ namespace docraft { [[nodiscard]] float footer_ratio() const; private: + /** + * @brief Refreshes cached backend interfaces from the main backend. + * Called after backend initialization or replacement. + */ + void refresh_backend_caches_(); + DocraftCursor cursor_; float current_rect_width_=0; std::shared_ptr renderer_; @@ -221,11 +227,11 @@ namespace docraft { std::shared_ptr footer_; std::shared_ptr font_applier_; std::shared_ptr backend_; - mutable std::shared_ptr line_backend_; - mutable std::shared_ptr shape_backend_; - mutable std::shared_ptr text_backend_; - mutable std::shared_ptr image_backend_; - mutable std::shared_ptr page_backend_; + std::shared_ptr line_backend_; + std::shared_ptr shape_backend_; + std::shared_ptr text_backend_; + std::shared_ptr image_backend_; + std::shared_ptr page_backend_; float header_ratio_ = 0.06F; float body_ratio_ = 0.88F; float footer_ratio_ = 0.06F; diff --git a/docraft/include/docraft/layout/handler/abstract_docraft_layout_handler.h b/docraft/include/docraft/layout/handler/abstract_docraft_layout_handler.h index 544d2be..446211d 100644 --- a/docraft/include/docraft/layout/handler/abstract_docraft_layout_handler.h +++ b/docraft/include/docraft/layout/handler/abstract_docraft_layout_handler.h @@ -59,7 +59,15 @@ namespace docraft::layout::handler { * @brief Returns the bound document context. * @return Document context. */ - std::shared_ptr context() const { + std::shared_ptr edit_context() const { + return context_; + } + + /** + * @brief Returns the bound document context as a const pointer. + * @return + */ + std::shared_ptr context() const { return context_; } protected: diff --git a/docraft/include/docraft/utils/docraft_font_registry.h b/docraft/include/docraft/utils/docraft_font_registry.h index 8b0beb1..dff554a 100644 --- a/docraft/include/docraft/utils/docraft_font_registry.h +++ b/docraft/include/docraft/utils/docraft_font_registry.h @@ -65,7 +65,8 @@ namespace docraft::utils { * @param name Font family or variant name. * @return Pointer to font data, or nullptr if not found. */ - const DocraftFontData* get_font(const std::string& name) const; + const DocraftFontData *find_font(const std::string &name) const; + /** * @brief Returns the list of registered font names. * @return Vector of font names. diff --git a/docraft/src/docraft/docraft_document_context.cc b/docraft/src/docraft/docraft_document_context.cc index eb9549e..8607740 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -22,8 +22,8 @@ namespace docraft { backend_ = std::make_shared(); page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); - current_rect_width_ = page_width_; + refresh_backend_caches_(); } DocraftDocumentContext::DocraftDocumentContext( @@ -32,6 +32,7 @@ namespace docraft { page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); current_rect_width_ = page_width_; + refresh_backend_caches_(); } DocraftDocumentContext::~DocraftDocumentContext() = default; @@ -60,16 +61,20 @@ namespace docraft { font_applier_ = font_applier; } + void DocraftDocumentContext::refresh_backend_caches_() { + docraft::ensure_lazy_backend(line_backend_, backend_); + docraft::ensure_lazy_backend(shape_backend_, backend_); + docraft::ensure_lazy_backend(text_backend_, backend_); + docraft::ensure_lazy_backend(image_backend_, backend_); + docraft::ensure_lazy_backend(page_backend_, backend_); + } + void DocraftDocumentContext::set_backend(const std::shared_ptr &backend) { backend_ = backend ? backend : std::make_shared(); page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); current_rect_width_ = page_width_; - line_backend_.reset(); - shape_backend_.reset(); - text_backend_.reset(); - image_backend_.reset(); - page_backend_.reset(); + refresh_backend_caches_(); } void DocraftDocumentContext::set_page_format(model::DocraftPageSize size, @@ -148,52 +153,42 @@ namespace docraft { } std::shared_ptr DocraftDocumentContext::line_backend() const { - docraft::ensure_lazy_backend(line_backend_, backend_); return line_backend_; } std::shared_ptr DocraftDocumentContext::edit_line_backend() { - docraft::ensure_lazy_backend(line_backend_, backend_); return line_backend_; } std::shared_ptr DocraftDocumentContext::shape_backend() const { - docraft::ensure_lazy_backend(shape_backend_, backend_); return shape_backend_; } std::shared_ptr DocraftDocumentContext::edit_shape_backend() { - docraft::ensure_lazy_backend(shape_backend_, backend_); return shape_backend_; } std::shared_ptr DocraftDocumentContext::text_backend() const { - docraft::ensure_lazy_backend(text_backend_, backend_); return text_backend_; } std::shared_ptr DocraftDocumentContext::edit_text_backend() { - docraft::ensure_lazy_backend(text_backend_, backend_); return text_backend_; } std::shared_ptr DocraftDocumentContext::image_backend() const { - docraft::ensure_lazy_backend(image_backend_, backend_); return image_backend_; } std::shared_ptr DocraftDocumentContext::edit_image_backend() { - docraft::ensure_lazy_backend(image_backend_, backend_); return image_backend_; } std::shared_ptr DocraftDocumentContext::page_backend() const { - docraft::ensure_lazy_backend(page_backend_, backend_); return page_backend_; } std::shared_ptr DocraftDocumentContext::edit_page_backend() { - docraft::ensure_lazy_backend(page_backend_, backend_); return page_backend_; } diff --git a/docraft/src/docraft/generic/docraft_font_applier.cc b/docraft/src/docraft/generic/docraft_font_applier.cc index 0620ce8..adbbda8 100644 --- a/docraft/src/docraft/generic/docraft_font_applier.cc +++ b/docraft/src/docraft/generic/docraft_font_applier.cc @@ -122,7 +122,7 @@ namespace docraft::generic { auto ®istry = utils::DocraftFontRegistry::instance(); const auto &fonts = builtin_fonts(); const bool is_builtin = std::find(fonts.begin(), fonts.end(), name) != fonts.end(); - if (!is_builtin && registry.get_font(name) == nullptr) { + if (!is_builtin && registry.find_font(name) == nullptr) { std::cerr << "Font " << name << " not found in the resources" << std::endl; return false; } @@ -152,7 +152,7 @@ namespace docraft::generic { } // Try to get resource by the requested name - const auto *font_resource = registry.get_font(name); + const auto *font_resource = registry.find_font(name); std::string resolved_registered_name = name; if (!font_resource || !font_resource->data || font_resource->size == 0) { // tolerant lookup: try to find a registered font with a similar name @@ -162,7 +162,7 @@ namespace docraft::generic { std::string cand_norm = normalize_font_name(candidate); if (cand_norm == target_norm) { LOG_DEBUG("Using registered font variant '" + candidate + "' for requested font '" + name + "'"); - font_resource = registry.get_font(candidate); + font_resource = registry.find_font(candidate); resolved_registered_name = candidate; break; } @@ -173,7 +173,7 @@ namespace docraft::generic { for (auto &ch : target_hyphen) if (ch == ' ') ch = '-'; if (cand_hyphen == target_hyphen) { LOG_DEBUG("Using registered font variant '" + candidate + "' for requested font '" + name + "' (hyphen/space normalized)"); - font_resource = registry.get_font(candidate); + font_resource = registry.find_font(candidate); resolved_registered_name = candidate; break; } diff --git a/docraft/src/docraft/layout/handler/docraft_basic_layout_handler.cc b/docraft/src/docraft/layout/handler/docraft_basic_layout_handler.cc index f5ef66f..08ece11 100644 --- a/docraft/src/docraft/layout/handler/docraft_basic_layout_handler.cc +++ b/docraft/src/docraft/layout/handler/docraft_basic_layout_handler.cc @@ -44,11 +44,11 @@ namespace docraft::layout::handler { if (node->width() > 0.0F) { box->set_width(node->width()); } else if (node->auto_fill_width()) { - box->set_width(std::max(context()->available_space(), child_width)); + box->set_width(std::max(edit_context()->available_space(), child_width)); } else if (is_rectangle) { box->set_width(child_width); - } else if (context()->available_space() < node->width() || node->width() == 0.0F) { - box->set_width(context()->available_space()); + } else if (edit_context()->available_space() < node->width() || node->width() == 0.0F) { + box->set_width(edit_context()->available_space()); } else { box->set_width(node->width()); } diff --git a/docraft/src/docraft/layout/handler/docraft_layout_blank_line.cc b/docraft/src/docraft/layout/handler/docraft_layout_blank_line.cc index d7bf4d0..a102f23 100644 --- a/docraft/src/docraft/layout/handler/docraft_layout_blank_line.cc +++ b/docraft/src/docraft/layout/handler/docraft_layout_blank_line.cc @@ -24,7 +24,7 @@ namespace docraft::layout::handler { throw std::invalid_argument("box is null"); } node->set_weight(1.0F); //blank line takes full width - box->set_width(context()->available_space());//get full available width + box->set_width(edit_context()->available_space()); //get full available width if (node->height() > 0.0F) { box->set_height(node->height()); } else { diff --git a/docraft/src/docraft/layout/handler/docraft_layout_handler.cc b/docraft/src/docraft/layout/handler/docraft_layout_handler.cc index 36c8078..5edd845 100644 --- a/docraft/src/docraft/layout/handler/docraft_layout_handler.cc +++ b/docraft/src/docraft/layout/handler/docraft_layout_handler.cc @@ -28,7 +28,7 @@ namespace docraft::layout::handler { } // If the layout has a weight, the parent already scoped available_space to that share. if (node->weight()!=-1.0F) { - node->set_width(context()->available_space()); + node->set_width(edit_context()->available_space()); box->set_width(node->width()); } cursor.pop_direction(); //remove layout direction diff --git a/docraft/src/docraft/layout/handler/docraft_layout_list_handler.cc b/docraft/src/docraft/layout/handler/docraft_layout_list_handler.cc index 7fa4eb7..1b6dbcb 100644 --- a/docraft/src/docraft/layout/handler/docraft_layout_list_handler.cc +++ b/docraft/src/docraft/layout/handler/docraft_layout_list_handler.cc @@ -50,7 +50,7 @@ namespace docraft::layout::handler { } node->update_items(); node->clear_markers(); - const float saved_available_space = context()->available_space(); + const float saved_available_space = edit_context()->available_space(); const float list_available_width = max_width; for (std::size_t i = 0; i < node->children().size(); ++i) { auto text_child = std::dynamic_pointer_cast(node->children()[i]); @@ -72,14 +72,14 @@ namespace docraft::layout::handler { marker_size = text_child->font_size() * 0.6F; marker_width = marker_size; } else { - generic::DocraftFontApplier font_applier(context()); + generic::DocraftFontApplier font_applier(edit_context()); font_applier.apply_font(marker_text); - marker_width = context()->text_backend()->measure_text_width(marker_text->text()); + marker_width = edit_context()->text_backend()->measure_text_width(marker_text->text()); } const float marker_gap = marker_width > 0.0F ? 6.0F : 0.0F; const float content_width = std::max(0.0F, list_available_width - marker_width - marker_gap); - context()->set_current_rect_width(content_width); + edit_context()->set_current_rect_width(content_width); cursor.move_to(item_x + marker_width + marker_gap, item_y); const float original_padding = text_child->padding(); @@ -113,7 +113,7 @@ namespace docraft::layout::handler { cursor.move_to(item_x, cursor.y()); } - context()->set_current_rect_width(saved_available_space); + edit_context()->set_current_rect_width(saved_available_space); } bool DocraftLayoutListHandler::handle(const std::shared_ptr &request, diff --git a/docraft/src/docraft/layout/handler/docraft_layout_table_handler.cc b/docraft/src/docraft/layout/handler/docraft_layout_table_handler.cc index deefde3..31bd52b 100644 --- a/docraft/src/docraft/layout/handler/docraft_layout_table_handler.cc +++ b/docraft/src/docraft/layout/handler/docraft_layout_table_handler.cc @@ -532,10 +532,10 @@ namespace docraft::layout::handler { switch (node->orientation()) { case model::LayoutOrientation::kHorizontal: - layout_horizontal_table(node, box, context(), cursor); + layout_horizontal_table(node, box, edit_context(), cursor); break; case model::LayoutOrientation::kVertical: - layout_vertical_table(node, box, context(), cursor); + layout_vertical_table(node, box, edit_context(), cursor); break; default: throw std::runtime_error("unsupported table orientation"); diff --git a/docraft/src/docraft/layout/handler/docraft_layout_text_handler.cc b/docraft/src/docraft/layout/handler/docraft_layout_text_handler.cc index 81fdf64..2d0b09a 100644 --- a/docraft/src/docraft/layout/handler/docraft_layout_text_handler.cc +++ b/docraft/src/docraft/layout/handler/docraft_layout_text_handler.cc @@ -38,20 +38,20 @@ namespace docraft::layout::handler { } float DocraftLayoutTextHandler::measure_text_width(const std::shared_ptr &node) const { - generic::DocraftFontApplier font_applier(context()); + generic::DocraftFontApplier font_applier(edit_context()); font_applier.apply_font(node); - return context()->text_backend()->measure_text_width(node->text()); + return edit_context()->text_backend()->measure_text_width(node->text()); } float DocraftLayoutTextHandler::measure_test_width(const std::string &text) const { - return context()->text_backend()->measure_text_width(text); + return edit_context()->text_backend()->measure_text_width(text); } void DocraftLayoutTextHandler::compute(const std::shared_ptr &node, model::DocraftTransform* box, DocraftCursor& cursor) { filter_text(node); - generic::DocraftFontApplier font_applier(context()); + generic::DocraftFontApplier font_applier(edit_context()); font_applier.apply_font(node); auto global_cursor = cursor; @@ -67,7 +67,7 @@ namespace docraft::layout::handler { node->clear_lines(); // Recompute wrapping from scratch to avoid duplicate lines. const float padding = std::max(0.0F, node->padding()); - const float available_width = std::max(0.0F, context()->available_space() - (2.0F * padding)); + const float available_width = std::max(0.0F, edit_context()->available_space() - (2.0F * padding)); auto add_wrapped_word = [&](const std::string& word) { if (word.empty()) { return; @@ -149,7 +149,7 @@ namespace docraft::layout::handler { // Always move to the first baseline below the current cursor Y, // but clamp so the first line doesn't get clipped above the page. - const float kTopSafe = context()->page_height() - line_height; + const float kTopSafe = edit_context()->page_height() - line_height; float first_baseline_y = text_cursor.y() - line_height; if (first_baseline_y > kTopSafe) { first_baseline_y = kTopSafe; diff --git a/docraft/src/docraft/utils/docraft_font_registry.cc b/docraft/src/docraft/utils/docraft_font_registry.cc index ae2c957..1e8bd6b 100644 --- a/docraft/src/docraft/utils/docraft_font_registry.cc +++ b/docraft/src/docraft/utils/docraft_font_registry.cc @@ -53,7 +53,7 @@ namespace docraft::utils { } } - const DocraftFontData* DocraftFontRegistry::get_font(const std::string& name) const { + const DocraftFontData *DocraftFontRegistry::find_font(const std::string &name) const { auto it = registry_.find(name); if (it != registry_.end()) { return &it->second; From 7424bf44b03d3f84fbf747a7bd965ec5b2b387ec Mon Sep 17 00:00:00 2001 From: cadons Date: Tue, 26 May 2026 23:01:17 +0200 Subject: [PATCH 15/15] refactor(document): reorganize document management and query classes --- docraft/CMakeLists.txt | 25 +- .../include/docraft/docraft_backend_cache.h | 102 ++++++++ docraft/include/docraft/docraft_document.h | 175 +++++-------- docraft/include/docraft/docraft_document.hpp | 17 +- .../include/docraft/docraft_document_config.h | 142 ++++++++++ .../docraft/docraft_document_context.h | 149 +++++------ .../include/docraft/docraft_document_query.h | 0 .../docraft_document_section_manager.h | 105 ++++++++ .../management/docraft_backend_cache.h | 90 +++++++ .../management/docraft_document_config.h | 144 +++++++++++ .../management/docraft_document_query.h | 112 ++++++++ .../management/docraft_document_query.hpp | 53 ++++ .../docraft_document_section_manager.h | 102 ++++++++ docraft/src/docraft/docraft_document.cc | 242 +++++++++--------- .../src/docraft/docraft_document_context.cc | 164 ++++++------ .../management/docraft_backend_cache.cc | 74 ++++++ .../management/docraft_document_config.cc | 101 ++++++++ .../management/docraft_document_query.cc | 97 +++++++ .../docraft_document_section_manager.cc | 69 +++++ 19 files changed, 1534 insertions(+), 429 deletions(-) create mode 100644 docraft/include/docraft/docraft_backend_cache.h create mode 100644 docraft/include/docraft/docraft_document_config.h create mode 100644 docraft/include/docraft/docraft_document_query.h create mode 100644 docraft/include/docraft/docraft_document_section_manager.h create mode 100644 docraft/include/docraft/management/docraft_backend_cache.h create mode 100644 docraft/include/docraft/management/docraft_document_config.h create mode 100644 docraft/include/docraft/management/docraft_document_query.h create mode 100644 docraft/include/docraft/management/docraft_document_query.hpp create mode 100644 docraft/include/docraft/management/docraft_document_section_manager.h create mode 100644 docraft/src/docraft/management/docraft_backend_cache.cc create mode 100644 docraft/src/docraft/management/docraft_document_config.cc create mode 100644 docraft/src/docraft/management/docraft_document_query.cc create mode 100644 docraft/src/docraft/management/docraft_document_section_manager.cc diff --git a/docraft/CMakeLists.txt b/docraft/CMakeLists.txt index 721d1fb..92f2a34 100644 --- a/docraft/CMakeLists.txt +++ b/docraft/CMakeLists.txt @@ -151,11 +151,20 @@ set(DOCRAFT_SOURCES src/docraft/model/docraft_foreach.cc include/docraft/model/docraft_foreach.h src/docraft/craft/parser/docraft_foreach_parser.cc + src/docraft/management/docraft_document_section_manager.cc + include/docraft/management/docraft_document_section_manager.h + src/docraft/management/docraft_backend_cache.cc + include/docraft/management/docraft_backend_cache.h + src/docraft/management/docraft_document_query.cc + include/docraft/management/docraft_document_query.h + include/docraft/management/docraft_document_query.hpp + src/docraft/management/docraft_document_config.cc + include/docraft/management/docraft_document_config.h ) set(DOCRAFT_LIBRARY_TYPE STATIC) -if(BUILD_SHARED_LIBS) +if (BUILD_SHARED_LIBS) set(DOCRAFT_LIBRARY_TYPE SHARED) -endif() +endif () add_library(docraft ${DOCRAFT_LIBRARY_TYPE} ${DOCRAFT_SOURCES} ) @@ -169,23 +178,23 @@ target_compile_definitions(docraft PUBLIC $<$:docraft_debugf> ) -if(BUILD_SHARED_LIBS) +if (BUILD_SHARED_LIBS) target_compile_definitions(docraft PRIVATE DOCRAFT_BUILD_SHARED_LIBS)#define the macro to build the shared lib target_compile_definitions(docraft INTERFACE DOCRAFT_USE_SHARED_LIB)#define the macro to use the shared lib -endif() +endif () generate_docraft_fonts(docraft "${CMAKE_CURRENT_SOURCE_DIR}/fonts.json") add_executable(docraft_tool src/docraft/main.cpp) #declare define in the c++ code to export version target_compile_definitions(docraft_tool PRIVATE DOCRAFT_VERSION="${DOCRAFT_LIBRARY_VERSION}") configure_artifact_version(docraft_tool ${PROJECT_VERSION}) -if(TARGET pugixml::static) +if (TARGET pugixml::static) set(PUGIXML_TARGET pugixml::static) -elseif(TARGET pugixml::pugixml) +elseif (TARGET pugixml::pugixml) set(PUGIXML_TARGET pugixml::pugixml) -else() +else () message(FATAL_ERROR "No pugixml CMake target found") -endif() +endif () target_link_libraries(docraft PUBLIC ${PUGIXML_TARGET} nlohmann_json::nlohmann_json ${LIBHARU_TARGET} fmt::fmt) target_link_libraries(docraft_tool PRIVATE docraft) diff --git a/docraft/include/docraft/docraft_backend_cache.h b/docraft/include/docraft/docraft_backend_cache.h new file mode 100644 index 0000000..aae71c8 --- /dev/null +++ b/docraft/include/docraft/docraft_backend_cache.h @@ -0,0 +1,102 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include + +namespace docraft::backend { + class IDocraftRenderingBackend; + class IDocraftLineRenderingBackend; + class IDocraftShapeRenderingBackend; + class IDocraftTextRenderingBackend; + class IDocraftImageRenderingBackend; + class IDocraftPageRenderingBackend; +} + +namespace docraft { + /** + * @brief Manages cached rendering backend interfaces. + * + * Provides access to specific backend interfaces (line, shape, text, image, page) + * derived from the main rendering backend. These are cached for performance. + */ + class DOCRAFT_LIB DocraftBackendCache { + public: + /** + * @brief Initializes the backend cache from a main rendering backend. + * @param backend The main rendering backend. + */ + void initialize_from_backend(const std::shared_ptr &backend); + + /** + * @brief Returns the line backend (cached). + * @return Line rendering backend. + */ + [[nodiscard]] std::shared_ptr line_backend() const; + + [[nodiscard]] std::shared_ptr edit_line_backend(); + + /** + * @brief Returns the shape backend (cached). + * @return Shape rendering backend. + */ + [[nodiscard]] std::shared_ptr shape_backend() const; + + [[nodiscard]] std::shared_ptr edit_shape_backend(); + + /** + * @brief Returns the text backend (cached). + * @return Text rendering backend. + */ + [[nodiscard]] std::shared_ptr text_backend() const; + + [[nodiscard]] std::shared_ptr edit_text_backend(); + + /** + * @brief Returns the image backend (cached). + * @return Image rendering backend. + */ + [[nodiscard]] std::shared_ptr image_backend() const; + + [[nodiscard]] std::shared_ptr edit_image_backend(); + + /** + * @brief Returns the page backend (cached). + * @return Page rendering backend. + */ + [[nodiscard]] std::shared_ptr page_backend() const; + + [[nodiscard]] std::shared_ptr edit_page_backend(); + + private: + friend class DocraftDocumentContext; + + /** + * @brief Refreshes all cached backend interfaces (called internally). + * @param backend The main rendering backend. + */ + void refresh_caches(const std::shared_ptr &backend); + + std::shared_ptr line_backend_; + std::shared_ptr shape_backend_; + std::shared_ptr text_backend_; + std::shared_ptr image_backend_; + std::shared_ptr page_backend_; + }; +} + diff --git a/docraft/include/docraft/docraft_document.h b/docraft/include/docraft/docraft_document.h index d2e8c02..e122c7c 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -20,25 +20,30 @@ #include #include #include +#include #include "docraft/docraft_document_context.h" #include "docraft/docraft_document_metadata.h" +#include "docraft/management/docraft_document_config.h" +#include "docraft/management/docraft_document_query.h" #include "docraft/model/docraft_node.h" #include "docraft/model/docraft_settings.h" #include "docraft/templating/docraft_template_engine.h" #include "docraft/utils/docraft_keyword_extractor.h" namespace docraft { + // Keep enum in docraft namespace for backward compatibility enum class DocraftDomTraverseOp { kEnter, kExit }; /** - * @brief High-level document container that owns settings, title, and the DOM node list. + * @brief High-level document container that owns document configuration and the DOM node list. * * DocraftDocument is the primary API surface for building a document tree, - * configuring settings, and invoking rendering. + * and invoking rendering. Configuration (metadata, settings, keywords) is delegated + * to DocraftDocumentConfig for single responsibility. */ class DOCRAFT_LIB DocraftDocument { public: @@ -83,147 +88,90 @@ namespace docraft { void set_backend(const std::shared_ptr &backend); /** - * @brief Sets the document title. - * @param document_title New title value. - */ - void set_document_title(const std::string &document_title); - - /** - * @brief Returns the current document title. - * @return Document title string. + * @brief Returns the document DOM nodes. + * @return Vector of root nodes. */ - [[nodiscard]] const std::string &document_title() const; + [[nodiscard]] std::vector > nodes() const; - [[nodiscard]] std::string &edit_document_title(); + [[nodiscard]] std::vector > &edit_nodes(); /** - * @brief Sets the output directory where the rendered file will be saved. - * @param document_path Output directory path. + * @brief Traverses the document DOM and executes a callback on each node. + * @param callback Function called for each node and operation (enter/exit). */ - void set_document_path(const std::string &document_path); + void traverse_dom( + const std::function &, DocraftDomTraverseOp)> &callback) + const; /** - * @brief Returns the current output directory path. - * @return Output directory path. + * @brief Returns the document configuration manager. + * @return Reference to the configuration container. */ - [[nodiscard]] const std::string &document_path() const; + management::DocraftDocumentConfig &edit_config(); - [[nodiscard]] std::string &edit_document_path(); + [[nodiscard]] const management::DocraftDocumentConfig &config() const; /** - * @brief Sets document settings (fonts, etc.). - * @param settings Settings node to apply. + * @brief Returns the document context used for rendering. + * @return Shared pointer to the rendering context. */ - void set_settings(const std::shared_ptr &settings); + [[nodiscard]] std::shared_ptr edit_context(); - /** - * @brief Returns the current settings object. - * @return Shared pointer to settings or nullptr if not set. - */ - [[nodiscard]] std::shared_ptr settings() const; + [[nodiscard]] std::shared_ptr context() const; - [[nodiscard]] std::shared_ptr edit_settings(); + // Backward compatibility delegates to config_ + void set_document_title(const std::string &document_title); - /** - * @brief Sets document metadata values. - * @param metadata Metadata values supported by library. - */ - void set_document_metadata(const DocraftDocumentMetadata &metadata); + [[nodiscard]] const std::string &document_title() const; - /** - * @brief Returns current document metadata values. - * @return Metadata object. - */ - [[nodiscard]] const DocraftDocumentMetadata &document_metadata() const; + void set_document_path(const std::string &document_path); - /** - * @brief Enables or disables automatic keyword extraction for metadata. - * @param enabled true to enable, false to disable. - */ - void enable_auto_keywords(bool enabled = true); + [[nodiscard]] const std::string &document_path() const; - /** - * @brief Returns whether automatic keyword extraction is enabled. - */ - [[nodiscard]] bool auto_keywords_enabled() const; + void set_settings(const std::shared_ptr &settings); - /** - * @brief Sets configuration for automatic keyword extraction. - * @param config Extractor configuration. - */ - void set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config); + [[nodiscard]] std::shared_ptr settings() const; - /** - * @brief Returns the current automatic keyword extraction configuration. - */ - [[nodiscard]] const utils::DocraftKeywordExtractor::Config &auto_keywords_config() const; + void set_document_metadata(const DocraftDocumentMetadata &metadata); - /** - * @brief Extracts keywords from the current document and merges them into metadata. - * - * No-op when auto-keyword extraction is disabled. - */ - void refresh_auto_keywords(); + [[nodiscard]] const DocraftDocumentMetadata &document_metadata() const; - void set_document_template_engine(const std::shared_ptr &template_engine); + // Backward compatibility: DOM query delegates + [[nodiscard]] std::vector > find_by_name( + const std::string &name) const; - [[nodiscard]] std::shared_ptr document_template_engine() const; + [[nodiscard]] std::vector > take_by_name(const std::string &name); - [[nodiscard]] std::shared_ptr edit_document_template_engine(); + [[nodiscard]] std::shared_ptr find_first_by_name(const std::string &name) const; - /** - * @brief Returns the document DOM nodes. - * @return Vector of root nodes. - */ - [[nodiscard]] std::vector > nodes() const; + [[nodiscard]] std::shared_ptr take_first_by_name(const std::string &name); - [[nodiscard]] std::vector > &edit_nodes(); + [[nodiscard]] std::shared_ptr find_last_by_name(const std::string &name) const; - /** - * @brief Finds nodes by name in the document DOM. - * @param name Node name to search for. - * @return Vector of nodes matching the name, or empty vector if none found. - */ - std::vector > find_by_name(const std::string &name) const; + [[nodiscard]] std::shared_ptr take_last_by_name(const std::string &name); - std::vector > take_by_name(const std::string &name); + template + [[nodiscard]] std::vector > find_by_type() const; - /** - * @brief Finds the first node by name in the document DOM. - * @param name Node name to search for. - * @return Shared pointer to the first matching node, or nullptr if not found. - */ - std::shared_ptr find_first_by_name(const std::string &name) const; + template + [[nodiscard]] std::vector > take_by_type(); - std::shared_ptr take_first_by_name(const std::string &name); + // Backward compatibility: config shortcuts + void enable_auto_keywords(bool enabled = true); - /** - * @brief Finds the last node by name in the document DOM. - * @param name Node name to search for. - * @return Shared pointer to the last matching node, or nullptr if not found. - */ - std::shared_ptr find_last_by_name(const std::string &name) const; + [[nodiscard]] bool auto_keywords_enabled() const; - std::shared_ptr take_last_by_name(const std::string &name); + void set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config); - /** - * @brief Finds nodes by type in the document DOM. - * @tparam T Node type to search for. - * @return Vector of nodes matching the type, or empty vector if none found. - */ - template - std::vector > find_by_type() const; + [[nodiscard]] const utils::DocraftKeywordExtractor::Config &auto_keywords_config() const; - template - std::vector > take_by_type(); + void set_document_template_engine(const std::shared_ptr &template_engine); - /** - * @brief Traverses the document DOM and executes a callback on each node. - * @param callback Function called for each node and operation (enter/exit). - */ - void traverse_dom( - const std::function &, DocraftDomTraverseOp)> &callback) - const; + [[nodiscard]] std::shared_ptr document_template_engine() const; + + [[nodiscard]] std::shared_ptr edit_document_template_engine(); + + void refresh_auto_keywords(); private: void traverse_node( @@ -231,18 +179,9 @@ namespace docraft { const std::function &, DocraftDomTraverseOp)> &callback) const; - std::vector > find_by_name_impl(const std::string &name) const; - std::shared_ptr context_; - std::shared_ptr settings_; - std::string document_title_; - std::string document_path_; - DocraftDocumentMetadata metadata_; - bool auto_keywords_enabled_ = false; - utils::DocraftKeywordExtractor::Config auto_keywords_config_{}; - std::shared_ptr backend_override_ = nullptr; std::vector > dom_; - std::shared_ptr template_engine_; + management::DocraftDocumentConfig config_; }; } diff --git a/docraft/include/docraft/docraft_document.hpp b/docraft/include/docraft/docraft_document.hpp index f64bc37..d99842c 100644 --- a/docraft/include/docraft/docraft_document.hpp +++ b/docraft/include/docraft/docraft_document.hpp @@ -1,12 +1,11 @@ #pragma once -#include -#include +#include "docraft/model/docraft_node.h" namespace docraft { - template - std::vector> DocraftDocument::find_by_type() const { - std::vector> result; + template + std::vector > DocraftDocument::find_by_type() const { + std::vector > result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { return; @@ -18,9 +17,9 @@ namespace docraft { return result; } - template - std::vector> DocraftDocument::take_by_type() { - std::vector> result; + template + std::vector > DocraftDocument::take_by_type() { + std::vector > result; traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { if (op != DocraftDomTraverseOp::kEnter) { return; @@ -32,3 +31,5 @@ namespace docraft { return result; } } + + diff --git a/docraft/include/docraft/docraft_document_config.h b/docraft/include/docraft/docraft_document_config.h new file mode 100644 index 0000000..c9afbe7 --- /dev/null +++ b/docraft/include/docraft/docraft_document_config.h @@ -0,0 +1,142 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include +#include + +#include "docraft/docraft_document_metadata.h" +#include "docraft/utils/docraft_keyword_extractor.h" + +namespace docraft::model { + class DocraftSettings; +} + +namespace docraft::templating { + class DocraftTemplateEngine; +} + +namespace docraft { + /** + * @brief Manages document metadata, settings, and configuration. + * + * Handles title, path, metadata, keywords, settings, and template engine. + */ + class DOCRAFT_LIB DocraftDocumentConfig { + public: + /** + * @brief Sets the document title. + * @param document_title New title value. + */ + void set_document_title(const std::string &document_title); + + /** + * @brief Returns the current document title. + * @return Document title string. + */ + [[nodiscard]] const std::string &document_title() const; + + [[nodiscard]] std::string &edit_document_title(); + + /** + * @brief Sets the output directory where the rendered file will be saved. + * @param document_path Output directory path. + */ + void set_document_path(const std::string &document_path); + + /** + * @brief Returns the current output directory path. + * @return Output directory path. + */ + [[nodiscard]] const std::string &document_path() const; + + [[nodiscard]] std::string &edit_document_path(); + + /** + * @brief Sets document settings (fonts, etc.). + * @param settings Settings node to apply. + */ + void set_settings(const std::shared_ptr &settings); + + /** + * @brief Returns the current settings object. + * @return Shared pointer to settings or nullptr if not set. + */ + [[nodiscard]] std::shared_ptr settings() const; + + [[nodiscard]] std::shared_ptr edit_settings(); + + /** + * @brief Sets document metadata values. + * @param metadata Metadata values supported by library. + */ + void set_document_metadata(const DocraftDocumentMetadata &metadata); + + /** + * @brief Returns current document metadata values. + * @return Metadata object. + */ + [[nodiscard]] const DocraftDocumentMetadata &document_metadata() const; + + /** + * @brief Enables or disables automatic keyword extraction for metadata. + * @param enabled true to enable, false to disable. + */ + void enable_auto_keywords(bool enabled = true); + + /** + * @brief Returns whether automatic keyword extraction is enabled. + */ + [[nodiscard]] bool auto_keywords_enabled() const; + + /** + * @brief Sets configuration for automatic keyword extraction. + * @param config Extractor configuration. + */ + void set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config); + + /** + * @brief Returns the current automatic keyword extraction configuration. + */ + [[nodiscard]] const utils::DocraftKeywordExtractor::Config &auto_keywords_config() const; + + /** + * @brief Extracts keywords from a set of nodes and merges them into metadata. + * @param nodes The nodes to extract keywords from (typically document DOM). + * + * No-op when auto-keyword extraction is disabled. + */ + void refresh_auto_keywords(const std::vector > &nodes); + + void set_document_template_engine(const std::shared_ptr &template_engine); + + [[nodiscard]] std::shared_ptr document_template_engine() const; + + [[nodiscard]] std::shared_ptr edit_document_template_engine(); + + private: + std::string document_title_ = "Untitled Document"; + std::string document_path_; + std::shared_ptr settings_; + DocraftDocumentMetadata metadata_; + bool auto_keywords_enabled_ = false; + utils::DocraftKeywordExtractor::Config auto_keywords_config_{}; + std::shared_ptr template_engine_; + }; +} + diff --git a/docraft/include/docraft/docraft_document_context.h b/docraft/include/docraft/docraft_document_context.h index f94ab44..75bbcb8 100644 --- a/docraft/include/docraft/docraft_document_context.h +++ b/docraft/include/docraft/docraft_document_context.h @@ -23,6 +23,8 @@ #include "docraft/backend/docraft_rendering_backend.h" #include "docraft/generic/docraft_font_applier.h" #include "docraft/model/docraft_page_format.h" +#include "docraft/management/docraft_backend_cache.h" +#include "docraft/management/docraft_document_section_manager.h" namespace docraft { namespace renderer { @@ -37,8 +39,8 @@ namespace docraft { /** * @brief Shared rendering and layout state for a document. * - * The context holds the active rendering backend, cached backend interfaces, - * page metrics, section nodes, and the layout cursor used by the engine. + * The context holds the active rendering backend, page metrics, cursors, and delegates + * section management and backend caching to specialized helper classes. */ class DOCRAFT_LIB DocraftDocumentContext { public: @@ -62,21 +64,25 @@ namespace docraft { */ [[nodiscard]] std::shared_ptr rendering_backend() const; [[nodiscard]] std::shared_ptr edit_rendering_backend(); + /** - * @brief Returns the mutable layout cursor. + * @brief Returns the layout cursor. * @return Reference to the cursor. */ DocraftCursor& cursor(); + /** * @brief Returns remaining vertical space on the current page section. * @return Available vertical space in points. */ float available_space() const; + /** * @brief Sets the renderer responsible for translating nodes to backend calls. * @param renderer Renderer instance. */ void set_renderer(const std::shared_ptr &renderer); + /** * @brief Returns the current renderer. * @return Shared pointer to the renderer (may be nullptr). @@ -88,152 +94,111 @@ namespace docraft { * @param x Width in points. */ void set_current_rect_width(float x); + /** * @brief Returns the page width in points. * @return Page width in points. */ [[nodiscard]] float page_width() const; + /** * @brief Returns the page height in points. * @return Page height in points. */ [[nodiscard]] float page_height() const; - /** - * @brief Sets the document header node. - * @param header Header node. - */ - void set_header(const std::shared_ptr &header); - /** - * @brief Returns the header node. - * @return Header node (may be nullptr). - */ - [[nodiscard]] std::shared_ptr header() const; - [[nodiscard]] std::shared_ptr edit_header(); - /** - * @brief Sets the document body node. - * @param body Body node. - */ - void set_body(const std::shared_ptr &body); - /** - * @brief Returns the body node. - * @return Body node (may be nullptr). - */ - [[nodiscard]] std::shared_ptr body() const; - [[nodiscard]] std::shared_ptr edit_body(); - /** - * @brief Sets the document footer node. - * @param footer Footer node. - */ - void set_footer(const std::shared_ptr &footer); - /** - * @brief Returns the footer node. - * @return Footer node (may be nullptr). - */ - [[nodiscard]] std::shared_ptr footer() const; - [[nodiscard]] std::shared_ptr edit_footer(); - /** - * @brief Sets the font applier used for text nodes. - * @param font_applier Font applier instance. - */ - void set_font_applier(const std::shared_ptr& font_applier); + /** * @brief Returns the font applier instance. * @return Font applier (may be nullptr). */ [[nodiscard]] std::shared_ptr font_applier() const; [[nodiscard]] std::shared_ptr edit_font_applier(); + /** - * @brief Returns the line backend (cached). - * @return Line rendering backend. - */ - [[nodiscard]] std::shared_ptr line_backend() const; - [[nodiscard]] std::shared_ptr edit_line_backend(); - /** - * @brief Returns the shape backend (cached). - * @return Shape rendering backend. - */ - [[nodiscard]] std::shared_ptr shape_backend() const; - [[nodiscard]] std::shared_ptr edit_shape_backend(); - /** - * @brief Returns the text backend (cached). - * @return Text rendering backend. - */ - [[nodiscard]] std::shared_ptr text_backend() const; - [[nodiscard]] std::shared_ptr edit_text_backend(); - /** - * @brief Returns the image backend (cached). - * @return Image rendering backend. - */ - [[nodiscard]] std::shared_ptr image_backend() const; - [[nodiscard]] std::shared_ptr edit_image_backend(); - /** - * @brief Returns the page backend (cached). - * @return Page rendering backend. + * @brief Sets the font applier used for text nodes. + * @param font_applier Font applier instance. */ - [[nodiscard]] std::shared_ptr page_backend() const; - [[nodiscard]] std::shared_ptr edit_page_backend(); + void set_font_applier(const std::shared_ptr& font_applier); + /** * @brief Replaces the underlying rendering backend. * @param backend New rendering backend. Pass nullptr to restore the default backend. */ void set_backend(const std::shared_ptr& backend); + /** * @brief Sets the page format for the backend and updates cached size. */ void set_page_format(model::DocraftPageSize size, model::DocraftPageOrientation orientation); + /** * @brief Moves to the first page (index 0). */ void go_to_first_page(); + /** * @brief Moves to the previous page. */ void go_to_previous_page(); + /** * @brief Moves to the last page. */ void go_to_last_page(); + /** - * @brief Sets header/body/footer ratios. + * @brief Returns the section manager for header/body/footer. */ - void set_section_ratios(float header_ratio, float body_ratio, float footer_ratio); + management::DocraftDocumentSectionManager §ion_manager(); + [[nodiscard]] const management::DocraftDocumentSectionManager §ion_manager() const; + /** - * @brief Returns the header ratio. + * @brief Returns the backend cache manager. */ + management::DocraftBackendCache &backend_cache(); + [[nodiscard]] const management::DocraftBackendCache &backend_cache() const; + + // Backward compatibility: delegate to backend_cache() + [[nodiscard]] std::shared_ptr line_backend() const; + [[nodiscard]] std::shared_ptr edit_line_backend(); + [[nodiscard]] std::shared_ptr shape_backend() const; + [[nodiscard]] std::shared_ptr edit_shape_backend(); + [[nodiscard]] std::shared_ptr text_backend() const; + [[nodiscard]] std::shared_ptr edit_text_backend(); + [[nodiscard]] std::shared_ptr image_backend() const; + [[nodiscard]] std::shared_ptr edit_image_backend(); + [[nodiscard]] std::shared_ptr page_backend() const; + [[nodiscard]] std::shared_ptr edit_page_backend(); + + // Backward compatibility: delegate to section_manager() + void set_header(const std::shared_ptr &header); + [[nodiscard]] std::shared_ptr header() const; + [[nodiscard]] std::shared_ptr edit_header(); + void set_body(const std::shared_ptr &body); + [[nodiscard]] std::shared_ptr body() const; + [[nodiscard]] std::shared_ptr edit_body(); + void set_footer(const std::shared_ptr &footer); + [[nodiscard]] std::shared_ptr footer() const; + [[nodiscard]] std::shared_ptr edit_footer(); + void set_section_ratios(float header_ratio, float body_ratio, float footer_ratio); [[nodiscard]] float header_ratio() const; - /** - * @brief Returns the body ratio. - */ [[nodiscard]] float body_ratio() const; - /** - * @brief Returns the footer ratio. - */ [[nodiscard]] float footer_ratio() const; private: /** - * @brief Refreshes cached backend interfaces from the main backend. - * Called after backend initialization or replacement. + * @brief Refreshes all backend caches (called after backend changes). */ - void refresh_backend_caches_(); + void refresh_backend_caches(); DocraftCursor cursor_; float current_rect_width_=0; std::shared_ptr renderer_; float page_width_; float page_height_; - std::shared_ptr header_; - std::shared_ptr body_; - std::shared_ptr footer_; std::shared_ptr font_applier_; std::shared_ptr backend_; - std::shared_ptr line_backend_; - std::shared_ptr shape_backend_; - std::shared_ptr text_backend_; - std::shared_ptr image_backend_; - std::shared_ptr page_backend_; - float header_ratio_ = 0.06F; - float body_ratio_ = 0.88F; - float footer_ratio_ = 0.06F; + management::DocraftDocumentSectionManager section_manager_; + management::DocraftBackendCache backend_cache_; }; } // docraft diff --git a/docraft/include/docraft/docraft_document_query.h b/docraft/include/docraft/docraft_document_query.h new file mode 100644 index 0000000..e69de29 diff --git a/docraft/include/docraft/docraft_document_section_manager.h b/docraft/include/docraft/docraft_document_section_manager.h new file mode 100644 index 0000000..d84e876 --- /dev/null +++ b/docraft/include/docraft/docraft_document_section_manager.h @@ -0,0 +1,105 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include + +namespace docraft::model { + class DocraftHeader; + class DocraftBody; + class DocraftFooter; +} + +namespace docraft { + /** + * @brief Manages document sections (header, body, footer) and their ratios. + */ + class DOCRAFT_LIB DocraftDocumentSectionManager { + public: + /** + * @brief Sets the document header node. + * @param header Header node. + */ + void set_header(const std::shared_ptr &header); + + /** + * @brief Returns the header node. + * @return Header node (may be nullptr). + */ + [[nodiscard]] std::shared_ptr header() const; + + [[nodiscard]] std::shared_ptr edit_header(); + + /** + * @brief Sets the document body node. + * @param body Body node. + */ + void set_body(const std::shared_ptr &body); + + /** + * @brief Returns the body node. + * @return Body node (may be nullptr). + */ + [[nodiscard]] std::shared_ptr body() const; + + [[nodiscard]] std::shared_ptr edit_body(); + + /** + * @brief Sets the document footer node. + * @param footer Footer node. + */ + void set_footer(const std::shared_ptr &footer); + + /** + * @brief Returns the footer node. + * @return Footer node (may be nullptr). + */ + [[nodiscard]] std::shared_ptr footer() const; + + [[nodiscard]] std::shared_ptr edit_footer(); + + /** + * @brief Sets header/body/footer ratios. + */ + void set_section_ratios(float header_ratio, float body_ratio, float footer_ratio); + + /** + * @brief Returns the header ratio. + */ + [[nodiscard]] float header_ratio() const; + + /** + * @brief Returns the body ratio. + */ + [[nodiscard]] float body_ratio() const; + + /** + * @brief Returns the footer ratio. + */ + [[nodiscard]] float footer_ratio() const; + + private: + std::shared_ptr header_; + std::shared_ptr body_; + std::shared_ptr footer_; + float header_ratio_ = 0.06F; + float body_ratio_ = 0.88F; + float footer_ratio_ = 0.06F; + }; +} + diff --git a/docraft/include/docraft/management/docraft_backend_cache.h b/docraft/include/docraft/management/docraft_backend_cache.h new file mode 100644 index 0000000..5188815 --- /dev/null +++ b/docraft/include/docraft/management/docraft_backend_cache.h @@ -0,0 +1,90 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include + +#include "docraft/backend/docraft_rendering_backend.h" + +namespace docraft::management { + /** + * @brief Manages cached rendering backend interfaces. + * + * Provides access to specific backend interfaces (line, shape, text, image, page) + * derived from the main rendering backend. These are cached for performance. + */ + class DOCRAFT_LIB DocraftBackendCache { + public: + /** + * @brief Initializes the backend cache from a main rendering backend. + * @param backend The main rendering backend. + */ + void initialize_from_backend(const std::shared_ptr& backend); + + /** + * @brief Returns the line backend (cached). + * @return Line rendering backend. + */ + [[nodiscard]] std::shared_ptr line_backend() const; + [[nodiscard]] std::shared_ptr edit_line_backend(); + + /** + * @brief Returns the shape backend (cached). + * @return Shape rendering backend. + */ + [[nodiscard]] std::shared_ptr shape_backend() const; + [[nodiscard]] std::shared_ptr edit_shape_backend(); + + /** + * @brief Returns the text backend (cached). + * @return Text rendering backend. + */ + [[nodiscard]] std::shared_ptr text_backend() const; + [[nodiscard]] std::shared_ptr edit_text_backend(); + + /** + * @brief Returns the image backend (cached). + * @return Image rendering backend. + */ + [[nodiscard]] std::shared_ptr image_backend() const; + [[nodiscard]] std::shared_ptr edit_image_backend(); + + /** + * @brief Returns the page backend (cached). + * @return Page rendering backend. + */ + [[nodiscard]] std::shared_ptr page_backend() const; + [[nodiscard]] std::shared_ptr edit_page_backend(); + + private: + friend class DocraftDocumentContext; + + /** + * @brief Refreshes all cached backend interfaces (called internally). + * @param backend The main rendering backend. + */ + void refresh_caches(const std::shared_ptr& backend); + + std::shared_ptr line_backend_; + std::shared_ptr shape_backend_; + std::shared_ptr text_backend_; + std::shared_ptr image_backend_; + std::shared_ptr page_backend_; + }; +} + diff --git a/docraft/include/docraft/management/docraft_document_config.h b/docraft/include/docraft/management/docraft_document_config.h new file mode 100644 index 0000000..28494ba --- /dev/null +++ b/docraft/include/docraft/management/docraft_document_config.h @@ -0,0 +1,144 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include +#include +#include + +#include "docraft/docraft_document_metadata.h" +#include "docraft/utils/docraft_keyword_extractor.h" + +namespace docraft::model { + class DocraftSettings; + class DocraftNode; +} + +namespace docraft::templating { + class DocraftTemplateEngine; +} + +namespace docraft::management { + /** + * @brief Manages document metadata, settings, and configuration. + * + * Handles title, path, metadata, keywords, settings, and template engine. + */ + class DOCRAFT_LIB DocraftDocumentConfig { + public: + /** + * @brief Sets the document title. + * @param document_title New title value. + */ + void set_document_title(const std::string &document_title); + + /** + * @brief Returns the current document title. + * @return Document title string. + */ + [[nodiscard]] const std::string &document_title() const; + + [[nodiscard]] std::string &edit_document_title(); + + /** + * @brief Sets the output directory where the rendered file will be saved. + * @param document_path Output directory path. + */ + void set_document_path(const std::string &document_path); + + /** + * @brief Returns the current output directory path. + * @return Output directory path. + */ + [[nodiscard]] const std::string &document_path() const; + + [[nodiscard]] std::string &edit_document_path(); + + /** + * @brief Sets document settings (fonts, etc.). + * @param settings Settings node to apply. + */ + void set_settings(const std::shared_ptr &settings); + + /** + * @brief Returns the current settings object. + * @return Shared pointer to settings or nullptr if not set. + */ + [[nodiscard]] std::shared_ptr settings() const; + + [[nodiscard]] std::shared_ptr edit_settings(); + + /** + * @brief Sets document metadata values. + * @param metadata Metadata values supported by library. + */ + void set_document_metadata(const DocraftDocumentMetadata &metadata); + + /** + * @brief Returns current document metadata values. + * @return Metadata object. + */ + [[nodiscard]] const DocraftDocumentMetadata &document_metadata() const; + + /** + * @brief Enables or disables automatic keyword extraction for metadata. + * @param enabled true to enable, false to disable. + */ + void enable_auto_keywords(bool enabled = true); + + /** + * @brief Returns whether automatic keyword extraction is enabled. + */ + [[nodiscard]] bool auto_keywords_enabled() const; + + /** + * @brief Sets configuration for automatic keyword extraction. + * @param config Extractor configuration. + */ + void set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config); + + /** + * @brief Returns the current automatic keyword extraction configuration. + */ + [[nodiscard]] const utils::DocraftKeywordExtractor::Config &auto_keywords_config() const; + + /** + * @brief Extracts keywords from a set of nodes and merges them into metadata. + * @param nodes The nodes to extract keywords from (typically document DOM). + * + * No-op when auto-keyword extraction is disabled. + */ + void refresh_auto_keywords(const std::vector > &nodes); + + void set_document_template_engine(const std::shared_ptr &template_engine); + + [[nodiscard]] std::shared_ptr document_template_engine() const; + + [[nodiscard]] std::shared_ptr edit_document_template_engine(); + + private: + std::string document_title_ = "Untitled Document"; + std::string document_path_; + std::shared_ptr settings_; + DocraftDocumentMetadata metadata_; + bool auto_keywords_enabled_ = false; + utils::DocraftKeywordExtractor::Config auto_keywords_config_{}; + std::shared_ptr template_engine_; + }; +} + diff --git a/docraft/include/docraft/management/docraft_document_query.h b/docraft/include/docraft/management/docraft_document_query.h new file mode 100644 index 0000000..c2f5bf9 --- /dev/null +++ b/docraft/include/docraft/management/docraft_document_query.h @@ -0,0 +1,112 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include +#include +#include +#include + +namespace docraft::model { + class DocraftNode; +} + +namespace docraft::management { + enum class DocraftDomTraverseOp { + kEnter, + kExit + }; + + /** + * @brief Provides query and traversal operations on document DOM trees. + * + * Separate responsibility for finding nodes and traversing the DOM. + */ + class DOCRAFT_LIB DocraftDocumentQuery { + public: + /** + * @brief Finds nodes by name in a DOM tree. + * @param root The root nodes to start searching from. + * @param name Node name to search for. + * @return Vector of nodes matching the name, or empty vector if none found. + */ + static std::vector > find_by_name( + const std::vector > &root, const std::string &name); + + static std::vector > take_by_name( + std::vector > &root, const std::string &name); + + /** + * @brief Finds the first node by name in a DOM tree. + * @param root The root nodes to start searching from. + * @param name Node name to search for. + * @return Shared pointer to the first matching node, or nullptr if not found. + */ + static std::shared_ptr find_first_by_name( + const std::vector > &root, const std::string &name); + + static std::shared_ptr take_first_by_name( + std::vector > &root, const std::string &name); + + /** + * @brief Finds the last node by name in a DOM tree. + * @param root The root nodes to start searching from. + * @param name Node name to search for. + * @return Shared pointer to the last matching node, or nullptr if not found. + */ + static std::shared_ptr find_last_by_name( + const std::vector > &root, const std::string &name); + + static std::shared_ptr take_last_by_name( + std::vector > &root, const std::string &name); + + /** + * @brief Finds nodes by type in a DOM tree. + * @tparam T Node type to search for. + * @param root The root nodes to start searching from. + * @return Vector of nodes matching the type, or empty vector if none found. + */ + template + static std::vector > find_by_type( + const std::vector > &root); + + template + static std::vector > take_by_type( + std::vector > &root); + + /** + * @brief Traverses a DOM tree and executes a callback on each node. + * @param root The root nodes to traverse. + * @param callback Function called for each node and operation (enter/exit). + */ + static void traverse_dom( + const std::vector > &root, + const std::function &, DocraftDomTraverseOp)> &callback); + + private: + static void traverse_node( + const std::shared_ptr &node, + const std::function &, DocraftDomTraverseOp)> &callback); + + static std::vector > find_by_name_impl( + const std::vector > &root, const std::string &name); + }; +} + +#include "docraft_document_query.hpp" + diff --git a/docraft/include/docraft/management/docraft_document_query.hpp b/docraft/include/docraft/management/docraft_document_query.hpp new file mode 100644 index 0000000..67f341e --- /dev/null +++ b/docraft/include/docraft/management/docraft_document_query.hpp @@ -0,0 +1,53 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft_document_query.h" +#include "docraft/model/docraft_node.h" + +namespace docraft::management { + template + std::vector > DocraftDocumentQuery::find_by_type( + const std::vector > &root) { + std::vector > result; + traverse_dom(root, [&](const std::shared_ptr &node, DocraftDomTraverseOp op) { + if (op != DocraftDomTraverseOp::kEnter) { + return; + } + if (auto casted = std::dynamic_pointer_cast(node)) { + result.push_back(casted); + } + }); + return result; + } + + template + std::vector > DocraftDocumentQuery::take_by_type( + std::vector > &root) { + std::vector > result; + traverse_dom(root, [&](const std::shared_ptr &node, DocraftDomTraverseOp op) { + if (op != DocraftDomTraverseOp::kEnter) { + return; + } + if (auto casted = std::dynamic_pointer_cast(node)) { + result.push_back(casted); + } + }); + return result; + } +} + diff --git a/docraft/include/docraft/management/docraft_document_section_manager.h b/docraft/include/docraft/management/docraft_document_section_manager.h new file mode 100644 index 0000000..c19aa57 --- /dev/null +++ b/docraft/include/docraft/management/docraft_document_section_manager.h @@ -0,0 +1,102 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "docraft/docraft_lib.h" +#include + +namespace docraft::model { + class DocraftHeader; + class DocraftBody; + class DocraftFooter; +} + +namespace docraft::management { + /** + * @brief Manages document sections (header, body, footer) and their ratios. + */ + class DOCRAFT_LIB DocraftDocumentSectionManager { + public: + /** + * @brief Sets the document header node. + * @param header Header node. + */ + void set_header(const std::shared_ptr &header); + + /** + * @brief Returns the header node. + * @return Header node (may be nullptr). + */ + [[nodiscard]] std::shared_ptr header() const; + [[nodiscard]] std::shared_ptr edit_header(); + + /** + * @brief Sets the document body node. + * @param body Body node. + */ + void set_body(const std::shared_ptr &body); + + /** + * @brief Returns the body node. + * @return Body node (may be nullptr). + */ + [[nodiscard]] std::shared_ptr body() const; + [[nodiscard]] std::shared_ptr edit_body(); + + /** + * @brief Sets the document footer node. + * @param footer Footer node. + */ + void set_footer(const std::shared_ptr &footer); + + /** + * @brief Returns the footer node. + * @return Footer node (may be nullptr). + */ + [[nodiscard]] std::shared_ptr footer() const; + [[nodiscard]] std::shared_ptr edit_footer(); + + /** + * @brief Sets header/body/footer ratios. + */ + void set_section_ratios(float header_ratio, float body_ratio, float footer_ratio); + + /** + * @brief Returns the header ratio. + */ + [[nodiscard]] float header_ratio() const; + + /** + * @brief Returns the body ratio. + */ + [[nodiscard]] float body_ratio() const; + + /** + * @brief Returns the footer ratio. + */ + [[nodiscard]] float footer_ratio() const; + + private: + std::shared_ptr header_; + std::shared_ptr body_; + std::shared_ptr footer_; + float header_ratio_ = 0.06F; + float body_ratio_ = 0.88F; + float footer_ratio_ = 0.06F; + }; +} + diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index 03616e5..3c3c459 100644 --- a/docraft/src/docraft/docraft_document.cc +++ b/docraft/src/docraft/docraft_document.cc @@ -32,6 +32,8 @@ #include "docraft/renderer/docraft_pdf_renderer.h" #include "docraft/utils/docraft_font_registry.h" #include "docraft/utils/docraft_logger.h" +#include "docraft/model/docraft_settings.h" +#include "docraft/templating/docraft_template_engine.h" namespace docraft { namespace { @@ -175,7 +177,6 @@ namespace docraft { } } } - bool ok = utils::DocraftFontRegistry::instance().register_font( external_font.name, resolved_path.string()); if (!ok) { @@ -190,8 +191,8 @@ namespace docraft { } } // namespace - DocraftDocument::DocraftDocument(std::string document_title) : document_title_(std::move(document_title)) { - metadata_.set_title(document_title_); + DocraftDocument::DocraftDocument(std::string document_title) { + config_.set_document_title(document_title); context_ = std::make_shared(); } @@ -203,36 +204,39 @@ namespace docraft { } void DocraftDocument::configure_document_settings() { - apply_page_format_settings(settings_, context_); - apply_section_ratio_settings(settings_, context_); - apply_font_settings(settings_); + apply_page_format_settings(config_.edit_settings(), context_); + apply_section_ratio_settings(config_.edit_settings(), context_); + apply_font_settings(config_.edit_settings()); } void DocraftDocument::template_document() { - if (template_engine_) { - LOG_DEBUG("Template document: '" + document_title_ + "'"); - template_engine_->template_nodes(dom_); + if (config_.edit_document_template_engine()) { + LOG_DEBUG("Template document: '" + config_.document_title() + "'"); + config_.edit_document_template_engine()->template_nodes(dom_); } } void DocraftDocument::render() { - context_->set_backend(backend_override_); context_->set_renderer(std::make_shared(context_)); context_->set_font_applier(std::make_shared(context_)); - LOG_DEBUG("Rendering document: " + document_title_); + LOG_DEBUG("Rendering document: " + config_.document_title()); - //Load settings configure_document_settings(); - - //replace template variables in the DOM template_document(); - refresh_auto_keywords(); - // Layout phase + if (config_.auto_keywords_enabled()) { + utils::DocraftKeywordExtractor extractor(config_.auto_keywords_config()); + const std::vector extracted_keywords = extractor.extract(*this); + if (!extracted_keywords.empty()) { + auto metadata = config_.document_metadata(); + metadata.set_keywords(merge_keywords(metadata.keywords(), extracted_keywords)); + config_.set_document_metadata(metadata); + } + } + layout::DocraftLayoutEngine layout_engine(context_); layout_engine.compute_document_layout(dom_); - // Rendering phase const auto page_backend = context_->edit_page_backend(); if (page_backend) { page_backend->go_to_first_page(); @@ -260,183 +264,165 @@ namespace docraft { } } - context_->edit_rendering_backend()->set_document_metadata(metadata_); + context_->edit_rendering_backend()->set_document_metadata(config_.document_metadata()); const std::string output_file_name = with_extension( - document_title_, context_->rendering_backend()->file_extension()); - context_->rendering_backend()->save_to_file(with_directory(document_path_, output_file_name)); + config_.document_title(), context_->rendering_backend()->file_extension()); + context_->rendering_backend()->save_to_file(with_directory(config_.document_path(), output_file_name)); } void DocraftDocument::set_backend(const std::shared_ptr &backend) { - backend_override_ = backend; - context_->set_backend(backend_override_); + context_->set_backend(backend); } - void DocraftDocument::set_document_title(const std::string &document_title) { - document_title_ = document_title; - metadata_.set_title(document_title); + std::vector> DocraftDocument::nodes() const { + return docraft::to_const_shared_vector(dom_); } - void DocraftDocument::set_document_path(const std::string &document_path) { - document_path_ = document_path; + std::vector> &DocraftDocument::edit_nodes() { + return dom_; } - const std::string &DocraftDocument::document_title() const { - return document_title_; + void DocraftDocument::traverse_dom( + const std::function &, DocraftDomTraverseOp)> &callback) const { + for (const auto &node: dom_) { + traverse_node(node, callback); + } } - std::string &DocraftDocument::edit_document_title() { - return document_title_; + void DocraftDocument::traverse_node( + const std::shared_ptr &node, + const std::function &, DocraftDomTraverseOp)> &callback) const { + if (!node) { + return; + } + callback(node, DocraftDomTraverseOp::kEnter); + if (auto parent_node = std::dynamic_pointer_cast(node)) { + for (const auto &child_node: parent_node->children()) { + traverse_node(child_node, callback); + } + } + callback(node, DocraftDomTraverseOp::kExit); } - const std::string &DocraftDocument::document_path() const { - return document_path_; + management::DocraftDocumentConfig &DocraftDocument::edit_config() { + return config_; } - std::string &DocraftDocument::edit_document_path() { - return document_path_; + const management::DocraftDocumentConfig &DocraftDocument::config() const { + return config_; } - std::shared_ptr DocraftDocument::settings() const { - return settings_; + std::shared_ptr DocraftDocument::edit_context() { + return context_; } - std::shared_ptr DocraftDocument::edit_settings() { - return settings_; + std::shared_ptr DocraftDocument::context() const { + return context_; } - void DocraftDocument::set_settings(const std::shared_ptr &settings) { - settings_ = settings; + // Backward compatibility delegates to config_ + void DocraftDocument::set_document_title(const std::string &document_title) { + config_.set_document_title(document_title); } - void DocraftDocument::set_document_metadata(const DocraftDocumentMetadata &metadata) { - metadata_ = metadata; - if (metadata_.title().has_value()) { - document_title_ = metadata_.title().value(); - } else { - metadata_.set_title(document_title_); - } + const std::string &DocraftDocument::document_title() const { + return config_.document_title(); } - const DocraftDocumentMetadata &DocraftDocument::document_metadata() const { - return metadata_; + void DocraftDocument::set_document_path(const std::string &document_path) { + config_.set_document_path(document_path); } - void DocraftDocument::enable_auto_keywords(bool enabled) { - auto_keywords_enabled_ = enabled; + const std::string &DocraftDocument::document_path() const { + return config_.document_path(); } - bool DocraftDocument::auto_keywords_enabled() const { - return auto_keywords_enabled_; + void DocraftDocument::set_settings(const std::shared_ptr &settings) { + config_.set_settings(settings); } - void DocraftDocument::set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config) { - auto_keywords_config_ = config; + std::shared_ptr DocraftDocument::settings() const { + return config_.settings(); } - const utils::DocraftKeywordExtractor::Config &DocraftDocument::auto_keywords_config() const { - return auto_keywords_config_; + void DocraftDocument::set_document_metadata(const DocraftDocumentMetadata &metadata) { + config_.set_document_metadata(metadata); } - void DocraftDocument::refresh_auto_keywords() { - if (!auto_keywords_enabled_) { - return; - } - utils::DocraftKeywordExtractor extractor(auto_keywords_config_); - const std::vector extracted_keywords = extractor.extract(*this); - if (extracted_keywords.empty()) { - return; - } - - DocraftDocumentMetadata metadata = metadata_; - metadata.set_keywords(merge_keywords(metadata.keywords(), extracted_keywords)); - set_document_metadata(metadata); + const DocraftDocumentMetadata &DocraftDocument::document_metadata() const { + return config_.document_metadata(); } - void DocraftDocument::set_document_template_engine( - const std::shared_ptr &template_engine) { - template_engine_ = template_engine; + // Backward compatibility: DOM query methods + std::vector > + DocraftDocument::find_by_name(const std::string &name) const { + return management::DocraftDocumentQuery::find_by_name( + const_cast > &>(dom_), name); } - std::shared_ptr DocraftDocument::document_template_engine() const { - return template_engine_; + std::vector > DocraftDocument::take_by_name(const std::string &name) { + return management::DocraftDocumentQuery::take_by_name(dom_, name); } - std::shared_ptr DocraftDocument::edit_document_template_engine() { - return template_engine_; + std::shared_ptr DocraftDocument::find_first_by_name(const std::string &name) const { + return management::DocraftDocumentQuery::find_first_by_name( + const_cast > &>(dom_), name); } - std::vector> DocraftDocument::nodes() const { - return docraft::to_const_shared_vector(dom_); + std::shared_ptr DocraftDocument::take_first_by_name(const std::string &name) { + return management::DocraftDocumentQuery::take_first_by_name(dom_, name); } - std::vector> &DocraftDocument::edit_nodes() { - return dom_; + std::shared_ptr DocraftDocument::find_last_by_name(const std::string &name) const { + return management::DocraftDocumentQuery::find_last_by_name( + const_cast > &>(dom_), name); } - std::vector> DocraftDocument::find_by_name(const std::string &name) const { - std::vector> result; - for (const auto &node: find_by_name_impl(name)) { - result.push_back(node); - } - return result; + std::shared_ptr DocraftDocument::take_last_by_name(const std::string &name) { + return management::DocraftDocumentQuery::take_last_by_name(dom_, name); } - std::vector> DocraftDocument::take_by_name(const std::string &name) { - return find_by_name_impl(name); + // Backward compatibility: config shortcuts + void DocraftDocument::enable_auto_keywords(bool enabled) { + config_.enable_auto_keywords(enabled); } - std::vector> DocraftDocument::find_by_name_impl(const std::string &name) const { - std::vector> result; - traverse_dom([&](const std::shared_ptr &node, DocraftDomTraverseOp op) { - if (op != DocraftDomTraverseOp::kEnter) { - return; - } - if (node && node->node_name() == name) { - result.push_back(node); - } - }); - return result; + bool DocraftDocument::auto_keywords_enabled() const { + return config_.auto_keywords_enabled(); } - std::shared_ptr DocraftDocument::find_first_by_name(const std::string &name) const { - const auto matches = find_by_name(name); - return matches.empty() ? nullptr : matches.front(); + void DocraftDocument::set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config) { + config_.set_auto_keywords_config(config); } - std::shared_ptr DocraftDocument::take_first_by_name(const std::string &name) { - const auto matches = take_by_name(name); - return matches.empty() ? nullptr : matches.front(); + const utils::DocraftKeywordExtractor::Config &DocraftDocument::auto_keywords_config() const { + return config_.auto_keywords_config(); } - std::shared_ptr DocraftDocument::find_last_by_name(const std::string &name) const { - const auto matches = find_by_name(name); - return matches.empty() ? nullptr : matches.back(); + void DocraftDocument::set_document_template_engine( + const std::shared_ptr &template_engine) { + config_.set_document_template_engine(template_engine); } - std::shared_ptr DocraftDocument::take_last_by_name(const std::string &name) { - const auto matches = take_by_name(name); - return matches.empty() ? nullptr : matches.back(); + std::shared_ptr DocraftDocument::document_template_engine() const { + return config_.document_template_engine(); } - void DocraftDocument::traverse_dom( - const std::function &, DocraftDomTraverseOp)> &callback) const { - for (const auto &node: dom_) { - traverse_node(node, callback); - } + std::shared_ptr DocraftDocument::edit_document_template_engine() { + return config_.edit_document_template_engine(); } - void DocraftDocument::traverse_node( - const std::shared_ptr &node, - const std::function &, DocraftDomTraverseOp)> &callback) const { - if (!node) { + void DocraftDocument::refresh_auto_keywords() { + if (!config_.auto_keywords_enabled()) { return; } - callback(node, DocraftDomTraverseOp::kEnter); - if (auto parent_node = std::dynamic_pointer_cast(node)) { - for (const auto &child_node: parent_node->children()) { - traverse_node(child_node, callback); - } + utils::DocraftKeywordExtractor extractor(config_.auto_keywords_config()); + const std::vector extracted_keywords = extractor.extract(*this); + if (extracted_keywords.empty()) { + return; } - callback(node, DocraftDomTraverseOp::kExit); + auto metadata = config_.document_metadata(); + metadata.set_keywords(merge_keywords(metadata.keywords(), extracted_keywords)); + config_.set_document_metadata(metadata); } } // docraft diff --git a/docraft/src/docraft/docraft_document_context.cc b/docraft/src/docraft/docraft_document_context.cc index 8607740..79e5252 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -16,6 +16,8 @@ #include "docraft/docraft_document_context.h" #include "docraft/backend/pdf/docraft_haru_backend.h" +#include "docraft/management/docraft_backend_cache.h" +#include "docraft/management/docraft_document_section_manager.h" namespace docraft { DocraftDocumentContext::DocraftDocumentContext() { @@ -23,7 +25,7 @@ namespace docraft { page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); current_rect_width_ = page_width_; - refresh_backend_caches_(); + refresh_backend_caches(); } DocraftDocumentContext::DocraftDocumentContext( @@ -32,7 +34,7 @@ namespace docraft { page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); current_rect_width_ = page_width_; - refresh_backend_caches_(); + refresh_backend_caches(); } DocraftDocumentContext::~DocraftDocumentContext() = default; @@ -45,28 +47,32 @@ namespace docraft { current_rect_width_ = current_rect_width; } - void DocraftDocumentContext::set_header(const std::shared_ptr &header) { - header_ = header; + std::shared_ptr DocraftDocumentContext::font_applier() const { + return font_applier_; } - void DocraftDocumentContext::set_body(const std::shared_ptr &body) { - body_ = body; + std::shared_ptr DocraftDocumentContext::edit_font_applier() { + return font_applier_; } - void DocraftDocumentContext::set_footer(const std::shared_ptr &footer) { - footer_ = footer; + void DocraftDocumentContext::refresh_backend_caches() { + backend_cache_.initialize_from_backend(backend_); } - void DocraftDocumentContext::set_font_applier(const std::shared_ptr &font_applier) { - font_applier_ = font_applier; + management::DocraftDocumentSectionManager &DocraftDocumentContext::section_manager() { + return section_manager_; + } + + const management::DocraftDocumentSectionManager &DocraftDocumentContext::section_manager() const { + return section_manager_; } - void DocraftDocumentContext::refresh_backend_caches_() { - docraft::ensure_lazy_backend(line_backend_, backend_); - docraft::ensure_lazy_backend(shape_backend_, backend_); - docraft::ensure_lazy_backend(text_backend_, backend_); - docraft::ensure_lazy_backend(image_backend_, backend_); - docraft::ensure_lazy_backend(page_backend_, backend_); + management::DocraftBackendCache &DocraftDocumentContext::backend_cache() { + return backend_cache_; + } + + const management::DocraftBackendCache &DocraftDocumentContext::backend_cache() const { + return backend_cache_; } void DocraftDocumentContext::set_backend(const std::shared_ptr &backend) { @@ -74,12 +80,12 @@ namespace docraft { page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); current_rect_width_ = page_width_; - refresh_backend_caches_(); + refresh_backend_caches(); } void DocraftDocumentContext::set_page_format(model::DocraftPageSize size, model::DocraftPageOrientation orientation) { - const auto backend = edit_page_backend(); + const auto backend = backend_cache_.edit_page_backend(); if (backend) { backend->set_page_format(size, orientation); page_height_ = backend->page_height(); @@ -87,6 +93,10 @@ namespace docraft { current_rect_width_ = page_width_; } } + + void DocraftDocumentContext::set_font_applier(const std::shared_ptr &font_applier) { + font_applier_ = font_applier; + } #pragma endregion #pragma region getter std::shared_ptr DocraftDocumentContext::rendering_backend() const { @@ -120,115 +130,119 @@ namespace docraft { return page_width_; } - std::shared_ptr DocraftDocumentContext::header() const { - return header_; - } - - std::shared_ptr DocraftDocumentContext::edit_header() { - return header_; - } - - std::shared_ptr DocraftDocumentContext::body() const { - return body_; - } - - std::shared_ptr DocraftDocumentContext::edit_body() { - return body_; - } - - std::shared_ptr DocraftDocumentContext::footer() const { - return footer_; - } - - std::shared_ptr DocraftDocumentContext::edit_footer() { - return footer_; + void DocraftDocumentContext::go_to_first_page() { + const auto backend = backend_cache_.edit_page_backend(); + if (backend) { + backend->go_to_first_page(); + } } - std::shared_ptr DocraftDocumentContext::font_applier() const { - return font_applier_; + void DocraftDocumentContext::go_to_previous_page() { + const auto backend = backend_cache_.edit_page_backend(); + if (backend) { + backend->go_to_previous_page(); + } } - std::shared_ptr DocraftDocumentContext::edit_font_applier() { - return font_applier_; + void DocraftDocumentContext::go_to_last_page() { + const auto backend = backend_cache_.edit_page_backend(); + if (backend) { + backend->go_to_last_page(); + } } + // Backward compatibility delegates to backend_cache() std::shared_ptr DocraftDocumentContext::line_backend() const { - return line_backend_; + return backend_cache_.line_backend(); } std::shared_ptr DocraftDocumentContext::edit_line_backend() { - return line_backend_; + return backend_cache_.edit_line_backend(); } std::shared_ptr DocraftDocumentContext::shape_backend() const { - return shape_backend_; + return backend_cache_.shape_backend(); } std::shared_ptr DocraftDocumentContext::edit_shape_backend() { - return shape_backend_; + return backend_cache_.edit_shape_backend(); } std::shared_ptr DocraftDocumentContext::text_backend() const { - return text_backend_; + return backend_cache_.text_backend(); } std::shared_ptr DocraftDocumentContext::edit_text_backend() { - return text_backend_; + return backend_cache_.edit_text_backend(); } std::shared_ptr DocraftDocumentContext::image_backend() const { - return image_backend_; + return backend_cache_.image_backend(); } std::shared_ptr DocraftDocumentContext::edit_image_backend() { - return image_backend_; + return backend_cache_.edit_image_backend(); } std::shared_ptr DocraftDocumentContext::page_backend() const { - return page_backend_; + return backend_cache_.page_backend(); } std::shared_ptr DocraftDocumentContext::edit_page_backend() { - return page_backend_; + return backend_cache_.edit_page_backend(); } - void DocraftDocumentContext::go_to_first_page() { - const auto backend = edit_page_backend(); - if (backend) { - backend->go_to_first_page(); - } + // Backward compatibility delegates to section_manager() + void DocraftDocumentContext::set_header(const std::shared_ptr &header) { + section_manager_.set_header(header); } - void DocraftDocumentContext::go_to_previous_page() { - const auto backend = edit_page_backend(); - if (backend) { - backend->go_to_previous_page(); - } + std::shared_ptr DocraftDocumentContext::header() const { + return section_manager_.header(); } - void DocraftDocumentContext::go_to_last_page() { - const auto backend = edit_page_backend(); - if (backend) { - backend->go_to_last_page(); - } + std::shared_ptr DocraftDocumentContext::edit_header() { + return section_manager_.edit_header(); + } + + void DocraftDocumentContext::set_body(const std::shared_ptr &body) { + section_manager_.set_body(body); + } + + std::shared_ptr DocraftDocumentContext::body() const { + return section_manager_.body(); + } + + std::shared_ptr DocraftDocumentContext::edit_body() { + return section_manager_.edit_body(); + } + + void DocraftDocumentContext::set_footer(const std::shared_ptr &footer) { + section_manager_.set_footer(footer); + } + + std::shared_ptr DocraftDocumentContext::footer() const { + return section_manager_.footer(); + } + + std::shared_ptr DocraftDocumentContext::edit_footer() { + return section_manager_.edit_footer(); } void DocraftDocumentContext::set_section_ratios(float header_ratio, float body_ratio, float footer_ratio) { - header_ratio_ = header_ratio; - body_ratio_ = body_ratio; - footer_ratio_ = footer_ratio; + section_manager_.set_section_ratios(header_ratio, body_ratio, footer_ratio); } float DocraftDocumentContext::header_ratio() const { - return header_ratio_; + return section_manager_.header_ratio(); } float DocraftDocumentContext::body_ratio() const { - return body_ratio_; + return section_manager_.body_ratio(); } float DocraftDocumentContext::footer_ratio() const { - return footer_ratio_; + return section_manager_.footer_ratio(); } #pragma endregion } // docraft diff --git a/docraft/src/docraft/management/docraft_backend_cache.cc b/docraft/src/docraft/management/docraft_backend_cache.cc new file mode 100644 index 0000000..6b05aec --- /dev/null +++ b/docraft/src/docraft/management/docraft_backend_cache.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "docraft/management/docraft_backend_cache.h" +#include "docraft/docraft_lib.h" + +namespace docraft::management { + void DocraftBackendCache::initialize_from_backend( + const std::shared_ptr &backend) { + refresh_caches(backend); + } + + std::shared_ptr DocraftBackendCache::line_backend() const { + return line_backend_; + } + + std::shared_ptr DocraftBackendCache::edit_line_backend() { + return line_backend_; + } + + std::shared_ptr DocraftBackendCache::shape_backend() const { + return shape_backend_; + } + + std::shared_ptr DocraftBackendCache::edit_shape_backend() { + return shape_backend_; + } + + std::shared_ptr DocraftBackendCache::text_backend() const { + return text_backend_; + } + + std::shared_ptr DocraftBackendCache::edit_text_backend() { + return text_backend_; + } + + std::shared_ptr DocraftBackendCache::image_backend() const { + return image_backend_; + } + + std::shared_ptr DocraftBackendCache::edit_image_backend() { + return image_backend_; + } + + std::shared_ptr DocraftBackendCache::page_backend() const { + return page_backend_; + } + + std::shared_ptr DocraftBackendCache::edit_page_backend() { + return page_backend_; + } + + void DocraftBackendCache::refresh_caches(const std::shared_ptr &backend) { + docraft::ensure_lazy_backend(line_backend_, backend); + docraft::ensure_lazy_backend(shape_backend_, backend); + docraft::ensure_lazy_backend(text_backend_, backend); + docraft::ensure_lazy_backend(image_backend_, backend); + docraft::ensure_lazy_backend(page_backend_, backend); + } +} // docraft::management + diff --git a/docraft/src/docraft/management/docraft_document_config.cc b/docraft/src/docraft/management/docraft_document_config.cc new file mode 100644 index 0000000..ed7cc73 --- /dev/null +++ b/docraft/src/docraft/management/docraft_document_config.cc @@ -0,0 +1,101 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "docraft/management/docraft_document_config.h" +#include "docraft/model/docraft_settings.h" +#include "docraft/templating/docraft_template_engine.h" + +namespace docraft::management { + void DocraftDocumentConfig::set_document_title(const std::string &document_title) { + document_title_ = document_title; + metadata_.set_title(document_title); + } + + const std::string &DocraftDocumentConfig::document_title() const { + return document_title_; + } + + std::string &DocraftDocumentConfig::edit_document_title() { + return document_title_; + } + + void DocraftDocumentConfig::set_document_path(const std::string &document_path) { + document_path_ = document_path; + } + + const std::string &DocraftDocumentConfig::document_path() const { + return document_path_; + } + + std::string &DocraftDocumentConfig::edit_document_path() { + return document_path_; + } + + void DocraftDocumentConfig::set_settings(const std::shared_ptr &settings) { + settings_ = settings; + } + + std::shared_ptr DocraftDocumentConfig::settings() const { + return settings_; + } + + std::shared_ptr DocraftDocumentConfig::edit_settings() { + return settings_; + } + + void DocraftDocumentConfig::set_document_metadata(const DocraftDocumentMetadata &metadata) { + metadata_ = metadata; + if (metadata_.title().has_value()) { + document_title_ = metadata_.title().value(); + } else { + metadata_.set_title(document_title_); + } + } + + const DocraftDocumentMetadata &DocraftDocumentConfig::document_metadata() const { + return metadata_; + } + + void DocraftDocumentConfig::enable_auto_keywords(bool enabled) { + auto_keywords_enabled_ = enabled; + } + + bool DocraftDocumentConfig::auto_keywords_enabled() const { + return auto_keywords_enabled_; + } + + void DocraftDocumentConfig::set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config) { + auto_keywords_config_ = config; + } + + const utils::DocraftKeywordExtractor::Config &DocraftDocumentConfig::auto_keywords_config() const { + return auto_keywords_config_; + } + + void DocraftDocumentConfig::set_document_template_engine( + const std::shared_ptr &template_engine) { + template_engine_ = template_engine; + } + + std::shared_ptr DocraftDocumentConfig::document_template_engine() const { + return template_engine_; + } + + std::shared_ptr DocraftDocumentConfig::edit_document_template_engine() { + return template_engine_; + } +} // docraft::management + diff --git a/docraft/src/docraft/management/docraft_document_query.cc b/docraft/src/docraft/management/docraft_document_query.cc new file mode 100644 index 0000000..1bff5d3 --- /dev/null +++ b/docraft/src/docraft/management/docraft_document_query.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "docraft/management/docraft_document_query.h" +#include "docraft/model/docraft_node.h" +#include "docraft/model/docraft_children_container_node.h" + +namespace docraft::management { + std::vector > DocraftDocumentQuery::find_by_name( + const std::vector > &root, const std::string &name) { + std::vector > result; + for (const auto &node: find_by_name_impl(root, name)) { + result.push_back(node); + } + return result; + } + + std::vector > DocraftDocumentQuery::take_by_name( + std::vector > &root, const std::string &name) { + return find_by_name_impl(root, name); + } + + std::vector > DocraftDocumentQuery::find_by_name_impl( + const std::vector > &root, const std::string &name) { + std::vector > result; + traverse_dom(root, [&](const std::shared_ptr &node, DocraftDomTraverseOp op) { + if (op != DocraftDomTraverseOp::kEnter) { + return; + } + if (node && node->node_name() == name) { + result.push_back(node); + } + }); + return result; + } + + std::shared_ptr DocraftDocumentQuery::find_first_by_name( + const std::vector > &root, const std::string &name) { + const auto matches = find_by_name(root, name); + return matches.empty() ? nullptr : matches.front(); + } + + std::shared_ptr DocraftDocumentQuery::take_first_by_name( + std::vector > &root, const std::string &name) { + const auto matches = take_by_name(root, name); + return matches.empty() ? nullptr : matches.front(); + } + + std::shared_ptr DocraftDocumentQuery::find_last_by_name( + const std::vector > &root, const std::string &name) { + const auto matches = find_by_name(root, name); + return matches.empty() ? nullptr : matches.back(); + } + + std::shared_ptr DocraftDocumentQuery::take_last_by_name( + std::vector > &root, const std::string &name) { + const auto matches = take_by_name(root, name); + return matches.empty() ? nullptr : matches.back(); + } + + void DocraftDocumentQuery::traverse_dom( + const std::vector > &root, + const std::function &, DocraftDomTraverseOp)> &callback) { + for (const auto &node: root) { + traverse_node(node, callback); + } + } + + void DocraftDocumentQuery::traverse_node( + const std::shared_ptr &node, + const std::function &, DocraftDomTraverseOp)> &callback) { + if (!node) { + return; + } + callback(node, DocraftDomTraverseOp::kEnter); + if (auto parent_node = std::dynamic_pointer_cast(node)) { + for (const auto &child_node: parent_node->children()) { + traverse_node(child_node, callback); + } + } + callback(node, DocraftDomTraverseOp::kExit); + } +} // docraft::management + diff --git a/docraft/src/docraft/management/docraft_document_section_manager.cc b/docraft/src/docraft/management/docraft_document_section_manager.cc new file mode 100644 index 0000000..72f1a0c --- /dev/null +++ b/docraft/src/docraft/management/docraft_document_section_manager.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2026 Matteo Cadoni (https://github.com/cadons) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "docraft/management/docraft_document_section_manager.h" +#include "docraft/model/docraft_header.h" +#include "docraft/model/docraft_body.h" +#include "docraft/model/docraft_footer.h" + +namespace docraft::management { + void DocraftDocumentSectionManager::set_header(const std::shared_ptr &header) { + header_ = header; + } + + std::shared_ptr DocraftDocumentSectionManager::header() const { + return header_; + } + + std::shared_ptr DocraftDocumentSectionManager::edit_header() { + return header_; + } + + void DocraftDocumentSectionManager::set_body(const std::shared_ptr &body) { + body_ = body; + } + + std::shared_ptr DocraftDocumentSectionManager::body() const { + return body_; + } + + std::shared_ptr DocraftDocumentSectionManager::edit_body() { + return body_; + } + + void DocraftDocumentSectionManager::set_footer(const std::shared_ptr &footer) { + footer_ = footer; + } + + std::shared_ptr DocraftDocumentSectionManager::footer() const { + return footer_; + } + + std::shared_ptr DocraftDocumentSectionManager::edit_footer() { + return footer_; + } + + void DocraftDocumentSectionManager::set_section_ratios(float header_ratio, float body_ratio, float footer_ratio) { + header_ratio_ = header_ratio; + body_ratio_ = body_ratio; + footer_ratio_ = footer_ratio; + } + + float DocraftDocumentSectionManager::header_ratio() const { return header_ratio_; } + float DocraftDocumentSectionManager::body_ratio() const { return body_ratio_; } + float DocraftDocumentSectionManager::footer_ratio() const { return footer_ratio_; } +} // docraft::management +