diff --git a/doc/project-doc/contributors/components/model-and-dom.md b/doc/project-doc/contributors/components/model-and-dom.md index 01172c0..ba26e6d 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: `find_by_name`, `find_first_by_name`, `find_last_by_name`, `find_by_type()`, +- mutable: `take_by_name`, `take_first_by_name`, `take_last_by_name`, `take_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..577b211 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->get_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->get_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/CMakeLists.txt b/docraft/CMakeLists.txt index 3e74a93..92f2a34 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 @@ -149,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} ) @@ -167,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/craft/docraft_craft_language_parser.h b/docraft/include/docraft/craft/docraft_craft_language_parser.h index ece521c..1d83f23 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; + [[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_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_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 a2b9943..e122c7c 100644 --- a/docraft/include/docraft/docraft_document.h +++ b/docraft/include/docraft/docraft_document.h @@ -20,24 +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: @@ -45,7 +51,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. @@ -82,136 +88,100 @@ namespace docraft { void set_backend(const std::shared_ptr &backend); /** - * @brief Sets the document title. - * @param document_title New title value. + * @brief Returns the document DOM nodes. + * @return Vector of root nodes. */ - void set_document_title(const std::string &document_title); + [[nodiscard]] std::vector > nodes() const; - /** - * @brief Returns the current document title. - * @return Document title string. - */ - std::string 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. */ - std::string document_path(); + management::DocraftDocumentConfig &edit_config(); + + [[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. */ + [[nodiscard]] std::shared_ptr edit_context(); + + [[nodiscard]] std::shared_ptr context() const; + + // Backward compatibility delegates to config_ + void set_document_title(const std::string &document_title); + + [[nodiscard]] const std::string &document_title() const; + + void set_document_path(const std::string &document_path); + + [[nodiscard]] const std::string &document_path() const; + void set_settings(const std::shared_ptr &settings); - /** - * @brief Returns the current settings object. - * @return Shared pointer to settings or nullptr if not set. - */ - std::shared_ptr settings() const; + [[nodiscard]] std::shared_ptr settings() const; - /** - * @brief Sets document metadata values. - * @param metadata Metadata values supported by libharu. - */ 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. - */ + // Backward compatibility: DOM query delegates + [[nodiscard]] std::vector > find_by_name( + const std::string &name) const; + + [[nodiscard]] std::vector > take_by_name(const std::string &name); + + [[nodiscard]] std::shared_ptr find_first_by_name(const std::string &name) const; + + [[nodiscard]] std::shared_ptr take_first_by_name(const std::string &name); + + [[nodiscard]] std::shared_ptr find_last_by_name(const std::string &name) const; + + [[nodiscard]] std::shared_ptr take_last_by_name(const std::string &name); + + template + [[nodiscard]] std::vector > find_by_type() const; + + template + [[nodiscard]] std::vector > take_by_type(); + + // Backward compatibility: config shortcuts 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 the current document and merges them into metadata. - * - * No-op when auto-keyword extraction is disabled. - */ - void refresh_auto_keywords(); - void set_document_template_engine(const std::shared_ptr &template_engine); - std::shared_ptr document_template_engine() const; - /** - * @brief Returns the document DOM nodes. - * @return Vector of root nodes. - */ - const std::vector> &nodes() 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> get_by_name(const std::string &name) 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 get_first_by_name(const std::string &name) const; - /** - * @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; - /** - * @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; - /** - * @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( const std::shared_ptr &node, - const std::function &, DocraftDomTraverseOp)> &callback) const; + const std::function &, DocraftDomTraverseOp)> &callback) + 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 9ee8301..d99842c 100644 --- a/docraft/include/docraft/docraft_document.hpp +++ b/docraft/include/docraft/docraft_document.hpp @@ -1,12 +1,25 @@ #pragma once -#include -#include +#include "docraft/model/docraft_node.h" namespace docraft { - template - std::vector> DocraftDocument::get_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; + } + if (auto casted = std::dynamic_pointer_cast(node)) { + result.push_back(casted); + } + }); + return 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; @@ -18,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 be7e10f..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: @@ -60,22 +62,27 @@ namespace docraft { * @brief Returns the active rendering backend. * @return Shared pointer to the rendering backend. */ - [[nodiscard]] const std::shared_ptr& rendering_backend() const; + [[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). @@ -87,137 +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]] const std::shared_ptr& header() const; - /** - * @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]] const std::shared_ptr& body() const; - /** - * @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). + * @brief Returns the font applier instance. + * @return Font applier (may be nullptr). */ - [[nodiscard]] const std::shared_ptr& footer() const; + [[nodiscard]] std::shared_ptr font_applier() const; + [[nodiscard]] std::shared_ptr edit_font_applier(); + /** * @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]] const std::shared_ptr& font_applier()const; - /** - * @brief Returns the line backend (cached). - * @return Line rendering backend. - */ - [[nodiscard]] const std::shared_ptr& line_backend() const; - /** - * @brief Returns the shape backend (cached). - * @return Shape rendering backend. - */ - [[nodiscard]] const std::shared_ptr& shape_backend() const; - /** - * @brief Returns the text backend (cached). - * @return Text rendering backend. - */ - [[nodiscard]] const std::shared_ptr& text_backend() const; - /** - * @brief Returns the image backend (cached). - * @return Image rendering backend. - */ - [[nodiscard]] const std::shared_ptr& image_backend() const; - /** - * @brief Returns the page backend (cached). - * @return Page rendering backend. - */ - [[nodiscard]] const std::shared_ptr& page_backend() const; + /** * @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() 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. + * @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 all backend caches (called after backend changes). + */ + 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_; - 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_; - 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_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/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/docraft_lib.h b/docraft/include/docraft/docraft_lib.h index a93b4e1..6041740 100644 --- a/docraft/include/docraft/docraft_lib.h +++ b/docraft/include/docraft/docraft_lib.h @@ -16,6 +16,9 @@ #pragma once +#include +#include + #if defined(_WIN32) || defined(__CYGWIN__) #if defined(DOCRAFT_BUILD_SHARED_LIBS) #define DOCRAFT_LIB __declspec(dllexport) @@ -24,12 +27,29 @@ #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 + #else #define DOCRAFT_LIB #endif + +namespace docraft { + template + std::vector> to_const_shared_vector( + const std::vector> &values) { + return {values.begin(), values.end()}; + } + + template + void ensure_lazy_backend(std::shared_ptr &cache, const std::shared_ptr &backend) { + if (!cache) { + cache = std::dynamic_pointer_cast(backend); + } + } +} // namespace docraft 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/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/include/docraft/templating/docraft_template_engine.h b/docraft/include/docraft/templating/docraft_template_engine.h index 8a5eca9..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); + std::string find_template_variable(const std::string &name) const; /** * @brief Clears all template variables. */ 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/craft/docraft_craft_language_parser.cc b/docraft/src/docraft/craft/docraft_craft_language_parser.cc index 8dee52a..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); } @@ -665,8 +673,4 @@ void DocraftCraftLanguageParser::load_document() { LOG_INFO("Document loaded successfully with title: " + document_->document_title()); } -std::shared_ptr DocraftCraftLanguageParser::get_document() const { - return document_; -} - } // namespace docraft::craft diff --git a/docraft/src/docraft/docraft_document.cc b/docraft/src/docraft/docraft_document.cc index bb5a2c9..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,37 +204,40 @@ 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_->page_backend(); + const auto page_backend = context_->edit_page_backend(); if (page_backend) { page_backend->go_to_first_page(); } @@ -260,139 +264,165 @@ namespace docraft { } } - context_->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); + } + + std::vector> DocraftDocument::nodes() const { + return docraft::to_const_shared_vector(dom_); + } + + std::vector> &DocraftDocument::edit_nodes() { + return dom_; + } + + void DocraftDocument::traverse_dom( + const std::function &, DocraftDomTraverseOp)> &callback) const { + for (const auto &node: dom_) { + traverse_node(node, callback); + } + } + + 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); + } + + management::DocraftDocumentConfig &DocraftDocument::edit_config() { + return config_; + } + + const management::DocraftDocumentConfig &DocraftDocument::config() const { + return config_; } + std::shared_ptr DocraftDocument::edit_context() { + return context_; + } + + std::shared_ptr DocraftDocument::context() const { + return context_; + } + + // Backward compatibility delegates to config_ void DocraftDocument::set_document_title(const std::string &document_title) { - document_title_ = document_title; - metadata_.set_title(document_title); + config_.set_document_title(document_title); } - std::string DocraftDocument::document_title() { - return document_title_; + const std::string &DocraftDocument::document_title() const { + return config_.document_title(); } void DocraftDocument::set_document_path(const std::string &document_path) { - document_path_ = document_path; + config_.set_document_path(document_path); } - std::string DocraftDocument::document_path() { - return document_path_; + const std::string &DocraftDocument::document_path() const { + return config_.document_path(); } void DocraftDocument::set_settings(const std::shared_ptr &settings) { - settings_ = settings; + config_.set_settings(settings); } - std::shared_ptr DocraftDocument::settings() const { - return settings_; + std::shared_ptr DocraftDocument::settings() const { + return config_.settings(); } 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_); - } + config_.set_document_metadata(metadata); } const DocraftDocumentMetadata &DocraftDocument::document_metadata() const { - return metadata_; + return config_.document_metadata(); } - void DocraftDocument::enable_auto_keywords(bool enabled) { - auto_keywords_enabled_ = enabled; + // 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); } - bool DocraftDocument::auto_keywords_enabled() const { - return auto_keywords_enabled_; + std::vector > DocraftDocument::take_by_name(const std::string &name) { + return management::DocraftDocumentQuery::take_by_name(dom_, name); } - void DocraftDocument::set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config) { - auto_keywords_config_ = config; + std::shared_ptr DocraftDocument::find_first_by_name(const std::string &name) const { + return management::DocraftDocumentQuery::find_first_by_name( + const_cast > &>(dom_), name); } - const utils::DocraftKeywordExtractor::Config &DocraftDocument::auto_keywords_config() const { - return auto_keywords_config_; + std::shared_ptr DocraftDocument::take_first_by_name(const std::string &name) { + return management::DocraftDocumentQuery::take_first_by_name(dom_, name); } - 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; - } + std::shared_ptr DocraftDocument::find_last_by_name(const std::string &name) const { + return management::DocraftDocumentQuery::find_last_by_name( + const_cast > &>(dom_), name); + } - DocraftDocumentMetadata metadata = metadata_; - metadata.set_keywords(merge_keywords(metadata.keywords(), extracted_keywords)); - set_document_metadata(metadata); + std::shared_ptr DocraftDocument::take_last_by_name(const std::string &name) { + return management::DocraftDocumentQuery::take_last_by_name(dom_, name); } - void DocraftDocument::set_document_template_engine( - const std::shared_ptr &template_engine) { - template_engine_ = template_engine; + // Backward compatibility: config shortcuts + void DocraftDocument::enable_auto_keywords(bool enabled) { + config_.enable_auto_keywords(enabled); } - std::shared_ptr DocraftDocument::document_template_engine() const { - return template_engine_; + bool DocraftDocument::auto_keywords_enabled() const { + return config_.auto_keywords_enabled(); } - const std::vector> &DocraftDocument::nodes() const { - return dom_; + void DocraftDocument::set_auto_keywords_config(const utils::DocraftKeywordExtractor::Config &config) { + config_.set_auto_keywords_config(config); } - std::vector> DocraftDocument::get_by_name(const std::string &name) const { - static std::vector> empty_result; - 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.empty() ? empty_result : result; + + const utils::DocraftKeywordExtractor::Config &DocraftDocument::auto_keywords_config() const { + return config_.auto_keywords_config(); } - 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(); + void DocraftDocument::set_document_template_engine( + const std::shared_ptr &template_engine) { + config_.set_document_template_engine(template_engine); } - 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::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 38bdf2d..79e5252 100644 --- a/docraft/src/docraft/docraft_document_context.cc +++ b/docraft/src/docraft/docraft_document_context.cc @@ -16,14 +16,16 @@ #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() { 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 +34,7 @@ namespace docraft { page_height_ = backend_->page_height(); page_width_ = backend_->page_width(); current_rect_width_ = page_width_; + refresh_backend_caches(); } DocraftDocumentContext::~DocraftDocumentContext() = default; @@ -44,20 +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_; + } + + 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) { @@ -65,16 +80,12 @@ namespace docraft { 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, model::DocraftPageOrientation orientation) { - const auto &backend = page_backend(); + const auto backend = backend_cache_.edit_page_backend(); if (backend) { backend->set_page_format(size, orientation); page_height_ = backend->page_height(); @@ -82,9 +93,17 @@ 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 - const std::shared_ptr &DocraftDocumentContext::rendering_backend() const { + std::shared_ptr DocraftDocumentContext::rendering_backend() const { + return backend_; + } + + std::shared_ptr DocraftDocumentContext::edit_rendering_backend() { return backend_; } @@ -111,96 +130,119 @@ namespace docraft { return page_width_; } + void DocraftDocumentContext::go_to_first_page() { + const auto backend = backend_cache_.edit_page_backend(); + if (backend) { + backend->go_to_first_page(); + } + } - const std::shared_ptr &DocraftDocumentContext::header() const { - return header_; + void DocraftDocumentContext::go_to_previous_page() { + const auto backend = backend_cache_.edit_page_backend(); + if (backend) { + backend->go_to_previous_page(); + } } + void DocraftDocumentContext::go_to_last_page() { + const auto backend = backend_cache_.edit_page_backend(); + if (backend) { + backend->go_to_last_page(); + } + } - const std::shared_ptr &DocraftDocumentContext::body() const { - return body_; + // Backward compatibility delegates to backend_cache() + std::shared_ptr DocraftDocumentContext::line_backend() const { + return backend_cache_.line_backend(); } - const std::shared_ptr &DocraftDocumentContext::footer() const { - return footer_; + std::shared_ptr DocraftDocumentContext::edit_line_backend() { + return backend_cache_.edit_line_backend(); } - const std::shared_ptr &DocraftDocumentContext::font_applier() const { - return font_applier_; + std::shared_ptr DocraftDocumentContext::shape_backend() const { + return backend_cache_.shape_backend(); } - const 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_shape_backend() { + return backend_cache_.edit_shape_backend(); } - const std::shared_ptr &DocraftDocumentContext::shape_backend() const { - if (!shape_backend_) { - shape_backend_ = std::dynamic_pointer_cast(backend_); - } - return shape_backend_; + std::shared_ptr DocraftDocumentContext::text_backend() const { + return backend_cache_.text_backend(); } - const 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() { + return backend_cache_.edit_text_backend(); } - const std::shared_ptr &DocraftDocumentContext::image_backend() const { - if (!image_backend_) { - image_backend_ = std::dynamic_pointer_cast(backend_); - } - return image_backend_; + std::shared_ptr DocraftDocumentContext::image_backend() const { + return backend_cache_.image_backend(); } - const 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_image_backend() { + return backend_cache_.edit_image_backend(); } - void DocraftDocumentContext::go_to_first_page() const { - const auto &backend = page_backend(); - if (backend) { - backend->go_to_first_page(); - } + std::shared_ptr DocraftDocumentContext::page_backend() const { + return backend_cache_.page_backend(); } - void DocraftDocumentContext::go_to_previous_page() const { - const auto &backend = page_backend(); - if (backend) { - backend->go_to_previous_page(); - } + std::shared_ptr DocraftDocumentContext::edit_page_backend() { + return backend_cache_.edit_page_backend(); } - void DocraftDocumentContext::go_to_last_page() const { - const auto &backend = page_backend(); - if (backend) { - backend->go_to_last_page(); - } + // Backward compatibility delegates to section_manager() + void DocraftDocumentContext::set_header(const std::shared_ptr &header) { + section_manager_.set_header(header); + } + + std::shared_ptr DocraftDocumentContext::header() const { + return section_manager_.header(); + } + + 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/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 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/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/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/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/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 + diff --git a/docraft/src/docraft/templating/docraft_template_engine.cc b/docraft/src/docraft/templating/docraft_template_engine.cc index ae8ad6b..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) { + 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/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; 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..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,9 +288,31 @@ 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"); } +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..23f8f46 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,26 @@ 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, EditFindMethodsAllowNodeMutation) { + 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.take_first_by_name("target")); + ASSERT_TRUE(editable); + editable->set_text("After"); + + 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"); } TEST(DocraftDocumentTest, GetByTypeFindsMatchingNodes) { @@ -146,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); 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);