From c8887ca47b63e7ae925020f67c156b080cd39bda Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Sun, 22 Jun 2025 07:06:45 +0200 Subject: [PATCH 1/2] Work in progress: Add hatch to path functionality. Still to do: Handle "offset". Update and fix on-screen rendering. --- src/object/sp-hatch-path.cpp | 41 +++++++ src/object/sp-hatch-path.h | 1 + src/object/sp-hatch.cpp | 228 ++++++++++++++++++++++++++++------- src/object/sp-hatch.h | 15 ++- src/path-chemistry.cpp | 35 ++++++ 5 files changed, 274 insertions(+), 46 deletions(-) diff --git a/src/object/sp-hatch-path.cpp b/src/object/sp-hatch-path.cpp index d476c9208c..ca4cadd56b 100644 --- a/src/object/sp-hatch-path.cpp +++ b/src/object/sp-hatch-path.cpp @@ -177,6 +177,47 @@ Geom::PathVector SPHatchPath::calculateRenderCurve(unsigned key) const return {}; } +// Hatch bounding box is box in hatch space that will completely cover +// object. We only use height here. +// Note: Offset not taken into account! +Geom::PathVector SPHatchPath::calculateRenderCurve(Geom::Rect const &hatch_bbox, + Geom::Point const &hatch_origin) +{ + Geom::PathVector calculated_curve; + + if (hatch_bbox.area() == 0) { + return calculated_curve; + } + + auto y_interval = hatch_bbox[Geom::Y]; + if (!_curve) { + calculated_curve = path_from_curve(Geom::LineSegment{Geom::Point{0, y_interval.min()}, Geom::Point{0, y_interval.max()}}); + } else { + double repeatLength = _repeatLength(); + if (repeatLength > 0) { + double initial_y = + floor((y_interval.min())/ repeatLength) * repeatLength; + int segment_count = + ceil((y_interval.extent()) / repeatLength) + 1; + + auto segment = *_curve; + segment *= Geom::Translate(0, initial_y); + + Geom::Affine step_transform = Geom::Translate(0, repeatLength); + for (int i = 0; i < segment_count; ++i) { + if (_continuous) { + pathvector_append_continuous(calculated_curve, segment); + } else { + pathvector_append(calculated_curve, segment); + } + segment *= step_transform; + } + } + } + std::cout << "SPHatchPath::calculateRenderCurve: " << calculated_curve << std::endl; + return calculated_curve; +} + double SPHatchPath::_repeatLength() const { double val = 0; diff --git a/src/object/sp-hatch-path.h b/src/object/sp-hatch-path.h index e57eca7f4d..bc4039cbd4 100644 --- a/src/object/sp-hatch-path.h +++ b/src/object/sp-hatch-path.h @@ -54,6 +54,7 @@ public: Geom::Interval bounds() const; Geom::PathVector calculateRenderCurve(unsigned key) const; + Geom::PathVector calculateRenderCurve(Geom::Rect const &hatch_bbox, Geom::Point const &hatch_origin); protected: void build(SPDocument* doc, Inkscape::XML::Node* repr) override; diff --git a/src/object/sp-hatch.cpp b/src/object/sp-hatch.cpp index 7cb6b3d2fa..515a01fe21 100644 --- a/src/object/sp-hatch.cpp +++ b/src/object/sp-hatch.cpp @@ -25,6 +25,7 @@ #include "bad-uri-exception.h" #include "document.h" +#include "display/curve.h" // pathvector_append #include "display/drawing.h" #include "display/drawing-pattern.h" @@ -32,6 +33,8 @@ #include "sp-hatch-path.h" #include "sp-item.h" +#include "livarot/LivarotDefs.h" // FillRule, BooleanOp +#include "path/path-boolop.h" #include "svg/svg.h" #include "xml/href-attribute-helper.h" @@ -270,6 +273,7 @@ void SPHatch::update(SPCtx* ctx, unsigned flags) std::vector children(hatchPaths()); + // TO DO: Swap inner/outer loops (to avoid recacluating strip extents repeatedly). for (auto child : children) { sp_object_ref(child, nullptr); @@ -574,11 +578,18 @@ Geom::Interval SPHatch::bounds() const return result; } +// Render info based on object bounding box. +SPHatch::RenderInfo SPHatch::calculateRenderInfo(Geom::OptRect const &bbox) const +{ + return _calculateRenderInfo(bbox); +} + +// Render info based on viewable area. SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const { for (auto const &v : views) { if (v.key == key) { - return _calculateRenderInfo(v); + return _calculateRenderInfo(v.bbox); } } g_assert_not_reached(); @@ -587,7 +598,7 @@ SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const void SPHatch::_updateView(View &view) { - RenderInfo info = _calculateRenderInfo(view); + RenderInfo info = _calculateRenderInfo(view.bbox); //The rendering of hatch overflow is implemented by repeated drawing //of hatch paths over one strip. Within each iteration paths are moved by pitch value. //The movement progresses from right to left. This gives the same result @@ -600,64 +611,115 @@ void SPHatch::_updateView(View &view) view.drawingitem->setOverflow(info.overflow_initial_transform, info.overflow_steps, info.overflow_step_transform); } -SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const +// Calculate render info based on x, y, rotate, pitch, and bounding box of child hatch paths. +// Tile height is not calculated here as different hatch paths in the same hatch can have +// different heights. +SPHatch::RenderInfo SPHatch::_calculateRenderInfo(Geom::OptRect const &bbox) const { - auto const extents = _calculateStripExtents(view.bbox); - if (!extents) { + if (!bbox) { + std::cerr << "SPHatch::RenderInfo: no bounding box!" << std::endl; return {}; } + std::cout << "\nSPHatch::_calculateRenderInfo(): bbox: " << bbox << std::endl; - double tile_x = x(); - double tile_y = y(); - double tile_width = pitch(); - double tile_height = extents->max() - extents->min(); - double tile_rotate = rotate(); - double tile_render_y = extents->min(); + // Calculate hatch transformation to user space. + double hatch_x = x(); // tile refers to "base" tile. + double hatch_y = y(); + double hatch_rotate = rotate(); - if (view.bbox && hatchUnits() == HatchUnits::ObjectBoundingBox) { - tile_x *= view.bbox->width(); - tile_y *= view.bbox->height(); - tile_width *= view.bbox->width(); - } + // Find size of strip: + double strip_width = pitch(); - // Extent calculated using content units, need to correct. - if (view.bbox && hatchContentUnits() == HatchUnits::ObjectBoundingBox) { - tile_height *= view.bbox->height(); - tile_render_y *= view.bbox->height(); + // Correct for units: + if (hatchUnits() == HatchUnits::ObjectBoundingBox) { + hatch_x = hatch_x * bbox->width() + bbox->min()[Geom::X]; + hatch_y = hatch_y * bbox->height() + bbox->min()[Geom::Y]; + strip_width *= bbox->width(); } - // Pattern size in hatch space - Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height); + Geom::Affine hatch2user = Geom::Rotate::from_degrees(hatch_rotate) * Geom::Translate(hatch_x, hatch_y) * hatchTransform(); + Geom::Affine user2hatch = hatch2user.inverse(); - // Content to bbox - Geom::Affine content2ps; - if (view.bbox && hatchContentUnits() == HatchUnits::ObjectBoundingBox) { - content2ps = Geom::Affine(view.bbox->width(), 0.0, 0.0, view.bbox->height(), 0, 0); + // Rotate/translate object bounding box to hatch space, find axis-aligned bounding box, this + // ensures the hatch will cover the object. Hatch origin is now at (0, 0). + RenderInfo info; + info.hatch_bbox = *bbox * user2hatch; + info.hatch_origin = Geom::Point(hatch_x, hatch_y); + info.strip_width = strip_width; + info.hatch_to_user = hatch2user; + + // Overflow (uses union of all hatch-path base tiles). + info.overflow_right = 0; + info.overflow_left = 0; + if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { + Geom::Interval bounds = this->bounds(); + if (hatchContentUnits() == HatchUnits::ObjectBoundingBox) { + bounds *= bbox->width(); + } + info.overflow_right = ceil( bounds.min() / strip_width); + info.overflow_left = floor(bounds.max() / strip_width); } - // Tile (hatch space) to user. - Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); + std::cout << "SPHatch::RenderInfo:" << std::endl; + std::cout << " hatch_bbox: " << info.hatch_bbox << std::endl; + std::cout << " hatch_origin: " << info.hatch_origin << std::endl; + std::cout << " overflow_right: " << info.overflow_right << std::endl; + std::cout << " overflow_left: " << info.overflow_left << std::endl; - RenderInfo info; - info.child_transform = content2ps; - info.pattern_to_user_transform = ps2user; - info.tile_rect = hatch_tile; + // OLD OLD + Geom::OptInterval extents = _calculateStripExtents(bbox); + if (extents) { + double tile_x = x(); + double tile_y = y(); + double tile_width = pitch(); + double tile_height = extents->max() - extents->min(); + double tile_rotate = rotate(); + double tile_render_y = extents->min(); + + if (bbox && (hatchUnits() == HatchUnits::ObjectBoundingBox)) { + tile_x *= bbox->width(); + tile_y *= bbox->height(); + tile_width *= bbox->width(); + } - if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { - Geom::Interval bounds = this->bounds(); - double pitch = this->pitch(); - if (view.bbox) { - if (hatchUnits() == HatchUnits::ObjectBoundingBox) { - pitch *= view.bbox->width(); - } - if (hatchContentUnits() == HatchUnits::ObjectBoundingBox) { - bounds *= view.bbox->width(); + // Extent calculated using content units, need to correct. + if (bbox && (hatchContentUnits() == HatchUnits::ObjectBoundingBox)) { + tile_height *= bbox->height(); + tile_render_y *= bbox->height(); + } + + // Pattern size in hatch space + Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height); + + // Content to bbox + Geom::Affine content2ps; + if (bbox && (hatchContentUnits() == HatchUnits::ObjectBoundingBox)) { + content2ps = Geom::Affine(bbox->width(), 0.0, 0.0, bbox->height(), 0, 0); + } + + // Tile (hatch space) to user. + Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); + + info.child_transform = content2ps; + info.pattern_to_user_transform = ps2user; + info.tile_rect = hatch_tile; + + if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { + Geom::Interval bounds = this->bounds(); + double pitch = this->pitch(); + if (bbox) { + if (hatchUnits() == HatchUnits::ObjectBoundingBox) { + pitch *= bbox->width(); + } + if (hatchContentUnits() == HatchUnits::ObjectBoundingBox) { + bounds *= bbox->width(); + } } + double overflow_right_strip = floor(bounds.max() / pitch) * pitch; + info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1; + info.overflow_step_transform = Geom::Translate(pitch, 0.0); + info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0); } - double overflow_right_strip = floor(bounds.max() / pitch) * pitch; - info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1; - info.overflow_step_transform = Geom::Translate(pitch, 0.0); - info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0); } else { info.overflow_steps = 1; } @@ -665,6 +727,82 @@ SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const return info; } +bool is_inside(int winding, SPWindRule wind_rule) { + switch (wind_rule) { + case SP_WIND_RULE_EVENODD: + return winding % 2 !=0; + case SP_WIND_RULE_POSITIVE: + return winding > 0; + case SP_WIND_RULE_NONZERO: + default: + return winding != 0; + } +} + +// Convert a hatch to pathvectors (one for each hatch-path). +// This is particularily useful for creating SVG's for plotters and cutters. +bool SPHatch::toPaths(SPShape &shape) +{ + auto shape_bbox = shape.geometricBounds(); + auto render_info = calculateRenderInfo(shape_bbox); + auto parent = shape.getRepr()->parent(); + auto xml_doc = shape.getRepr()->document(); + + for (auto hatch_path : hatchPaths()) { + auto curve = hatch_path->calculateRenderCurve(render_info.hatch_bbox, render_info.hatch_origin); + std::cout << "toPaths: curve: " << curve << std::endl; + + // Calculate maximum, minimum strip. Overflow is handled by adding strips to the left and right. + auto x_interval = render_info.hatch_bbox[Geom::X]; + auto hatch_origin = render_info.hatch_origin; + auto strip_width = render_info.strip_width; + int strip_min = floor(x_interval.min()/strip_width); + int strip_max = ceil( x_interval.max()/strip_width); + strip_min -= render_info.overflow_left; + strip_max -= render_info.overflow_right; + + Geom::PathVector new_curve; + for (int i = strip_min; i < strip_max; ++i) { + auto temp_curve = curve; + temp_curve *= Geom::Translate(i * strip_width, 0); + pathvector_append(new_curve, temp_curve); + } + + // Curve is in hatch space, transform it to object space. + new_curve *= render_info.hatch_to_user; + + auto shape_curve = shape.curve(); + auto shape_fill_rule = shape.style->fill_rule.computed; + auto shape_fill_rule_livarot = to_livarot(shape_fill_rule); + + // Cut hatch by shape. + auto cut_vector = sp_pathvector_boolop(*shape_curve, new_curve, bool_op_slice, + shape_fill_rule_livarot, fill_oddEven); + + // Find part of hatch inside shape. + Geom::PathVector inside_vector; + for (auto path : cut_vector) { + if (is_inside(shape_curve->winding(path.pointAt(0.5)), shape_fill_rule)) { + inside_vector.push_back(path); + } + } + + // Create new path. + auto new_path = xml_doc->createElement("svg:path"); + new_path->setAttribute("d", sp_svg_write_path(inside_vector)); + new_path->setAttribute("transform", shape.getRepr()->attribute("transform")); + + // Find style + auto style = hatch_path->style; + Glib::ustring style_string = "fill:none;stroke:purple;";// + style->write(); + new_path->setAttribute("style", style_string); + + parent->addChildAtPos(new_path, shape.getRepr()->position()); + Inkscape::GC::release(new_path); + } + return true; +} + //calculates strip extents in content space Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect const &bbox) const { diff --git a/src/object/sp-hatch.h b/src/object/sp-hatch.h index ce13d07814..cb94e63ba2 100644 --- a/src/object/sp-hatch.h +++ b/src/object/sp-hatch.h @@ -57,6 +57,15 @@ public: struct RenderInfo { + // NEW + Geom::Rect hatch_bbox; + double strip_width = 0; + Geom::Point hatch_origin; + Geom::Affine hatch_to_user; + int overflow_right = 0; + int overflow_left = 0; + + // OLD Geom::Affine child_transform; Geom::Affine pattern_to_user_transform; Geom::Rect tile_rect; @@ -95,7 +104,11 @@ public: Inkscape::DrawingPattern *show(Inkscape::Drawing &drawing, unsigned key, Geom::OptRect const &bbox) override; void hide(unsigned key) override; + RenderInfo calculateRenderInfo(Geom::OptRect const &bbox) const; RenderInfo calculateRenderInfo(unsigned key) const; + + bool toPaths(SPShape &shape); + Geom::Interval bounds() const; void setBBox(unsigned int key, Geom::OptRect const &bbox) override; @@ -114,7 +127,7 @@ private: static bool _hasHatchPatchChildren(SPHatch const *hatch); void _updateView(View &view); - RenderInfo _calculateRenderInfo(View const &view) const; + RenderInfo _calculateRenderInfo(Geom::OptRect const &bbox) const; Geom::OptInterval _calculateStripExtents(Geom::OptRect const &bbox) const; /** diff --git a/src/path-chemistry.cpp b/src/path-chemistry.cpp index bc2760627a..140751de6b 100644 --- a/src/path-chemistry.cpp +++ b/src/path-chemistry.cpp @@ -33,6 +33,7 @@ #include "object/box3d.h" #include "object/object-set.h" #include "object/sp-flowtext.h" +#include "object/sp-hatch.h" #include "object/sp-path.h" #include "object/sp-root.h" #include "object/sp-text.h" @@ -374,9 +375,35 @@ static void collect_object_items(SPObject *object, std::vector &items) } } +bool convert_hatch_to_paths(SPShape &shape) +{ + std::cout << "convert_hatch_to_paths: " << shape << std::endl; + auto style = shape.style; + if (!style) { + return false; + } + + if (auto paint = style->getFillOrStroke(true)) { + if (paint->set) { + auto server = style->getFillPaintServer(); + if (auto hatch = cast(server)) { + auto curves = hatch->toPaths(shape); + auto parent = shape.parent; + if (parent) { + std::cout << " Parent: " << parent << std::endl; + } else { + std::cout << " No parent" << std::endl; + } + } + } + } + return true; +} + bool sp_item_list_to_curves(const std::vector &items, std::vector& selected, std::vector &to_select, bool skip_all_lpeitems) { + std::cout << "\nsp_item_list_to_curves: Entrance" << std::endl; bool did = false; for (auto item : items){ g_assert(item != nullptr); @@ -436,6 +463,9 @@ sp_item_list_to_curves(const std::vector &items, std::vector& item->removeAttribute("inkscape:connector-curvature"); did = true; } + if (convert_hatch_to_paths(*cast(item))) { + did = true; + } continue; // already a path, and no path effect } @@ -521,11 +551,16 @@ void Inkscape::convert_text_to_curves(SPDocument *doc) Inkscape::XML::Node * sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) { + std::cout << "\nsp_selected_item_to_curved_repr" << std::endl; if (!item) return nullptr; Inkscape::XML::Document *xml_doc = item->getRepr()->document(); + if (auto shape = cast(item)) { + convert_hatch_to_paths(*shape); + } + if (is(item) || is(item)) { // Special treatment for text: convert each glyph to separate path, then group the paths -- GitLab From b9cafa727c371a9112f9dc19132c55c3d8ed31fe Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Thu, 3 Jul 2025 16:36:09 +0200 Subject: [PATCH 2/2] Hatch to path improvements. Also fix a variety of rendering bugs, especially when hatch units are objectBoundingBox. --- .../internal/cairo-render-context.cpp | 6 +- src/object/sp-hatch-path.cpp | 8 +- src/object/sp-hatch.cpp | 108 ++++++------------ src/object/sp-hatch.h | 15 ++- 4 files changed, 51 insertions(+), 86 deletions(-) diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp index 66a597bb9d..978b442622 100644 --- a/src/extension/internal/cairo-render-context.cpp +++ b/src/extension/internal/cairo-render-context.cpp @@ -1168,7 +1168,7 @@ CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, evil->show(drawing, dkey, pbox); SPHatch::RenderInfo render_info = hatch->calculateRenderInfo(dkey); - Geom::Rect tile_rect = render_info.tile_rect; + Geom::Rect tile_rect = render_info.hatch_tile; // Cairo requires an integer pattern surface width/height. // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel. @@ -1179,7 +1179,7 @@ CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, Geom::Affine drawing_scale = Geom::Scale(surface_width / tile_rect.width(), surface_height / tile_rect.height()); Geom::Affine drawing_transform = Geom::Translate(-tile_rect.min()) * drawing_scale; - Geom::Affine child_transform = render_info.child_transform; + Geom::Affine child_transform = render_info.content_to_hatch; child_transform *= drawing_transform; //The rendering of hatch overflow is implemented by repeated drawing @@ -1219,7 +1219,7 @@ CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT); Geom::Affine pattern_transform; - pattern_transform = render_info.pattern_to_user_transform.inverse() * drawing_transform; + pattern_transform = render_info.hatch_to_user.inverse() * drawing_transform; ink_cairo_pattern_set_matrix(result, pattern_transform); evil->hide(dkey); diff --git a/src/object/sp-hatch-path.cpp b/src/object/sp-hatch-path.cpp index ca4cadd56b..a14ed2d9f6 100644 --- a/src/object/sp-hatch-path.cpp +++ b/src/object/sp-hatch-path.cpp @@ -177,9 +177,8 @@ Geom::PathVector SPHatchPath::calculateRenderCurve(unsigned key) const return {}; } -// Hatch bounding box is box in hatch space that will completely cover -// object. We only use height here. -// Note: Offset not taken into account! +// We construct a curve that will completely cover the height of the hatch bbox +// (scaling for hatchContentUnits happens in SPHatch). Geom::PathVector SPHatchPath::calculateRenderCurve(Geom::Rect const &hatch_bbox, Geom::Point const &hatch_origin) { @@ -214,7 +213,8 @@ Geom::PathVector SPHatchPath::calculateRenderCurve(Geom::Rect const &hatch_bbox, } } } - std::cout << "SPHatchPath::calculateRenderCurve: " << calculated_curve << std::endl; + + calculated_curve *= Geom::Translate(offset.computed, 0); return calculated_curve; } diff --git a/src/object/sp-hatch.cpp b/src/object/sp-hatch.cpp index 515a01fe21..cdd047bb83 100644 --- a/src/object/sp-hatch.cpp +++ b/src/object/sp-hatch.cpp @@ -604,9 +604,9 @@ void SPHatch::_updateView(View &view) //The movement progresses from right to left. This gives the same result //as drawing whole strips in left-to-right order. - view.drawingitem->setChildTransform(info.child_transform); - view.drawingitem->setPatternToUserTransform(info.pattern_to_user_transform); - view.drawingitem->setTileRect(info.tile_rect); + view.drawingitem->setChildTransform(info.content_to_hatch); + view.drawingitem->setPatternToUserTransform(info.hatch_to_user); + view.drawingitem->setTileRect(info.hatch_tile); view.drawingitem->setStyle(style); view.drawingitem->setOverflow(info.overflow_initial_transform, info.overflow_steps, info.overflow_step_transform); } @@ -620,7 +620,6 @@ SPHatch::RenderInfo SPHatch::_calculateRenderInfo(Geom::OptRect const &bbox) con std::cerr << "SPHatch::RenderInfo: no bounding box!" << std::endl; return {}; } - std::cout << "\nSPHatch::_calculateRenderInfo(): bbox: " << bbox << std::endl; // Calculate hatch transformation to user space. double hatch_x = x(); // tile refers to "base" tile. @@ -640,90 +639,47 @@ SPHatch::RenderInfo SPHatch::_calculateRenderInfo(Geom::OptRect const &bbox) con Geom::Affine hatch2user = Geom::Rotate::from_degrees(hatch_rotate) * Geom::Translate(hatch_x, hatch_y) * hatchTransform(); Geom::Affine user2hatch = hatch2user.inverse(); + Geom::Affine content2hatch; + if (hatchContentUnits() == HatchUnits::ObjectBoundingBox) { + content2hatch = Geom::Scale(bbox->width(), bbox->height()); + } + Geom::Affine hatch2content = content2hatch.inverse(); + // Rotate/translate object bounding box to hatch space, find axis-aligned bounding box, this // ensures the hatch will cover the object. Hatch origin is now at (0, 0). RenderInfo info; info.hatch_bbox = *bbox * user2hatch; + info.hatch_tile = Geom::Rect(Geom::Interval(0, strip_width), info.hatch_bbox[Geom::Y]); info.hatch_origin = Geom::Point(hatch_x, hatch_y); info.strip_width = strip_width; info.hatch_to_user = hatch2user; + info.content_to_hatch = content2hatch; // Overflow (uses union of all hatch-path base tiles). - info.overflow_right = 0; - info.overflow_left = 0; if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { Geom::Interval bounds = this->bounds(); if (hatchContentUnits() == HatchUnits::ObjectBoundingBox) { bounds *= bbox->width(); } - info.overflow_right = ceil( bounds.min() / strip_width); - info.overflow_left = floor(bounds.max() / strip_width); + info.overflow_right = floor(bounds.min() / strip_width); // Number of extra strips on right (negative) + info.overflow_left = ceil (bounds.max() / strip_width); // Number of extra strips on left + 1 + + info.overflow_steps = info.overflow_left - info.overflow_right; // Includes base strip. + info.overflow_step_transform = Geom::Translate(strip_width, 0.0); + info.overflow_initial_transform = Geom::Translate((-info.overflow_left + 1) * strip_width, 0.0); + } std::cout << "SPHatch::RenderInfo:" << std::endl; std::cout << " hatch_bbox: " << info.hatch_bbox << std::endl; std::cout << " hatch_origin: " << info.hatch_origin << std::endl; + std::cout << " hatch_to_user: " << info.hatch_to_user << std::endl; + std::cout << " content_to_hatch: " << info.content_to_hatch << std::endl; std::cout << " overflow_right: " << info.overflow_right << std::endl; std::cout << " overflow_left: " << info.overflow_left << std::endl; - - // OLD OLD - Geom::OptInterval extents = _calculateStripExtents(bbox); - if (extents) { - double tile_x = x(); - double tile_y = y(); - double tile_width = pitch(); - double tile_height = extents->max() - extents->min(); - double tile_rotate = rotate(); - double tile_render_y = extents->min(); - - if (bbox && (hatchUnits() == HatchUnits::ObjectBoundingBox)) { - tile_x *= bbox->width(); - tile_y *= bbox->height(); - tile_width *= bbox->width(); - } - - // Extent calculated using content units, need to correct. - if (bbox && (hatchContentUnits() == HatchUnits::ObjectBoundingBox)) { - tile_height *= bbox->height(); - tile_render_y *= bbox->height(); - } - - // Pattern size in hatch space - Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height); - - // Content to bbox - Geom::Affine content2ps; - if (bbox && (hatchContentUnits() == HatchUnits::ObjectBoundingBox)) { - content2ps = Geom::Affine(bbox->width(), 0.0, 0.0, bbox->height(), 0, 0); - } - - // Tile (hatch space) to user. - Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); - - info.child_transform = content2ps; - info.pattern_to_user_transform = ps2user; - info.tile_rect = hatch_tile; - - if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { - Geom::Interval bounds = this->bounds(); - double pitch = this->pitch(); - if (bbox) { - if (hatchUnits() == HatchUnits::ObjectBoundingBox) { - pitch *= bbox->width(); - } - if (hatchContentUnits() == HatchUnits::ObjectBoundingBox) { - bounds *= bbox->width(); - } - } - double overflow_right_strip = floor(bounds.max() / pitch) * pitch; - info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1; - info.overflow_step_transform = Geom::Translate(pitch, 0.0); - info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0); - } - } else { - info.overflow_steps = 1; - } - + std::cout << " overflow_steps: " << info.overflow_steps << std::endl; + std::cout << " overflow_steps: " << info.overflow_step_transform << std::endl; + std::cout << " overflow_init: " << info.overflow_initial_transform << std::endl; return info; } @@ -741,6 +697,7 @@ bool is_inside(int winding, SPWindRule wind_rule) { // Convert a hatch to pathvectors (one for each hatch-path). // This is particularily useful for creating SVG's for plotters and cutters. +// Note: This does not handle CSS visible="none" (which isn't that useful). bool SPHatch::toPaths(SPShape &shape) { auto shape_bbox = shape.geometricBounds(); @@ -749,8 +706,10 @@ bool SPHatch::toPaths(SPShape &shape) auto xml_doc = shape.getRepr()->document(); for (auto hatch_path : hatchPaths()) { - auto curve = hatch_path->calculateRenderCurve(render_info.hatch_bbox, render_info.hatch_origin); - std::cout << "toPaths: curve: " << curve << std::endl; + auto curve = hatch_path->calculateRenderCurve( + render_info.hatch_bbox * render_info.content_to_hatch.inverse(), + render_info.hatch_origin * render_info.content_to_hatch.inverse()); + curve *= render_info.content_to_hatch; // Calculate maximum, minimum strip. Overflow is handled by adding strips to the left and right. auto x_interval = render_info.hatch_bbox[Geom::X]; @@ -760,6 +719,7 @@ bool SPHatch::toPaths(SPShape &shape) int strip_max = ceil( x_interval.max()/strip_width); strip_min -= render_info.overflow_left; strip_max -= render_info.overflow_right; + strip_min++; // overflow_left includes base strip Geom::PathVector new_curve; for (int i = strip_min; i < strip_max; ++i) { @@ -794,7 +754,7 @@ bool SPHatch::toPaths(SPShape &shape) // Find style auto style = hatch_path->style; - Glib::ustring style_string = "fill:none;stroke:purple;";// + style->write(); + Glib::ustring style_string = "fill:none;stroke:purple;stroke-width:3";// + style->write(); new_path->setAttribute("style", style_string); parent->addChildAtPos(new_path, shape.getRepr()->position()); @@ -814,7 +774,13 @@ Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect const &bbox) con double tile_y = y(); double tile_rotate = rotate(); - Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform(); + // Correct for units: + if (hatchUnits() == HatchUnits::ObjectBoundingBox) { + tile_x = tile_x * bbox->width() + bbox->min()[Geom::X]; + tile_y = tile_y * bbox->height() + bbox->min()[Geom::Y]; + } + + Geom::Affine ps2user = Geom::Rotate::from_degrees(tile_rotate) * Geom::Translate(tile_x, tile_y) * hatchTransform(); Geom::Affine user2ps = ps2user.inverse(); Geom::Interval extents; diff --git a/src/object/sp-hatch.h b/src/object/sp-hatch.h index cb94e63ba2..aa469b66ce 100644 --- a/src/object/sp-hatch.h +++ b/src/object/sp-hatch.h @@ -57,20 +57,19 @@ public: struct RenderInfo { - // NEW Geom::Rect hatch_bbox; + Geom::Rect hatch_tile; double strip_width = 0; Geom::Point hatch_origin; Geom::Affine hatch_to_user; - int overflow_right = 0; - int overflow_left = 0; + Geom::Affine content_to_hatch; - // OLD - Geom::Affine child_transform; - Geom::Affine pattern_to_user_transform; - Geom::Rect tile_rect; + // Used by toPath() + int overflow_right = 0; + int overflow_left = 1; - int overflow_steps = 0; + // Used by rendering to screen + int overflow_steps = 1; Geom::Affine overflow_step_transform; Geom::Affine overflow_initial_transform; }; -- GitLab