3
* @brief Canvas item belonging to an SVG drawing element
6
* Krzysztof Kosiński <tweenk.pl@gmail.com>
8
* Copyright (C) 2011 Authors
9
* Released under GNU GPL, read the file 'COPYING' for more information
13
#include "display/cairo-utils.h"
14
#include "display/cairo-templates.h"
15
#include "display/drawing.h"
16
#include "display/drawing-context.h"
17
#include "display/drawing-item.h"
18
#include "display/drawing-group.h"
19
#include "display/drawing-surface.h"
20
#include "nr-filter.h"
21
#include "preferences.h"
26
/** @class DrawingItem
27
* @brief SVG drawing item for display.
29
* This was previously known as NRArenaItem. It represents the renderable
30
* portion of the SVG document. Typically this is created by the SP tree,
31
* in particular the show() virtual function.
33
* @section ObjectLifetime Object lifetime
34
* Deleting a DrawingItem will cause all of its children to be deleted as well.
35
* This can lead to nasty surprises if you hold references to things
36
* which are children of what is being deleted. Therefore, in the SP tree,
37
* you always need to delete the item views of children before deleting
38
* the view of the parent. Do not call delete on things returned from show()
39
* - this will cause dangling pointers inside the SPItem and lead to a crash.
40
* Use the corresponing hide() method.
42
* Outside of the SP tree, you should not use any references after the root node
46
DrawingItem::DrawingItem(Drawing &drawing)
58
, _child_type(CHILD_ORPHAN)
60
, _background_accumulate(0)
64
, _cached_persistent(0)
65
, _has_cache_iterator(0)
67
// , _renders_opacity(0)
71
DrawingItem::~DrawingItem()
73
_drawing.signal_item_deleted.emit(this);
74
//if (!_children.empty()) {
75
// g_warning("Removing item with children");
78
// remove from the set of cached items and delete cache
79
setCached(false, true);
80
if (_has_cache_iterator) {
81
_drawing._candidate_items.erase(_cache_iterator);
83
// remove this item from parent's children list
84
// due to the effect of clearChildren(), this only happens for the top-level deleted item
89
switch (_child_type) {
91
ChildrenList::iterator ithis = _parent->_children.iterator_to(*this);
92
_parent->_children.erase(ithis);
95
// we cannot call setClip(NULL) or setMask(NULL),
96
// because that would be an endless loop
97
_parent->_clip = NULL;
100
_parent->_mask = NULL;
103
_drawing._root = NULL;
109
_parent->_markForUpdate(STATE_ALL, false);
119
DrawingItem::parent() const
121
// initially I wanted to return NULL if we are a clip or mask child,
122
// but the previous behavior was just to return the parent regardless of child type
126
/// Returns true if item is among the descendants. Will return false if item == this.
128
DrawingItem::isAncestorOf(DrawingItem *item) const
130
for (DrawingItem *i = item->_parent; i; i = i->_parent) {
131
if (i == this) return true;
137
DrawingItem::appendChild(DrawingItem *item)
139
item->_parent = this;
140
assert(item->_child_type == CHILD_ORPHAN);
141
item->_child_type = CHILD_NORMAL;
142
_children.push_back(*item);
143
_markForUpdate(STATE_ALL, false);
147
DrawingItem::prependChild(DrawingItem *item)
149
item->_parent = this;
150
assert(item->_child_type == CHILD_ORPHAN);
151
item->_child_type = CHILD_NORMAL;
152
_children.push_front(*item);
153
_markForUpdate(STATE_ALL, false);
156
/// Delete all regular children of this item (not mask or clip).
158
DrawingItem::clearChildren()
160
// prevent children from referencing the parent during deletion
161
// this way, children won't try to remove themselves from a list
162
// from which they have already been removed by clear_and_dispose
163
for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) {
165
i->_child_type = CHILD_ORPHAN;
167
_children.clear_and_dispose(DeleteDisposer());
170
/// Set the incremental transform for this item
172
DrawingItem::setTransform(Geom::Affine const &new_trans)
174
Geom::Affine current;
176
current = *_transform;
179
if (!Geom::are_near(current, new_trans, 1e-18)) {
180
// mark the area where the object was for redraw.
182
if (new_trans.isIdentity()) {
183
delete _transform; // delete NULL; is safe
186
_transform = new Geom::Affine(new_trans);
188
_markForUpdate(STATE_ALL, true);
193
DrawingItem::setOpacity(float opacity)
200
DrawingItem::setVisible(bool v)
206
/// This is currently unused
208
DrawingItem::setSensitive(bool s)
213
/** @brief Enable / disable storing the rendering in memory.
214
* Calling setCached(false, true) will also remove the persistent status
217
DrawingItem::setCached(bool cached, bool persistent)
219
static const char *cache_env = getenv("_INKSCAPE_DISABLE_CACHE");
220
if (cache_env) return;
222
if (_cached_persistent && !persistent)
226
_cached_persistent = persistent ? cached : false;
228
_drawing._cached_items.insert(this);
230
_drawing._cached_items.erase(this);
237
DrawingItem::setClip(DrawingItem *item)
243
item->_parent = this;
244
assert(item->_child_type == CHILD_ORPHAN);
245
item->_child_type = CHILD_CLIP;
247
_markForUpdate(STATE_ALL, true);
251
DrawingItem::setMask(DrawingItem *item)
257
item->_parent = this;
258
assert(item->_child_type == CHILD_ORPHAN);
259
item->_child_type = CHILD_MASK;
261
_markForUpdate(STATE_ALL, true);
264
/// Move this item to the given place in the Z order of siblings.
265
/// Does nothing if the item has no parent.
267
DrawingItem::setZOrder(unsigned z)
269
if (!_parent) return;
271
ChildrenList::iterator it = _parent->_children.iterator_to(*this);
272
_parent->_children.erase(it);
274
ChildrenList::iterator i = _parent->_children.begin();
275
std::advance(i, std::min(z, unsigned(_parent->_children.size())));
276
_parent->_children.insert(i, *this);
281
DrawingItem::setItemBounds(Geom::OptRect const &bounds)
286
/** @brief Update derived data before operations.
287
* The purpose of this call is to recompute internal data which depends
288
* on the attributes of the object, but is not directly settable by the user.
289
* Precomputing this data speeds up later rendering, because some items
292
* Currently this method handles updating the visual and geometric bounding boxes
293
* in pixels, storing the total transformation from item space to the screen
294
* and cache invalidation.
296
* @param area Area to which the update should be restricted. Only takes effect
297
* if the bounding box is known.
298
* @param ctx A structure to store cascading state.
299
* @param flags Which internal data should be recomputed. This can be any combination
301
* @param reset State fields that should be reset before processing them. This is
302
* a means to force a recomputation of internal data even if the item
303
* considers it up to date. Mainly for internal use, such as
304
* propagating bunding box recomputation to children when the item's
308
DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
310
bool render_filters = _drawing.renderFilters();
311
bool outline = _drawing.outline();
313
// Set reset flags according to propagation status
314
reset |= _propagate_state;
315
_propagate_state = 0;
317
_state &= ~reset; // reset state of this item
319
if ((~_state & flags) == 0) return; // nothing to do
321
// TODO this might be wrong
322
if (_state & STATE_BBOX) {
323
// we have up-to-date bbox
324
if (!area.intersects(outline ? _bbox : _drawbox)) return;
327
// compute which elements need an update
328
unsigned to_update = _state ^ flags;
330
// this needs to be called before we recurse into children
331
if (to_update & STATE_BACKGROUND) {
332
_background_accumulate = _background_new;
333
if (_child_type == CHILD_NORMAL && _parent->_background_accumulate)
334
_background_accumulate = true;
337
UpdateContext child_ctx(ctx);
339
child_ctx.ctm = *_transform * ctx.ctm;
341
/* Remember the transformation matrix */
342
Geom::Affine ctm_change = _ctm.inverse() * child_ctx.ctm;
343
_ctm = child_ctx.ctm;
345
// update _bbox and call this function for children
346
_state = _updateItem(area, child_ctx, flags, reset);
348
if (to_update & STATE_BBOX) {
350
if (_filter && render_filters) {
351
_drawbox = _filter->compute_drawbox(this, _item_bbox);
358
_clip->update(area, child_ctx, flags, reset);
360
_bbox.unionWith(_clip->_bbox);
362
_drawbox.intersectWith(_clip->_bbox);
367
_mask->update(area, child_ctx, flags, reset);
369
_bbox.unionWith(_mask->_bbox);
371
// for masking, we need full drawbox of mask
372
_drawbox.intersectWith(_mask->_drawbox);
377
if (to_update & STATE_CACHE) {
378
// Update cache score for this item
379
if (_has_cache_iterator) {
380
// remove old score information
381
_drawing._candidate_items.erase(_cache_iterator);
382
_has_cache_iterator = false;
384
double score = _cacheScore();
385
if (score >= _drawing._cache_score_threshold) {
388
// if _cacheRect() is empty, a negative score will be returned from _cacheScore(),
389
// so this will not execute (cache score threshold must be positive)
390
cr.cache_size = _cacheRect()->area() * 4;
392
_drawing._candidate_items.push_front(cr);
393
_cache_iterator = _drawing._candidate_items.begin();
394
_has_cache_iterator = true;
397
/* Update cache if enabled.
398
* General note: here we only tell the cache how it has to transform
399
* during the render phase. The transformation is deferred because
400
* after the update the item can have its caching turned off,
401
* e.g. because its filter was removed. This way we avoid tempoerarily
402
* using more memory than the cache budget */
404
Geom::OptIntRect cl = _cacheRect();
405
if (_visible && cl) { // never create cache for invisible items
406
// this takes care of invalidation on transform
407
_cache->scheduleTransform(*cl, ctm_change);
409
// Destroy cache for this item - outside of canvas or invisible.
410
// The opposite transition (invisible -> visible or object
411
// entering the canvas) is handled during the render phase
418
if (to_update & STATE_RENDER) {
419
// now that we know drawbox, dirty the corresponding rect on canvas
420
// unless filtered, groups do not need to render by themselves, only their members
421
if (!is_drawing_group(this) || (_filter && render_filters)) {
427
struct MaskLuminanceToAlpha {
428
guint32 operator()(guint32 in) {
429
EXTRACT_ARGB32(in, a, r, g, b)
430
// the operation of unpremul -> luminance-to-alpha -> multiply by alpha
431
// is equivalent to luminance-to-alpha on premultiplied color values
432
// original computation in double: r*0.2125 + g*0.7154 + b*0.0721
433
guint32 ao = r*109 + g*366 + b*37; // coeffs add up to 512
434
return ((ao + 256) << 15) & 0xff000000; // equivalent to ((ao + 256) / 512) << 24
438
/** @brief Rasterize items.
439
* This method submits the drawing opeartions required to draw this item
440
* to the supplied DrawingContext, restricting drawing the the specified area.
442
* This method does some common tasks and calls the item-specific rendering
443
* function, _renderItem(), to render e.g. paths or bitmaps.
445
* @param flags Rendering options. This deals mainly with cache control.
448
DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at)
450
bool outline = _drawing.outline();
451
bool render_filters = _drawing.renderFilters();
453
// stop_at is handled in DrawingGroup, but this check is required to handle the case
454
// where a filtered item with background-accessing filter has enable-background: new
455
if (this == stop_at) return RENDER_STOP;
457
// If we are invisible, return immediately
458
if (!_visible) return RENDER_OK;
459
if (_ctm.isSingular(1e-18)) return RENDER_OK;
461
// TODO convert outline rendering to a separate virtual function
463
_renderOutline(ct, area, flags);
467
// carea is the area to paint
468
Geom::OptIntRect carea = Geom::intersect(area, _drawbox);
469
if (!carea) return RENDER_OK;
471
// render from cache if possible
475
_cache->paintFromCache(ct, carea);
476
if (!carea) return RENDER_OK;
478
// There is no cache. This could be because caching of this item
479
// was just turned on after the last update phase, or because
480
// we were previously outside of the canvas.
481
Geom::OptIntRect cl = _drawing.cacheLimit();
482
cl.intersectWith(_drawbox);
484
_cache = new DrawingCache(*cl);
488
// if our caching was turned off after the last update, it was already
489
// deleted in setCached()
492
// determine whether this shape needs intermediate rendering.
493
bool needs_intermediate_rendering = false;
494
bool &nir = needs_intermediate_rendering;
495
bool needs_opacity = (_opacity < 0.995);
497
// this item needs an intermediate rendering if:
498
nir |= (_clip != NULL); // 1. it has a clipping path
499
nir |= (_mask != NULL); // 2. it has a mask
500
nir |= (_filter != NULL && render_filters); // 3. it has a filter
501
nir |= needs_opacity; // 4. it is non-opaque
502
nir |= (_cache != NULL); // 5. it is cached
504
/* How the rendering is done.
506
* Clipping, masking and opacity are done by rendering them to a surface
507
* and then compositing the object's rendering onto it with the IN operator.
508
* The object itself is rendered to a group.
510
* Opacity is done by rendering the clipping path with an alpha
511
* value corresponding to the opacity. If there is no clipping path,
512
* the entire intermediate surface is painted with alpha corresponding
513
* to the opacity value.
516
// Short-circuit the simple case.
517
// We also use this path for filter background rendering, because masking, clipping,
518
// filters and opacity do not apply when rendering the ancestors of the filtered
520
if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) {
521
return _renderItem(ct, *carea, flags & ~RENDER_FILTER_BACKGROUND, stop_at);
524
// iarea is the bounding box for intermediate rendering
525
// Note 1: Pixels inside iarea but outside carea are invalid
526
// (incomplete filter dependence region).
527
// Note 2: We only need to render carea of clip and mask, but
528
// iarea of the object.
529
Geom::OptIntRect iarea = carea;
530
// expand carea to contain the dependent area of filters.
531
if (_filter && render_filters) {
532
_filter->area_enlarge(*iarea, this);
533
iarea.intersectWith(_drawbox);
536
DrawingSurface intermediate(*iarea);
537
DrawingContext ict(intermediate);
538
unsigned render_result = RENDER_OK;
540
// 1. Render clipping path with alpha = opacity.
541
ict.setSource(0,0,0,_opacity);
542
// Since clip can be combined with opacity, the result could be incorrect
543
// for overlapping clip children. To fix this we use the SOURCE operator
544
// instead of the default OVER.
545
ict.setOperator(CAIRO_OPERATOR_SOURCE);
547
_clip->clip(ict, *carea); // fixme: carea or area?
549
// if there is no clipping path, fill the entire surface with alpha = opacity.
552
ict.setOperator(CAIRO_OPERATOR_OVER); // reset back to default
554
// 2. Render the mask if present and compose it with the clipping path + opacity.
557
_mask->render(ict, *carea, flags);
559
cairo_surface_t *mask_s = ict.rawTarget();
560
// Convert mask's luminance to alpha
561
ink_cairo_surface_filter(mask_s, mask_s, MaskLuminanceToAlpha());
562
ict.popGroupToSource();
563
ict.setOperator(CAIRO_OPERATOR_IN);
565
ict.setOperator(CAIRO_OPERATOR_OVER);
568
// 3. Render object itself
570
render_result = _renderItem(ict, *iarea, flags, stop_at);
573
if (_filter && render_filters) {
574
bool rendered = false;
575
if (_filter->uses_background() && _background_accumulate) {
576
DrawingItem *bg_root = this;
577
for (; bg_root; bg_root = bg_root->_parent) {
578
if (bg_root->_background_new) break;
581
DrawingSurface bg(*iarea);
582
DrawingContext bgct(bg);
583
bg_root->render(bgct, *iarea, flags | RENDER_FILTER_BACKGROUND, this);
584
_filter->render(this, ict, &bgct);
589
_filter->render(this, ict, NULL);
591
// Note that because the object was rendered to a group,
592
// the internals of the filter need to use cairo_get_group_target()
593
// instead of cairo_get_target().
596
// 5. Render object inside the composited mask + clip
597
ict.popGroupToSource();
598
ict.setOperator(CAIRO_OPERATOR_IN);
601
// 6. Paint the completed rendering onto the base context (or into cache)
602
if (_cached && _cache) {
603
DrawingContext cachect(*_cache);
604
cachect.rectangle(*carea);
605
cachect.setOperator(CAIRO_OPERATOR_SOURCE);
606
cachect.setSource(&intermediate);
608
_cache->markClean(*carea);
610
ct.rectangle(*carea);
611
ct.setSource(&intermediate);
613
ct.setSource(0,0,0,0);
614
// the call above is to clear a ref on the intermediate surface held by ct
616
return render_result;
620
DrawingItem::_renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsigned flags)
622
// intersect with bbox rather than drawbox, as we want to render things outside
623
// of the clipping path as well
624
Geom::OptIntRect carea = Geom::intersect(area, _bbox);
627
// just render everything: item, clip, mask
628
// First, render the object itself
629
_renderItem(ct, *carea, flags, NULL);
631
// render clip and mask, if any
632
guint32 saved_rgba = _drawing.outlinecolor; // save current outline color
633
// render clippath as an object, using a different color
634
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
636
_drawing.outlinecolor = prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff); // green clips
637
_clip->render(ct, *carea, flags);
639
// render mask as an object, using a different color
641
_drawing.outlinecolor = prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); // blue masks
642
_mask->render(ct, *carea, flags);
644
_drawing.outlinecolor = saved_rgba; // restore outline color
647
/** @brief Rasterize the clipping path.
648
* This method submits drawing operations required to draw a basic filled shape
649
* of the item to the supplied drawing context. Rendering is limited to the
650
* given area. The rendering of the clipped object is composited into
651
* the result of this call using the IN operator. See the implementation
652
* of render() for details.
655
DrawingItem::clip(Inkscape::DrawingContext &ct, Geom::IntRect const &area)
657
// don't bother if the object does not implement clipping (e.g. DrawingImage)
658
if (!_canClip()) return;
659
if (!_visible) return;
660
if (!area.intersects(_bbox)) return;
662
// The item used as the clipping path itself has a clipping path.
663
// Render this item's clipping path onto a temporary surface, then composite it
664
// with the item using the IN operator
667
{ Inkscape::DrawingContext::Save save(ct);
668
ct.setSource(0,0,0,1);
669
_clip->clip(ct, area);
674
// rasterize the clipping path
678
ct.popGroupToSource();
679
ct.setOperator(CAIRO_OPERATOR_IN);
681
ct.popGroupToSource();
682
ct.setOperator(CAIRO_OPERATOR_SOURCE);
687
/** @brief Get the item under the specified point.
688
* Searches the tree for the first item in the Z-order which is closer than
689
* @a delta to the given point. The pick should be visual - for example
690
* an object with a thick stroke should pick on the entire area of the stroke.
691
* @param p Search point
692
* @param delta Maximum allowed distance from the point
693
* @param sticky Whether the pick should ignore visibility and sensitivity.
694
* When false, only visible and sensitive objects are considered.
695
* When true, invisible and insensitive objects can also be picked.
698
DrawingItem::pick(Geom::Point const &p, double delta, unsigned flags)
700
// Sometimes there's no BBOX in state, reason unknown (bug 992817)
701
// I made this not an assert to remove the warning
702
if (!(_state & STATE_BBOX) || !(_state & STATE_PICK))
704
// ignore invisible and insensitive items unless sticky
705
if (!(flags & PICK_STICKY) && !(_visible && _sensitive))
708
bool outline = _drawing.outline();
710
if (!_drawing.outline()) {
711
// pick inside clipping path; if NULL, it means the object is clipped away there
713
DrawingItem *cpick = _clip->pick(p, delta, flags | PICK_AS_CLIP);
714
if (!cpick) return NULL;
718
DrawingItem *mpick = _mask->pick(p, delta, flags);
719
if (!mpick) return NULL;
723
Geom::OptIntRect box = (outline || (flags & PICK_AS_CLIP)) ? _bbox : _drawbox;
724
if (!box) return NULL;
726
Geom::Rect expanded = *box;
727
expanded.expandBy(delta);
729
if (expanded.contains(p)) {
730
return _pickItem(p, delta, flags);
735
/** Marks the current visual bounding box of the item for redrawing.
736
* This is called whenever the object changes its visible appearance.
737
* For some cases (such as setting opacity) this is enough, but for others
738
* _markForUpdate() also needs to be called.
741
DrawingItem::_markForRendering()
743
bool outline = _drawing.outline();
744
Geom::OptIntRect dirty = outline ? _bbox : _drawbox;
747
// dirty the caches of all parents
748
DrawingItem *bkg_root = NULL;
750
for (DrawingItem *i = this; i; i = i->_parent) {
751
if (i != this && i->_filter) {
752
i->_filter->area_enlarge(*dirty, i);
755
i->_cache->markDirty(*dirty);
757
if (i->_background_accumulate) {
763
bkg_root->_invalidateFilterBackground(*dirty);
765
_drawing.signal_request_render.emit(*dirty);
769
DrawingItem::_invalidateFilterBackground(Geom::IntRect const &area)
771
if (!_drawbox.intersects(area)) return;
773
if (_cache && _filter && _filter->uses_background()) {
774
_cache->markDirty(area);
777
for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) {
778
i->_invalidateFilterBackground(area);
782
/** @brief Marks the item as needing a recomputation of internal data.
784
* This mechanism avoids traversing the entire rendering tree (which could be vast)
785
* on every trivial state changed in any item. Only items marked as needing
786
* an update (having some bits in their _state unset) will be traversed
787
* during the update call.
789
* The _propagate variable is another optimization. We use it to specify that
790
* all children should also have the corresponding flags unset before checking
791
* whether they need to be traversed. This way there is one less traversal
792
* of the tree. Without this we would need to unset state bits in all children.
793
* With _propagate we do this during the update call, when we have to traverse
797
DrawingItem::_markForUpdate(unsigned flags, bool propagate)
800
_propagate_state |= flags;
803
if (_state & flags) {
806
_parent->_markForUpdate(flags, false);
808
_drawing.signal_request_update.emit(this);
814
DrawingItem::_setStyleCommon(SPStyle *&_style, SPStyle *style)
816
if (style) sp_style_ref(style);
817
if (_style) sp_style_unref(_style);
820
// if group has a filter
821
if (style->filter.set && style->getFilter()) {
823
int primitives = sp_filter_primitive_count(SP_FILTER(style->getFilter()));
824
_filter = new Inkscape::Filters::Filter(primitives);
826
sp_filter_build_renderer(SP_FILTER(style->getFilter()), _filter);
828
// no filter set for this group
833
if (style && style->enable_background.set) {
834
if (style->enable_background.value == SP_CSS_BACKGROUND_NEW && !_background_new) {
835
_background_new = true;
836
_markForUpdate(STATE_BACKGROUND, true);
837
} else if (style->enable_background.value == SP_CSS_BACKGROUND_ACCUMULATE && _background_new) {
838
_background_new = false;
839
_markForUpdate(STATE_BACKGROUND, true);
843
_markForUpdate(STATE_ALL, false);
846
/** @brief Compute the caching score.
848
* Higher scores mean the item is more aggresively prioritized for automatic
849
* caching by Inkscape::Drawing. */
851
DrawingItem::_cacheScore()
853
Geom::OptIntRect cache_rect = _cacheRect();
854
if (!cache_rect) return -1.0;
856
// a crude first approximation:
857
// the basic score is the number of pixels in the drawbox
858
double score = cache_rect->area();
859
// this is multiplied by the filter complexity and its expansion
860
if (_filter &&_drawing.renderFilters()) {
861
score *= _filter->complexity(_ctm);
862
Geom::IntRect ref_area = Geom::IntRect::from_xywh(0, 0, 16, 16);
863
Geom::IntRect test_area = ref_area;
864
Geom::IntRect limit_area(0, INT_MIN, 16, INT_MAX);
865
_filter->area_enlarge(test_area, this);
866
// area_enlarge never shrinks the rect, so the result of intersection below
868
score *= double((test_area & limit_area)->area()) / ref_area.area();
870
// if the object is clipped, add 1/2 of its bbox pixels
871
if (_clip && _clip->_bbox) {
872
score += _clip->_bbox->area() * 0.5;
874
// if masked, add mask score
876
score += _mask->_cacheScore();
878
//g_message("caching score: %f", score);
883
DrawingItem::_cacheRect()
885
Geom::OptIntRect r = _drawbox & _drawing.cacheLimit();
889
} // end namespace Inkscape
894
c-file-style:"stroustrup"
895
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
900
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :