Inkscape
Vector Graphics Editor
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages Concepts
drawing-shape.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
6 * Authors:
7 * Krzysztof Kosiński <tweenk.pl@gmail.com>
8 *
9 * Copyright (C) 2011 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <glibmm.h>
14#include <2geom/curves.h>
15#include <2geom/pathvector.h>
16#include <2geom/path-sink.h>
18
19#include "style.h"
20
21#include "curve.h"
22#include "drawing.h"
23#include "drawing-context.h"
24#include "drawing-shape.h"
26
27#include "helper/geom.h"
28
29#include "ui/widget/canvas.h" // Canvas area
30
31namespace Inkscape {
32
34 : DrawingItem(drawing)
35 , style_vector_effect_stroke(false)
36 , style_stroke_extensions_hairline(false)
37 , style_clip_rule(SP_WIND_RULE_EVENODD)
38 , style_fill_rule(SP_WIND_RULE_EVENODD)
39 , style_opacity(SP_SCALE24_MAX)
40 , _last_pick(nullptr)
41 , _repick_after(0)
42{
43}
44
45void DrawingShape::setPath(std::shared_ptr<SPCurve const> curve)
46{
47 defer([this, curve = std::move(curve)] () mutable {
49 _curve = std::move(curve);
51 });
52}
53
54void DrawingShape::setStyle(SPStyle const *style, SPStyle const *context_style)
55{
56 DrawingItem::setStyle(style, context_style);
57
58 auto vector_effect_stroke = false;
59 auto stroke_extensions_hairline = false;
60 auto clip_rule = SP_WIND_RULE_EVENODD;
61 auto fill_rule = SP_WIND_RULE_EVENODD;
62 auto opacity = SP_SCALE24_MAX;
63 if (style) {
64 vector_effect_stroke = style->vector_effect.stroke;
65 stroke_extensions_hairline = style->stroke_extensions.hairline;
66 clip_rule = style->clip_rule.value;
67 fill_rule = style->fill_rule.value;
68 opacity = style->opacity.value;
69 }
70
71 defer([=, this, nrstyle = NRStyleData(_style, _context_style)] () mutable {
72 _nrstyle.set(std::move(nrstyle));
73 style_vector_effect_stroke = vector_effect_stroke;
74 style_stroke_extensions_hairline = stroke_extensions_hairline;
75 style_clip_rule = clip_rule;
76 style_fill_rule = fill_rule;
77 style_opacity = opacity;
78 });
79}
80
81void DrawingShape::setChildrenStyle(SPStyle const *context_style)
82{
83 DrawingItem::setChildrenStyle(context_style);
84
85 defer([this, nrstyle = NRStyleData(_style, _context_style)] () mutable {
86 _nrstyle.set(std::move(nrstyle));
87 });
88}
89
90unsigned DrawingShape::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
91{
92 // update markers
93 for (auto &c : _children) {
94 c.update(area, ctx, flags, reset);
95 }
96
97 // clear Cairo data to force update
98 if (flags & STATE_RENDER) {
100 }
101
102 auto calc_curve_bbox = [&, this] () -> Geom::OptIntRect {
103 if (!_curve) {
104 return {};
105 }
106
107 auto rect = bounds_exact_transformed(_curve->get_pathvector(), ctx.ctm);
108 if (!rect) {
109 return {};
110 }
111
112 float stroke_max = 0.0f;
113
114 // Get the normal stroke.
116 // Expand by stroke width.
117 stroke_max = _nrstyle.data.stroke_width * 0.5f;
118
119 // Scale by view transformation, unless vector effect stroke.
121 stroke_max *= max_expansion(ctx.ctm);
122 }
123
124 // Cap minimum line width if asked.
126 stroke_max = std::max(stroke_max, 0.5f);
127 }
128 }
129
130 // Get the outline stroke.
132 stroke_max = std::max(stroke_max, 0.5f);
133 }
134
135 if (stroke_max > 0.0f) {
136 // Expand by mitres, if present.
137 if (_nrstyle.data.line_join == CAIRO_LINE_JOIN_MITER && _nrstyle.data.miter_limit >= 1.0f) {
138 stroke_max *= _nrstyle.data.miter_limit;
139 }
140
141 // Apply expansion if non-zero.
142 if (stroke_max > 0.01) {
143 rect->expandBy(stroke_max);
144 }
145 }
146
147 return rect->roundOutwards();
148 };
149
150 if (flags & STATE_BBOX) {
151 _bbox = calc_curve_bbox();
152
153 for (auto &c : _children) {
154 _bbox.unionWith(c.bbox());
155 }
156 }
157
158 return _state | flags;
159}
160
162{
164 dc.transform(_ctm);
165
167
168 if (has_fill) {
169 dc.path(_curve->get_pathvector());
171 dc.fillPreserve();
172 dc.newPath(); // clear path
173 }
174}
175
176void DrawingShape::_renderStroke(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags) const
177{
179 dc.transform(_ctm);
180
183 has_stroke.reset();
184 }
185
186 if (has_stroke) {
187 // TODO: remove segments outside of bbox when no dashes present
188 dc.path(_curve->get_pathvector());
190 dc.restore();
191 dc.save();
192 }
194
195 // If the stroke is a hairline, set it to exactly 1px on screen.
196 // If visible hairline mode is on, make sure the line is at least 1px.
198 double dx = 1.0, dy = 0.0;
199 dc.device_to_user_distance(dx, dy);
200 auto pixel_size = std::hypot(dx, dy);
202 dc.setHairline();
203 }
204 }
205
206 dc.strokePreserve();
207 dc.newPath(); // clear path
208 }
209}
210
211void DrawingShape::_renderMarkers(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags, DrawingItem const *stop_at) const
212{
213 // marker rendering
214 for (auto &i : _children) {
215 i.render(dc, rc, area, flags, stop_at);
216 }
217}
218
219unsigned DrawingShape::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags, DrawingItem const *stop_at) const
220{
221 if (!_curve) return RENDER_OK;
222
223 auto visible = area & _bbox;
224 if (!visible) return RENDER_OK; // skip if not within bounding box
225
226 bool outline = flags & RENDER_OUTLINE;
227
228 if (outline) {
229 auto rgba = rc.outline_color;
230
231 // paint-order doesn't matter
232 {
234 dc.transform(_ctm);
235 dc.path(_curve->get_pathvector());
236 }
237 {
239 dc.setSource(rgba);
240 dc.setLineWidth(0.5);
241 dc.setTolerance(0.5);
242 dc.stroke();
243 }
244
245 _renderMarkers(dc, rc, area, flags, stop_at);
246 return RENDER_OK;
247 }
248
250 // This is the most common case, special case so we don't call get_pathvector(), etc. twice
251
252 {
253 // we assume the context has no path
255 dc.transform(_ctm);
256
257 // update fill and stroke paints.
258 // this cannot be done during nr_arena_shape_update, because we need a Cairo context
259 // to render svg:pattern
263 has_stroke.reset();
264 }
265 if (has_fill || has_stroke) {
266 dc.path(_curve->get_pathvector());
267 // TODO: remove segments outside of bbox when no dashes present
268 if (has_fill) {
270 dc.fillPreserve();
271 }
273 dc.restore();
274 dc.save();
275 }
276 if (has_stroke) {
278
279 // If the draw mode is set to visible hairlines, don't let anything get smaller
280 // than half a pixel.
281 if (flags & RENDER_VISIBLE_HAIRLINES) {
282 double dx = 1.0, dy = 0.0;
283 dc.device_to_user_distance(dx, dy);
284 auto half_pixel_size = std::hypot(dx, dy) * 0.5;
285 if (_nrstyle.data.stroke_width < half_pixel_size) {
286 dc.setLineWidth(half_pixel_size);
287 }
288 }
289
290 dc.strokePreserve();
291 }
292 dc.newPath(); // clear path
293 } // has fill or stroke pattern
294 }
295 _renderMarkers(dc, rc, area, flags, stop_at);
296 return RENDER_OK;
297
298 }
299
300 // Handle different paint orders
301 for (auto &i : _nrstyle.data.paint_order_layer) {
302 switch (i) {
304 _renderFill(dc, rc, *visible);
305 break;
307 _renderStroke(dc, rc, *visible, flags);
308 break;
310 _renderMarkers(dc, rc, area, flags, stop_at);
311 break;
312 default:
313 // PAINT_ORDER_AUTO Should not happen
314 break;
315 }
316 }
317
318 return RENDER_OK;
319}
320
322{
323 if (!_curve) return;
324
327 dc.setFillRule(CAIRO_FILL_RULE_EVEN_ODD);
328 } else {
329 dc.setFillRule(CAIRO_FILL_RULE_WINDING);
330 }
331 dc.transform(_ctm);
332 dc.path(_curve->get_pathvector());
333 dc.fill();
334}
335
336DrawingItem *DrawingShape::_pickItem(Geom::Point const &p, double delta, unsigned flags)
337{
338 if (_repick_after > 0)
340
341 if (_repick_after > 0) { // we are a slow, huge path
342 return _last_pick; // skip this pick, returning what was returned last time
343 }
344
345 if (!_curve) return nullptr;
346 bool outline = flags & PICK_OUTLINE;
347 bool pick_as_clip = flags & PICK_AS_CLIP;
348
349 if (SP_SCALE24_TO_FLOAT(style_opacity) == 0 && !outline && !pick_as_clip && !_drawing.selectZeroOpacity()) {
350 // fully transparent, no pick unless outline mode
351 return nullptr;
352 }
353
354 gint64 tstart = g_get_monotonic_time();
355
356 double width;
357 if (pick_as_clip) {
358 width = 0; // no width should be applied to clip picking
359 // this overrides display mode and stroke style considerations
360 } else if (outline) {
361 width = 0.5; // in outline mode, everything is stroked with the same 0.5px line width
363 auto stroke_width = _nrstyle.data.hairline ? 1 : _nrstyle.data.stroke_width;
364 // for normal picking calculate the distance corresponding top the stroke width
365 float scale = max_expansion(_ctm);
366 width = std::max(0.125f, stroke_width * scale) / 2;
367 } else {
368 width = 0;
369 }
370
371 double dist = Geom::infinity();
372 int wind = 0;
373 bool needfill = pick_as_clip || (_nrstyle.data.fill.type != NRStyleData::PaintType::NONE && (_nrstyle.data.fill.opacity > 1e-3 || _drawing.selectZeroOpacity()) && !outline);
374 bool wind_evenodd = (pick_as_clip ? style_clip_rule : style_fill_rule) == SP_WIND_RULE_EVENODD;
375
376 // actual shape picking
379 viewbox.expandBy (width);
380 pathv_matrix_point_bbox_wind_distance(_curve->get_pathvector(), _ctm, p, nullptr, needfill? &wind : nullptr, &dist, 0.5, &viewbox);
381 } else {
382 pathv_matrix_point_bbox_wind_distance(_curve->get_pathvector(), _ctm, p, nullptr, needfill? &wind : nullptr, &dist, 0.5, nullptr);
383 }
384
385 gint64 tfinish = g_get_monotonic_time();
386 gint64 this_pick = tfinish - tstart;
387 //g_print ("pick time %lu\n", this_pick);
388 if (this_pick > 10000) { // slow picking, remember to skip several new picks
389 _repick_after = this_pick / 5000;
390 }
391
392 // covered by fill?
393 if (needfill) {
394 if (wind_evenodd) {
395 if (wind & 0x1) {
396 _last_pick = this;
397 return this;
398 }
399 } else {
400 if (wind != 0) {
401 _last_pick = this;
402 return this;
403 }
404 }
405 }
406
407 // close to the edge, as defined by strokewidth and delta?
408 // this ignores dashing (as if the stroke is solid) and always works as if caps are round
409 if (needfill || width > 0) { // if either fill or stroke visible,
410 if ((dist - width) < delta) {
411 _last_pick = this;
412 return this;
413 }
414 }
415
416 // if not picked on the shape itself, try its markers
417 for (auto &i : _children) {
418 DrawingItem *ret = i.pick(p, delta, flags & ~PICK_STICKY);
419 if (ret) {
420 _last_pick = this;
421 return this;
422 }
423 }
424
425 _last_pick = nullptr;
426 return nullptr;
427}
428
429} // namespace Inkscape
430
431/*
432 Local Variables:
433 mode:c++
434 c-file-style:"stroustrup"
435 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
436 indent-tabs-mode:nil
437 fill-column:99
438 End:
439*/
440// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Inkscape canvas widget.
Axis-aligned generic rectangle that can be empty.
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
Axis aligned, non-empty, generic rectangle.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
UI::Widget::Canvas * get_canvas() const
Definition canvas-item.h:61
RAII idiom for saving the state of DrawingContext.
Minimal wrapper over Cairo.
void path(Geom::PathVector const &pv)
void setSource(cairo_pattern_t *source)
void transform(Geom::Affine const &trans)
void setTolerance(double tol)
void device_to_user_distance(double &dx, double &dy)
void setFillRule(cairo_fill_rule_t rule)
SVG drawing item for display.
DrawingPattern * _fill_pattern
DrawingItem * pick(Geom::Point const &p, double delta, unsigned flags=0)
Get the item under the specified point.
Geom::OptIntRect _bbox
Bounding box in display (pixel) coords including stroke.
virtual void setStyle(SPStyle const *style, SPStyle const *context_style=nullptr)
Process information related to the new style.
Geom::OptRect _item_bbox
Geometric bounding box in item's user space.
virtual void setChildrenStyle(SPStyle const *context_style)
Recursively update children style.
SPStyle const * _style
void _markForUpdate(unsigned state, bool propagate)
Marks the item as needing a recomputation of internal data.
DrawingPattern * _stroke_pattern
SPStyle const * _context_style
Geom::Affine _ctm
Total transform from item coords to display coords.
void _markForRendering()
Marks the current visual bounding box of the item for redrawing.
DrawingShape(Drawing &drawing)
unsigned _renderItem(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags, DrawingItem const *stop_at) const override
void _clipItem(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area) const override
void setPath(std::shared_ptr< SPCurve const > curve)
unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) override
void _renderStroke(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags) const
void setChildrenStyle(SPStyle const *context_style) override
Recursively update children style.
DrawingItem * _pickItem(Geom::Point const &p, double delta, unsigned flags) override
DrawingItem * _last_pick
void setStyle(SPStyle const *style, SPStyle const *context_style=nullptr) override
Process information related to the new style.
void _renderMarkers(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags, DrawingItem const *stop_at) const
std::shared_ptr< SPCurve const > _curve
void _renderFill(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area) const
CanvasItemDrawing * getCanvasItemDrawing()
Definition drawing.h:47
RenderMode renderMode() const
Definition drawing.h:67
bool selectZeroOpacity() const
Definition drawing.h:79
bool outlineOverlay() const
Definition drawing.h:69
NRStyleData data
Definition nr-style.h:149
void applyStroke(DrawingContext &dc, CairoPatternUniqPtr const &cp) const
Definition nr-style.cpp:377
CairoPatternUniqPtr prepareFill(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, DrawingPattern const *pattern) const
Definition nr-style.cpp:345
CairoPatternUniqPtr prepareStroke(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, DrawingPattern const *pattern) const
Definition nr-style.cpp:350
void applyFill(DrawingContext &dc, CairoPatternUniqPtr const &cp) const
Definition nr-style.cpp:365
void set(NRStyleData &&data)
Definition nr-style.cpp:339
Geom::IntRect get_area_world() const
Return the area shown in the canvas in world coordinates.
Definition canvas.cpp:1560
An SVG style object.
Definition style.h:45
T< SPAttr::CLIP_RULE, SPIEnum< SPWindRule > > clip_rule
clip-rule: 0 nonzero, 1 evenodd
Definition style.h:204
T< SPAttr::FILL_RULE, SPIEnum< SPWindRule > > fill_rule
fill-rule: 0 nonzero, 1 evenodd
Definition style.h:244
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Definition style.h:216
T< SPAttr::STROKE_EXTENSIONS, SPIStrokeExtensions > stroke_extensions
-inkscape-stroke
Definition style.h:263
T< SPAttr::VECTOR_EFFECT, SPIVectorEffect > vector_effect
vector effect
Definition style.h:237
RectangularCluster rc
Include all curve types.
double c[8][4]
Cairo drawing context with Inkscape extensions.
Group belonging to an SVG drawing element.
SVG drawing for display.
static bool has_fill(SPObject *source)
static bool has_stroke(SPObject *source)
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
Geom::OptRect bounds_exact_transformed(Geom::PathVector const &pv, Geom::Affine const &t)
Definition geom.cpp:153
void pathv_matrix_point_bbox_wind_distance(Geom::PathVector const &pathv, Geom::Affine const &m, Geom::Point const &pt, Geom::Rect *bbox, int *wind, Geom::Coord *dist, Geom::Coord tolerance, Geom::Rect const *viewbox)
Definition geom.cpp:464
Specific geometry functions for Inkscape, not provided my lib2geom.
double max_expansion(Geom::Affine const &affine)
Compute the maximum factor by which affine can increase a vector's length.
Definition geom.h:143
Helper class to stream background task notifications as a series of messages.
Geom::PathVector outline(Geom::Path const &input, double width, double miter, LineJoinType join, LineCapType butt, double tolerance)
Strokes the path given by input.
callback interface for SVG path data
PathVector - a sequence of subpaths.
std::array< PaintOrderType, 3 > paint_order_layer
Definition nr-style.h:88
cairo_line_join_t line_join
Definition nr-style.h:78
Definition curve.h:24
@ SP_WIND_RULE_EVENODD
Definition style-enums.h:26
static const unsigned SP_SCALE24_MAX
SPStyle - a style object for SPItem objects.
parse SVG path specifications
int delta
double width