summaryrefslogtreecommitdiffstats
path: root/src/object/sp-hatch-path.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/object/sp-hatch-path.cpp285
1 files changed, 285 insertions, 0 deletions
diff --git a/src/object/sp-hatch-path.cpp b/src/object/sp-hatch-path.cpp
new file mode 100644
index 0000000..6cf0cdb
--- /dev/null
+++ b/src/object/sp-hatch-path.cpp
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * SVG <hatchPath> implementation
+ */
+/*
+ * Author:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2014 Tomasz Boczkowski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <2geom/path.h>
+
+#include "style.h"
+#include "svg/svg.h"
+#include "display/curve.h"
+#include "display/drawing.h"
+#include "display/drawing-shape.h"
+#include "helper/geom.h"
+#include "attributes.h"
+#include "sp-item.h"
+#include "sp-hatch-path.h"
+#include "svg/css-ostringstream.h"
+
+SPHatchPath::SPHatchPath() = default;
+
+SPHatchPath::~SPHatchPath() = default;
+
+void SPHatchPath::build(SPDocument *doc, Inkscape::XML::Node *repr)
+{
+ SPObject::build(doc, repr);
+
+ readAttr(SPAttr::D);
+ readAttr(SPAttr::OFFSET);
+ readAttr(SPAttr::STYLE);
+
+ style->fill.setNone();
+}
+
+void SPHatchPath::release()
+{
+ views.clear();
+ SPObject::release();
+}
+
+void SPHatchPath::set(SPAttr key, gchar const *value)
+{
+ switch (key) {
+ case SPAttr::D:
+ if (value) {
+ Geom::PathVector pv;
+ _readHatchPathVector(value, pv, _continuous);
+ _curve.emplace(std::move(pv));
+ } else {
+ _curve.reset();
+ }
+
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SPAttr::OFFSET:
+ offset.readOrUnset(value);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ default:
+ if (SP_ATTRIBUTE_IS_CSS(key)) {
+ style->clear(key);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
+ } else {
+ SPObject::set(key, value);
+ }
+ break;
+ }
+}
+
+void SPHatchPath::update(SPCtx *ctx, unsigned int flags)
+{
+ if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
+ flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B;
+ }
+
+ if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
+ if (style->stroke_width.unit == SP_CSS_UNIT_PERCENT) {
+ //TODO: Check specification
+
+ SPItemCtx *ictx = static_cast<SPItemCtx *>(ctx);
+ double const aw = (ictx) ? 1.0 / ictx->i2vp.descrim() : 1.0;
+ style->stroke_width.computed = style->stroke_width.value * aw;
+
+ for (auto &v : views) {
+ v.drawingitem->setStyle(style);
+ }
+ }
+ }
+
+ if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
+ for (auto &v : views) {
+ _updateView(v);
+ }
+ }
+}
+
+bool SPHatchPath::isValid() const
+{
+ return !_curve || _repeatLength() > 0;
+}
+
+Inkscape::DrawingItem *SPHatchPath::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptInterval extents)
+{
+ views.emplace_back(make_drawingitem<Inkscape::DrawingShape>(drawing), extents, key);
+ auto &v = views.back();
+ auto s = v.drawingitem.get();
+
+ _updateView(v);
+
+ return s;
+}
+
+void SPHatchPath::hide(unsigned int key)
+{
+ auto it = std::find_if(views.begin(), views.end(), [=] (auto &v) {
+ return v.key == key;
+ });
+
+ if (it != views.end()) {
+ views.erase(it);
+ return;
+ }
+
+ g_assert_not_reached();
+}
+
+void SPHatchPath::setStripExtents(unsigned int key, Geom::OptInterval const &extents)
+{
+ for (auto &v : views) {
+ if (v.key == key) {
+ v.extents = extents;
+ break;
+ }
+ }
+}
+
+Geom::Interval SPHatchPath::bounds() const
+{
+ Geom::OptRect bbox;
+ Geom::Interval result;
+
+ Geom::Affine transform = Geom::Translate(offset.computed, 0);
+ if (!_curve) {
+ SPCurve test_curve;
+ test_curve.moveto(Geom::Point(0, 0));
+ test_curve.moveto(Geom::Point(0, 1));
+ bbox = bounds_exact_transformed(test_curve.get_pathvector(), transform);
+ } else {
+ bbox = bounds_exact_transformed(_curve->get_pathvector(), transform);
+ }
+
+ double stroke_width = style->stroke_width.computed;
+ result.setMin(bbox->left() - stroke_width / 2);
+ result.setMax(bbox->right() + stroke_width / 2);
+ return result;
+}
+
+SPCurve SPHatchPath::calculateRenderCurve(unsigned key) const
+{
+ for (auto const &v : views) {
+ if (v.key == key) {
+ return _calculateRenderCurve(v);
+ }
+ }
+ g_assert_not_reached();
+ return SPCurve{};
+}
+
+gdouble SPHatchPath::_repeatLength() const
+{
+ gdouble val = 0;
+
+ if (_curve && _curve->last_point()) {
+ val = _curve->last_point()->y();
+ }
+
+ return val;
+}
+
+void SPHatchPath::_updateView(View &view)
+{
+ auto calculated_curve = _calculateRenderCurve(view);
+
+ Geom::Affine offset_transform = Geom::Translate(offset.computed, 0);
+ view.drawingitem->setTransform(offset_transform);
+ style->fill.setNone();
+ view.drawingitem->setStyle(style);
+ view.drawingitem->setPath(std::make_shared<SPCurve>(std::move(calculated_curve)));
+}
+
+SPCurve SPHatchPath::_calculateRenderCurve(View const &view) const
+{
+ SPCurve calculated_curve;
+
+ if (!view.extents) {
+ return calculated_curve;
+ }
+
+ if (!_curve) {
+ calculated_curve.moveto(0, view.extents->min());
+ calculated_curve.lineto(0, view.extents->max());
+ //TODO: if hatch has a dasharray defined, adjust line ends
+ } else {
+ gdouble repeatLength = _repeatLength();
+ if (repeatLength > 0) {
+ gdouble initial_y = floor(view.extents->min() / repeatLength) * repeatLength;
+ int segment_cnt = ceil((view.extents->extent()) / repeatLength) + 1;
+
+ auto segment = *_curve;
+ segment.transform(Geom::Translate(0, initial_y));
+
+ Geom::Affine step_transform = Geom::Translate(0, repeatLength);
+ for (int i = 0; i < segment_cnt; ++i) {
+ if (_continuous) {
+ calculated_curve.append_continuous(segment);
+ } else {
+ calculated_curve.append(segment);
+ }
+ segment.transform(step_transform);
+ }
+ }
+ }
+ return calculated_curve;
+}
+
+void SPHatchPath::_readHatchPathVector(char const *str, Geom::PathVector &pathv, bool &continous_join)
+{
+ if (!str) {
+ return;
+ }
+
+ pathv = sp_svg_read_pathv(str);
+
+ if (!pathv.empty()) {
+ continous_join = false;
+ } else {
+ Glib::ustring str2 = Glib::ustring::compose("M0,0 %1", str);
+ pathv = sp_svg_read_pathv(str2.c_str());
+ if (pathv.empty()) {
+ return;
+ }
+
+ gdouble last_point_x = pathv.back().finalPoint().x();
+ Inkscape::CSSOStringStream stream;
+ stream << last_point_x;
+ Glib::ustring str3 = Glib::ustring::compose("M%1,0 %2", stream.str(), str);
+ Geom::PathVector pathv3 = sp_svg_read_pathv(str3.c_str());
+
+ //Path can be composed of relative commands only. In this case final point
+ //coordinates would depend on first point position. If this happens, fall
+ //back to using 0,0 as first path point
+ if (pathv3.back().finalPoint().y() == pathv.back().finalPoint().y()) {
+ pathv = pathv3;
+ }
+ continous_join = true;
+ }
+}
+
+SPHatchPath::View::View(DrawingItemPtr<Inkscape::DrawingShape> drawingitem, Geom::OptInterval const &extents, unsigned key)
+ : drawingitem(std::move(drawingitem))
+ , extents(extents)
+ , key(key) {}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+ */
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :