diff options
Diffstat (limited to '')
-rw-r--r-- | src/display/drawing-text.cpp | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/src/display/drawing-text.cpp b/src/display/drawing-text.cpp new file mode 100644 index 0000000..fce8644 --- /dev/null +++ b/src/display/drawing-text.cpp @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Group belonging to an SVG drawing element. + *//* + * Authors: + * Krzysztof KosiĆski <tweenk.pl@gmail.com> + * + * Copyright (C) 2011 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "2geom/pathvector.h" + +#include "style.h" + +#include "display/cairo-utils.h" +#include "display/drawing-context.h" +#include "display/drawing-surface.h" +#include "display/drawing-text.h" +#include "display/drawing.h" + +#include "helper/geom.h" + +#include "libnrtype/font-instance.h" + +namespace Inkscape { + + +DrawingGlyphs::DrawingGlyphs(Drawing &drawing) + : DrawingItem(drawing) + , _font(nullptr) + , _glyph(0) +{} + +DrawingGlyphs::~DrawingGlyphs() +{ + if (_font) { + _font->Unref(); + _font = nullptr; + } +} + +void +DrawingGlyphs::setGlyph(font_instance *font, int glyph, Geom::Affine const &trans) +{ + _markForRendering(); + + setTransform(trans); + + if (font) font->Ref(); + if (_font) _font->Unref(); + _font = font; + _glyph = glyph; + + _markForUpdate(STATE_ALL, false); +} + +void +DrawingGlyphs::setStyle(SPStyle * /*style*/, SPStyle * /*context_style*/) +{ + std::cerr << "DrawingGlyphs: Use parent style" << std::endl; +} + + +unsigned DrawingGlyphs::_updateItem(Geom::IntRect const &/*area*/, UpdateContext const &ctx, unsigned /*flags*/, unsigned /*reset*/) +{ + DrawingText *ggroup = dynamic_cast<DrawingText *>(_parent); + if (!ggroup) { + throw InvalidItemException(); + } + + if (!_font || !ggroup->_style) { + return STATE_ALL; + } + + + _pick_bbox = Geom::IntRect(); + _bbox = Geom::IntRect(); + + /* + Make a bounding box for drawing that is a little taller and lower (currently 10% extra) than + the font's drawing box. Extra space is to hold overline or underline, if present. All + characters in a font use the same ascent and descent, but different widths. This lets leading + and trailing spaces have text decorations. If it is not done the bounding box is limited to + the box surrounding the drawn parts of visible glyphs only, and draws outside are ignored. + The box is also a hair wider than the text, since the glyphs do not always start or end at + the left and right edges of the box defined in the font. + */ + + float scale_bigbox = 1.0; + if (_transform) { + scale_bigbox /= _transform->descrim(); + } + + /* Because there can be text decorations the bounding box must correspond in Y to a little above the glyph's ascend + and a little below its descend. This leaves room for overline and underline. The left and right sides + come from the glyph's bounding box. Note that the initial direction of ascender is positive down in Y, and + this flips after the transform is applied. So change the sign on descender. 1.1 provides a little extra space + above and below the max/min y positions of the letters to place the text decorations.*/ + + Geom::Rect b; + if (_drawable) { + Geom::OptRect tiltb = bounds_exact(*_font->PathVector(_glyph)); + if (tiltb) { + Geom::Rect bigbox(Geom::Point(tiltb->left(),-_dsc*scale_bigbox*1.1),Geom::Point(tiltb->right(),_asc*scale_bigbox*1.1)); + b = bigbox * ctx.ctm; + } + } + if (b.hasZeroArea()) { // Fallback, spaces mostly + Geom::Rect bigbox(Geom::Point(0.0, -_dsc*scale_bigbox*1.1),Geom::Point(_width*scale_bigbox, _asc*scale_bigbox*1.1)); + b = bigbox * ctx.ctm; + } + + /* + The pick box matches the characters as best as it can, leaving no extra space above or below + for decorations. The pathvector may include spaces, and spaces have no drawable glyph. + Catch those and do not pass them to bounds_exact_transformed(), which crashes Inkscape if it + sees a nondrawable glyph. Instead mock up a pickbox for them using font characteristics. + There may also be some other similar white space characters in some other unforeseen context + which should be handled by this code as well.. + */ + + Geom::OptRect pb; + if (_drawable) { + Geom::PathVector *glyphv = _font->PathVector(_glyph); + if (glyphv && !glyphv->empty()) { + pb = bounds_exact_transformed(*glyphv, ctx.ctm); + } + glyphv = _font->PathVector(42); + if (glyphv && !glyphv->empty()) { + if (pb) { + pb.unionWith(bounds_exact_transformed(*glyphv, ctx.ctm)); + } else { + pb = bounds_exact_transformed(*glyphv, ctx.ctm); + } + pb.expandTo(Geom::Point((*pb).right() + (_width * ctx.ctm.descrim()), (*pb).bottom())); + } + } + if (!pb) { // Fallback + Geom::Rect pbigbox(Geom::Point(0.0, _asc*scale_bigbox*0.66),Geom::Point(_width*scale_bigbox, 0.0)); + pb = pbigbox * ctx.ctm; + } + +#if 0 + /* FIXME if this is commented out then not even an approximation of pick on decorations */ + /* adjust the pick box up or down to include the decorations. + This is only approximate since at this point we don't know how wide that line is, if it has + an unusual offset, and so forth. The selection point is set at what is roughly the center of + the decoration (vertically) for the wide ones, like wavy and double line. + The text decorations are not actually selectable. + */ + if (_decorations.overline || _decorations.underline) { + double top = _asc*scale_bigbox*0.66; + double bot = 0; + if (_decorations.overline) { top = _asc * scale_bigbox * 1.025; } + if (_decorations.underline) { bot = -_dsc * scale_bigbox * 0.2; } + Geom::Rect padjbox(Geom::Point(0.0, top),Geom::Point(_width*scale_bigbox, bot)); + pb.unionWith(padjbox * ctx.ctm); + } +#endif + + if (ggroup->_nrstyle.stroke.type != NRStyle::PAINT_NONE) { + // this expands the selection box for cases where the stroke is "thick" + float scale = ctx.ctm.descrim(); + if (_transform) { + scale /= _transform->descrim(); // FIXME temporary hack + } + 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 = pb->roundOutwards(); + + float miterMax = width * ggroup->_nrstyle.miter_limit; + if ( miterMax > 0.01 ) { + // grunt mode. we should compute the various miters instead + // (one for each point on the curve) + b.expandBy(miterMax); + } + _bbox = b.roundOutwards(); + } else { + _bbox = b.roundOutwards(); + _pick_bbox = pb->roundOutwards(); + } + return STATE_ALL; +} + +DrawingItem *DrawingGlyphs::_pickItem(Geom::Point const &p, double /*delta*/, unsigned /*flags*/) +{ + DrawingText *ggroup = dynamic_cast<DrawingText *>(_parent); + if (!ggroup) { + throw InvalidItemException(); + } + DrawingItem *result = nullptr; + bool invisible = (ggroup->_nrstyle.fill.type == NRStyle::PAINT_NONE) && + (ggroup->_nrstyle.stroke.type == NRStyle::PAINT_NONE); + + if (_font && _bbox && (_drawing.outline() || _drawing.getOutlineSensitive() || !invisible)) { + // With text we take a simple approach: pick if the point is in a character bbox + Geom::Rect expanded(_pick_bbox); + // FIXME, why expand by delta? When is the next line needed? + // expanded.expandBy(delta); + if (expanded.contains(p)) { + result = this; + } + } + return result; +} + + + +DrawingText::DrawingText(Drawing &drawing) + : DrawingGroup(drawing) +{} + +DrawingText::~DrawingText() += default; + +void +DrawingText::clear() +{ + _markForRendering(); + _children.clear_and_dispose(DeleteDisposer()); +} + +bool +DrawingText::addComponent(font_instance *font, int glyph, Geom::Affine const &trans, + float width, float ascent, float descent, float phase_length) +{ +/* original, did not save a glyph for white space characters, causes problems for text-decoration + if (!font || !font->PathVector(glyph)) { + return(false); + } +*/ + if (!font)return(false); + + _markForRendering(); + DrawingGlyphs *ng = new DrawingGlyphs(_drawing); + ng->setGlyph(font, glyph, trans); + if(font->PathVector(glyph)){ ng->_drawable = true; } + else { ng->_drawable = false; } + ng->_width = width; // used especially when _drawable = false, otherwise, it is the advance of the font + ng->_asc = ascent; // of font, not of this one character + ng->_dsc = descent; // of font, not of this one character + ng->_pl = phase_length; // used for phase of dots, dashes, and wavy + appendChild(ng); + return(true); +} + +void +DrawingText::setStyle(SPStyle *style, SPStyle *context_style) +{ + DrawingGroup::setStyle(style, context_style); // Must be first + _nrstyle.set(_style, _context_style); +} + +void +DrawingText::setChildrenStyle(SPStyle* context_style) +{ + DrawingGroup::setChildrenStyle( context_style ); + _nrstyle.set(_style, _context_style); +} + +unsigned +DrawingText::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) +{ + _nrstyle.update(); + return DrawingGroup::_updateItem(area, ctx, flags, reset); +} + +void DrawingText::decorateStyle(DrawingContext &dc, double vextent, double xphase, Geom::Point const &p1, Geom::Point const &p2, double thickness) +{ + double wave[16]={ + // clang-format off + 0.000000, 0.382499, 0.706825, 0.923651, 1.000000, 0.923651, 0.706825, 0.382499, + 0.000000, -0.382499, -0.706825, -0.923651, -1.000000, -0.923651, -0.706825, -0.382499, + // clang-format on + }; + int dashes[16]={ + // clang-format off + 8, 7, 6, 5, + 4, 3, 2, 1, + -8, -7, -6, -5, + -4, -3, -2, -1 + // clang-format on + }; + int dots[16]={ + // clang-format off + 4, 3, 2, 1, + -4, -3, -2, -1, + 4, 3, 2, 1, + -4, -3, -2, -1 + // clang-format on + }; + double step = vextent/32.0; + unsigned i = 15 & (unsigned) round(xphase/step); // xphase is >= 0.0 + + /* For most spans draw the last little bit right to p2 or even a little beyond. + 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. + */ + /* snap to nearest step in X */ + 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 & NRStyle::TEXT_DECORATION_STYLE_ISDOUBLE){ + ps -= Geom::Point(0, vextent/12.0); + pf -= Geom::Point(0, vextent/12.0); + dc.rectangle( Geom::Rect(ps + poff, pf - poff)); + ps += Geom::Point(0, vextent/6.0); + pf += Geom::Point(0, vextent/6.0); + dc.rectangle( 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 + to figure where in each of their cycles to start. Only accurate to 1 part in 16. + Huge positive offset should keep the phase calculation from ever being negative. + */ + else if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_DOTTED){ + // FIXME: Per spec, this should produce round dots. + Geom::Point pv = ps; + while(true){ + Geom::Point pvlast = pv; + if(dots[i]>0){ + if(pv[Geom::X] > pf[Geom::X]) break; + + pv += Geom::Point(step * (double)dots[i], 0.0); + + if(pv[Geom::X]>= pf[Geom::X]){ + // Last dot + dc.rectangle( Geom::Rect(pvlast + poff, pf - poff)); + break; + } else { + dc.rectangle( Geom::Rect(pvlast + poff, pv - poff)); + } + + pv += Geom::Point(step * 4.0, 0.0); + + } else { + pv += Geom::Point(step * -(double)dots[i], 0.0); + } + i = 0; // once in phase, it stays in phase + } + } + else if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_DASHED){ + Geom::Point pv = ps; + while(true){ + Geom::Point pvlast = pv; + if(dashes[i]>0){ + if(pv[Geom::X]> pf[Geom::X]) break; + + pv += Geom::Point(step * (double)dashes[i], 0.0); + + if(pv[Geom::X]>= pf[Geom::X]){ + // Last dash + dc.rectangle( Geom::Rect(pvlast + poff, pf - poff)); + break; + } else { + dc.rectangle( Geom::Rect(pvlast + poff, pv - poff)); + } + + pv += Geom::Point(step * 8.0, 0.0); + + } else { + pv += Geom::Point(step * -(double)dashes[i], 0.0); + } + i = 0; // once in phase, it stays in phase + } + } + else if(_nrstyle.text_decoration_style & NRStyle::TEXT_DECORATION_STYLE_WAVY){ + double amp = vextent/10.0; + double x = ps[Geom::X]; + double y = ps[Geom::Y] + poff[Geom::Y]; + dc.moveTo(Geom::Point(x, y + amp * wave[i])); + while(true){ + i = ((i + 1) & 15); + x += step; + dc.lineTo(Geom::Point(x, y + amp * wave[i])); + if(x >= pf[Geom::X])break; + } + y = ps[Geom::Y] - poff[Geom::Y]; + dc.lineTo(Geom::Point(x, y + amp * wave[i])); + while(true){ + i = ((i - 1) & 15); + x -= step; + dc.lineTo(Geom::Point(x, y + amp * wave[i])); + if(x <= ps[Geom::X])break; + } + dc.closePath(); + } + else { // TEXT_DECORATION_STYLE_SOLID, also default in case it was not set for some reason + dc.rectangle( Geom::Rect(ps + poff, pf - poff)); + } +} + +/* returns scaled line thickness */ +void DrawingText::decorateItem(DrawingContext &dc, double phase_length, bool under) +{ + if ( _nrstyle.font_size <= 1.0e-32 )return; // might cause a divide by zero or overflow and nothing would be visible anyway + double tsp_width_adj = _nrstyle.tspan_width / _nrstyle.font_size; + double tsp_asc_adj = _nrstyle.ascender / _nrstyle.font_size; + double tsp_size_adj = (_nrstyle.ascender + _nrstyle.descender) / _nrstyle.font_size; + + 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 xphase = phase_length/ _nrstyle.font_size; // used to figure out phase of patterns + + 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 ( thickness <= 1.0e-32 )return; // might cause a divide by zero or overflow and nothing would be visible anyway + dc.setTolerance(0.5); // Is this really necessary... could effect dots. + + if( under ) { + + if(_nrstyle.text_decoration_line & NRStyle::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, thickness); + } + + if(_nrstyle.text_decoration_line & NRStyle::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, thickness); + } + + } else { + // Over + + if(_nrstyle.text_decoration_line & NRStyle::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, thickness); + } + + // Obviously this does not blink, but it does indicate which text has been set with that attribute + if(_nrstyle.text_decoration_line & NRStyle::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, 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, thickness); + } + } +} + +unsigned DrawingText::_renderItem(DrawingContext &dc, Geom::IntRect const &/*area*/, unsigned /*flags*/, DrawingItem * /*stop_at*/) +{ + if (_drawing.outline()) { + guint32 rgba = _drawing.outlinecolor; + Inkscape::DrawingContext::Save save(dc); + dc.setSource(rgba); + dc.setTolerance(0.5); // low quality, but good enough for outline mode + + for (auto & i : _children) { + DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i); + if (!g) throw InvalidItemException(); + + Inkscape::DrawingContext::Save save(dc); + // skip glyphs with singular transforms + if (g->_ctm.isSingular()) continue; + dc.transform(g->_ctm); + if(g->_drawable){ + dc.path(*g->_font->PathVector(g->_glyph)); + dc.fill(); + } + } + return RENDER_OK; + } + + // NOTE: This is very similar to drawing-shape.cpp; the only differences are in path feeding + // and in applying text decorations. + + + // Do we have text decorations? + bool decorate = (_nrstyle.text_decoration_line != NRStyle::TEXT_DECORATION_LINE_CLEAR ); + + // prepareFill / prepareStroke need to be called with _ctm in effect. + // However, we might need to apply a different ctm for glyphs. + // Therefore, only apply this ctm temporarily. + bool has_stroke = false; + bool has_fill = false; + bool has_td_fill = false; + bool has_td_stroke = false; + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); + + 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, _fill_pattern); + has_td_stroke = _nrstyle.prepareTextDecorationStroke(dc, _item_bbox, _stroke_pattern); + } + } + + if (has_fill || has_stroke || has_td_fill || has_td_stroke) { + + // Determine order for fill and stroke. + // Text doesn't have markers, we can do paint-order quick and dirty. + bool fill_first = false; + if( _nrstyle.paint_order_layer[0] == NRStyle::PAINT_ORDER_NORMAL || + _nrstyle.paint_order_layer[0] == NRStyle::PAINT_ORDER_FILL || + _nrstyle.paint_order_layer[2] == NRStyle::PAINT_ORDER_STROKE ) { + fill_first = true; + } // Won't get "stroke fill stroke" but that isn't 'valid' + + + // Determine geometry of text decoration + double phase_length = 0.0; + Geom::Affine aff; + if( decorate ) { + + Geom::Affine rotinv; + bool invset = false; + double leftmost = DBL_MAX; + bool first_y = true; + double start_y = 0.0; + for (auto & i : _children) { + + DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i); + if (!g) throw InvalidItemException(); + + if (!invset) { + rotinv = g->_ctm.withoutTranslation().inverse(); + invset = true; + } + + Geom::Point pt = g->_ctm.translation() * rotinv; + if (pt[Geom::X] < leftmost) { + leftmost = pt[Geom::X]; + aff = g->_ctm; + phase_length = g->_pl; + } + + // Check for text on a path. FIXME: This needs better test (and probably not here). + if (first_y) { + first_y = false; + start_y = pt[Geom::Y]; + } + else if (fabs(pt[Geom::Y] - start_y) > 1.0e-6) { + // If the text has been mapped onto a path, which causes y to vary, drop the + // text decorations. To handle that properly would need a conformal map. + decorate = false; + } + } + } + + // Draw text decorations that go UNDER the text (underline, over-line) + if( decorate ) { + + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(aff); // must be leftmost affine in span + decorateItem(dc, phase_length, true); + } + + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); // Needed so that fill pattern rotates with text + + if (has_td_fill && fill_first) { + _nrstyle.applyTextDecorationFill(dc); + dc.fillPreserve(); + } + + if (has_td_stroke) { + _nrstyle.applyTextDecorationStroke(dc); + dc.strokePreserve(); + } + + if (has_td_fill && !fill_first) { + _nrstyle.applyTextDecorationFill(dc); + dc.fillPreserve(); + } + + } + + dc.newPath(); // Clear text-decoration path + } + + // Accumulate the path that represents the glyphs and/or draw SVG glyphs. + for (auto & i : _children) { + DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i); + if (!g) throw InvalidItemException(); + + Inkscape::DrawingContext::Save save(dc); + if (g->_ctm.isSingular()) continue; + dc.transform(g->_ctm); + if (g->_drawable) { + if (g->_font->FontHasSVG()) { + Inkscape::Pixbuf* pixbuf = g->_font->PixBuf(g->_glyph); + if (pixbuf) { + // Geom::OptRect box = bounds_exact(*g->_font->PathVector(g->_glyph)); + // if (box) { + // Inkscape::DrawingContext::Save save(dc); + // dc.newPath(); + // dc.rectangle(*box); + // dc.setLineWidth(0.01); + // dc.setSource(0x8080ffff); + // dc.stroke(); + // } + { + // pixbuf is in font design units, scale to embox. + double scale = g->_font->GetDesignUnits(); + if (scale <= 0) scale = 1000; + Inkscape::DrawingContext::Save save(dc); + dc.translate(0, 1); + dc.scale(1.0/scale, -1.0/scale); + dc.setSource(pixbuf->getSurfaceRaw(), 0, 0); + dc.paint(1); + } + } else { + dc.path(*g->_font->PathVector(g->_glyph)); + } + } else { + dc.path(*g->_font->PathVector(g->_glyph)); + } + } + } + + // Draw the glyphs (non-SVG glyphs). + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); + if (has_fill && fill_first) { + _nrstyle.applyFill(dc); + dc.fillPreserve(); + } + } + { + Inkscape::DrawingContext::Save save(dc); + if (!_style || !(_style->vector_effect.stroke)) { + dc.transform(_ctm); + } + if (has_stroke) { + _nrstyle.applyStroke(dc); + + // If the stroke is a hairline, set it to exactly 1px on screen. + // If visible hairline mode is on, make sure the line is at least 1px. + if (_drawing.visibleHairlines() || _style->stroke_extensions.hairline) { + double pixel_size_x = 1.0, pixel_size_y = 1.0; + dc.device_to_user_distance(pixel_size_x, pixel_size_y); + if (_style->stroke_extensions.hairline || _nrstyle.stroke_width < std::min(pixel_size_x, pixel_size_y)) { + dc.setHairline(); + } + } + + dc.strokePreserve(); + } + } + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); + if (has_fill && !fill_first) { + _nrstyle.applyFill(dc); + dc.fillPreserve(); + } + } + dc.newPath(); // Clear glyphs path + + // Draw text decorations that go OVER the text (line through, blink) + if (decorate) { + + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(aff); // must be leftmost affine in span + decorateItem(dc, phase_length, false); + } + + { + Inkscape::DrawingContext::Save save(dc); + dc.transform(_ctm); // Needed so that fill pattern rotates with text + + if (has_td_fill && fill_first) { + _nrstyle.applyTextDecorationFill(dc); + dc.fillPreserve(); + } + + if (has_td_stroke) { + _nrstyle.applyTextDecorationStroke(dc); + dc.strokePreserve(); + } + + if (has_td_fill && !fill_first) { + _nrstyle.applyTextDecorationFill(dc); + dc.fillPreserve(); + } + + } + + dc.newPath(); // Clear text-decoration path + } + + } + return RENDER_OK; +} + +void DrawingText::_clipItem(DrawingContext &dc, Geom::IntRect const &/*area*/) +{ + Inkscape::DrawingContext::Save save(dc); + + // handle clip-rule + if (_style) { + if (_style->clip_rule.computed == SP_WIND_RULE_EVENODD) { + dc.setFillRule(CAIRO_FILL_RULE_EVEN_ODD); + } else { + dc.setFillRule(CAIRO_FILL_RULE_WINDING); + } + } + + for (auto & i : _children) { + DrawingGlyphs *g = dynamic_cast<DrawingGlyphs *>(&i); + if (!g) { + throw InvalidItemException(); + } + + Inkscape::DrawingContext::Save save(dc); + dc.transform(g->_ctm); + if(g->_drawable){ + dc.path(*g->_font->PathVector(g->_glyph)); + } + } + dc.fill(); +} + +DrawingItem * +DrawingText::_pickItem(Geom::Point const &p, double delta, unsigned flags) +{ + return DrawingGroup::_pickItem(p, delta, flags) ? this : nullptr; +} + +bool +DrawingText::_canClip() +{ + return true; +} + +} // 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 : |