summaryrefslogtreecommitdiffstats
path: root/src/display/drawing-text.cpp
blob: 4eb4224d52d7eb482962f4e53a5280167a1b5bfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
// 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 "display/cairo-utils.h"
//#include "display/canvas-bpath.h" // for SPWindRule (WTF!)
#include "display/drawing.h"
#include "display/drawing-context.h"
#include "display/drawing-surface.h"
#include "display/drawing-text.h"
#include "helper/geom.h"
#include "libnrtype/font-instance.h"
#include "style.h"
#include "2geom/pathvector.h"

#include "display/cairo-utils.h"
#include "display/canvas-bpath.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]={
        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,
    };
    int dashes[16]={
        8,   7,   6,   5,
        4,   3,   2,   1,
        -8, -7,  -6,  -5,
        -4, -3,  -2,  -1
    };
    int dots[16]={
        4,     3,   2,   1,
        -4,   -3,  -2,  -1,
        4,     3,   2,   1,
        -4,   -3,  -2,  -1
    };
    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 draw mode is set to visible hairlines, don't let anything get smaller
                // than half a pixel.
                if (_drawing.visibleHairlines()) {
                    double half_pixel_size = 0.5, trash = 0.5;
                    dc.device_to_user_distance(half_pixel_size, trash);
                    if (_nrstyle.stroke_width < half_pixel_size) {
                        dc.setLineWidth(half_pixel_size);
                    }
                }

                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 :