summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTavmjong Bah <tavmjong@free.fr>2014-10-14 18:38:41 +0000
committertavmjong-free <tavmjong@free.fr>2014-10-14 18:38:41 +0000
commite056d0fa49ed5b9369c0c16c1c4df0c671b88b68 (patch)
tree1788e981f21d9b8ae27a3fb23cd7ee68c98e27f9 /src
parentImplement marker 'orient' attribute value 'auto-start-reverse'. (diff)
parentMerged hatch pdf and png export code (diff)
downloadinkscape-e056d0fa49ed5b9369c0c16c1c4df0c671b88b68.tar.gz
inkscape-e056d0fa49ed5b9369c0c16c1c4df0c671b88b68.zip
Merge hatch rendering code from Tomasz Boczkowski.
(bzr r13614)
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/Makefile_insert2
-rw-r--r--src/attributes.cpp5
-rw-r--r--src/attributes.h5
-rw-r--r--src/display/CMakeLists.txt2
-rw-r--r--src/display/Makefile_insert2
-rw-r--r--src/display/drawing-item.cpp45
-rw-r--r--src/display/drawing-item.h9
-rw-r--r--src/display/drawing-pattern.cpp194
-rw-r--r--src/display/drawing-pattern.h87
-rw-r--r--src/display/drawing-shape.cpp8
-rw-r--r--src/display/drawing-text.cpp8
-rw-r--r--src/display/nr-style.cpp78
-rw-r--r--src/display/nr-style.h9
-rw-r--r--src/extension/internal/cairo-render-context.cpp116
-rw-r--r--src/extension/internal/cairo-render-context.h1
-rw-r--r--src/extension/internal/cairo-renderer.cpp16
-rw-r--r--src/extension/internal/cairo-renderer.h2
-rw-r--r--src/sp-hatch-path.cpp331
-rw-r--r--src/sp-hatch-path.h91
-rw-r--r--src/sp-hatch.cpp678
-rw-r--r--src/sp-hatch.h174
-rw-r--r--src/sp-item.cpp99
-rw-r--r--src/sp-item.h4
-rw-r--r--src/sp-paint-server.cpp5
-rw-r--r--src/sp-paint-server.h58
-rw-r--r--src/style-internal.cpp6
-rw-r--r--src/style-internal.h3
-rw-r--r--src/style.cpp14
-rw-r--r--src/style.h16
-rw-r--r--src/svg/CMakeLists.txt2
-rw-r--r--src/svg/Makefile_insert2
-rw-r--r--src/svg/svg-angle.cpp136
-rw-r--r--src/svg/svg-angle.h69
34 files changed, 2222 insertions, 59 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4a792af6b..25556e3bc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,6 +32,8 @@ set(sp_SRC
sp-gradient-reference.cpp
sp-gradient.cpp
sp-guide.cpp
+ sp-hatch.cpp
+ sp-hatch-path.cpp
sp-image.cpp
sp-item-group.cpp
sp-item-notify-moveto.cpp
@@ -115,6 +117,8 @@ set(sp_SRC
sp-guide-attachment.h
sp-guide-constraint.h
sp-guide.h
+ sp-hatch.h
+ sp-hatch-path.h
sp-image.h
sp-item-group.h
sp-item-notify-moveto.h
diff --git a/src/Makefile_insert b/src/Makefile_insert
index f191839a1..66188f502 100644
--- a/src/Makefile_insert
+++ b/src/Makefile_insert
@@ -158,6 +158,8 @@ ink_common_sources += \
sp-guide-attachment.h \
sp-guide-constraint.h \
sp-guide.cpp sp-guide.h \
+ sp-hatch.cpp sp-hatch.h \
+ sp-hatch-path.cpp sp-hatch-path.h \
sp-image.cpp sp-image.h \
sp-item.cpp sp-item.h \
sp-item-group.cpp sp-item-group.h \
diff --git a/src/attributes.cpp b/src/attributes.cpp
index 2474e4abe..aa17c581b 100644
--- a/src/attributes.cpp
+++ b/src/attributes.cpp
@@ -292,6 +292,11 @@ static SPStyleProp const props[] = {
{SP_ATTR_PATTERNUNITS, "patternUnits"},
{SP_ATTR_PATTERNCONTENTUNITS, "patternContentUnits"},
{SP_ATTR_PATTERNTRANSFORM, "patternTransform"},
+ /* SPHatch */
+ {SP_ATTR_HATCHUNITS, "hatchUnits"},
+ {SP_ATTR_HATCHCONTENTUNITS, "hatchContentUnits"},
+ {SP_ATTR_HATCHTRANSFORM, "hatchTransform"},
+ {SP_ATTR_PITCH, "pitch"},
/* SPClipPath */
{SP_ATTR_CLIPPATHUNITS, "clipPathUnits"},
/* SPMask */
diff --git a/src/attributes.h b/src/attributes.h
index 7f18cb5ea..6594f1bbd 100644
--- a/src/attributes.h
+++ b/src/attributes.h
@@ -293,6 +293,11 @@ enum SPAttributeEnum {
SP_ATTR_PATTERNUNITS,
SP_ATTR_PATTERNCONTENTUNITS,
SP_ATTR_PATTERNTRANSFORM,
+ /* SPHatch */
+ SP_ATTR_HATCHUNITS,
+ SP_ATTR_HATCHCONTENTUNITS,
+ SP_ATTR_HATCHTRANSFORM,
+ SP_ATTR_PITCH,
/* SPClipPath */
SP_ATTR_CLIPPATHUNITS,
/* SPMask */
diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt
index 20424c845..800c4d0d4 100644
--- a/src/display/CMakeLists.txt
+++ b/src/display/CMakeLists.txt
@@ -13,6 +13,7 @@ set(display_SRC
drawing-group.cpp
drawing-image.cpp
drawing-item.cpp
+ drawing-pattern.cpp
drawing-shape.cpp
drawing-surface.cpp
drawing-text.cpp
@@ -75,6 +76,7 @@ set(display_SRC
drawing-group.h
drawing-image.h
drawing-item.h
+ drawing-pattern.h
drawing-shape.h
drawing-surface.h
drawing-text.h
diff --git a/src/display/Makefile_insert b/src/display/Makefile_insert
index abbd89a68..2355c3653 100644
--- a/src/display/Makefile_insert
+++ b/src/display/Makefile_insert
@@ -33,6 +33,8 @@ ink_common_sources += \
display/drawing-image.h \
display/drawing-item.cpp \
display/drawing-item.h \
+ display/drawing-pattern.cpp \
+ display/drawing-pattern.h \
display/drawing-shape.cpp \
display/drawing-shape.h \
display/drawing-surface.cpp \
diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp
index 2ea3641dc..5226edda3 100644
--- a/src/display/drawing-item.cpp
+++ b/src/display/drawing-item.cpp
@@ -16,6 +16,7 @@
#include "display/drawing-context.h"
#include "display/drawing-item.h"
#include "display/drawing-group.h"
+#include "display/drawing-pattern.h"
#include "display/drawing-surface.h"
#include "nr-filter.h"
#include "preferences.h"
@@ -110,6 +111,8 @@ DrawingItem::DrawingItem(Drawing &drawing)
, _transform(NULL)
, _clip(NULL)
, _mask(NULL)
+ , _fill_pattern(NULL)
+ , _stroke_pattern(NULL)
, _filter(NULL)
, _user_data(NULL)
, _cache(NULL)
@@ -164,6 +167,12 @@ DrawingItem::~DrawingItem()
case CHILD_ROOT:
_drawing._root = NULL;
break;
+ case CHILD_FILL_PATTERN:
+ _parent->_fill_pattern = NULL;
+ break;
+ case CHILD_STROKE_PATTERN:
+ _parent->_stroke_pattern = NULL;
+ break;
default: ;
}
@@ -172,6 +181,8 @@ DrawingItem::~DrawingItem()
}
clearChildren();
delete _transform;
+ delete _stroke_pattern;
+ delete _fill_pattern;
delete _clip;
delete _mask;
delete _filter;
@@ -366,6 +377,34 @@ DrawingItem::setMask(DrawingItem *item)
_markForUpdate(STATE_ALL, true);
}
+void
+DrawingItem::setFillPattern(DrawingPattern *pattern)
+{
+ _markForRendering();
+ delete _fill_pattern;
+ _fill_pattern = pattern;
+ if (pattern) {
+ pattern->_parent = this;
+ assert(pattern->_child_type == CHILD_ORPHAN);
+ pattern->_child_type = CHILD_FILL_PATTERN;
+ }
+ _markForUpdate(STATE_ALL, true);
+}
+
+void
+DrawingItem::setStrokePattern(DrawingPattern *pattern)
+{
+ _markForRendering();
+ delete _stroke_pattern;
+ _stroke_pattern = pattern;
+ if (pattern) {
+ pattern->_parent = this;
+ assert(pattern->_child_type == CHILD_ORPHAN);
+ pattern->_child_type = CHILD_STROKE_PATTERN;
+ }
+ _markForUpdate(STATE_ALL, true);
+}
+
/// Move this item to the given place in the Z order of siblings.
/// Does nothing if the item has no parent.
void
@@ -530,6 +569,12 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne
if (to_update & STATE_RENDER) {
// now that we know drawbox, dirty the corresponding rect on canvas
// unless filtered, groups do not need to render by themselves, only their members
+ if (_fill_pattern) {
+ _fill_pattern->update(area, child_ctx, flags, reset);
+ }
+ if (_stroke_pattern) {
+ _stroke_pattern->update(area, child_ctx, flags, reset);
+ }
if (!is_drawing_group(this) || (_filter && render_filters)) {
_markForRendering();
}
diff --git a/src/display/drawing-item.h b/src/display/drawing-item.h
index 925bcbddb..c0c6e321e 100644
--- a/src/display/drawing-item.h
+++ b/src/display/drawing-item.h
@@ -28,6 +28,7 @@ class Drawing;
class DrawingCache;
class DrawingContext;
class DrawingItem;
+class DrawingPattern;
namespace Filters {
@@ -114,6 +115,8 @@ public:
void setTransform(Geom::Affine const &trans);
void setClip(DrawingItem *item);
void setMask(DrawingItem *item);
+ void setFillPattern(DrawingPattern *pattern);
+ void setStrokePattern(DrawingPattern *pattern);
void setZOrder(unsigned z);
void setItemBounds(Geom::OptRect const &bounds);
void setFilterBounds(Geom::OptRect const &bounds);
@@ -135,8 +138,8 @@ protected:
CHILD_CLIP = 2, // referenced by _clip member of parent
CHILD_MASK = 3, // referenced by _mask member of parent
CHILD_ROOT = 4, // root item of _drawing
- CHILD_FILL_PATTERN = 5, // not yet implemented: referenced by fill pattern of parent
- CHILD_STROKE_PATTERN = 6 // not yet implemented: referenced by stroke pattern of parent
+ CHILD_FILL_PATTERN = 5, // referenced by fill pattern of parent
+ CHILD_STROKE_PATTERN = 6 // referenced by stroke pattern of parent
};
enum RenderResult {
RENDER_OK = 0,
@@ -185,6 +188,8 @@ protected:
DrawingItem *_clip;
DrawingItem *_mask;
+ DrawingPattern *_fill_pattern;
+ DrawingPattern *_stroke_pattern;
Inkscape::Filters::Filter *_filter;
void *_user_data; ///< Used to associate DrawingItems with SPItems that created them
DrawingCache *_cache;
diff --git a/src/display/drawing-pattern.cpp b/src/display/drawing-pattern.cpp
new file mode 100644
index 000000000..cf6358278
--- /dev/null
+++ b/src/display/drawing-pattern.cpp
@@ -0,0 +1,194 @@
+/**
+ * @file
+ * Canvas belonging to SVG pattern.
+ *//*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-pattern.h"
+#include "display/drawing-surface.h"
+
+namespace Inkscape {
+
+DrawingPattern::DrawingPattern(Drawing &drawing, bool debug)
+ : DrawingGroup(drawing)
+ , _pattern_to_user(NULL)
+ , _overflow_steps(1)
+ , _debug(debug)
+{
+}
+
+DrawingPattern::~DrawingPattern()
+{
+ delete _pattern_to_user; // delete NULL; is safe
+}
+
+void
+DrawingPattern::setPatternToUserTransform(Geom::Affine const &new_trans) {
+ Geom::Affine current;
+ if (_pattern_to_user) {
+ current = *_pattern_to_user;
+ }
+
+ if (!Geom::are_near(current, new_trans, 1e-18)) {
+ // mark the area where the object was for redraw.
+ _markForRendering();
+ if (new_trans.isIdentity()) {
+ delete _pattern_to_user; // delete NULL; is safe
+ _pattern_to_user = NULL;
+ } else {
+ _pattern_to_user = new Geom::Affine(new_trans);
+ }
+ _markForUpdate(STATE_ALL, true);
+ }
+}
+
+void
+DrawingPattern::setTileRect(Geom::Rect const &tile_rect) {
+ _tile_rect = tile_rect;
+}
+
+void
+DrawingPattern::setOverflow(Geom::Affine initial_transform, int steps, Geom::Affine step_transform) {
+ _overflow_initial_transform = initial_transform;
+ _overflow_steps = steps;
+ _overflow_step_transform = step_transform;
+}
+
+cairo_pattern_t *
+DrawingPattern::renderPattern(float opacity) {
+ bool needs_opacity = (1.0 - opacity) >= 1e-3;
+ bool visible = opacity >= 1e-3;
+
+ if (!visible) {
+ return NULL;
+ }
+
+ if (!_tile_rect || (_tile_rect->area() == 0)) {
+ return NULL;
+ }
+ Geom::Rect pattern_tile = *_tile_rect;
+
+ //TODO: If pattern_to_user set it to identity transform
+
+ // The DrawingSurface class handles the mapping from "logical space"
+ // (coordinates in the rendering) to "physical space" (surface pixels).
+ // An oversampling is done as the pattern may not pixel align with the final surface.
+ // The cairo surface is created when the DrawingContext is declared.
+ // Create drawing surface with size of pattern tile (in pattern space) but with number of pixels
+ // based on required resolution (c).
+ Inkscape::DrawingSurface pattern_surface(pattern_tile, _pattern_resolution);
+ Inkscape::DrawingContext dc(pattern_surface);
+ dc.transform( pattern_surface.drawingTransform().inverse() );
+
+ pattern_tile *= pattern_surface.drawingTransform();
+ Geom::IntRect one_tile = pattern_tile.roundOutwards();
+
+ // Render pattern.
+ if (needs_opacity) {
+ dc.pushGroup(); // this group is for pattern + opacity
+ }
+
+ if (_debug) {
+ dc.setSource(0.8, 0.0, 0.8);
+ dc.paint();
+ }
+
+ //FIXME: What flags to choose?
+ if (_overflow_steps == 1) {
+ render(dc, one_tile, RENDER_DEFAULT);
+ } else {
+ //Overflow transforms need to be transformed to the new coordinate system
+ //introduced by dc.transform( pattern_surface.drawingTransform().inverse() );
+ Geom::Affine dt = pattern_surface.drawingTransform();
+ Geom::Affine idt = pattern_surface.drawingTransform().inverse();
+ Geom::Affine initial_transform = idt * _overflow_initial_transform * dt;
+ Geom::Affine step_transform = idt * _overflow_step_transform * dt;
+
+ dc.transform(initial_transform);
+ for (int i = 0; i < _overflow_steps; i++) {
+ render(dc, one_tile, RENDER_DEFAULT);
+ dc.transform(step_transform);
+ }
+ }
+
+ //Uncomment to debug
+ // cairo_surface_t* raw = pattern_surface.raw();
+ // std::cout << " cairo_surface (sp-pattern): "
+ // << " width: " << cairo_image_surface_get_width( raw )
+ // << " height: " << cairo_image_surface_get_height( raw )
+ // << std::endl;
+ // std::string filename = "drawing-pattern.png";
+ // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() );
+
+ if (needs_opacity) {
+ dc.popGroupToSource(); // pop raw pattern
+ dc.paint(opacity); // apply opacity
+ }
+
+ cairo_pattern_t *cp = cairo_pattern_create_for_surface(pattern_surface.raw());
+ // Apply transformation to user space. Also compensate for oversampling.
+ if (_pattern_to_user) {
+ ink_cairo_pattern_set_matrix(cp, _pattern_to_user->inverse() * pattern_surface.drawingTransform());
+ } else {
+ ink_cairo_pattern_set_matrix(cp, pattern_surface.drawingTransform());
+ }
+
+ if (_debug) {
+ cairo_pattern_set_extend(cp, CAIRO_EXTEND_NONE);
+ } else {
+ cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT);
+ }
+
+ return cp;
+}
+
+unsigned
+DrawingPattern::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
+{
+ UpdateContext pattern_ctx;
+
+ if (!_tile_rect || (_tile_rect->area() == 0)) {
+ return STATE_NONE;
+ }
+
+ Geom::Rect pattern_tile = *_tile_rect;
+ Geom::Coord det_ctm = ctx.ctm.descrim();
+ Geom::Coord det_ps2user = _pattern_to_user ? _pattern_to_user->descrim() : 1.0;
+ Geom::Coord det_child_transform = _child_transform ? _child_transform->descrim() : 1.0;
+ const double oversampling = 2.0;
+ double scale = det_ctm*det_ps2user*det_child_transform * oversampling;
+ //FIXME: When scale is too big (zooming in a hatch), cairo doesn't render the pattern
+ //More precisely it fails when seting pattern matrix in DrawingPattern::renderPattern
+ //Fully correct solution should make use of visible area bbox and change hach tile rect
+ //accordingly
+ if (scale > 25) {
+ scale = 25;
+ }
+ Geom::Point c(pattern_tile.dimensions()*scale*oversampling);
+ _pattern_resolution = c.ceil();
+
+ Inkscape::DrawingSurface pattern_surface(pattern_tile, _pattern_resolution);
+
+ pattern_ctx.ctm = pattern_surface.drawingTransform();
+ return DrawingGroup::_updateItem(Geom::IntRect::infinite(), pattern_ctx, flags, reset);
+}
+
+} // end 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/display/drawing-pattern.h b/src/display/drawing-pattern.h
new file mode 100644
index 000000000..7483ba067
--- /dev/null
+++ b/src/display/drawing-pattern.h
@@ -0,0 +1,87 @@
+/**
+ * @file
+ * Canvas belonging to SVG pattern.
+ *//*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_INKSCAPE_DISPLAY_DRAWING_PATTERN_H
+#define SEEN_INKSCAPE_DISPLAY_DRAWING_PATTERN_H
+
+#include "display/drawing-group.h"
+
+typedef struct _cairo_pattern cairo_pattern_t;
+class SPStyle;
+
+namespace Inkscape {
+
+/**
+ * @brief Drawing tree node used for rendering paints.
+ *
+ * DrawingPattern is used for rendering patterns and hatches.
+ *
+ * It renders it's children to a cairo_pattern_t structure that can be
+ * applied as source for fill or stroke operations.
+ */
+class DrawingPattern
+ : public DrawingGroup
+{
+public:
+ DrawingPattern(Drawing &drawing, bool debug = false);
+ ~DrawingPattern();
+
+ /**
+ * Set the transformation from pattern to user coordinate systems.
+ * @see SPPattern description for explanation of coordinate systems.
+ */
+ void setPatternToUserTransform(Geom::Affine const &new_trans);
+ /**
+ * Set the tile rect position and dimentions in content coordinate system
+ */
+ void setTileRect(Geom::Rect const &tile_rect);
+ /**
+ * Turn on overflow rendering.
+ *
+ * Overflow is implemented as repeated rendering of pattern contents. In every step
+ * a translation transform is applied.
+ */
+ void setOverflow(Geom::Affine initial_transform, int steps, Geom::Affine step_transform);
+ /**
+ * Render the pattern.
+ *
+ * Returns caito_pattern_t structure that can be set as source surface.
+ */
+ cairo_pattern_t *renderPattern(float opacity);
+protected:
+ virtual unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx,
+ unsigned flags, unsigned reset);
+
+ Geom::Affine *_pattern_to_user;
+ Geom::Affine _overflow_initial_transform;
+ Geom::Affine _overflow_step_transform;
+ int _overflow_steps;
+ Geom::OptRect _tile_rect;
+ bool _debug;
+ Geom::IntPoint _pattern_resolution;
+};
+
+bool is_drawing_group(DrawingItem *item);
+
+} // end namespace Inkscape
+
+#endif // !SEEN_INKSCAPE_DISPLAY_DRAWING_PATTERN_H
+
+/*
+ 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 :
diff --git a/src/display/drawing-shape.cpp b/src/display/drawing-shape.cpp
index 1a41bdb3a..fe525b1a5 100644
--- a/src/display/drawing-shape.cpp
+++ b/src/display/drawing-shape.cpp
@@ -155,7 +155,7 @@ DrawingShape::_renderFill(DrawingContext &dc)
Inkscape::DrawingContext::Save save(dc);
dc.transform(_ctm);
- bool has_fill = _nrstyle.prepareFill(dc, _item_bbox);
+ bool has_fill = _nrstyle.prepareFill(dc, _item_bbox, _fill_pattern);
if( has_fill ) {
dc.path(_curve->get_pathvector());
@@ -171,7 +171,7 @@ DrawingShape::_renderStroke(DrawingContext &dc)
Inkscape::DrawingContext::Save save(dc);
dc.transform(_ctm);
- bool has_stroke = _nrstyle.prepareStroke(dc, _item_bbox);
+ bool has_stroke = _nrstyle.prepareStroke(dc, _item_bbox, _stroke_pattern);
has_stroke &= (_nrstyle.stroke_width != 0);
if( has_stroke ) {
@@ -231,8 +231,8 @@ DrawingShape::_renderItem(DrawingContext &dc, Geom::IntRect const &area, unsigne
// update fill and stroke paints.
// this cannot be done during nr_arena_shape_update, because we need a Cairo context
// to render svg:pattern
- bool has_fill = _nrstyle.prepareFill(dc, _item_bbox);
- bool has_stroke = _nrstyle.prepareStroke(dc, _item_bbox);
+ bool has_fill = _nrstyle.prepareFill(dc, _item_bbox, _fill_pattern);
+ bool has_stroke = _nrstyle.prepareStroke(dc, _item_bbox, _stroke_pattern);
has_stroke &= (_nrstyle.stroke_width != 0);
if (has_fill || has_stroke) {
diff --git a/src/display/drawing-text.cpp b/src/display/drawing-text.cpp
index 9f3b447df..cb6520bcd 100644
--- a/src/display/drawing-text.cpp
+++ b/src/display/drawing-text.cpp
@@ -445,13 +445,13 @@ unsigned DrawingText::_renderItem(DrawingContext &dc, Geom::IntRect const &/*are
Inkscape::DrawingContext::Save save(dc);
dc.transform(_ctm);
- has_fill = _nrstyle.prepareFill( dc, _item_bbox);
- has_stroke = _nrstyle.prepareStroke( dc, _item_bbox);
+ has_fill = _nrstyle.prepareFill( dc, _item_bbox, _fill_pattern);
+ has_stroke = _nrstyle.prepareStroke( dc, _item_bbox, _stroke_pattern);
// Avoid creating patterns if not needed
if( decorate ) {
- has_td_fill = _nrstyle.prepareTextDecorationFill( dc, _item_bbox);
- has_td_stroke = _nrstyle.prepareTextDecorationStroke(dc, _item_bbox);
+ has_td_fill = _nrstyle.prepareTextDecorationFill( dc, _item_bbox, _fill_pattern);
+ has_td_stroke = _nrstyle.prepareTextDecorationStroke(dc, _item_bbox, _stroke_pattern);
}
}
diff --git a/src/display/nr-style.cpp b/src/display/nr-style.cpp
index ec3117079..96d16bf06 100644
--- a/src/display/nr-style.cpp
+++ b/src/display/nr-style.cpp
@@ -14,6 +14,7 @@
#include "sp-paint-server.h"
#include "display/canvas-bpath.h" // contains SPStrokeJoinType, SPStrokeCapType etc. (WTF!)
#include "display/drawing-context.h"
+#include "display/drawing-pattern.h"
void NRStyle::Paint::clear()
{
@@ -95,7 +96,14 @@ NRStyle::~NRStyle()
void NRStyle::set(SPStyle *style)
{
if ( style->fill.isPaintserver() ) {
- fill.set(style->getFillPaintServer());
+ SPPaintServer* server = style->getFillPaintServer();
+ if ( server && server->isValid() ) {
+ fill.set(server);
+ } else if ( style->fill.colorSet ) {
+ fill.set(style->fill.value.color);
+ } else {
+ fill.clear();
+ }
} else if ( style->fill.isColor() ) {
fill.set(style->fill.value.color);
} else if ( style->fill.isNone() ) {
@@ -117,7 +125,14 @@ void NRStyle::set(SPStyle *style)
}
if ( style->stroke.isPaintserver() ) {
- stroke.set(style->getStrokePaintServer());
+ SPPaintServer* server = style->getStrokePaintServer();
+ if ( server && server->isValid() ) {
+ stroke.set(server);
+ } else if ( style->stroke.isColor() ) {
+ stroke.set(style->stroke.colorSet);
+ } else {
+ stroke.clear();
+ }
} else if ( style->stroke.isColor() ) {
stroke.set(style->stroke.value.color);
} else if ( style->stroke.isNone() ) {
@@ -285,13 +300,16 @@ void NRStyle::set(SPStyle *style)
update();
}
-bool NRStyle::prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox)
+bool NRStyle::prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
{
// update fill pattern
if (!fill_pattern) {
switch (fill.type) {
- case PAINT_SERVER: {
- fill_pattern = fill.server->pattern_new(dc.raw(), paintbox, fill.opacity);
+ case PAINT_SERVER:
+ if (pattern) {
+ fill_pattern = pattern->renderPattern(fill.opacity);
+ } else {
+ fill_pattern = fill.server->pattern_new(dc.raw(), paintbox, fill.opacity);
} break;
case PAINT_COLOR: {
SPColor const &c = fill.color;
@@ -311,14 +329,20 @@ void NRStyle::applyFill(Inkscape::DrawingContext &dc)
dc.setFillRule(fill_rule);
}
-bool NRStyle::prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox)
+bool NRStyle::prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
{
// update text decoration pattern
if (!text_decoration_fill_pattern) {
switch (text_decoration_fill.type) {
- case PAINT_SERVER: {
- text_decoration_fill_pattern = text_decoration_fill.server->pattern_new(dc.raw(), paintbox, text_decoration_fill.opacity);
- } break;
+ case PAINT_SERVER:
+ if (pattern) {
+ text_decoration_fill_pattern = pattern->renderPattern(
+ text_decoration_fill.opacity);
+ } else {
+ text_decoration_fill_pattern = text_decoration_fill.server->pattern_new(dc.raw(),
+ paintbox, text_decoration_fill.opacity);
+ }
+ break;
case PAINT_COLOR: {
SPColor const &c = text_decoration_fill.color;
text_decoration_fill_pattern = cairo_pattern_create_rgba(
@@ -337,19 +361,25 @@ void NRStyle::applyTextDecorationFill(Inkscape::DrawingContext &dc)
// Fill rule does not matter, no intersections.
}
-bool NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox)
+bool NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
{
if (!stroke_pattern) {
switch (stroke.type) {
- case PAINT_SERVER: {
- stroke_pattern = stroke.server->pattern_new(dc.raw(), paintbox, stroke.opacity);
- } break;
+ case PAINT_SERVER:
+ if (pattern) {
+ stroke_pattern = pattern->renderPattern(stroke.opacity);
+ } else {
+ stroke_pattern = stroke.server->pattern_new(dc.raw(), paintbox, stroke.opacity);
+ }
+ break;
case PAINT_COLOR: {
SPColor const &c = stroke.color;
- stroke_pattern = cairo_pattern_create_rgba(
- c.v.c[0], c.v.c[1], c.v.c[2], stroke.opacity);
- } break;
- default: break;
+ stroke_pattern = cairo_pattern_create_rgba(c.v.c[0], c.v.c[1], c.v.c[2],
+ stroke.opacity);
+ }
+ break;
+ default:
+ break;
}
}
if (!stroke_pattern) return false;
@@ -366,13 +396,19 @@ void NRStyle::applyStroke(Inkscape::DrawingContext &dc)
cairo_set_dash(dc.raw(), dash, n_dash, dash_offset); // fixme
}
-bool NRStyle::prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox)
+bool NRStyle::prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern)
{
if (!text_decoration_stroke_pattern) {
switch (text_decoration_stroke.type) {
- case PAINT_SERVER: {
- text_decoration_stroke_pattern = text_decoration_stroke.server->pattern_new(dc.raw(), paintbox, text_decoration_stroke.opacity);
- } break;
+ case PAINT_SERVER:
+ if (pattern) {
+ text_decoration_stroke_pattern = pattern->renderPattern(
+ text_decoration_stroke.opacity);
+ } else {
+ text_decoration_stroke_pattern = text_decoration_stroke.server->pattern_new(
+ dc.raw(), paintbox, text_decoration_stroke.opacity);
+ }
+ break;
case PAINT_COLOR: {
SPColor const &c = text_decoration_stroke.color;
text_decoration_stroke_pattern = cairo_pattern_create_rgba(
diff --git a/src/display/nr-style.h b/src/display/nr-style.h
index 83bcb1ab7..f324fdb56 100644
--- a/src/display/nr-style.h
+++ b/src/display/nr-style.h
@@ -21,6 +21,7 @@ class SPStyle;
namespace Inkscape {
class DrawingContext;
+class DrawingPattern;
}
struct NRStyle {
@@ -28,10 +29,10 @@ struct NRStyle {
~NRStyle();
void set(SPStyle *);
- bool prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox);
- bool prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox);
- bool prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox);
- bool prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox);
+ bool prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ bool prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ bool prepareTextDecorationFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
+ bool prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox, Inkscape::DrawingPattern *pattern);
void applyFill(Inkscape::DrawingContext &dc);
void applyStroke(Inkscape::DrawingContext &dc);
void applyTextDecorationFill(Inkscape::DrawingContext &dc);
diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp
index c09b8e9c8..7e61cdbbb 100644
--- a/src/extension/internal/cairo-render-context.cpp
+++ b/src/extension/internal/cairo-render-context.cpp
@@ -39,6 +39,7 @@
#include "sp-item.h"
#include "sp-item-group.h"
#include "style.h"
+#include "sp-hatch.h"
#include "sp-linear-gradient.h"
#include "sp-radial-gradient.h"
#include "sp-pattern.h"
@@ -1130,6 +1131,82 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver
}
cairo_pattern_t*
+CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox) {
+ g_assert( SP_IS_HATCH(paintserver) );
+ SPHatch *hatch = SP_HATCH(paintserver);
+
+ g_assert(hatch->pitch() > 0);
+
+ // create drawing and group
+ Inkscape::Drawing drawing;
+ unsigned dkey = SPItem::display_key_new(1);
+
+ hatch->show(drawing, dkey, pbox);
+
+ SPHatch::RenderInfo render_info = hatch->calculateRenderInfo(dkey);
+ Geom::Rect tile_rect = render_info.tile_rect;
+
+ // Cairo requires an integer pattern surface width/height.
+ // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel.
+ // Multiply by SUBPIX_SCALE to allow for less than a pixel precision
+ const int subpix_scale = 10;
+ double surface_width = MAX(ceil(subpix_scale * tile_rect.width() - 0.5), 1);
+ double surface_height = MAX(ceil(subpix_scale * tile_rect.height() - 0.5), 1);
+ Geom::Affine drawing_scale = Geom::Scale(surface_width / tile_rect.width(), surface_height / tile_rect.height());
+ Geom::Affine drawing_transform = Geom::Translate(-tile_rect.min()) * drawing_scale;
+
+ Geom::Affine child_transform = render_info.child_transform;
+ child_transform *= drawing_transform;
+
+ //The rendering of hatch overflow is implemented by repeated drawing
+ //of hatch paths over one strip. Within each iteration paths are moved by pitch value.
+ //The movement progresses from right to left. This gives the same result
+ //as drawing whole strips in left-to-right order.
+ gdouble overflow_right_strip = 0.0;
+ int overflow_steps = 1;
+ Geom::Affine overflow_transform;
+ if (hatch->style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) {
+ Geom::Interval bounds = hatch->bounds();
+ overflow_right_strip = floor(bounds.max() / hatch->pitch()) * hatch->pitch();
+ overflow_steps = ceil((overflow_right_strip - bounds.min()) / hatch->pitch()) + 1;
+ overflow_transform = Geom::Translate(hatch->pitch(), 0.0);
+ }
+
+ CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height);
+ pattern_ctx->setTransform(child_transform);
+ pattern_ctx->transform(Geom::Translate(-overflow_right_strip, 0.0));
+ pattern_ctx->pushState();
+
+ std::vector<SPHatchPath *> children;
+ hatch->hatchPaths(children);
+
+ for (int i = 0; i < overflow_steps; i++) {
+ for (std::vector<SPHatchPath *>::iterator iter = children.begin(); iter != children.end(); iter++) {
+ SPHatchPath *path = *iter;
+ _renderer->renderHatchPath(pattern_ctx, *path, dkey);
+ }
+ pattern_ctx->transform(overflow_transform);
+ }
+
+ pattern_ctx->popState();
+
+ // setup a cairo_pattern_t
+ cairo_surface_t *pattern_surface = pattern_ctx->getSurface();
+ TEST(pattern_ctx->saveAsPng("hatch.png"));
+ cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface);
+ cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT);
+
+ Geom::Affine pattern_transform;
+ pattern_transform = render_info.pattern_to_user_transform.inverse() * drawing_transform;
+ ink_cairo_pattern_set_matrix(result, pattern_transform);
+
+ hatch->hide(dkey);
+
+ delete pattern_ctx;
+ return result;
+}
+
+cairo_pattern_t*
CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const paintserver,
Geom::OptRect const &pbox, float alpha)
{
@@ -1182,8 +1259,9 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain
cairo_pattern_add_color_stop_rgba(pattern, rg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], rg->vector.stops[i].opacity * alpha);
}
} else if (SP_IS_PATTERN (paintserver)) {
-
pattern = _createPatternPainter(paintserver, pbox);
+ } else if (SP_IS_HATCH (paintserver)) {
+ pattern = _createHatchPainter(paintserver, pbox);
} else {
return NULL;
}
@@ -1249,26 +1327,29 @@ CairoRenderContext::_setFillStyle(SPStyle const *const style, Geom::OptRect cons
TRACE(("merged op=%f\n", alpha));
}
- if (style->fill.isColor()) {
- float rgb[3];
- sp_color_get_rgb_floatv(&style->fill.value.color, rgb);
+ SPPaintServer const *paint_server = style->getFillPaintServer();
+ if (paint_server && paint_server->isValid()) {
- cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
-
- } else if (!style->fill.set) { // unset fill is black
- cairo_set_source_rgba(_cr, 0, 0, 0, alpha);
-
- } else {
- g_assert( style->fill.isPaintserver()
- || SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
- || SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style)) );
-
- cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_FILL_SERVER(style), pbox, alpha);
+ g_assert(SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
+ || SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))
+ || SP_IS_HATCH(SP_STYLE_FILL_SERVER(style)));
+ cairo_pattern_t *pattern = _createPatternForPaintServer(paint_server, pbox, alpha);
if (pattern) {
cairo_set_source(_cr, pattern);
cairo_pattern_destroy(pattern);
}
+ } else if (style->fill.colorSet) {
+ float rgb[3];
+ sp_color_get_rgb_floatv(&style->fill.value.color, rgb);
+
+ cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
+
+ } else { // unset fill is black
+ g_assert(!style->fill.set
+ || (paint_server && !paint_server->isValid()));
+
+ cairo_set_source_rgba(_cr, 0, 0, 0, alpha);
}
}
@@ -1279,7 +1360,7 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &p
if (_state->merge_opacity)
alpha *= _state->opacity;
- if (style->stroke.isColor()) {
+ if (style->stroke.isColor() || (style->stroke.isPaintserver() && !style->getStrokePaintServer()->isValid())) {
float rgb[3];
sp_color_get_rgb_floatv(&style->stroke.value.color, rgb);
@@ -1287,7 +1368,8 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &p
} else {
g_assert( style->stroke.isPaintserver()
|| SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
- || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) );
+ || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style))
+ || SP_IS_HATCH(SP_STYLE_STROKE_SERVER(style)));
cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_STROKE_SERVER(style), pbox, alpha);
diff --git a/src/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h
index 8d3e63775..59781a49c 100644
--- a/src/extension/internal/cairo-render-context.h
+++ b/src/extension/internal/cairo-render-context.h
@@ -201,6 +201,7 @@ protected:
cairo_pattern_t *_createPatternForPaintServer(SPPaintServer const *const paintserver,
Geom::OptRect const &pbox, float alpha);
cairo_pattern_t *_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox);
+ cairo_pattern_t *_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox);
unsigned int _showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoGlyphInfo> const &glyphtext, bool is_stroke);
diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp
index 0fec68c06..367bfadbf 100644
--- a/src/extension/internal/cairo-renderer.cpp
+++ b/src/extension/internal/cairo-renderer.cpp
@@ -50,6 +50,7 @@
#include "sp-use.h"
#include "sp-text.h"
#include "sp-flowtext.h"
+#include "sp-hatch-path.h"
#include "sp-image.h"
#include "sp-symbol.h"
#include "sp-pattern.h"
@@ -598,6 +599,21 @@ void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item)
ctx->popState();
}
+void CairoRenderer::renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key) {
+ ctx->pushState();
+ ctx->setStateForStyle(hatchPath.style);
+ ctx->transform(Geom::Translate(hatchPath.offset.computed, 0));
+
+ SPCurve *curve = hatchPath.calculateRenderCurve(key);
+ Geom::PathVector const & pathv =curve->get_pathvector();
+ if (!pathv.empty()) {
+ ctx->renderPathVector(pathv, hatchPath.style, Geom::OptRect());
+ }
+
+ curve->unref();
+ ctx->popState();
+}
+
bool
CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, float bleedmargin_px, SPItem *base)
{
diff --git a/src/extension/internal/cairo-renderer.h b/src/extension/internal/cairo-renderer.h
index cfef6bdea..abc0447d8 100644
--- a/src/extension/internal/cairo-renderer.h
+++ b/src/extension/internal/cairo-renderer.h
@@ -29,6 +29,7 @@
class SPClipPath;
class SPMask;
+class SPHatchPath;
namespace Inkscape {
namespace Extension {
@@ -57,6 +58,7 @@ public:
/** Traverses the object tree and invokes the render methods. */
void renderItem(CairoRenderContext *ctx, SPItem *item);
+ void renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key);
};
// FIXME: this should be a static method of CairoRenderer
diff --git a/src/sp-hatch-path.cpp b/src/sp-hatch-path.cpp
new file mode 100644
index 000000000..f7138fac2
--- /dev/null
+++ b/src/sp-hatch-path.cpp
@@ -0,0 +1,331 @@
+/** @file
+ * SVG <hatchPath> implementation
+ *//*
+ * Author:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Tomasz Boczkowski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <cstring>
+#include <string>
+#include <2geom/path.h>
+#include <2geom/transforms.h>
+
+#include "svg/svg.h"
+#include "display/cairo-utils.h"
+#include "display/curve.h"
+#include "display/drawing-context.h"
+#include "display/drawing-surface.h"
+#include "display/drawing.h"
+#include "display/drawing-shape.h"
+#include "helper/geom.h"
+#include "attributes.h"
+#include "document-private.h"
+#include "uri.h"
+#include "style.h"
+#include "sp-hatch-path.h"
+#include "svg/css-ostringstream.h"
+#include "xml/repr.h"
+
+#include "sp-factory.h"
+
+namespace {
+SPObject* createHatchPath() {
+ return new SPHatchPath();
+}
+
+bool hatchRegistered = SPFactory::instance().registerObject("svg:hatchPath", createHatchPath);
+}
+
+SPHatchPath::SPHatchPath()
+ : _curve(NULL)
+ , _continuous(false)
+{
+ offset.unset();
+}
+
+SPHatchPath::~SPHatchPath() {
+}
+
+void SPHatchPath::setCurve(SPCurve *new_curve, bool owner) {
+ if (_curve) {
+ _curve = _curve->unref();
+ }
+
+ if (new_curve) {
+ if (owner) {
+ _curve = new_curve->ref();
+ } else {
+ _curve = new_curve->copy();
+ }
+ }
+
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
+void SPHatchPath::build(SPDocument* doc, Inkscape::XML::Node* repr) {
+ SPObject::build(doc, repr);
+
+ this->readAttr("d");
+ this->readAttr("offset");
+ this->readAttr( "style" );
+
+ style->fill.setNone();
+}
+
+void SPHatchPath::release() {
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ delete iter->arenaitem;
+ iter->arenaitem = NULL;
+ }
+
+ SPObject::release();
+}
+
+void SPHatchPath::set(unsigned int key, const gchar* value) {
+ switch (key) {
+ case SP_ATTR_D:
+ if (value) {
+ Geom::PathVector pv;
+ _readHatchPathVector(value, pv, _continuous);
+ SPCurve *curve = new SPCurve(pv);
+
+ if (curve) {
+ this->setCurve(curve, true);
+ curve->unref();
+ }
+ } else {
+ this->setCurve(NULL, true);
+ }
+
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_OFFSET:
+ offset.readOrUnset(value);
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ default:
+ if (SP_ATTRIBUTE_IS_CSS(key)) {
+ sp_style_read_from_object(this->style, this);
+ 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 (this->style->stroke_width.unit == SP_CSS_UNIT_PERCENT) {
+ //TODO: Check specification
+
+ SPItemCtx *ictx = (SPItemCtx *) ctx;
+ double const aw = 1.0 / ictx->i2vp.descrim();
+ this->style->stroke_width.computed = this->style->stroke_width.value * aw;
+
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ iter->arenaitem->setStyle(this->style);
+ }
+ }
+ }
+
+ if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ _updateView(*iter);
+ }
+ }
+}
+
+bool SPHatchPath::isValid() const {
+ if (_curve && (_repeatLength() <= 0)) {
+ return false;
+ }
+ return true;
+}
+
+Inkscape::DrawingItem *SPHatchPath::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptInterval extents) {
+ Inkscape::DrawingShape *s = new Inkscape::DrawingShape(drawing);
+ _display.push_front(View(s, key));
+ _display.front().extents = extents;
+
+ _updateView(_display.front());
+
+ return s;
+}
+
+void SPHatchPath::hide(unsigned int key) {
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ if (iter->key == key) {
+ delete iter->arenaitem;
+ _display.erase(iter);
+ return;
+ }
+ }
+
+ g_assert_not_reached();
+}
+
+void SPHatchPath::setStripExtents(unsigned int key, Geom::OptInterval const &extents) {
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ if (iter->key == key) {
+ iter->extents = extents;
+ break;
+ }
+ }
+}
+
+Geom::Interval SPHatchPath::bounds() const {
+ Geom::OptRect bbox;
+ Geom::Interval result;
+
+ Geom::Affine transform = Geom::Translate(offset.computed, 0);
+ if (!this->_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(this->_curve->get_pathvector(), transform);
+ }
+
+ gdouble 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 (ConstViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ if (iter->key == key) {
+ return _calculateRenderCurve(*iter);
+ }
+ }
+ g_assert_not_reached();
+ return NULL;
+}
+
+gdouble SPHatchPath::_repeatLength() const {
+ if (!_curve) {
+ return 0;
+ }
+
+ if (!_curve->last_point()) {
+ return 0;
+ }
+
+ return _curve->last_point()->y();
+}
+
+void SPHatchPath::_updateView(View &view) {
+ SPCurve *calculated_curve = _calculateRenderCurve(view);
+
+ Geom::Affine offset_transform = Geom::Translate(offset.computed, 0);
+ view.arenaitem->setTransform(offset_transform);
+ style->fill.setNone();
+ view.arenaitem->setStyle(this->style);
+ view.arenaitem->setPath(calculated_curve);
+
+ calculated_curve->unref();
+}
+
+SPCurve *SPHatchPath::_calculateRenderCurve(View const &view) const {
+ SPCurve *calculated_curve = new SPCurve;
+
+ 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;
+
+ SPCurve *segment =_curve->copy();
+ 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, 0.0625);
+ } else {
+ calculated_curve->append(segment, false);
+ }
+ segment->transform(step_transform);
+ }
+
+ segment->unref();
+ }
+ }
+ 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(Inkscape::DrawingShape *arenaitem, int key)
+ : arenaitem(arenaitem), 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 :
diff --git a/src/sp-hatch-path.h b/src/sp-hatch-path.h
new file mode 100644
index 000000000..57b3a8237
--- /dev/null
+++ b/src/sp-hatch-path.h
@@ -0,0 +1,91 @@
+/** @file
+ * SVG <hatchPath> implementation
+ *//*
+ * Author:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Tomasz Boczkowski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_SP_HATCH_PATH_H
+#define SEEN_SP_HATCH_PATH_H
+
+#include <list>
+#include <stddef.h>
+#include <glibmm/ustring.h>
+#include <sigc++/connection.h>
+
+#include "svg/svg-length.h"
+
+namespace Inkscape {
+
+class Drawing;
+class DrawingShape;
+
+}
+
+#define SP_HATCH_PATH(obj) (dynamic_cast<SPHatchPath*>((SPObject*)obj))
+#define SP_IS_HATCH_PATH(obj) (dynamic_cast<const SPHatchPath*>((SPObject*)obj) != NULL)
+
+class SPHatchPath : public SPObject {
+public:
+ SPHatchPath();
+ virtual ~SPHatchPath();
+
+ SVGLength offset;
+
+ void setCurve(SPCurve *curve, bool owner);
+
+ bool isValid() const;
+
+ Inkscape::DrawingItem *show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptInterval extents);
+ void hide(unsigned int key);
+
+ void setStripExtents(unsigned int key, Geom::OptInterval const &extents);
+ Geom::Interval bounds() const;
+
+ SPCurve *calculateRenderCurve(unsigned key) const;
+
+protected:
+ virtual void build(SPDocument* doc, Inkscape::XML::Node* repr);
+ virtual void release();
+ virtual void set(unsigned int key, const gchar* value);
+ virtual void update(SPCtx* ctx, unsigned int flags);
+
+private:
+ struct View {
+ View(Inkscape::DrawingShape *arenaitem, int key);
+ //Do not delete arenaitem in destructor.
+
+ Inkscape::DrawingShape *arenaitem;
+ Geom::OptInterval extents;
+ unsigned int key;
+ };
+ typedef std::list<SPHatchPath::View>::iterator ViewIterator;
+ typedef std::list<SPHatchPath::View>::const_iterator ConstViewIterator;
+ std::list<View> _display;
+
+ gdouble _repeatLength() const;
+ void _updateView(View &view);
+ SPCurve *_calculateRenderCurve(View const &view) const;
+
+ void _readHatchPathVector(char const *str, Geom::PathVector &pathv, bool &continous_join);
+
+ SPCurve *_curve;
+ bool _continuous;
+};
+
+#endif // SEEN_SP_HATCH_PATH_H
+
+/*
+ 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 :
diff --git a/src/sp-hatch.cpp b/src/sp-hatch.cpp
new file mode 100644
index 000000000..98cc596d7
--- /dev/null
+++ b/src/sp-hatch.cpp
@@ -0,0 +1,678 @@
+/** @file
+ * SVG <hatch> implementation
+ *//*
+ * Author:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Tomasz Boczkowski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <cstring>
+#include <string>
+#include <2geom/transforms.h>
+#include <sigc++/functors/mem_fun.h>
+
+#include "svg/svg.h"
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-surface.h"
+#include "display/drawing.h"
+#include "display/drawing-pattern.h"
+#include "attributes.h"
+#include "document-private.h"
+#include "uri.h"
+#include "style.h"
+#include "sp-hatch.h"
+#include "sp-hatch-path.h"
+#include "xml/repr.h"
+
+#include "sp-factory.h"
+
+namespace {
+SPObject* createHatch() {
+ return new SPHatch();
+}
+
+bool hatchRegistered = SPFactory::instance().registerObject("svg:hatch", createHatch);
+}
+
+SPHatch::SPHatch()
+ : SPPaintServer()
+{
+ this->ref = new SPHatchReference(this);
+ this->ref->changedSignal().connect(sigc::mem_fun(this, &SPHatch::_onRefChanged));
+
+ this->_hatchUnits = UNITS_OBJECTBOUNDINGBOX;
+ this->_hatchUnits_set = false;
+
+ this->_hatchContentUnits = UNITS_USERSPACEONUSE;
+ this->_hatchContentUnits_set = false;
+
+ this->_hatchTransform = Geom::identity();
+ this->_hatchTransform_set = false;
+
+ this->_x.unset();
+ this->_y.unset();
+ this->_pitch.unset();
+ this->_rotate.unset();
+}
+
+SPHatch::~SPHatch() {
+}
+
+void SPHatch::build(SPDocument* doc, Inkscape::XML::Node* repr) {
+ SPPaintServer::build(doc, repr);
+
+ this->readAttr("hatchUnits");
+ this->readAttr("hatchContentUnits");
+ this->readAttr("hatchTransform");
+ this->readAttr("x");
+ this->readAttr("y");
+ this->readAttr("pitch");
+ this->readAttr("rotate");
+ this->readAttr("xlink:href");
+ this->readAttr( "style" );
+
+ /* Register ourselves */
+ doc->addResource("hatch", this);
+}
+
+void SPHatch::release() {
+ if (this->document) {
+ // Unregister ourselves
+ this->document->removeResource("hatch", this);
+ }
+
+ std::vector<SPHatchPath *> children;
+ hatchPaths(children);
+ for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); view_iter++) {
+ for (ChildIterator child_iter = children.begin(); child_iter != children.end();
+ child_iter++) {
+ SPHatchPath *child = *child_iter;
+ child->hide(view_iter->key);
+ }
+ delete view_iter->arenaitem;
+ view_iter->arenaitem = NULL;
+ }
+
+ if (this->ref) {
+ this->_modified_connection.disconnect();
+ this->ref->detach();
+ delete this->ref;
+ this->ref = NULL;
+ }
+
+ SPPaintServer::release();
+}
+
+void SPHatch::child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref) {
+ SPObject::child_added(child, ref);
+
+ SPHatchPath *path_child = SP_HATCH_PATH(this->document->getObjectByRepr(child));
+
+ if (path_child) {
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ Geom::OptInterval extents = _calculateStripExtents(iter->bbox);
+ Inkscape::DrawingItem *ac = path_child->show(iter->arenaitem->drawing(), iter->key, extents);
+
+ path_child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ if (ac) {
+ iter->arenaitem->prependChild(ac);
+ }
+ }
+ }
+ //FIXME: notify all hatches that refer to this child set
+}
+
+void SPHatch::set(unsigned int key, const gchar* value) {
+ switch (key) {
+ case SP_ATTR_HATCHUNITS:
+ if (value) {
+ if (!strcmp(value, "userSpaceOnUse")) {
+ this->_hatchUnits = UNITS_USERSPACEONUSE;
+ } else {
+ this->_hatchUnits = UNITS_OBJECTBOUNDINGBOX;
+ }
+
+ this->_hatchUnits_set = true;
+ } else {
+ this->_hatchUnits_set = false;
+ }
+
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_HATCHCONTENTUNITS:
+ if (value) {
+ if (!strcmp(value, "userSpaceOnUse")) {
+ this->_hatchContentUnits = UNITS_USERSPACEONUSE;
+ } else {
+ this->_hatchContentUnits = UNITS_OBJECTBOUNDINGBOX;
+ }
+
+ this->_hatchContentUnits_set = true;
+ } else {
+ this->_hatchContentUnits_set = false;
+ }
+
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_HATCHTRANSFORM: {
+ Geom::Affine t;
+
+ if (value && sp_svg_transform_read(value, &t)) {
+ this->_hatchTransform = t;
+ this->_hatchTransform_set = true;
+ } else {
+ this->_hatchTransform = Geom::identity();
+ this->_hatchTransform_set = false;
+ }
+
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ }
+ case SP_ATTR_X:
+ this->_x.readOrUnset(value);
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_Y:
+ this->_y.readOrUnset(value);
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_PITCH:
+ this->_pitch.readOrUnset(value);
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_ROTATE:
+ this->_rotate.readOrUnset(value);
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ case SP_ATTR_XLINK_HREF:
+ if (value && this->href == value) {
+ /* Href unchanged, do nothing. */
+ } else {
+ this->href.clear();
+
+ if (value) {
+ // First, set the href field; it's only used in the "unchanged" check above.
+ this->href = value;
+ // Now do the attaching, which emits the changed signal.
+ if (value) {
+ try {
+ this->ref->attach(Inkscape::URI(value));
+ } catch (Inkscape::BadURIException &e) {
+ g_warning("%s", e.what());
+ this->ref->detach();
+ }
+ } else {
+ this->ref->detach();
+ }
+ }
+ }
+ this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+
+ default:
+ if (SP_ATTRIBUTE_IS_CSS(key)) {
+ sp_style_read_from_object(this->style, this);
+ requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
+ } else {
+ SPPaintServer::set(key, value);
+ }
+ break;
+ }
+}
+
+bool SPHatch::_hasHatchPatchChildren(SPHatch const *hatch) {
+ for (SPObject const *child = hatch->firstChild(); child; child = child->getNext() ) {
+ if (SP_IS_HATCH_PATH(child)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void SPHatch::hatchPaths(std::vector<SPHatchPath*>& l) {
+ SPHatch *src = chase_hrefs<SPHatch>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
+
+ if (src) {
+ for (SPObject *child = src->firstChild(); child; child = child->getNext()) {
+ if (SP_IS_HATCH_PATH(child)) {
+ l.push_back(SP_HATCH_PATH(child));
+ }
+ }
+ }
+}
+
+void SPHatch::hatchPaths(std::vector<SPHatchPath const*>& l) const {
+ SPHatch const *src = chase_hrefs<SPHatch const>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
+
+ if (src) {
+ for (SPObject const *child = src->firstChild(); child; child = child->getNext()) {
+ if (SP_IS_HATCH_PATH(child)) {
+ l.push_back(SP_HATCH_PATH(child));
+ }
+ }
+ }
+}
+
+/* TODO: ::remove_child and ::order_changed handles - see SPPattern */
+
+
+void SPHatch::update(SPCtx* ctx, unsigned int flags) {
+ typedef std::list<SPHatch::View>::iterator ViewIterator;
+
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
+ }
+
+ flags &= SP_OBJECT_MODIFIED_CASCADE;
+
+ std::vector<SPHatchPath *> children;
+ hatchPaths(children);
+
+ for (ChildIterator iter = children.begin(); iter != children.end(); iter++) {
+ SPHatchPath* child = *iter;
+
+ sp_object_ref(child, NULL);
+
+ for (ViewIterator view_iter = _display.begin(); view_iter != _display.end(); view_iter++) {
+ Geom::OptInterval strip_extents = _calculateStripExtents(view_iter->bbox);
+ child->setStripExtents(view_iter->key, strip_extents);
+ }
+
+ if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
+
+ child->updateDisplay(ctx, flags);
+ }
+
+ sp_object_unref(child, NULL);
+ }
+
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ _updateView(*iter);
+ }
+}
+
+void SPHatch::modified(unsigned int flags) {
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
+ }
+
+ flags &= SP_OBJECT_MODIFIED_CASCADE;
+
+ std::vector<SPHatchPath *> children;
+ hatchPaths(children);
+
+ for (ChildIterator iter = children.begin(); iter != children.end(); iter++) {
+ SPObject *child = *iter;
+
+ sp_object_ref(child, NULL);
+
+ if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
+ child->emitModified(flags);
+ }
+
+ sp_object_unref(child, NULL);
+ }
+}
+
+void SPHatch::_onRefChanged(SPObject *old_ref, SPObject *ref) {
+ typedef std::list<SPHatch::View>::iterator ViewIterator;
+
+ if (old_ref) {
+ _modified_connection.disconnect();
+ }
+
+ if (SP_IS_HATCH(ref)) {
+ _modified_connection = ref->connectModified(sigc::mem_fun(this, &SPHatch::_onRefModified));
+ }
+
+ if (!_hasHatchPatchChildren(this)) {
+ SPHatch *old_shown = NULL;
+ SPHatch *new_shown = NULL;
+ std::vector<SPHatchPath *> oldhatchPaths;
+ std::vector<SPHatchPath *> newhatchPaths;
+ if (SP_IS_HATCH(old_ref)) {
+ old_shown = SP_HATCH(old_ref)->rootHatch();
+ old_shown->hatchPaths(oldhatchPaths);
+ }
+ if (SP_IS_HATCH(ref)) {
+ new_shown = SP_HATCH(ref)->rootHatch();
+ new_shown->hatchPaths(newhatchPaths);
+ }
+ if (old_shown != new_shown) {
+
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ Geom::OptInterval extents = _calculateStripExtents(iter->bbox);
+
+ for (ChildIterator child_iter = oldhatchPaths.begin(); child_iter != oldhatchPaths.end(); child_iter++) {
+ SPHatchPath *child = *child_iter;
+ child->hide(iter->key);
+ }
+ for (ChildIterator child_iter = newhatchPaths.begin(); child_iter != newhatchPaths.end(); child_iter++) {
+ SPHatchPath *child = *child_iter;
+ Inkscape::DrawingItem *cai = child->show(iter->arenaitem->drawing(), iter->key, extents);
+ child->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ if (cai) {
+ iter->arenaitem->appendChild(cai);
+ }
+
+ }
+ }
+ }
+ }
+
+ _onRefModified(ref, 0);
+}
+
+void SPHatch::_onRefModified(SPObject */*ref*/, guint /*flags*/) {
+ requestModified(SP_OBJECT_MODIFIED_FLAG);
+ // Conditional to avoid causing infinite loop if there's a cycle in the href chain.
+}
+
+
+SPHatch *SPHatch::rootHatch() {
+ SPHatch *src = chase_hrefs<SPHatch>(this, sigc::ptr_fun(&_hasHatchPatchChildren));
+ return src ? src : this; // document is broken, we can't get to root; but at least we can return pat which is supposedly a valid hatch
+}
+
+// Access functions that look up fields up the chain of referenced hatchs and return the first one which is set
+// FIXME: all of them must use chase_hrefs as children() and rootHatch()
+
+SPHatch::HatchUnits SPHatch::hatchUnits() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_hatchUnits_set)
+ return pat_i->_hatchUnits;
+ }
+ return _hatchUnits;
+}
+
+SPHatch::HatchUnits SPHatch::hatchContentUnits() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_hatchContentUnits_set)
+ return pat_i->_hatchContentUnits;
+ }
+ return _hatchContentUnits;
+}
+
+Geom::Affine const &SPHatch::hatchTransform() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_hatchTransform_set)
+ return pat_i->_hatchTransform;
+ }
+ return _hatchTransform;
+}
+
+gdouble SPHatch::x() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_x._set)
+ return pat_i->_x.computed;
+ }
+ return 0;
+}
+
+gdouble SPHatch::y() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_y._set)
+ return pat_i->_y.computed;
+ }
+ return 0;
+}
+
+gdouble SPHatch::pitch() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_pitch._set)
+ return pat_i->_pitch.computed;
+ }
+ return 0;
+}
+
+gdouble SPHatch::rotate() const {
+ for (SPHatch const *pat_i = this; pat_i != NULL;
+ pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
+ if (pat_i->_rotate._set)
+ return pat_i->_rotate.computed;
+ }
+ return 0;
+}
+
+bool SPHatch::isValid() const {
+ double strip_pitch = pitch();
+ if (strip_pitch <= 0) {
+ return false;
+ }
+
+ std::vector<SPHatchPath const *> children;
+ hatchPaths(children);
+ if (children.empty()) {
+ return false;
+ }
+ for (ConstChildIterator iter = children.begin(); iter != children.end(); iter++) {
+ SPHatchPath const *child = *iter;
+ if (!child->isValid()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Inkscape::DrawingPattern *SPHatch::show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox) {
+ Inkscape::DrawingPattern *ai = new Inkscape::DrawingPattern(drawing);
+ //TODO: set some debug flag to see DrawingPattern
+ _display.push_front(View(ai, key));
+ _display.front().bbox = bbox;
+
+ std::vector<SPHatchPath *> children;
+ hatchPaths(children);
+
+ Geom::OptInterval extents = _calculateStripExtents(bbox);
+ for (ChildIterator iter = children.begin(); iter != children.end(); iter++) {
+ SPHatchPath *child = *iter;
+ Inkscape::DrawingItem *cai = child->show(drawing, key, extents);
+ if (cai) {
+ ai->appendChild(cai);
+ }
+ }
+
+ View& view = _display.front();
+ _updateView(view);
+
+ return ai;
+}
+
+void SPHatch::hide(unsigned int key) {
+ std::vector<SPHatchPath *> children;
+ hatchPaths(children);
+
+ for (ChildIterator iter = children.begin(); iter != children.end(); iter++) {
+ SPHatchPath *child = *iter;
+ child->hide(key);
+ }
+
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ if (iter->key == key) {
+ delete iter->arenaitem;
+ _display.erase(iter);
+ return;
+ }
+ }
+
+ g_assert_not_reached();
+}
+
+
+Geom::Interval SPHatch::bounds() const {
+ Geom::Interval result;
+ std::vector<SPHatchPath const *> children;
+ hatchPaths(children);
+
+ for (ConstChildIterator iter = children.begin(); iter != children.end(); iter++) {
+ SPHatchPath const *child = *iter;
+ if (result.extent() == 0) {
+ result = child->bounds();
+ } else {
+ result |= child->bounds();
+ }
+ }
+ return result;
+}
+
+SPHatch::RenderInfo SPHatch::calculateRenderInfo(unsigned key) const {
+ RenderInfo info;
+ for (ConstViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ if (iter->key == key) {
+ return _calculateRenderInfo(*iter);
+ }
+ }
+ g_assert_not_reached();
+ return info;
+}
+
+void SPHatch::_updateView(View &view) {
+ RenderInfo info = _calculateRenderInfo(view);
+ //The rendering of hatch overflow is implemented by repeated drawing
+ //of hatch paths over one strip. Within each iteration paths are moved by pitch value.
+ //The movement progresses from right to left. This gives the same result
+ //as drawing whole strips in left-to-right order.
+
+
+ view.arenaitem->setChildTransform(info.child_transform);
+ view.arenaitem->setPatternToUserTransform(info.pattern_to_user_transform);
+ view.arenaitem->setTileRect(info.tile_rect);
+ view.arenaitem->setStyle(this->style);
+ view.arenaitem->setOverflow(info.overflow_initial_transform, info.overflow_steps,
+ info.overflow_step_transform);
+}
+
+SPHatch::RenderInfo SPHatch::_calculateRenderInfo(View const &view) const {
+ RenderInfo info;
+
+ Geom::OptInterval extents = _calculateStripExtents(view.bbox);
+ if (!extents) {
+ return info;
+ }
+
+ double tile_x = x();
+ double tile_y = y();
+ double tile_width = pitch();
+ double tile_height = extents->max() - extents->min();
+ double tile_rotate = rotate();
+ double tile_render_y = extents->min();
+
+ if (view.bbox && (hatchUnits() == UNITS_OBJECTBOUNDINGBOX)) {
+ tile_x *= view.bbox->width();
+ tile_y *= view.bbox->height();
+ tile_width *= view.bbox->width();
+ tile_height *= view.bbox->height();
+ tile_render_y *= view.bbox->height();
+ }
+
+ // Pattern size in hatch space
+ Geom::Rect hatch_tile = Geom::Rect::from_xywh(0, tile_render_y, tile_width, tile_height);
+ // Content to bbox
+ Geom::Affine content2ps;
+ if (view.bbox && (hatchContentUnits() == UNITS_OBJECTBOUNDINGBOX)) {
+ content2ps = Geom::Affine(view.bbox->width(), 0.0, 0.0, view.bbox->height(), 0, 0);
+ }
+
+ // Tile (hatch space) to user.
+ Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform();
+
+ info.child_transform = content2ps;
+ info.pattern_to_user_transform = ps2user;
+ info.tile_rect = hatch_tile;
+
+ if (style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) {
+ Geom::Interval bounds = this->bounds();
+ gdouble pitch = this->pitch();
+ gdouble overflow_right_strip = floor(bounds.max() / pitch) * pitch;
+ info.overflow_steps = ceil((overflow_right_strip - bounds.min()) / pitch) + 1;
+ info.overflow_step_transform = Geom::Translate(pitch, 0.0);
+ info.overflow_initial_transform = Geom::Translate(-overflow_right_strip, 0.0);
+ } else {
+ info.overflow_steps = 1;
+ }
+
+ return info;
+}
+
+//calculates strip extents in content space
+Geom::OptInterval SPHatch::_calculateStripExtents(Geom::OptRect bbox) const {
+ if (!bbox || (bbox->area() == 0)) {
+ return Geom::OptInterval();
+ }
+
+ double tile_x = x();
+ double tile_y = y();
+ double tile_rotate = rotate();
+
+ Geom::Affine ps2user = Geom::Translate(tile_x, tile_y) * Geom::Rotate::from_degrees(tile_rotate) * hatchTransform();
+ Geom::Affine user2ps = ps2user.inverse();
+
+ Geom::Interval extents;
+ for (int i = 0; i < 4; i++) {
+ Geom::Point corner = bbox->corner(i);
+ Geom::Point corner_ps = corner * user2ps;
+ if (i == 0 || corner_ps.y() < extents.min()) {
+ extents.setMin(corner_ps.y());
+ }
+ if (i == 0 || corner_ps.y() > extents.max()) {
+ extents.setMax(corner_ps.y());
+ }
+ }
+
+ if (hatchUnits() == UNITS_OBJECTBOUNDINGBOX) {
+ extents /= bbox->height();
+ }
+
+ return extents;
+}
+
+cairo_pattern_t* SPHatch::pattern_new(cairo_t *base_ct, Geom::OptRect const &bbox, double opacity) {
+ //this code should not be used
+ //it is however required by the fact that SPPaintServer::hatch_new is pure virtual
+ return cairo_pattern_create_rgb(0.5, 0.5, 1.0);
+}
+
+void SPHatch::setBBox(unsigned int key, Geom::OptRect const &bbox) {
+ for (ViewIterator iter = _display.begin(); iter != _display.end(); iter++) {
+ if (iter->key == key) {
+ iter->bbox = bbox;
+ break;
+ }
+ }
+}
+
+SPHatch::View::View(Inkscape::DrawingPattern *arenaitem, int key)
+ : arenaitem(arenaitem), 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 :
diff --git a/src/sp-hatch.h b/src/sp-hatch.h
new file mode 100644
index 000000000..dc6ee0add
--- /dev/null
+++ b/src/sp-hatch.h
@@ -0,0 +1,174 @@
+/** @file
+ * SVG <hatch> implementation
+ *//*
+ * Author:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Tomasz Boczkowski
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_SP_HATCH_H
+#define SEEN_SP_HATCH_H
+
+#include <list>
+#include <stddef.h>
+#include <glibmm/ustring.h>
+#include <sigc++/connection.h>
+
+#include "svg/svg-length.h"
+#include "svg/svg-angle.h"
+#include "sp-paint-server.h"
+#include "uri-references.h"
+
+class SPHatchReference;
+class SPHatchPath;
+class SPItem;
+
+namespace Inkscape {
+
+class Drawing;
+class DrawingPattern;
+
+namespace XML {
+
+class Node;
+
+}
+}
+
+#define SP_HATCH(obj) (dynamic_cast<SPHatch*>((SPObject*)obj))
+#define SP_IS_HATCH(obj) (dynamic_cast<const SPHatch*>((SPObject*)obj) != NULL)
+
+class SPHatch : public SPPaintServer {
+public:
+ enum HatchUnits {
+ UNITS_USERSPACEONUSE,
+ UNITS_OBJECTBOUNDINGBOX
+ };
+
+ struct RenderInfo {
+ Geom::Affine child_transform;
+ Geom::Affine pattern_to_user_transform;
+ Geom::Rect tile_rect;
+
+ int overflow_steps;
+ Geom::Affine overflow_step_transform;
+ Geom::Affine overflow_initial_transform;
+ };
+
+ SPHatch();
+ virtual ~SPHatch();
+
+ /* Reference (href) */
+ Glib::ustring href;
+ SPHatchReference *ref;
+
+ gdouble x() const;
+ gdouble y() const;
+ gdouble pitch() const;
+ gdouble rotate() const;
+ HatchUnits hatchUnits() const;
+ HatchUnits hatchContentUnits() const;
+ Geom::Affine const &hatchTransform() const;
+ SPHatch *rootHatch(); //TODO: const
+
+ void hatchPaths(std::vector<SPHatchPath*>& l);
+ void hatchPaths(std::vector<SPHatchPath const *>& l) const;
+
+ bool isValid() const;
+
+ Inkscape::DrawingPattern *show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox);
+ void hide(unsigned int key);
+ virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity);
+
+ RenderInfo calculateRenderInfo(unsigned key) const;
+ Geom::Interval bounds() const;
+ void setBBox(unsigned int key, Geom::OptRect const &bbox);
+
+protected:
+ virtual void build(SPDocument* doc, Inkscape::XML::Node* repr);
+ virtual void release();
+ virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref);
+ virtual void set(unsigned int key, const gchar* value);
+ virtual void update(SPCtx* ctx, unsigned int flags);
+ virtual void modified(unsigned int flags);
+
+private:
+ struct View {
+ View(Inkscape::DrawingPattern *arenaitem, int key);
+ //Do not delete arenaitem in destructor.
+
+ Inkscape::DrawingPattern *arenaitem;
+ Geom::OptRect bbox;
+ unsigned int key;
+ };
+
+ typedef std::vector<SPHatchPath *>::iterator ChildIterator;
+ typedef std::vector<SPHatchPath const *>::const_iterator ConstChildIterator;
+ typedef std::list<View>::iterator ViewIterator;
+ typedef std::list<View>::const_iterator ConstViewIterator;
+
+ static bool _hasHatchPatchChildren(SPHatch const* hatch);
+
+ void _updateView(View &view);
+ RenderInfo _calculateRenderInfo(View const &view) const;
+ Geom::OptInterval _calculateStripExtents(Geom::OptRect bbox) const;
+
+
+ /**
+ Gets called when the hatch is reattached to another <hatch>
+ */
+ void _onRefChanged(SPObject *old_ref, SPObject *ref);
+
+ /**
+ Gets called when the referenced <hatch> is changed
+ */
+ void _onRefModified(SPObject *ref, guint flags);
+
+ /* patternUnits and patternContentUnits attribute */
+ HatchUnits _hatchUnits : 1;
+ bool _hatchUnits_set : 1;
+ HatchUnits _hatchContentUnits : 1;
+ bool _hatchContentUnits_set : 1;
+ /* hatchTransform attribute */
+ Geom::Affine _hatchTransform;
+ bool _hatchTransform_set : 1;
+ /* Strip */
+ SVGLength _x;
+ SVGLength _y;
+ SVGLength _pitch;
+ SVGAngle _rotate;
+
+ sigc::connection _modified_connection;
+
+ std::list<View> _display;
+};
+
+
+class SPHatchReference : public Inkscape::URIReference {
+public:
+ SPHatchReference (SPObject *obj) : URIReference(obj) {}
+ SPHatch *getObject() const {
+ return reinterpret_cast<SPHatch *>(URIReference::getObject());
+ }
+
+protected:
+ virtual bool _acceptObject(SPObject *obj) const {
+ return SP_IS_HATCH (obj);
+ }
+};
+
+#endif // SEEN_SP_HATCH_H
+
+/*
+ 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 :
diff --git a/src/sp-item.cpp b/src/sp-item.cpp
index 71cdfa394..f6de165a6 100644
--- a/src/sp-item.cpp
+++ b/src/sp-item.cpp
@@ -118,6 +118,9 @@ SPItem::SPItem() : SPObject() {
mask_ref = new SPMaskReference(this);
mask_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(mask_ref_changed), this));
+ style->signal_fill_ps_changed.connect(sigc::bind(sigc::ptr_fun(fill_ps_ref_changed), this));
+ style->signal_stroke_ps_changed.connect(sigc::bind(sigc::ptr_fun(stroke_ps_ref_changed), this));
+
avoidRef = new SPAvoidRef(this);
}
@@ -442,7 +445,15 @@ void SPItem::release() {
SPObject::release();
+ SPPaintServer *fill_ps = style->getFillPaintServer();
+ SPPaintServer *stroke_ps = style->getStrokePaintServer();
while (item->display) {
+ if (fill_ps) {
+ fill_ps->hide(item->display->arenaitem->key());
+ }
+ if (stroke_ps) {
+ stroke_ps->hide(item->display->arenaitem->key());
+ }
item->display = sp_item_view_list_remove(item->display, item->display);
}
@@ -588,6 +599,56 @@ void SPItem::mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item)
}
}
+void SPItem::fill_ps_ref_changed(SPObject *old_ps, SPObject *ps, SPItem *item) {
+ SPPaintServer *old_fill_ps = SP_PAINT_SERVER(old_ps);
+ if (old_fill_ps) {
+ for (SPItemView *v =item->display; v != NULL; v = v->next) {
+ old_fill_ps->hide(v->arenaitem->key());
+ }
+ }
+
+ SPPaintServer *new_fill_ps = SP_PAINT_SERVER(ps);
+ if (new_fill_ps) {
+ Geom::OptRect bbox = item->geometricBounds();
+ for (SPItemView *v = item->display; v != NULL; v = v->next) {
+ if (!v->arenaitem->key()) {
+ v->arenaitem->setKey(SPItem::display_key_new(3));
+ }
+ Inkscape::DrawingPattern *pi = new_fill_ps->show(
+ v->arenaitem->drawing(), v->arenaitem->key(), bbox);
+ v->arenaitem->setFillPattern(pi);
+ if (pi) {
+ new_fill_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ }
+ }
+}
+
+void SPItem::stroke_ps_ref_changed(SPObject *old_ps, SPObject *ps, SPItem *item) {
+ SPPaintServer *old_stroke_ps = SP_PAINT_SERVER(old_ps);
+ if (old_stroke_ps) {
+ for (SPItemView *v =item->display; v != NULL; v = v->next) {
+ old_stroke_ps->hide(v->arenaitem->key());
+ }
+ }
+
+ SPPaintServer *new_stroke_ps = SP_PAINT_SERVER(ps);
+ if (new_stroke_ps) {
+ Geom::OptRect bbox = item->geometricBounds();
+ for (SPItemView *v = item->display; v != NULL; v = v->next) {
+ if (!v->arenaitem->key()) {
+ v->arenaitem->setKey(SPItem::display_key_new(3));
+ }
+ Inkscape::DrawingPattern *pi = new_stroke_ps->show(
+ v->arenaitem->drawing(), v->arenaitem->key(), bbox);
+ v->arenaitem->setStrokePattern(pi);
+ if (pi) {
+ new_stroke_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ }
+ }
+}
+
void SPItem::update(SPCtx* /*ctx*/, guint flags) {
SPItem *item = this;
SPItem* object = item;
@@ -647,6 +708,9 @@ void SPItem::update(SPCtx* /*ctx*/, guint flags) {
item->avoidRef->handleSettingChange();
}
+void SPItem::modified(unsigned int flags) {
+}
+
Inkscape::XML::Node* SPItem::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
SPItem *item = this;
SPItem* object = item;
@@ -1090,6 +1154,33 @@ Inkscape::DrawingItem *SPItem::invoke_show(Inkscape::Drawing &drawing, unsigned
SP_MASK(mask)->sp_mask_set_bbox(mask_key, item_bbox);
mask->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}
+
+ SPPaintServer *fill_ps = style->getFillPaintServer();
+ if (fill_ps) {
+ if (!display->arenaitem->key()) {
+ display->arenaitem->setKey(display_key_new(3));
+ }
+ int fill_key = display->arenaitem->key();
+
+ Inkscape::DrawingPattern *ap = fill_ps->show(drawing, fill_key, item_bbox);
+ ai->setFillPattern(ap);
+ if (ap) {
+ fill_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ }
+ SPPaintServer *stroke_ps = style->getStrokePaintServer();
+ if (stroke_ps) {
+ if (!display->arenaitem->key()) {
+ display->arenaitem->setKey(display_key_new(3));
+ }
+ int stroke_key = display->arenaitem->key();
+
+ Inkscape::DrawingPattern *ap = stroke_ps->show(drawing, stroke_key, item_bbox);
+ ai->setStrokePattern(ap);
+ if (ap) {
+ stroke_ps->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ }
ai->setData(this);
ai->setItemBounds(geometricBounds());
}
@@ -1119,6 +1210,14 @@ void SPItem::invoke_hide(unsigned key)
mask_ref->getObject()->sp_mask_hide(v->arenaitem->key());
v->arenaitem->setMask(NULL);
}
+ SPPaintServer *fill_ps = style->getFillPaintServer();
+ if (fill_ps) {
+ fill_ps->hide(v->arenaitem->key());
+ }
+ SPPaintServer *stroke_ps = style->getStrokePaintServer();
+ if (stroke_ps) {
+ stroke_ps->hide(v->arenaitem->key());
+ }
if (!ref) {
display = v->next;
} else {
diff --git a/src/sp-item.h b/src/sp-item.h
index 15784d041..98aed0fd6 100644
--- a/src/sp-item.h
+++ b/src/sp-item.h
@@ -33,6 +33,7 @@
class SPClipPathReference;
class SPMaskReference;
class SPAvoidRef;
+class SPPattern;
struct SPPrintContext;
namespace Inkscape {
@@ -236,12 +237,15 @@ private:
static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, Inkscape::DrawingItem *arenaitem);
static void clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
static void mask_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
+ static void fill_ps_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
+ static void stroke_ps_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
public:
virtual void build(SPDocument *document, Inkscape::XML::Node *repr);
virtual void release();
virtual void set(unsigned int key, gchar const* value);
virtual void update(SPCtx *ctx, guint flags);
+ virtual void modified(unsigned int flags);
virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags);
virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type) const;
diff --git a/src/sp-paint-server.cpp b/src/sp-paint-server.cpp
index 692265bd8..d692fc611 100644
--- a/src/sp-paint-server.cpp
+++ b/src/sp-paint-server.cpp
@@ -58,6 +58,11 @@ bool SPPaintServer::isSolid() const
return solid;
}
+bool SPPaintServer::isValid() const
+{
+ return true;
+}
+
/*
Local Variables:
mode:c++
diff --git a/src/sp-paint-server.h b/src/sp-paint-server.h
index 02e2f10ec..6c88199b9 100644
--- a/src/sp-paint-server.h
+++ b/src/sp-paint-server.h
@@ -17,8 +17,16 @@
#include <cairo.h>
#include <2geom/rect.h>
+#include <sigc++/slot.h>
#include "sp-object.h"
+namespace Inkscape {
+
+class Drawing;
+class DrawingPattern;
+
+}
+
#define SP_PAINT_SERVER(obj) (dynamic_cast<SPPaintServer*>((SPObject*)obj))
#define SP_IS_PAINT_SERVER(obj) (dynamic_cast<const SPPaintServer*>((SPObject*)obj) != NULL)
@@ -29,13 +37,61 @@ public:
bool isSwatch() const;
bool isSolid() const;
+ virtual bool isValid() const;
- virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity) = 0;
+ //There are two ways to render a paint. The simple one is to create cairo_pattern_t structure
+ //on demand by pattern_new method. It is used for gradients. The other one is to add elements
+ //representing PaintServer in NR tree. It is used by hatches and patterns.
+ //Either pattern new or all three methods show, hide, setBBox need to be implemented
+ virtual Inkscape::DrawingPattern *show(Inkscape::Drawing &drawing, unsigned int key, Geom::OptRect bbox) {return NULL;}
+ virtual void hide(unsigned int key) {};
+ virtual void setBBox(unsigned int key, Geom::OptRect const &bbox) {};
+
+ virtual cairo_pattern_t* pattern_new(cairo_t *ct, Geom::OptRect const &bbox, double opacity) {return NULL;}
protected:
bool swatch;
};
+/**
+ * Returns the first of {src, src-\>ref-\>getObject(),
+ * src-\>ref-\>getObject()-\>ref-\>getObject(),...}
+ * for which \a match is true, or NULL if none found.
+ *
+ * The raison d'être of this routine is that it correctly handles cycles in the href chain (e.g., if
+ * a gradient gives itself as its href, or if each of two gradients gives the other as its href).
+ *
+ * \pre SP_IS_GRADIENT(src).
+ */
+template <class PaintServer>
+PaintServer *chase_hrefs(PaintServer *src, sigc::slot<bool, PaintServer const *> match) {
+ /* Use a pair of pointers for detecting loops: p1 advances half as fast as p2. If there is a
+ loop, then once p1 has entered the loop, we'll detect it the next time the distance between
+ p1 and p2 is a multiple of the loop size. */
+ PaintServer *p1 = src, *p2 = src;
+ bool do1 = false;
+ for (;;) {
+ if (match(p2)) {
+ return p2;
+ }
+
+ p2 = p2->ref->getObject();
+ if (!p2) {
+ return p2;
+ }
+ if (do1) {
+ p1 = p1->ref->getObject();
+ }
+ do1 = !do1;
+
+ if ( p2 == p1 ) {
+ /* We've been here before, so return NULL to indicate that no matching gradient found
+ * in the chain. */
+ return NULL;
+ }
+ }
+}
+
#endif // SEEN_SP_PAINT_SERVER_H
/*
Local Variables:
diff --git a/src/style-internal.cpp b/src/style-internal.cpp
index a81a2799d..b858e5cb6 100644
--- a/src/style-internal.cpp
+++ b/src/style-internal.cpp
@@ -964,7 +964,11 @@ SPIPaint::read( gchar const *str ) {
if (!value.href && document) {
// std::cout << " Creating value.href" << std::endl;
value.href = new SPPaintServerReference(document);
- value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun((this == &style->fill)? sp_style_fill_paint_server_ref_changed : sp_style_stroke_paint_server_ref_changed), style));
+ if (this == &style->fill) {
+ style->fill_ps_changed_connection = value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style));
+ } else {
+ style->stroke_ps_changed_connection = value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style));
+ }
}
// std::cout << "uri: " << (uri?uri:"null") << std::endl;
diff --git a/src/style-internal.h b/src/style-internal.h
index 69784199c..0b2b40001 100644
--- a/src/style-internal.h
+++ b/src/style-internal.h
@@ -505,8 +505,7 @@ class SPIPaint : public SPIBase {
void setColor( float r, float g, float b ) {value.color.set( r, g, b ); colorSet = true;}
void setColor( guint32 val ) {value.color.set( val ); colorSet = true;}
void setColor( SPColor const& color ) {value.color = color; colorSet = true;}
-
-
+ void setNone() {noneSet = true; colorSet=false;}
// To do: make private
public:
diff --git a/src/style.cpp b/src/style.cpp
index a91611d89..c17f06761 100644
--- a/src/style.cpp
+++ b/src/style.cpp
@@ -440,6 +440,8 @@ SPStyle::~SPStyle() {
// Remove connections
release_connection.disconnect();
+ fill_ps_changed_connection.disconnect();
+ stroke_ps_changed_connection.disconnect();
// The following shoud be moved into SPIPaint and SPIFilter
if (fill.value.href) {
@@ -499,10 +501,10 @@ SPStyle::clear() {
filter.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), this));
fill.value.href = new SPPaintServerReference(document);
- fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this));
+ fill_ps_changed_connection = fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this));
stroke.value.href = new SPPaintServerReference(document);
- stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this));
+ stroke_ps_changed_connection = stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this));
}
cloned = false;
@@ -1118,6 +1120,7 @@ sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle
ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
}
+ style->signal_fill_ps_changed.emit(old_ref, ref);
sp_style_paint_server_ref_modified(ref, 0, style);
}
@@ -1135,6 +1138,7 @@ sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPSty
ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
}
+ style->signal_stroke_ps_changed.emit(old_ref, ref);
sp_style_paint_server_ref_modified(ref, 0, style);
}
@@ -1363,7 +1367,11 @@ sp_style_set_ipaint_to_uri(SPStyle *style, SPIPaint *paint, const Inkscape::URI
// now that we have a document, we can create it here
if (!paint->value.href && document) {
paint->value.href = new SPPaintServerReference(document);
- paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun((paint == &style->fill)? sp_style_fill_paint_server_ref_changed : sp_style_stroke_paint_server_ref_changed), style));
+ if (paint == &style->fill) {
+ style->fill_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style));
+ } else {
+ style->stroke_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style));
+ }
}
if (paint->value.href){
diff --git a/src/style.h b/src/style.h
index 01a9d4b84..98d25d24c 100644
--- a/src/style.h
+++ b/src/style.h
@@ -257,6 +257,22 @@ public:
sigc::connection filter_modified_connection;
sigc::connection fill_ps_modified_connection;
sigc::connection stroke_ps_modified_connection;
+ sigc::connection fill_ps_changed_connection;
+ sigc::connection stroke_ps_changed_connection;
+
+ /**
+ * Emitted when paint server object, fill paint refers to, is changed. That is
+ * when the reference starts pointing to a different address in memory.
+ *
+ * NB It is different from fill_ps_modified signal. When paint server is modified
+ * it means some of it's attributes or chilren change.
+ */
+ sigc::signal<void, SPObject *, SPObject *> signal_fill_ps_changed;
+ /**
+ * Emitted when paint server object, fill paint refers to, is changed. That is
+ * when the reference starts pointing to a different address in memory.
+ */
+ sigc::signal<void, SPObject *, SPObject *> signal_stroke_ps_changed;
SPObject *getFilter() { return (filter.href) ? filter.href->getObject() : NULL; }
SPObject const *getFilter() const { return (filter.href) ? filter.href->getObject() : NULL; }
diff --git a/src/svg/CMakeLists.txt b/src/svg/CMakeLists.txt
index 968287895..f9d0bc52d 100644
--- a/src/svg/CMakeLists.txt
+++ b/src/svg/CMakeLists.txt
@@ -7,6 +7,7 @@ set(svg_SRC
strip-trailing-zeros.cpp
svg-affine.cpp
svg-color.cpp
+ svg-angle.cpp
svg-length.cpp
svg-path.cpp
# test-stubs.cpp
@@ -24,6 +25,7 @@ set(svg_SRC
svg-color-test.h
svg-color.h
svg-icc-color.h
+ svg-angle.h
svg-length-test.h
svg-length.h
svg-path-geom-test.h
diff --git a/src/svg/Makefile_insert b/src/svg/Makefile_insert
index cf9bf3fbb..4f82bdd76 100644
--- a/src/svg/Makefile_insert
+++ b/src/svg/Makefile_insert
@@ -13,6 +13,8 @@ ink_common_sources += \
svg/svg-color.cpp \
svg/svg-color.h \
svg/svg-icc-color.h \
+ svg/svg-angle.cpp \
+ svg/svg-angle.h \
svg/svg-length.cpp \
svg/svg-length.h \
svg/svg-path.cpp \
diff --git a/src/svg/svg-angle.cpp b/src/svg/svg-angle.cpp
new file mode 100644
index 000000000..63152368e
--- /dev/null
+++ b/src/svg/svg-angle.cpp
@@ -0,0 +1,136 @@
+/**
+ * \file src/svg/svg-angle.cpp
+ * \brief SVG angle type
+ */
+/*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <cstring>
+#include <string>
+#include <math.h>
+#include <glib.h>
+
+#include "svg.h"
+#include "stringstream.h"
+#include "svg/svg-angle.h"
+#include "util/units.h"
+
+
+static bool sp_svg_angle_read_lff(gchar const *str, SVGAngle::Unit &unit, float &val, float &computed);
+
+SVGAngle::SVGAngle()
+ : _set(false)
+ , unit(NONE)
+ , value(0)
+ , computed(0)
+{
+}
+
+/* Angle */
+
+bool SVGAngle::read(gchar const *str)
+{
+ if (!str) {
+ return false;
+ }
+
+ SVGAngle::Unit u;
+ float v;
+ float c;
+ if (!sp_svg_angle_read_lff(str, u, v, c)) {
+ return false;
+ }
+
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+
+ return true;
+}
+
+void SVGAngle::unset(SVGAngle::Unit u, float v, float c) {
+ _set = false;
+ unit = u;
+ value = v;
+ computed = c;
+}
+
+void SVGAngle::readOrUnset(gchar const *str, Unit u, float v, float c) {
+ if (!read(str)) {
+ unset(u, v, c);
+ }
+}
+
+static bool sp_svg_angle_read_lff(gchar const *str, SVGAngle::Unit &unit, float &val, float &computed)
+{
+ if (!str) {
+ return false;
+ }
+
+ gchar const *e;
+ float const v = g_ascii_strtod(str, (char **) &e);
+ if (e == str) {
+ return false;
+ }
+
+ if (!e[0]) {
+ /* Unitless (defaults to degrees)*/
+ unit = SVGAngle::NONE;
+ val = v;
+ computed = v;
+ return true;
+ } else if (!g_ascii_isalnum(e[0])) {
+ if (g_ascii_isspace(e[0]) && e[1] && g_ascii_isalpha(e[1])) {
+ return false; // spaces between value and unit are not allowed
+ } else {
+ /* Unitless (defaults to degrees)*/
+ unit = SVGAngle::NONE;
+ val = v;
+ computed = v;
+ return true;
+ }
+ } else {
+ if (strncmp(e, "deg", 3) == 0) {
+ unit = SVGAngle::DEG;
+ computed = v;
+ } else if (strncmp(e, "grad", 4) == 0) {
+ unit = SVGAngle::GRAD;
+ computed = Inkscape::Util::Quantity::convert(v, "grad", "°");
+ } else if (strncmp(e, "rad", 3) == 0) {
+ unit = SVGAngle::RAD;
+ computed = Inkscape::Util::Quantity::convert(v, "rad", "°");
+ } else if (strncmp(e, "turn", 4) == 0) {
+ unit = SVGAngle::TURN;
+ computed = Inkscape::Util::Quantity::convert(v, "turn", "°");
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /* Invalid */
+ return false;
+}
+
+/*
+ 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 :
diff --git a/src/svg/svg-angle.h b/src/svg/svg-angle.h
new file mode 100644
index 000000000..966491174
--- /dev/null
+++ b/src/svg/svg-angle.h
@@ -0,0 +1,69 @@
+#ifndef SEEN_SP_SVG_ANGLE_H
+#define SEEN_SP_SVG_ANGLE_H
+
+/**
+ * \file src/svg/svg-angle.h
+ * \brief SVG angle type
+ */
+/*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib.h>
+
+class SVGAngle
+{
+public:
+ SVGAngle();
+
+ enum Unit {
+ NONE,
+ DEG,
+ GRAD,
+ RAD,
+ TURN,
+ LAST_UNIT = TURN
+ };
+
+ // The object's value is valid / exists in SVG.
+ bool _set;
+
+ // The unit of value.
+ Unit unit;
+
+ // The value of this SVGAngle as found in the SVG.
+ float value;
+
+ // The value in degrees.
+ float computed;
+
+ float operator=(float v) {
+ _set = true;
+ unit = NONE;
+ value = computed = v;
+ return v;
+ }
+
+ bool read(gchar const *str);
+ void unset(Unit u = NONE, float v = 0, float c = 0);
+ void readOrUnset(gchar const *str, Unit u = NONE, float v = 0, float c = 0);
+};
+
+#endif // SEEN_SP_SVG_ANGLE_H
+
+/*
+ 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 :