1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Inkscape::Text::Layout::Calculator - text layout engine meaty bits
4  *
5  * Authors:
6  *   Richard Hughes <cyreve@users.sf.net>
7  *
8  * Copyright (C) 2005 Richard Hughes
9  *
10  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11  */
12 
13 #include <iomanip>
14 
15 #include "Layout-TNG.h"
16 #include "style.h"
17 #include "font-instance.h"
18 #include "svg/svg-length.h"
19 #include "object/sp-object.h"
20 #include "Layout-TNG-Scanline-Maker.h"
21 #include <limits>
22 #include "livarot/Shape.h"
23 
24 namespace Inkscape {
25 namespace Text {
26 
27 //#define DEBUG_LAYOUT_TNG_COMPUTE
28 //#define DEBUG_GLYPH
29 
30 //#define IFTRACE(_code) _code
31 #define IFTRACE(_code)
32 
33 #define TRACE(_args) IFTRACE(g_print _args)
34 
35 /** \brief private to Layout. Does the real work of text flowing.
36 
37 This class does a standard greedy paragraph wrapping algorithm.
38 
39 Very high-level overview:
40 
41 <pre>
42 foreach(paragraph) {
43   call pango_itemize() (_buildPangoItemizationForPara())
44   break into spans, without dealing with wrapping (_buildSpansForPara())
45   foreach(line in flow shape) {
46     foreach(chunk in flow shape) {   (in _buildChunksInScanRun())
47       // this inner loop in _measureUnbrokenSpan()
48       if the line height changed discard the line and start again
49       keep adding characters until we run out of space in the chunk, then back up to the last word boundary
50       (do sensible things if there is no previous word break)
51     }
52     push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
53   }
54   push the paragraph (in calculate())
55 }
56 </pre>
57 
58 ...and all of that needs to work vertically too, and with all the little details that make life annoying
59 */
60 class Layout::Calculator
61 {
62     class SpanPosition;
63     friend class SpanPosition;
64     Layout &_flow;
65     ScanlineMaker *_scanline_maker;
66     unsigned _current_shape_index;     /// index into Layout::_input_wrap_shapes
67     PangoContext *_pango_context;
68     Direction _block_progression;
69 
70     /**
71       * For y= attributes in tspan elements et al, we do the adjustment by moving each
72       * glyph individually by this number. The spec means that this is maintained across
73       * paragraphs.
74       *
75       * To do non-flow text layout, only the first "y" attribute is normally used. If there is only one
76       * "y" attribute in a <tspan> other than the first <tspan>, it is ignored. This allows Inkscape to
77       * insert a new line anywhere. On output, the Inkscape determined "y" is written out so other SVG
78       * viewers know where to place the <tspans>.
79       */
80     double _y_offset;
81 
82     /** to stop pango from hinting its output, the font factory creates all fonts very large.
83     All numbers returned from pango have to be divided by this number \em and divided by
84     PANGO_SCALE. See font_factory::font_factory(). */
85     double _font_factory_size_multiplier;
86 
87     /** Temporary storage associated with each item in Layout::_input_stream. */
88     struct InputItemInfo {
89         bool in_sub_flow;
90         Layout *sub_flow;    // this is only set for the first input item in a sub-flow
91 
InputItemInfoInkscape::Text::Layout::Calculator::InputItemInfo92         InputItemInfo() : in_sub_flow(false), sub_flow(nullptr) {}
93 
94         /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
95          * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
96          * that does delete or Unref.
97          *
98          * I suggest using the garbage collector to manage deletion.
99          */
freeInkscape::Text::Layout::Calculator::InputItemInfo100         void free()
101         {
102             if (sub_flow) {
103                 delete sub_flow;
104                 sub_flow = nullptr;
105             }
106         }
107     };
108 
109     /** Temporary storage associated with each item returned by the call to
110         pango_itemize(). */
111     struct PangoItemInfo {
112         PangoItem *item;
113         font_instance *font;
114 
PangoItemInfoInkscape::Text::Layout::Calculator::PangoItemInfo115         PangoItemInfo() : item(nullptr), font(nullptr) {}
116 
117         /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
118          * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
119          * that does delete or Unref.
120          *
121          * I suggest using the garbage collector to manage deletion.
122          */
freeInkscape::Text::Layout::Calculator::PangoItemInfo123         void free()
124         {
125             if (item) {
126                 pango_item_free(item);
127                 item = nullptr;
128             }
129             if (font) {
130                 font->Unref();
131                 font = nullptr;
132             }
133         }
134     };
135 
136 
137     /** These spans have approximately the same definition as that used for
138       * Layout::Span (constant font, direction, etc), except that they are from
139       * before we have located the line breaks, so bear no relation to chunks.
140       * They are guaranteed to be in at most one PangoItem (spans with no text in
141       * them will not have an associated PangoItem), exactly one input object and
142       * will only have one change of x, y, dx, dy or rotate attribute, which will
143       * be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
144       * BrokenSpan.
145       */
146     struct UnbrokenSpan {
147         PangoGlyphString *glyph_string;
148         int pango_item_index;           /// index into _para.pango_items, or -1 if this is style only
149         unsigned input_index;           /// index into Layout::_input_stream
150         Glib::ustring::const_iterator input_stream_first_character;
151         double font_size;
152         FontMetrics line_height;         /// This is not the CSS line-height attribute!
153         double line_height_multiplier;  /// calculated from the font-height css property
154         double baseline_shift;          /// calculated from the baseline-shift css property
155         SPCSSTextOrientation text_orientation;
156         unsigned text_bytes;
157         unsigned char_index_in_para;    /// the index of the first character in this span in the paragraph, for looking up char_attributes
158         SVGLength x, y, dx, dy, rotate;  // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
159 
UnbrokenSpanInkscape::Text::Layout::Calculator::UnbrokenSpan160         UnbrokenSpan() : glyph_string(nullptr) {}
freeInkscape::Text::Layout::Calculator::UnbrokenSpan161         void free()
162         {
163             if (glyph_string)
164                 pango_glyph_string_free(glyph_string);
165             glyph_string = nullptr;
166         }
167     };
168 
169 
170     /** Used to provide storage for anything that applies to the current
171     paragraph only. Since we're only processing one paragraph at a time,
172     there's only one instantiation of this struct, on the stack of
173     calculate(). */
174     struct ParagraphInfo {
175         Glib::ustring text;
176         unsigned first_input_index;      ///< Index into Layout::_input_stream.
177         Direction direction;
178         Alignment alignment;
179         std::vector<InputItemInfo> input_items;
180         std::vector<PangoItemInfo> pango_items;
181         std::vector<PangoLogAttr> char_attributes;    ///< For every character in the paragraph.
182         std::vector<UnbrokenSpan> unbroken_spans;
183 
free_sequenceInkscape::Text::Layout::Calculator::ParagraphInfo184         template<typename T> static void free_sequence(T &seq)
185         {
186             for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
187                 it->free();
188             }
189             seq.clear();
190         }
191 
freeInkscape::Text::Layout::Calculator::ParagraphInfo192         void free()
193         {
194             text = "";
195             free_sequence(input_items);
196             free_sequence(pango_items);
197             free_sequence(unbroken_spans);
198         }
199     };
200 
201 
202     /**
203       * A useful little iterator for moving char-by-char across spans.
204       */
205     struct UnbrokenSpanPosition {
206         std::vector<UnbrokenSpan>::iterator iter_span;
207         unsigned char_byte;
208         unsigned char_index;
209 
210         void increment();   ///< Step forward by one character.
211 
operator ==Inkscape::Text::Layout::Calculator::UnbrokenSpanPosition212         inline bool operator== (UnbrokenSpanPosition const &other) const
213             {return char_byte == other.char_byte && iter_span == other.iter_span;}
operator !=Inkscape::Text::Layout::Calculator::UnbrokenSpanPosition214         inline bool operator!= (UnbrokenSpanPosition const &other) const
215             {return char_byte != other.char_byte || iter_span != other.iter_span;}
216     };
217 
218     /**
219       * The line breaking algorithm will convert each UnbrokenSpan into one
220       * or more of these. A BrokenSpan will never cross a chunk boundary,
221       * c.f. UnbrokenSpan.
222       */
223     struct BrokenSpan {
224         UnbrokenSpanPosition start;
225         UnbrokenSpanPosition end;    // the end of this will always be the same as the start of the next
226         unsigned start_glyph_index;
227         unsigned end_glyph_index;
228         double width;
229         unsigned whitespace_count;
230         bool ends_with_whitespace;
231         double each_whitespace_width;
232         double letter_spacing; // Save so we can subtract from width at end of line (for center justification)
233         double word_spacing;
234         void setZero();
235     };
236 
237     /** The definition of a chunk used here is the same as that used in Layout:
238     A collection of contiguous broken spans on the same line. (One chunk per line
239     unless shape splits line into several sections... then one chunk per section. */
240     struct ChunkInfo {
241         std::vector<BrokenSpan> broken_spans;
242         double scanrun_width;
243         double text_width;       ///< Total width used by the text (excluding justification).
244         double x;
245         int whitespace_count;
246     };
247 
248     void _buildPangoItemizationForPara(ParagraphInfo *para) const;
249     static double _computeFontLineHeight( SPStyle const *style ); // Returns line_height_multiplier
250     unsigned _buildSpansForPara(ParagraphInfo *para) const;
251     bool _goToNextWrapShape();
252     void _createFirstScanlineMaker();
253 
254     bool _findChunksForLine(ParagraphInfo const &para,
255                             UnbrokenSpanPosition *start_span_pos,
256                             std::vector<ChunkInfo> *chunk_info,
257                             FontMetrics *line_box_height,
258                             FontMetrics const *strut_height);
259 
260     bool _buildChunksInScanRun(ParagraphInfo const &para,
261                                UnbrokenSpanPosition const &start_span_pos,
262                                ScanlineMaker::ScanRun const &scan_run,
263                                std::vector<ChunkInfo> *chunk_info,
264                                FontMetrics *line_height) const;
265 
266     bool _measureUnbrokenSpan(ParagraphInfo const &para,
267                               BrokenSpan *span,
268                               BrokenSpan *last_break_span,
269                               BrokenSpan *last_emergency_break_span,
270                               double maximum_width) const;
271 
272     double _getChunkLeftWithAlignment(ParagraphInfo const &para,
273                                       std::vector<ChunkInfo>::const_iterator it_chunk,
274                                       double *add_to_each_whitespace) const;
275 
276     void _outputLine(ParagraphInfo const &para,
277                      FontMetrics const &line_height,
278                      std::vector<ChunkInfo> const &chunk_info,
279                      bool hidden);
280 
_charAttributes(ParagraphInfo const & para,UnbrokenSpanPosition const & span_pos)281     static inline PangoLogAttr const &_charAttributes(ParagraphInfo const &para,
282                                                       UnbrokenSpanPosition const &span_pos)
283     {
284         return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
285     }
286 
287 #ifdef DEBUG_LAYOUT_TNG_COMPUTE
288     static void dumpPangoItemsOut(ParagraphInfo *para);
289     static void dumpUnbrokenSpans(ParagraphInfo *para);
290 #endif //DEBUG_LAYOUT_TNG_COMPUTE
291 
292 public:
Calculator(Layout * text_flow)293     Calculator(Layout *text_flow)
294         : _flow(*text_flow) {}
295 
296     bool calculate();
297 };
298 
299 
300 /**
301  *  Computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
302  *  and outputs its vital statistics into the other fields of \a span.
303  *  Measuring will stop if maximum_width is reached and in that case the
304  *  function will return false. In other cases where a line break must be
305  *  done immediately the function will also return false. On return
306  *  \a last_break_span will contain the vital statistics for the span only
307  *  up to the last line breaking change. If there are no line breaking
308  *  characters in the span then \a last_break_span will not be altered.
309  *  Similarly, \a last_emergency_break_span will contain the vital
310  *  statistics for the span up to the last inter-character boundary,
311  *  or will be unaltered if there is none.
312  *
313  *  An unbroken span corresponds to at most one PangoItem
314  */
_measureUnbrokenSpan(ParagraphInfo const & para,BrokenSpan * span,BrokenSpan * last_break_span,BrokenSpan * last_emergency_break_span,double maximum_width) const315 bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo const &para,
316                                               BrokenSpan *span,
317                                               BrokenSpan *last_break_span,
318                                               BrokenSpan *last_emergency_break_span,
319                                               double maximum_width) const
320 {
321     TRACE(("      start _measureUnbrokenSpan %g\n", maximum_width));
322     span->setZero();
323 
324     if (span->start.iter_span->dx._set && span->start.char_byte == 0){
325         if(para.direction == RIGHT_TO_LEFT){
326             span->width -= span->start.iter_span->dx.computed;
327         } else {
328             span->width += span->start.iter_span->dx.computed;
329         }
330     }
331 
332     if (span->start.iter_span->pango_item_index == -1) {
333         // if this is a style-only span there's no text in it
334         // so we don't need to do very much at all
335         span->end.iter_span++;
336         return true;
337     }
338 
339     if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
340 
341         InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
342 
343         if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
344             *last_emergency_break_span = *last_break_span = *span;
345             return false;
346         }
347 
348         if (control_code->code == ARBITRARY_GAP) { // Not used!
349             if (span->width + control_code->width > maximum_width)
350                 return false;
351             TRACE(("        fitted control code, width = %f\n", control_code->width));
352             span->width += control_code->width;
353             span->end.increment();
354         }
355         return true;
356     }
357 
358     if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
359         return true;  // never happens
360 
361     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
362 
363     if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
364         // TODO: block-progression altered in the middle
365         // Measure the precomputed flow from para.input_items
366         span->end.iter_span++;  // for now, skip to the next span
367         return true;
368     }
369 
370     // a normal span going with a normal block-progression
371     double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
372     double soft_hyphen_glyph_width = 0.0;
373     bool soft_hyphen_in_word = false;
374     bool is_soft_hyphen = false;
375     IFTRACE(int char_count = 0);
376 
377     // if we're not at the start of the span we need to pre-init glyph_index
378     span->start_glyph_index = 0;
379     while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
380            && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
381         span->start_glyph_index++;
382     span->end_glyph_index = span->start_glyph_index;
383 
384     // go char-by-char summing the width, while keeping track of the previous break point
385     do {
386         PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
387 
388         // guint32 c = *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte);
389         // std::cout << "        char_byte: " << span->end.char_byte
390         //           << "  char_index: " << span->end.char_index
391         //           << "  c: " << c << " " << char(c==10 ? '␤' : c)
392         //           << "  line: "      << std::boolalpha << char_attributes.is_line_break
393         //           << "  mandatory: " << std::boolalpha << char_attributes.is_mandatory_break // Note, break is before character!
394         //           << "  char: "      << std::boolalpha << char_attributes.is_char_break
395         //           << std::endl;
396 
397         if (char_attributes.is_mandatory_break && span->end != span->start) {
398             TRACE(("      is_mandatory_break  ************\n"));
399             *last_emergency_break_span = *last_break_span = *span;
400             TRACE(("        span %ld end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
401             return false;
402         }
403 
404         if (char_attributes.is_line_break) {
405             TRACE(("        is_line_break  ************\n"));
406             // a suitable position to break at, record where we are
407             *last_emergency_break_span = *last_break_span = *span;
408             if (soft_hyphen_in_word) {
409                 // if there was a previous soft hyphen we're not going to need it any more so we can remove it
410                 span->width -= soft_hyphen_glyph_width;
411                 if (!is_soft_hyphen)
412                     soft_hyphen_in_word = false;
413             }
414         } else if (char_attributes.is_char_break) {
415             *last_emergency_break_span = *span;
416         }
417         // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
418 
419         // sum the glyph widths, letter spacing, word spacing, and textLength adjustment to get the character width
420         double char_width = 0.0;
421         while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
422                && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
423 
424             PangoGlyphInfo *info = &(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index]);
425             double glyph_width    = font_size_multiplier * info->geometry.width;
426 
427             // Advance does not include kerning but Pango gives wrong advances for vertical text
428             // with upright orientation (pre 1.44.0).
429             font_instance *font = para.pango_items[span->end.iter_span->pango_item_index].font;
430             double font_size = span->start.iter_span->font_size;
431           //double glyph_h_advance = font_size * font->Advance(info->glyph, false);
432             double glyph_v_advance = font_size * font->Advance(info->glyph, true );
433 
434             if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
435                 // Vertical text
436 
437                 if( text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS ||
438                     (text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED &&
439                      para.pango_items[span->end.iter_span->pango_item_index].item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
440                     // Sideways orientation
441                     char_width += glyph_width;
442                 } else {
443                     // Upright orientation
444                     guint32 c = *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte);
445                     if (g_unichar_type (c) != G_UNICODE_NON_SPACING_MARK) {
446                         // Non-spacing marks should not contribute to width. Fonts may not report the correct advance, especially if the 'vmtx' table is missing.
447                         if (pango_version_check(1,44,0) != nullptr) {
448                             // Pango >= 1.44.0
449                             char_width += glyph_width;
450                         } else {
451                             // Pango < 1.44.0  glyph_width returned is horizontal width, not vertical.
452                             char_width += glyph_v_advance;
453                         }
454                     }
455                 }
456             } else {
457                 // Horizontal text
458                 char_width += glyph_width;
459             }
460             span->end_glyph_index++;
461         }
462 
463         if (char_attributes.is_cursor_position)
464             char_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue();
465         if (char_attributes.is_white)
466             char_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue();
467         char_width += _flow.getTextLengthIncrementDue();
468         span->width += char_width;
469         IFTRACE(char_count++);
470 
471         if (char_attributes.is_white) {
472             span->whitespace_count++;
473             span->each_whitespace_width = char_width;
474         }
475         span->ends_with_whitespace = char_attributes.is_white;
476 
477         is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
478         if (is_soft_hyphen)
479             soft_hyphen_glyph_width = char_width;
480 
481         // Go to next character (resets end.char_byte to zero if at end)
482         span->end.increment();
483 
484         // Width should not include letter_spacing (or word_spacing) after last letter at end of line.
485         // word_spacing is attached to white space that is already removed from line end (?)
486         double test_width = span->width - text_source->style->letter_spacing.computed;
487 
488         // Save letter_spacing and word_spacing for subtraction later if span is last span in line.
489         span->letter_spacing = text_source->style->letter_spacing.computed;
490         span->word_spacing   = text_source->style->word_spacing.computed;
491 
492         if (test_width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
493             TRACE(("        span %ld exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
494             return false;
495         }
496 
497     } while (span->end.char_byte != 0);  // while we haven't wrapped to the next span
498 
499     TRACE(("        fitted span %ld width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
500     TRACE(("      end _measureUnbrokenSpan %g\n", maximum_width));
501     return true;
502 }
503 
504 /* *********************************************************************************************************/
505 //                             Per-line functions (output)
506 
507 /** Uses the paragraph alignment and the chunk information to work out
508  *  where the actual left of the final chunk must be. Also sets
509  *  \a add_to_each_whitespace to be the amount of x to add at each
510  *  whitespace character to make full justification work.
511  */
_getChunkLeftWithAlignment(ParagraphInfo const & para,std::vector<ChunkInfo>::const_iterator it_chunk,double * add_to_each_whitespace) const512 double Layout::Calculator::_getChunkLeftWithAlignment(ParagraphInfo const &para,
513                                                       std::vector<ChunkInfo>::const_iterator it_chunk,
514                                                       double *add_to_each_whitespace) const
515 {
516     *add_to_each_whitespace = 0.0;
517     if (_flow._input_wrap_shapes.empty()) {
518         switch (para.alignment) {
519             case FULL:
520             case LEFT:
521             default:
522                 return it_chunk->x;
523             case RIGHT:
524                 return it_chunk->x - it_chunk->text_width;
525             case CENTER:
526                 return it_chunk->x - it_chunk->text_width/ 2;
527         }
528     }
529 
530     switch (para.alignment) {
531         case FULL:
532             if (!it_chunk->broken_spans.empty()
533                 && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) {   // don't justify the last chunk in the para
534                 if (it_chunk->whitespace_count)
535                     *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
536                 //else
537                     //add_to_each_charspace = something
538             }
539             return it_chunk->x;
540         case LEFT:
541         default:
542             return it_chunk->x;
543         case RIGHT:
544             return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
545         case CENTER:
546             return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
547     }
548 }
549 
550 /**
551  * Once we've got here we have finished making changes to the line
552  * and are ready to output the final result to #_flow.
553  * This method takes its input parameters and does that.
554  */
_outputLine(ParagraphInfo const & para,FontMetrics const & line_height,std::vector<ChunkInfo> const & chunk_info,bool hidden)555 void Layout::Calculator::_outputLine(ParagraphInfo const &para,
556                                      FontMetrics const &line_height,
557                                      std::vector<ChunkInfo> const &chunk_info,
558                                      bool hidden)
559 {
560     TRACE(("  Start _outputLine: ascent %f, descent %f, top of box %f\n", line_height.ascent, line_height.descent, _scanline_maker->yCoordinate() ));
561     if (chunk_info.empty()) {
562         TRACE(("    line too short to fit anything on it, go to next\n"));
563         return;
564     }
565 
566     // we've finished fiddling about with ascents and descents: create the output
567     TRACE(("    found line fit; creating output\n"));
568     Layout::Line new_line;
569     new_line.in_paragraph = _flow._paragraphs.size() - 1;
570     new_line.baseline_y = _scanline_maker->yCoordinate();
571     new_line.hidden = hidden;
572 
573     // The y coordinate is at the beginning edge of the line box (top for horizontal text, left
574     // edge for vertical lr text, right edge for vertical rl text. We align, by default to the
575     // alphabetic baseline for horizontal text and the central baseline for vertical text.
576     if( _block_progression == RIGHT_TO_LEFT ) {
577         // Vertical text, use em box center as baseline
578         new_line.baseline_y -= 0.5 * line_height.emSize();
579     } else if ( _block_progression == LEFT_TO_RIGHT ) {
580         // Vertical text, use em box center as baseline
581         new_line.baseline_y += 0.5 * line_height.emSize();
582     } else {
583         new_line.baseline_y += line_height.getTypoAscent();
584     }
585 
586 
587     TRACE(("    initial new_line.baseline_y: %f\n", new_line.baseline_y ));
588 
589     new_line.in_shape = _current_shape_index;
590     _flow._lines.push_back(new_line);
591 
592     for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
593         double add_to_each_whitespace;
594         // add the chunk to the list
595         Layout::Chunk new_chunk;
596         new_chunk.in_line = _flow._lines.size() - 1;
597 
598         TRACE(("    New chunk: in_line: %d\n", new_chunk.in_line));
599         if (hidden) {
600             new_chunk.left_x = it_chunk->x; // Don't align. We'll place below last shape.
601         } else {
602             new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
603         }
604 
605         // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
606 
607         // Comment updated:  23 July 2010:
608         // We must handle two cases:
609         //
610         //   1. Inkscape SVG where the first line is placed by the read-in "y" value and the
611         //      rest are determined by 'font-size' and 'line-height' (and not by any
612         //      y-kerning). <tspan>s in this case are marked by sodipodi:role="line". This
613         //      allows new lines to be inserted in the middle of a <text> object. On output,
614         //      new "y" values are calculated for each <tspan> that represents a new line. Line
615         //      spacing is already handled by the calling routine.
616         //
617         //   2. Plain SVG where each <text> or <tspan> is placed by its own "x" and "y" values.
618         //      Note that in this case Inkscape treats each <text> object with any included
619         //      <tspan>s as a single line of text. This can be confusing in the code below.
620 
621         if (!it_chunk->broken_spans.empty()                               // Not empty paragraph
622             && it_chunk->broken_spans.front().start.char_byte == 0 ) {    // Beginning of unbroken span
623 
624             // If empty or new line (sodipode:role="line")
625             if( _flow._characters.empty() ||
626                 _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
627 
628                 // This is the Inkscape SVG case.
629                 //
630                 // If <tspan> "y" attribute is set, use it (initial "y" attributes in
631                 // <tspans> other than the first have already been stripped for <tspans>
632                 // marked with role="line", see sp-text.cpp: SPText::_buildLayoutInput).
633                 // NOTE: for vertical text, "y" is the user-space "x" value.
634                 if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
635 
636                     // Use set "y" attribute for baseline
637                     new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
638 
639                     TRACE(("      chunk new_line.baseline_y: %f\n", new_line.baseline_y ));
640 
641                     // Save baseline
642                     _flow._lines.back().baseline_y = new_line.baseline_y;
643 
644                     // Calculate new top of box... given specified baseline.
645                     double top_of_line_box = new_line.baseline_y;
646                     if( _block_progression == RIGHT_TO_LEFT ) {
647                         // Vertical text, use em box center as baseline
648                         top_of_line_box += 0.5 * line_height.emSize();
649                     } else if (_block_progression == LEFT_TO_RIGHT ) {
650                         // Vertical text, use em box center as baseline
651                         top_of_line_box -= 0.5 * line_height.emSize();
652                     } else {
653                         top_of_line_box -= line_height.getTypoAscent();
654                     }
655                     TRACE(("      y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
656                     // Set the initial y coordinate of the for this line (see above).
657                     _scanline_maker->setNewYCoordinate(top_of_line_box);
658                 }
659 
660                 // Reset relative y_offset ("dy" attribute is relative but should be reset at
661                 // the beginning of each line since each line will have a new "y" written out.)
662                 _y_offset = 0.0;
663 
664             } else {
665 
666                 // This is the plain SVG case
667                 //
668                 // "x" and "y" are used to place text, simulating lines as necessary
669                 if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
670                     _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
671                 }
672             }
673         }
674         _flow._chunks.push_back(new_chunk);
675 
676         double current_x;
677         double direction_sign;
678         Direction previous_direction = para.direction;
679         double counter_directional_width_remaining = 0.0;
680         float glyph_rotate = 0.0;
681         if (para.direction == LEFT_TO_RIGHT) {
682             direction_sign = +1.0;
683             current_x = 0.0;
684         } else {
685             direction_sign = -1.0;
686             if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){
687                 current_x = it_chunk->scanrun_width;
688             }
689             else {
690                 current_x = it_chunk->text_width;
691             }
692         }
693 
694         // Loop over broken spans; a broken span is part of no more than one PangoItem.
695         for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
696 
697             // begin adding spans to the list
698             UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
699             double x_in_span_last = 0.0;  // set at the END when a new cluster starts
700             double x_in_span      = 0.0;  // set from the preceding at the START when a new cluster starts.
701 
702             // for (int i = 0; i < unbroken_span.glyph_string->num_glyphs; ++i) {
703             //     std::cout << "Unbroken span: " << unbroken_span.glyph_string->glyphs[i].glyph << std::endl;
704             // }
705 
706             if (it_span->start.char_byte == 0) {
707                 // Start of an unbroken span, we might have dx, dy or rotate still to process
708                 // (x and y are done per chunk)
709                 if (unbroken_span.dx._set) current_x += unbroken_span.dx.computed;
710                 if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
711                 if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
712             }
713 
714             if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
715                 && unbroken_span.pango_item_index == -1) {
716                 // style only, nothing to output
717                 continue;
718             }
719 
720             Layout::Span new_span;
721 
722             new_span.in_chunk = _flow._chunks.size() - 1;
723             new_span.line_height = unbroken_span.line_height;
724             new_span.in_input_stream_item = unbroken_span.input_index;
725             new_span.baseline_shift = 0.0;
726             new_span.block_progression = _block_progression;
727             new_span.text_orientation = unbroken_span.text_orientation;
728             if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
729             {
730                 new_span.font->Ref();
731                 new_span.font_size = unbroken_span.font_size;
732                 new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
733                 new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
734             } else {  // a control code
735                 new_span.font = nullptr;
736                 new_span.font_size = new_span.line_height.emSize();
737                 new_span.direction = para.direction;
738             }
739 
740             if (new_span.direction == para.direction) {
741                 current_x -= counter_directional_width_remaining;
742                 counter_directional_width_remaining = 0.0;
743             } else if (new_span.direction != previous_direction) {
744                 // measure width of spans we need to switch round
745                 counter_directional_width_remaining = 0.0;
746                 std::vector<BrokenSpan>::const_iterator it_following_span;
747                 for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
748                     if (it_following_span->start.iter_span->pango_item_index == -1) break;
749                     Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
750                     if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
751                         if (new_span.direction != (para.pango_items[it_following_span->start.iter_span->pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT)) break;
752                     }
753                     counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
754                 }
755                 current_x += counter_directional_width_remaining;
756                 counter_directional_width_remaining = 0.0;    // we want to go increasingly negative
757             }
758             new_span.x_start = current_x;
759             new_span.y_offset = _y_offset;  // Offset from baseline due to 'y' and 'dy' attributes (used to simulate multiline text).
760 
761             if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
762                 // the span is set up, push the glyphs and chars
763 
764                 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
765                 Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
766                 unsigned char_index_in_unbroken_span = it_span->start.char_index;
767                 double   font_size_multiplier        = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
768                 int      log_cluster_size_glyphs     = 0;   // Number of glyphs in this log_cluster
769                 int      log_cluster_size_chars      = 0;   // Number of characters in this log_cluster
770                 unsigned end_byte                    = 0;
771 
772                 // Get some pointers (constant for an unbroken span).
773                 font_instance *font   = para.pango_items[unbroken_span.pango_item_index].font;
774                 PangoItem *pango_item = para.pango_items[unbroken_span.pango_item_index].item;
775 
776                 // Loop over glyphs in span
777 #if PANGO_VERSION_CHECK(1,44,0)
778                 double x_offset_cluster = 0.0; // Handle wrong glyph positioning post-1.44 Pango.
779 #endif
780                 double x_offset_center  = 0.0; // Handle wrong glyph positioning in pre-1.44 Pango.
781                 double x_offset_advance = 0.0; // Handle wrong advance in pre-1.44 Pango.
782 
783 #ifdef DEBUG_GLYPH
784                 std::cout << "\nGlyphs in span: x_start: " << new_span.x_start << " y_offset: " << new_span.y_offset
785                           << "  PangoItem flags: " << (int)pango_item->analysis.flags << " Gravity: " << (int)pango_item->analysis.gravity << std::endl;
786                 std::cout << "  Unicode  Glyph  h_advance  v_advance  width  cluster    orientation   new_glyph         delta"     << std::endl;
787                 std::cout << "   (hex)     No.                                start                   x       y       x       y"   << std::endl;
788                 std::cout << "  -------------------------------------------------------------------------------------------------" << std::endl;
789 #endif
790 
791                 for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
792 
793                     unsigned char_byte              = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
794                     bool     newcluster             = false;
795                     if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
796                         newcluster = true;
797                         x_in_span = x_in_span_last;
798                     }
799 
800                     if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
801                         && *iter_source_text == UNICODE_SOFT_HYPHEN
802                         && glyph_index + 1 != it_span->end_glyph_index) {
803                         // if we're looking at a soft hyphen and it's not the last glyph in the
804                         // chunk we don't draw the glyph but we still need to add to _characters
805                         Layout::Character new_character;
806                         new_character.the_char = *iter_source_text;
807                         new_character.in_span = _flow._spans.size();     // the span hasn't been added yet, so no -1
808                         new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
809                         new_character.in_glyph = -1;
810                         _flow._characters.push_back(new_character);
811                         iter_source_text++;
812                         char_index_in_unbroken_span++;
813                         while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
814                                && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
815                             glyph_index++;
816                         glyph_index--;
817                         continue;
818                     }
819 
820                     // create the Layout::Glyph
821                     PangoGlyphInfo *unbroken_span_glyph_info = &unbroken_span.glyph_string->glyphs[glyph_index];
822                     double glyph_width = font_size_multiplier * unbroken_span_glyph_info->geometry.width;
823 
824                     Layout::Glyph new_glyph;
825                     new_glyph.glyph = unbroken_span_glyph_info->glyph;
826                     new_glyph.in_character = _flow._characters.size();
827                     new_glyph.rotation = glyph_rotate;
828                     new_glyph.orientation = ORIENTATION_UPRIGHT; // Only effects vertical text
829                     new_glyph.hidden = hidden; // SVG 2 overflow
830 
831                     // Advance does not include kerning but Pango <= 1.43 gives wrong advances for verical upright text.
832                     double glyph_h_advance = new_span.font_size * font->Advance(new_glyph.glyph, false);
833                     double glyph_v_advance = new_span.font_size * font->Advance(new_glyph.glyph, true );
834 
835 #ifdef DEBUG_GLYPH
836 
837                     bool is_cluster_start = unbroken_span_glyph_info->attr.is_cluster_start;
838                     std::cout << "  " << std::hex << std::setw(6) << *iter_source_text << std::dec
839                               << "  " << std::setw(6) << new_glyph.glyph
840                               << std::fixed << std::showpoint << std::setprecision(2)
841                               << "   " << std::setw(6) << glyph_h_advance
842                               << "   " << std::setw(6) << glyph_v_advance
843                               << "   " << std::setw(6) << glyph_width
844                               << "   " << std::setw(6) << std::boolalpha << is_cluster_start; // << std::endl;
845 #endif
846 
847                     // We may have scaled font size to fit textLength; now, if
848                     // @lengthAdjust=spacingAndGlyphs, this scaling must be only horizontal,
849                     // not vertical, so we unscale it back vertically during output
850                     if (_flow.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS)
851                         new_glyph.vertical_scale = 1.0 / _flow.getTextLengthMultiplierDue();
852                     else
853                         new_glyph.vertical_scale = 1.0;
854 
855                     // Position glyph --------------------
856                     new_glyph.x = current_x;
857                     new_glyph.y =_y_offset;
858                     new_glyph.advance = glyph_width;
859 
860                     if (*iter_source_text == '\n') {
861                         // Line feeds should take zero space but they are given 'space' width.
862                         new_glyph.advance = 0.0;
863                     }
864 
865                     // y-coordinate is flipped between vertical and horizontal text...
866                     // delta_y is common offset but applied with opposite sign
867                     double delta_x = unbroken_span_glyph_info->geometry.x_offset * font_size_multiplier;
868                     double delta_y = unbroken_span_glyph_info->geometry.y_offset * font_size_multiplier - unbroken_span.baseline_shift;
869                     SPCSSBaseline dominant_baseline = _flow._blockBaseline();
870 
871                     if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
872                         // Vertical text
873 
874                         // Default dominant baseline is determined by overall block (i.e. <text>) 'text-orientation' value.
875                         if( _flow._blockTextOrientation() != SP_CSS_TEXT_ORIENTATION_SIDEWAYS ) {
876                             if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_CENTRAL;
877                         } else {
878                             if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC;
879                         }
880 
881                         // TODO: Should also check 'glyph_orientation_vertical' if 'text-orientation' is unset...
882                         if( new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_SIDEWAYS ||
883                             (new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_MIXED && pango_item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
884 
885                             // Sideways orientation (Latin characters, CJK punctuation), 90deg rotation done at output stage.
886 
887 #ifdef DEBUG_GLYPH
888                             std::cout << "       Sideways"
889                                       << "  " << std::setw(6) << new_glyph.x
890                                       << "  " << std::setw(6) << new_glyph.y
891                                       << "  " << std::setw(6) << delta_x
892                                       << "  " << std::setw(6) << delta_y
893                                       << std::endl;
894 #endif
895 
896                             new_glyph.orientation = ORIENTATION_SIDEWAYS;
897 
898                             new_glyph.x += delta_x;
899                             new_glyph.y -= delta_y;
900 
901                             // Multiplying by font-size could cause slight differences in
902                             // positioning for different baselines if the font size varies within a
903                             // line of text (e.g. sub-scripts and super-scripts).
904                             new_glyph.y -= new_span.font_size * font->GetBaselines()[ dominant_baseline ];
905 
906                         } else {
907                             // Upright orientation
908 
909 #if PANGO_VERSION_CHECK(1,44,0)
910                             auto hb_font = pango_font_get_hb_font(font->pFont);
911 #endif
912 
913 #ifdef DEBUG_GLYPH
914                             std::cout << "        Upright"
915                                       << "  " << std::setw(6) << new_glyph.x
916                                       << "  " << std::setw(6) << new_glyph.y
917                                       << "  " << std::setw(6) << delta_x
918                                       << "  " << std::setw(6) << delta_y;
919 #if PANGO_VERSION_CHECK(1,44,0)
920                             char glyph_name[32];
921                             hb_font_get_glyph_name(hb_font, new_glyph.glyph, glyph_name, sizeof (glyph_name));
922                             std::cout << "  " << (glyph_name ? glyph_name : "");
923 #endif
924                             std::cout << std::endl;
925 #endif
926 
927                             if (pango_version_check(1,44,0) != nullptr) {
928                                 // Pango < 1.44.0 (pre HarfBuzz)
929                                 new_glyph.x += delta_x;
930                                 new_glyph.y -= delta_y;
931 
932                                 double shift = 0;
933                                 double scale_factor = PANGO_SCALE * _font_factory_size_multiplier;
934                                 if (!FT_HAS_VERTICAL(font->theFace)) {
935 
936                                     // If there are no vertical metrics, glyphs are vertically
937                                     // centered before base anchor to mark anchor distance is
938                                     // calculated by shaper. We must undo this!
939                                     PangoRectangle ink_rect;
940                                     PangoRectangle logical_rect;
941                                     pango_font_get_glyph_extents (font->pFont,
942                                                                   new_glyph.glyph,
943                                                                   &ink_rect,
944                                                                   &logical_rect);
945 
946                                     // Shift required to move centered glyph back to proper position
947                                     // relative to baseline.
948                                     shift =
949                                         font->GetTypoAscent() +
950                                         ink_rect.y / scale_factor + // negative
951                                         (ink_rect.height / scale_factor / 2.0) -
952                                         0.5;
953                                 }
954 
955                                 // Advance is wrong (horizontal width used instead of vertical)...
956                                 if (g_unichar_type(*iter_source_text) != G_UNICODE_NON_SPACING_MARK) {
957 
958                                     x_offset_advance = new_glyph.advance - glyph_v_advance;
959                                     new_glyph.advance = glyph_v_advance;
960 
961                                     x_offset_center = shift;
962                                 } else {
963                                     // Is non-spacing mark!
964                                     if (!FT_HAS_VERTICAL(font->theFace)) {
965 
966                                         // If font lacks vertical metrics, all glyphs have em-box advance
967                                         // but non-spacing marks should have zero advance.
968                                         new_glyph.advance = 0;
969 
970                                         // Correct for base anchor to mark anchor shift.
971                                         new_glyph.x += (x_offset_center - shift) * new_span.font_size;
972                                     }
973 
974                                     // Correct for advance error.
975                                     new_glyph.x += x_offset_advance;
976                                 }
977 
978                                 // Need to shift by horizontal to vertical origin (as we don't load glyph
979                                 // with vertical metrics).
980                                 new_glyph.x += font->GetTypoAscent() * new_span.font_size;
981                                 new_glyph.y -= glyph_h_advance/2.0;
982 
983                             } else if (pango_version_check(1,48,1) != nullptr) {
984                                 // 1.44.0 <= Pango < 1.48.1 (minus sign error, mismatch between Cairo/Harfbuzz glyph
985                                 // placement)
986                                 new_glyph.x += (glyph_width - delta_x);
987                                 new_glyph.y -= delta_y;
988                             } else if (pango_version_check(1,48,4) != nullptr) {
989                                 // 1.48.1 <= Pango < 1.48.4 (minus sign fix, partial fix for Cairo/Harfbuzz mismatch,
990                                 // but bad mark positioning)
991                                 new_glyph.x += delta_x;
992                                 new_glyph.y -= delta_y;
993 
994 #if PANGO_VERSION_CHECK(1,44,0)
995                                 // Need to shift by horizontal to vertical origin. Recall Pango lays out vertical text
996                                 // as horizontal text then rotates by 90 degress so y_origin -> x, x_origin -> -y.
997                                 hb_position_t x_origin = 0.0;
998                                 hb_position_t y_origin = 0.0;
999                                 hb_font_get_glyph_v_origin(hb_font, new_glyph.glyph, &x_origin, &y_origin);
1000                                 new_glyph.x += y_origin * font_size_multiplier;
1001                                 new_glyph.y -= x_origin * font_size_multiplier;
1002 #endif
1003                             } else {
1004                                 // 1.48.4 <= Pango (good mark positioning)
1005                                 new_glyph.x += delta_x;
1006                                 new_glyph.y -= delta_y;
1007                             }
1008 
1009 #if PANGO_VERSION_CHECK(1,44,0)
1010                             // If a font has no vertical metrics, HarfBuzz using OpenType functions
1011                             // (which Pango uses by default from 1.44.0) to position glyphs so that
1012                             // the top of their "ink rectangle" is at the top of the "em-box". This
1013                             // section of code moves each cluster (base glyph with marks) down to
1014                             // match fonts with vertical metrics.
1015                             hb_font_extents_t hb_font_extents_not_used;
1016                             if (!hb_font_get_v_extents(hb_font, &hb_font_extents_not_used)) {
1017                                 // Font does not have vertical metrics!
1018 
1019                                 if (g_unichar_type(*iter_source_text) !=
1020                                     G_UNICODE_NON_SPACING_MARK) { // Probably should include other marks!
1021                                     hb_glyph_extents_t glyph_extents;
1022                                     if (hb_font_get_glyph_extents(hb_font, new_glyph.glyph, &glyph_extents)) {
1023 
1024                                         // double baseline_adjust =
1025                                         //     font_instance->get_baseline(BASELINE_TEXT_BEFORE_EDGE) -
1026                                         //     font_instance->get_baseline(BASELINE_ALPHABETIC);
1027                                         // std::cout << "baseline_adjust: " << baseline_adjust << std::endl;
1028                                         double baseline_adjust = new_span.line_height.ascent / new_span.font_size;
1029                                         int hb_x_scale = 0;
1030                                         int hb_y_scale = 0;
1031                                         hb_font_get_scale(hb_font, &hb_x_scale, &hb_y_scale);
1032                                         x_offset_cluster =
1033                                             ((glyph_extents.y_bearing / (double)hb_y_scale) - baseline_adjust) *
1034                                             new_span.font_size;
1035                                     } else {
1036                                         x_offset_cluster = 0.0; // Failed to find extents.
1037                                     }
1038                                 } else {
1039                                     // Is non-spacing mark!
1040 
1041                                     // Many fonts report a non-zero vertical advance for marks, especially if the 'vmtx'
1042                                     // table is missing.
1043                                     new_glyph.advance = 0;
1044                                 }
1045 
1046                                 new_glyph.x -= x_offset_cluster;
1047                             }
1048 #endif
1049 
1050                         }
1051                     } else {
1052                         // Horizontal text
1053 
1054 #ifdef DEBUG_GLYPH
1055                         std::cout << "     Horizontal"
1056                                   << "  " << std::setw(6) << new_glyph.x
1057                                   << "  " << std::setw(6) << new_glyph.y
1058                                   << "  " << std::setw(6) << delta_x
1059                                   << "  " << std::setw(6) << delta_y
1060                                   << std::endl;
1061 #endif
1062 
1063                         if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC;
1064 
1065                         new_glyph.x += delta_x;
1066                         new_glyph.y += delta_y;
1067 
1068                         new_glyph.y += new_span.font_size * font->GetBaselines()[ dominant_baseline ];
1069                     }
1070 
1071                     // Correct for right to left text
1072                     if (new_span.direction == RIGHT_TO_LEFT) {
1073 
1074                         // The following commented out code is from 2005. Subtracting cluster width gives wrong placement if more
1075                         // than one glyph has a horizontal advance. See GitHub issue 469. I leave the old code here in case switching to
1076                         // subtracting only the glyph width causes unforseen bugs.
1077 
1078                         // // pango wanted to give us glyphs in visual order but we refused, so we need to work
1079                         // // out where the cluster start is ourselves
1080 
1081                         // // Add up widths of remaining glyphs in span.
1082                         // double cluster_width = 0.0;
1083                         // std::cout << "  glyph_index: " << glyph_index << " end_glyph_index: " << it_span->end_glyph_index << std::endl;
1084                         // for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
1085                         //     if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index) {
1086                         //         break;
1087                         //     }
1088                         //     cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
1089                         // }
1090                         // new_glyph.x -= cluster_width;
1091 
1092                         new_glyph.x -= font_size_multiplier * unbroken_span.glyph_string->glyphs[glyph_index].geometry.width;
1093                     }
1094 
1095                     // Store glyph data
1096                     _flow._glyphs.push_back(new_glyph);
1097 
1098                     // Create the Layout::Character(s)
1099                     if (newcluster) {
1100                         newcluster = false;
1101 
1102                         // Figure out how many glyphs are in the log_cluster.
1103                         log_cluster_size_glyphs = 0;
1104                         for (; log_cluster_size_glyphs + glyph_index < it_span->end_glyph_index; log_cluster_size_glyphs++){
1105                            if(unbroken_span.glyph_string->log_clusters[glyph_index                          ] !=
1106                               unbroken_span.glyph_string->log_clusters[glyph_index + log_cluster_size_glyphs]) break;
1107                         }
1108 
1109                         // Find where the text ends for this log_cluster.
1110                         end_byte = it_span->start.iter_span->text_bytes;  // Upper limit
1111                         for(int next_glyph_index = glyph_index+1; next_glyph_index < unbroken_span.glyph_string->num_glyphs; next_glyph_index++){
1112                             if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){
1113                                 end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index];
1114                                 break;
1115                             }
1116                         }
1117 
1118                         // Figure out how many characters are in the log_cluster.
1119                         log_cluster_size_chars  = 0;
1120                         Glib::ustring::const_iterator lclist = iter_source_text;
1121                         unsigned lcb = char_byte;
1122                         while (lcb < end_byte){
1123                             log_cluster_size_chars++;
1124                             lclist++;
1125                             lcb = lclist.base() - unbroken_span.input_stream_first_character.base();
1126                         }
1127                     }
1128 
1129                     double advance_width = new_glyph.advance;
1130                     while (char_byte < end_byte) {
1131 
1132                         /* Hack to survive ligatures:  in log_cluster keep the number of available chars >= number of glyphs remaining.
1133                            When there are no ligatures these two sizes are always the same.
1134                         */
1135                         if (log_cluster_size_chars < log_cluster_size_glyphs) {
1136                            log_cluster_size_glyphs--;
1137                            break;
1138                         }
1139 
1140                         // Store character info
1141                         Layout::Character new_character;
1142                         new_character.the_char = *iter_source_text;
1143                         new_character.in_span = _flow._spans.size();
1144                         new_character.x = x_in_span;
1145                         new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
1146                         new_character.in_glyph = (hidden ? -1 : _flow._glyphs.size() - 1);
1147                         _flow._characters.push_back(new_character);
1148 
1149                         // Letter/word spacing and justification
1150                         if (new_character.char_attributes.is_white)
1151                             advance_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue() + add_to_each_whitespace;    // justification
1152                         if (new_character.char_attributes.is_cursor_position)
1153                             advance_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue();
1154                         advance_width += _flow.getTextLengthIncrementDue();
1155 
1156                         // Update counters
1157                         iter_source_text++;
1158                         char_index_in_unbroken_span++;
1159                         char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
1160                         log_cluster_size_chars--;
1161                     }
1162 
1163                     // Update x position variables
1164                     advance_width *= direction_sign;
1165                     if (new_span.direction != para.direction) {
1166                         counter_directional_width_remaining -= advance_width;
1167                         current_x -= advance_width;
1168                         x_in_span_last -= advance_width;
1169                     } else {
1170                         current_x += advance_width;
1171                         x_in_span_last += advance_width;
1172                     }
1173                 } // Loop over glyphs in span
1174 
1175             } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
1176                 current_x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
1177             }
1178 
1179             new_span.x_end = new_span.x_start + x_in_span_last;
1180             _flow._spans.push_back(new_span);
1181             previous_direction = new_span.direction;
1182         }
1183         // end adding spans to the list, on to the next chunk...
1184     }
1185     TRACE(("  End _outputLine\n"));
1186 }
1187 
1188 /**
1189  * Initialises the ScanlineMaker for the first shape in the flow,
1190  * or the infinite version if we're not doing wrapping.
1191  */
_createFirstScanlineMaker()1192 void Layout::Calculator::_createFirstScanlineMaker()
1193 {
1194     _current_shape_index = 0;
1195     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1196     if (_flow._input_wrap_shapes.empty()) {
1197         // create the special no-wrapping infinite scanline maker
1198         double initial_x = 0, initial_y = 0;
1199         if (!text_source->x.empty()) {
1200             initial_x = text_source->x.front().computed;
1201         }
1202         if (!text_source->y.empty()) {
1203             initial_y = text_source->y.front().computed;
1204         }
1205         _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
1206         TRACE(("  wrapping disabled\n"));
1207     }
1208     else {
1209         _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1210         TRACE(("  begin wrap shape 0\n"));
1211 
1212         // 'inline-size' uses an infinitely high (wide) shape. We must set initial y. (We only need to do it here as there is only one shape.)
1213         if (_flow.wrap_mode == WRAP_INLINE_SIZE) {
1214             _block_progression = _flow._blockProgression();
1215             if( _block_progression == RIGHT_TO_LEFT ||
1216                 _block_progression == LEFT_TO_RIGHT ) {
1217                 // Vertical text, CJK
1218                 if (!text_source->x.empty()) {
1219                     double initial_x = text_source->x.front().computed;
1220                     _scanline_maker->setNewYCoordinate(initial_x);
1221                 } else {
1222                     std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no x value with 'inline-size'!" << std::endl;
1223                     _scanline_maker->setNewYCoordinate(0);
1224                 }
1225             } else {
1226                 // Horizontal text
1227                 if (!text_source->y.empty()) {
1228                     double initial_y = text_source->y.front().computed;
1229                     _scanline_maker->setNewYCoordinate(initial_y);
1230                 } else {
1231                     std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no y value with 'inline-size'!" << std::endl;
1232                     _scanline_maker->setNewYCoordinate(0);
1233                 }
1234             }
1235         }
1236     }
1237 }
1238 
increment()1239 void Layout::Calculator::UnbrokenSpanPosition::increment()
1240 {
1241     gchar const *text_base = &*iter_span->input_stream_first_character.base();
1242     char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
1243     char_index++;
1244     if (char_byte == iter_span->text_bytes) {
1245         iter_span++;
1246         char_index = char_byte = 0;
1247     }
1248 }
1249 
setZero()1250 void Layout::Calculator::BrokenSpan::setZero()
1251 {
1252     end = start;
1253     width = 0.0;
1254     whitespace_count = 0;
1255     end_glyph_index = start_glyph_index = 0;
1256     ends_with_whitespace = false;
1257     each_whitespace_width = 0.0;
1258     letter_spacing = 0.0;
1259     word_spacing = 0.0;
1260 }
1261 
1262 ///**
1263 // * For sections of text with a block-progression different to the rest
1264 // * of the flow, the best thing to do is to detect them in advance and
1265 // * create child TextFlow objects with just the rotated text. In the
1266 // * parent we then effectively use ARBITRARY_GAP fields during the
1267 // * flowing (because we don't allow wrapping when the block-progression
1268 // * changes) and copy the actual text in during the output phase.
1269 // *
1270 // * NB: this code not enabled yet.
1271 // */
1272 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
1273 //{
1274 //    Direction prev_block_progression = _block_progression;
1275 //    int run_start_input_index = para->first_input_index;
1276 //
1277 //    para->free_sequence(para->input_items);
1278 //    for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
1279 //        InputItemInfo input_item;
1280 //
1281 //        input_item.in_sub_flow = false;
1282 //        input_item.sub_flow = NULL;
1283 //        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1284 //            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1285 //            if (   control_code->code == SHAPE_BREAK
1286 //                   || control_code->code == PARAGRAPH_BREAK)
1287 //                break;                                    // stop at the end of the paragraph
1288 //            // all other control codes we'll pick up later
1289 //
1290 //        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
1291 //            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
1292 //            Direction this_block_progression = text_source->styleGetBlockProgression();
1293 //            if (this_block_progression != prev_block_progression) {
1294 //                if (prev_block_progression != _block_progression) {
1295 //                    // need to back up so that control codes belong outside the block-progression change
1296 //                    int run_end_input_index = input_index - 1;
1297 //                    while (run_end_input_index > run_start_input_index
1298 //                           && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
1299 //                        run_end_input_index--;
1300 //                    // now create the sub-flow
1301 //                    input_item.sub_flow = new Layout;
1302 //                    for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
1303 //                        input_item.in_sub_flow = true;
1304 //                        if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
1305 //                            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
1306 //                            input_item.sub_flow->appendControlCode(control_code->code, control_code->source, control_code->width, control_code->ascent, control_code->descent);
1307 //                        } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
1308 //                            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
1309 //                            input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source, NULL, 0, text_source->text_begin, text_source->text_end);
1310 //                            Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
1311 //                            sub_flow_text_source->x = text_source->x;    // this is easier than going via optionalattrs for the appendText() call
1312 //                            sub_flow_text_source->y = text_source->y;    // should these actually be allowed anyway? You'll almost never get the results you expect
1313 //                            sub_flow_text_source->dx = text_source->dx;  // (not that it's very clear what you should expect, anyway)
1314 //                            sub_flow_text_source->dy = text_source->dy;
1315 //                            sub_flow_text_source->rotate = text_source->rotate;
1316 //                        }
1317 //                    }
1318 //                    input_item.sub_flow->calculateFlow();
1319 //                }
1320 //                run_start_input_index = input_index;
1321 //            }
1322 //            prev_block_progression = this_block_progression;
1323 //        }
1324 //        para->input_items.push_back(input_item);
1325 //    }
1326 //}
1327 
1328 /**
1329  * Take all the text from \a _para.first_input_index to the end of the
1330  * paragraph and stitch it together so that pango_itemize() can be called on
1331  * the whole thing.
1332  *
1333  * Input: para.first_input_index.
1334  * Output: para.direction, para.pango_items, para.char_attributes.
1335  * Returns: the number of spans created by pango_itemize
1336  */
_buildPangoItemizationForPara(ParagraphInfo * para) const1337 void  Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
1338 {
1339     TRACE(("pango version string: %s\n", pango_version_string() ));
1340 #if PANGO_VERSION_CHECK(1,37,1)
1341     TRACE((" ... compiled for font features\n"));
1342 #endif
1343 
1344     TRACE(("itemizing para, first input %d\n", para->first_input_index));
1345 
1346     PangoAttrList *attributes_list = pango_attr_list_new();
1347     for (unsigned input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1348         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1349             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1350             if (   control_code->code == SHAPE_BREAK
1351                    || control_code->code == PARAGRAPH_BREAK)
1352                 break;                                    // stop at the end of the paragraph
1353             // all other control codes we'll pick up later
1354 
1355         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
1356             Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
1357 
1358             // create the font_instance
1359             font_instance *font = text_source->styleGetFontInstance();
1360             if (font == nullptr)
1361                 continue;  // bad news: we'll have to ignore all this text because we know of no font to render it
1362 
1363             PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
1364             attribute_font_description->start_index = para->text.bytes();
1365 
1366 #if PANGO_VERSION_CHECK(1,37,1)
1367             PangoAttribute *attribute_font_features =
1368                 pango_attr_font_features_new( text_source->style->getFontFeatureString().c_str());
1369             attribute_font_features->start_index = para->text.bytes();
1370 #endif
1371             para->text.append(&*text_source->text_begin.base(), text_source->text_length);     // build the combined text
1372 
1373             attribute_font_description->end_index = para->text.bytes();
1374             pango_attr_list_insert(attributes_list, attribute_font_description);
1375 
1376 #if PANGO_VERSION_CHECK(1,37,1)
1377             attribute_font_features->end_index = para->text.bytes();
1378             pango_attr_list_insert(attributes_list, attribute_font_features);
1379 #endif
1380 
1381             // Set language
1382             SPObject * object = text_source->source;
1383             if (!object->lang.empty()) {
1384                 PangoLanguage* language = pango_language_from_string(object->lang.c_str());
1385                 PangoAttribute *attribute_language = pango_attr_language_new( language );
1386                 pango_attr_list_insert(attributes_list, attribute_language);
1387             }
1388 
1389             // ownership of attribute is assumed by the list
1390             font->Unref();
1391         }
1392     }
1393 
1394     TRACE(("whole para: \"%s\"\n", para->text.data()));
1395     TRACE(("%d input sources used\n", input_index - para->first_input_index));
1396 
1397     // Pango Itemize
1398     GList *pango_items_glist = nullptr;
1399     para->direction = LEFT_TO_RIGHT; // CSS default
1400     if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
1401         Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
1402 
1403         para->direction =                (text_source->style->direction.computed == SP_CSS_DIRECTION_LTR) ? LEFT_TO_RIGHT : RIGHT_TO_LEFT;
1404         PangoDirection pango_direction = (text_source->style->direction.computed == SP_CSS_DIRECTION_LTR) ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
1405         pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para->text.data(), 0, para->text.bytes(), attributes_list, nullptr);
1406     }
1407 
1408     if( pango_items_glist == nullptr ) {
1409         // Type wasn't TEXT_SOURCE or direction was not set.
1410         pango_items_glist = pango_itemize(_pango_context, para->text.data(), 0, para->text.bytes(), attributes_list, nullptr);
1411     }
1412 
1413     pango_attr_list_unref(attributes_list);
1414 
1415     // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
1416     para->pango_items.reserve(g_list_length(pango_items_glist));
1417     TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
1418     for (GList *current_pango_item = pango_items_glist ; current_pango_item != nullptr ; current_pango_item = current_pango_item->next) {
1419         PangoItemInfo new_item;
1420         new_item.item = (PangoItem*)current_pango_item->data;
1421         PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
1422         new_item.font = (font_factory::Default())->Face(font_description);
1423         pango_font_description_free(font_description);   // Face() makes a copy
1424         para->pango_items.push_back(new_item);
1425     }
1426     g_list_free(pango_items_glist);
1427 
1428     // and get the character attributes on everything
1429     para->char_attributes.resize(para->text.length() + 1);
1430     pango_get_log_attrs(para->text.data(), para->text.bytes(), -1, nullptr, &*para->char_attributes.begin(), para->char_attributes.size());
1431 
1432     TRACE(("end para itemize, direction = %d\n", para->direction));
1433 }
1434 
1435 /**
1436  * Finds the value of line_height_multiplier given the 'line-height' property. The result of
1437  * multiplying \a l by \a line_height_multiplier is the inline box height as specified in css2
1438  * section 10.8.  http://www.w3.org/TR/CSS2/visudet.html#line-height
1439  *
1440  * The 'computed' value of 'line-height' does not have a consistent meaning. We need to find the
1441  * 'used' value and divide that by the font size.
1442  */
_computeFontLineHeight(SPStyle const * style)1443 double Layout::Calculator::_computeFontLineHeight( SPStyle const *style )
1444 {
1445     // This is a bit backwards... we should be returning the absolute height
1446     // but as the code expects line_height_multiplier we return that.
1447     if (style->line_height.normal) {
1448         return (LINE_HEIGHT_NORMAL);
1449     } else if (style->line_height.unit == SP_CSS_UNIT_NONE) {
1450         // Special case per CSS, computed value is multiplier
1451         return style->line_height.computed;
1452     } else {
1453         // Normal case, computed value is absolute height. Turn it into multiplier.
1454         return style->line_height.computed / style->font_size.computed;
1455     }
1456 }
1457 
compareGlyphWidth(const PangoGlyphInfo & a,const PangoGlyphInfo & b)1458 bool compareGlyphWidth(const PangoGlyphInfo &a, const PangoGlyphInfo &b)
1459 {
1460     bool retval = false;
1461     if ( b.geometry.width == 0 && (a.geometry.width > 0))retval = true;
1462     return (retval);
1463 }
1464 
1465 
1466 /**
1467  * Split the paragraph into spans. Also call pango_shape() on them.
1468  *
1469  * Input: para->first_input_index, para->pango_items
1470  * Output: para->spans
1471  * Returns: the index of the beginning of the following paragraph in _flow._input_stream
1472  */
_buildSpansForPara(ParagraphInfo * para) const1473 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
1474 {
1475     unsigned pango_item_index = 0;
1476     unsigned char_index_in_para = 0;
1477     unsigned byte_index_in_para = 0;
1478     unsigned input_index;
1479     unsigned para_text_index = 0;
1480 
1481     TRACE(("build spans\n"));
1482     para->free_sequence(para->unbroken_spans);
1483 
1484     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1485         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1486             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1487 
1488             if (   control_code->code == SHAPE_BREAK
1489                    || control_code->code == PARAGRAPH_BREAK) {
1490 
1491                 // Add span to be used to calculate line spacing of blank lines.
1492                 UnbrokenSpan new_span;
1493                 new_span.pango_item_index    = -1;
1494                 new_span.input_index         = input_index;
1495 
1496                 // No pango object, so find font and line height ourselves.
1497                 SPObject * object = control_code->source;
1498                 if (object) {
1499                     SPStyle * style = object->style;
1500                     if (style) {
1501                         new_span.font_size = style->font_size.computed * _flow.getTextLengthMultiplierDue();
1502                         font_factory * factory = font_factory::Default();
1503                         font_instance * font = factory->FaceFromStyle( style );
1504                         new_span.line_height_multiplier = _computeFontLineHeight( object->style );
1505                         new_span.line_height.set( font );
1506                         new_span.line_height *= new_span.font_size;
1507                     }
1508                 }
1509                 new_span.text_bytes          = 0;
1510                 new_span.char_index_in_para  = char_index_in_para;
1511                 para->unbroken_spans.push_back(new_span);
1512                 TRACE(("add empty span for break %lu\n", para->unbroken_spans.size() - 1));
1513                 break;                                    // stop at the end of the paragraph
1514 
1515             } else if (control_code->code == ARBITRARY_GAP) { // Not used!
1516 
1517                 UnbrokenSpan new_span;
1518                 new_span.pango_item_index    = -1;
1519                 new_span.input_index         = input_index;
1520                 new_span.line_height.ascent  = control_code->ascent * _flow.getTextLengthMultiplierDue();
1521                 new_span.line_height.descent = control_code->descent * _flow.getTextLengthMultiplierDue();
1522                 new_span.text_bytes          = 0;
1523                 new_span.char_index_in_para  = char_index_in_para;
1524                 para->unbroken_spans.push_back(new_span);
1525                 TRACE(("add gap span %lu\n", para->unbroken_spans.size() - 1));
1526             }
1527         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
1528             Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
1529             unsigned char_index_in_source = 0;
1530             unsigned span_start_byte_in_source = 0;
1531 
1532             // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
1533             for ( ; ; ) {
1534                 /* we need to change spans at every change of PangoItem, source stream change,
1535                    or change in one of the attributes altering position/rotation. */
1536 
1537                 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
1538                                                     ? 0
1539                                                     : ( para->pango_items[pango_item_index].item->offset
1540                                                         + para->pango_items[pango_item_index].item->length
1541                                                         - byte_index_in_para ) );
1542                 unsigned const text_source_bytes = ( text_source->text_end.base()
1543                                                      - text_source->text_begin.base()
1544                                                      - span_start_byte_in_source );
1545                 TRACE(("New Span\n"));
1546                 UnbrokenSpan new_span;
1547                 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
1548                 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
1549                 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
1550                 new_span.input_index = input_index;
1551 
1552                 // cut at <tspan> attribute changes as well
1553                 new_span.x._set = false;
1554                 new_span.y._set = false;
1555                 new_span.dx._set = false;
1556                 new_span.dy._set = false;
1557                 new_span.rotate._set = false;
1558                 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1559                     // Horizontal text
1560                     if (text_source->x.size()  > char_index_in_source) new_span.x  = text_source->x[char_index_in_source];
1561                     if (text_source->y.size()  > char_index_in_source) new_span.y  = text_source->y[char_index_in_source];
1562                     if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1563                     if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1564                 } else {
1565                     // Vertical text
1566                     if (text_source->x.size()  > char_index_in_source) new_span.y  = text_source->x[char_index_in_source];
1567                     if (text_source->y.size()  > char_index_in_source) new_span.x  = text_source->y[char_index_in_source];
1568                     if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1569                     if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1570                 }
1571                 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1572                 else if (char_index_in_source == 0) new_span.rotate = 0.f;
1573                 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1574                     // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1575                     // so that the top of the letters is at zero, not the baseline
1576                     new_span.y = 0.0;
1577                 }
1578                 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1579                 iter_text++;
1580                 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1581                     if (iter_text >= text_source->text_end) break;
1582                     if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1583                     if (   i >= text_source->x.size() && i >= text_source->y.size()
1584                         && i >= text_source->dx.size() && i >= text_source->dy.size()
1585                         && i >= text_source->rotate.size()) break;
1586                     if (   (text_source->x.size()  > i && text_source->x[i]._set)
1587                         || (text_source->y.size()  > i && text_source->y[i]._set)
1588                         || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1589                         || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1590                         || (text_source->rotate.size() > i && text_source->rotate[i]._set
1591                             && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1592                         new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1593                         break;
1594                     }
1595                 }
1596 
1597                 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1598                 new_span.font_size = text_source->style->font_size.computed * _flow.getTextLengthMultiplierDue();
1599                 if (new_span.text_bytes) {
1600                     new_span.glyph_string = pango_glyph_string_new();
1601                     /* Some assertions intended to help diagnose bug #1277746. */
1602                     g_assert( 0 < new_span.text_bytes );
1603                     g_assert( span_start_byte_in_source < text_source->text->bytes() );
1604                     g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1605                     g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1606                               == nullptr );
1607 
1608                     /* Notes as of 4/29/13.  Pango_shape is not generating English language ligatures, but it is generating
1609                     them for Hebrew (and probably other similar languages).  In the case observed 3 unicode characters (a base
1610                     and 2 Mark, nonspacings) are merged into two glyphs (the base + first Mn, the 2nd Mn).  All of these map
1611                     from glyph to first character of the log_cluster range.  This destroys the 1:1 correspondence between
1612                     characters and glyphs.  A big chunk of the conditional code which immediately follows this call
1613                     is there to clean up the resulting mess.
1614                     */
1615 
1616                     // Assumption: old and new arguments are the same.
1617                     auto gold = std::string_view(text_source->text->data() + span_start_byte_in_source, new_span.text_bytes);
1618                     auto gnew = std::string_view(para->text.data()         + para_text_index,           new_span.text_bytes);
1619                     assert (gold == gnew);
1620 
1621                     // Convert characters to glyphs
1622                     pango_shape_full(para->text.data() + para_text_index,
1623                                      new_span.text_bytes,
1624                                      para->text.data(),
1625                                      -1,
1626                                      &para->pango_items[pango_item_index].item->analysis,
1627                                      new_span.glyph_string);
1628 
1629                     if (para->pango_items[pango_item_index].item->analysis.level & 1) {
1630                         // Right to left text (Arabic, Hebrew, etc.)
1631 
1632                         // pango_shape() will reorder glyphs in rtl sections into visual order
1633                         // (start offsets in accending order) which messes us up because the svg
1634                         // spec requires us to draw glyphs in logical order so let's reverse the
1635                         // glyphstring.
1636 
1637                         const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1638                         std::vector<PangoGlyphInfo> infos(nglyphs);
1639                         std::vector<gint>           clusters(nglyphs);
1640 
1641                         for (int i = 0; i < nglyphs; ++i) {
1642                             std::copy(&new_span.glyph_string->glyphs[i],       &new_span.glyph_string->glyphs[i+1],       infos.end() - i - 1);
1643                             std::copy(&new_span.glyph_string->log_clusters[i], &new_span.glyph_string->log_clusters[i+1], clusters.end() - i - 1);
1644                         }
1645 
1646                         std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
1647                         std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
1648 
1649                         // We've messed up the flag that tells a glyph it is first in a cluster.
1650                         for (int i = 0; i < nglyphs; ++i) {
1651 
1652                             // Set flag for start of cluster, we skip all other glyphs in cluster below.
1653                             new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
1654 
1655                             // Find index of first glyph in next cluster
1656                             int j = i + 1;
1657                             while( (j < nglyphs) &&
1658                                    (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
1659                                 ) {
1660                                 new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0; // Zero
1661                                 j++;
1662                             }
1663 
1664                             // Move on to next cluster.
1665                             i = j;
1666                         }
1667 
1668                     } // End right to left text.
1669 
1670                     //  The following sorting doesn't seem to be necessary, and causes
1671                     //  https://gitlab.com/inkscape/inkscape/-/issues/394 ...
1672 
1673                     /*
1674                         CAREFUL, within a log_cluster the order of glyphs may not map 1:1, or
1675                         even in the same order, to the original unicode characters!!!  Among
1676                         other things, diacritical mark glyphs can end up sequentially in front of the base
1677                         character glyph.  That makes determining kerning, even approximately, difficult
1678                         later on.
1679 
1680                         To resolve this to the extent possible sort the glyphs within the same
1681                         log_cluster into descending order by width in a special manner before copying.  Diacritical marks
1682                         and similar have zero width and the glyph they modify has nonzero width.  The order
1683                         of the zero width ones does not matter.  A logical cluster is sorted into sequential order
1684                            [base] [zw_modifier1] [zw_modifier2]
1685                         where all the modifiers have zero width and the base does not. This works for languages like Hebrew.
1686 
1687                         Pango also creates log clusters for languages like Telugu having many glyphs with nonzero widths.
1688                         Since these are nonzero, their order is not modified.
1689 
1690                         If some language mixes these modes, having a log cluster having something like
1691                            [base1] [zw_modifier1] [base2] [zw_modifier2]
1692                         the result will be incorrect:
1693                            base1] [base2] [zw_modifier1] [zw_modifier2]
1694 
1695                            If ligatures other than with Mark, nonspacing are ever implemented in Pango this will screw up, for instance
1696                         changing "fi" to "if".
1697                     */
1698 
1699                     // If it is necessary to move zero width glyphs.. then it applies to both right-to-left and left-to-right text.
1700                     // const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1701                     // for (int i = 0; i < nglyphs; ++i) {
1702 
1703                     //     // Zero flag for start of cluster, we zero the rest below, and then reset it after sorting.
1704                     //     new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0;
1705 
1706                     //     // Find index of first glyph in next cluster
1707                     //     int j = i + 1;
1708                     //     while( (j < nglyphs) &&
1709                     //            (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
1710                     //         ) {
1711                     //         new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0; // Zero
1712                     //         j++;
1713                     //     }
1714 
1715                     //     if (j - i) {
1716                     //         // More than one glyph in cluster -> sort.
1717                     //         std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j]), compareGlyphWidth);
1718                     //     }
1719 
1720                     //     // Now we're sorted, set flag for start of cluster.
1721                     //     new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
1722 
1723                     //     // Move on to next cluster.
1724                     //     i = j;
1725                     // }
1726                     /* glyphs[].x_offset values are probably out of order within any log_clusters, apparently harmless */
1727 
1728 
1729                     new_span.pango_item_index = pango_item_index;
1730                     new_span.line_height_multiplier = _computeFontLineHeight( text_source->style );
1731                     new_span.line_height.set( para->pango_items[pango_item_index].font );
1732                     new_span.line_height *= new_span.font_size;
1733 
1734                     // At some point we may want to calculate baseline_shift here (to take advantage
1735                     // of otm features like superscript baseline), but for now we use style baseline_shift.
1736                     new_span.baseline_shift = text_source->style->baseline_shift.computed;
1737                     new_span.text_orientation = (SPCSSTextOrientation)text_source->style->text_orientation.computed;
1738 
1739                     // TODO: metrics for vertical text
1740                     TRACE(("add text span %lu \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
1741                     TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
1742                 } else {
1743                     // if there's no text we still need to initialise the styles
1744                     new_span.pango_item_index = -1;
1745                     font_instance *font = text_source->styleGetFontInstance();
1746                     if (font) {
1747                         new_span.line_height_multiplier = _computeFontLineHeight( text_source->style );
1748                         new_span.line_height.set( font );
1749                         new_span.line_height *= new_span.font_size;
1750                         font->Unref();
1751                     } else {
1752                         new_span.line_height *= 0.0;  // Set all to zero
1753                         new_span.line_height_multiplier = LINE_HEIGHT_NORMAL;
1754                     }
1755                     TRACE(("add style init span %lu\n", para->unbroken_spans.size()));
1756                 }
1757                 para->unbroken_spans.push_back(new_span);
1758 
1759                 // calculations for moving to the next UnbrokenSpan
1760                 byte_index_in_para += new_span.text_bytes;
1761                 para_text_index += new_span.text_bytes;
1762                 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1763 
1764                 if (new_span.text_bytes >= pango_item_bytes) {   // end of pango item
1765                     pango_item_index++;
1766                     if (pango_item_index == para->pango_items.size()) break;  // end of paragraph
1767                 }
1768                 if (new_span.text_bytes == text_source_bytes)
1769                     break;    // end of source
1770                 // else <tspan> attribute changed
1771                 span_start_byte_in_source += new_span.text_bytes;
1772             }
1773             char_index_in_para += char_index_in_source; // This seems wrong. Probably should be inside loop.
1774         }
1775     }
1776     TRACE(("end build spans\n"));
1777     return input_index;
1778 }
1779 
1780 /**
1781  * Moves onto next shape with a new scanline_maker.
1782  * If there is no next shape, creates an infinite scanline maker to stash remaining text.
1783  * Returns false if an infinite scanline maker is created.
1784  */
_goToNextWrapShape()1785 bool Layout::Calculator::_goToNextWrapShape()
1786 {
1787     if (_flow._input_wrap_shapes.size() == 0) {
1788         // Shouldn't happen.
1789         std::cerr << "Layout::Calculator::_goToNextWrapShape() called for text without shapes!" << std::endl;
1790         return false;
1791     }
1792 
1793     if (_current_shape_index >= _flow._input_wrap_shapes.size()) {
1794         // Shouldn't happen.
1795         std::cerr << "Layout::Calculator::_goToNextWrapShape(): shape index too large!" << std::endl;
1796     }
1797 
1798     _current_shape_index++;
1799 
1800     delete _scanline_maker;
1801     _scanline_maker = nullptr;
1802 
1803     if (_current_shape_index < _flow._input_wrap_shapes.size()) {
1804         _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1805         TRACE(("begin wrap shape %u\n", _current_shape_index));
1806         return true;
1807     } else {
1808         // Out of shapes, create infinite scanline maker to stash overflow.
1809 
1810         // First find a suitable position for overflow text.  (index - 1 exists since we just incremented index)
1811         double x = _flow._input_wrap_shapes[_current_shape_index - 1].shape->leftX;
1812         double y = _flow._input_wrap_shapes[_current_shape_index - 1].shape->bottomY;
1813 
1814         _scanline_maker = new InfiniteScanlineMaker(x, y, _block_progression);
1815         TRACE(("out of wrap shapes, stash leftover\n"));
1816         return false;
1817     }
1818 
1819     // Shouldn't reach
1820 }
1821 
1822 /**
1823  * Given \a para filled in and \a start_span_pos set, keeps trying to
1824  * find somewhere it can fit the next line of text. The process of finding
1825  * the text that fits will involve creating one or more entries in
1826  * \a chunk_info describing the bounds of the fitted text and several
1827  * bits of information that will prove useful when we come to output the
1828  * line to #_flow. Returns with \a start_span_pos set to the end of the
1829  * text that was fitted, \a chunk_info completely filled out and
1830  * \a line_box_height set with the largest ascent and the largest
1831  * descent (individually per CSS) on the line. The line_box_height
1832  * can never be smaller than the line_box_strut (which is determined
1833  * by the block level value of line_height). The return
1834  * value is false only if we've run out of shapes to wrap inside (and
1835  * hence stashed overflow).
1836  */
_findChunksForLine(ParagraphInfo const & para,UnbrokenSpanPosition * start_span_pos,std::vector<ChunkInfo> * chunk_info,FontMetrics * line_box_height,FontMetrics const * strut_height)1837 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
1838                                             UnbrokenSpanPosition *start_span_pos,
1839                                             std::vector<ChunkInfo> *chunk_info,
1840                                             FontMetrics *line_box_height,
1841                                             FontMetrics const *strut_height)
1842 {
1843     TRACE(("  begin _findChunksForLine: chunks: %lu, em size: %f\n", chunk_info->size(), line_box_height->emSize() ));
1844 
1845     // CSS 2.1 dictates that the minimum line height (i.e. the strut height)
1846     // is found from the block element.
1847     *line_box_height = *strut_height;
1848     TRACE(("    initial line_box_height (em size): %f\n", line_box_height->emSize() ));
1849 
1850     bool truncated = false;
1851 
1852     UnbrokenSpanPosition span_pos;
1853     for( ; ; ) {
1854         // Get regions where one can place one line of text (can be more than one, if filling a
1855         // donut for example).
1856         std::vector<ScanlineMaker::ScanRun> scan_runs;
1857         scan_runs = _scanline_maker->makeScanline(*line_box_height); // 1 scan run with "InfiniteScanlineMaker"
1858 
1859         // If scan_runs is empty, we must have reached the bottom of a shape. Go to next shape.
1860         while (scan_runs.empty()) {
1861             // Reset for new shape.
1862             *line_box_height = *strut_height;
1863 
1864             // Only used by ShapeScanlineMaker
1865             if (!_goToNextWrapShape()) {
1866                 truncated = true;
1867             }
1868 
1869             // If we've run out of shapes, this will be the infinite line scanline maker with one scan_run).
1870             scan_runs = _scanline_maker->makeScanline(*line_box_height);
1871         }
1872 
1873 
1874         TRACE(("    finding line fit y=%f, %lu scan runs\n", scan_runs.front().y, scan_runs.size()));
1875         chunk_info->clear();
1876         chunk_info->reserve(scan_runs.size());
1877         if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1878         unsigned scan_run_index;
1879         span_pos = *start_span_pos;
1880         for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1881             // Returns false if some text in line requires a taller line_box_height.
1882             // (We try again with a larger line_box_height.)
1883             if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_box_height)) {
1884                 break;
1885             }
1886 
1887             if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty()) {
1888                 span_pos = chunk_info->back().broken_spans.back().end;
1889             }
1890         }
1891 
1892         if (scan_run_index == scan_runs.size()) break;  // ie when buildChunksInScanRun() succeeded
1893 
1894     } // End for loop
1895 
1896     *start_span_pos = span_pos;
1897     TRACE(("    final line_box_height: %f\n", line_box_height->emSize() ));
1898     TRACE(("  end _findChunksForLine: chunks: %lu, truncated: %s\n", chunk_info->size(), truncated ? "true" : "false"));
1899     return !truncated;
1900 }
1901 
1902 /**
1903  * Given a scan run and a first character, append one or more chunks to
1904  * the \a chunk_info vector that describe all the spans and other detail
1905  * necessary to output the greatest amount of text that will fit on this scan
1906  * line (greedy line breaking algorithm). Each chunk contains one or more
1907  * BrokenSpan structures that link back to UnbrokenSpan structures that link
1908  * to the text itself. Normally there will be either one or zero (if the
1909  * scanrun is too short to fit any text) chunk added to \a chunk_info by
1910  * each call to this method, but we will add more than one if an x or y
1911  * attribute has been set on a tspan. \a line_height must be set on input,
1912  * and if it needs to be made larger and the #_scanline_maker can't do
1913  * an in-situ resize then it will be set to the required value and the
1914  * method will return false.
1915  */
_buildChunksInScanRun(ParagraphInfo const & para,UnbrokenSpanPosition const & start_span_pos,ScanlineMaker::ScanRun const & scan_run,std::vector<ChunkInfo> * chunk_info,FontMetrics * line_height) const1916 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
1917                                                UnbrokenSpanPosition const &start_span_pos,
1918                                                ScanlineMaker::ScanRun const &scan_run,
1919                                                std::vector<ChunkInfo> *chunk_info,
1920                                                FontMetrics *line_height) const
1921 {
1922     TRACE(("    begin _buildChunksInScanRun: chunks: %lu, em size: %f\n", chunk_info->size(), line_height->emSize() ));
1923 
1924     FontMetrics line_height_saved = *line_height; // Store for recalculating line height if chunks are backed out
1925 
1926     ChunkInfo new_chunk;
1927     new_chunk.text_width = 0.0;
1928     new_chunk.whitespace_count = 0;
1929     new_chunk.scanrun_width = scan_run.width();
1930     new_chunk.x = scan_run.x_start;
1931 
1932     // we haven't done anything yet so the last valid break position is the beginning
1933     BrokenSpan last_span_at_break, last_span_at_emergency_break;
1934     last_span_at_break.start = start_span_pos;
1935     last_span_at_break.setZero();
1936     last_span_at_emergency_break.start = start_span_pos;
1937     last_span_at_emergency_break.setZero();
1938 
1939     TRACE(("      trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1940     BrokenSpan new_span;
1941     new_span.end = start_span_pos;
1942     while (new_span.end.iter_span != para.unbroken_spans.end()) {    // this loops once for each UnbrokenSpan
1943         new_span.start = new_span.end;
1944 
1945         // force a chunk change at x or y attribute change
1946         if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1947 
1948             if (new_span.start.iter_span != start_span_pos.iter_span)
1949                 chunk_info->push_back(new_chunk);
1950 
1951             new_chunk.x += new_chunk.text_width;
1952             new_chunk.text_width = 0.0;
1953             new_chunk.whitespace_count = 0;
1954             new_chunk.broken_spans.clear();
1955             if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1956             // y doesn't need to be done until output time
1957         }
1958 
1959         // see if this span is too tall to fit on the current line
1960         FontMetrics new_span_height = new_span.start.iter_span->line_height;
1961         new_span_height.computeEffective( new_span.start.iter_span->line_height_multiplier );
1962 
1963         /* floating point 80-bit/64-bit rounding problems require epsilon. See
1964            discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1965         if ( new_span_height.ascent  > line_height->ascent  + std::numeric_limits<float>::epsilon() ||
1966              new_span_height.descent > line_height->descent + std::numeric_limits<float>::epsilon() ) {
1967             // Take larger of each of the two ascents and two descents per CSS
1968             line_height->max(new_span_height);
1969 
1970             // Currently always true for flowed text and false for Inkscape multiline text.
1971             if (!_scanline_maker->canExtendCurrentScanline(*line_height)) {
1972                 return false;
1973             }
1974         }
1975 
1976         bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1977 
1978         new_chunk.text_width += new_span.width;
1979         new_chunk.whitespace_count += new_span.whitespace_count;
1980         new_chunk.broken_spans.push_back(new_span);   // if !span_fitted we'll correct ourselves below
1981 
1982         if (!span_fitted) break;
1983 
1984         if (new_span.end.iter_span == para.unbroken_spans.end()) {
1985             last_span_at_break = new_span;
1986             break;
1987         }
1988 
1989         PangoLogAttr const &char_attributes = _charAttributes(para, new_span.end);
1990         if (char_attributes.is_mandatory_break) {
1991             last_span_at_break = new_span;
1992             break;
1993         }
1994     }
1995 
1996     TRACE(("      chunk complete, used %f width (%d whitespaces, %lu brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1997     chunk_info->push_back(new_chunk);
1998 
1999     if (scan_run.width() >= 4.0 * line_height->emSize() && last_span_at_break.end == start_span_pos) {
2000         /* **non-SVG spec bit**: See bug #1191102
2001            If the user types a very long line with no spaces, the way the spec
2002            is written at the moment means that when the length of the text
2003            exceeds the available width of all remaining areas, the text is
2004            completely hidden. This condition alters that behaviour so that if
2005            the length of the line is greater than four times the line-height
2006            and there are no spaces, it'll be emergency-wrapped at the last
2007            character. One could read the SVG Tiny 1.2 draft as permitting this
2008            sort of behaviour, but it's still a bit dodgy. The hard-coding of
2009            4x is not nice, either. */
2010         last_span_at_break = last_span_at_emergency_break;
2011     }
2012 
2013     if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
2014         // need to back out spans until we come to the one with the last break in it
2015         while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
2016             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
2017             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
2018             chunk_info->back().broken_spans.pop_back();
2019             if (chunk_info->back().broken_spans.empty())
2020                 chunk_info->pop_back();
2021         }
2022         if (!chunk_info->empty()) {
2023             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
2024             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
2025             if (last_span_at_break.start == last_span_at_break.end) {
2026                 chunk_info->back().broken_spans.pop_back();   // last break was at an existing boundary
2027                 if (chunk_info->back().broken_spans.empty())
2028                     chunk_info->pop_back();
2029             } else {
2030                 chunk_info->back().broken_spans.back() = last_span_at_break;
2031                 chunk_info->back().text_width += last_span_at_break.width;
2032                 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
2033             }
2034             TRACE(("      correction: fitted span %lu width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
2035         }
2036     }
2037 
2038     // Recalculate line_box_height after backing out chunks
2039     *line_height = line_height_saved;
2040     for (const auto & it_chunk : *chunk_info) {
2041         for (const auto & broken_span : it_chunk.broken_spans) {
2042             FontMetrics span_height = broken_span.start.iter_span->line_height;
2043             TRACE(("      brokenspan line_height: %f\n", span_height.emSize() ));
2044             span_height.computeEffective( broken_span.start.iter_span->line_height_multiplier );
2045             line_height->max( span_height );
2046         }
2047     }
2048     TRACE(("      line_box_height: %f\n", line_height->emSize()));
2049 
2050     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
2051         // for justification we need to discard space occupied by the single whitespace at the end of the chunk
2052         TRACE(("      backing out whitespace\n"));
2053         chunk_info->back().broken_spans.back().ends_with_whitespace = false;
2054         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
2055         chunk_info->back().broken_spans.back().whitespace_count--;
2056         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
2057         chunk_info->back().whitespace_count--;
2058     }
2059 
2060     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
2061         // for justification we need to discard line-spacing and word-spacing at end of the chunk
2062         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().letter_spacing;
2063         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().letter_spacing;
2064         TRACE(("      width after subtracting last letter_spacing: %f\n", chunk_info->back().broken_spans.back().width));
2065     }
2066 
2067     TRACE(("    end _buildChunksInScanRun: chunks: %lu\n", chunk_info->size()));
2068     return true;
2069 }
2070 
2071 #ifdef DEBUG_LAYOUT_TNG_COMPUTE
2072 /**
2073  * For debugging, not called in distributed code
2074  *
2075  * Input: para->first_input_index, para->pango_items
2076  */
dumpPangoItemsOut(ParagraphInfo * para)2077 void Layout::Calculator::dumpPangoItemsOut(ParagraphInfo *para){
2078     std::cout << "Pango items: " << para->pango_items.size() << std::endl;
2079     font_factory * factory = font_factory::Default();
2080     for(unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){
2081         std::cout
2082         << "idx: " << pidx
2083         << " offset: "
2084         << para->pango_items[pidx].item->offset
2085         << " length: "
2086         << para->pango_items[pidx].item->length
2087         << " font: "
2088         << factory->ConstructFontSpecification( para->pango_items[pidx].font )
2089         << std::endl;
2090     }
2091 }
2092 
2093 /**
2094  * For debugging, not called in distributed code
2095  *
2096  * Input: para->first_input_index, para->pango_items
2097  */
dumpUnbrokenSpans(ParagraphInfo * para)2098 void Layout::Calculator::dumpUnbrokenSpans(ParagraphInfo *para){
2099     std::cout << "Unbroken Spans: " << para->unbroken_spans.size() << std::endl;
2100     for(unsigned uidx = 0 ; uidx < para->unbroken_spans.size(); uidx++){
2101         std::cout
2102         << "idx: "                 << uidx
2103         << " pango_item_index: "   << para->unbroken_spans[uidx].pango_item_index
2104         << " input_index: "        << para->unbroken_spans[uidx].input_index
2105         << " char_index_in_para: " << para->unbroken_spans[uidx].char_index_in_para
2106         << " text_bytes: "         << para->unbroken_spans[uidx].text_bytes
2107         << std::endl;
2108     }
2109 }
2110 #endif //DEBUG_LAYOUT_TNG_COMPUTE
2111 
2112 /** The management function to start the whole thing off. */
calculate()2113 bool Layout::Calculator::calculate()
2114 {
2115     if (_flow._input_stream.empty())
2116         return false;
2117     /**
2118     * hm, why do we want assert (crash) the application, now do simply return false
2119     * \todo check if this is the correct behaviour
2120     * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
2121     */
2122     if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
2123     {
2124         g_warning("flow text is not of type TEXT_SOURCE. Abort.");
2125         return false;
2126     }
2127     TRACE(("begin calculate()\n"));
2128 
2129     _flow._clearOutputObjects();
2130 
2131     _pango_context = (font_factory::Default())->fontContext;
2132 
2133     _font_factory_size_multiplier = (font_factory::Default())->fontSize;
2134 
2135     _block_progression = _flow._blockProgression();
2136     if( _block_progression == RIGHT_TO_LEFT || _block_progression == LEFT_TO_RIGHT ) {
2137         // Vertical text, CJK
2138         switch (_flow._blockTextOrientation()) {
2139             case SP_CSS_TEXT_ORIENTATION_MIXED:
2140                 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
2141                 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
2142                 break;
2143             case SP_CSS_TEXT_ORIENTATION_UPRIGHT:
2144                 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
2145                 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
2146                 break;
2147             case SP_CSS_TEXT_ORIENTATION_SIDEWAYS:
2148                 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_SOUTH);
2149                 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
2150                 break;
2151             default:
2152                 std::cerr << "Layout::Calculator: Unhandled text orientation!" << std::endl;
2153         }
2154     } else {
2155         // Horizontal text
2156         pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_AUTO);
2157         pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
2158     }
2159 
2160     // Minimum line box height determined by block container.
2161     FontMetrics strut_height = _flow.strut;
2162     _y_offset = 0.0;
2163     _createFirstScanlineMaker();
2164 
2165     ParagraphInfo para;
2166     FontMetrics line_box_height; // Current value of line box height for line.
2167     bool keep_going = true; // Set false if we ran out of space and had to stash overflow.
2168     for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
2169 
2170         // jump to the next wrap shape if this is a SHAPE_BREAK control code
2171         if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
2172             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
2173             if (control_code->code == SHAPE_BREAK) {
2174                 TRACE(("shape break control code\n"));
2175                 if (!_goToNextWrapShape()) {
2176                     std::cerr << "Layout::Calculator::calculate: Found SHAPE_BREAK but out of shapes!" << std::endl;
2177                 }
2178                 continue; // Go to next paragraph (paragraph only contained control code).
2179             }
2180         }
2181 
2182         // Break things up into little pango units with unique direction, gravity, etc.
2183         _buildPangoItemizationForPara(&para);
2184 
2185         // Do shaping (convert characters to glyphs)
2186         unsigned para_end_input_index = _buildSpansForPara(&para);
2187 
2188         if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
2189             para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
2190         else
2191             para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
2192 
2193         TRACE(("para prepared, adding as #%lu\n", _flow._paragraphs.size()));
2194         Layout::Paragraph new_paragraph;
2195         new_paragraph.base_direction = para.direction;
2196         new_paragraph.alignment = para.alignment;
2197         _flow._paragraphs.push_back(new_paragraph);
2198 
2199         // start scanning lines
2200         UnbrokenSpanPosition span_pos;
2201         span_pos.iter_span = para.unbroken_spans.begin();
2202         span_pos.char_byte = 0;
2203         span_pos.char_index = 0;
2204 
2205         do {   // Until end of paragraph
2206             TRACE(("begin line\n"));
2207 
2208             std::vector<ChunkInfo> line_chunk_info;
2209 
2210             // Fill line.
2211             // If we've run out of space, we've put the remaining text in a single line and
2212             // returned false. If we ran out of space on previous paragraph, we continue with
2213             // single-line scan-line maker.
2214             bool flowed =_findChunksForLine(para, &span_pos, &line_chunk_info, &line_box_height, &strut_height );
2215             if (!flowed) {
2216                 keep_going = false;
2217             }
2218 
2219             if (line_box_height.emSize() < 0.001 && line_chunk_info.empty()) {
2220                 // We need to avoid an infinite (or semi-infinite) loop.
2221                 std::cerr << "Layout::Calculator::calculate: No room for text and line advance is very small" << std::endl;
2222                 return false; // For the moment
2223             }
2224 
2225 
2226             // For Inkscape multi-line text (using role="line") we run into a problem if the first
2227             // line is empty - namely, there is no character to attach a 'y' attribute value. The
2228             // result is that the code that takes a baseline position (e.g. 'y') and finds the top
2229             // of the layout box is bypassed resulting in wrongly placed text (we layout the text
2230             // relative to the top of the box as this is required for text-in-a-shape). We don't
2231             // know how to find the top of the box from the 'y' position until we have found the
2232             // line height parameters for the given line (after calling _findChunksForLine() just
2233             // above).
2234             if (para.first_input_index == 0 && (_flow.wrap_mode == WRAP_NONE)) {
2235 
2236                 // Calculate new top of box... given specified baseline.
2237                 double top_of_line_box = _scanline_maker->yCoordinate(); // Set in constructor.
2238                 if( _block_progression == RIGHT_TO_LEFT ) {
2239                     // Vertical text, use em box center as baseline
2240                     top_of_line_box += 0.5 * line_box_height.emSize();
2241                 } else if (_block_progression == LEFT_TO_RIGHT ) {
2242                     // Vertical text, use em box center as baseline
2243                     top_of_line_box -= 0.5 * line_box_height.emSize();
2244                 } else {
2245                     top_of_line_box -= line_box_height.getTypoAscent();
2246                 }
2247                 TRACE(("      y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
2248                 // Set the initial y coordinate of the for this line (see above).
2249                 _scanline_maker->setNewYCoordinate(top_of_line_box);
2250             }
2251 
2252             // !keep_going --> truncated --> hidden
2253             _outputLine(para, line_box_height, line_chunk_info, !keep_going);
2254 
2255             _scanline_maker->setLineHeight( line_box_height );
2256             _scanline_maker->completeLine(); // Increments y by line height
2257             TRACE(("end line\n"));
2258         } while (span_pos.iter_span != para.unbroken_spans.end());
2259 
2260         TRACE(("para %lu end\n\n", _flow._paragraphs.size() - 1));
2261         if (keep_going) {
2262             // We have more to do, setup next section.
2263             bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
2264             if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
2265                 || para_end_input_index + 1 < _flow._input_stream.size()) {
2266                 // we need a span just for the para if it's either an empty last para or a break in the middle
2267                 Layout::Span new_span;
2268                 if (_flow._spans.empty()) {
2269                     new_span.font = nullptr;
2270                     new_span.font_size = line_box_height.emSize();
2271                     new_span.line_height = line_box_height;
2272                     new_span.x_end = 0.0;
2273                 } else {
2274                     new_span = _flow._spans.back();
2275                     if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
2276                         new_span.x_end = 0.0;
2277                 }
2278                 new_span.in_chunk = _flow._chunks.size() - 1;
2279                 if (new_span.font)
2280                     new_span.font->Ref();
2281                 new_span.x_start = new_span.x_end;
2282                 new_span.baseline_shift = 0.0;
2283                 new_span.direction = para.direction;
2284                 new_span.block_progression = _block_progression;
2285                 if (para_end_input_index == _flow._input_stream.size())
2286                     new_span.in_input_stream_item = _flow._input_stream.size() - 1;
2287                 else
2288                     new_span.in_input_stream_item = para_end_input_index;
2289                 _flow._spans.push_back(new_span);
2290             }
2291             if (para_end_input_index + 1 < _flow._input_stream.size()) {
2292                 // we've got to add an invisible character between paragraphs so that we can position iterators
2293                 // (and hence cursors) both before and after the paragraph break
2294                 Layout::Character new_character;
2295                 new_character.the_char = '@';
2296                 new_character.in_span = _flow._spans.size() - 1;
2297                 new_character.char_attributes.is_line_break = 1;
2298                 new_character.char_attributes.is_mandatory_break = 1;
2299                 new_character.char_attributes.is_char_break = 1;
2300                 new_character.char_attributes.is_white = 1;
2301                 new_character.char_attributes.is_cursor_position = 1;
2302                 new_character.char_attributes.is_word_start = 0;
2303                 new_character.char_attributes.is_word_end = 1;
2304                 new_character.char_attributes.is_sentence_start = 0;
2305                 new_character.char_attributes.is_sentence_end = 1;
2306                 new_character.char_attributes.is_sentence_boundary = 1;
2307                 new_character.char_attributes.backspace_deletes_character = 1;
2308                 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
2309                 new_character.in_glyph = -1;
2310                 _flow._characters.push_back(new_character);
2311             }
2312         }
2313         // dumpPangoItemsOut(&para);
2314         // dumpUnbrokenSpans(&para);
2315 
2316         para.free();
2317         para.first_input_index = para_end_input_index + 1;
2318     } // Loop over paras
2319 
2320     para.free();
2321     if (_scanline_maker) {
2322         delete _scanline_maker;
2323     }
2324 
2325     _flow._input_truncated = !keep_going;
2326 
2327     if (_flow.textLength._set) {
2328         // Calculate the adjustment needed to meet the textLength
2329         double actual_length = _flow.getActualLength();
2330         double difference = _flow.textLength.computed - actual_length;
2331         _flow.textLengthMultiplier = (actual_length + difference) / actual_length;
2332         _flow.textLengthIncrement = difference / (_flow._characters.size() == 1? 1 : _flow._characters.size() - 1);
2333     }
2334 
2335     return true;
2336 }
2337 
_calculateCursorShapeForEmpty()2338 void Layout::_calculateCursorShapeForEmpty()
2339 {
2340     _empty_cursor_shape.position = Geom::Point(0, 0);
2341     _empty_cursor_shape.height = 0.0;
2342     _empty_cursor_shape.rotation = 0.0;
2343     if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
2344         return;
2345 
2346     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
2347 
2348     font_instance *font = text_source->styleGetFontInstance();
2349     double font_size = text_source->style->font_size.computed;
2350     double caret_slope_run = 0.0, caret_slope_rise = 1.0;
2351     FontMetrics line_height;
2352     if (font) {
2353         const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
2354         font->FontMetrics(line_height.ascent, line_height.descent, line_height.xheight);
2355         line_height *= font_size;
2356         font->Unref();
2357     }
2358 
2359     double caret_slope = atan2(caret_slope_run, caret_slope_rise);
2360     _empty_cursor_shape.height = font_size / cos(caret_slope);
2361     _empty_cursor_shape.rotation = caret_slope;
2362 
2363     if (_input_wrap_shapes.empty()) {
2364         _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
2365                                                  text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
2366     } else if (wrap_mode == WRAP_INLINE_SIZE) {
2367         // 'inline-size' has a wrap shape of an "infinite" rectangle, we need the place where the text should begin.
2368         double x = 0;
2369         double y = 0;
2370         if (!text_source->x.empty())
2371             x = text_source->x.front().computed;
2372         if (!text_source->y.empty())
2373             y = text_source->y.front().computed;
2374         _empty_cursor_shape.position = Geom::Point(x, y);
2375     } else {
2376         Direction block_progression = text_source->styleGetBlockProgression();
2377         ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
2378         std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
2379         if (!scan_runs.empty()) {
2380             if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
2381                 // Vertical text
2382                 _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
2383             } else {
2384                 // Horizontal text
2385                 _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
2386             }
2387         }
2388     }
2389 }
2390 
calculateFlow()2391 bool Layout::calculateFlow()
2392 {
2393     TRACE(("begin calculateFlow()\n"));
2394     Layout::Calculator calc = Calculator(this);
2395     bool result = calc.calculate();
2396 
2397     if (textLengthIncrement != 0) {
2398         TRACE(("Recalculating layout the second time to fit textLength!\n"));
2399         result = calc.calculate();
2400     }
2401 
2402     if (_characters.empty()) {
2403         _calculateCursorShapeForEmpty();
2404     }
2405     return result;
2406 }
2407 
2408 }//namespace Text
2409 }//namespace Inkscape
2410 
2411 
2412 /*
2413   Local Variables:
2414   mode:c++
2415   c-file-style:"stroustrup"
2416   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2417   indent-tabs-mode:nil
2418   fill-column:99
2419   End:
2420 */
2421 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
2422