summaryrefslogtreecommitdiffstats
path: root/src/display/control/canvas-item-ctrl.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/display/control/canvas-item-ctrl.cpp
parentInitial commit. (diff)
downloadinkscape-upstream/1.2.2.tar.xz
inkscape-upstream/1.2.2.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/display/control/canvas-item-ctrl.cpp')
-rw-r--r--src/display/control/canvas-item-ctrl.cpp1221
1 files changed, 1221 insertions, 0 deletions
diff --git a/src/display/control/canvas-item-ctrl.cpp b/src/display/control/canvas-item-ctrl.cpp
new file mode 100644
index 0000000..f2696c8
--- /dev/null
+++ b/src/display/control/canvas-item-ctrl.cpp
@@ -0,0 +1,1221 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * A class to represent a control node.
+ */
+
+/*
+ * Author:
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2020 Tavmjong Bah
+ *
+ * Rewrite of SPCtrl
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/transforms.h>
+
+#include "canvas-item-ctrl.h"
+
+#include "preferences.h" // Default size.
+#include "display/cairo-utils.h" // argb32_from_rgba()
+
+#include "ui/widget/canvas.h"
+
+namespace Inkscape {
+
+CanvasItemCtrl::~CanvasItemCtrl()
+{
+ delete[] _cache;
+}
+
+/**
+ * Create an null control node.
+ */
+CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group)
+ : CanvasItem(group)
+{
+ _name = "CanvasItemCtrl:Null";
+ _pickable = true; // Everybody gets events from this class!
+}
+
+/**
+ * Create a control ctrl. Shape auto-set by type.
+ */
+CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlType type)
+ : CanvasItem(group)
+ , _type(type)
+{
+ _name = "CanvasItemCtrl:Type_" + std::to_string(_type);
+ _pickable = true; // Everybody gets events from this class!
+
+ // Use _type to set default values:
+ set_shape_default();
+ set_size_default();
+}
+
+/**
+ * Create a control ctrl. Point is in document coordinates.
+ */
+CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlType type, Geom::Point const &p)
+ : CanvasItemCtrl(group, type)
+{
+ _position = p;
+}
+
+
+/**
+ * Create a control ctrl.
+ */
+CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlShape shape)
+ : CanvasItem(group)
+ , _shape(shape)
+ , _type(CANVAS_ITEM_CTRL_TYPE_DEFAULT)
+{
+ _name = "CanvasItemCtrl:Shape_" + std::to_string(_shape);
+ _pickable = true; // Everybody gets events from this class!
+}
+
+
+/**
+ * Create a control ctrl. Point is in document coordinates.
+ */
+CanvasItemCtrl::CanvasItemCtrl(CanvasItemGroup *group, Inkscape::CanvasItemCtrlShape shape, Geom::Point const &p)
+ : CanvasItemCtrl(group, shape)
+{
+ _position = p;
+ request_update();
+}
+
+/**
+ * Set the position. Point is in document coordinates.
+ */
+void CanvasItemCtrl::set_position(Geom::Point const &position)
+{
+ // std::cout << "CanvasItemCtrl::set_ctrl: " << _name << ": " << position << std::endl;
+ if (_position != position) {
+ _position = position;
+ request_update();
+ }
+}
+
+/**
+ * Returns distance between point in canvas units and position of ctrl.
+ */
+double CanvasItemCtrl::closest_distance_to(Geom::Point const &p)
+{
+ // TODO: Different criteria for different shapes.
+ Geom::Point position = _position * _affine;
+ return Geom::distance(p, position);
+}
+
+/**
+ * If tolerance is zero, returns true if point p (in canvas units) is inside bounding box,
+ * else returns true if p (in canvas units) is within tolerance (canvas units) distance of ctrl.
+ * The latter assumes ctrl center anchored.
+ */
+bool CanvasItemCtrl::contains(Geom::Point const &p, double tolerance)
+{
+ // TODO: Different criteria for different shapes.
+ if (tolerance == 0) {
+ return _bounds.interiorContains(p);
+ } else {
+ return closest_distance_to(p) <= tolerance;
+ }
+}
+
+/**
+ * Update and redraw control ctrl.
+ */
+void CanvasItemCtrl::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;
+
+ // We must be pixel aligned, width and height are always odd.
+ _bounds = Geom::Rect::from_xywh(-(_width/2.0 - 0.5), -(_height/2.0 - 0.5), _width, _height);
+
+ // Adjust for anchor
+ int w_half = _width/2;
+ int h_half = _height/2;
+ int dx = 0;
+ int dy = 0;
+
+ switch (_shape) {
+ case CANVAS_ITEM_CTRL_SHAPE_DARROW:
+ case CANVAS_ITEM_CTRL_SHAPE_SARROW:
+ case CANVAS_ITEM_CTRL_SHAPE_CARROW:
+ case CANVAS_ITEM_CTRL_SHAPE_SALIGN:
+ case CANVAS_ITEM_CTRL_SHAPE_CALIGN:
+ {
+
+ _angle = _anchor * M_PI/4.0 + std::atan2(_affine[1], _affine[0]);
+
+ double half = _width/2.0;
+
+ dx = - (half + 2) * cos(_angle); // Add a bit to prevent tip from overlapping due to rounding errors.
+ dy = - (half + 2) * sin(_angle);
+
+ switch (_shape) {
+
+ case CANVAS_ITEM_CTRL_SHAPE_CARROW:
+ _angle += 5 * M_PI/4.0;
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_SARROW:
+ _angle += M_PI/2.0;
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_SALIGN:
+ dx = - (half/2 + 2) * cos(_angle);
+ dy = - (half/2 + 2) * sin(_angle);
+ _angle -= M_PI/2.0;
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_CALIGN:
+ _angle -= M_PI/4.0;
+ dx = (half/2 + 2) * ( sin(_angle) - cos(_angle));
+ dy = (half/2 + 2) * (-sin(_angle) - cos(_angle));
+ break;
+
+ default:
+ break;
+ }
+
+ _built = false; // Angle may have change, must rebuild!
+
+ break;
+ }
+
+ case CANVAS_ITEM_CTRL_SHAPE_PIVOT:
+ case CANVAS_ITEM_CTRL_SHAPE_MALIGN:
+
+ _angle = std::atan2(_affine[1], _affine[0]);
+
+ _built = false; // Angle may have change, must rebuild!
+
+ break;
+
+ default:
+
+ switch (_anchor) {
+ case SP_ANCHOR_N:
+ case SP_ANCHOR_CENTER:
+ case SP_ANCHOR_S:
+ break;
+
+ case SP_ANCHOR_NW:
+ case SP_ANCHOR_W:
+ case SP_ANCHOR_SW:
+ dx = w_half;
+ break;
+
+ case SP_ANCHOR_NE:
+ case SP_ANCHOR_E:
+ case SP_ANCHOR_SE:
+ dx = -w_half;
+ break;
+ }
+
+ switch (_anchor) {
+ case SP_ANCHOR_W:
+ case SP_ANCHOR_CENTER:
+ case SP_ANCHOR_E:
+ break;
+
+ case SP_ANCHOR_NW:
+ case SP_ANCHOR_N:
+ case SP_ANCHOR_NE:
+ dy = h_half;
+ break;
+
+ case SP_ANCHOR_SW:
+ case SP_ANCHOR_S:
+ case SP_ANCHOR_SE:
+ dy = -h_half;
+ break;
+ }
+ break;
+ }
+
+ _bounds *= Geom::Translate(Geom::IntPoint(dx, dy));
+
+ // Position must also be integer.
+ Geom::Point position = _position * _affine;
+ Geom::IntPoint iposition = position.floor();
+
+ _bounds *= Geom::Translate(iposition);
+
+ // Queue redraw of new area
+ request_redraw();
+
+ _need_update = false;
+}
+
+
+static inline guint32 compose_xor(guint32 bg, guint32 fg, guint32 a)
+{
+ guint32 c = bg * (255-a) + (((bg ^ ~fg) + (bg >> 2) - (bg > 127 ? 63 : 0)) & 255) * a;
+ return (c + 127) / 255;
+}
+
+/**
+ * Render ctrl to screen via Cairo.
+ */
+void CanvasItemCtrl::render(Inkscape::CanvasItemBuffer *buf)
+{
+ if (!buf) {
+ std::cerr << "CanvasItemCtrl::Render: No buffer!" << std::endl;
+ return;
+ }
+
+ if (!_bounds.intersects(buf->rect)) {
+ return; // Control not inside buffer rectangle.
+ }
+
+ if (!_visible) {
+ return; // Hidden.
+ }
+
+ if (!_built) {
+ build_cache(buf->device_scale);
+ }
+
+ Geom::Point c = _bounds.min() - buf->rect.min();
+ int x = c.x(); // Must be pixel aligned.
+ int y = c.y();
+
+ buf->cr->save();
+
+ // This code works regardless of source type.
+
+ // 1. Copy the affected part of output to a temporary surface
+
+ // Size in device pixels. Does not set device scale.
+ int width = _width * buf->device_scale;
+ int height = _height * buf->device_scale;
+ auto work = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width, height);
+ cairo_surface_set_device_scale(work->cobj(), buf->device_scale, buf->device_scale); // No C++ API!
+
+ auto cr = Cairo::Context::create(work);
+ cr->translate(-_bounds.left(), -_bounds.top());
+ cr->set_source(buf->cr->get_target(), buf->rect.left(), buf->rect.top());
+ cr->paint();
+ // static int a = 0;
+ // std::string name0 = "ctrl0_" + _name + "_" + std::to_string(a++) + ".png";
+ // work->write_to_png(name0);
+
+ // 2. Composite the control on a temporary surface
+ work->flush();
+ int strideb = work->get_stride();
+ unsigned char *pxb = work->get_data();
+
+ // this code allow background become isolated from rendering so we can do things like outline overlay
+ cairo_pattern_t *pattern = _canvas->get_background_pattern()->cobj();
+ guint32 backcolor = ink_cairo_pattern_get_argb32(pattern);
+ guint32 *p = _cache;
+ for (int i = 0; i < height; ++i) {
+ guint32 *pb = reinterpret_cast<guint32*>(pxb + i*strideb);
+ for (int j = 0; j < width; ++j) {
+ guint32 base = *pb;
+ guint32 cc = *p++;
+ guint32 ac = cc & 0xff;
+ if (*pb == 0 && cc != 0) {
+ base = backcolor;
+ }
+ if (ac == 0 && cc != 0) {
+ *pb++ = argb32_from_rgba(cc | 0x000000ff);
+ } else if (ac == 0) {
+ *pb++ = base;
+ } else if (
+ _mode == CANVAS_ITEM_CTRL_MODE_XOR ||
+ _mode == CANVAS_ITEM_CTRL_MODE_GRAYSCALED_XOR ||
+ _mode == CANVAS_ITEM_CTRL_MODE_DESATURATED_XOR)
+ {
+ EXTRACT_ARGB32(base, ab,rb,gb,bb)
+ // here we get canvas color and if color to draw
+ // has opacity, we override base colors
+ // flattenig canvas color
+ EXTRACT_ARGB32(backcolor, abb,rbb,gbb,bbb)
+ if (abb != ab) {
+ rb = (ab/255.0) * rb + (1-(ab/255.0)) * rbb;
+ gb = (ab/255.0) * gb + (1-(ab/255.0)) * gbb;
+ bb = (ab/255.0) * bb + (1-(ab/255.0)) * bbb;
+ ab = 255;
+ }
+ guint32 ro = compose_xor(rb, (cc & 0xff000000) >> 24, ac);
+ guint32 go = compose_xor(gb, (cc & 0x00ff0000) >> 16, ac);
+ guint32 bo = compose_xor(bb, (cc & 0x0000ff00) >> 8, ac);
+ if (_mode == CANVAS_ITEM_CTRL_MODE_GRAYSCALED_XOR ||
+ _mode == CANVAS_ITEM_CTRL_MODE_DESATURATED_XOR) {
+ guint32 gray = ro * 0.299 + go * 0.587 + bo * 0.114;
+ if (_mode == CANVAS_ITEM_CTRL_MODE_DESATURATED_XOR) {
+ double f = 0.85; // desaturate by 15%
+ double p = sqrt(ro * ro * 0.299 + go * go * 0.587 + bo * bo * 0.114);
+ ro = p + (ro - p) * f;
+ go = p + (go - p) * f;
+ bo = p + (bo - p) * f;
+ } else {
+ ro = gray;
+ go = gray;
+ bo = gray;
+ }
+ }
+ ASSEMBLE_ARGB32(px, ab,ro,go,bo)
+ *pb++ = px;
+ } else {
+ *pb++ = argb32_from_rgba(cc | 0x000000ff);
+ }
+ }
+ }
+ work->mark_dirty();
+ // std::string name1 = "ctrl1_" + _name + "_" + std::to_string(a) + ".png";
+ // work->write_to_png(name1);
+
+ // 3. Replace the affected part of output with contents of temporary surface
+ buf->cr->set_source(work, x, y);
+
+
+
+ buf->cr->rectangle(x, y, _width, _height);
+ buf->cr->clip();
+ buf->cr->set_operator(Cairo::OPERATOR_SOURCE);
+ buf->cr->paint();
+ buf->cr->restore();
+}
+
+void CanvasItemCtrl::set_fill(guint32 rgba)
+{
+ if (_fill != rgba) {
+ _fill = rgba;
+ _built = false;
+ request_redraw();
+ }
+}
+
+void CanvasItemCtrl::set_stroke(guint32 rgba)
+{
+ if (_stroke != rgba) {
+ _stroke = rgba;
+ _built = false;
+ request_redraw();
+ }
+}
+
+void CanvasItemCtrl::set_shape(int shape)
+{
+ if (_shape != shape) {
+ _shape = Inkscape::CanvasItemCtrlShape(shape); // Fixme
+ _built = false;
+ request_update(); // Geometry could change
+ }
+}
+
+void CanvasItemCtrl::set_shape_default()
+{
+ switch (_type) {
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_DARROW;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_SARROW;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_CARROW;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_PIVOT;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_SALIGN;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_CALIGN;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_MALIGN;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_NODE_AUTO:
+ case CANVAS_ITEM_CTRL_TYPE_ROTATE:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_CIRCLE;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_CENTER:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_PLUS;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_SHAPER:
+ case CANVAS_ITEM_CTRL_TYPE_LPE:
+ case CANVAS_ITEM_CTRL_TYPE_NODE_CUSP:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_DIAMOND;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_POINT:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_CROSS;
+ break;
+
+ default:
+ _shape = CANVAS_ITEM_CTRL_SHAPE_SQUARE;
+ }
+}
+
+void CanvasItemCtrl::set_mode(int mode)
+{
+ if (_mode != mode) {
+ _mode = Inkscape::CanvasItemCtrlMode(mode); // Fixme
+ _built = false;
+ request_update();
+ }
+}
+
+void CanvasItemCtrl::set_pixbuf(GdkPixbuf *pixbuf)
+{
+ if (_pixbuf != pixbuf) {
+ _pixbuf = pixbuf;
+ _width = gdk_pixbuf_get_width(pixbuf);
+ _height = gdk_pixbuf_get_height(pixbuf);
+ _built = false;
+ request_update();
+ }
+}
+
+// Nominally width == height == size except possibly for pixmaps.
+void CanvasItemCtrl::set_size(int size)
+{
+ if (_pixbuf) {
+ // std::cerr << "CanvasItemCtrl::set_size: Attempting to set size on pixbuf control!" << std::endl;
+ return;
+ }
+ if (_width != size + _extra || _height != size + _extra) {
+ _width = size + _extra;
+ _height = size + _extra;
+ _built = false;
+ request_update(); // Geometry change
+ }
+}
+
+void CanvasItemCtrl::set_size_via_index(int size_index)
+{
+ // Size must always be an odd number to center on pixel.
+
+ if (size_index < 1 || size_index > 15) {
+ std::cerr << "CanvasItemCtrl::set_size_via_index: size_index out of range!" << std::endl;
+ size_index = 3;
+ }
+
+ int size = 0;
+ switch (_type) {
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE:
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW:
+ size = size_index * 2 + 7;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE:
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER:
+ size = size_index * 2 + 9; // 2 larger than HANDLE/SKEW
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN:
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN:
+ case CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN:
+ size = size_index * 4 + 5; // Needs to be larger to allow for rotating.
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_POINT:
+ case CANVAS_ITEM_CTRL_TYPE_ROTATE:
+ case CANVAS_ITEM_CTRL_TYPE_CENTER:
+ case CANVAS_ITEM_CTRL_TYPE_SIZER:
+ case CANVAS_ITEM_CTRL_TYPE_SHAPER:
+ case CANVAS_ITEM_CTRL_TYPE_LPE:
+ case CANVAS_ITEM_CTRL_TYPE_NODE_AUTO:
+ case CANVAS_ITEM_CTRL_TYPE_NODE_CUSP:
+ size = size_index * 2 + 5;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_NODE_SMOOTH:
+ case CANVAS_ITEM_CTRL_TYPE_NODE_SYMETRICAL:
+ size = size_index * 2 + 3;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_INVISIPOINT:
+ size = 1;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_ANCHOR: // vanishing point for 3D box and anchor for pencil
+ size = size_index * 2 + 1;
+ break;
+
+ case CANVAS_ITEM_CTRL_TYPE_DEFAULT:
+ size = size_index * 2 + 1;
+ break;
+
+ default:
+ g_warning("set_size_via_index: missing case for handle type: %d", static_cast<int>(_type));
+ size = size_index * 2 + 1;
+ break;
+ }
+
+ set_size(size);
+}
+
+void CanvasItemCtrl::set_size_default()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int size = prefs->getIntLimited("/options/grabsize/value", 3, 1, 15);
+ set_size_via_index(size);
+}
+
+void CanvasItemCtrl::set_size_extra(int extra)
+{
+ if (_extra != extra && _pixbuf == nullptr) { // Don't enlarge pixbuf!
+ _width += (extra - _extra);
+ _height += (extra - _extra);
+ _extra = extra;
+ _built = false;
+ request_update(); // Geometry change
+ }
+}
+
+void CanvasItemCtrl::set_type(Inkscape::CanvasItemCtrlType type)
+{
+ if (_type != type) {
+ _type = type;
+
+ // Use _type to set default values:
+ set_shape_default();
+ set_size_default();
+ _built = false;
+ request_update(); // Possible Geometry change
+ }
+}
+
+void CanvasItemCtrl::set_angle(double angle)
+{
+ if (_angle != angle) {
+ _angle = angle;
+ request_update(); // Geometry change
+ }
+}
+
+void CanvasItemCtrl::set_anchor(SPAnchorType anchor)
+{
+ if (_anchor != anchor) {
+ _anchor = anchor;
+ request_update(); // Geometry change
+ }
+}
+
+// ---------- Protected ----------
+
+// Helper function for build_cache():
+bool point_inside_triangle(Geom::Point p1,Geom::Point p2,Geom::Point p3, Geom::Point point){
+ using Geom::X;
+ using Geom::Y;
+ double denominator = (p1[X]*(p2[Y] - p3[Y]) + p1[Y]*(p3[X] - p2[X]) + p2[X]*p3[Y] - p2[Y]*p3[X]);
+ double t1 = (point[X]*(p3[Y] - p1[Y]) + point[Y]*(p1[X] - p3[X]) - p1[X]*p3[Y] + p1[Y]*p3[X]) / denominator;
+ double t2 = (point[X]*(p2[Y] - p1[Y]) + point[Y]*(p1[X] - p2[X]) - p1[X]*p2[Y] + p1[Y]*p2[X]) / -denominator;
+ double see = t1 + t2;
+ return 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1 && see <= 1;
+}
+
+
+void draw_darrow(Cairo::RefPtr<Cairo::Context>cr, double size) {
+
+ // Find points, starting from tip of one arrowhead, working clockwise.
+ /* 1 4
+ ╱│ │╲
+ ╱ └────────┘ ╲
+ 0╱ 2 3 ╲5
+ ╲ 8 7 ╱
+ ╲ ┌────────┐ ╱
+ ╲│9 6│╱
+ */
+
+ // Length of arrowhead (not including stroke).
+ double delta = (size-1)/4.0; // Use unscaled width.
+
+ // Tip of arrow (0)
+ double tip_x = 0.5; // At edge, allow room for stroke.
+ double tip_y = size/2.0; // Center, assuming width == height.
+
+ // Outer corner (1)
+ double out_x = tip_x + delta;
+ double out_y = tip_y - delta;
+
+ // Inner corner (2)
+ double in_x = out_x;
+ double in_y = out_y + (delta/2.0);
+
+ double x0 = tip_x; double y0 = tip_y;
+ double x1 = out_x; double y1 = out_y;
+ double x2 = in_x; double y2 = in_y;
+ double x3 = size - in_x; double y3 = in_y;
+ double x4 = size - out_x; double y4 = out_y;
+ double x5 = size - tip_x; double y5 = tip_y;
+ double x6 = size - out_x; double y6 = size - out_y;
+ double x7 = size - in_x; double y7 = size - in_y;
+ double x8 = in_x; double y8 = size - in_y;
+ double x9 = out_x; double y9 = size - out_y;
+
+ // Draw arrow
+ cr->move_to(x0, y0);
+ cr->line_to(x1, y1);
+ cr->line_to(x2, y2);
+ cr->line_to(x3, y3);
+ cr->line_to(x4, y4);
+ cr->line_to(x5, y5);
+ cr->line_to(x6, y6);
+ cr->line_to(x7, y7);
+ cr->line_to(x8, y8);
+ cr->line_to(x9, y9);
+ cr->close_path();
+}
+
+void draw_carrow(Cairo::RefPtr<Cairo::Context>cr, double size) {
+
+ // Length of arrowhead (not including stroke).
+ double delta = (size-3)/4.0; // Use unscaled width.
+
+ // Tip of arrow
+ double tip_x = 1.5; // Edge, allow room for stroke when rotated.
+ double tip_y = delta + 1.5;
+
+ // Outer corner (1)
+ double out_x = tip_x + delta;
+ double out_y = tip_y - delta;
+
+ // Inner corner (2)
+ double in_x = out_x;
+ double in_y = out_y + (delta/2.0);
+
+ double x0 = tip_x; double y0 = tip_y;
+ double x1 = out_x; double y1 = out_y;
+ double x2 = in_x; double y2 = in_y;
+ double x3 = size - in_y; //double y3 = size - in_x;
+ double x4 = size - out_y; double y4 = size - out_x;
+ double x5 = size - tip_y; double y5 = size - tip_x;
+ double x6 = x5 - delta; double y6 = y4;
+ double x7 = x5 - delta/2.0; double y7 = y4;
+ double x8 = x1; //double y8 = y0 + delta/2.0;
+ double x9 = x1; double y9 = y0 + delta;
+
+ // Draw arrow
+ cr->move_to(x0, y0);
+ cr->line_to(x1, y1);
+ cr->line_to(x2, y2);
+ cr->arc(x1, y4, x3-x2, 3.0*M_PI/2.0, 0);
+ cr->line_to(x4, y4);
+ cr->line_to(x5, y5);
+ cr->line_to(x6, y6);
+ cr->line_to(x7, y7);
+ cr->arc_negative(x1, y4, x7-x8, 0, 3.0*M_PI/2.0);
+ cr->line_to(x9, y9);
+ cr->close_path();
+}
+
+void draw_pivot(Cairo::RefPtr<Cairo::Context>cr, double size) {
+
+ double delta4 = (size-5)/4.0; // Keep away from edge or will clip when rotating.
+ double delta8 = delta4/2;
+
+ // Line start
+ double center = size/2.0;
+
+ cr->move_to (center - delta8, center - 2*delta4 - delta8);
+ cr->rel_line_to ( delta4, 0 );
+ cr->rel_line_to ( 0, delta4);
+
+ cr->rel_line_to ( delta4, delta4);
+
+ cr->rel_line_to ( delta4, 0 );
+ cr->rel_line_to ( 0, delta4);
+ cr->rel_line_to (-delta4, 0 );
+
+ cr->rel_line_to (-delta4, delta4);
+
+ cr->rel_line_to ( 0, delta4);
+ cr->rel_line_to (-delta4, 0 );
+ cr->rel_line_to ( 0, -delta4);
+
+ cr->rel_line_to (-delta4, -delta4);
+
+ cr->rel_line_to (-delta4, 0 );
+ cr->rel_line_to ( 0, -delta4);
+ cr->rel_line_to ( delta4, 0 );
+
+ cr->rel_line_to ( delta4, -delta4);
+ cr->close_path();
+
+ cr->begin_new_sub_path();
+ cr->arc_negative(center, center, delta4, 0, -2 * M_PI);
+}
+
+void draw_salign(Cairo::RefPtr<Cairo::Context>cr, double size) {
+
+ // Triangle pointing at line.
+
+ // Basic units.
+ double delta4 = (size-1)/4.0; // Use unscaled width.
+ double delta8 = delta4/2;
+ if (delta8 < 2) {
+ // Keep a minimum gap of at least one pixel (after stroking).
+ delta8 = 2;
+ }
+
+ // Tip of triangle
+ double tip_x = size/2.0; // Center (also rotation point).
+ double tip_y = size/2.0;
+
+ // Corner triangle position.
+ double outer = size/2.0 - delta4;
+
+ // Outer line position
+ double oline = size/2.0 + (int)delta4;
+
+ // Inner line position
+ double iline = size/2.0 + (int)delta8;
+
+ // Draw triangle
+ cr->move_to(tip_x, tip_y);
+ cr->line_to(outer, outer);
+ cr->line_to(size - outer, outer);
+ cr->close_path();
+
+ // Draw line
+ cr->move_to(outer, iline);
+ cr->line_to(size - outer, iline);
+ cr->line_to(size - outer, oline);
+ cr->line_to(outer, oline);
+ cr->close_path();
+}
+
+void draw_calign(Cairo::RefPtr<Cairo::Context>cr, double size) {
+
+ // Basic units.
+ double delta4 = (size-1)/4.0; // Use unscaled width.
+ double delta8 = delta4/2;
+ if (delta8 < 2) {
+ // Keep a minimum gap of at least one pixel (after stroking).
+ delta8 = 2;
+ }
+
+ // Tip of triangle
+ double tip_x = size/2.0; // Center (also rotation point).
+ double tip_y = size/2.0;
+
+ // Corner triangle position.
+ double outer = size/2.0 - delta8 - delta4;
+
+ // End of line positin
+ double eline = size/2.0 - delta8;
+
+ // Outer line position
+ double oline = size/2.0 + (int)delta4;
+
+ // Inner line position
+ double iline = size/2.0 + (int)delta8;
+
+ // Draw triangle
+ cr->move_to(tip_x, tip_y);
+ cr->line_to(outer, tip_y);
+ cr->line_to(tip_x, outer);
+ cr->close_path();
+
+ // Draw line
+ cr->move_to(iline, iline);
+ cr->line_to(iline, eline);
+ cr->line_to(oline, eline);
+ cr->line_to(oline, oline);
+ cr->line_to(eline, oline);
+ cr->line_to(eline, iline);
+ cr->close_path();
+}
+
+void draw_malign(Cairo::RefPtr<Cairo::Context>cr, double size) {
+
+ // Basic units.
+ double delta4 = (size-1)/4.0; // Use unscaled width.
+ double delta8 = delta4/2;
+ if (delta8 < 2) {
+ // Keep a minimum gap of at least one pixel (after stroking).
+ delta8 = 2;
+ }
+
+ // Tip of triangle
+ double tip_0 = size/2.0;
+ double tip_1 = size/2.0 - delta8;
+
+ // Draw triangles
+ cr->move_to(tip_0, tip_1);
+ cr->line_to(tip_0 - delta4, tip_1 - delta4);
+ cr->line_to(tip_0 + delta4, tip_1 - delta4);
+ cr->close_path();
+
+ cr->move_to(size - tip_1, tip_0);
+ cr->line_to(size - tip_1 + delta4, tip_0 - delta4);
+ cr->line_to(size - tip_1 + delta4, tip_0 + delta4);
+ cr->close_path();
+
+ cr->move_to(size - tip_0, size - tip_1);
+ cr->line_to(size - tip_0 + delta4, size - tip_1 + delta4);
+ cr->line_to(size - tip_0 - delta4, size - tip_1 + delta4);
+ cr->close_path();
+
+ cr->move_to(tip_1, tip_0);
+ cr->line_to(tip_1 - delta4, tip_0 + delta4);
+ cr->line_to(tip_1 - delta4, tip_0 - delta4);
+ cr->close_path();
+
+}
+
+void CanvasItemCtrl::build_cache(int device_scale)
+{
+ if (_width < 2 || _height < 2) {
+ return; // Nothing to render
+ }
+
+ // Get colors
+ guint32 fill = 0x0;
+ guint32 stroke = 0x0;
+ fill = _fill;
+ stroke = _stroke;
+
+ if (_shape != CANVAS_ITEM_CTRL_SHAPE_BITMAP) {
+ if (_width % 2 == 0 || _height % 2 == 0) {
+ std::cerr << "CanvasItemCtrl::build_cache: Width and/or height not odd integer! "
+ << _name << ": width: " << _width << " height: " << _height << std::endl;
+ }
+ }
+
+ // Get memory for cache.
+ int width = _width * device_scale; // Not unsigned or math errors occur!
+ int height = _height * device_scale;
+ int size = width * height;
+
+ if (_cache) delete[] _cache;
+ _cache = new guint32[size];
+ guint32 *p = _cache;
+
+ switch (_shape) {
+
+ case CANVAS_ITEM_CTRL_SHAPE_SQUARE:
+ // Actually any rectanglular shape.
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < width; ++j) {
+ if (i + 1 > device_scale && device_scale < width - i &&
+ j + 1 > device_scale && device_scale < height - j) {
+ *p++ = fill;
+ } else {
+ *p++ = stroke;
+ }
+ }
+ }
+ _built = true;
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_DIAMOND:
+ { // Assume width == height.
+ int m = (width+1)/2;
+
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ if ( i + j > m-1+device_scale &&
+ (width-1-i) + j > m-1+device_scale &&
+ (width-1-i) + (height-1-j) > m-1+device_scale &&
+ i + (height-1-j) > m-1+device_scale ) {
+ *p++ = fill;
+ } else
+ if ( i + j > m-2 &&
+ (width-1-i) + j > m-2 &&
+ (width-1-i) + (height-1-j) > m-2 &&
+ i + (height-1-j) > m-2 ) {
+ *p++ = stroke;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+ _built = true;
+ break;
+ }
+
+ case CANVAS_ITEM_CTRL_SHAPE_CIRCLE:
+ { // Assume width == height.
+ double rs = width/2.0;
+ double rs2 = rs*rs;
+ double rf = rs-device_scale;
+ double rf2 = rf*rf;
+
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+
+ double rx = i - (width /2.0) + 0.5;
+ double ry = j - (height/2.0) + 0.5;
+ double r2 = rx*rx + ry*ry;
+
+ if (r2 < rf2) {
+ *p++ = fill;
+ } else if (r2 < rs2) {
+ *p++ = stroke;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+ _built = true;
+ break;
+ }
+
+ case CANVAS_ITEM_CTRL_SHAPE_TRIANGLE:
+ {
+ Geom::Affine m = Geom::Translate(Geom::Point(-width/2.0,-height/2.0));
+ m *= Geom::Rotate(-_angle);
+ m *= Geom::Translate(Geom::Point(width/2.0, height/2.0));
+
+ // Construct an arrowhead (triangle) of maximum size that won't leak out of rectangle
+ // defined by width and height, assuming width == height.
+ double w2 = width/2.0;
+ double h2 = height/2.0;
+ double w2cos = w2 * cos( M_PI/6 );
+ double h2sin = h2 * sin( M_PI/6 );
+ Geom::Point p1s(0, h2);
+ Geom::Point p2s(w2 + w2cos, h2 + h2sin);
+ Geom::Point p3s(w2 + w2cos, h2 - h2sin);
+
+ // Needed for constructing smaller arrowhead below.
+ double theta = atan2( Geom::Point( p2s - p1s ) );
+
+ p1s *= m;
+ p2s *= m;
+ p3s *= m;
+
+ // Construct a smaller arrow head for fill.
+ Geom::Point p1f(device_scale/sin(theta), h2);
+ Geom::Point p2f(w2 + w2cos, h2 - h2sin + device_scale/cos(theta));
+ Geom::Point p3f(w2 + w2cos, h2 + h2sin - device_scale/cos(theta));
+ p1f *= m;
+ p2f *= m;
+ p3f *= m;
+
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ Geom::Point point = Geom::Point(x+0.5, y+0.5);
+
+ if (point_inside_triangle(p1f, p2f, p3f, point)) {
+ p[(y*width)+x] = fill;
+
+ } else if (point_inside_triangle(p1s, p2s, p3s, point)) {
+ p[(y*width)+x] = stroke;
+
+ } else {
+ p[(y*width)+x] = 0;
+ }
+ }
+ }
+ break;
+ }
+
+ case CANVAS_ITEM_CTRL_SHAPE_CROSS:
+ // Actually an 'X'.
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ if ( abs(x - y) < device_scale ||
+ abs(width - 1 - x - y) < device_scale ) {
+ *p++ = stroke;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+ _built = true;
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_PLUS:
+ // Actually an '+'.
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ if ( std::abs(x-width/2) < device_scale ||
+ std::abs(y-height/2) < device_scale ) {
+ *p++ = stroke;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+ _built = true;
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_DARROW: // Double arrow
+ case CANVAS_ITEM_CTRL_SHAPE_SARROW: // Same shape as darrow but rendered rotated 90 degrees.
+ case CANVAS_ITEM_CTRL_SHAPE_CARROW: // Double corner arrow
+ case CANVAS_ITEM_CTRL_SHAPE_PIVOT: // Fancy "plus"
+ case CANVAS_ITEM_CTRL_SHAPE_SALIGN: // Side align (triangle pointing toward line)
+ case CANVAS_ITEM_CTRL_SHAPE_CALIGN: // Corner align (triangle pointing into "L")
+ case CANVAS_ITEM_CTRL_SHAPE_MALIGN: // Middle align (four triangles poining inward)
+ {
+ double size = _width; // Use unscaled width.
+
+ auto work = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, device_scale * size, device_scale * size);
+ cairo_surface_set_device_scale(work->cobj(), device_scale, device_scale); // No C++ API!
+ auto cr = Cairo::Context::create(work);
+
+ // Rotate around center
+ cr->translate( size/2.0, size/2.0);
+ cr->rotate(_angle);
+ cr->translate(-size/2.0, -size/2.0);
+
+ // Construct path
+ switch (_shape) {
+ case CANVAS_ITEM_CTRL_SHAPE_DARROW:
+ case CANVAS_ITEM_CTRL_SHAPE_SARROW:
+ draw_darrow(cr, size);
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_CARROW:
+ draw_carrow(cr, size);
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_PIVOT:
+ draw_pivot(cr, size);
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_SALIGN:
+ draw_salign(cr, size);
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_CALIGN:
+ draw_calign(cr, size);
+ break;
+
+ case CANVAS_ITEM_CTRL_SHAPE_MALIGN:
+ draw_malign(cr, size);
+ break;
+
+ default:
+ // Shouldn't happen
+ break;
+ }
+
+ // Fill and stroke.
+ cr->set_source_rgba(SP_RGBA32_R_F(_fill),
+ SP_RGBA32_G_F(_fill),
+ SP_RGBA32_B_F(_fill),
+ SP_RGBA32_A_F(_fill));
+ cr->fill_preserve();
+ cr->set_source_rgba(SP_RGBA32_R_F(_stroke),
+ SP_RGBA32_G_F(_stroke),
+ SP_RGBA32_B_F(_stroke),
+ SP_RGBA32_A_F(_stroke));
+ cr->set_line_width(1);
+ cr->stroke();
+
+ // Copy to buffer.
+ work->flush();
+ int strideb = work->get_stride();
+ unsigned char* pxb = work->get_data();
+ guint32 *p = _cache;
+ for (int i = 0; i < device_scale * size; ++i) {
+ guint32 *pb = reinterpret_cast<guint32*>(pxb + i*strideb);
+ for (int j = 0; j < width; ++j) {
+
+ guint32 color = 0x0;
+
+ // Need to un-premultiply alpha and change order argb -> rgba.
+ guint32 alpha = (*pb & 0xff000000) >> 24;
+ if (alpha == 0x0) {
+ color = 0x0;
+ } else {
+ guint32 rgb = unpremul_alpha(*pb & 0xffffff, alpha);
+ color = (rgb << 8) + alpha;
+ }
+
+ *p++ = color;
+ pb++;
+ }
+ }
+ _built = true;
+ break;
+ }
+
+ case CANVAS_ITEM_CTRL_SHAPE_BITMAP:
+ {
+ if (_pixbuf) {
+ unsigned char* px = gdk_pixbuf_get_pixels (_pixbuf);
+ unsigned int rs = gdk_pixbuf_get_rowstride (_pixbuf);
+ for (int y = 0; y < height/device_scale; y++){
+ for (int x = 0; x < width/device_scale; x++) {
+ unsigned char *s = px + rs*y + 4*x;
+ guint32 color;
+ if (s[3] < 0x80) {
+ color = 0;
+ } else if (s[0] < 0x80) {
+ color = _stroke;
+ } else {
+ color = _fill;
+ }
+
+ // Fill in device_scale x device_scale block
+ for (int i = 0; i < device_scale; ++i) {
+ for (int j = 0; j < device_scale; ++j) {
+ guint* p = _cache +
+ (x * device_scale + i) + // Column
+ (y * device_scale + j) * width; // Row
+ *p = color;
+ }
+ }
+ }
+ }
+ } else {
+ std::cerr << "CanvasItemCtrl::build_cache: No bitmap!" << std::endl;
+ guint *p = _cache;
+ for (int y = 0; y < height/device_scale; y++){
+ for (int x = 0; x < width/device_scale; x++) {
+ if (x == y) {
+ *p++ = 0xffff0000;
+ } else {
+ *p++ = 0;
+ }
+ }
+ }
+ }
+ _built = true;
+ break;
+ }
+
+ case CANVAS_ITEM_CTRL_SHAPE_IMAGE:
+ std::cerr << "CanvasItemCtrl::build_cache: image: UNIMPLEMENTED" << std::endl;
+ break;
+
+ default:
+ std::cerr << "CanvasItemCtrl::build_cache: unhandled shape!" << std::endl;
+ }
+}
+
+} // 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 :