diff --git a/.claude/skills/craft-language/references/nodes.md b/.claude/skills/craft-language/references/nodes.md index ecd2e53..31107ab 100644 --- a/.claude/skills/craft-language/references/nodes.md +++ b/.claude/skills/craft-language/references/nodes.md @@ -76,6 +76,7 @@ background/border. | `background_color` | color | fully transparent | | `border_color` | color | fully transparent | | `border_width` | float | `1.0` (invisible unless `border_color` has alpha) | +| `border_style` | enum: `solid`, `dashed` | `solid` | | `width`, `height` | float | `0` = shrink-to-fit / use incoming width, not literally zero | | `padding` | float (common) | `10.0`, but collapses to `0` if the rectangle paints neither fill nor stroke | @@ -146,6 +147,7 @@ Rectangle/Text). | Attribute | Type | Notes | |---|---|---| | `background_color`, `border_color`, `border_width` | color/color/float | shared shape style | +| `border_style` | enum: `solid`, `dashed` | default `solid`; applies to the arc too, when present | | `radius` | float | true circle; mutually exclusive with `width`/`height` | | `width`, `height` | float | inscribes an ellipse in that bounding box; both required together | | `start_x`, `start_y`, `finish_x`, `finish_y` | float | arc endpoints, all four or none | @@ -171,6 +173,7 @@ direction — swapping them picks the complementary arc. | Attribute | Type | Notes | |---|---|---| | `background_color`, `border_color`, `border_width` | color/color/float | | +| `border_style` | enum: `solid`, `dashed` | default `solid` | | `points` | string, space-separated `x,y` tokens | **must be exactly 3 points**, else throws | `points="0,0 50,0 25,50"`. Y-down offsets from the node's own frame origin. @@ -191,6 +194,7 @@ shape node — no `background_color` (fill) at all. | `points` | space-separated `x,y` tokens | **minimum 2 points**, else throws | | `border_color` | color | black | | `border_width` | float | `1.0` | +| `border_style` | enum: `solid`, `dashed` | `solid` | ```xml @@ -204,6 +208,7 @@ shape node — no `background_color` (fill) at all. | `x2`, `y2` | float | `(100, 0)` | | `border_color` | color | black | | `border_width` | float | `1.0` | +| `border_style` | enum: `solid`, `dashed` | `solid` | ## `` / `` diff --git a/doc/source/craft_language/shapes.rst b/doc/source/craft_language/shapes.rst index 1ff374c..aa6b232 100644 --- a/doc/source/craft_language/shapes.rst +++ b/doc/source/craft_language/shapes.rst @@ -2,7 +2,18 @@ Shapes ====== Docraft supports several geometric shape elements. All shapes share -``background_color``, ``border_color``, and ``border_width`` attributes. +``background_color``, ``border_color``, ``border_width``, and ``border_style`` +attributes. ```` and ```` are stroke-only (no ``background_color``) but +share the same ``border_color``/``border_width``/``border_style`` trio. + +``border_style`` accepts ``solid`` (the default) or ``dashed``, and applies to every +shape's border as well as to ````/````: + +.. code-block:: xml + + + Rectangle --------- @@ -33,6 +44,9 @@ Rectangle * - ``border_width`` - float - Stroke width in points (default ``1``). + * - ``border_style`` + - ``solid`` | ``dashed`` + - Stroke pattern (default ``solid``). Circle ------ @@ -76,6 +90,9 @@ the center is ``(x + width/2, y + height/2)``. * - ``border_width`` - float - Stroke width in points. + * - ``border_style`` + - ``solid`` | ``dashed`` + - Stroke pattern (default ``solid``). Applies to the arc too, when present. The two sizing methods are exclusive on purpose, and every violation is a parse error rather than a silently misdrawn shape: @@ -158,6 +175,9 @@ Triangle * - ``border_width`` - float - Stroke width in points. + * - ``border_style`` + - ``solid`` | ``dashed`` + - Stroke pattern (default ``solid``). Line ---- @@ -201,6 +221,9 @@ they are clipped at its bounds. * - ``border_width`` - float - Stroke width in points. + * - ``border_style`` + - ``solid`` | ``dashed`` + - Stroke pattern (default ``solid``). .. _craft-canvas: @@ -259,6 +282,9 @@ curved counterpart of ````, and what a spline chart's series line is made * - ``border_width`` - float - Stroke width in points. + * - ``border_style`` + - ``solid`` | ``dashed`` + - Stroke pattern (default ``solid``). The curve **interpolates**: it passes exactly through every point rather than near them. With exactly 2 points it degenerates to a straight segment, which is why 2 is a @@ -304,4 +330,7 @@ an open curve through the same points see :ref:`CurveLine ` abo * - ``border_width`` - float - Stroke width in points. + * - ``border_style`` + - ``solid`` | ``dashed`` + - Stroke pattern (default ``solid``). diff --git a/docraft/CMakeLists.txt b/docraft/CMakeLists.txt index 8401922..15f6be0 100644 --- a/docraft/CMakeLists.txt +++ b/docraft/CMakeLists.txt @@ -69,6 +69,9 @@ set(DOCRAFT_SOURCES include/docraft/loom/nodes/docraft_loom_rectangle.h src/docraft/loom/nodes/docraft_loom_canvas.cc include/docraft/loom/nodes/docraft_loom_canvas.h + include/docraft/loom/nodes/docraft_loom_line_style.h + src/docraft/loom/nodes/docraft_loom_stroked_line.cc + include/docraft/loom/nodes/docraft_loom_stroked_line.h include/docraft/loom/nodes/docraft_loom_shape_style.h src/docraft/loom/nodes/docraft_loom_shape.cc include/docraft/loom/nodes/docraft_loom_shape.h @@ -141,6 +144,7 @@ set(DOCRAFT_SOURCES include/docraft/craft/parser/docraft_craft_enum_parsers.h src/docraft/craft/parser/docraft_shape_parser_utils.cc include/docraft/craft/parser/docraft_shape_parser_utils.h + include/docraft/craft/parser/docraft_parsed_line_style.h include/docraft/craft/parser/docraft_parser.h src/docraft/craft/parser/docraft_rectangle_parser.cc src/docraft/craft/parser/docraft_chart_parser.cc diff --git a/docraft/include/docraft/backend/docraft_line_rendering_backend.h b/docraft/include/docraft/backend/docraft_line_rendering_backend.h index bd3f764..ebf1ca2 100644 --- a/docraft/include/docraft/backend/docraft_line_rendering_backend.h +++ b/docraft/include/docraft/backend/docraft_line_rendering_backend.h @@ -46,6 +46,14 @@ namespace docraft::backend { */ virtual void set_line_width(float thickness) const = 0; + /** + * @brief Sets the dash pattern used for subsequent line/curve stroking: + * alternating on/off segment lengths in points, starting with an "on" segment. + * An empty pattern resets to a solid (continuous) line. + * @param pattern The on/off segment lengths, in points. + */ + virtual void set_line_dash_pattern(const std::vector& pattern) const = 0; + /** * @brief Draws a line between two points using the current stroke settings. * @param x1 The x-coordinate of the line start. diff --git a/docraft/include/docraft/backend/pdf/docraft_haru_line_backend.h b/docraft/include/docraft/backend/pdf/docraft_haru_line_backend.h index 8a1038a..a31b8cd 100644 --- a/docraft/include/docraft/backend/pdf/docraft_haru_line_backend.h +++ b/docraft/include/docraft/backend/pdf/docraft_haru_line_backend.h @@ -45,6 +45,12 @@ namespace docraft::backend::pdf { */ void set_line_width(float thickness) const override; + /** + * @brief Sets the dash pattern used for line/curve stroking via libharu's + * HPDF_Page_SetDash. An empty pattern resets to a solid line (num_dash=0). + */ + void set_line_dash_pattern(const std::vector& pattern) const override; + /** * @brief Draws a line segment between two points. */ diff --git a/docraft/include/docraft/craft/docraft_craft_language_tokens.h b/docraft/include/docraft/craft/docraft_craft_language_tokens.h index d262863..1cabbb6 100644 --- a/docraft/include/docraft/craft/docraft_craft_language_tokens.h +++ b/docraft/include/docraft/craft/docraft_craft_language_tokens.h @@ -251,12 +251,14 @@ namespace docraft::craft { constexpr string kBackgroundColor = "background_color"; constexpr string kBorderColor = "border_color"; constexpr string kBorderWidth = "border_width"; + constexpr string kBorderStyle = "border_style"; } namespace circle::attribute { constexpr string kBackgroundColor = "background_color"; constexpr string kBorderColor = "border_color"; constexpr string kBorderWidth = "border_width"; + constexpr string kBorderStyle = "border_style"; constexpr string kRadius = "radius"; // Arc endpoints: given as points, all four together or none. constexpr string kStartX = "start_x"; @@ -268,6 +270,7 @@ namespace docraft::craft { constexpr string kBackgroundColor = "background_color"; constexpr string kBorderColor = "border_color"; constexpr string kBorderWidth = "border_width"; + constexpr string kBorderStyle = "border_style"; constexpr string kPoints = "points"; } namespace line::attribute { @@ -277,17 +280,20 @@ namespace docraft::craft { constexpr string kY2 = "y2"; constexpr string kBorderColor = "border_color"; constexpr string kBorderWidth = "border_width"; + constexpr string kBorderStyle = "border_style"; } namespace polygon::attribute { constexpr string kBackgroundColor = "background_color"; constexpr string kBorderColor = "border_color"; constexpr string kBorderWidth = "border_width"; + constexpr string kBorderStyle = "border_style"; constexpr string kPoints = "points"; } // No background_color: an open curve has no interior to fill. namespace curve_line::attribute { constexpr string kBorderColor = "border_color"; constexpr string kBorderWidth = "border_width"; + constexpr string kBorderStyle = "border_style"; constexpr string kPoints = "points"; } @@ -361,4 +367,12 @@ namespace docraft::craft { constexpr string kVertical = "vertical"; } + /** + * @brief `border_style` string literals for line-like elements and shape borders. + */ + namespace line_style { + constexpr string kSolid = "solid"; + constexpr string kDashed = "dashed"; + } + } diff --git a/docraft/include/docraft/craft/parser/docraft_circle_parser.h b/docraft/include/docraft/craft/parser/docraft_circle_parser.h index 96880d6..3e156fe 100644 --- a/docraft/include/docraft/craft/parser/docraft_circle_parser.h +++ b/docraft/include/docraft/craft/parser/docraft_circle_parser.h @@ -22,6 +22,7 @@ #include "docraft/docraft_lib.h" #include "docraft/craft/i_docraft_parser.h" +#include "docraft/craft/parser/docraft_parsed_line_style.h" namespace docraft::craft::parser { /** @@ -31,11 +32,8 @@ namespace docraft::craft::parser { * used a width/height bounding box); `docraft::loom::nodes::DocraftLoomCircle` only * ever has a single radius. */ - struct ParsedCircleData + struct ParsedCircleData : ParsedShapeStyleData { - std::optional background_color; - std::optional border_color; - std::optional border_width; std::optional radius; /** * @brief Arc endpoints, as points in the node's own box. All four are required diff --git a/docraft/include/docraft/craft/parser/docraft_curve_line_parser.h b/docraft/include/docraft/craft/parser/docraft_curve_line_parser.h index 72967c1..1656405 100644 --- a/docraft/include/docraft/craft/parser/docraft_curve_line_parser.h +++ b/docraft/include/docraft/craft/parser/docraft_curve_line_parser.h @@ -24,6 +24,7 @@ #include "docraft/docraft_lib.h" #include "docraft/craft/i_docraft_parser.h" +#include "docraft/craft/parser/docraft_parsed_line_style.h" #include "docraft/docraft_position.h" namespace docraft::craft::parser { @@ -34,11 +35,9 @@ namespace docraft::craft::parser { * `` this element is stroke-only by construction rather than by * convention. */ - struct ParsedCurveLineData + struct ParsedCurveLineData : ParsedStrokeStyleData { std::vector points; - std::optional border_color; - std::optional border_width; }; /** diff --git a/docraft/include/docraft/craft/parser/docraft_line_parser.h b/docraft/include/docraft/craft/parser/docraft_line_parser.h index ff9faa8..d911cce 100644 --- a/docraft/include/docraft/craft/parser/docraft_line_parser.h +++ b/docraft/include/docraft/craft/parser/docraft_line_parser.h @@ -22,19 +22,18 @@ #include "docraft/docraft_lib.h" #include "docraft/craft/i_docraft_parser.h" +#include "docraft/craft/parser/docraft_parsed_line_style.h" namespace docraft::craft::parser { /** * @brief Tag-specific payload parsed from a `` element. */ - struct ParsedLineData + struct ParsedLineData : ParsedStrokeStyleData { std::optional x1; std::optional y1; std::optional x2; std::optional y2; - std::optional border_color; - std::optional border_width; }; /** diff --git a/docraft/include/docraft/craft/parser/docraft_parsed_line_style.h b/docraft/include/docraft/craft/parser/docraft_parsed_line_style.h new file mode 100644 index 0000000..ed26a4b --- /dev/null +++ b/docraft/include/docraft/craft/parser/docraft_parsed_line_style.h @@ -0,0 +1,57 @@ +/* + * 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 +#include + +namespace docraft::craft::parser { + /** + * @brief Stroke style for line-like elements (``/``) and shape + * borders (``/``/``/``), duplicated from + * (rather than shared with) loom's own `docraft::loom::nodes::DocraftLineStyle` -- + * see `ParsedTextStyle` for the same "duplicate a tiny enum and translate" + * rationale. + */ + enum class ParsedLineStyle + { + kSolid, + kDashed + }; + + /** + * @brief Border styling shared by every stroke-only, line-like element + * (``/``). Base struct rather than duplicating the same three + * fields on each `Parsed*Data`. + */ + struct ParsedStrokeStyleData + { + std::optional border_color; + std::optional border_width; + std::optional border_style; + }; + + /** + * @brief Background+border styling shared by every fillable shape element + * (``/``/``/``). Extends + * `ParsedStrokeStyleData` with the fill color those elements additionally support. + */ + struct ParsedShapeStyleData : ParsedStrokeStyleData + { + std::optional background_color; + }; +} // namespace docraft::craft::parser diff --git a/docraft/include/docraft/craft/parser/docraft_parser.h b/docraft/include/docraft/craft/parser/docraft_parser.h index 8fc3293..3cf7a19 100644 --- a/docraft/include/docraft/craft/parser/docraft_parser.h +++ b/docraft/include/docraft/craft/parser/docraft_parser.h @@ -25,16 +25,14 @@ #include "docraft/craft/docraft_craft_parsed_element.h" #include "docraft/craft/i_docraft_parser.h" +#include "docraft/craft/parser/docraft_parsed_line_style.h" namespace docraft::craft::parser { /** * @brief Tag-specific payload parsed from a `` element. */ - struct ParsedRectangleData + struct ParsedRectangleData : ParsedShapeStyleData { - std::optional background_color; - std::optional border_color; - std::optional border_width; }; /** diff --git a/docraft/include/docraft/craft/parser/docraft_parser_helpers.h b/docraft/include/docraft/craft/parser/docraft_parser_helpers.h index 5e4866e..2e15ae3 100644 --- a/docraft/include/docraft/craft/parser/docraft_parser_helpers.h +++ b/docraft/include/docraft/craft/parser/docraft_parser_helpers.h @@ -102,6 +102,16 @@ namespace docraft::craft::parser::detail { */ ParsedTextAlignment parse_text_alignment(const std::string& alignment_str); + /** + * @brief Parses a `border_style` attribute value (e.g. "dashed") into a + * `ParsedLineStyle`. Shared by every line/shape-border parser (`Line`/`CurveLine`/ + * `Rectangle`/`Circle`/`Triangle`/`Polygon`). + * @param style_str The raw attribute string. + * @return The parsed line style. + * @throws docraft::exception::InvalidInputException if the string is not a recognized style. + */ + ParsedLineStyle parse_line_style(const std::string& style_str); + /** * @brief Trims leading/trailing whitespace (space, tab, newline, CR, form feed, * vertical tab) from a string. diff --git a/docraft/include/docraft/craft/parser/docraft_polygon_parser.h b/docraft/include/docraft/craft/parser/docraft_polygon_parser.h index 4577c66..a2d15cc 100644 --- a/docraft/include/docraft/craft/parser/docraft_polygon_parser.h +++ b/docraft/include/docraft/craft/parser/docraft_polygon_parser.h @@ -24,16 +24,14 @@ #include "docraft/docraft_position.h" #include "docraft/craft/i_docraft_parser.h" +#include "docraft/craft/parser/docraft_parsed_line_style.h" namespace docraft::craft::parser { /** * @brief Tag-specific payload parsed from a `` element. */ - struct ParsedPolygonData + struct ParsedPolygonData : ParsedShapeStyleData { - std::optional background_color; - std::optional border_color; - std::optional border_width; std::vector points; }; diff --git a/docraft/include/docraft/craft/parser/docraft_triangle_parser.h b/docraft/include/docraft/craft/parser/docraft_triangle_parser.h index c4459dd..c56c703 100644 --- a/docraft/include/docraft/craft/parser/docraft_triangle_parser.h +++ b/docraft/include/docraft/craft/parser/docraft_triangle_parser.h @@ -24,16 +24,14 @@ #include "docraft/docraft_position.h" #include "docraft/craft/i_docraft_parser.h" +#include "docraft/craft/parser/docraft_parsed_line_style.h" namespace docraft::craft::parser { /** * @brief Tag-specific payload parsed from a `` element. */ - struct ParsedTriangleData + struct ParsedTriangleData : ParsedShapeStyleData { - std::optional background_color; - std::optional border_color; - std::optional border_width; std::vector points; // exactly 3 when present }; diff --git a/docraft/include/docraft/loom/craft/docraft_loom_tree_builder_utils.h b/docraft/include/docraft/loom/craft/docraft_loom_tree_builder_utils.h index 7142031..5f97f7c 100644 --- a/docraft/include/docraft/loom/craft/docraft_loom_tree_builder_utils.h +++ b/docraft/include/docraft/loom/craft/docraft_loom_tree_builder_utils.h @@ -22,6 +22,7 @@ #include "docraft/craft/docraft_craft_parsed_element.h" #include "docraft/craft/parser/docraft_parser.h" #include "docraft/docraft_lib.h" +#include "docraft/loom/nodes/docraft_loom_line_style.h" #include "docraft/loom/nodes/docraft_loom_node.h" #include "docraft/loom/nodes/docraft_loom_text.h" @@ -34,6 +35,13 @@ namespace docraft::loom::craft { DOCRAFT_LIB nodes::TextAlignment to_loom_alignment(docraft::craft::parser::ParsedTextAlignment alignment); + /** + * @brief Translates the parser's `ParsedLineStyle` into loom's own + * `DocraftLineStyle`, for the same "duplicate a tiny enum and translate" reason as + * to_loom_alignment(). + */ + DOCRAFT_LIB nodes::DocraftLineStyle to_loom_line_style(docraft::craft::parser::ParsedLineStyle style); + /** * @brief Normalizes a JSON string that may use single quotes for strings instead of * double quotes (the `.craft` convention for embedding JSON in an XML attribute, e.g. @@ -139,5 +147,15 @@ namespace docraft::loom::craft { { node.edit_style().border_width = *data.border_width; } + // Not every ShapeDataT carries a border_style (e.g. Canvas/Section/Chart's parsed + // data doesn't) -- if constexpr keeps this a no-op for those instead of a hard + // compile error, the same idiom apply_common_attributes() uses for width/height. + if constexpr (requires { data.border_style; }) + { + if (data.border_style.has_value()) + { + node.edit_style().border_style = to_loom_line_style(*data.border_style); + } + } } } // namespace docraft::loom::craft diff --git a/docraft/include/docraft/loom/nodes/docraft_loom_curve_line.h b/docraft/include/docraft/loom/nodes/docraft_loom_curve_line.h index dd14fc4..4563b2d 100644 --- a/docraft/include/docraft/loom/nodes/docraft_loom_curve_line.h +++ b/docraft/include/docraft/loom/nodes/docraft_loom_curve_line.h @@ -1,8 +1,7 @@ #pragma once #include -#include "docraft/docraft_color.h" -#include "docraft/loom/nodes/docraft_loom_node.h" +#include "docraft/loom/nodes/docraft_loom_stroked_line.h" namespace docraft::loom::nodes { /** @@ -19,23 +18,17 @@ namespace docraft::loom::nodes { * through `IDocraftLineRenderingBackend::draw_curve()`, which interpolates a uniform * Catmull-Rom spline: it passes exactly through every point rather than near them. */ - class DOCRAFT_LIB DocraftLoomCurveLine : public DocraftLoomNode + class DOCRAFT_LIB DocraftLoomCurveLine : public DocraftLoomStrokedLine { public: - DocraftLoomCurveLine(); + DocraftLoomCurveLine() = default; ~DocraftLoomCurveLine() override = default; void accept(loom::interfaces::DocraftLoomIVisitor& visitor) override; const std::vector& points() const; void set_points(const std::vector& points); - const DocraftColor& border_color() const; - void set_border_color(const DocraftColor& color); - float border_width() const; - void set_border_width(float width); private: std::vector points_; - DocraftColor border_color_; - float border_width_ = 1.0F; }; } // docraft diff --git a/docraft/include/docraft/loom/nodes/docraft_loom_line.h b/docraft/include/docraft/loom/nodes/docraft_loom_line.h index 40c1f37..bd1dfd7 100644 --- a/docraft/include/docraft/loom/nodes/docraft_loom_line.h +++ b/docraft/include/docraft/loom/nodes/docraft_loom_line.h @@ -1,13 +1,11 @@ #pragma once -#include "docraft/docraft_color.h" -#include "docraft/loom/nodes/docraft_loom_node.h" -#include "docraft/loom/nodes/docraft_loom_node.h" +#include "docraft/loom/nodes/docraft_loom_stroked_line.h" namespace docraft::loom::nodes { - class DOCRAFT_LIB DocraftLoomLine : public DocraftLoomNode + class DOCRAFT_LIB DocraftLoomLine : public DocraftLoomStrokedLine { public: - DocraftLoomLine(); + DocraftLoomLine() = default; ~DocraftLoomLine() override = default; void accept(loom::interfaces::DocraftLoomIVisitor& visitor) override; @@ -15,15 +13,9 @@ namespace docraft::loom::nodes { void set_start(const Position& start); const Position& end() const; void set_end(const Position& end); - const DocraftColor& border_color() const; - void set_border_color(const DocraftColor& color); - float border_width() const; - void set_border_width(float width); private: Position start_{.x = 0.0F, .y = 0.0F}; Position end_{.x = 100.0F, .y = 0.0F}; - DocraftColor border_color_; - float border_width_ = 1.0F; }; } // docraft \ No newline at end of file diff --git a/docraft/include/docraft/loom/nodes/docraft_loom_line_style.h b/docraft/include/docraft/loom/nodes/docraft_loom_line_style.h new file mode 100644 index 0000000..588406e --- /dev/null +++ b/docraft/include/docraft/loom/nodes/docraft_loom_line_style.h @@ -0,0 +1,13 @@ +#pragma once + +namespace docraft::loom::nodes { + /** + * @brief Stroke pattern for line-like nodes (Line, CurveLine) and shape borders + * (via DocraftLoomShapeStyle::border_style). + */ + enum class DocraftLineStyle + { + kSolid, + kDashed + }; +} // docraft diff --git a/docraft/include/docraft/loom/nodes/docraft_loom_shape_style.h b/docraft/include/docraft/loom/nodes/docraft_loom_shape_style.h index 5c948de..2481c55 100644 --- a/docraft/include/docraft/loom/nodes/docraft_loom_shape_style.h +++ b/docraft/include/docraft/loom/nodes/docraft_loom_shape_style.h @@ -1,5 +1,6 @@ #pragma once #include "docraft/docraft_color.h" +#include "docraft/loom/nodes/docraft_loom_line_style.h" namespace docraft::loom::nodes { /** @@ -12,5 +13,6 @@ namespace docraft::loom::nodes { DocraftColor background_color = DocraftColor::fromRGB(0.0F, 0.0F, 0.0F, 0.0F); DocraftColor border_color = DocraftColor::fromRGB(0.0F, 0.0F, 0.0F, 0.0F); float border_width = 1.0F; + DocraftLineStyle border_style = DocraftLineStyle::kSolid; }; } // docraft \ No newline at end of file diff --git a/docraft/include/docraft/loom/nodes/docraft_loom_stroked_line.h b/docraft/include/docraft/loom/nodes/docraft_loom_stroked_line.h new file mode 100644 index 0000000..d19e92d --- /dev/null +++ b/docraft/include/docraft/loom/nodes/docraft_loom_stroked_line.h @@ -0,0 +1,34 @@ +#pragma once +#include "docraft/docraft_color.h" +#include "docraft/loom/nodes/docraft_loom_line_style.h" +#include "docraft/loom/nodes/docraft_loom_node.h" + +namespace docraft::loom::nodes { + /** + * @brief Common base for stroke-only line-like nodes (Line, CurveLine), holding the + * border color/width/style they share. Composition-over-inheritance doesn't apply + * here the way it does for DocraftLoomShapeStyle (a plain member on each shape node): + * Line and CurveLine have no other divergent styling to keep separate, so a shared + * base -- mirroring DocraftLoomShape's role for fillable shapes -- avoids duplicating + * the same three members and six accessors twice. Concrete nodes still implement + * accept() themselves; DocraftLoomStrokedLine is never instantiated directly. + */ + class DOCRAFT_LIB DocraftLoomStrokedLine : public DocraftLoomNode + { + public: + DocraftLoomStrokedLine(); + ~DocraftLoomStrokedLine() override = default; + + const DocraftColor& border_color() const; + void set_border_color(const DocraftColor& color); + float border_width() const; + void set_border_width(float width); + DocraftLineStyle border_style() const; + void set_border_style(DocraftLineStyle style); + + private: + DocraftColor border_color_; + float border_width_ = 1.0F; + DocraftLineStyle border_style_ = DocraftLineStyle::kSolid; + }; +} // docraft diff --git a/docraft/include/docraft/loom/pipeline/docraft_loom_shape_draw_utils.h b/docraft/include/docraft/loom/pipeline/docraft_loom_shape_draw_utils.h index ee02421..978c2ee 100644 --- a/docraft/include/docraft/loom/pipeline/docraft_loom_shape_draw_utils.h +++ b/docraft/include/docraft/loom/pipeline/docraft_loom_shape_draw_utils.h @@ -55,10 +55,28 @@ namespace docraft::loom::pipeline { ShapeRenderTarget target; const DocraftColor &border_color; float border_width; + nodes::DocraftLineStyle border_style = nodes::DocraftLineStyle::kSolid; nodes::Position origin; const std::vector &points; }; + /** + * @brief On/off segment length (points) of a dashed stroke's repeating unit. + */ + static constexpr float kDashSegmentLength = 4.0F; + + /** + * @brief Gap length (points) between dashes of a dashed stroke. + */ + static constexpr float kDashGapLength = 2.0F; + + /** + * @brief Resolves a DocraftLineStyle to the on/off pattern IDocraftLineRenderingBackend:: + * set_line_dash_pattern() expects -- empty (solid) for kSolid, {kDashSegmentLength, + * kDashGapLength} for kDashed. + */ + static std::vector resolve_dash_pattern(nodes::DocraftLineStyle style); + static ShapeDrawFlags resolve_shape_draw_flags(const nodes::DocraftLoomShapeStyle &style); /** diff --git a/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc b/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc index 117fe3c..ddc528d 100644 --- a/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc +++ b/docraft/src/docraft/backend/pdf/docraft_haru_line_backend.cc @@ -18,9 +18,42 @@ #include +#include +#include +#include #include +#include namespace docraft::backend::pdf { + namespace { + // Deduces the element type HPDF_Page_SetDash actually expects for its dash_ptn + // parameter (e.g. HPDF_UINT16 on some libharu builds, HPDF_REAL/float on others) + // straight from the linked function's signature, so the conversion below adapts + // to whichever libharu is linked instead of guessing per platform. + template + ElemT dash_pattern_element_type(HPDF_STATUS (*)(PageT, const ElemT*, NumT, PhaseT)); + + using DashPatternElement = decltype(dash_pattern_element_type(&HPDF_Page_SetDash)); + + void invoke_set_dash(HPDF_Page page, const std::vector& pattern) { + std::vector dash_ptn; + dash_ptn.reserve(pattern.size()); + for (float segment : pattern) { + const float clamped = std::max(0.0F, segment); + if constexpr (std::is_integral_v) { + dash_ptn.push_back(static_cast(std::round(clamped))); + } else { + dash_ptn.push_back(static_cast(clamped)); + } + } + if (dash_ptn.empty()) { + HPDF_Page_SetDash(page, nullptr, 0, 0.0F); + } else { + HPDF_Page_SetDash(page, dash_ptn.data(), static_cast(dash_ptn.size()), 0.0F); + } + } + } // namespace + DocraftHaruLineBackend::DocraftHaruLineBackend(const std::shared_ptr &state) : state_(state) { } @@ -35,6 +68,11 @@ namespace docraft::backend::pdf { HPDF_Page_SetLineWidth(provider->current_page(), thickness); } + void DocraftHaruLineBackend::set_line_dash_pattern(const std::vector& pattern) const { + auto *provider = state_->ensure_page_provider(); + invoke_set_dash(provider->current_page(), pattern); + } + void DocraftHaruLineBackend::draw_line(float x1, float y1, float x2, float y2) const { auto *provider = state_->ensure_page_provider(); float px1, py1, px2, py2; diff --git a/docraft/src/docraft/craft/parser/docraft_circle_parser.cc b/docraft/src/docraft/craft/parser/docraft_circle_parser.cc index 5f77dfe..71e46ab 100644 --- a/docraft/src/docraft/craft/parser/docraft_circle_parser.cc +++ b/docraft/src/docraft/craft/parser/docraft_circle_parser.cc @@ -35,6 +35,10 @@ namespace docraft::craft::parser { elements::circle::attribute::kBorderWidth.data())) { data.border_width = border_width_attr.as_float(); } + if (auto border_style_attr = craft_language_source.attribute( + elements::circle::attribute::kBorderStyle.data())) { + data.border_style = detail::parse_line_style(border_style_attr.as_string()); + } if (auto radius_attr = craft_language_source.attribute(elements::circle::attribute::kRadius.data())) { data.radius = radius_attr.as_float(); @@ -64,6 +68,7 @@ namespace docraft::craft::parser { elements::circle::attribute::kBackgroundColor, elements::circle::attribute::kBorderColor, elements::circle::attribute::kBorderWidth, + elements::circle::attribute::kBorderStyle, elements::circle::attribute::kRadius, elements::circle::attribute::kStartX, elements::circle::attribute::kStartY, diff --git a/docraft/src/docraft/craft/parser/docraft_curve_line_parser.cc b/docraft/src/docraft/craft/parser/docraft_curve_line_parser.cc index d7f917a..0af5ff1 100644 --- a/docraft/src/docraft/craft/parser/docraft_curve_line_parser.cc +++ b/docraft/src/docraft/craft/parser/docraft_curve_line_parser.cc @@ -34,6 +34,11 @@ namespace docraft::craft::parser { { data.border_width = border_width_attr.as_float(); } + if (auto border_style_attr = craft_language_source.attribute( + elements::curve_line::attribute::kBorderStyle.data())) + { + data.border_style = detail::parse_line_style(border_style_attr.as_string()); + } auto points = detail::parse_points_attribute(craft_language_source, elements::curve_line::attribute::kPoints.data()); @@ -49,7 +54,8 @@ namespace docraft::craft::parser { return { elements::curve_line::attribute::kPoints, elements::curve_line::attribute::kBorderColor, - elements::curve_line::attribute::kBorderWidth + elements::curve_line::attribute::kBorderWidth, + elements::curve_line::attribute::kBorderStyle }; } } // namespace docraft::craft::parser diff --git a/docraft/src/docraft/craft/parser/docraft_line_parser.cc b/docraft/src/docraft/craft/parser/docraft_line_parser.cc index eacf855..a9a1a98 100644 --- a/docraft/src/docraft/craft/parser/docraft_line_parser.cc +++ b/docraft/src/docraft/craft/parser/docraft_line_parser.cc @@ -49,6 +49,11 @@ namespace docraft::craft::parser { { data.border_width = border_width_attr.as_float(); } + if (auto border_style_attr = craft_language_source.attribute( + elements::line::attribute::kBorderStyle.data())) + { + data.border_style = detail::parse_line_style(border_style_attr.as_string()); + } return data; } @@ -60,7 +65,8 @@ namespace docraft::craft::parser { elements::line::attribute::kX2, elements::line::attribute::kY2, elements::line::attribute::kBorderColor, - elements::line::attribute::kBorderWidth + elements::line::attribute::kBorderWidth, + elements::line::attribute::kBorderStyle }; } } // namespace docraft::craft::parser diff --git a/docraft/src/docraft/craft/parser/docraft_parser_helpers.cc b/docraft/src/docraft/craft/parser/docraft_parser_helpers.cc index c0ce2f2..cda9af7 100644 --- a/docraft/src/docraft/craft/parser/docraft_parser_helpers.cc +++ b/docraft/src/docraft/craft/parser/docraft_parser_helpers.cc @@ -312,6 +312,16 @@ namespace docraft::craft::parser::detail { throw docraft::exception::InvalidInputException("Invalid text alignment: " + alignment_str); } + ParsedLineStyle parse_line_style(const std::string& style_str) { + if (style_str == std::string{line_style::kDashed}) { + return ParsedLineStyle::kDashed; + } + if (style_str == std::string{line_style::kSolid}) { + return ParsedLineStyle::kSolid; + } + throw docraft::exception::InvalidInputException("Invalid border style: " + style_str); + } + std::string trim_whitespace(const std::string& text) { constexpr auto whitespace = " \t\n\r\f\v"; diff --git a/docraft/src/docraft/craft/parser/docraft_polygon_parser.cc b/docraft/src/docraft/craft/parser/docraft_polygon_parser.cc index 3522218..81cef40 100644 --- a/docraft/src/docraft/craft/parser/docraft_polygon_parser.cc +++ b/docraft/src/docraft/craft/parser/docraft_polygon_parser.cc @@ -39,6 +39,11 @@ namespace docraft::craft::parser { { data.border_width = border_width_attr.as_float(); } + if (auto border_style_attr = craft_language_source.attribute( + elements::polygon::attribute::kBorderStyle.data())) + { + data.border_style = detail::parse_line_style(border_style_attr.as_string()); + } auto points = detail::parse_points_attribute(craft_language_source, elements::polygon::attribute::kPoints.data()); @@ -56,7 +61,8 @@ namespace docraft::craft::parser { elements::polygon::attribute::kPoints, elements::polygon::attribute::kBackgroundColor, elements::polygon::attribute::kBorderColor, - elements::polygon::attribute::kBorderWidth + elements::polygon::attribute::kBorderWidth, + elements::polygon::attribute::kBorderStyle }; } } // namespace docraft::craft::parser diff --git a/docraft/src/docraft/craft/parser/docraft_rectangle_parser.cc b/docraft/src/docraft/craft/parser/docraft_rectangle_parser.cc index e65fee2..e268c58 100644 --- a/docraft/src/docraft/craft/parser/docraft_rectangle_parser.cc +++ b/docraft/src/docraft/craft/parser/docraft_rectangle_parser.cc @@ -34,6 +34,10 @@ namespace docraft::craft::parser { elements::rectangle::attribute::kBorderWidth.data())) { data.border_width = border_width_attr.as_float(); } + if (auto border_style_attr = craft_language_source.attribute( + elements::rectangle::attribute::kBorderStyle.data())) { + data.border_style = detail::parse_line_style(border_style_attr.as_string()); + } return data; } @@ -42,7 +46,8 @@ namespace docraft::craft::parser { return { elements::rectangle::attribute::kBackgroundColor, elements::rectangle::attribute::kBorderColor, - elements::rectangle::attribute::kBorderWidth + elements::rectangle::attribute::kBorderWidth, + elements::rectangle::attribute::kBorderStyle }; } } // namespace docraft::craft::parser diff --git a/docraft/src/docraft/craft/parser/docraft_triangle_parser.cc b/docraft/src/docraft/craft/parser/docraft_triangle_parser.cc index 48cce23..81e63fb 100644 --- a/docraft/src/docraft/craft/parser/docraft_triangle_parser.cc +++ b/docraft/src/docraft/craft/parser/docraft_triangle_parser.cc @@ -37,6 +37,10 @@ namespace docraft::craft::parser { elements::triangle::attribute::kBorderWidth.data())) { data.border_width = border_width_attr.as_float(); } + if (auto border_style_attr = craft_language_source.attribute( + elements::triangle::attribute::kBorderStyle.data())) { + data.border_style = detail::parse_line_style(border_style_attr.as_string()); + } auto points = detail::parse_points_attribute(craft_language_source, elements::triangle::attribute::kPoints.data()); @@ -56,7 +60,8 @@ namespace docraft::craft::parser { elements::triangle::attribute::kPoints, elements::triangle::attribute::kBackgroundColor, elements::triangle::attribute::kBorderColor, - elements::triangle::attribute::kBorderWidth + elements::triangle::attribute::kBorderWidth, + elements::triangle::attribute::kBorderStyle }; } } // namespace docraft::craft::parser diff --git a/docraft/src/docraft/loom/craft/docraft_loom_tree_builder_utils.cc b/docraft/src/docraft/loom/craft/docraft_loom_tree_builder_utils.cc index 9728964..b5cbb92 100644 --- a/docraft/src/docraft/loom/craft/docraft_loom_tree_builder_utils.cc +++ b/docraft/src/docraft/loom/craft/docraft_loom_tree_builder_utils.cc @@ -61,6 +61,18 @@ namespace docraft::loom::craft { } } + nodes::DocraftLineStyle to_loom_line_style(parser::ParsedLineStyle style) + { + switch (style) + { + case parser::ParsedLineStyle::kDashed: + return nodes::DocraftLineStyle::kDashed; + case parser::ParsedLineStyle::kSolid: + default: + return nodes::DocraftLineStyle::kSolid; + } + } + namespace { bool is_word_char(char ch) { diff --git a/docraft/src/docraft/loom/craft/handlers/docraft_loom_curve_line_handler.cc b/docraft/src/docraft/loom/craft/handlers/docraft_loom_curve_line_handler.cc index 310843b..037113f 100644 --- a/docraft/src/docraft/loom/craft/handlers/docraft_loom_curve_line_handler.cc +++ b/docraft/src/docraft/loom/craft/handlers/docraft_loom_curve_line_handler.cc @@ -46,6 +46,10 @@ namespace docraft::loom::craft { { node->set_border_width(*data.border_width); } + if (data.border_style.has_value()) + { + node->set_border_style(to_loom_line_style(*data.border_style)); + } apply_common_attributes(*node, element.common); return node; } diff --git a/docraft/src/docraft/loom/craft/handlers/docraft_loom_line_handler.cc b/docraft/src/docraft/loom/craft/handlers/docraft_loom_line_handler.cc index 93836df..3b45d4f 100644 --- a/docraft/src/docraft/loom/craft/handlers/docraft_loom_line_handler.cc +++ b/docraft/src/docraft/loom/craft/handlers/docraft_loom_line_handler.cc @@ -56,6 +56,10 @@ namespace docraft::loom::craft { { node->set_border_width(*data.border_width); } + if (data.border_style.has_value()) + { + node->set_border_style(to_loom_line_style(*data.border_style)); + } apply_common_attributes(*node, element.common); return node; } diff --git a/docraft/src/docraft/loom/nodes/docraft_loom_curve_line.cc b/docraft/src/docraft/loom/nodes/docraft_loom_curve_line.cc index d2f52e8..4125d78 100644 --- a/docraft/src/docraft/loom/nodes/docraft_loom_curve_line.cc +++ b/docraft/src/docraft/loom/nodes/docraft_loom_curve_line.cc @@ -1,11 +1,6 @@ #include "docraft/loom/nodes/docraft_loom_curve_line.h" namespace docraft::loom::nodes { - DocraftLoomCurveLine::DocraftLoomCurveLine() - : border_color_(DocraftColor::fromRGB(0.0F, 0.0F, 0.0F, 1.0F)) - { - } - void DocraftLoomCurveLine::accept(loom::interfaces::DocraftLoomIVisitor& visitor) { visitor.visit(this); @@ -20,24 +15,4 @@ namespace docraft::loom::nodes { { points_ = points; } - - const DocraftColor& DocraftLoomCurveLine::border_color() const - { - return border_color_; - } - - void DocraftLoomCurveLine::set_border_color(const DocraftColor& color) - { - border_color_ = color; - } - - float DocraftLoomCurveLine::border_width() const - { - return border_width_; - } - - void DocraftLoomCurveLine::set_border_width(float width) - { - border_width_ = width; - } } // docraft diff --git a/docraft/src/docraft/loom/nodes/docraft_loom_line.cc b/docraft/src/docraft/loom/nodes/docraft_loom_line.cc index 79d31be..d43060f 100644 --- a/docraft/src/docraft/loom/nodes/docraft_loom_line.cc +++ b/docraft/src/docraft/loom/nodes/docraft_loom_line.cc @@ -1,11 +1,6 @@ #include "docraft/loom/nodes/docraft_loom_line.h" namespace docraft::loom::nodes { - DocraftLoomLine::DocraftLoomLine() - : border_color_(DocraftColor::fromRGB(0.0F, 0.0F, 0.0F, 1.0F)) - { - } - void DocraftLoomLine::accept(loom::interfaces::DocraftLoomIVisitor& visitor) { visitor.visit(this); @@ -30,24 +25,4 @@ namespace docraft::loom::nodes { { end_ = end; } - - const DocraftColor& DocraftLoomLine::border_color() const - { - return border_color_; - } - - void DocraftLoomLine::set_border_color(const DocraftColor& color) - { - border_color_ = color; - } - - float DocraftLoomLine::border_width() const - { - return border_width_; - } - - void DocraftLoomLine::set_border_width(float width) - { - border_width_ = width; - } -} // docraft \ No newline at end of file +} // docraft diff --git a/docraft/src/docraft/loom/nodes/docraft_loom_stroked_line.cc b/docraft/src/docraft/loom/nodes/docraft_loom_stroked_line.cc new file mode 100644 index 0000000..9a1986f --- /dev/null +++ b/docraft/src/docraft/loom/nodes/docraft_loom_stroked_line.cc @@ -0,0 +1,38 @@ +#include "docraft/loom/nodes/docraft_loom_stroked_line.h" + +namespace docraft::loom::nodes { + DocraftLoomStrokedLine::DocraftLoomStrokedLine() + : border_color_(DocraftColor::fromRGB(0.0F, 0.0F, 0.0F, 1.0F)) + { + } + + const DocraftColor& DocraftLoomStrokedLine::border_color() const + { + return border_color_; + } + + void DocraftLoomStrokedLine::set_border_color(const DocraftColor& color) + { + border_color_ = color; + } + + float DocraftLoomStrokedLine::border_width() const + { + return border_width_; + } + + void DocraftLoomStrokedLine::set_border_width(float width) + { + border_width_ = width; + } + + DocraftLineStyle DocraftLoomStrokedLine::border_style() const + { + return border_style_; + } + + void DocraftLoomStrokedLine::set_border_style(DocraftLineStyle style) + { + border_style_ = style; + } +} // docraft diff --git a/docraft/src/docraft/loom/pipeline/docraft_loom_rendering_processor.cc b/docraft/src/docraft/loom/pipeline/docraft_loom_rendering_processor.cc index 6d58597..10e2fcc 100644 --- a/docraft/src/docraft/loom/pipeline/docraft_loom_rendering_processor.cc +++ b/docraft/src/docraft/loom/pipeline/docraft_loom_rendering_processor.cc @@ -371,6 +371,7 @@ namespace docraft::loom::pipeline { } line_backend_->set_line_width(line->border_width()); line_backend_->set_stroke_color(rgba.r, rgba.g, rgba.b); + line_backend_->set_line_dash_pattern(DocraftLoomShapeDrawUtils::resolve_dash_pattern(line->border_style())); line_backend_->draw_line(p1.x, p1.y, p2.x, p2.y); shape_backend_->restore_state(); } @@ -409,6 +410,8 @@ namespace docraft::loom::pipeline { } line_backend_->set_line_width(node->style().border_width); line_backend_->set_stroke_color(rgba.r, rgba.g, rgba.b); + line_backend_->set_line_dash_pattern( + DocraftLoomShapeDrawUtils::resolve_dash_pattern(node->style().border_style)); shape_backend_->draw_arc(center.x, center.y, radius_x, node->arc_start_angle(), node->arc_end_angle()); shape_backend_->stroke(); shape_backend_->restore_state(); @@ -467,6 +470,7 @@ namespace docraft::loom::pipeline { .target = {.shape_backend = shape_backend_, .line_backend = line_backend_}, .border_color = node->border_color(), .border_width = node->border_width(), + .border_style = node->border_style(), .origin = nodes::sealed_frame(*node).position, .points = node->points(), }); diff --git a/docraft/src/docraft/loom/pipeline/docraft_loom_shape_draw_utils.cc b/docraft/src/docraft/loom/pipeline/docraft_loom_shape_draw_utils.cc index dc8cf92..2240ed4 100644 --- a/docraft/src/docraft/loom/pipeline/docraft_loom_shape_draw_utils.cc +++ b/docraft/src/docraft/loom/pipeline/docraft_loom_shape_draw_utils.cc @@ -5,6 +5,13 @@ #include "docraft/loom/pipeline/docraft_loom_shape_draw_utils.h" namespace docraft::loom::pipeline { + std::vector DocraftLoomShapeDrawUtils::resolve_dash_pattern(nodes::DocraftLineStyle style) { + if (style == nodes::DocraftLineStyle::kDashed) { + return {kDashSegmentLength, kDashGapLength}; + } + return {}; + } + DocraftLoomShapeDrawUtils::ShapeDrawFlags DocraftLoomShapeDrawUtils::resolve_shape_draw_flags( const nodes::DocraftLoomShapeStyle &style) { return { @@ -30,6 +37,7 @@ namespace docraft::loom::pipeline { } target.line_backend->set_line_width(style.border_width); target.line_backend->set_stroke_color(border.r, border.g, border.b); + target.line_backend->set_line_dash_pattern(resolve_dash_pattern(style.border_style)); } } @@ -90,6 +98,7 @@ namespace docraft::loom::pipeline { } request.target.line_backend->set_line_width(request.border_width); request.target.line_backend->set_stroke_color(rgba.r, rgba.g, rgba.b); + request.target.line_backend->set_line_dash_pattern(resolve_dash_pattern(request.border_style)); request.target.line_backend->draw_curve(transformed); request.target.shape_backend->restore_state(); } diff --git a/docraft/test/docraft/craft/docraft_shape_parser_test.cc b/docraft/test/docraft/craft/docraft_shape_parser_test.cc index 416f69c..8f5f1ce 100644 --- a/docraft/test/docraft/craft/docraft_shape_parser_test.cc +++ b/docraft/test/docraft/craft/docraft_shape_parser_test.cc @@ -139,3 +139,61 @@ TEST(DocraftRectangleParserTest, ParsesBorderWidth) { ASSERT_TRUE(data.border_width.has_value()); EXPECT_FLOAT_EQ(*data.border_width, 3.0F); } + +TEST(DocraftRectangleParserTest, ParsesDashedBorderStyle) { + const char *xml = R"XML( + +)XML"; + + pugi::xml_document doc; + ASSERT_TRUE(doc.load_string(xml)); + + docraft::craft::parser::DocraftRectangleParser parser; + const auto data = std::any_cast(parser.parse(doc.child("Rectangle"))); + + ASSERT_TRUE(data.border_style.has_value()); + EXPECT_EQ(*data.border_style, docraft::craft::parser::ParsedLineStyle::kDashed); +} + +TEST(DocraftLineParserTest, ParsesDashedBorderStyle) { + const char *xml = R"XML( + +)XML"; + + pugi::xml_document doc; + ASSERT_TRUE(doc.load_string(xml)); + + docraft::craft::parser::DocraftLineParser parser; + const auto data = std::any_cast(parser.parse(doc.child("Line"))); + + ASSERT_TRUE(data.border_style.has_value()); + EXPECT_EQ(*data.border_style, docraft::craft::parser::ParsedLineStyle::kDashed); +} + +TEST(DocraftLineParserTest, DefaultsBorderStyleToUnset) +{ + const char *xml = R"XML( + +)XML"; + + pugi::xml_document doc; + ASSERT_TRUE(doc.load_string(xml)); + + docraft::craft::parser::DocraftLineParser parser; + const auto data = std::any_cast(parser.parse(doc.child("Line"))); + + EXPECT_FALSE(data.border_style.has_value()); +} + +TEST(DocraftLineParserTest, RejectsUnrecognizedBorderStyle) +{ + const char *xml = R"XML( + +)XML"; + + pugi::xml_document doc; + ASSERT_TRUE(doc.load_string(xml)); + + docraft::craft::parser::DocraftLineParser parser; + EXPECT_THROW(parser.parse(doc.child("Line")), docraft::exception::InvalidInputException); +} diff --git a/docraft/test/docraft/loom/nodes/docraft_loom_rectangle_test.cc b/docraft/test/docraft/loom/nodes/docraft_loom_rectangle_test.cc index 6b74f5c..2e73c3a 100644 --- a/docraft/test/docraft/loom/nodes/docraft_loom_rectangle_test.cc +++ b/docraft/test/docraft/loom/nodes/docraft_loom_rectangle_test.cc @@ -149,4 +149,24 @@ namespace docraft::test { EXPECT_FLOAT_EQ(backend.clip_calls()[0].width, frame.size.width); EXPECT_FLOAT_EQ(backend.clip_calls()[0].height, frame.size.height); } + + TEST_F(DocraftLoomRectangleTest, DashedBorderStyleSendsTheDashPatternBeforeDrawing) + { + utils::MockRenderingBackend backend; + loom::pipeline::DocraftLoomRenderingProcessor rendering(&backend); + + auto rect = std::make_shared(); + rect->set_width(80.0F); + rect->set_height(60.0F); + rect->edit_style().border_color = DocraftColor::fromRGB(0.0F, 0.0F, 0.0F, 1.0F); + rect->edit_style().border_width = 2.0F; + rect->edit_style().border_style = loom::nodes::DocraftLineStyle::kDashed; + + rect->accept(*measure_); + rect->accept(*layout_); + rect->accept(rendering); + + ASSERT_EQ(backend.dash_pattern_calls().size(), 1U); + EXPECT_FALSE(backend.dash_pattern_calls()[0].pattern.empty()); + } } // namespace docraft::test \ No newline at end of file diff --git a/docraft/test/docraft/loom/nodes/docraft_loom_shape_nodes_test.cc b/docraft/test/docraft/loom/nodes/docraft_loom_shape_nodes_test.cc index e22fc93..67f0b08 100644 --- a/docraft/test/docraft/loom/nodes/docraft_loom_shape_nodes_test.cc +++ b/docraft/test/docraft/loom/nodes/docraft_loom_shape_nodes_test.cc @@ -4,6 +4,7 @@ #include "docraft/exception/docraft_input_exceptions.h" #include "docraft/loom/nodes/docraft_loom_circle.h" #include "docraft/loom/nodes/docraft_loom_curve_line.h" +#include "docraft/loom/nodes/docraft_loom_line.h" #include "docraft/loom/nodes/docraft_loom_polygon.h" #include "docraft/loom/nodes/docraft_loom_triangle.h" #include "docraft/loom/pipeline/docraft_loom_layout_processor.h" @@ -26,6 +27,39 @@ namespace docraft::test { std::unique_ptr layout_; }; + // ── Line ──────────────────────────────────────────────────────────────────── + + TEST_F(DocraftLoomShapeNodesTest, LineDefaultsToSolidAndSendsAnEmptyDashPattern) + { + utils::MockRenderingBackend backend; + loom::pipeline::DocraftLoomRenderingProcessor rendering(&backend); + + auto line = std::make_shared(); + line->accept(*measure_); + line->accept(*layout_); + line->accept(rendering); + + ASSERT_EQ(backend.dash_pattern_calls().size(), 1U); + EXPECT_TRUE(backend.dash_pattern_calls()[0].pattern.empty()); + } + + TEST_F(DocraftLoomShapeNodesTest, DashedLineSendsTheDashPatternBeforeDrawing) + { + utils::MockRenderingBackend backend; + loom::pipeline::DocraftLoomRenderingProcessor rendering(&backend); + + auto line = std::make_shared(); + line->set_border_style(loom::nodes::DocraftLineStyle::kDashed); + + line->accept(*measure_); + line->accept(*layout_); + line->accept(rendering); + + ASSERT_EQ(backend.dash_pattern_calls().size(), 1U); + EXPECT_FALSE(backend.dash_pattern_calls()[0].pattern.empty()); + ASSERT_EQ(backend.draw_line_calls().size(), 1U); + } + // ── Circle ────────────────────────────────────────────────────────────────── TEST_F(DocraftLoomShapeNodesTest, CircleMeasuresToDiameterFromRadius) @@ -156,6 +190,26 @@ namespace docraft::test { EXPECT_TRUE(backend.draw_ellipse_calls().empty()); } + TEST_F(DocraftLoomShapeNodesTest, DashedArcSendsTheDashPatternBeforeDrawing) + { + utils::MockRenderingBackend backend; + loom::pipeline::DocraftLoomRenderingProcessor rendering(&backend); + + auto circle = std::make_shared(); + circle->set_radius(50.0F); + circle->set_arc(0.0F, 90.0F); + circle->edit_style().border_color = DocraftColor::fromRGB(1.0F, 0.0F, 0.0F, 1.0F); + circle->edit_style().border_width = 2.0F; + circle->edit_style().border_style = loom::nodes::DocraftLineStyle::kDashed; + + circle->accept(*measure_); + circle->accept(*layout_); + circle->accept(rendering); + + ASSERT_EQ(backend.dash_pattern_calls().size(), 1U); + EXPECT_FALSE(backend.dash_pattern_calls()[0].pattern.empty()); + } + TEST_F(DocraftLoomShapeNodesTest, ArcIsSkippedWhenItWouldHaveNoStroke) { utils::MockRenderingBackend backend; @@ -296,6 +350,25 @@ namespace docraft::test { EXPECT_EQ(backend.draw_curve_calls()[0].points.size(), 3U); } + TEST_F(DocraftLoomShapeNodesTest, DashedCurveLineSendsTheDashPatternBeforeDrawing) + { + utils::MockRenderingBackend backend; + loom::pipeline::DocraftLoomRenderingProcessor rendering(&backend); + + auto curve = std::make_shared(); + curve->set_points({{.x = 0.0F, .y = 0.0F}, {.x = 10.0F, .y = 5.0F}, {.x = 20.0F, .y = 0.0F}}); + curve->set_border_color(DocraftColor::fromRGB(0.0F, 0.0F, 1.0F, 1.0F)); + curve->set_border_width(2.0F); + curve->set_border_style(loom::nodes::DocraftLineStyle::kDashed); + + curve->accept(*measure_); + curve->accept(*layout_); + curve->accept(rendering); + + ASSERT_EQ(backend.dash_pattern_calls().size(), 1U); + EXPECT_FALSE(backend.dash_pattern_calls()[0].pattern.empty()); + } + TEST_F(DocraftLoomShapeNodesTest, CurveLineMeasuresToTheBoundingBoxOfItsPoints) { loom::nodes::DocraftLoomCurveLine curve; diff --git a/docraft/test/docraft/utils/docraft_mock_rendering_backend.h b/docraft/test/docraft/utils/docraft_mock_rendering_backend.h index d4ebc26..656c2d2 100644 --- a/docraft/test/docraft/utils/docraft_mock_rendering_backend.h +++ b/docraft/test/docraft/utils/docraft_mock_rendering_backend.h @@ -79,6 +79,11 @@ namespace docraft::test::utils { std::vector points; }; + struct DashPatternCall + { + std::vector pattern; + }; + // Records draw_circle() and draw_ellipse() alike -- a circle is stored with // radius_x == radius_y, so a test can assert on the resulting geometry without // caring which of the two primitives the rendering stage picked. @@ -108,6 +113,7 @@ namespace docraft::test::utils { mutable std::vector clip_calls; mutable std::vector draw_line_calls; mutable std::vector draw_curve_calls; + mutable std::vector dash_pattern_calls; mutable std::vector draw_ellipse_calls; mutable std::vector draw_arc_calls; @@ -138,6 +144,13 @@ namespace docraft::test::utils { state_->ensure_page_available(); } + void set_line_dash_pattern(const std::vector& pattern) const override { + MockBackendSharedState::ensure_supported(state_->config.supports_line_backend, + "Line backend capability not supported"); + state_->ensure_page_available(); + state_->dash_pattern_calls.push_back({.pattern = pattern}); + } + void draw_line(float x1, float y1, float x2, float y2) const override { MockBackendSharedState::ensure_supported(state_->config.supports_line_backend, "Line backend capability not supported"); @@ -617,6 +630,11 @@ namespace docraft::test::utils { return state_->draw_curve_calls; } + [[nodiscard]] const std::vector& dash_pattern_calls() const + { + return state_->dash_pattern_calls; + } + [[nodiscard]] const std::string &last_saved_path() const { return state_->last_saved_path; }