Code

rearrange effect submenus, rename some effects, fix capitalization
[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                             && it_span + 1 != it_chunk->broken_spans.end()
528                             && glyph_index + 1 != it_span->end_glyph_index) {
529                             // if we're looking at a soft hyphen and it's not the last glyph in the
530                             // chunk we don't draw the glyph but we still need to add to _characters
531                             Layout::Character new_character;
532                             new_character.in_span = _flow._spans.size();     // the span hasn't been added yet, so no -1
533                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
534                             new_character.in_glyph = -1;
535                             _flow._characters.push_back(new_character);
536                             iter_source_text++;
537                             char_index_in_unbroken_span++;
538                             while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
539                                    && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
540                                 glyph_index++;
541                             glyph_index--;
542                             continue;
543                         }
545                         // create the Layout::Glyph
546                         Layout::Glyph new_glyph;
547                         new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
548                         new_glyph.in_character = cluster_start_char_index;
549                         new_glyph.rotation = glyph_rotate;
551                         /* put something like this back in when we do glyph-rotation-horizontal/vertical
552                         if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
553                             new_glyph.x += new_span.line_height.ascent;
554                             new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
555                             new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
556                         } else */
558                         if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
559                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
560                             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;
561                             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);
562                         } else {
563                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
564                             new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
565                             new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
566                             if (new_glyph.width == 0)
567                                 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);
568                                 // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
569                         }
570                         if (new_span.direction == RIGHT_TO_LEFT) {
571                             // pango wanted to give us glyphs in visual order but we refused, so we need to work
572                             // out where the cluster start is ourselves
573                             double cluster_width = 0.0;
574                             for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
575                                 if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
576                                     break;
577                                 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
578                                     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);
579                                 else
580                                     cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
581                             }
582                             new_glyph.x -= cluster_width;
583                         }
584                         _flow._glyphs.push_back(new_glyph);
586                         // create the Layout::Character(s)
587                         double advance_width = new_glyph.width;
588                         unsigned end_byte;
589                         if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
590                             end_byte = it_span->start.iter_span->text_bytes;
591                         else {
592                             // output chars for the whole cluster that is commenced by this glyph
593                             if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
594                                 int next_cluster_glyph_index = glyph_index + 1;
595                                 while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
596                                        && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
597                                     next_cluster_glyph_index++;
598                                 if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
599                                     end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
600                                 else
601                                     end_byte = it_span->start.iter_span->text_bytes;
602                             } else
603                                 end_byte = char_byte;    // don't output any chars if we're not at the start of a cluster
604                         }
605                         while (char_byte < end_byte) {
606                             Layout::Character new_character;
607                             new_character.in_span = _flow._spans.size();
608                             new_character.x = x_in_span;
609                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
610                             new_character.in_glyph = _flow._glyphs.size() - 1;
611                             _flow._characters.push_back(new_character);
612                             if (new_character.char_attributes.is_white)
613                                 advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace;    // justification
614                             if (new_character.char_attributes.is_cursor_position)
615                                 advance_width += text_source->style->letter_spacing.computed;
616                             iter_source_text++;
617                             char_index_in_unbroken_span++;
618                             char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
619                             glyph_rotate = 0.0;
620                         }
622                         advance_width *= direction_sign;
623                         if (new_span.direction != para.direction) {
624                             counter_directional_width_remaining -= advance_width;
625                             x -= advance_width;
626                             x_in_span -= advance_width;
627                         } else {
628                             x += advance_width;
629                             x_in_span += advance_width;
630                         }
631                     }
632                 } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
633                     x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
634                 }
636                 new_span.x_end = new_span.x_start + x_in_span;
637                 _flow._spans.push_back(new_span);
638                 previous_direction = new_span.direction;
639             }
640             // end adding spans to the list, on to the next chunk...
641         }
642         TRACE(("output done\n"));
643     }
645 /* *********************************************************************************************************/
646 //                             Setup and top-level functions
648     /** initialises the ScanlineMaker for the first shape in the flow, or
649     the infinite version if we're not doing wrapping. */
650     void _createFirstScanlineMaker()
651     {
652         _current_shape_index = 0;
653         if (_flow._input_wrap_shapes.empty()) {
654             // create the special no-wrapping infinite scanline maker
655             double initial_x = 0, initial_y = 0;
656             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
657             if (!text_source->x.empty())
658                 initial_x = text_source->x.front().computed;
659             if (!text_source->y.empty())
660                 initial_y = text_source->y.front().computed;
661             _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
662             TRACE(("  wrapping disabled\n"));
663         }
664         else {
665             _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
666             TRACE(("  begin wrap shape 0\n"));
667         }
668     }
670 public:
671     Calculator(Layout *text_flow)
672         : _flow(*text_flow) {}
674     bool calculate();
675 };
677 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
678  * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
679  * that does delete or Unref.
680  *
681  * I suggest using the garbage collector to manage deletion.
682  */
683 void Layout::Calculator::InputItemInfo::free()
685     if (sub_flow) {
686         delete sub_flow;
687         sub_flow = NULL;
688     }
691 void Layout::Calculator::PangoItemInfo::free()
693     if (item) {
694         pango_item_free(item);
695         item = NULL;
696     }
697     if (font) {
698         font->Unref();
699         font = NULL;
700     }
703 void Layout::Calculator::UnbrokenSpanPosition::increment()
705     gchar const *text_base = &*iter_span->input_stream_first_character.base();
706     char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
707     char_index++;
708     if (char_byte == iter_span->text_bytes) {
709         iter_span++;
710         char_index = char_byte = 0;
711     }
714 void Layout::Calculator::BrokenSpan::setZero()
716     end = start;
717     width = 0.0;
718     whitespace_count = 0;
719     end_glyph_index = start_glyph_index = 0;
720     ends_with_whitespace = false;
721     each_whitespace_width = 0.0;
724 template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
726     for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
727         it->free();
728     }
729     seq.clear();
732 void Layout::Calculator::ParagraphInfo::free()
734     free_sequence(input_items);
735     free_sequence(pango_items);
736     free_sequence(unbroken_spans);
739 ///**
740 // * For sections of text with a block-progression different to the rest
741 // * of the flow, the best thing to do is to detect them in advance and
742 // * create child TextFlow objects with just the rotated text. In the
743 // * parent we then effectively use ARBITRARY_GAP fields during the
744 // * flowing (because we don't allow wrapping when the block-progression
745 // * changes) and copy the actual text in during the output phase.
746 // *
747 // * NB: this code not enabled yet.
748 // */
749 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
750 //{
751 //    Direction prev_block_progression = _block_progression;
752 //    int run_start_input_index = para->first_input_index;
753 //
754 //    para->free_sequence(para->input_items);
755 //    for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
756 //        InputItemInfo input_item;
757 //
758 //        input_item.in_sub_flow = false;
759 //        input_item.sub_flow = NULL;
760 //        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
761 //            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
762 //            if (   control_code->code == SHAPE_BREAK
763 //                   || control_code->code == PARAGRAPH_BREAK)
764 //                break;                                    // stop at the end of the paragraph
765 //            // all other control codes we'll pick up later
766 //
767 //        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
768 //            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
769 //            Direction this_block_progression = text_source->styleGetBlockProgression();
770 //            if (this_block_progression != prev_block_progression) {
771 //                if (prev_block_progression != _block_progression) {
772 //                    // need to back up so that control codes belong outside the block-progression change
773 //                    int run_end_input_index = input_index - 1;
774 //                    while (run_end_input_index > run_start_input_index
775 //                           && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
776 //                        run_end_input_index--;
777 //                    // now create the sub-flow
778 //                    input_item.sub_flow = new Layout;
779 //                    for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
780 //                        input_item.in_sub_flow = true;
781 //                        if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
782 //                            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
783 //                            input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
784 //                        } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
785 //                            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
786 //                            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);
787 //                            Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
788 //                            sub_flow_text_source->x = text_source->x;    // this is easier than going via optionalattrs for the appendText() call
789 //                            sub_flow_text_source->y = text_source->y;    // should these actually be allowed anyway? You'll almost never get the results you expect
790 //                            sub_flow_text_source->dx = text_source->dx;  // (not that it's very clear what you should expect, anyway)
791 //                            sub_flow_text_source->dy = text_source->dy;
792 //                            sub_flow_text_source->rotate = text_source->rotate;
793 //                        }
794 //                    }
795 //                    input_item.sub_flow->calculateFlow();
796 //                }
797 //                run_start_input_index = input_index;
798 //            }
799 //            prev_block_progression = this_block_progression;
800 //        }
801 //        para->input_items.push_back(input_item);
802 //    }
803 //}
805 /**
806  * Take all the text from \a _para.first_input_index to the end of the
807  * paragraph and stitch it together so that pango_itemize() can be called on
808  * the whole thing.
809  *
810  * Input: para.first_input_index.
811  * Output: para.direction, para.pango_items, para.char_attributes.
812  */
813 void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
815     Glib::ustring para_text;
816     PangoAttrList *attributes_list;
817     unsigned input_index;
819     para->free_sequence(para->pango_items);
820     para->char_attributes.clear();
822     TRACE(("itemizing para, first input %d\n", para->first_input_index));
824     attributes_list = pango_attr_list_new();
825     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
826         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
827             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
828             if (   control_code->code == SHAPE_BREAK
829                    || control_code->code == PARAGRAPH_BREAK)
830                 break;                                    // stop at the end of the paragraph
831             // all other control codes we'll pick up later
833         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
834             Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
836             // create the font_instance
837             font_instance *font = text_source->styleGetFontInstance();
838             if (font == NULL)
839                 continue;  // bad news: we'll have to ignore all this text because we know of no font to render it
841             PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
842             attribute_font_description->start_index = para_text.bytes();
843             para_text.append(&*text_source->text_begin.base(), text_source->text_length);     // build the combined text
844             attribute_font_description->end_index = para_text.bytes();
845             pango_attr_list_insert(attributes_list, attribute_font_description);
846             // ownership of attribute is assumed by the list
847         }
848     }
850     TRACE(("whole para: \"%s\"\n", para_text.data()));
851     TRACE(("%d input sources used\n", input_index - para->first_input_index));
853     // do the pango_itemize()
854     GList *pango_items_glist = NULL;
855     if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
856         Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
857         if (text_source->style->direction.set) {
858             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]));
859             pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
860             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]));
861         }
862     }
863     if (pango_items_glist == NULL) {  // no direction specified, guess it
864         pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
866         // I think according to the css spec this is wrong and we're never allowed to guess the directionality
867         // of a paragraph. Need to talk to an rtl speaker.
868         if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
869         else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
870     }
871     pango_attr_list_unref(attributes_list);
873     // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
874     para->pango_items.reserve(g_list_length(pango_items_glist));
875     TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
876     for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
877         PangoItemInfo new_item;
878         new_item.item = (PangoItem*)current_pango_item->data;
879         PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
880         new_item.font = (font_factory::Default())->Face(font_description);
881         pango_font_description_free(font_description);   // Face() makes a copy
882         para->pango_items.push_back(new_item);
883     }
884     g_list_free(pango_items_glist);
886     // and get the character attributes on everything
887     para->char_attributes.resize(para_text.length() + 1);
888     pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
890     TRACE(("end para itemize, direction = %d\n", para->direction));
893 /**
894  * Gets the ascent, descent and leading for a font and the alteration that has to be performed
895  * according to the value specified by the line-height css property. The result of multiplying
896  * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
897  * section 10.8.
898  */
899 void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
900                                                 SPStyle const *style, LineHeight *line_height,
901                                                 double *line_height_multiplier)
903     if (font == NULL) {
904         line_height->setZero();
905         *line_height_multiplier = 1.0;
906     }
907     font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
908     *line_height *= font_size;
910     // yet another borked SPStyle member that we're going to have to fix ourselves
911     for ( ; ; ) {
912         if (style->line_height.set && !style->line_height.inherit) {
913             if (style->line_height.normal)
914                 break;
915             switch (style->line_height.unit) {
916                 case SP_CSS_UNIT_NONE:
917                     *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
918                     return;
919                 case SP_CSS_UNIT_EX:
920                     *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
921                     // 0.5 is an approximation of the x-height. Fixme.
922                     return;
923                 case SP_CSS_UNIT_EM:
924                 case SP_CSS_UNIT_PERCENT:
925                     *line_height_multiplier = style->line_height.value * font_size / line_height->total();
926                     return;
927                 default:  // absolute values
928                     *line_height_multiplier = style->line_height.computed / line_height->total();
929                     return;
930             }
931             break;
932         }
933         if (style->object->parent == NULL) break;
934         style = style->object->parent->style;
935         if (style == NULL) break;
936     }
937     *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
940 /**
941  * Split the paragraph into spans. Also call pango_shape() on them.
942  *
943  * Input: para->first_input_index, para->pango_items
944  * Output: para->spans
945  * Returns: the index of the beginning of the following paragraph in _flow._input_stream
946  */
947 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
949     unsigned pango_item_index = 0;
950     unsigned char_index_in_para = 0;
951     unsigned byte_index_in_para = 0;
952     unsigned input_index;
954     TRACE(("build spans\n"));
955     para->free_sequence(para->unbroken_spans);
957     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
958         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
959             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
960             if (   control_code->code == SHAPE_BREAK
961                    || control_code->code == PARAGRAPH_BREAK)
962                 break;                                    // stop at the end of the paragraph
963             else if (control_code->code == ARBITRARY_GAP) {
964                 UnbrokenSpan new_span;
965                 new_span.pango_item_index = -1;
966                 new_span.input_index = input_index;
967                 new_span.line_height.ascent = control_code->ascent;
968                 new_span.line_height.descent = control_code->descent;
969                 new_span.line_height.leading = 0.0;
970                 new_span.text_bytes = 0;
971                 new_span.char_index_in_para = char_index_in_para;
972                 para->unbroken_spans.push_back(new_span);
973                 TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
974             }
975         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
976             Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
977             unsigned char_index_in_source = 0;
979             unsigned span_start_byte_in_source = 0;
980             // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
981             for ( ; ; ) {
982                 /* we need to change spans at every change of PangoItem, source stream change,
983                    or change in one of the attributes altering position/rotation. */
985                 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
986                                                     ? 0
987                                                     : ( para->pango_items[pango_item_index].item->offset
988                                                         + para->pango_items[pango_item_index].item->length
989                                                         - byte_index_in_para ) );
990                 unsigned const text_source_bytes = ( text_source->text_end.base()
991                                                      - text_source->text_begin.base()
992                                                      - span_start_byte_in_source );
993                 UnbrokenSpan new_span;
994                 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
995                 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
996                 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
997                 new_span.input_index = input_index;
999                 // cut at <tspan> attribute changes as well
1000                 new_span.x._set = false;
1001                 new_span.y._set = false;
1002                 new_span.dx._set = false;
1003                 new_span.dy._set = false;
1004                 new_span.rotate._set = false;
1005                 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1006                     if (text_source->x.size()  > char_index_in_source) new_span.x  = text_source->x[char_index_in_source];
1007                     if (text_source->y.size()  > char_index_in_source) new_span.y  = text_source->y[char_index_in_source];
1008                     if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
1009                     if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
1010                 } else {
1011                     if (text_source->x.size()  > char_index_in_source) new_span.y  = text_source->x[char_index_in_source];
1012                     if (text_source->y.size()  > char_index_in_source) new_span.x  = text_source->y[char_index_in_source];
1013                     if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
1014                     if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
1015                 }
1016                 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1017                 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1018                     // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1019                     // so that the top of the letters is at zero, not the baseline
1020                     new_span.y = 0.0;
1021                 }
1022                 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1023                 iter_text++;
1024                 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1025                     if (iter_text >= text_source->text_end) break;
1026                     if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1027                     if (   i >= text_source->x.size() && i >= text_source->y.size()
1028                         && i >= text_source->dx.size() && i >= text_source->dy.size()
1029                         && i >= text_source->rotate.size()) break;
1030                     if (   (text_source->x.size()  > i && text_source->x[i]._set)
1031                         || (text_source->y.size()  > i && text_source->y[i]._set)
1032                         || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1033                         || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1034                         || (text_source->rotate.size() > i && text_source->rotate[i]._set
1035                             && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1036                         new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1037                         break;
1038                     }
1039                 }
1041                 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1042                 new_span.font_size = text_source->styleComputeFontSize();
1043                 if (new_span.text_bytes) {
1044                     int const original_bidi_level = para->pango_items[pango_item_index].item->analysis.level;
1045                     para->pango_items[pango_item_index].item->analysis.level = 0;
1046                     // pango_shape() will reorder glyphs in rtl sections which messes us up because
1047                     // the svg spec requires us to draw glyphs in character order
1048                     new_span.glyph_string = pango_glyph_string_new();
1049                     /* Some assertions intended to help diagnose bug #1277746. */
1050                     g_assert( 0 < new_span.text_bytes );
1051                     g_assert( span_start_byte_in_source < text_source->text->bytes() );
1052                     g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1053                     g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1054                               == NULL );
1055                     pango_shape(text_source->text->data() + span_start_byte_in_source,
1056                                 new_span.text_bytes,
1057                                 &para->pango_items[pango_item_index].item->analysis,
1058                                 new_span.glyph_string);
1059                     para->pango_items[pango_item_index].item->analysis.level = original_bidi_level;
1060                     new_span.pango_item_index = pango_item_index;
1061                     _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1062                     // TODO: metrics for vertical text
1063                     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()));
1064                     TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
1065                 } else {
1066                     // if there's no text we still need to initialise the styles
1067                     new_span.pango_item_index = -1;
1068                     font_instance *font = text_source->styleGetFontInstance();
1069                     if (font) {
1070                         _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1071                         font->Unref();
1072                     } else {
1073                         new_span.line_height.setZero();
1074                         new_span.line_height_multiplier = 1.0;
1075                     }
1076                     TRACE(("add style init span %d\n", para->unbroken_spans.size()));
1077                 }
1078                 para->unbroken_spans.push_back(new_span);
1080                 // calculations for moving to the next UnbrokenSpan
1081                 byte_index_in_para += new_span.text_bytes;
1082                 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1084                 if (new_span.text_bytes >= pango_item_bytes) {   // end of pango item
1085                     pango_item_index++;
1086                     if (pango_item_index == para->pango_items.size()) break;  // end of paragraph
1087                 }
1088                 if (new_span.text_bytes == text_source_bytes)
1089                     break;    // end of source
1090                 // else <tspan> attribute changed
1091                 span_start_byte_in_source += new_span.text_bytes;
1092             }
1093             char_index_in_para += char_index_in_source;
1094         }
1095     }
1096     TRACE(("end build spans\n"));
1097     return input_index;
1100 /**
1101  * Reinitialises the variables required on completion of one shape and
1102  * moving on to the next. Returns false if there are no more shapes to wrap
1103  * in to.
1104  */
1105 bool Layout::Calculator::_goToNextWrapShape()
1107     delete _scanline_maker;
1108     _scanline_maker = NULL;
1109     _current_shape_index++;
1110     if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
1111     _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1112     TRACE(("begin wrap shape %d\n", _current_shape_index));
1113     return true;
1116 /**
1117  * Given \a para filled in and \a start_span_pos set, keeps trying to
1118  * find somewhere it can fit the next line of text. The process of finding
1119  * the text that fits will involve creating one or more entries in
1120  * \a chunk_info describing the bounds of the fitted text and several
1121  * bits of information that will prove useful when we come to output the
1122  * line to #_flow. Returns with \a start_span_pos set to the end of the
1123  * text that was fitted, \a chunk_info completely filled out and
1124  * \a line_height set to the largest line box on the line. The return
1125  * value is false only if we've run out of shapes to wrap inside (and
1126  * hence couldn't create any chunks).
1127  */
1128 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
1129                                             UnbrokenSpanPosition *start_span_pos,
1130                                             std::vector<ChunkInfo> *chunk_info,
1131                                             LineHeight *line_height)
1133     // init the initial line_height
1134     if (start_span_pos->iter_span == para.unbroken_spans.end()) {
1135         if (_flow._spans.empty()) {
1136             // empty first para: create a font for the sole purpose of measuring it
1137             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1138             font_instance *font = text_source->styleGetFontInstance();
1139             if (font) {
1140                 double font_size = text_source->styleComputeFontSize();
1141                 double multiplier;
1142                 _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
1143                 font->Unref();
1144                 *line_height *= multiplier;
1145                 _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
1146             }
1147         }
1148         // else empty subsequent para: keep the old line height
1149     } else {
1150         if (_flow._input_wrap_shapes.empty()) {
1151             // if we're not wrapping set the line_height big and negative so we can use negative line height
1152             line_height->ascent = -1.0e10;
1153             line_height->descent = -1.0e10;
1154             line_height->leading = -1.0e10;
1155         }
1156         else
1157             line_height->setZero();
1158     }
1160     UnbrokenSpanPosition span_pos;
1161     for( ; ; ) {
1162         std::vector<ScanlineMaker::ScanRun> scan_runs;
1163         scan_runs = _scanline_maker->makeScanline(*line_height);
1164         while (scan_runs.empty()) {
1165             if (!_goToNextWrapShape()) return false;  // no more shapes to wrap in to
1166             scan_runs = _scanline_maker->makeScanline(*line_height);
1167         }
1169         TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
1170         chunk_info->clear();
1171         chunk_info->reserve(scan_runs.size());
1172         if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1173         unsigned scan_run_index;
1174         span_pos = *start_span_pos;
1175         for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1176             if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
1177                 break;
1178             if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
1179                 span_pos = chunk_info->back().broken_spans.back().end;
1180         }
1181         if (scan_run_index == scan_runs.size()) break;  // ie when buildChunksInScanRun() succeeded
1182     }
1183     *start_span_pos = span_pos;
1184     return true;
1187 /**
1188  * Given a scan run and a first character, append one or more chunks to
1189  * the \a chunk_info vector that describe all the spans and other detail
1190  * necessary to output the greatest amount of text that will fit on this scan
1191  * line (greedy line breaking algorithm). Each chunk contains one or more
1192  * BrokenSpan structures that link back to UnbrokenSpan structures that link
1193  * to the text itself. Normally there will be either one or zero (if the
1194  * scanrun is too short to fit any text) chunk added to \a chunk_info by
1195  * each call to this method, but we will add more than one if an x or y
1196  * attribute has been set on a tspan. \a line_height must be set on input,
1197  * and if it needs to be made larger and the #_scanline_maker can't do
1198  * an in-situ resize then it will be set to the required value and the
1199  * method will return false.
1200  */
1201 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
1202                                                UnbrokenSpanPosition const &start_span_pos,
1203                                                ScanlineMaker::ScanRun const &scan_run,
1204                                                std::vector<ChunkInfo> *chunk_info,
1205                                                LineHeight *line_height) const
1207     ChunkInfo new_chunk;
1208     new_chunk.text_width = 0.0;
1209     new_chunk.whitespace_count = 0;
1210     new_chunk.scanrun_width = scan_run.width();
1211     new_chunk.x = scan_run.x_start;
1213     // we haven't done anything yet so the last valid break position is the beginning
1214     BrokenSpan last_span_at_break, last_span_at_emergency_break;
1215     last_span_at_break.start = start_span_pos;
1216     last_span_at_break.setZero();
1217     last_span_at_emergency_break.start = start_span_pos;
1218     last_span_at_emergency_break.setZero();
1220     TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1221     BrokenSpan new_span;
1222     new_span.end = start_span_pos;
1223     while (new_span.end.iter_span != para.unbroken_spans.end()) {    // this loops once for each UnbrokenSpan
1225         new_span.start = new_span.end;
1227         // force a chunk change at x or y attribute change
1228         if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1230             if (new_span.start.iter_span != start_span_pos.iter_span)
1231                 chunk_info->push_back(new_chunk);
1233             new_chunk.x += new_chunk.text_width;
1234             new_chunk.text_width = 0.0;
1235             new_chunk.whitespace_count = 0;
1236             new_chunk.broken_spans.clear();
1237             if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1238             // y doesn't need to be done until output time
1239         }
1241         // see if this span is too tall to fit on the current line
1242         LineHeight total_height = new_span.start.iter_span->line_height;
1243         total_height *= new_span.start.iter_span->line_height_multiplier;
1244         /* floating point 80-bit/64-bit rounding problems require epsilon. See
1245            discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1246         if (   total_height.ascent  > line_height->ascent  + FLT_EPSILON
1247                || total_height.descent > line_height->descent + FLT_EPSILON
1248                || total_height.leading > line_height->leading + FLT_EPSILON) {
1249             line_height->max(total_height);
1250             if (!_scanline_maker->canExtendCurrentScanline(*line_height))
1251                 return false;
1252         }
1254         bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1256         new_chunk.text_width += new_span.width;
1257         new_chunk.whitespace_count += new_span.whitespace_count;
1258         new_chunk.broken_spans.push_back(new_span);   // if !span_fitted we'll correct ourselves below
1260         if (!span_fitted) break;
1262         if (new_span.end.iter_span == para.unbroken_spans.end()) {
1263             last_span_at_break = new_span;
1264             break;
1265         }
1266     }
1268     TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1269     chunk_info->push_back(new_chunk);
1271     if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
1272         /* **non-SVG spec bit**: See bug #1191102
1273            If the user types a very long line with no spaces, the way the spec
1274            is written at the moment means that when the length of the text
1275            exceeds the available width of all remaining areas, the text is
1276            completely hidden. This condition alters that behaviour so that if
1277            the length of the line is greater than four times the line-height
1278            and there are no spaces, it'll be emergency-wrapped at the last
1279            character. One could read the SVG Tiny 1.2 draft as permitting this
1280            sort of behaviour, but it's still a bit dodgy. The hard-coding of
1281            4x is not nice, either. */
1282         last_span_at_break = last_span_at_emergency_break;
1283     }
1285     if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
1286         // need to back out spans until we come to the one with the last break in it
1287         while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
1288             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1289             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1290             chunk_info->back().broken_spans.pop_back();
1291             if (chunk_info->back().broken_spans.empty())
1292                 chunk_info->pop_back();
1293         }
1294         if (!chunk_info->empty()) {
1295             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1296             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1297             if (last_span_at_break.start == last_span_at_break.end) {
1298                 chunk_info->back().broken_spans.pop_back();   // last break was at an existing boundary
1299                 if (chunk_info->back().broken_spans.empty())
1300                     chunk_info->pop_back();
1301             } else {
1302                 chunk_info->back().broken_spans.back() = last_span_at_break;
1303                 chunk_info->back().text_width += last_span_at_break.width;
1304                 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
1305             }
1306             TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
1307         }
1308     }
1310     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
1311         // for justification we need to discard space occupied by the single whitespace at the end of the chunk
1312         chunk_info->back().broken_spans.back().ends_with_whitespace = false;
1313         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1314         chunk_info->back().broken_spans.back().whitespace_count--;
1315         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1316         chunk_info->back().whitespace_count--;
1317     }
1319     return true;
1322 /** The management function to start the whole thing off. */
1323 bool Layout::Calculator::calculate()
1325     if (_flow._input_stream.empty())
1326         return false;
1327     g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
1328     if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
1329         return false;
1331     TRACE(("begin calculateFlow()\n"));
1333     _flow._clearOutputObjects();
1335     _pango_context = (font_factory::Default())->fontContext;
1336     _font_factory_size_multiplier = (font_factory::Default())->fontSize;
1338     _block_progression = _flow._blockProgression();
1339     _y_offset = 0.0;
1340     _createFirstScanlineMaker();
1342     ParagraphInfo para;
1343     LineHeight line_height;     // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
1344     for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
1345         // jump to the next wrap shape if this is a SHAPE_BREAK control code
1346         if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
1347             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
1348             if (control_code->code == SHAPE_BREAK) {
1349                 TRACE(("shape break control code\n"));
1350                 if (!_goToNextWrapShape()) break;
1351                 continue;
1352             }
1353         }
1354         if (_scanline_maker == NULL)
1355             break;       // we're trying to flow past the last wrap shape
1357         _buildPangoItemizationForPara(&para);
1358         unsigned para_end_input_index = _buildSpansForPara(&para);
1360         if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
1361             para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
1362         else
1363             para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
1365         TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
1366         Layout::Paragraph new_paragraph;
1367         new_paragraph.base_direction = para.direction;
1368         new_paragraph.alignment = para.alignment;
1369         _flow._paragraphs.push_back(new_paragraph);
1371         // start scanning lines
1372         UnbrokenSpanPosition span_pos;
1373         span_pos.iter_span = para.unbroken_spans.begin();
1374         span_pos.char_byte = 0;
1375         span_pos.char_index = 0;
1377         do {   // for each line in the paragraph
1378             TRACE(("begin line\n"));
1379             std::vector<ChunkInfo> line_chunk_info;
1380             if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
1381                 break;   // out of shapes to wrap in to
1383             _outputLine(para, line_height, line_chunk_info);
1384             _scanline_maker->completeLine();
1385         } while (span_pos.iter_span != para.unbroken_spans.end());
1387         TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
1388         if (_scanline_maker != NULL) {
1389             bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
1390             if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
1391                 || para_end_input_index + 1 < _flow._input_stream.size()) {
1392                 // we need a span just for the para if it's either an empty last para or a break in the middle
1393                 Layout::Span new_span;
1394                 if (_flow._spans.empty()) {
1395                     new_span.font = NULL;
1396                     new_span.font_size = line_height.ascent + line_height.descent;
1397                     new_span.line_height = line_height;
1398                     new_span.x_end = 0.0;
1399                 } else {
1400                     new_span = _flow._spans.back();
1401                     if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
1402                         new_span.x_end = 0.0;
1403                 }
1404                 new_span.in_chunk = _flow._chunks.size() - 1;
1405                 if (new_span.font)
1406                     new_span.font->Ref();
1407                 new_span.x_start = new_span.x_end;
1408                 new_span.baseline_shift = 0.0;
1409                 new_span.direction = para.direction;
1410                 new_span.block_progression = _block_progression;
1411                 if (para_end_input_index == _flow._input_stream.size())
1412                     new_span.in_input_stream_item = _flow._input_stream.size() - 1;
1413                 else
1414                     new_span.in_input_stream_item = para_end_input_index;
1415                 _flow._spans.push_back(new_span);
1416             }
1417             if (para_end_input_index + 1 < _flow._input_stream.size()) {
1418                 // we've got to add an invisible character between paragraphs so that we can position iterators
1419                 // (and hence cursors) both before and after the paragraph break
1420                 Layout::Character new_character;
1421                 new_character.in_span = _flow._spans.size() - 1;
1422                 new_character.char_attributes.is_line_break = 1;
1423                 new_character.char_attributes.is_mandatory_break = 1;
1424                 new_character.char_attributes.is_char_break = 1;
1425                 new_character.char_attributes.is_white = 1;
1426                 new_character.char_attributes.is_cursor_position = 1;
1427                 new_character.char_attributes.is_word_start = 0;
1428                 new_character.char_attributes.is_word_end = 1;
1429                 new_character.char_attributes.is_sentence_start = 0;
1430                 new_character.char_attributes.is_sentence_end = 1;
1431                 new_character.char_attributes.is_sentence_boundary = 1;
1432                 new_character.char_attributes.backspace_deletes_character = 1;
1433                 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
1434                 new_character.in_glyph = -1;
1435                 _flow._characters.push_back(new_character);
1436             }
1437         }
1438         para.free();
1439         para.first_input_index = para_end_input_index + 1;
1440     }
1442     para.free();
1443     if (_scanline_maker)
1444         delete _scanline_maker;
1446     return true;
1449 void Layout::_calculateCursorShapeForEmpty()
1451     _empty_cursor_shape.position = NR::Point(0, 0);
1452     _empty_cursor_shape.height = 0.0;
1453     _empty_cursor_shape.rotation = 0.0;
1454     if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
1455         return;
1457     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
1459     font_instance *font = text_source->styleGetFontInstance();
1460     double font_size = text_source->styleComputeFontSize();
1461     double caret_slope_run = 0.0, caret_slope_rise = 1.0;
1462     LineHeight line_height;
1463     if (font) {
1464         const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
1465         font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
1466         line_height *= font_size;
1467         font->Unref();
1468     } else {
1469         line_height.ascent = font_size * 0.85;      // random guesses
1470         line_height.descent = font_size * 0.15;
1471         line_height.leading = 0.0;
1472     }
1473     double caret_slope = atan2(caret_slope_run, caret_slope_rise);
1474     _empty_cursor_shape.height = font_size / cos(caret_slope);
1475     _empty_cursor_shape.rotation = caret_slope;
1477     if (_input_wrap_shapes.empty()) {
1478         _empty_cursor_shape.position = NR::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
1479                                                  text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
1480     } else {
1481         Direction block_progression = text_source->styleGetBlockProgression();
1482         ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
1483         std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
1484         if (!scan_runs.empty()) {
1485             if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
1486                 _empty_cursor_shape.position = NR::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
1487             else
1488                 _empty_cursor_shape.position = NR::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
1489         }
1490     }
1493 bool Layout::calculateFlow()
1495     bool result = Calculator(this).calculate();
1496     if (_characters.empty())
1497         _calculateCursorShapeForEmpty();
1498     return result;
1501 }//namespace Text
1502 }//namespace Inkscape
1505 /*
1506   Local Variables:
1507   mode:c++
1508   c-file-style:"stroustrup"
1509   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1510   indent-tabs-mode:nil
1511   fill-column:99
1512   End:
1513 */
1514 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :