Code

remove unnecessary casts which didn't compile on old versions of FreeType
[inkscape.git] / src / libnrtype / Layout-TNG-Compute.cpp
1 /*
2  * Inkscape::Text::Layout::Calculator - text layout engine meaty bits
3  *
4  * Authors:
5  *   Richard Hughes <cyreve@users.sf.net>
6  *
7  * Copyright (C) 2005 Richard Hughes
8  *
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
11 #include "Layout-TNG.h"
12 #include "style.h"
13 #include "font-instance.h"
14 #include "svg/svg-length.h"
15 #include "sp-object.h"
16 #include "Layout-TNG-Scanline-Maker.h"
18 namespace Inkscape {
19 namespace Text {
21 //#define IFTRACE(_code) _code
22 //#define TRACE(_args) g_print _args
23 #define IFTRACE(_code)
24 #define TRACE(_args)
26 // ******* enum conversion tables
27 static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_pango_direction[] = {
28     {SP_CSS_WRITING_MODE_LR_TB, PANGO_DIRECTION_LTR},
29     {SP_CSS_WRITING_MODE_RL_TB, PANGO_DIRECTION_RTL},
30     {SP_CSS_WRITING_MODE_TB_LR, PANGO_DIRECTION_LTR}};   // this is correct
32 static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_my_direction[] = {
33     {SP_CSS_WRITING_MODE_LR_TB, Layout::LEFT_TO_RIGHT},
34     {SP_CSS_WRITING_MODE_RL_TB, Layout::RIGHT_TO_LEFT},
35     {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}};   // this is correct
37 /** \brief private to Layout. Does the real work of text flowing.
39 This class does a standard greedy paragraph wrapping algorithm.
41 Very high-level overview:
43 <pre>
44 foreach(paragraph) {
45   call pango_itemize() (_buildPangoItemizationForPara())
46   break into spans, without dealing with wrapping (_buildSpansForPara())
47   foreach(line in flow shape) {
48     foreach(chunk in flow shape) {   (in _buildChunksInScanRun())
49       // this inner loop in _measureUnbrokenSpan()
50       if the line height changed discard the line and start again
51       keep adding characters until we run out of space in the chunk, then back up to the last word boundary
52       (do sensible things if there is no previous word break)
53     }
54     push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
55   }
56   push the paragraph (in calculate())
57 }
58 </pre>
60 ...and all of that needs to work vertically too, and with all the little details that make life annoying
61 */
62 class Layout::Calculator
63 {
64     class SpanPosition;
65     friend class SpanPosition;
67     Layout &_flow;
69     ScanlineMaker *_scanline_maker;
71     unsigned _current_shape_index;     /// index into Layout::_input_wrap_shapes
73     PangoContext *_pango_context;
75     Direction _block_progression;
77     /** for y= attributes in tspan elements et al, we do the adjustment by moving each
78     glyph individually by this number. The spec means that this is maintained across
79     paragraphs. */
80     double _y_offset;
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;
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
92         InputItemInfo() : in_sub_flow(false), sub_flow(NULL) {}
93         void free();
94     };
96     /** Temporary storage associated with each item returned by the call to
97         pango_itemize(). */
98     struct PangoItemInfo {
99         PangoItem *item;
100         font_instance *font;
102         PangoItemInfo() : item(NULL), font(NULL) {}
103         void free();
104     };
106     /** These spans have approximately the same definition as that used for
107     Layout::Span (constant font, direction, etc), except that they are from
108     before we have located the line breaks, so bear no relation to chunks.
109     They are guaranteed to be in at most one PangoItem (spans with no text in
110     them will not have an associated PangoItem), exactly one input source and
111     will only have one change of x, y, dx, dy or rotate attribute, which will
112     be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
113     BrokenSpan. */
114     struct UnbrokenSpan {
115         PangoGlyphString *glyph_string;
116         int pango_item_index;    /// index into _para.pango_items, or -1 if this is style only
117         unsigned input_index;         /// index into Layout::_input_stream
118         Glib::ustring::const_iterator input_stream_first_character;
119         double font_size;
120         LineHeight line_height;
121         double line_height_multiplier;  /// calculated from the font-height css property
122         unsigned text_bytes;
123         unsigned char_index_in_para;    /// the index of the first character in this span in the paragraph, for looking up char_attributes
124         SVGLength x, y, dx, dy, rotate;  // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
126         UnbrokenSpan() : glyph_string(NULL) {}
127         void free() {if (glyph_string) pango_glyph_string_free(glyph_string); glyph_string = NULL;}
128     };
130     /** a useful little iterator for moving char-by-char across spans. */
131     struct UnbrokenSpanPosition {
132         std::vector<UnbrokenSpan>::iterator iter_span;
133         unsigned char_byte;
134         unsigned char_index;
136         void increment();   ///< Step forward by one character.
138         inline bool operator== (UnbrokenSpanPosition const &other) const
139             {return char_byte == other.char_byte && iter_span == other.iter_span;}
140         inline bool operator!= (UnbrokenSpanPosition const &other) const
141             {return char_byte != other.char_byte || iter_span != other.iter_span;}
142     };
144     /** The line breaking algorithm will convert each UnbrokenSpan into one
145     or more of these. A BrokenSpan will never cross a chunk boundary, c.f.
146     UnbrokenSpan. */
147     struct BrokenSpan {
148         UnbrokenSpanPosition start;
149         UnbrokenSpanPosition end;    // the end of this will always be the same as the start of the next
150         unsigned start_glyph_index;
151         unsigned end_glyph_index;
152         double width;
153         unsigned whitespace_count;
154         bool ends_with_whitespace;
155         double each_whitespace_width;
156         void setZero();
157     };
159     /** The definition of a chunk used here is the same as that used in Layout. */
160     struct ChunkInfo {
161         std::vector<BrokenSpan> broken_spans;
162         double scanrun_width;
163         double text_width;       ///< Total width used by the text (excluding justification).
164         double x;
165         int whitespace_count;
166     };
168     /** Used to provide storage for anything that applies to the current
169     paragraph only. Since we're only processing one paragraph at a time,
170     there's only one instantiation of this struct, on the stack of
171     calculate(). */
172     struct ParagraphInfo {
173         unsigned first_input_index;      ///< Index into Layout::_input_stream.
174         Direction direction;
175         Alignment alignment;
176         std::vector<InputItemInfo> input_items;
177         std::vector<PangoItemInfo> pango_items;
178         std::vector<PangoLogAttr> char_attributes;    ///< For every character in the paragraph.
179         std::vector<UnbrokenSpan> unbroken_spans;
181         template<typename T> static void free_sequence(T &seq);
182         void free();
183     };
185 /* *********************************************************************************************************/
186 //                       Initialisation of ParagraphInfo structure
189 #if 0 /* unused */
190     void _initialiseInputItems(ParagraphInfo *para) const;
191 #endif
193     void _buildPangoItemizationForPara(ParagraphInfo *para) const;
195     static void _computeFontLineHeight(font_instance *font, double font_size,
196                                        SPStyle const *style, LineHeight *line_height,
197                                        double *line_height_multiplier);
199     unsigned _buildSpansForPara(ParagraphInfo *para) const;
201 /* *********************************************************************************************************/
202 //                             Per-line functions
205     bool _goToNextWrapShape();
207     bool _findChunksForLine(ParagraphInfo const &para, UnbrokenSpanPosition *start_span_pos,
208                             std::vector<ChunkInfo> *chunk_info, LineHeight *line_height);
210     static inline PangoLogAttr const &_charAttributes(ParagraphInfo const &para,
211                                                       UnbrokenSpanPosition const &span_pos)
212     {
213         return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
214     }
216     bool _buildChunksInScanRun(ParagraphInfo const &para,
217                                UnbrokenSpanPosition const &start_span_pos,
218                                ScanlineMaker::ScanRun const &scan_run,
219                                std::vector<ChunkInfo> *chunk_info,
220                                LineHeight *line_height) const;
222     /** computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
223     and outputs its vital statistics into the other fields of \a span.
224     Measuring will stop if maximum_width is reached and in that case the
225     function will return false. In other cases where a line break must be
226     done immediately the function will also return false. On return
227     \a last_break_span will contain the vital statistics for the span only
228     up to the last line breaking change. If there are no line breaking
229     characters in the span then \a last_break_span will not be altered.
230     Similarly, \a last_emergency_break_span will contain the vital
231     statistics for the span up to the last inter-character boundary,
232     or will be unaltered if there is none. */
233     bool _measureUnbrokenSpan(ParagraphInfo const &para, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const
234     {
235         span->setZero();
237         if (span->start.iter_span->dx._set && span->start.char_byte == 0)
238             span->width += span->start.iter_span->dx.computed;
240         if (span->start.iter_span->pango_item_index == -1) {
241             // if this is a style-only span there's no text in it
242             // so we don't need to do very much at all
243             span->end.iter_span++;
244             return true;
245         }
247         if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
248             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
249             if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
250                 *last_emergency_break_span = *last_break_span = *span;
251                 return false;
252             }
253             if (control_code->code == ARBITRARY_GAP) {
254                 if (span->width + control_code->width > maximum_width)
255                     return false;
256                 TRACE(("fitted control code, width = %f\n", control_code->width));
257                 span->width += control_code->width;
258                 span->end.increment();
259             }
260             return true;
262         }
264         if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
265             return true;  // never happens
267         InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
269         if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
270             // TODO: block-progression altered in the middle
271             // Measure the precomputed flow from para.input_items
272             span->end.iter_span++;  // for now, skip to the next span
273             return true;
274         }
276         // a normal span going with a normal block-progression
277         double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
278         double soft_hyphen_glyph_width = 0.0;
279         bool soft_hyphen_in_word = false;
280         bool is_soft_hyphen = false;
281         IFTRACE(int char_count = 0);
283         // if we're not at the start of the span we need to pre-init glyph_index
284         span->start_glyph_index = 0;
285         while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
286                && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
287             span->start_glyph_index++;
288         span->end_glyph_index = span->start_glyph_index;
290         // go char-by-char summing the width, while keeping track of the previous break point
291         do {
292             PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
294             if (char_attributes.is_mandatory_break) {
295                 *last_emergency_break_span = *last_break_span = *span;
296                 TRACE(("span %d end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
297                 return false;
298             }
300             if (char_attributes.is_line_break) {
301                 // a suitable position to break at, record where we are
302                 *last_emergency_break_span = *last_break_span = *span;
303                 if (soft_hyphen_in_word) {
304                     // if there was a previous soft hyphen we're not going to need it any more so we can remove it
305                     span->width -= soft_hyphen_glyph_width;
306                     if (!is_soft_hyphen)
307                         soft_hyphen_in_word = false;
308                 }
309             } else if (char_attributes.is_char_break) {
310                 *last_emergency_break_span = *span;
311             }
312             // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
314             // sum the glyph widths, letter spacing and word spacing to get the character width
315             double char_width = 0.0;
316             while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
317                    && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
318                 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
319                     char_width += span->start.iter_span->font_size * para.pango_items[span->end.iter_span->pango_item_index].font->Advance(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].glyph, true);
320                 else
321                     char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width;
322                 span->end_glyph_index++;
323             }
324             if (char_attributes.is_cursor_position)
325                 char_width += text_source->style->letter_spacing.computed;
326             if (char_attributes.is_white)
327                 char_width += text_source->style->word_spacing.computed;
328             span->width += char_width;
329             IFTRACE(char_count++);
331             if (char_attributes.is_white) {
332                 span->whitespace_count++;
333                 span->each_whitespace_width = char_width;
334             }
335             span->ends_with_whitespace = char_attributes.is_white;
337             is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
338             if (is_soft_hyphen)
339                 soft_hyphen_glyph_width = char_width;
341             span->end.increment();
343             if (span->width > maximum_width && !char_attributes.is_white) {       // whitespaces don't matter, we can put as many as we want at eol
344                 TRACE(("span %d exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
345                 return false;
346             }
348         } while (span->end.char_byte != 0);  // while we haven't wrapped to the next span
349         TRACE(("fitted span %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
350         return true;
351     }
353 /* *********************************************************************************************************/
354 //                             Per-line functions (output)
356     /** Uses the paragraph alignment and the chunk information to work out
357     where the actual left of the final chunk must be. Also sets
358     \a add_to_each_whitespace to be the amount of x to add at each
359     whitespace character to make full justification work. */
360     double _getChunkLeftWithAlignment(ParagraphInfo const &para, std::vector<ChunkInfo>::const_iterator it_chunk, double *add_to_each_whitespace) const
361     {
362         *add_to_each_whitespace = 0.0;
363         if (_flow._input_wrap_shapes.empty()) {
364             switch (para.alignment) {
365                 case FULL:
366                 case LEFT:
367                 default:
368                     return it_chunk->x;
369                 case RIGHT:
370                     return it_chunk->x - it_chunk->text_width;
371                 case CENTER:
372                     return it_chunk->x - it_chunk->text_width / 2;
373             }
374         }
376         switch (para.alignment) {
377             case FULL:
378                 if (!it_chunk->broken_spans.empty()
379                     && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) {   // don't justify the last chunk in the para
380                     if (it_chunk->whitespace_count)
381                         *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
382                     //else
383                         //add_to_each_charspace = something
384                 }
385                 return it_chunk->x;
386             case LEFT:
387             default:
388                 return it_chunk->x;
389             case RIGHT:
390                 return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
391             case CENTER:
392                 return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
393         }
394     }
396     /** Once we've got here we have finished making changes to the line and
397     are ready to output the final result to #_flow. This method takes its
398     input parameters and does that.
399     */
400     void _outputLine(ParagraphInfo const &para, LineHeight const &line_height, std::vector<ChunkInfo> const &chunk_info)
401     {
402         if (chunk_info.empty()) {
403             TRACE(("line too short to fit anything on it, go to next\n"));
404             return;
405         }
407         // we've finished fiddling about with ascents and descents: create the output
408         TRACE(("found line fit; creating output\n"));
409         Layout::Line new_line;
410         new_line.in_paragraph = _flow._paragraphs.size() - 1;
411         new_line.baseline_y = _scanline_maker->yCoordinate() + line_height.ascent;
412         new_line.in_shape = _current_shape_index;
413         _flow._lines.push_back(new_line);
415         for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
417             double add_to_each_whitespace;
418             // add the chunk to the list
419             Layout::Chunk new_chunk;
420             new_chunk.in_line = _flow._lines.size() - 1;
421             new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
422             // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
423             if (!it_chunk->broken_spans.empty()    // this one only happens for empty paragraphs
424                 && it_chunk->broken_spans.front().start.char_byte == 0
425                 && it_chunk->broken_spans.front().start.iter_span->y._set) {
426                 // if this is the start of a line, we should change the baseline rather than each glyph individually
427                 if (_flow._characters.empty() || _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
428                     new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
429                     _flow._lines.back().baseline_y = new_line.baseline_y;
430                     _y_offset = 0.0;
431                     _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
432                 } else
433                     _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
434             }
435             _flow._chunks.push_back(new_chunk);
437             double x;
438             double direction_sign;
439             Direction previous_direction = para.direction;
440             double counter_directional_width_remaining = 0.0;
441             float glyph_rotate = 0.0;
442             if (para.direction == LEFT_TO_RIGHT) {
443                 direction_sign = +1.0;
444                 x = 0.0;
445             } else {
446                 direction_sign = -1.0;
447                 if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
448                     x = it_chunk->scanrun_width;
449                 else
450                     x = it_chunk->text_width;
451             }
453             for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
454                 // begin adding spans to the list
455                 UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
457                 if (it_span->start.char_byte == 0) {
458                     // start of an unbroken span, we might have dx, dy or rotate still to process (x and y are done per chunk)
459                     if (unbroken_span.dx._set) x += unbroken_span.dx.computed;
460                     if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
461                     if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
462                 }
464                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
465                     && unbroken_span.pango_item_index == -1) {
466                     // style only, nothing to output
467                     continue;
468                 }
470                 Layout::Span new_span;
471                 double x_in_span = 0.0;
473                 new_span.in_chunk = _flow._chunks.size() - 1;
474                 new_span.line_height = unbroken_span.line_height;
475                 new_span.in_input_stream_item = unbroken_span.input_index;
476                 new_span.baseline_shift = _y_offset;
477                 new_span.block_progression = _block_progression;
478                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
479                     new_span.font = para.pango_items[unbroken_span.pango_item_index].font;
480                     new_span.font->Ref();
481                     new_span.font_size = unbroken_span.font_size;
482                     new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
483                     new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
484                 } else {  // a control code
485                     new_span.font = NULL;
486                     new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
487                     new_span.direction = para.direction;
488                 }
490                 if (new_span.direction == para.direction) {
491                     x -= counter_directional_width_remaining;
492                     counter_directional_width_remaining = 0.0;
493                 } else if (new_span.direction != previous_direction) {
494                     // measure width of spans we need to switch round
495                     counter_directional_width_remaining = 0.0;
496                     std::vector<BrokenSpan>::const_iterator it_following_span;
497                     for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
498                         Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
499                         if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
500                             if (it_following_span->start.iter_span->pango_item_index == -1) {   // when the span came from a control code
501                                 if (new_span.direction != para.direction) break;
502                             } else
503                                 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;
504                         }
505                         counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
506                     }
507                     x += counter_directional_width_remaining;
508                     counter_directional_width_remaining = 0.0;    // we want to go increasingly negative
509                 }
510                 new_span.x_start = x;
512                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
513                     // the span is set up, push the glyphs and chars
514                     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
515                     Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
516                     unsigned char_index_in_unbroken_span = it_span->start.char_index;
517                     unsigned cluster_start_char_index = _flow._characters.size();
518                     double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
520                     for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
521                         unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
522                         if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
523                             cluster_start_char_index = _flow._characters.size();
525                         if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
526                             && *iter_source_text == UNICODE_SOFT_HYPHEN
527                             && glyph_index + 1 != it_span->end_glyph_index) {
528                             // if we're looking at a soft hyphen and it's not the last glyph in the
529                             // chunk we don't draw the glyph but we still need to add to _characters
530                             Layout::Character new_character;
531                             new_character.in_span = _flow._spans.size();     // the span hasn't been added yet, so no -1
532                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
533                             new_character.in_glyph = -1;
534                             _flow._characters.push_back(new_character);
535                             iter_source_text++;
536                             char_index_in_unbroken_span++;
537                             while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
538                                    && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
539                                 glyph_index++;
540                             glyph_index--;
541                             continue;
542                         }
544                         // create the Layout::Glyph
545                         Layout::Glyph new_glyph;
546                         new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
547                         new_glyph.in_character = cluster_start_char_index;
548                         new_glyph.rotation = glyph_rotate;
550                         /* put something like this back in when we do glyph-rotation-horizontal/vertical
551                         if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
552                             new_glyph.x += new_span.line_height.ascent;
553                             new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
554                             new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
555                         } else */
557                         if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
558                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
559                             new_glyph.y = _y_offset + (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset - unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
560                             new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, true);
561                         } else {
562                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
563                             new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
564                             new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
565                             if (new_glyph.width == 0)
566                                 new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, false);
567                                 // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
568                         }
569                         if (new_span.direction == RIGHT_TO_LEFT) {
570                             // pango wanted to give us glyphs in visual order but we refused, so we need to work
571                             // out where the cluster start is ourselves
572                             double cluster_width = 0.0;
573                             for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
574                                 if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
575                                     break;
576                                 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
577                                     cluster_width += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[rtl_index].glyph, true);
578                                 else
579                                     cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
580                             }
581                             new_glyph.x -= cluster_width;
582                         }
583                         _flow._glyphs.push_back(new_glyph);
585                         // create the Layout::Character(s)
586                         double advance_width = new_glyph.width;
587                         unsigned end_byte;
588                         if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
589                             end_byte = it_span->start.iter_span->text_bytes;
590                         else {
591                             // output chars for the whole cluster that is commenced by this glyph
592                             if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
593                                 int next_cluster_glyph_index = glyph_index + 1;
594                                 while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
595                                        && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
596                                     next_cluster_glyph_index++;
597                                 if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
598                                     end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
599                                 else
600                                     end_byte = it_span->start.iter_span->text_bytes;
601                             } else
602                                 end_byte = char_byte;    // don't output any chars if we're not at the start of a cluster
603                         }
604                         while (char_byte < end_byte) {
605                             Layout::Character new_character;
606                             new_character.in_span = _flow._spans.size();
607                             new_character.x = x_in_span;
608                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
609                             new_character.in_glyph = _flow._glyphs.size() - 1;
610                             _flow._characters.push_back(new_character);
611                             if (new_character.char_attributes.is_white)
612                                 advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace;    // justification
613                             if (new_character.char_attributes.is_cursor_position)
614                                 advance_width += text_source->style->letter_spacing.computed;
615                             iter_source_text++;
616                             char_index_in_unbroken_span++;
617                             char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
618                             glyph_rotate = 0.0;
619                         }
621                         advance_width *= direction_sign;
622                         if (new_span.direction != para.direction) {
623                             counter_directional_width_remaining -= advance_width;
624                             x -= advance_width;
625                             x_in_span -= advance_width;
626                         } else {
627                             x += advance_width;
628                             x_in_span += advance_width;
629                         }
630                     }
631                 } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
632                     x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
633                 }
635                 new_span.x_end = new_span.x_start + x_in_span;
636                 _flow._spans.push_back(new_span);
637                 previous_direction = new_span.direction;
638             }
639             // end adding spans to the list, on to the next chunk...
640         }
641         TRACE(("output done\n"));
642     }
644 /* *********************************************************************************************************/
645 //                             Setup and top-level functions
647     /** initialises the ScanlineMaker for the first shape in the flow, or
648     the infinite version if we're not doing wrapping. */
649     void _createFirstScanlineMaker()
650     {
651         _current_shape_index = 0;
652         if (_flow._input_wrap_shapes.empty()) {
653             // create the special no-wrapping infinite scanline maker
654             double initial_x = 0, initial_y = 0;
655             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
656             if (!text_source->x.empty())
657                 initial_x = text_source->x.front().computed;
658             if (!text_source->y.empty())
659                 initial_y = text_source->y.front().computed;
660             _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
661             TRACE(("  wrapping disabled\n"));
662         }
663         else {
664             _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
665             TRACE(("  begin wrap shape 0\n"));
666         }
667     }
669 public:
670     Calculator(Layout *text_flow)
671         : _flow(*text_flow) {}
673     bool calculate();
674 };
676 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
677  * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
678  * that does delete or Unref.
679  *
680  * I suggest using the garbage collector to manage deletion.
681  */
682 void Layout::Calculator::InputItemInfo::free()
684     if (sub_flow) {
685         delete sub_flow;
686         sub_flow = NULL;
687     }
690 void Layout::Calculator::PangoItemInfo::free()
692     if (item) {
693         pango_item_free(item);
694         item = NULL;
695     }
696     if (font) {
697         font->Unref();
698         font = NULL;
699     }
702 void Layout::Calculator::UnbrokenSpanPosition::increment()
704     gchar const *text_base = &*iter_span->input_stream_first_character.base();
705     char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
706     char_index++;
707     if (char_byte == iter_span->text_bytes) {
708         iter_span++;
709         char_index = char_byte = 0;
710     }
713 void Layout::Calculator::BrokenSpan::setZero()
715     end = start;
716     width = 0.0;
717     whitespace_count = 0;
718     end_glyph_index = start_glyph_index = 0;
719     ends_with_whitespace = false;
720     each_whitespace_width = 0.0;
723 template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
725     for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
726         it->free();
727     }
728     seq.clear();
731 void Layout::Calculator::ParagraphInfo::free()
733     free_sequence(input_items);
734     free_sequence(pango_items);
735     free_sequence(unbroken_spans);
738 ///**
739 // * For sections of text with a block-progression different to the rest
740 // * of the flow, the best thing to do is to detect them in advance and
741 // * create child TextFlow objects with just the rotated text. In the
742 // * parent we then effectively use ARBITRARY_GAP fields during the
743 // * flowing (because we don't allow wrapping when the block-progression
744 // * changes) and copy the actual text in during the output phase.
745 // *
746 // * NB: this code not enabled yet.
747 // */
748 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
749 //{
750 //    Direction prev_block_progression = _block_progression;
751 //    int run_start_input_index = para->first_input_index;
752 //
753 //    para->free_sequence(para->input_items);
754 //    for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
755 //        InputItemInfo input_item;
756 //
757 //        input_item.in_sub_flow = false;
758 //        input_item.sub_flow = NULL;
759 //        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
760 //            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
761 //            if (   control_code->code == SHAPE_BREAK
762 //                   || control_code->code == PARAGRAPH_BREAK)
763 //                break;                                    // stop at the end of the paragraph
764 //            // all other control codes we'll pick up later
765 //
766 //        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
767 //            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
768 //            Direction this_block_progression = text_source->styleGetBlockProgression();
769 //            if (this_block_progression != prev_block_progression) {
770 //                if (prev_block_progression != _block_progression) {
771 //                    // need to back up so that control codes belong outside the block-progression change
772 //                    int run_end_input_index = input_index - 1;
773 //                    while (run_end_input_index > run_start_input_index
774 //                           && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
775 //                        run_end_input_index--;
776 //                    // now create the sub-flow
777 //                    input_item.sub_flow = new Layout;
778 //                    for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
779 //                        input_item.in_sub_flow = true;
780 //                        if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
781 //                            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
782 //                            input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
783 //                        } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
784 //                            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
785 //                            input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source_cookie, NULL, 0, text_source->text_begin, text_source->text_end);
786 //                            Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
787 //                            sub_flow_text_source->x = text_source->x;    // this is easier than going via optionalattrs for the appendText() call
788 //                            sub_flow_text_source->y = text_source->y;    // should these actually be allowed anyway? You'll almost never get the results you expect
789 //                            sub_flow_text_source->dx = text_source->dx;  // (not that it's very clear what you should expect, anyway)
790 //                            sub_flow_text_source->dy = text_source->dy;
791 //                            sub_flow_text_source->rotate = text_source->rotate;
792 //                        }
793 //                    }
794 //                    input_item.sub_flow->calculateFlow();
795 //                }
796 //                run_start_input_index = input_index;
797 //            }
798 //            prev_block_progression = this_block_progression;
799 //        }
800 //        para->input_items.push_back(input_item);
801 //    }
802 //}
804 /**
805  * Take all the text from \a _para.first_input_index to the end of the
806  * paragraph and stitch it together so that pango_itemize() can be called on
807  * the whole thing.
808  *
809  * Input: para.first_input_index.
810  * Output: para.direction, para.pango_items, para.char_attributes.
811  */
812 void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
814     Glib::ustring para_text;
815     PangoAttrList *attributes_list;
816     unsigned input_index;
818     para->free_sequence(para->pango_items);
819     para->char_attributes.clear();
821     TRACE(("itemizing para, first input %d\n", para->first_input_index));
823     attributes_list = pango_attr_list_new();
824     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
825         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
826             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
827             if (   control_code->code == SHAPE_BREAK
828                    || control_code->code == PARAGRAPH_BREAK)
829                 break;                                    // stop at the end of the paragraph
830             // all other control codes we'll pick up later
832         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
833             Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
835             // create the font_instance
836             font_instance *font = text_source->styleGetFontInstance();
837             if (font == NULL)
838                 continue;  // bad news: we'll have to ignore all this text because we know of no font to render it
840             PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
841             attribute_font_description->start_index = para_text.bytes();
842             para_text.append(&*text_source->text_begin.base(), text_source->text_length);     // build the combined text
843             attribute_font_description->end_index = para_text.bytes();
844             pango_attr_list_insert(attributes_list, attribute_font_description);
845             // ownership of attribute is assumed by the list
846         }
847     }
849     TRACE(("whole para: \"%s\"\n", para_text.data()));
850     TRACE(("%d input sources used\n", input_index - para->first_input_index));
852     // do the pango_itemize()
853     GList *pango_items_glist = NULL;
854     if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
855         Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
856         if (text_source->style->direction.set) {
857             PangoDirection pango_direction = (PangoDirection)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_pango_direction, sizeof(enum_convert_spstyle_direction_to_pango_direction)/sizeof(enum_convert_spstyle_direction_to_pango_direction[0]));
858             pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
859             para->direction = (Layout::Direction)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_my_direction, sizeof(enum_convert_spstyle_direction_to_my_direction)/sizeof(enum_convert_spstyle_direction_to_my_direction[0]));
860         }
861     }
862     if (pango_items_glist == NULL) {  // no direction specified, guess it
863         pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
865         // I think according to the css spec this is wrong and we're never allowed to guess the directionality
866         // of a paragraph. Need to talk to an rtl speaker.
867         if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
868         else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
869     }
870     pango_attr_list_unref(attributes_list);
872     // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
873     para->pango_items.reserve(g_list_length(pango_items_glist));
874     TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
875     for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
876         PangoItemInfo new_item;
877         new_item.item = (PangoItem*)current_pango_item->data;
878         PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
879         new_item.font = (font_factory::Default())->Face(font_description);
880         pango_font_description_free(font_description);   // Face() makes a copy
881         para->pango_items.push_back(new_item);
882     }
883     g_list_free(pango_items_glist);
885     // and get the character attributes on everything
886     para->char_attributes.resize(para_text.length() + 1);
887     pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
889     TRACE(("end para itemize, direction = %d\n", para->direction));
892 /**
893  * Gets the ascent, descent and leading for a font and the alteration that has to be performed
894  * according to the value specified by the line-height css property. The result of multiplying
895  * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
896  * section 10.8.
897  */
898 void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
899                                                 SPStyle const *style, LineHeight *line_height,
900                                                 double *line_height_multiplier)
902     if (font == NULL) {
903         line_height->setZero();
904         *line_height_multiplier = 1.0;
905     }
906     font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
907     *line_height *= font_size;
909     // yet another borked SPStyle member that we're going to have to fix ourselves
910     for ( ; ; ) {
911         if (style->line_height.set && !style->line_height.inherit) {
912             if (style->line_height.normal)
913                 break;
914             switch (style->line_height.unit) {
915                 case SP_CSS_UNIT_NONE:
916                     *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
917                     return;
918                 case SP_CSS_UNIT_EX:
919                     *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
920                     // 0.5 is an approximation of the x-height. Fixme.
921                     return;
922                 case SP_CSS_UNIT_EM:
923                 case SP_CSS_UNIT_PERCENT:
924                     *line_height_multiplier = style->line_height.value * font_size / line_height->total();
925                     return;
926                 default:  // absolute values
927                     *line_height_multiplier = style->line_height.computed / line_height->total();
928                     return;
929             }
930             break;
931         }
932         if (style->object->parent == NULL) break;
933         style = style->object->parent->style;
934         if (style == NULL) break;
935     }
936     *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
939 /**
940  * Split the paragraph into spans. Also call pango_shape() on them.
941  *
942  * Input: para->first_input_index, para->pango_items
943  * Output: para->spans
944  * Returns: the index of the beginning of the following paragraph in _flow._input_stream
945  */
946 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
948     unsigned pango_item_index = 0;
949     unsigned char_index_in_para = 0;
950     unsigned byte_index_in_para = 0;
951     unsigned input_index;
953     TRACE(("build spans\n"));
954     para->free_sequence(para->unbroken_spans);
956     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
957         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
958             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
959             if (   control_code->code == SHAPE_BREAK
960                    || control_code->code == PARAGRAPH_BREAK)
961                 break;                                    // stop at the end of the paragraph
962             else if (control_code->code == ARBITRARY_GAP) {
963                 UnbrokenSpan new_span;
964                 new_span.pango_item_index = -1;
965                 new_span.input_index = input_index;
966                 new_span.line_height.ascent = control_code->ascent;
967                 new_span.line_height.descent = control_code->descent;
968                 new_span.line_height.leading = 0.0;
969                 new_span.text_bytes = 0;
970                 new_span.char_index_in_para = char_index_in_para;
971                 para->unbroken_spans.push_back(new_span);
972                 TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
973             }
974         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
975             Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
976             unsigned char_index_in_source = 0;
978             unsigned span_start_byte_in_source = 0;
979             // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
980             for ( ; ; ) {
981                 /* we need to change spans at every change of PangoItem, source stream change,
982                    or change in one of the attributes altering position/rotation. */
984                 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
985                                                     ? 0
986                                                     : ( para->pango_items[pango_item_index].item->offset
987                                                         + para->pango_items[pango_item_index].item->length
988                                                         - byte_index_in_para ) );
989                 unsigned const text_source_bytes = ( text_source->text_end.base()
990                                                      - text_source->text_begin.base()
991                                                      - span_start_byte_in_source );
992                 UnbrokenSpan new_span;
993                 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
994                 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
995                 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
996                 new_span.input_index = input_index;
998                 // cut at <tspan> attribute changes as well
999                 new_span.x._set = false;
1000                 new_span.y._set = false;
1001                 new_span.dx._set = false;
1002                 new_span.dy._set = false;
1003                 new_span.rotate._set = false;
1004                 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1005                     if (text_source->x.size()  > char_index_in_source) new_span.x  = text_source->x[char_index_in_source];
1006                     if (text_source->y.size()  > char_index_in_source) new_span.y  = text_source->y[char_index_in_source];
1007                     if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
1008                     if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
1009                 } else {
1010                     if (text_source->x.size()  > char_index_in_source) new_span.y  = text_source->x[char_index_in_source];
1011                     if (text_source->y.size()  > char_index_in_source) new_span.x  = text_source->y[char_index_in_source];
1012                     if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
1013                     if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
1014                 }
1015                 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1016                 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1017                     // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1018                     // so that the top of the letters is at zero, not the baseline
1019                     new_span.y = 0.0;
1020                 }
1021                 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1022                 iter_text++;
1023                 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1024                     if (iter_text >= text_source->text_end) break;
1025                     if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1026                     if (   i >= text_source->x.size() && i >= text_source->y.size()
1027                         && i >= text_source->dx.size() && i >= text_source->dy.size()
1028                         && i >= text_source->rotate.size()) break;
1029                     if (   (text_source->x.size()  > i && text_source->x[i]._set)
1030                         || (text_source->y.size()  > i && text_source->y[i]._set)
1031                         || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1032                         || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1033                         || (text_source->rotate.size() > i && text_source->rotate[i]._set
1034                             && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1035                         new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1036                         break;
1037                     }
1038                 }
1040                 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1041                 new_span.font_size = text_source->styleComputeFontSize();
1042                 if (new_span.text_bytes) {
1043                     int const original_bidi_level = para->pango_items[pango_item_index].item->analysis.level;
1044                     para->pango_items[pango_item_index].item->analysis.level = 0;
1045                     // pango_shape() will reorder glyphs in rtl sections which messes us up because
1046                     // the svg spec requires us to draw glyphs in character order
1047                     new_span.glyph_string = pango_glyph_string_new();
1048                     /* Some assertions intended to help diagnose bug #1277746. */
1049                     g_assert( 0 < new_span.text_bytes );
1050                     g_assert( span_start_byte_in_source < text_source->text->bytes() );
1051                     g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1052                     g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1053                               == NULL );
1054                     pango_shape(text_source->text->data() + span_start_byte_in_source,
1055                                 new_span.text_bytes,
1056                                 &para->pango_items[pango_item_index].item->analysis,
1057                                 new_span.glyph_string);
1058                     para->pango_items[pango_item_index].item->analysis.level = original_bidi_level;
1059                     new_span.pango_item_index = pango_item_index;
1060                     _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1061                     // TODO: metrics for vertical text
1062                     TRACE(("add text span %d \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
1063                     TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
1064                 } else {
1065                     // if there's no text we still need to initialise the styles
1066                     new_span.pango_item_index = -1;
1067                     font_instance *font = text_source->styleGetFontInstance();
1068                     if (font) {
1069                         _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1070                         font->Unref();
1071                     } else {
1072                         new_span.line_height.setZero();
1073                         new_span.line_height_multiplier = 1.0;
1074                     }
1075                     TRACE(("add style init span %d\n", para->unbroken_spans.size()));
1076                 }
1077                 para->unbroken_spans.push_back(new_span);
1079                 // calculations for moving to the next UnbrokenSpan
1080                 byte_index_in_para += new_span.text_bytes;
1081                 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1083                 if (new_span.text_bytes >= pango_item_bytes) {   // end of pango item
1084                     pango_item_index++;
1085                     if (pango_item_index == para->pango_items.size()) break;  // end of paragraph
1086                 }
1087                 if (new_span.text_bytes == text_source_bytes)
1088                     break;    // end of source
1089                 // else <tspan> attribute changed
1090                 span_start_byte_in_source += new_span.text_bytes;
1091             }
1092             char_index_in_para += char_index_in_source;
1093         }
1094     }
1095     TRACE(("end build spans\n"));
1096     return input_index;
1099 /**
1100  * Reinitialises the variables required on completion of one shape and
1101  * moving on to the next. Returns false if there are no more shapes to wrap
1102  * in to.
1103  */
1104 bool Layout::Calculator::_goToNextWrapShape()
1106     delete _scanline_maker;
1107     _scanline_maker = NULL;
1108     _current_shape_index++;
1109     if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
1110     _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1111     TRACE(("begin wrap shape %d\n", _current_shape_index));
1112     return true;
1115 /**
1116  * Given \a para filled in and \a start_span_pos set, keeps trying to
1117  * find somewhere it can fit the next line of text. The process of finding
1118  * the text that fits will involve creating one or more entries in
1119  * \a chunk_info describing the bounds of the fitted text and several
1120  * bits of information that will prove useful when we come to output the
1121  * line to #_flow. Returns with \a start_span_pos set to the end of the
1122  * text that was fitted, \a chunk_info completely filled out and
1123  * \a line_height set to the largest line box on the line. The return
1124  * value is false only if we've run out of shapes to wrap inside (and
1125  * hence couldn't create any chunks).
1126  */
1127 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
1128                                             UnbrokenSpanPosition *start_span_pos,
1129                                             std::vector<ChunkInfo> *chunk_info,
1130                                             LineHeight *line_height)
1132     // init the initial line_height
1133     if (start_span_pos->iter_span == para.unbroken_spans.end()) {
1134         if (_flow._spans.empty()) {
1135             // empty first para: create a font for the sole purpose of measuring it
1136             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1137             font_instance *font = text_source->styleGetFontInstance();
1138             if (font) {
1139                 double font_size = text_source->styleComputeFontSize();
1140                 double multiplier;
1141                 _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
1142                 font->Unref();
1143                 *line_height *= multiplier;
1144                 _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
1145             }
1146         }
1147         // else empty subsequent para: keep the old line height
1148     } else {
1149         if (_flow._input_wrap_shapes.empty()) {
1150             // if we're not wrapping set the line_height big and negative so we can use negative line height
1151             line_height->ascent = -1.0e10;
1152             line_height->descent = -1.0e10;
1153             line_height->leading = -1.0e10;
1154         }
1155         else
1156             line_height->setZero();
1157     }
1159     UnbrokenSpanPosition span_pos;
1160     for( ; ; ) {
1161         std::vector<ScanlineMaker::ScanRun> scan_runs;
1162         scan_runs = _scanline_maker->makeScanline(*line_height);
1163         while (scan_runs.empty()) {
1164             if (!_goToNextWrapShape()) return false;  // no more shapes to wrap in to
1165             scan_runs = _scanline_maker->makeScanline(*line_height);
1166         }
1168         TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
1169         chunk_info->clear();
1170         chunk_info->reserve(scan_runs.size());
1171         if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1172         unsigned scan_run_index;
1173         span_pos = *start_span_pos;
1174         for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1175             if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
1176                 break;
1177             if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
1178                 span_pos = chunk_info->back().broken_spans.back().end;
1179         }
1180         if (scan_run_index == scan_runs.size()) break;  // ie when buildChunksInScanRun() succeeded
1181     }
1182     *start_span_pos = span_pos;
1183     return true;
1186 /**
1187  * Given a scan run and a first character, append one or more chunks to
1188  * the \a chunk_info vector that describe all the spans and other detail
1189  * necessary to output the greatest amount of text that will fit on this scan
1190  * line (greedy line breaking algorithm). Each chunk contains one or more
1191  * BrokenSpan structures that link back to UnbrokenSpan structures that link
1192  * to the text itself. Normally there will be either one or zero (if the
1193  * scanrun is too short to fit any text) chunk added to \a chunk_info by
1194  * each call to this method, but we will add more than one if an x or y
1195  * attribute has been set on a tspan. \a line_height must be set on input,
1196  * and if it needs to be made larger and the #_scanline_maker can't do
1197  * an in-situ resize then it will be set to the required value and the
1198  * method will return false.
1199  */
1200 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
1201                                                UnbrokenSpanPosition const &start_span_pos,
1202                                                ScanlineMaker::ScanRun const &scan_run,
1203                                                std::vector<ChunkInfo> *chunk_info,
1204                                                LineHeight *line_height) const
1206     ChunkInfo new_chunk;
1207     new_chunk.text_width = 0.0;
1208     new_chunk.whitespace_count = 0;
1209     new_chunk.scanrun_width = scan_run.width();
1210     new_chunk.x = scan_run.x_start;
1212     // we haven't done anything yet so the last valid break position is the beginning
1213     BrokenSpan last_span_at_break, last_span_at_emergency_break;
1214     last_span_at_break.start = start_span_pos;
1215     last_span_at_break.setZero();
1216     last_span_at_emergency_break.start = start_span_pos;
1217     last_span_at_emergency_break.setZero();
1219     TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1220     BrokenSpan new_span;
1221     new_span.end = start_span_pos;
1222     while (new_span.end.iter_span != para.unbroken_spans.end()) {    // this loops once for each UnbrokenSpan
1224         new_span.start = new_span.end;
1226         // force a chunk change at x or y attribute change
1227         if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1229             if (new_span.start.iter_span != start_span_pos.iter_span)
1230                 chunk_info->push_back(new_chunk);
1232             new_chunk.x += new_chunk.text_width;
1233             new_chunk.text_width = 0.0;
1234             new_chunk.whitespace_count = 0;
1235             new_chunk.broken_spans.clear();
1236             if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1237             // y doesn't need to be done until output time
1238         }
1240         // see if this span is too tall to fit on the current line
1241         LineHeight total_height = new_span.start.iter_span->line_height;
1242         total_height *= new_span.start.iter_span->line_height_multiplier;
1243         /* floating point 80-bit/64-bit rounding problems require epsilon. See
1244            discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1245         if (   total_height.ascent  > line_height->ascent  + FLT_EPSILON
1246                || total_height.descent > line_height->descent + FLT_EPSILON
1247                || total_height.leading > line_height->leading + FLT_EPSILON) {
1248             line_height->max(total_height);
1249             if (!_scanline_maker->canExtendCurrentScanline(*line_height))
1250                 return false;
1251         }
1253         bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1255         new_chunk.text_width += new_span.width;
1256         new_chunk.whitespace_count += new_span.whitespace_count;
1257         new_chunk.broken_spans.push_back(new_span);   // if !span_fitted we'll correct ourselves below
1259         if (!span_fitted) break;
1261         if (new_span.end.iter_span == para.unbroken_spans.end()) {
1262             last_span_at_break = new_span;
1263             break;
1264         }
1265     }
1267     TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1268     chunk_info->push_back(new_chunk);
1270     if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
1271         /* **non-SVG spec bit**: See bug #1191102
1272            If the user types a very long line with no spaces, the way the spec
1273            is written at the moment means that when the length of the text
1274            exceeds the available width of all remaining areas, the text is
1275            completely hidden. This condition alters that behaviour so that if
1276            the length of the line is greater than four times the line-height
1277            and there are no spaces, it'll be emergency-wrapped at the last
1278            character. One could read the SVG Tiny 1.2 draft as permitting this
1279            sort of behaviour, but it's still a bit dodgy. The hard-coding of
1280            4x is not nice, either. */
1281         last_span_at_break = last_span_at_emergency_break;
1282     }
1284     if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
1285         // need to back out spans until we come to the one with the last break in it
1286         while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
1287             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1288             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1289             chunk_info->back().broken_spans.pop_back();
1290             if (chunk_info->back().broken_spans.empty())
1291                 chunk_info->pop_back();
1292         }
1293         if (!chunk_info->empty()) {
1294             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1295             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1296             if (last_span_at_break.start == last_span_at_break.end) {
1297                 chunk_info->back().broken_spans.pop_back();   // last break was at an existing boundary
1298                 if (chunk_info->back().broken_spans.empty())
1299                     chunk_info->pop_back();
1300             } else {
1301                 chunk_info->back().broken_spans.back() = last_span_at_break;
1302                 chunk_info->back().text_width += last_span_at_break.width;
1303                 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
1304             }
1305             TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
1306         }
1307     }
1309     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
1310         // for justification we need to discard space occupied by the single whitespace at the end of the chunk
1311         chunk_info->back().broken_spans.back().ends_with_whitespace = false;
1312         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1313         chunk_info->back().broken_spans.back().whitespace_count--;
1314         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1315         chunk_info->back().whitespace_count--;
1316     }
1318     return true;
1321 /** The management function to start the whole thing off. */
1322 bool Layout::Calculator::calculate()
1324     if (_flow._input_stream.empty())
1325         return false;
1326     g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
1327     if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
1328         return false;
1330     TRACE(("begin calculateFlow()\n"));
1332     _flow._clearOutputObjects();
1334     _pango_context = (font_factory::Default())->fontContext;
1335     _font_factory_size_multiplier = (font_factory::Default())->fontSize;
1337     _block_progression = _flow._blockProgression();
1338     _y_offset = 0.0;
1339     _createFirstScanlineMaker();
1341     ParagraphInfo para;
1342     LineHeight line_height;     // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
1343     for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
1344         // jump to the next wrap shape if this is a SHAPE_BREAK control code
1345         if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
1346             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
1347             if (control_code->code == SHAPE_BREAK) {
1348                 TRACE(("shape break control code\n"));
1349                 if (!_goToNextWrapShape()) break;
1350                 continue;
1351             }
1352         }
1353         if (_scanline_maker == NULL)
1354             break;       // we're trying to flow past the last wrap shape
1356         _buildPangoItemizationForPara(&para);
1357         unsigned para_end_input_index = _buildSpansForPara(&para);
1359         if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
1360             para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
1361         else
1362             para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
1364         TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
1365         Layout::Paragraph new_paragraph;
1366         new_paragraph.base_direction = para.direction;
1367         new_paragraph.alignment = para.alignment;
1368         _flow._paragraphs.push_back(new_paragraph);
1370         // start scanning lines
1371         UnbrokenSpanPosition span_pos;
1372         span_pos.iter_span = para.unbroken_spans.begin();
1373         span_pos.char_byte = 0;
1374         span_pos.char_index = 0;
1376         do {   // for each line in the paragraph
1377             TRACE(("begin line\n"));
1378             std::vector<ChunkInfo> line_chunk_info;
1379             if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
1380                 break;   // out of shapes to wrap in to
1382             _outputLine(para, line_height, line_chunk_info);
1383             _scanline_maker->completeLine();
1384         } while (span_pos.iter_span != para.unbroken_spans.end());
1386         TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
1387         if (_scanline_maker != NULL) {
1388             bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
1389             if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
1390                 || para_end_input_index + 1 < _flow._input_stream.size()) {
1391                 // we need a span just for the para if it's either an empty last para or a break in the middle
1392                 Layout::Span new_span;
1393                 if (_flow._spans.empty()) {
1394                     new_span.font = NULL;
1395                     new_span.font_size = line_height.ascent + line_height.descent;
1396                     new_span.line_height = line_height;
1397                     new_span.x_end = 0.0;
1398                 } else {
1399                     new_span = _flow._spans.back();
1400                     if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
1401                         new_span.x_end = 0.0;
1402                 }
1403                 new_span.in_chunk = _flow._chunks.size() - 1;
1404                 if (new_span.font)
1405                     new_span.font->Ref();
1406                 new_span.x_start = new_span.x_end;
1407                 new_span.baseline_shift = 0.0;
1408                 new_span.direction = para.direction;
1409                 new_span.block_progression = _block_progression;
1410                 if (para_end_input_index == _flow._input_stream.size())
1411                     new_span.in_input_stream_item = _flow._input_stream.size() - 1;
1412                 else
1413                     new_span.in_input_stream_item = para_end_input_index;
1414                 _flow._spans.push_back(new_span);
1415             }
1416             if (para_end_input_index + 1 < _flow._input_stream.size()) {
1417                 // we've got to add an invisible character between paragraphs so that we can position iterators
1418                 // (and hence cursors) both before and after the paragraph break
1419                 Layout::Character new_character;
1420                 new_character.in_span = _flow._spans.size() - 1;
1421                 new_character.char_attributes.is_line_break = 1;
1422                 new_character.char_attributes.is_mandatory_break = 1;
1423                 new_character.char_attributes.is_char_break = 1;
1424                 new_character.char_attributes.is_white = 1;
1425                 new_character.char_attributes.is_cursor_position = 1;
1426                 new_character.char_attributes.is_word_start = 0;
1427                 new_character.char_attributes.is_word_end = 1;
1428                 new_character.char_attributes.is_sentence_start = 0;
1429                 new_character.char_attributes.is_sentence_end = 1;
1430                 new_character.char_attributes.is_sentence_boundary = 1;
1431                 new_character.char_attributes.backspace_deletes_character = 1;
1432                 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
1433                 new_character.in_glyph = -1;
1434                 _flow._characters.push_back(new_character);
1435             }
1436         }
1437         para.free();
1438         para.first_input_index = para_end_input_index + 1;
1439     }
1441     para.free();
1442     if (_scanline_maker)
1443         delete _scanline_maker;
1445     return true;
1448 void Layout::_calculateCursorShapeForEmpty()
1450     _empty_cursor_shape.position = NR::Point(0, 0);
1451     _empty_cursor_shape.height = 0.0;
1452     _empty_cursor_shape.rotation = 0.0;
1453     if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
1454         return;
1456     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
1458     font_instance *font = text_source->styleGetFontInstance();
1459     double font_size = text_source->styleComputeFontSize();
1460     double caret_slope_run = 0.0, caret_slope_rise = 1.0;
1461     LineHeight line_height;
1462     if (font) {
1463         const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
1464         font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
1465         line_height *= font_size;
1466         font->Unref();
1467     } else {
1468         line_height.ascent = font_size * 0.85;      // random guesses
1469         line_height.descent = font_size * 0.15;
1470         line_height.leading = 0.0;
1471     }
1472     double caret_slope = atan2(caret_slope_run, caret_slope_rise);
1473     _empty_cursor_shape.height = font_size / cos(caret_slope);
1474     _empty_cursor_shape.rotation = caret_slope;
1476     if (_input_wrap_shapes.empty()) {
1477         _empty_cursor_shape.position = NR::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
1478                                                  text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
1479     } else {
1480         Direction block_progression = text_source->styleGetBlockProgression();
1481         ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
1482         std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
1483         if (!scan_runs.empty()) {
1484             if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
1485                 _empty_cursor_shape.position = NR::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
1486             else
1487                 _empty_cursor_shape.position = NR::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
1488         }
1489     }
1492 bool Layout::calculateFlow()
1494     bool result = Calculator(this).calculate();
1495     if (_characters.empty())
1496         _calculateCursorShapeForEmpty();
1497     return result;
1500 }//namespace Text
1501 }//namespace Inkscape
1504 /*
1505   Local Variables:
1506   mode:c++
1507   c-file-style:"stroustrup"
1508   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1509   indent-tabs-mode:nil
1510   fill-column:99
1511   End:
1512 */
1513 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :