summaryrefslogtreecommitdiffstats
path: root/src/display/control/canvas-item-bpath.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/display/control/canvas-item-bpath.cpp')
-rw-r--r--src/display/control/canvas-item-bpath.cpp262
1 files changed, 262 insertions, 0 deletions
diff --git a/src/display/control/canvas-item-bpath.cpp b/src/display/control/canvas-item-bpath.cpp
new file mode 100644
index 0000000..6938ac5
--- /dev/null
+++ b/src/display/control/canvas-item-bpath.cpp
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * A class to represent a Bezier path.
+ */
+
+/*
+ * Author:
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * Rewrite of SPCanvasBPath
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "canvas-item-bpath.h"
+
+#include <memory> // unique_ptr
+#include <utility> // std::move
+
+#include "color.h" // SP_RGBA_x_F
+
+#include "display/curve.h"
+#include "display/cairo-utils.h"
+
+#include "helper/geom.h" // bounds_exact_transformed()
+
+#include "ui/widget/canvas.h"
+
+namespace Inkscape {
+
+/**
+ * Create an null control bpath.
+ */
+CanvasItemBpath::CanvasItemBpath(CanvasItemGroup *group)
+ : CanvasItem(group)
+{
+ _name = "CanvasItemBpath:Null";
+ _pickable = true; // For now, nobody gets events from this class!
+}
+
+/**
+ * Create a control bpath. Curve is in document coordinates.
+ */
+CanvasItemBpath::CanvasItemBpath(CanvasItemGroup *group, SPCurve *curve, bool phantom_line)
+ : CanvasItem(group)
+ , _phantom_line(phantom_line)
+{
+ _name = "CanvasItemBpath";
+ _pickable = true; // For now, everyone gets events from this class!
+ if (curve) {
+ _path = curve->get_pathvector();
+ }
+
+ request_update(); // Render immediately or temporary bpaths won't show.
+}
+
+/**
+ * Create a control bpath. Path is in document coordinates.
+ */
+CanvasItemBpath::CanvasItemBpath(CanvasItemGroup *group, Geom::PathVector path, bool phantom_line)
+ : CanvasItem(group)
+ , _path(std::move(path))
+ , _phantom_line(phantom_line)
+{
+ _name = "CanvasItemBpath";
+ _pickable = true; // For now, everyone gets events from this class!
+ request_update(); // Render immediately or temporary bpaths won't show.
+}
+
+/**
+ * Set a control bpath. Curve is in document coordinates.
+ */
+void CanvasItemBpath::set_bpath(SPCurve *curve, bool phantom_line)
+{
+ if (curve) { // No test to see if *curve == *_curve so we always do the swap.
+ _path = curve->get_pathvector();
+ } else {
+ _path.clear();
+ }
+
+ _phantom_line = phantom_line;
+
+ request_update();
+}
+
+/**
+ * Set a control bpath. Path is in document coordinates.
+ */
+void CanvasItemBpath::set_bpath(Geom::PathVector const &path, bool phantom_line)
+{
+ _path = path;
+ _phantom_line = phantom_line;
+
+ request_update();
+}
+
+/**
+ * Set the fill color and fill rule.
+ */
+void CanvasItemBpath::set_fill(guint rgba, SPWindRule fill_rule)
+{
+ if (_fill != rgba || _fill_rule != fill_rule) {
+ _fill = rgba;
+ _fill_rule = fill_rule;
+ request_redraw();
+ }
+}
+
+/**
+ * Returns distance between point in canvas units and nearest point on bpath.
+ */
+double CanvasItemBpath::closest_distance_to(Geom::Point const &p)
+{
+ double d = Geom::infinity();
+
+ // Convert p to document coordinates (quicker than converting path to canvas units).
+ Geom::Point p_doc = p * _affine.inverse();
+ _path.nearestTime(p_doc, &d);
+ d *= _affine.descrim(); // Uniform scaling and rotation only.
+
+ return d;
+}
+
+/**
+ * Returns true if point p (in canvas units) is within tolerance (canvas units) distance of bpath.
+ */
+bool CanvasItemBpath::contains(Geom::Point const &p, double tolerance)
+{
+ if (tolerance == 0) {
+ tolerance = 1; // Need a minimum tolerance value or always returns false.
+ }
+
+ return closest_distance_to(p) < tolerance;
+}
+
+/**
+ * Update and redraw control bpath.
+ */
+void CanvasItemBpath::update(Geom::Affine const &affine)
+{
+ if (_affine == affine && !_need_update) {
+ // Nothing to do.
+ return;
+ }
+
+ // Queue redraw of old area (erase previous content).
+ request_redraw();
+
+ // Get new bounds
+ _affine = affine;
+
+ _bounds = Geom::Rect(); // Reset bounds
+
+ if (_path.empty()) return; // No path, no chocolate!
+
+ Geom::OptRect bbox = bounds_exact_transformed(_path, _affine);
+
+ if (bbox) {
+ _bounds = *bbox;
+ _bounds.expandBy(2);
+ } else {
+ _bounds = Geom::Rect();
+ }
+
+ // Queue redraw of new area
+ request_redraw();
+
+ _need_update = false;
+}
+
+/**
+ * Render bpath to screen via Cairo.
+ */
+void CanvasItemBpath::render(Inkscape::CanvasItemBuffer *buf)
+{
+ if (!buf) {
+ std::cerr << "CanvasItemBpath::Render: No buffer!" << std::endl;
+ return;
+ }
+
+ if (!_visible) {
+ // Hidden
+ return;
+ }
+
+ if (_path.empty()) {
+ return;
+ }
+
+ bool do_fill = (_fill & 0xff) != 0; // Not invisible.
+ bool do_stroke = (_stroke & 0xff) != 0; // Not invisible.
+
+ if (!do_fill && !do_stroke) {
+ // Both fill and stroke invisible.
+ return;
+ }
+
+ buf->cr->save();
+
+ // Setup path
+ buf->cr->set_tolerance(0.5);
+ buf->cr->begin_new_path();
+
+ feed_pathvector_to_cairo (buf->cr->cobj(), _path, _affine, buf->rect,
+ /* optimized_stroke = */ !do_fill, 1);
+
+ // Do fill
+ if (do_fill) {
+ buf->cr->set_source_rgba(SP_RGBA32_R_F(_fill), SP_RGBA32_G_F(_fill),
+ SP_RGBA32_B_F(_fill), SP_RGBA32_A_F(_fill));
+ buf->cr->set_fill_rule(_fill_rule == SP_WIND_RULE_EVENODD ?
+ Cairo::FILL_RULE_EVEN_ODD : Cairo::FILL_RULE_WINDING);
+ buf->cr->fill_preserve();
+ }
+
+ // Do stroke
+ if (do_stroke) {
+
+ if (!_dashes.empty()) {
+ buf->cr->set_dash(_dashes, 0.0); // 0.0 is offset
+ }
+
+ if (_phantom_line) {
+ buf->cr->set_source_rgba(1.0, 1.0, 1.0, 0.25);
+ buf->cr->set_line_width(2.0);
+ buf->cr->stroke_preserve();
+ }
+
+ buf->cr->set_source_rgba(SP_RGBA32_R_F(_stroke), SP_RGBA32_G_F(_stroke),
+ SP_RGBA32_B_F(_stroke), SP_RGBA32_A_F(_stroke));
+ buf->cr->set_line_width(1.0);
+ buf->cr->stroke();
+
+ } else {
+ buf->cr->begin_new_path(); // Clears path
+ }
+
+ // Uncomment to show bounds
+ // Geom::Rect bounds = _bounds;
+ // bounds.expandBy(-1);
+ // bounds -= buf->rect.min();
+ // buf->cr->set_source_rgba(1.0, 0.0, 0.0, 1.0);
+ // buf->cr->rectangle(bounds.min().x(), bounds.min().y(), bounds.width(), bounds.height());
+ // buf->cr->stroke();
+
+ buf->cr->restore();
+}
+
+} // namespace Inkscape
+
+/*
+ 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 :