diff options
| author | Tavmjong Bah <tavmjong@free.fr> | 2014-10-14 18:38:41 +0000 |
|---|---|---|
| committer | tavmjong-free <tavmjong@free.fr> | 2014-10-14 18:38:41 +0000 |
| commit | e056d0fa49ed5b9369c0c16c1c4df0c671b88b68 (patch) | |
| tree | 1788e981f21d9b8ae27a3fb23cd7ee68c98e27f9 /src | |
| parent | Implement marker 'orient' attribute value 'auto-start-reverse'. (diff) | |
| parent | Merged hatch pdf and png export code (diff) | |
| download | inkscape-e056d0fa49ed5b9369c0c16c1c4df0c671b88b68.tar.gz inkscape-e056d0fa49ed5b9369c0c16c1c4df0c671b88b68.zip | |
Merge hatch rendering code from Tomasz Boczkowski.
(bzr r13614)
Diffstat (limited to 'src')
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 : |
