=== modified file 'share/icons/icons.svg' --- share/icons/icons.svg 2013-09-20 17:04:04 +0000 +++ share/icons/icons.svg 2014-02-10 20:22:34 +0000 @@ -3819,7 +3819,6 @@ - @@ -3851,4 +3850,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +2 +3 + + + + + + + + + + + + === modified file 'src/desktop-style.cpp' --- src/desktop-style.cpp 2014-02-09 11:08:00 +0000 +++ src/desktop-style.cpp 2014-02-10 20:22:34 +0000 @@ -1499,6 +1499,113 @@ } /** + * Write to style_res the text decorations of a list of objects. + */ +int +objects_query_textdecoration (GSList *objects, SPStyle *style_res) +{ + if (g_slist_length(objects) == 0) { + /* No objects, set empty */ + return QUERY_STYLE_NOTHING; + } + + SPITextDecorationLine text_decoration_line; + SPITextDecorationStyle text_decoration_style; + SPITextDecorationStyle sumt_decoration_style; + SPIPaint text_decoration_fill; + SPIPaint text_decoration_stroke; + int n_count = 0; + bool multi = false; + + sumt_decoration_style.set = 0; + sumt_decoration_style.inherit = 0; + sumt_decoration_style.solid = 0; + sumt_decoration_style.isdouble = 0; + sumt_decoration_style.dotted = 0; + sumt_decoration_style.dashed = 0; + sumt_decoration_style.wavy = 0; + + text_decoration_line.set = 0; + text_decoration_line.inherit = 0; + text_decoration_line.underline = 0; + text_decoration_line.overline = 0; + text_decoration_line.line_through = 0; + text_decoration_line.blink = 0; + + for (GSList const *i = objects; i != NULL; i = i->next) { + SPObject *obj = SP_OBJECT (i->data); + if (!SP_IS_ITEM(obj)) { + continue; + } + SPStyle *style = obj->style; + if (!style) { + continue; + } + + if ( !style->text_decoration_line.set ) { + continue; + } + + n_count++; + + text_decoration_line.set |= style->text_decoration_line.set; + text_decoration_line.inherit |= style->text_decoration_line.inherit; + text_decoration_line.underline |= style->text_decoration_line.underline; + text_decoration_line.overline |= style->text_decoration_line.overline; + text_decoration_line.line_through |= style->text_decoration_line.line_through; + text_decoration_line.blink |= style->text_decoration_line.blink; + + sumt_decoration_style.set |= style->text_decoration_style.set; + sumt_decoration_style.inherit |= style->text_decoration_style.inherit; + sumt_decoration_style.solid |= style->text_decoration_style.solid; + sumt_decoration_style.isdouble |= style->text_decoration_style.isdouble; + sumt_decoration_style.dotted |= style->text_decoration_style.dotted; + sumt_decoration_style.dashed |= style->text_decoration_style.dashed; + sumt_decoration_style.wavy |= style->text_decoration_style.wavy; + + if(style->text_decoration_style.set){ + // last tspan style that has a style set wins + text_decoration_style.set = style->text_decoration_style.set; + text_decoration_style.inherit = style->text_decoration_style.inherit; + text_decoration_style.solid = style->text_decoration_style.solid; + text_decoration_style.isdouble = style->text_decoration_style.isdouble; + text_decoration_style.dotted = style->text_decoration_style.dotted; + text_decoration_style.dashed = style->text_decoration_style.dashed; + text_decoration_style.wavy = style->text_decoration_style.wavy; + } + text_decoration_fill.set = style->text_decoration_fill.set; + text_decoration_fill.inherit = style->text_decoration_fill.inherit; + text_decoration_stroke.set = style->text_decoration_stroke.set; + text_decoration_stroke.inherit = style->text_decoration_stroke.inherit; + //Todo Fixme - copy the actual colors too + } + if((sumt_decoration_style.set + sumt_decoration_style.inherit + sumt_decoration_style.solid + + sumt_decoration_style.isdouble + sumt_decoration_style.dotted + sumt_decoration_style.dashed + + sumt_decoration_style.wavy) + > 1)multi = true; + + if((text_decoration_line.set + text_decoration_line.inherit + text_decoration_line.underline + + text_decoration_line.overline + text_decoration_line.line_through + text_decoration_line.blink) + > 1)multi = true; + + style_res->text_decoration_line = text_decoration_line; + style_res->text_decoration_style = text_decoration_style; + style_res->text_decoration_fill = text_decoration_fill; + style_res->text_decoration_stroke = text_decoration_stroke; + + if (n_count == 0) { + return QUERY_STYLE_NOTHING; + } else if (n_count == 1) { + return QUERY_STYLE_SINGLE; + } else { + if (!multi) + return QUERY_STYLE_MULTIPLE_SAME; + else + return QUERY_STYLE_MULTIPLE_DIFFERENT; + } +} + +/** * Query the given list of objects for the given property, write * the result to style, return appropriate flag. */ @@ -1537,6 +1644,8 @@ return objects_query_blend (list, style); } else if (property == QUERY_STYLE_PROPERTY_BLUR) { return objects_query_blur (list, style); + } else if (property == QUERY_STYLE_PROPERTY_TEXTDECORATION) { + return objects_query_textdecoration (list, style); } return QUERY_STYLE_NOTHING; } === modified file 'src/desktop-style.h' --- src/desktop-style.h 2013-03-14 10:28:27 +0000 +++ src/desktop-style.h 2014-02-10 20:22:34 +0000 @@ -51,7 +51,8 @@ QUERY_STYLE_PROPERTY_BASELINES, // baseline-shift QUERY_STYLE_PROPERTY_MASTEROPACITY, // opacity QUERY_STYLE_PROPERTY_BLEND, // blend - QUERY_STYLE_PROPERTY_BLUR // blur + QUERY_STYLE_PROPERTY_BLUR, // blur + QUERY_STYLE_PROPERTY_TEXTDECORATION // text-decoration }; void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines); @@ -78,6 +79,7 @@ int objects_query_strokejoin (GSList *objects, SPStyle *style_res); int objects_query_blur (GSList *objects, SPStyle *style_res); +int objects_query_textdecoration (GSList *objects, SPStyle *style_res); int sp_desktop_query_style_from_list (GSList *list, SPStyle *style, int property); int sp_desktop_query_style(SPDesktop *desktop, SPStyle *style, int property); === modified file 'src/display/drawing-shape.cpp' --- src/display/drawing-shape.cpp 2014-02-08 08:44:12 +0000 +++ src/display/drawing-shape.cpp 2014-02-10 20:22:34 +0000 @@ -179,19 +179,19 @@ // update fill and stroke paints. // this cannot be done during nr_arena_shape_update, because we need a Cairo context // to render svg:pattern - has_fill = _nrstyle.prepareFill(dc, _item_bbox); - has_stroke = _nrstyle.prepareStroke(dc, _item_bbox); + has_fill = _nrstyle.prepareFill(dc.raw(), _item_bbox); + has_stroke = _nrstyle.prepareStroke(dc.raw(), _item_bbox); has_stroke &= (_nrstyle.stroke_width != 0); if (has_fill || has_stroke) { // TODO: remove segments outside of bbox when no dashes present dc.path(_curve->get_pathvector()); if (has_fill) { - _nrstyle.applyFill(dc); + _nrstyle.applyFill(dc.raw()); dc.fillPreserve(); } if (has_stroke) { - _nrstyle.applyStroke(dc); + _nrstyle.applyStroke(dc.raw()); dc.strokePreserve(); } dc.newPath(); // clear path === modified file 'src/display/drawing-text.cpp' --- src/display/drawing-text.cpp 2014-02-08 08:44:12 +0000 +++ src/display/drawing-text.cpp 2014-02-10 20:33:56 +0000 @@ -84,8 +84,12 @@ scale_bigbox /= _transform->descrim(); } + // bounding box for draws, a little bigger because some of the text decorations can extend outwards quite a ways Geom::Rect bigbox(Geom::Point(0.0, _asc*scale_bigbox*1.1),Geom::Point(_width*scale_bigbox, -_dsc*scale_bigbox*1.1)); Geom::Rect b = bigbox * ctx.ctm; + // bounding box for picking - vertical limits are maximum font ascent/descent, horizontal limits are full width of the text. + Geom::Rect pbigbox(Geom::Point(0.0, _asc*scale_bigbox*1.0),Geom::Point(_width*scale_bigbox, -_dsc*scale_bigbox*1.0)); + Geom::Rect pb = pbigbox * ctx.ctm; if (ggroup->_nrstyle.stroke.type != NRStyle::PAINT_NONE) { // this expands the selection box for cases where the stroke is "thick" @@ -96,10 +100,11 @@ float width = MAX(0.125, ggroup->_nrstyle.stroke_width * scale); if ( fabs(ggroup->_nrstyle.stroke_width * scale) > 0.01 ) { // FIXME: this is always true b.expandBy(0.5 * width); + pb.expandBy(0.5 * width); } // save bbox without miters for picking - _pick_bbox = b.roundOutwards(); + _pick_bbox = pb.roundOutwards(); float miterMax = width * ggroup->_nrstyle.miter_limit; if ( miterMax > 0.01 ) { @@ -110,14 +115,8 @@ _bbox = b.roundOutwards(); } else { _bbox = b.roundOutwards(); - _pick_bbox = *_bbox; + _pick_bbox = pb.roundOutwards(); } -/* -std::cout << "DEBUG _bbox" -<< " { " << _bbox->min()[Geom::X] << " , " << _bbox->min()[Geom::Y] -<< " } , { " << _bbox->max()[Geom::X] << " , " << _bbox->max()[Geom::Y] -<< " }" << std::endl; -*/ return STATE_ALL; } @@ -136,8 +135,9 @@ // With text we take a simple approach: pick if the point is in a character bbox Geom::Rect expanded(_pick_bbox); - expanded.expandBy(delta); - if (expanded.contains(p)) return this; + // FIXME, why expand by delta? When is the next line needed? + // expanded.expandBy(delta); + if (expanded.contains(p))return this; return NULL; } @@ -195,7 +195,21 @@ return DrawingGroup::_updateItem(area, ctx, flags, reset); } -void DrawingText::decorateStyle(DrawingContext &dc, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2) +void +revrectangle(cairo_t *ct, Geom::Rect const &r) { + cairo_move_to ( ct, r.left(), r.top() ); + cairo_rel_line_to (ct, 0, r.height() ); + cairo_rel_line_to (ct, r.width(), 0 ); + cairo_rel_line_to (ct, 0, -r.height() ); + cairo_close_path ( ct); +} +void rectangle(cairo_t *ct, Geom::Rect const &r) { + cairo_rectangle(ct, r.left(), r.top(), r.width(), r.height()); +} + + +//Fixme The next method should probably be elsewhere, as it is also called by render +void DrawingText::decorateStyle(cairo_t *ct, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2, double thickness) { double wave[16]={ 0.000000, 0.382499, 0.706825, 0.923651, 1.000000, 0.923651, 0.706825, 0.382499, @@ -213,7 +227,6 @@ 4, 3, 2, 1, -4, -3, -2, -1 }; - Geom::Point p3,p4,ps,pf; double step = vextent/32.0; unsigned i = 15 & (unsigned) round(xphase/step); // xphase is >= 0.0 @@ -221,26 +234,23 @@ This allows decoration continuity within the line, and does not step outside the clip box off the end For the first/last section on the line though, stay well clear of the edge, or when the text is dragged it may "spray" pixels. - if(_nrstyle.tspan_line_end){ pf = p2 - Geom::Point(2*step, 0.0); } - else { pf = p2; } - if(_nrstyle.tspan_line_start){ ps = p1 + Geom::Point(2*step, 0.0); - i = 15 & (i + 2); - } - else { ps = p1; } */ /* snap to nearest step in X */ -ps = Geom::Point(step * round(p1[Geom::X]/step),p1[Geom::Y]); -pf = Geom::Point(step * round(p2[Geom::X]/step),p2[Geom::Y]); + Geom::Point ps = Geom::Point(step * round(p1[Geom::X]/step),p1[Geom::Y]); + Geom::Point pf = Geom::Point(step * round(p2[Geom::X]/step),p2[Geom::Y]); + Geom::Point poff = Geom::Point(0,thickness/2.0); if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_ISDOUBLE){ ps -= Geom::Point(0, vextent/12.0); pf -= Geom::Point(0, vextent/12.0); - dc.moveTo(ps); - dc.lineTo(pf); +// cairo_move_to(ct, ps[Geom::X], ps[Geom::Y]); +// cairo_line_to(ct, pf[Geom::X], pf[Geom::Y]); + revrectangle(ct, Geom::Rect(ps + poff, pf - poff)); ps += Geom::Point(0, vextent/6.0); pf += Geom::Point(0, vextent/6.0); - dc.moveTo(ps); - dc.lineTo(pf); +// cairo_move_to(ct, ps[Geom::X], ps[Geom::Y]); +// cairo_line_to(ct, pf[Geom::X], pf[Geom::Y]); + revrectangle(ct, Geom::Rect(ps + poff, pf - poff)); } /* The next three have a problem in that they are phase dependent. The bits of a line are not necessarily passing through this routine in order, so we have to use the xphase information @@ -248,43 +258,51 @@ Huge possitive offset should keep the phase calculation from ever being negative. */ else if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_DOTTED){ + Geom::Point pv = ps; while(1){ + Geom::Point pvlast = pv; if(dots[i]>0){ - if(ps[Geom::X]> pf[Geom::X])break; - dc.moveTo(ps); - ps += Geom::Point(step * (double)dots[i], 0.0); - if(ps[Geom::X]>= pf[Geom::X]){ - dc.lineTo(pf); + if(pv[Geom::X] > pf[Geom::X])break; +// cairo_move_to(ct, pv[Geom::X], pv[Geom::Y]); + pv += Geom::Point(step * (double)dots[i], 0.0); + if(pv[Geom::X]>= pf[Geom::X]){ +// cairo_line_to(ct, pf[Geom::X], pf[Geom::Y]); + revrectangle(ct, Geom::Rect(pvlast + poff, pf - poff)); break; } else { - dc.lineTo(ps); +// cairo_line_to(ct, pv[Geom::X], pv[Geom::Y]); + revrectangle(ct, Geom::Rect(pvlast + poff, pv - poff)); } - ps += Geom::Point(step * 4.0, 0.0); + pv += Geom::Point(step * 4.0, 0.0); } else { - ps += Geom::Point(step * -(double)dots[i], 0.0); + pv += Geom::Point(step * -(double)dots[i], 0.0); } i = 0; // once in phase, it stays in phase } } else if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_DASHED){ + Geom::Point pv = ps; while(1){ + Geom::Point pvlast = pv; if(dashes[i]>0){ - if(ps[Geom::X]> pf[Geom::X])break; - dc.moveTo(ps); - ps += Geom::Point(step * (double)dashes[i], 0.0); - if(ps[Geom::X]>= pf[Geom::X]){ - dc.lineTo(pf); + if(pv[Geom::X] > pf[Geom::X])break; +// cairo_move_to(ct, ps[Geom::X], ps[Geom::Y]); + pv += Geom::Point(step * (double)dashes[i], 0.0); + if(pv[Geom::X]>= pf[Geom::X]){ +// cairo_line_to(ct, pf[Geom::X], pf[Geom::Y]); + revrectangle(ct, Geom::Rect(pvlast + poff, pf - poff)); break; } else { - dc.lineTo(ps); +// cairo_line_to(ct, ps[Geom::X], ps[Geom::Y]); + revrectangle(ct, Geom::Rect(pvlast + poff, pv - poff)); } - ps += Geom::Point(step * 8.0, 0.0); + pv += Geom::Point(step * 8.0, 0.0); } else { - ps += Geom::Point(step * -(double)dashes[i], 0.0); + pv += Geom::Point(step * -(double)dashes[i], 0.0); } i = 0; // once in phase, it stays in phase } @@ -292,24 +310,35 @@ else if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_WAVY){ double amp = vextent/10.0; double x = ps[Geom::X]; - double y = ps[Geom::Y]; - dc.moveTo(Geom::Point(x, y + amp * wave[i])); + double y = ps[Geom::Y] + poff[Geom::Y]; + cairo_move_to(ct, x, y + amp * wave[i]); while(1){ i = ((i + 1) & 15); x += step; - dc.lineTo(Geom::Point(x, y + amp * wave[i])); + cairo_line_to(ct, x, y + amp * wave[i]); if(x >= pf[Geom::X])break; } + y = ps[Geom::Y] - poff[Geom::Y]; + cairo_line_to(ct, x, y + amp * wave[i]); + while(1){ + i = ((i - 1) & 15); + x -= step; + cairo_line_to(ct, x, y + amp * wave[i]); + if(x <= ps[Geom::X])break; + } + cairo_close_path ( ct); } else { // TEXT_DECORATION_STYLE_SOLID, also default in case it was not set for some reason - dc.moveTo(ps); - dc.lineTo(pf); -// dc.revrectangle(Geom::Rect(ps,pf)); +// cairo_move_to(ct, ps[Geom::X], ps[Geom::Y]); +// cairo_line_to(ct, pf[Geom::X], pf[Geom::Y]); + revrectangle(ct, Geom::Rect(ps + poff, pf - poff)); } } -/* returns scaled line thickness */ -double DrawingText::decorateItem(DrawingContext &dc, Geom::Affine const &aff, double phase_length) +//Fixme The next method should probably be elsewhere, as it is also called by render +/* returns the thickness of the line + under is true for decorations that go under the glyphs, false for those that go over */ +void DrawingText::decorateItem(cairo_t *ct, double phase_length, bool under) { double tsp_width_adj = _nrstyle.tspan_width / _nrstyle.font_size; double tsp_asc_adj = _nrstyle.ascender / _nrstyle.font_size; @@ -318,44 +347,42 @@ double final_underline_thickness = CLAMP(_nrstyle.underline_thickness, tsp_size_adj/30.0, tsp_size_adj/10.0); double final_line_through_thickness = CLAMP(_nrstyle.line_through_thickness, tsp_size_adj/30.0, tsp_size_adj/10.0); - double scale = aff.descrim(); double xphase = phase_length/ _nrstyle.font_size; // used to figure out phase of patterns - Inkscape::DrawingContext::Save save(dc); - dc.transform(aff); // must be leftmost affine in span Geom::Point p1; Geom::Point p2; // All lines must be the same thickness, in combinations, line_through trumps underline double thickness = final_underline_thickness; - if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_UNDERLINE){ - p1 = Geom::Point(0.0, -_nrstyle.underline_position); - p2 = Geom::Point(tsp_width_adj,-_nrstyle.underline_position); - decorateStyle(dc, tsp_size_adj, xphase, p1, p2); - } - if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_OVERLINE){ - p1 = Geom::Point(0.0, tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness); - p2 = Geom::Point(tsp_width_adj,tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness); - decorateStyle(dc, tsp_size_adj, xphase, p1, p2); - } - if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_LINETHROUGH){ - thickness = final_line_through_thickness; - p1 = Geom::Point(0.0, _nrstyle.line_through_position); - p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position); - decorateStyle(dc, tsp_size_adj, xphase, p1, p2); - } - // Obviously this does not blink, but it does indicate which text has been set with that attribute - if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_BLINK){ - thickness = final_line_through_thickness; - p1 = Geom::Point(0.0, _nrstyle.line_through_position - 2*final_line_through_thickness); - p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position - 2*final_line_through_thickness); - decorateStyle(dc, tsp_size_adj, xphase, p1, p2); - p1 = Geom::Point(0.0, _nrstyle.line_through_position + 2*final_line_through_thickness); - p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position + 2*final_line_through_thickness); - decorateStyle(dc, tsp_size_adj, xphase, p1, p2); - } - thickness *= scale; - return(thickness); + cairo_set_tolerance(ct,0.5); + if(under){ + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_UNDERLINE){ + p1 = Geom::Point(0.0, -_nrstyle.underline_position); + p2 = Geom::Point(tsp_width_adj,-_nrstyle.underline_position); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2, thickness); + } + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_OVERLINE){ + p1 = Geom::Point(0.0, tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness); + p2 = Geom::Point(tsp_width_adj,tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2, thickness); + } + } + else { + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_LINETHROUGH){ + p1 = Geom::Point(0.0, _nrstyle.line_through_position); + p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2, thickness); + } + // Obviously this does not blink, but it does indicate which text has been set with that attribute + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_BLINK){ + p1 = Geom::Point(0.0, _nrstyle.line_through_position - 2*final_line_through_thickness); + p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position - 2*final_line_through_thickness); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2, thickness); + p1 = Geom::Point(0.0, _nrstyle.line_through_position + 2*final_line_through_thickness); + p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position + 2*final_line_through_thickness); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2, thickness); + } + } } unsigned DrawingText::_renderItem(DrawingContext &dc, Geom::IntRect const &/*area*/, unsigned /*flags*/, DrawingItem * /*stop_at*/) @@ -386,47 +413,46 @@ double leftmost = DBL_MAX; double phase_length = 0.0; bool firsty = true; - bool decorate = true; + bool decorate = false; double starty = 0.0; Geom::Affine aff; using Geom::X; using Geom::Y; // NOTE: - // prepareFill / prepareStroke need to be called with _ctm in effect. + // prepareFill / prepareStroke need to be called with _ctm in effect + // so that patterns will scale along with the text. // However, we might need to apply a different ctm for glyphs. // Therefore, only apply this ctm temporarily. - bool has_stroke, has_fill; + bool has_stroke, has_fill, has_td_fill, has_td_stroke; { 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.raw(), _item_bbox); + has_stroke = _nrstyle.prepareStroke(dc.raw(), _item_bbox); + has_td_fill = _nrstyle.prepareTextDecorationFill(dc.raw(), _item_bbox); + has_td_stroke = _nrstyle.prepareTextDecorationStroke(dc.raw(), _item_bbox); } if (has_fill || has_stroke) { Geom::Affine rotinv; bool invset = false; - // accumulate the path that represents the glyphs - for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { - DrawingGlyphs *g = dynamic_cast(&*i); - if (!g) throw InvalidItemException(); - if (!invset) { - rotinv = g->_ctm.withoutTranslation().inverse(); - invset = true; - } - Inkscape::DrawingContext::Save save(dc); - if (g->_ctm.isSingular()) continue; - dc.transform(g->_ctm); - if (g->_drawable) { - dc.path(*g->_font->PathVector(g->_glyph)); - } - // get the leftmost affine transform (leftmost defined with respect to the x axis of the first transform). - // That way the decoration will work no matter what mix of L->R, R->L text is in the span. - if (_nrstyle.text_decoration_line != TEXT_DECORATION_LINE_CLEAR) { + /* Find the leftmost affine transform (leftmost defined with respect to the x axis of the + first transform). That way the decoration will work no matter what mix of L->R, R->L + text is in the span. rotinv/invset will be used again later. + */ + if ((_nrstyle.text_decoration_line != TEXT_DECORATION_LINE_CLEAR) && (has_td_fill || has_td_stroke)) { + decorate = true; + for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { + DrawingGlyphs *g = dynamic_cast(&*i); + if (!g) throw InvalidItemException(); + if (!invset) { + rotinv = g->_ctm.withoutTranslation().inverse(); + invset = true; + } Geom::Point pt = g->_ctm.translation() * rotinv; if (pt[X] < leftmost) { leftmost = pt[X]; @@ -446,44 +472,77 @@ } } - // draw the text itself - // we need to apply this object's ctm again - Inkscape::DrawingContext::Save save(dc); - dc.transform(_ctm); - if (has_fill) { - _nrstyle.applyFill(dc); - dc.fillPreserve(); - } - if (has_stroke) { - _nrstyle.applyStroke(dc); - dc.strokePreserve(); - } - dc.newPath(); // clear path - - // draw text decoration - if (_nrstyle.text_decoration_line != TEXT_DECORATION_LINE_CLEAR && decorate) { - guint32 ergba; - if (_nrstyle.text_decoration_useColor) { // color different from the glyph - ergba = SP_RGBA32_F_COMPOSE( - _nrstyle.text_decoration_color.color.v.c[0], - _nrstyle.text_decoration_color.color.v.c[1], - _nrstyle.text_decoration_color.color.v.c[2], - 1.0); - } - else { // whatever the current fill color is - ergba = SP_RGBA32_F_COMPOSE( - _nrstyle.fill.color.v.c[0], - _nrstyle.fill.color.v.c[1], - _nrstyle.fill.color.v.c[2], - 1.0); - } - dc.setSource(ergba); - dc.setTolerance(0.5); - double thickness = decorateItem(dc, aff, phase_length); - dc.setLineWidth(thickness); - dc.strokePreserve(); - dc.newPath(); // clear path - } + // draw text decorations that go UNDER the text + if (decorate) { + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(aff); // must be leftmost affine in span + decorateItem(dc.raw(),phase_length, true); + } + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); // Needed so that fill pattern rotates with text + if (has_td_fill) { + _nrstyle.applyTextDecorationFill(dc.raw()); + dc.fillPreserve(); + } + if (has_td_stroke) { + _nrstyle.applyTextDecorationStroke(dc.raw()); + dc.strokePreserve(); + } + } + } + dc.newPath(); // clear path + + // accumulate the path that represents the glyphs + for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { + DrawingGlyphs *g = dynamic_cast(&*i); + if (!g) throw InvalidItemException(); + + Inkscape::DrawingContext::Save save(dc); + if (g->_ctm.isSingular()) continue; + dc.transform(g->_ctm); + if (g->_drawable) { + dc.path(*g->_font->PathVector(g->_glyph)); + } + } + + // Draw the glyphs. + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); // Needed so that fill pattern rotates with text + if (has_fill) { + _nrstyle.applyFill(dc.raw()); + dc.fillPreserve(); + } + if (has_stroke) { + _nrstyle.applyStroke(dc.raw()); + dc.strokePreserve(); + } + } + dc.newPath(); // clear path + + // draw text decorations that go OVER the text + if (decorate) { + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(aff); // must be leftmost affine in span + decorateItem(dc.raw(),phase_length, false); + } + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); // Needed so that fill pattern rotates with text + if (has_td_fill) { + _nrstyle.applyTextDecorationFill(dc.raw()); + dc.fillPreserve(); + } + if (has_td_stroke) { + _nrstyle.applyTextDecorationStroke(dc.raw()); + dc.strokePreserve(); + } + } + } + dc.newPath(); // clear path } return RENDER_OK; } @@ -520,9 +579,7 @@ DrawingText::_pickItem(Geom::Point const &p, double delta, unsigned flags) { DrawingItem *picked = DrawingGroup::_pickItem(p, delta, flags); - if (picked) { - return this; - } + if (picked) return this; return NULL; } === modified file 'src/display/drawing-text.h' --- src/display/drawing-text.h 2014-02-08 08:44:12 +0000 +++ src/display/drawing-text.h 2014-02-10 20:22:34 +0000 @@ -58,6 +58,9 @@ float width, float ascent, float descent, float phase_length); void setStyle(SPStyle *style); + //Fixme The next two should probably be elsewhere, as they are also called by render + void decorateItem(cairo_t *ct, double phase_length, bool under); + void decorateStyle(cairo_t *ct, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2, double thickness); protected: virtual unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx, @@ -68,8 +71,6 @@ virtual DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags); virtual bool _canClip(); - double decorateItem(DrawingContext &dc, Geom::Affine const &aff, double phase_length); - void decorateStyle(DrawingContext &dc, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2); NRStyle _nrstyle; friend class DrawingGlyphs; === modified file 'src/display/nr-style.cpp' --- src/display/nr-style.cpp 2014-02-08 08:44:12 +0000 +++ src/display/nr-style.cpp 2014-02-10 20:22:34 +0000 @@ -54,8 +54,13 @@ , line_join(CAIRO_LINE_JOIN_MITER) , fill_pattern(NULL) , stroke_pattern(NULL) + , text_decoration_fill_pattern(NULL) + , text_decoration_stroke_pattern(NULL) , text_decoration_line(TEXT_DECORATION_LINE_CLEAR) , text_decoration_style(TEXT_DECORATION_STYLE_CLEAR) + , text_decoration_fill() + , text_decoration_stroke() + , text_decoration_stroke_width(0.0) , phase_length(0.0) , tspan_line_start(false) , tspan_line_end(false) @@ -74,11 +79,15 @@ { if (fill_pattern) cairo_pattern_destroy(fill_pattern); if (stroke_pattern) cairo_pattern_destroy(stroke_pattern); + if (text_decoration_fill_pattern) cairo_pattern_destroy(text_decoration_fill_pattern); + if (text_decoration_stroke_pattern) cairo_pattern_destroy(text_decoration_stroke_pattern); if (dash){ delete [] dash; } fill.clear(); stroke.clear(); + text_decoration_fill.clear(); + text_decoration_stroke.clear(); } void NRStyle::set(SPStyle *style) @@ -175,17 +184,49 @@ if(style->text_decoration_style.dashed ){ text_decoration_style |= TEXT_DECORATION_STYLE_DASHED + TEXT_DECORATION_STYLE_SET; } if(style->text_decoration_style.wavy ){ text_decoration_style |= TEXT_DECORATION_STYLE_WAVY + TEXT_DECORATION_STYLE_SET; } - if( style->text_decoration_color.set || - style->text_decoration_color.inherit || - style->text_decoration_color.currentcolor || - style->text_decoration_color.colorSet){ - text_decoration_color.set(style->text_decoration_color.value.color); - text_decoration_useColor = true; - } - else { - text_decoration_color.clear(); - text_decoration_useColor = false; - } + if( style->text_decoration_fill.set || + style->text_decoration_fill.inherit || + style->text_decoration_fill.currentcolor || + style->text_decoration_fill.colorSet || + style->text_decoration_fill.value.href + ){ + if ( style->text_decoration_fill.isPaintserver() ) { + text_decoration_fill.set(style->getTextDecorationFillPaintServer()); + } else if ( style->text_decoration_fill.isColor() ) { + text_decoration_fill.set(style->text_decoration_fill.value.color); + } else if ( style->text_decoration_fill.isNone() ) { + text_decoration_fill.clear(); + } else { + g_assert_not_reached(); + } + text_decoration_fill.opacity = 1.0; + } + else { + text_decoration_fill.clear(); + } + + if( style->text_decoration_stroke.set || + style->text_decoration_stroke.inherit || + style->text_decoration_stroke.currentcolor || + style->text_decoration_stroke.colorSet || + style->text_decoration_stroke.value.href + ){ + if ( style->text_decoration_stroke.isPaintserver() ) { + text_decoration_stroke.set(style->getTextDecorationStrokePaintServer()); + } else if ( style->text_decoration_stroke.isColor() ) { + text_decoration_stroke.set(style->text_decoration_stroke.value.color); + } else if ( style->text_decoration_stroke.isNone() ) { + text_decoration_stroke.clear(); + } else { + g_assert_not_reached(); + } + text_decoration_stroke.opacity = 1.0; + } + else { + text_decoration_stroke.clear(); + } + text_decoration_stroke.opacity = SP_SCALE24_TO_FLOAT(style->text_decoration_stroke_opacity.value); + text_decoration_stroke_width = style->text_decoration_stroke_width.computed; if(text_decoration_line != TEXT_DECORATION_LINE_CLEAR){ phase_length = style->text_decoration_data.phase_length; @@ -207,21 +248,20 @@ update(); } -bool NRStyle::prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox) +bool NRStyle::prepareFill(cairo_t *ct, Geom::OptRect const &paintbox) { // update fill pattern if (!fill_pattern) { switch (fill.type) { case PAINT_SERVER: { //fill_pattern = sp_paint_server_create_pattern(fill.server, dc.raw(), paintbox, fill.opacity); - fill_pattern = fill.server->pattern_new(dc.raw(), paintbox, fill.opacity); - - } break; + fill_pattern = fill.server->pattern_new(ct, paintbox, fill.opacity); + } break; case PAINT_COLOR: { SPColor const &c = fill.color; fill_pattern = cairo_pattern_create_rgba( c.v.c[0], c.v.c[1], c.v.c[2], fill.opacity); - } break; + } break; default: break; } } @@ -229,19 +269,45 @@ return true; } -void NRStyle::applyFill(Inkscape::DrawingContext &dc) -{ - dc.setSource(fill_pattern); - dc.setFillRule(fill_rule); -} - -bool NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox) +void NRStyle::applyFill(cairo_t *ct) +{ + cairo_set_source(ct, fill_pattern); + cairo_set_fill_rule(ct, fill_rule); +} + +bool NRStyle::prepareTextDecorationFill(cairo_t *ct, Geom::OptRect const &paintbox) +{ + // 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(ct, paintbox, text_decoration_fill.opacity); + } break; + case PAINT_COLOR: { + SPColor const &c = text_decoration_fill.color; + text_decoration_fill_pattern = cairo_pattern_create_rgba( + c.v.c[0], c.v.c[1], c.v.c[2], text_decoration_fill.opacity); + } break; + default: break; + } + } + if (!text_decoration_fill_pattern) return false; + return true; +} + +void NRStyle::applyTextDecorationFill(cairo_t *ct) +{ + cairo_set_source(ct, text_decoration_fill_pattern); + //fill rule will not matter, no intersections +} + + +bool NRStyle::prepareStroke(cairo_t *ct, Geom::OptRect const &paintbox) { if (!stroke_pattern) { switch (stroke.type) { case PAINT_SERVER: { - //stroke_pattern = sp_paint_server_create_pattern(stroke.server, dc.raw(), paintbox, stroke.opacity); - stroke_pattern = stroke.server->pattern_new(dc.raw(), paintbox, stroke.opacity); + stroke_pattern = stroke.server->pattern_new(ct, paintbox, stroke.opacity); } break; case PAINT_COLOR: { @@ -256,23 +322,57 @@ return true; } -void NRStyle::applyStroke(Inkscape::DrawingContext &dc) -{ - dc.setSource(stroke_pattern); - dc.setLineWidth(stroke_width); - dc.setLineCap(line_cap); - dc.setLineJoin(line_join); - dc.setMiterLimit(miter_limit); - cairo_set_dash(dc.raw(), dash, n_dash, dash_offset); // fixme -} +void NRStyle::applyStroke(cairo_t *ct) +{ + cairo_set_source(ct, stroke_pattern); + cairo_set_line_width(ct, stroke_width); + cairo_set_line_cap(ct, line_cap); + cairo_set_line_join(ct, line_join); + cairo_set_miter_limit(ct, miter_limit); + cairo_set_dash(ct, dash, n_dash, dash_offset); // fixme +} + +bool NRStyle::prepareTextDecorationStroke(cairo_t *ct, Geom::OptRect const &paintbox) +{ + if (!text_decoration_stroke_pattern) { + switch (text_decoration_stroke.type) { + case PAINT_SERVER: { + text_decoration_stroke_pattern = text_decoration_stroke.server->pattern_new(ct, paintbox, text_decoration_stroke.opacity); + } break; + case PAINT_COLOR: { + SPColor const &c = text_decoration_stroke.color; + text_decoration_stroke_pattern = cairo_pattern_create_rgba( + c.v.c[0], c.v.c[1], c.v.c[2], text_decoration_stroke.opacity); + } break; + default: break; + } + } + if (!text_decoration_stroke_pattern) return false; + return true; +} + +void NRStyle::applyTextDecorationStroke(cairo_t *ct) +{ + cairo_set_source(ct, text_decoration_stroke_pattern); + cairo_set_line_width(ct, text_decoration_stroke_width); + cairo_set_line_cap(ct, CAIRO_LINE_CAP_BUTT); + cairo_set_line_join(ct, CAIRO_LINE_JOIN_MITER); + cairo_set_miter_limit(ct, 10.0); + cairo_set_dash(ct, 0, 0, 0.0); // fixme no dashes. +} + void NRStyle::update() { // force pattern update if (fill_pattern) cairo_pattern_destroy(fill_pattern); if (stroke_pattern) cairo_pattern_destroy(stroke_pattern); + if (text_decoration_fill_pattern) cairo_pattern_destroy(text_decoration_fill_pattern); + if (text_decoration_stroke_pattern) cairo_pattern_destroy(text_decoration_stroke_pattern); fill_pattern = NULL; stroke_pattern = NULL; + text_decoration_fill_pattern = NULL; + text_decoration_stroke_pattern = NULL; } /* === modified file 'src/display/nr-style.h' --- src/display/nr-style.h 2014-02-08 08:44:12 +0000 +++ src/display/nr-style.h 2014-02-10 20:22:34 +0000 @@ -28,10 +28,14 @@ ~NRStyle(); void set(SPStyle *); - bool prepareFill(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox); - bool prepareStroke(Inkscape::DrawingContext &dc, Geom::OptRect const &paintbox); - void applyFill(Inkscape::DrawingContext &dc); - void applyStroke(Inkscape::DrawingContext &dc); + bool prepareFill(cairo_t *ct, Geom::OptRect const &paintbox); + bool prepareStroke(cairo_t *ct, Geom::OptRect const &paintbox); + bool prepareTextDecorationFill(cairo_t *ct, Geom::OptRect const &paintbox); + bool prepareTextDecorationStroke(cairo_t *ct, Geom::OptRect const &paintbox); + void applyFill(cairo_t *ct); + void applyStroke(cairo_t *ct); + void applyTextDecorationFill(cairo_t *ct); + void applyTextDecorationStroke(cairo_t *ct); void update(); enum PaintType { @@ -67,6 +71,8 @@ cairo_pattern_t *fill_pattern; cairo_pattern_t *stroke_pattern; + cairo_pattern_t *text_decoration_fill_pattern; + cairo_pattern_t *text_decoration_stroke_pattern; #define TEXT_DECORATION_LINE_CLEAR 0x00 #define TEXT_DECORATION_LINE_SET 0x01 @@ -87,8 +93,9 @@ int text_decoration_line; int text_decoration_style; - Paint text_decoration_color; - bool text_decoration_useColor; // if false, use whatever the glyph color was + Paint text_decoration_fill; + Paint text_decoration_stroke; + float text_decoration_stroke_width; // These are the same as in style.h float phase_length; bool tspan_line_start; === modified file 'src/libnrtype/Layout-TNG-Output.cpp' --- src/libnrtype/Layout-TNG-Output.cpp 2013-10-06 20:59:49 +0000 +++ src/libnrtype/Layout-TNG-Output.cpp 2014-02-10 20:22:34 +0000 @@ -241,6 +241,7 @@ sp_print_fill(ctx, temp_pv, ctm, text_source->style, pbox, dbox, bbox); if (!text_source->style->stroke.isNone()) sp_print_stroke(ctx, temp_pv, ctm, text_source->style, pbox, dbox, bbox); + //ToDo - currently no text decorations are enabled for text->path printing } } } @@ -456,7 +457,8 @@ } } while (glyph_index < _glyphs.size() && _path_fitted == NULL - && (font_matrix * glyph_matrix.inverse()).isIdentity() +// Fixme. This breaks at every glyph for some reason. Commented out seems to work. What is it for? +// && (font_matrix * glyph_matrix.inverse()).isIdentity() && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index); // remove vertical flip === modified file 'src/sp-item.cpp' --- src/sp-item.cpp 2013-12-10 12:40:42 +0000 +++ src/sp-item.cpp 2014-02-10 20:22:34 +0000 @@ -1124,6 +1124,8 @@ sp_pattern_transform_multiply(pattern, postmul, set); } } + + // Fixme: text_decoration_fill/stroke adjustments are linked to the fill/stroke adjustments, above? } void SPItem::adjust_gradient( Geom::Affine const &postmul, bool set ) @@ -1154,6 +1156,8 @@ sp_gradient_transform_multiply( gradient, postmul, set ); } } + + // Fixme: text_decoration_fill/stroke gradients are linked to the fill/stroke adjustments, above? } void SPItem::adjust_stroke( gdouble ex ) === modified file 'src/style.cpp' --- src/style.cpp 2013-12-10 12:40:42 +0000 +++ src/style.cpp 2014-02-10 20:22:34 +0000 @@ -447,8 +447,8 @@ } /** - * Emit style modified signal on style's object if server is style's fill - * or stroke paint server. + * Emit style modified signal on style's object if server is style's fill, + * stroke, text_decoration_fill, or text_decoration_stroke paint server. */ static void sp_style_paint_server_ref_modified(SPObject *obj, guint flags, SPStyle *style) @@ -456,8 +456,10 @@ (void)flags; // TODO SPPaintServer *server = static_cast(obj); - if ((style->fill.isPaintserver()) - && style->getFillPaintServer() == server) + if ((style->fill.isPaintserver() && (style->getFillPaintServer() == server)) || + (style->stroke.isPaintserver() && (style->getStrokePaintServer() == server)) || + (style->text_decoration_fill.isPaintserver() && (style->getTextDecorationFillPaintServer() == server)) || + (style->text_decoration_stroke.isPaintserver() && (style->getTextDecorationStrokePaintServer() == server))) { if (style->object) { /** \todo @@ -470,13 +472,6 @@ */ style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } - } else if ((style->stroke.isPaintserver()) - && style->getStrokePaintServer() == server) - { - if (style->object) { - /// \todo fixme: - style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); - } } else if (server) { g_assert_not_reached(); } @@ -517,6 +512,40 @@ } /** + * Gets called when the paintserver is (re)attached to the style + */ +static void +sp_style_text_decoration_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style) +{ + if (old_ref) { + style->text_decoration_fill_ps_modified_connection.disconnect(); + } + if (SP_IS_PAINT_SERVER(ref)) { + style->text_decoration_fill_ps_modified_connection = + ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style)); + } + + sp_style_paint_server_ref_modified(ref, 0, style); +} + +/** + * Gets called when the paintserver is (re)attached to the style + */ +static void +sp_style_text_decoration_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style) +{ + if (old_ref) { + style->text_decoration_stroke_ps_modified_connection.disconnect(); + } + if (SP_IS_PAINT_SERVER(ref)) { + style->text_decoration_stroke_ps_modified_connection = + ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style)); + } + + sp_style_paint_server_ref_modified(ref, 0, style); +} + +/** * Returns a new SPStyle object with settings as per sp_style_clear(). */ SPStyle * @@ -538,6 +567,8 @@ new (&style->filter_modified_connection) sigc::connection(); new (&style->fill_ps_modified_connection) sigc::connection(); new (&style->stroke_ps_modified_connection) sigc::connection(); + new (&style->text_decoration_fill_ps_modified_connection) sigc::connection(); + new (&style->text_decoration_stroke_ps_modified_connection) sigc::connection(); return style; } @@ -605,6 +636,16 @@ delete style->stroke.value.href; style->stroke.value.href = NULL; } + if (style->text_decoration_fill.value.href) { + style->text_decoration_fill_ps_modified_connection.disconnect(); + delete style->text_decoration_fill.value.href; + style->text_decoration_fill.value.href = NULL; + } + if (style->text_decoration_stroke.value.href) { + style->text_decoration_stroke_ps_modified_connection.disconnect(); + delete style->text_decoration_stroke.value.href; + style->text_decoration_stroke.value.href = NULL; + } if (style->filter.href) { style->filter_modified_connection.disconnect(); delete style->filter.href; @@ -614,9 +655,13 @@ style->filter_modified_connection.~connection(); style->fill_ps_modified_connection.~connection(); style->stroke_ps_modified_connection.~connection(); + style->text_decoration_fill_ps_modified_connection.~connection(); + style->text_decoration_stroke_ps_modified_connection.~connection(); style->fill.clear(); style->stroke.clear(); + style->text_decoration_fill.clear(); + style->text_decoration_stroke.clear(); sp_style_filter_clear(style); g_free(style->stroke_dash.dash); @@ -684,10 +729,11 @@ /* Text (css2 chapter 16) */ SPS_READ_PLENGTH_IF_UNSET(&style->text_indent, repr, "text-indent"); SPS_READ_PENUM_IF_UNSET(&style->text_align, repr, "text-align", enum_text_align, true); + //At present SVG2/CSS3 only supports a single color, so equate it to fill. if (!style->text_decoration_line.set) { // assume it uses either text-decoration or text-decoration-line, but not both if ((val = repr->attribute("text-decoration")) || (val = repr->attribute("text-decoration-line"))) { - sp_style_read_itextdecoration(&style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_color, val); + sp_style_read_itextdecoration(&style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_fill, val); } } if (!style->line_height.set) { @@ -1142,8 +1188,9 @@ SPS_READ_IENUM_IF_UNSET(&style->text_align, val, enum_text_align, true); break; case SP_PROP_TEXT_DECORATION: + //At present SVG2/CSS3 only supports a single color, so equate it to fill. if (!style->text_decoration_line.set) { - sp_style_read_itextdecoration(&style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_color, val); + sp_style_read_itextdecoration(&style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_fill, val); } break; case SP_PROP_TEXT_DECORATION_LINE: @@ -1157,8 +1204,9 @@ } break; case SP_PROP_TEXT_DECORATION_COLOR: - if (!style->text_decoration_color.set) { - sp_style_read_itextdecorationColor(&style->text_decoration_color, val); + //At present SVG2/CSS3 only supports a single color, so equate it to fill. + if (!style->text_decoration_fill.set) { + sp_style_read_itextdecorationColor(&style->text_decoration_fill, val); } break; case SP_PROP_LINE_HEIGHT: @@ -1753,25 +1801,6 @@ style->text_align.computed = parent->text_align.computed; } - if (!style->text_decoration_line.set || style->text_decoration_line.inherit) { - style->text_decoration_line.underline = parent->text_decoration_line.underline; - style->text_decoration_line.overline = parent->text_decoration_line.overline; - style->text_decoration_line.line_through = parent->text_decoration_line.line_through; - style->text_decoration_line.blink = parent->text_decoration_line.blink; - } - - if (!style->text_decoration_style.set || style->text_decoration_style.inherit) { - style->text_decoration_style.solid = parent->text_decoration_style.solid; - style->text_decoration_style.isdouble = parent->text_decoration_style.isdouble; - style->text_decoration_style.dotted = parent->text_decoration_style.dotted; - style->text_decoration_style.dashed = parent->text_decoration_style.dashed; - style->text_decoration_style.wavy = parent->text_decoration_style.wavy; - } - - if (!style->text_decoration_color.set || style->text_decoration_color.inherit) { - sp_style_merge_ipaint(style, &style->text_decoration_color, &parent->text_decoration_color); - } - if (!style->line_height.set || style->line_height.inherit) { style->line_height.value = parent->line_height.value; style->line_height.computed = parent->line_height.computed; @@ -1903,6 +1932,74 @@ } } + /* text_decoration_fill/stroke MUST follow fill/stroke being set, as in some cases the color comes from one of those */ + if (!style->text_decoration_line.set || style->text_decoration_line.inherit) { + style->text_decoration_line.underline = parent->text_decoration_line.underline; + style->text_decoration_line.overline = parent->text_decoration_line.overline; + style->text_decoration_line.line_through = parent->text_decoration_line.line_through; + style->text_decoration_line.blink = parent->text_decoration_line.blink; + style->text_decoration_line.noneSet = parent->text_decoration_line.noneSet; + style->text_decoration_line.active = parent->text_decoration_line.active; + } + + if (!style->text_decoration_style.set || style->text_decoration_style.inherit) { + style->text_decoration_style.solid = parent->text_decoration_style.solid; + style->text_decoration_style.isdouble = parent->text_decoration_style.isdouble; + style->text_decoration_style.dotted = parent->text_decoration_style.dotted; + style->text_decoration_style.dashed = parent->text_decoration_style.dashed; + style->text_decoration_style.wavy = parent->text_decoration_style.wavy; + } + + /* worry about decoration color only if one must be drawn */ + if(style->text_decoration_line.set && !style->text_decoration_line.inherit) { + /* Text decoration colors can come from many places... + Fill may be set explicitly with a color (CSS3). + It may be set implicitly (CSS2, when the text decoration is specified). + It may be inherited from a parent. + */ + if(!style->text_decoration_fill.set) { + sp_style_merge_ipaint(style, &style->text_decoration_fill, &style->fill); + style->text_decoration_fill_opacity = style->fill_opacity; + } + else { + // already set explicitly (CSS 3) + } + + // no way to set text_decoration_stroke directly, it always comes from stroke + if (!style->text_decoration_stroke.set) { + sp_style_merge_ipaint(style, &style->text_decoration_stroke, &style->stroke); + style->text_decoration_stroke_opacity = style->stroke_opacity; + style->text_decoration_stroke_width = style->stroke_width; + } + } + else if(style->text_decoration_line.active || style->text_decoration_line.inherit) { + if (!style->text_decoration_fill.set || style->text_decoration_fill.inherit) { + sp_style_merge_ipaint(style, &style->text_decoration_fill, &parent->text_decoration_fill); + } + if (!style->text_decoration_stroke.set || style->text_decoration_stroke.inherit) { + sp_style_merge_ipaint(style, &style->text_decoration_stroke, &parent->text_decoration_stroke); + } + if (!style->text_decoration_fill_opacity.set || style->text_decoration_fill_opacity.inherit) { + style->text_decoration_fill_opacity.value = parent->text_decoration_fill_opacity.value; + } + if (!style->text_decoration_stroke_opacity.set || style->text_decoration_stroke_opacity.inherit) { + style->text_decoration_stroke_opacity.value = parent->text_decoration_stroke_opacity.value; + } + if (!style->text_decoration_stroke_width.set || style->text_decoration_stroke_width.inherit) { + style->text_decoration_stroke_width.computed = parent->text_decoration_stroke_width.computed; + } else { + /* Update computed value for any change in font inherited from parent. */ + double const em = style->font_size.computed; + if (style->text_decoration_stroke_width.unit == SP_CSS_UNIT_EM) { + style->text_decoration_stroke_width.computed = style->text_decoration_stroke_width.value * em; + } + else if (style->text_decoration_stroke_width.unit == SP_CSS_UNIT_EX) { + double const ex = em * 0.5; // fixme: Get x height from libnrtype or pango + style->text_decoration_stroke_width.computed = style->text_decoration_stroke_width.value * ex; + } + } + } + /* Markers - Free the old value and make copy of the new */ for (unsigned i = SP_MARKER_LOC; i < SP_MARKER_LOC_QTY; i++) { if (!style->marker[i].set || style->marker[i].inherit) { @@ -2258,8 +2355,9 @@ * special fill/stroke inheritance mentioned by the spec. */ sp_style_merge_prop_from_dying_parent( style->text_decoration_line, parent->text_decoration_line); - sp_style_merge_prop_from_dying_parent(style->text_decoration_style, parent->text_decoration_style); - sp_style_merge_paint_prop_from_dying_parent(style,style->text_decoration_color, parent->text_decoration_color); + sp_style_merge_prop_from_dying_parent(style->text_decoration_style, parent->text_decoration_style); //CSS3 + sp_style_merge_prop_from_dying_parent(style->text_decoration_fill_opacity, parent->text_decoration_fill_opacity); + sp_style_merge_prop_from_dying_parent(style->text_decoration_stroke_opacity, parent->text_decoration_stroke_opacity); //nyi: font-size-adjust, // | none | inherit //nyi: glyph-orientation-horizontal, @@ -2282,6 +2380,7 @@ SPILength SPStyle::*const lfields[] = { &SPStyle::stroke_width, + &SPStyle::text_decoration_stroke_width, &SPStyle::text_indent }; for (unsigned i = 0; i < G_N_ELEMENTS(lfields); ++i) { @@ -2453,7 +2552,9 @@ SPIPaint SPStyle::*const fields[] = { &SPStyle::color, &SPStyle::fill, - &SPStyle::stroke + &SPStyle::stroke, + &SPStyle::text_decoration_fill, + &SPStyle::text_decoration_stroke }; for (unsigned i = 0; i < G_N_ELEMENTS(fields); ++i) { SPIPaint SPStyle::*const fld = fields[i]; @@ -2710,8 +2811,9 @@ /* Text */ p += sp_style_write_ilength(p, c + BMAX - p, "text-indent", &style->text_indent, NULL, flags); p += sp_style_write_ienum(p, c + BMAX - p, "text-align", enum_text_align, &style->text_align, NULL, flags); + // SVG2/CSS3 only supports "color", use whatever is in fill p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", - &style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_color, + &style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_fill, NULL, NULL, NULL, flags); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "line-height", &style->line_height, NULL, flags); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "letter-spacing", &style->letter_spacing, NULL, flags); @@ -2892,9 +2994,10 @@ /* Text */ p += sp_style_write_ilength(p, c + BMAX - p, "text-indent", &from->text_indent, &to->text_indent, SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ienum(p, c + BMAX - p, "text-align", enum_text_align, &from->text_align, &to->text_align, SP_STYLE_FLAG_IFDIFF); + // SVG2/CSS3 only supports "color", use whatever is in fill p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", - &from->text_decoration_line, &from->text_decoration_style, &from->text_decoration_color, - &to->text_decoration_line, &to->text_decoration_style, &to->text_decoration_color, + &from->text_decoration_line, &from->text_decoration_style, &from->text_decoration_fill, + &to->text_decoration_line, &to->text_decoration_style, &to->text_decoration_fill, SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "line-height", &from->line_height, &to->line_height, SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "letter-spacing", &from->letter_spacing, &to->letter_spacing, SP_STYLE_FLAG_IFDIFF); @@ -3048,11 +3151,25 @@ delete style->fill.value.href; style->fill.value.href = NULL; } + style->stroke_ps_modified_connection.disconnect(); if (style->stroke.value.href) { delete style->stroke.value.href; style->stroke.value.href = NULL; } + + style->text_decoration_fill_ps_modified_connection.disconnect(); + if (style->text_decoration_fill.value.href) { + delete style->text_decoration_fill.value.href; + style->text_decoration_fill.value.href = NULL; + } + + style->text_decoration_stroke_ps_modified_connection.disconnect(); + if (style->text_decoration_stroke.value.href) { + delete style->text_decoration_stroke.value.href; + style->text_decoration_stroke.value.href = NULL; + } + style->filter_modified_connection.disconnect(); if (style->filter.href) { delete style->filter.href; @@ -3087,6 +3204,12 @@ style->stroke.value.href = new SPPaintServerReference(document); style->stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style)); + + style->text_decoration_fill.value.href = new SPPaintServerReference(document); + style->text_decoration_fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_text_decoration_fill_paint_server_ref_changed), style)); + + style->text_decoration_stroke.value.href = new SPPaintServerReference(document); + style->text_decoration_stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_text_decoration_stroke_paint_server_ref_changed), style)); } style->text = text; @@ -3133,6 +3256,8 @@ style->text_decoration_line.overline = FALSE; style->text_decoration_line.line_through = FALSE; style->text_decoration_line.blink = FALSE; + style->text_decoration_line.noneSet = FALSE; + style->text_decoration_line.active = FALSE; style->text_decoration_style.set = FALSE; style->text_decoration_style.inherit = FALSE; @@ -3142,7 +3267,20 @@ style->text_decoration_style.dashed = FALSE; style->text_decoration_style.wavy = FALSE; - style->text_decoration_color.clear(); + style->text_decoration_stroke.clear(); + style->text_decoration_stroke_opacity.set = FALSE; + style->text_decoration_stroke_opacity.inherit = FALSE; + style->text_decoration_stroke_opacity.value = SP_SCALE24_MAX; + + style->text_decoration_fill.clear(); + style->text_decoration_fill_opacity.set = FALSE; + style->text_decoration_fill_opacity.inherit = FALSE; + style->text_decoration_fill_opacity.value = SP_SCALE24_MAX; + + style->text_decoration_stroke_width.set = FALSE; + style->text_decoration_stroke_width.inherit = FALSE; + style->text_decoration_stroke_width.unit = SP_CSS_UNIT_NONE; + style->text_decoration_stroke_width.value = style->stroke_width.computed = 1.0; style->line_height.set = FALSE; style->line_height.inherit = FALSE; @@ -3673,16 +3811,21 @@ */ static void sp_style_read_itextdecorationLine(SPITextDecorationLine *line, gchar const *str){ + line->set = false; + line->inherit = false; + line->underline = false; + line->overline = false; + line->line_through = false; + line->blink = false; + line->noneSet = false; + line->active = false; if (!strcmp(str, "inherit")) { line->set = true; line->inherit = true; + // active will be set from parent } else if (!strcmp(str, "none")) { line->set = true; - line->inherit = false; - line->underline = false; - line->overline = false; - line->line_through = false; - line->blink = false; + line->noneSet = true; } else { bool found_one = false; bool hit_one = false; @@ -3692,8 +3835,9 @@ bool found_overline = false; bool found_line_through = false; bool found_blink = false; + bool found_none = false; - // this method ignores inlineid keys and extra delimiters, so " ,,, blink hello" will set blink and ignore hello + // this method ignores inlined keys and extra delimiters, so " ,,, blink hello" will set blink and ignore hello const gchar *hstr = str; while (1) { if (*str == ' ' || *str == ',' || *str == '\0'){ @@ -3705,7 +3849,7 @@ if ((slen == 8) && strneq(hstr, "overline", slen)){ found_overline = true; break; } if ((slen == 12) && strneq(hstr, "line-through", slen)){ found_line_through = true; break; } if ((slen == 5) && strneq(hstr, "blink", slen)){ found_blink = true; break; } - if ((slen == 4) && strneq(hstr, "none", slen)){ break; } + if ((slen == 4) && strneq(hstr, "none", slen)){ found_none = true; break; } hit_one = false; // whatever this thing is, we do not recognize it break; @@ -3718,15 +3862,15 @@ } if (found_one) { line->set = true; - line->inherit = false; + line->noneSet = found_none; line->underline = found_underline; line->overline = found_overline; line->line_through = found_line_through; line->blink = found_blink; + line->active = found_underline | found_overline | found_line_through | found_blink; } else { - line->set = false; - line->inherit = false; + // all fields stay false } } } @@ -4360,6 +4504,8 @@ || (a->overline != b->overline ) || (a->line_through != b->line_through) || (a->blink != b->blink ) + || (a->noneSet != b->noneSet ) + || (a->active != b->active ) ); } @@ -4417,26 +4563,33 @@ ){ os << key << ":"; if (line->inherit || style->inherit || color->inherit) { - os << " inherit"; + os << "inherit"; + } + else if (line->noneSet) { + os << "none"; } else if (line->underline || line->overline || line->line_through || line->blink) { - if (line->underline) os << " underline"; - if (line->overline) os << " overline"; - if (line->line_through) os << " line-through"; - if (line->blink) os << " blink"; - - if ( style->solid) os << " solid"; - else if (style->isdouble) os << " double"; - else if (style->dotted) os << " dotted"; - else if (style->dashed) os << " dashed"; - else if (style->wavy) os << " wavy"; - // color, if it is set, otherwise omit it - if(color->set){ - char color_buf[8]; - sp_svg_write_color(color_buf, sizeof(color_buf), color->value.color.toRGBA32( 0 )); - os << " "; - os << color_buf; - } + if (line->underline) { os << "underline "; } + if (line->overline) { os << "overline "; } + if (line->line_through) { os << "line-through "; } + if (line->blink) { os << "blink "; } + + // CSS 3 + if(style->isdouble || style->dotted || style->dashed || style->wavy || color->set) { + // color must be written if style is, and vice versa + if ( style->solid) { os << "solid "; } + else if (style->isdouble) { os << "double "; } + else if (style->dotted) { os << "dotted "; } + else if (style->dashed) { os << "dashed "; } + else if (style->wavy) { os << "wavy "; } + + if (color->inherit) { os << "inherit"; } + else { + char color_buf[8]; + sp_svg_write_color(color_buf, sizeof(color_buf), color->value.color.toRGBA32( 0 )); + os << color_buf; + } + } } else { os << "none"; @@ -4875,6 +5028,9 @@ if (style->text_rendering.set) { repr->setAttribute("text-rendering", NULL); } + if (style->text_decoration_line.set) { + repr->setAttribute("text_decoration", NULL); // not necessary to shut off style and color separately. + } } /** === modified file 'src/style.h' --- src/style.h 2013-12-10 12:40:42 +0000 +++ src/style.h 2014-02-10 20:22:34 +0000 @@ -133,6 +133,8 @@ #define SP_STYLE_FILL_SERVER(s) ((const_cast (s))->getFillPaintServer()) #define SP_STYLE_STROKE_SERVER(s) ((const_cast (s))->getStrokePaintServer()) +#define SP_STYLE_TEXT_DECORATION_FILL_SERVER(s) ((const_cast (s))->getTextDecorationFillPaintServer()) +#define SP_STYLE_TEXT_DECORATION_STROKE_SERVER(s) ((const_cast (s))->getTextDecorationStrokePaintServer()) /// Paint type internal to SPStyle. struct SPIPaint { @@ -226,6 +228,8 @@ unsigned overline : 1; unsigned line_through : 1; unsigned blink : 1; // "Conforming user agents are not required to support this value." yay! + unsigned noneSet : 1; + unsigned active : 1; // set whenever a decoration will be drawn, whether set or inherited }; // CSS3 2.2 @@ -311,7 +315,11 @@ /** CSS 3 2.1, 2.2, 2.3 */ /** Not done yet, test_decoration3 = css3 2.4*/ SPITextDecorationLine text_decoration_line; - SPIPaint text_decoration_color; + SPIPaint text_decoration_fill; + SPIPaint text_decoration_stroke; + SPIScale24 text_decoration_fill_opacity; + SPIScale24 text_decoration_stroke_opacity; + SPILength text_decoration_stroke_width; SPITextDecorationStyle text_decoration_style; // used to implement text_decoration, not saved to or read from SVG file @@ -433,6 +441,8 @@ sigc::connection filter_modified_connection; sigc::connection fill_ps_modified_connection; sigc::connection stroke_ps_modified_connection; + sigc::connection text_decoration_fill_ps_modified_connection; + sigc::connection text_decoration_stroke_ps_modified_connection; SPObject *getFilter() { return (filter.href) ? filter.href->getObject() : NULL; } SPObject const *getFilter() const { return (filter.href) ? filter.href->getObject() : NULL; } @@ -445,6 +455,14 @@ SPPaintServer *getStrokePaintServer() { return (stroke.value.href) ? stroke.value.href->getObject() : NULL; } SPPaintServer const *getStrokePaintServer() const { return (stroke.value.href) ? stroke.value.href->getObject() : NULL; } gchar const *getStrokeURI() const { return (stroke.value.href) ? stroke.value.href->getURI()->toString() : NULL; } + + SPPaintServer *getTextDecorationFillServer() { return (text_decoration_fill.value.href) ? text_decoration_fill.value.href->getObject() : NULL; } + SPPaintServer *getTextDecorationFillPaintServer() const { return (text_decoration_fill.value.href) ? text_decoration_fill.value.href->getObject() : NULL; } + gchar const *getTextDecorationFillURI() const { return (text_decoration_fill.value.href) ? text_decoration_fill.value.href->getURI()->toString() : NULL; } + + SPPaintServer *getTextDecorationStrokeServer() { return (text_decoration_stroke.value.href) ? text_decoration_stroke.value.href->getObject() : NULL; } + SPPaintServer *getTextDecorationStrokePaintServer() const { return (text_decoration_stroke.value.href) ? text_decoration_stroke.value.href->getObject() : NULL; } + gchar const *getTextDecorationStrokeURI() const { return (text_decoration_stroke.value.href) ? text_decoration_stroke.value.href->getURI()->toString() : NULL; } }; SPStyle *sp_style_new(SPDocument *document); === modified file 'src/text-editing.cpp' --- src/text-editing.cpp 2013-10-28 09:31:07 +0000 +++ src/text-editing.cpp 2014-02-10 20:22:34 +0000 @@ -776,7 +776,7 @@ bool has_text_decoration = false; gchar const *root_style = (item)->getRepr()->attribute("style"); - if(strstr(root_style,"text-decoration"))has_text_decoration = true; + if(root_style && strstr(root_style,"text-decoration"))has_text_decoration = true; if (start_item == end_item) { // the quick case where we're deleting stuff all from the same string @@ -2035,7 +2035,7 @@ roundtrippability. */ bool has_text_decoration = false; gchar const *root_style = (text)->getRepr()->attribute("style"); - if(strstr(root_style,"text-decoration"))has_text_decoration = true; + if(root_style && strstr(root_style,"text-decoration"))has_text_decoration = true; while (tidy_xml_tree_recursively(common_ancestor, has_text_decoration)){}; // if we only modified subobjects this won't have been automatically sent === modified file 'src/ui/dialog/text-edit.cpp' --- src/ui/dialog/text-edit.cpp 2014-01-02 17:34:58 +0000 +++ src/ui/dialog/text-edit.cpp 2014-02-10 20:22:34 +0000 @@ -68,6 +68,7 @@ TextEdit::TextEdit() : UI::Widget::Panel("", "/dialogs/textandfont", SP_VERB_DIALOG_TEXT), font_label(_("_Font"), true), + decorate_label(_("_Decorate"), true), layout_frame(), text_label(_("_Text"), true), setasdefault_button(_("Set as _default")), @@ -93,11 +94,95 @@ fsel = SP_FONT_SELECTOR(fontsel); fontsel_hbox.pack_start(*Gtk::manage(Glib::wrap(fontsel)), true, true); + Gtk::HBox *hb; +// Gtk::HBox *f = new Gtk::HBox(false, 0); +// f->show(); +// add(*f); +#if WITH_GTKMM_3_0 + table = new Gtk::Grid(); + table->set_border_width(4); + table->set_row_spacing(4); +#else + table = new Gtk::Table(3, 6, false); + table->set_border_width(4); + table->set_row_spacings(4); +#endif + table->show(); +// f->add(*table); + decorate_hbox.add(*table); + gint i = 0; + + /* Action type */ + // TRANSLATORS: The text decoration action type specifies type of text decoration line. + // It can be "none", "set" (add a text-decoration field to the style), "unset". + spw_label(table, _("Text Decoration Action:"), 0, i, NULL); + hb = spw_hbox(table, 3, 1, i); +// none is the absence of all the others. + hbRadioButton(hb, &text_decoration_line_none, _("Decorate none"), INKSCAPE_ICON("paint-none" ), NULL); + hbRadioButton(hb, &text_decoration_line_act, _("Decorate set"), INKSCAPE_ICON("text-decorate-line-underline" ), &text_decoration_line_none); + hbRadioButton(hb, &text_decoration_line_inherit, _("unset Decorate - (make it undefined so it can be inherited)"), INKSCAPE_ICON("paint-unknown" ), &text_decoration_line_none); + i++; + + /* Line type */ + // TRANSLATORS: The text decoration line type specifies the type of text decoration line. + // It can be "none", "underline", "overline", "strikethrough", or "blink". + spw_label(table, _("Text Decoration Line:"), 0, i, NULL); + hb = spw_hbox(table, 3, 1, i); + + /* Text Decoration Line buttons */ + + hbToggleButton(hb, &text_decoration_line_under, _("Decorate with underline"), INKSCAPE_ICON("text-decorate-line-underline" )); + hbToggleButton(hb, &text_decoration_line_over, _("Decorate with overline"), INKSCAPE_ICON("text-decorate-line-overline" )); + hbToggleButton(hb, &text_decoration_line_through, _("Decorate with strikethrough"), INKSCAPE_ICON("text-decorate-line-strikethrough")); + hbToggleButton(hb, &text_decoration_line_blink, _("Decorate with blink"), INKSCAPE_ICON("text-decorate-line-blink" )); + i++; + + /* Style type */ + // TRANSLATORS: The text decoration style type specifies the type of text decoration style. + // It can be "solid", "dotted", "dashed", "double", or "wavy". + spw_label(table, _("Text Decoration Style:"), 0, i, NULL); + hb = spw_hbox(table, 3, 1, i); + + /* Text Decoration Style buttons */ + hbRadioButton(hb, &text_decoration_style_solid, _("Decorate with solid"), INKSCAPE_ICON("text-decorate-style-solid" ), NULL); + hbRadioButton(hb, &text_decoration_style_dotted, _("Decorate with dotted"), INKSCAPE_ICON("text-decorate-style-dotted"), &text_decoration_style_solid); + hbRadioButton(hb, &text_decoration_style_dashed, _("Decorate with dashed"), INKSCAPE_ICON("text-decorate-style-dashed"), &text_decoration_style_solid); + hbRadioButton(hb, &text_decoration_style_double, _("Decorate with double"), INKSCAPE_ICON("text-decorate-style-double"), &text_decoration_style_solid); + hbRadioButton(hb, &text_decoration_style_wavy, _("Decorate with wavy"), INKSCAPE_ICON("text-decorate-style-wavy" ), &text_decoration_style_solid); + i++; + + /* Text Decoration Color */ + // TRANSLATORS: The text decoration color type specifies the color of the text decoration. + // It can be "Text" or "Custom" + spw_label(table, _("Text Decoration Color:"), 0, i, NULL); + hb = spw_hbox(table, 3, 1, i); + + /* Text Decoration Color buttons */ + hbRadioButton(hb, &text_decoration_color_text, _("Decorate Color from Text"), INKSCAPE_ICON("text-decorate-color-text" ), NULL); + hbRadioButton(hb, &text_decoration_color_custom, _("Decorate Custom Color"), INKSCAPE_ICON("text-decorate-color-custom"), &text_decoration_color_text); + i++; + // FIXME, add a color selector here, for now the custom one always sets 0xFF00FF + + /* Comment on CSS 2,3. SVG 1.1 only supports CSS 2, future SVG will probably support CSS 3. + Mixing CSS 3 and CSS 2 text-decoration syntax can produce some odd results. For instance, if + on outer tspan has "overline wavy" set an inner tspan of just "underline" will also be wavy. Other than + throwing a warning there is not much one can do to avoid this sort of issue, because CSS 2 has no + way to say "not wavy" and CSS 2 syntax is still compatible with CSS 3 - there is no way to tell if CSS 2 + or CSS 3 was intended. */ + /* Text Decoration CSS level */ + // TRANSLATORS: The text decoration CSS level supported by this menu for text decoration. + // It can be "2" or "3" + spw_label(table, _("CSS Level:"), 0, i, NULL); + hb = spw_hbox(table, 3, 1, i); + + /* Text Decoration Color buttons */ + hbRadioButton(hb, &text_decoration_css_2, _("CSS 2"), INKSCAPE_ICON("CSS-2" ), NULL); + hbRadioButton(hb, &text_decoration_css_3, _("CSS 3"), INKSCAPE_ICON("CSS-3"), &text_decoration_css_2); + i++; + /* Align buttons */ - styleButton(&align_left, _("Align left"), INKSCAPE_ICON("format-justify-left"), NULL); - styleButton(&align_center, _("Align center"), INKSCAPE_ICON("format-justify-center"), &align_left); - styleButton(&align_right, _("Align right"), INKSCAPE_ICON("format-justify-right"), &align_left); - styleButton(&align_justify, _("Justify (only flowed text)"), INKSCAPE_ICON("format-justify-fill"), &align_left); + hbRadioButton(&layout_hbox, &align_left, _("Align left"), INKSCAPE_ICON("format-justify-left"), NULL); + hbRadioButton(&layout_hbox, &align_center, _("Align center"), INKSCAPE_ICON("format-justify-center"), &align_left); #if WITH_GTKMM_3_0 align_sep.set_orientation(Gtk::ORIENTATION_VERTICAL); @@ -106,8 +191,8 @@ layout_hbox.pack_start(align_sep, false, false, 10); /* Direction buttons */ - styleButton(&text_horizontal, _("Horizontal text"), INKSCAPE_ICON("format-text-direction-horizontal"), NULL); - styleButton(&text_vertical, _("Vertical text"), INKSCAPE_ICON("format-text-direction-vertical"), &text_horizontal); + hbRadioButton(&layout_hbox, &text_horizontal, _("Horizontal text"), INKSCAPE_ICON("format-text-direction-horizontal"), NULL); + hbRadioButton(&layout_hbox, &text_vertical, _("Vertical text"), INKSCAPE_ICON("format-text-direction-vertical"), &text_horizontal); #if WITH_GTKMM_3_0 text_sep.set_orientation(Gtk::ORIENTATION_VERTICAL); @@ -133,6 +218,7 @@ layout_frame.set_padding(4,4,4,4); layout_frame.add(layout_hbox); + // Text start Offset { startOffset = gtk_combo_box_text_new_with_entry (); @@ -147,16 +233,14 @@ gtk_widget_set_tooltip_text(startOffset, _("Text path offset")); #if WITH_GTKMM_3_0 - Gtk::Separator *sep = Gtk::manage(new Gtk::Separator()); - sep->set_orientation(Gtk::ORIENTATION_VERTICAL); -#else - Gtk::VSeparator *sep = Gtk::manage(new Gtk::VSeparator); + path_offset_sep.set_orientation(Gtk::ORIENTATION_VERTICAL); #endif - layout_hbox.pack_start(*sep, false, false, 10); + layout_hbox.pack_start(path_offset_sep, false, false, 10); layout_hbox.pack_start(*Gtk::manage(Glib::wrap(startOffset)), false, false); } + /* Font preview */ preview_label.set_ellipsize(Pango::ELLIPSIZE_END); preview_label.set_justify(Gtk::JUSTIFY_CENTER); @@ -193,9 +277,10 @@ gtk_text_view_set_editable (GTK_TEXT_VIEW (text_view), TRUE); scroller.add(*Gtk::manage(Glib::wrap(text_view))); text_vbox.pack_start(scroller, true, true, 0); - + notebook.append_page(font_vbox, font_label); notebook.append_page(text_vbox, text_label); + notebook.append_page(decorate_hbox, decorate_label); /* Buttons */ setasdefault_button.set_use_underline(true); @@ -233,7 +318,7 @@ deskTrack.disconnect(); } -void TextEdit::styleButton(Gtk::RadioButton *button, gchar const *tooltip, gchar const *icon_name, Gtk::RadioButton *group_button ) +void TextEdit::hbRadioButton(Gtk::HBox *hb, Gtk::RadioButton *button, gchar const *tooltip, gchar const *icon_name, Gtk::RadioButton *group_button ) { GtkWidget *icon = sp_icon_new( Inkscape::ICON_SIZE_SMALL_TOOLBAR, icon_name ); if (!GTK_IS_IMAGE(icon)) { @@ -251,7 +336,23 @@ button->set_mode(false); button->signal_clicked().connect(sigc::mem_fun(*this, &TextEdit::onToggle)); - layout_hbox.pack_start(*button, false, false); + hb->pack_start(*button, false, false); +} + +void TextEdit::hbToggleButton(Gtk::HBox *hb, Gtk::ToggleButton *button, gchar const *tooltip, gchar const *icon_name ) +{ + GtkWidget *icon = sp_icon_new( Inkscape::ICON_SIZE_SMALL_TOOLBAR, icon_name ); + if (!GTK_IS_IMAGE(icon)) { + icon = gtk_image_new_from_icon_name ( icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR ); + } + + button->add(*Gtk::manage(Glib::wrap(icon))); + button->set_tooltip_text(tooltip); + button->set_relief(Gtk::RELIEF_NONE); + button->set_mode(false); + button->signal_clicked().connect(sigc::mem_fun(*this, &TextEdit::onToggle)); + + hb->pack_start(*button, false, false); } void TextEdit::onSelectionModified(guint flags ) @@ -332,11 +433,12 @@ int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY); int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE); int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS); + int result_text_decorations = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_TEXTDECORATION); // If querying returned nothing, read the style from the text tool prefs (default style for new texts) // (Ok to not get a font specification - must just rely on the family and style in that case) if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING - || result_numbers == QUERY_STYLE_NOTHING) { + || result_numbers == QUERY_STYLE_NOTHING || result_text_decorations == QUERY_STYLE_NOTHING) { sp_style_read_from_prefs(query, "/tools/text"); } @@ -369,6 +471,37 @@ align_right.set_active(); } + text_decoration_line_under.set_active(false); + text_decoration_line_over.set_active(false); + text_decoration_line_through.set_active(false); + text_decoration_line_blink.set_active(false); + if (query->text_decoration_line.set) { + text_decoration_line_act.set_active(); + if (query->text_decoration_line.underline ) text_decoration_line_under.set_active(); + if (query->text_decoration_line.overline ) text_decoration_line_over.set_active(); + if (query->text_decoration_line.line_through) text_decoration_line_through.set_active(); + if (query->text_decoration_line.blink ) text_decoration_line_blink.set_active(); + } + else if (query->text_decoration_line.inherit) { + text_decoration_line_inherit.set_active(); + } + else { + text_decoration_line_none.set_active(); + } + text_decoration_style_solid.set_active(false); + text_decoration_style_dotted.set_active(false); + text_decoration_style_dashed.set_active(false); + text_decoration_style_double.set_active(false); + text_decoration_style_wavy.set_active(false); + if (query->text_decoration_style.solid) { text_decoration_style_solid.set_active(); } + else if (query->text_decoration_style.dotted) { text_decoration_style_dotted.set_active(); } + else if (query->text_decoration_style.dashed) { text_decoration_style_dashed.set_active(); } + else if (query->text_decoration_style.isdouble) { text_decoration_style_double.set_active(); } + else if (query->text_decoration_style.wavy) { text_decoration_style_wavy.set_active(); } + else { text_decoration_style_solid.set_active(); } + /* Because of the odd way some of the text_decoration information is inherited there is no + simple way to determine if CSS 3 or CSS 2 is in use all of the time. So do not even try.*/ + if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) { text_horizontal.set_active(); } else { @@ -513,6 +646,74 @@ } else { sp_repr_css_set_property (css, "writing-mode", "tb"); } + + /* Text decoration is a little tricky, as it may be specified using CSS2's + "text-decoration", or it may be specified using CSS3's variant of the same (with + line style and color parameters) or CSS3's separate text-decoration-color, + text-decoration-line, and text-decoration-style. Always employ CSS2 style if + that is sufficient (color and style are defaults) + */ + + gchar td_color_string[16] = ""; + gchar td_style_string[16] = ""; + bool use_css3 = false; + if (text_decoration_css_3.get_active()) { //FixMe Setting CSS2 should gray out CSS3 options in the dialog + use_css3 = true; + } + + if(use_css3){ + if(text_decoration_color_custom.get_active()) { + g_snprintf(td_color_string, sizeof(td_color_string), "%s", "#FF00FF"); //FIXME, should really get a custom color from somewhere + } + else { + g_snprintf(td_color_string, sizeof(td_color_string), "%s", "currentColor"); + } + + // one of these will always be set + if( text_decoration_style_solid.get_active()) { + g_snprintf(td_style_string, sizeof(td_style_string), "%s", "solid"); + } + else if (text_decoration_style_double.get_active()) { + g_snprintf(td_style_string, sizeof(td_style_string), "%s", "double"); + } + else if (text_decoration_style_dashed.get_active()) { + g_snprintf(td_style_string, sizeof(td_style_string), "%s", "dashed"); + } + else if (text_decoration_style_dotted.get_active()) { + g_snprintf(td_style_string, sizeof(td_style_string), "%s", "dotted"); + } + else { // must be wavy + g_snprintf(td_style_string, sizeof(td_style_string), "%s", "wavy"); + } + } + + + if(text_decoration_line_none.get_active()) { // this overrides bits and erases any text-decoration that might be there + sp_repr_css_set_property(css, "text-decoration", "none"); + } + else if(text_decoration_line_inherit.get_active()) { // this overrides bits and sets inherit (write nothing, same as explicit inherit) + sp_repr_css_unset_property(css, "text-decoration"); + } + else { // a decoration will be shown if a line type was specified, text_decoration_line_inherit.get_active() is true + // from 0 to all 4 of these might be set + gchar td_line_string[64] = ""; + g_snprintf(td_line_string, sizeof(td_line_string), "%s%s%s%s", + (text_decoration_line_under.get_active() ? "underline " : ""), + (text_decoration_line_over.get_active() ? "overline " : ""), + (text_decoration_line_through.get_active() ? "line-through " : ""), + (text_decoration_line_blink.get_active() ? "blink " : "") + ); + gchar td_full_string[96] = ""; + if(td_line_string[0]){ // there is a text decoration + if (use_css3) { + g_snprintf(td_full_string, sizeof(td_full_string), "%s %s %s", td_line_string, td_style_string, td_color_string); + } + else { + g_snprintf(td_full_string, sizeof(td_full_string), "%s", td_line_string); + } + } + sp_repr_css_set_property(css, "text-decoration", td_full_string); + } // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext === modified file 'src/ui/dialog/text-edit.h' --- src/ui/dialog/text-edit.h 2013-06-07 03:18:19 +0000 +++ src/ui/dialog/text-edit.h 2014-02-10 20:22:34 +0000 @@ -33,9 +33,15 @@ #include #include #include +#if WITH_GTKMM_3_0 +#include +#else +#include +#endif #include "ui/widget/panel.h" #include "ui/widget/frame.h" #include "ui/dialog/desktop-tracker.h" +#include "widgets/spw-utilities.h" class SPItem; struct SPFontSelector; @@ -146,16 +152,27 @@ SPCSSAttr *fillTextStyle (); /** - * Helper function to style radio buttons with icons, tooltips. + * Helper function to set radio buttons with icons, tooltips. * - * styleButton is used when creating the dialog. + * hbRadioButton is used when creating the Decorate dialog. * * @param button pointer to the button which is created * @param tooltip pointer to its tooltip string * @param iconname string identifying the icon to be shown * @param group_button group to which the radio button belongs to */ - void styleButton(Gtk::RadioButton *button, gchar const *tooltip, gchar const *iconname, Gtk::RadioButton *group_button ); + void hbRadioButton(Gtk::HBox *hb, Gtk::RadioButton *button, gchar const *tooltip, gchar const *icon_name, Gtk::RadioButton *group_button ); + + /** + * Helper function to set toggle buttons with icons, tooltips. + * + * hbToggleButton is used when creating the Decorate dialog. + * + * @param button pointer to the button which is created + * @param tooltip pointer to its tooltip string + * @param iconname string identifying the icon to be shown + */ + void hbToggleButton(Gtk::HBox *hb, Gtk::ToggleButton *button, gchar const *tooltip, gchar const *icon_name ); /** * Can be invoked for setting the desktop. Currently not used. @@ -178,11 +195,21 @@ */ Gtk::Notebook notebook; +#if WITH_GTKMM_3_0 + Gtk::Grid *table; +#else + Gtk::Table *table; +#endif + Gtk::VBox font_vbox; Gtk::Label font_label; Gtk::HBox fontsel_hbox; SPFontSelector *fsel; + Gtk::VBox decorate_vbox; + Gtk::Label decorate_label; + Gtk::HBox decorate_hbox; + Gtk::Alignment layout_frame; Gtk::HBox layout_hbox; Gtk::RadioButton align_left; @@ -196,6 +223,34 @@ Gtk::VSeparator align_sep; #endif +#if WITH_GTKMM_3_0 + Gtk::Separator path_offset_sep; +#else + Gtk::VSeparator path_offset_sep; +#endif + + Gtk::RadioButton text_decoration_line_none; + Gtk::RadioButton text_decoration_line_act; + Gtk::RadioButton text_decoration_line_inherit; + + Gtk::ToggleButton text_decoration_line_under; + Gtk::ToggleButton text_decoration_line_over; + Gtk::ToggleButton text_decoration_line_through; + Gtk::ToggleButton text_decoration_line_blink; + + Gtk::RadioButton text_decoration_style_solid; + Gtk::RadioButton text_decoration_style_dotted; + Gtk::RadioButton text_decoration_style_dashed; + Gtk::RadioButton text_decoration_style_double; + Gtk::RadioButton text_decoration_style_wavy; + + Gtk::RadioButton text_decoration_color_text; + Gtk::RadioButton text_decoration_color_custom; + + Gtk::RadioButton text_decoration_css_2; + Gtk::RadioButton text_decoration_css_3; + + Gtk::RadioButton text_vertical; Gtk::RadioButton text_horizontal;