Code

Add support for baseline-shift attribute (all possible values).
[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 IFTRACE(_code)
24 #define TRACE(_args) IFTRACE(g_print _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.
81     To do non-flow text layout, only the first "y" attribute is normally used. If there is only one
82     "y" attribute in a <tspan> other than the first <tspan>, it is ignored. This allows Inkscape to
83     insert a new line anywhere. On output, the Inkscape determined "y" is written out so other SVG
84     viewers know where to place the <tspans>.
85     */
87     double _y_offset;
89     /** to stop pango from hinting its output, the font factory creates all fonts very large.
90     All numbers returned from pango have to be divided by this number \em and divided by
91     PANGO_SCALE. See font_factory::font_factory(). */
92     double _font_factory_size_multiplier;
94     /** Temporary storage associated with each item in Layout::_input_stream. */
95     struct InputItemInfo {
96         bool in_sub_flow;
97         Layout *sub_flow;    // this is only set for the first input item in a sub-flow
99         InputItemInfo() : in_sub_flow(false), sub_flow(NULL) {}
100         void free();
101     };
103     /** Temporary storage associated with each item returned by the call to
104         pango_itemize(). */
105     struct PangoItemInfo {
106         PangoItem *item;
107         font_instance *font;
109         PangoItemInfo() : item(NULL), font(NULL) {}
110         void free();
111     };
113     /** These spans have approximately the same definition as that used for
114     Layout::Span (constant font, direction, etc), except that they are from
115     before we have located the line breaks, so bear no relation to chunks.
116     They are guaranteed to be in at most one PangoItem (spans with no text in
117     them will not have an associated PangoItem), exactly one input source and
118     will only have one change of x, y, dx, dy or rotate attribute, which will
119     be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
120     BrokenSpan. */
121     struct UnbrokenSpan {
122         PangoGlyphString *glyph_string;
123         int pango_item_index;    /// index into _para.pango_items, or -1 if this is style only
124         unsigned input_index;         /// index into Layout::_input_stream
125         Glib::ustring::const_iterator input_stream_first_character;
126         double font_size;
127         LineHeight line_height;         /// This is not the CSS line-height attribute!
128         double line_height_multiplier;  /// calculated from the font-height css property
129         double baseline_shift;          /// calculated from the baseline-shift css property
130         unsigned text_bytes;
131         unsigned char_index_in_para;    /// the index of the first character in this span in the paragraph, for looking up char_attributes
132         SVGLength x, y, dx, dy, rotate;  // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
134         UnbrokenSpan() : glyph_string(NULL) {}
135         void free() {if (glyph_string) pango_glyph_string_free(glyph_string); glyph_string = NULL;}
136     };
138     /** a useful little iterator for moving char-by-char across spans. */
139     struct UnbrokenSpanPosition {
140         std::vector<UnbrokenSpan>::iterator iter_span;
141         unsigned char_byte;
142         unsigned char_index;
144         void increment();   ///< Step forward by one character.
146         inline bool operator== (UnbrokenSpanPosition const &other) const
147             {return char_byte == other.char_byte && iter_span == other.iter_span;}
148         inline bool operator!= (UnbrokenSpanPosition const &other) const
149             {return char_byte != other.char_byte || iter_span != other.iter_span;}
150     };
152     /** The line breaking algorithm will convert each UnbrokenSpan into one
153     or more of these. A BrokenSpan will never cross a chunk boundary, c.f.
154     UnbrokenSpan. */
155     struct BrokenSpan {
156         UnbrokenSpanPosition start;
157         UnbrokenSpanPosition end;    // the end of this will always be the same as the start of the next
158         unsigned start_glyph_index;
159         unsigned end_glyph_index;
160         double width;
161         unsigned whitespace_count;
162         bool ends_with_whitespace;
163         double each_whitespace_width;
164         double letter_spacing; // Save so we can subtract from width at end of line (for center justification)
165         double word_spacing;
166         void setZero();
167     };
169     /** The definition of a chunk used here is the same as that used in Layout:
170     A collection of contiguous broken spans on the same line. (One chunk per line
171     unless shape splits line into several sections... then one chunk per section. */
172     struct ChunkInfo {
173         std::vector<BrokenSpan> broken_spans;
174         double scanrun_width;
175         double text_width;       ///< Total width used by the text (excluding justification).
176         double x;
177         int whitespace_count;
178     };
180     /** Used to provide storage for anything that applies to the current
181     paragraph only. Since we're only processing one paragraph at a time,
182     there's only one instantiation of this struct, on the stack of
183     calculate(). */
184     struct ParagraphInfo {
185         unsigned first_input_index;      ///< Index into Layout::_input_stream.
186         Direction direction;
187         Alignment alignment;
188         std::vector<InputItemInfo> input_items;
189         std::vector<PangoItemInfo> pango_items;
190         std::vector<PangoLogAttr> char_attributes;    ///< For every character in the paragraph.
191         std::vector<UnbrokenSpan> unbroken_spans;
193         template<typename T> static void free_sequence(T &seq);
194         void free();
195     };
197 /* *********************************************************************************************************/
198 //                       Initialisation of ParagraphInfo structure
201 #if 0 /* unused */
202     void _initialiseInputItems(ParagraphInfo *para) const;
203 #endif
205     void _buildPangoItemizationForPara(ParagraphInfo *para) const;
207     static void _computeFontLineHeight(font_instance *font, double font_size,
208                                        SPStyle const *style, LineHeight *line_height,
209                                        double *line_height_multiplier);
211     unsigned _buildSpansForPara(ParagraphInfo *para) const;
213 /* *********************************************************************************************************/
214 //                             Per-line functions
217     bool _goToNextWrapShape();
219     bool _findChunksForLine(ParagraphInfo const &para, UnbrokenSpanPosition *start_span_pos,
220                             std::vector<ChunkInfo> *chunk_info, LineHeight *line_height);
222     static inline PangoLogAttr const &_charAttributes(ParagraphInfo const &para,
223                                                       UnbrokenSpanPosition const &span_pos)
224     {
225         return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
226     }
228     bool _buildChunksInScanRun(ParagraphInfo const &para,
229                                UnbrokenSpanPosition const &start_span_pos,
230                                ScanlineMaker::ScanRun const &scan_run,
231                                std::vector<ChunkInfo> *chunk_info,
232                                LineHeight *line_height) const;
234     /** computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
235     and outputs its vital statistics into the other fields of \a span.
236     Measuring will stop if maximum_width is reached and in that case the
237     function will return false. In other cases where a line break must be
238     done immediately the function will also return false. On return
239     \a last_break_span will contain the vital statistics for the span only
240     up to the last line breaking change. If there are no line breaking
241     characters in the span then \a last_break_span will not be altered.
242     Similarly, \a last_emergency_break_span will contain the vital
243     statistics for the span up to the last inter-character boundary,
244     or will be unaltered if there is none. */
245     bool _measureUnbrokenSpan(ParagraphInfo const &para, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const
246     {
247         span->setZero();
249         if (span->start.iter_span->dx._set && span->start.char_byte == 0)
250             span->width += span->start.iter_span->dx.computed;
252         if (span->start.iter_span->pango_item_index == -1) {
253             // if this is a style-only span there's no text in it
254             // so we don't need to do very much at all
255             span->end.iter_span++;
256             return true;
257         }
259         if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
260             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
261             if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
262                 *last_emergency_break_span = *last_break_span = *span;
263                 return false;
264             }
265             if (control_code->code == ARBITRARY_GAP) {
266                 if (span->width + control_code->width > maximum_width)
267                     return false;
268                 TRACE(("fitted control code, width = %f\n", control_code->width));
269                 span->width += control_code->width;
270                 span->end.increment();
271             }
272             return true;
274         }
276         if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
277             return true;  // never happens
279         InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
281         if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
282             // TODO: block-progression altered in the middle
283             // Measure the precomputed flow from para.input_items
284             span->end.iter_span++;  // for now, skip to the next span
285             return true;
286         }
288         // a normal span going with a normal block-progression
289         double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
290         double soft_hyphen_glyph_width = 0.0;
291         bool soft_hyphen_in_word = false;
292         bool is_soft_hyphen = false;
293         IFTRACE(int char_count = 0);
295         // if we're not at the start of the span we need to pre-init glyph_index
296         span->start_glyph_index = 0;
297         while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
298                && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
299             span->start_glyph_index++;
300         span->end_glyph_index = span->start_glyph_index;
302         // go char-by-char summing the width, while keeping track of the previous break point
303         do {
304             PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
306             if (char_attributes.is_mandatory_break && span->end != span->start) {
307                 *last_emergency_break_span = *last_break_span = *span;
308                 TRACE(("span %d end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
309                 return false;
310             }
312             if (char_attributes.is_line_break) {
313                 // a suitable position to break at, record where we are
314                 *last_emergency_break_span = *last_break_span = *span;
315                 if (soft_hyphen_in_word) {
316                     // if there was a previous soft hyphen we're not going to need it any more so we can remove it
317                     span->width -= soft_hyphen_glyph_width;
318                     if (!is_soft_hyphen)
319                         soft_hyphen_in_word = false;
320                 }
321             } else if (char_attributes.is_char_break) {
322                 *last_emergency_break_span = *span;
323             }
324             // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
326             // sum the glyph widths, letter spacing and word spacing to get the character width
327             double char_width = 0.0;
328             while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
329                    && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
330                 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
331                     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);
332                 else
333                     char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width;
334                 span->end_glyph_index++;
335             }
336             if (char_attributes.is_cursor_position)
337                 char_width += text_source->style->letter_spacing.computed;
338             if (char_attributes.is_white)
339                 char_width += text_source->style->word_spacing.computed;
340             span->width += char_width;
341             IFTRACE(char_count++);
343             if (char_attributes.is_white) {
344                 span->whitespace_count++;
345                 span->each_whitespace_width = char_width;
346             }
347             span->ends_with_whitespace = char_attributes.is_white;
349             is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
350             if (is_soft_hyphen)
351                 soft_hyphen_glyph_width = char_width;
353             span->end.increment();
355             // Width should not include letter_spacing (or word_spacing) after last letter at end of line.
356             // word_spacing is attached to white space that is already removed from line end (?)
357             double test_width = span->width - text_source->style->letter_spacing.computed;
359             // Save letter_spacing and word_spacing for subtraction later if span is last span in line.
360             span->letter_spacing = text_source->style->letter_spacing.computed;
361             span->word_spacing   = text_source->style->word_spacing.computed;
363             if (test_width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
364                 TRACE(("span %d exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
365                 return false;
366             }
368         } while (span->end.char_byte != 0);  // while we haven't wrapped to the next span
370         TRACE(("fitted span %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
371         return true;
372     }
374 /* *********************************************************************************************************/
375 //                             Per-line functions (output)
377     /** Uses the paragraph alignment and the chunk information to work out
378     where the actual left of the final chunk must be. Also sets
379     \a add_to_each_whitespace to be the amount of x to add at each
380     whitespace character to make full justification work. */
381     double _getChunkLeftWithAlignment(ParagraphInfo const &para, std::vector<ChunkInfo>::const_iterator it_chunk, double *add_to_each_whitespace) const
382     {
383         *add_to_each_whitespace = 0.0;
384         if (_flow._input_wrap_shapes.empty()) {
385             switch (para.alignment) {
386                 case FULL:
387                 case LEFT:
388                 default:
389                     return it_chunk->x;
390                 case RIGHT:
391                     return it_chunk->x - it_chunk->text_width;
392                 case CENTER:
393                     return it_chunk->x - it_chunk->text_width/ 2;
394             }
395         }
397         switch (para.alignment) {
398             case FULL:
399                 if (!it_chunk->broken_spans.empty()
400                     && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) {   // don't justify the last chunk in the para
401                     if (it_chunk->whitespace_count)
402                         *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
403                     //else
404                         //add_to_each_charspace = something
405                 }
406                 return it_chunk->x;
407             case LEFT:
408             default:
409                 return it_chunk->x;
410             case RIGHT:
411                 return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
412             case CENTER:
413                 return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
414         }
415     }
417     /** Once we've got here we have finished making changes to the line and
418     are ready to output the final result to #_flow. This method takes its
419     input parameters and does that.
420     */
421     void _outputLine(ParagraphInfo const &para, LineHeight const &line_height, std::vector<ChunkInfo> const &chunk_info)
422     {
423         TRACE(("Start _outputLine\n"));
424         if (chunk_info.empty()) {
425             TRACE(("line too short to fit anything on it, go to next\n"));
426             return;
427         }
429         // we've finished fiddling about with ascents and descents: create the output
430         TRACE(("found line fit; creating output\n"));
431         Layout::Line new_line;
432         new_line.in_paragraph = _flow._paragraphs.size() - 1;
433         new_line.baseline_y = _scanline_maker->yCoordinate() + line_height.ascent;
434         new_line.in_shape = _current_shape_index;
435         _flow._lines.push_back(new_line);
437         for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
439             double add_to_each_whitespace;
440             // add the chunk to the list
441             Layout::Chunk new_chunk;
442             new_chunk.in_line = _flow._lines.size() - 1;
443             TRACE(("  New chunk: in_line: %d\n", new_chunk.in_line));
444             new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
446             // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
448             // Comment added:  1 June 2010:
449             // The first line in a normal <text> object is placed by the read-in "y" value. The rest are
450             // determined by Inkscape. This is to allow insertation of new lines in the middle
451             // of a <text> object. New "y" values are then stored in each <tspan> that represents
452             // a new line. The line spacing should depend only on font-size and line-height (and not
453             // on any y-kerning). Line spacing is already handled by the calling routine. Note that
454             // this may render improperly any SVG with <tspan>s created/edited by other programs.
455             if (!it_chunk->broken_spans.empty()                               // Not empty paragraph
456                 && it_chunk->broken_spans.front().start.char_byte == 0 ) {    // Beginning of unbroken span
458                 // If empty or new line
459                 if( _flow._characters.empty() ||
460                     _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
462                     // If <tspan> "y" attribute is set, use it (initial "y" attributes in
463                     // <tspans> other than the first have already been stripped).
464                     if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
466                         // Use set "y" attribute
467                         new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
469                         // Save baseline
470                         _flow._lines.back().baseline_y = new_line.baseline_y;
472                         // Save new <tspan> y coordinate
473                         _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
475                     }
477                     // Reset relative y_offset ("dy" attribute is relative but should be reset at
478                     // the beginning of each <tspan>.)
479                     _y_offset = 0.0;
481                 }
482             }
483             _flow._chunks.push_back(new_chunk);
485             double x;
486             double direction_sign;
487             Direction previous_direction = para.direction;
488             double counter_directional_width_remaining = 0.0;
489             float glyph_rotate = 0.0;
490             if (para.direction == LEFT_TO_RIGHT) {
491                 direction_sign = +1.0;
492                 x = 0.0;
493             } else {
494                 direction_sign = -1.0;
495                 if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
496                     x = it_chunk->scanrun_width;
497                 else
498                     x = it_chunk->text_width;
499             }
501             for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
502                 // begin adding spans to the list
503                 UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
505                 if (it_span->start.char_byte == 0) {
506                     // Start of an unbroken span, we might have dx, dy or rotate still to process
507                     // (x and y are done per chunk)
508                     if (unbroken_span.dx._set) x += unbroken_span.dx.computed;
509                     if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
510                     if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
511                 }
513                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
514                     && unbroken_span.pango_item_index == -1) {
515                     // style only, nothing to output
516                     continue;
517                 }
519                 Layout::Span new_span;
520                 double x_in_span = 0.0;
522                 new_span.in_chunk = _flow._chunks.size() - 1;
523                 new_span.line_height = unbroken_span.line_height;
524                 new_span.in_input_stream_item = unbroken_span.input_index;
525                 new_span.baseline_shift = 0.0;
526                 new_span.block_progression = _block_progression;
527                 if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
528                     {
529                     new_span.font->Ref();
530                     new_span.font_size = unbroken_span.font_size;
531                     new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
532                     new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
533                 } else {  // a control code
534                     new_span.font = NULL;
535                     new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
536                     new_span.direction = para.direction;
537                 }
539                 if (new_span.direction == para.direction) {
540                     x -= counter_directional_width_remaining;
541                     counter_directional_width_remaining = 0.0;
542                 } else if (new_span.direction != previous_direction) {
543                     // measure width of spans we need to switch round
544                     counter_directional_width_remaining = 0.0;
545                     std::vector<BrokenSpan>::const_iterator it_following_span;
546                     for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
547                         Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
548                         if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
549                             if (it_following_span->start.iter_span->pango_item_index == -1) {   // when the span came from a control code
550                                 if (new_span.direction != para.direction) break;
551                             } else
552                                 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;
553                         }
554                         counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
555                     }
556                     x += counter_directional_width_remaining;
557                     counter_directional_width_remaining = 0.0;    // we want to go increasingly negative
558                 }
559                 new_span.x_start = x;
561                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
562                     // the span is set up, push the glyphs and chars
563                     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
564                     Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
565                     unsigned char_index_in_unbroken_span = it_span->start.char_index;
566                     unsigned cluster_start_char_index = _flow._characters.size();
567                     double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
569                     for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
570                         unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
571                         if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
572                             cluster_start_char_index = _flow._characters.size();
574                         if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
575                             && *iter_source_text == UNICODE_SOFT_HYPHEN
576                             && glyph_index + 1 != it_span->end_glyph_index) {
577                             // if we're looking at a soft hyphen and it's not the last glyph in the
578                             // chunk we don't draw the glyph but we still need to add to _characters
579                             Layout::Character new_character;
580                             new_character.in_span = _flow._spans.size();     // the span hasn't been added yet, so no -1
581                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
582                             new_character.in_glyph = -1;
583                             _flow._characters.push_back(new_character);
584                             iter_source_text++;
585                             char_index_in_unbroken_span++;
586                             while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
587                                    && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
588                                 glyph_index++;
589                             glyph_index--;
590                             continue;
591                         }
593                         // create the Layout::Glyph
594                         Layout::Glyph new_glyph;
595                         new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
596                         new_glyph.in_character = cluster_start_char_index;
597                         new_glyph.rotation = glyph_rotate;
599                         /* put something like this back in when we do glyph-rotation-horizontal/vertical
600                         if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
601                             new_glyph.x += new_span.line_height.ascent;
602                             new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
603                             new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
604                         } else */
606                         if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
607                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
608                             new_glyph.y = _y_offset -
609                                 unbroken_span.baseline_shift +
610                                 (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset -
611                                  unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
612                             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);
613                         } else {
614                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
615                             new_glyph.y = _y_offset -
616                                 unbroken_span.baseline_shift +
617                                 unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
618                             new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
619                             if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font))
620                                 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);
621                                 // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
622                         }
623                         if (new_span.direction == RIGHT_TO_LEFT) {
624                             // pango wanted to give us glyphs in visual order but we refused, so we need to work
625                             // out where the cluster start is ourselves
626                             double cluster_width = 0.0;
627                             for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
628                                 if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
629                                     break;
630                                 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
631                                     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);
632                                 else
633                                     cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
634                             }
635                             new_glyph.x -= cluster_width;
636                         }
637                         _flow._glyphs.push_back(new_glyph);
639                         // create the Layout::Character(s)
640                         double advance_width = new_glyph.width;
641                         unsigned end_byte;
642                         if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
643                             end_byte = it_span->start.iter_span->text_bytes;
644                         else {
645                             // output chars for the whole cluster that is commenced by this glyph
646                             if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
647                                 int next_cluster_glyph_index = glyph_index + 1;
648                                 while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
649                                        && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
650                                     next_cluster_glyph_index++;
651                                 if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
652                                     end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
653                                 else
654                                     end_byte = it_span->start.iter_span->text_bytes;
655                             } else
656                                 end_byte = char_byte;    // don't output any chars if we're not at the start of a cluster
657                         }
658                         while (char_byte < end_byte) {
659                             Layout::Character new_character;
660                             new_character.in_span = _flow._spans.size();
661                             new_character.x = x_in_span;
662                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
663                             new_character.in_glyph = _flow._glyphs.size() - 1;
664                             _flow._characters.push_back(new_character);
665                             if (new_character.char_attributes.is_white)
666                                 advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace;    // justification
667                             if (new_character.char_attributes.is_cursor_position)
668                                 advance_width += text_source->style->letter_spacing.computed;
669                             iter_source_text++;
670                             char_index_in_unbroken_span++;
671                             char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
672                         }
674                         advance_width *= direction_sign;
675                         if (new_span.direction != para.direction) {
676                             counter_directional_width_remaining -= advance_width;
677                             x -= advance_width;
678                             x_in_span -= advance_width;
679                         } else {
680                             x += advance_width;
681                             x_in_span += advance_width;
682                         }
683                     }
684                 } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
685                     x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
686                 }
688                 new_span.x_end = new_span.x_start + x_in_span;
689                 _flow._spans.push_back(new_span);
690                 previous_direction = new_span.direction;
691             }
692             // end adding spans to the list, on to the next chunk...
693         }
694         TRACE(("output done\n"));
695     }
697 /* *********************************************************************************************************/
698 //                             Setup and top-level functions
700     /** initialises the ScanlineMaker for the first shape in the flow, or
701     the infinite version if we're not doing wrapping. */
702     void _createFirstScanlineMaker()
703     {
704         _current_shape_index = 0;
705         if (_flow._input_wrap_shapes.empty()) {
706             // create the special no-wrapping infinite scanline maker
707             double initial_x = 0, initial_y = 0;
708             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
709             if (!text_source->x.empty())
710                 initial_x = text_source->x.front().computed;
711             if (!text_source->y.empty())
712                 initial_y = text_source->y.front().computed;
713             _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
714             TRACE(("  wrapping disabled\n"));
715         }
716         else {
717             _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
718             TRACE(("  begin wrap shape 0\n"));
719         }
720     }
722 public:
723     Calculator(Layout *text_flow)
724         : _flow(*text_flow) {}
726     bool calculate();
727 };
729 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
730  * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
731  * that does delete or Unref.
732  *
733  * I suggest using the garbage collector to manage deletion.
734  */
735 void Layout::Calculator::InputItemInfo::free()
737     if (sub_flow) {
738         delete sub_flow;
739         sub_flow = NULL;
740     }
743 void Layout::Calculator::PangoItemInfo::free()
745     if (item) {
746         pango_item_free(item);
747         item = NULL;
748     }
749     if (font) {
750         font->Unref();
751         font = NULL;
752     }
755 void Layout::Calculator::UnbrokenSpanPosition::increment()
757     gchar const *text_base = &*iter_span->input_stream_first_character.base();
758     char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
759     char_index++;
760     if (char_byte == iter_span->text_bytes) {
761         iter_span++;
762         char_index = char_byte = 0;
763     }
766 void Layout::Calculator::BrokenSpan::setZero()
768     end = start;
769     width = 0.0;
770     whitespace_count = 0;
771     end_glyph_index = start_glyph_index = 0;
772     ends_with_whitespace = false;
773     each_whitespace_width = 0.0;
776 template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
778     for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
779         it->free();
780     }
781     seq.clear();
784 void Layout::Calculator::ParagraphInfo::free()
786     free_sequence(input_items);
787     free_sequence(pango_items);
788     free_sequence(unbroken_spans);
791 ///**
792 // * For sections of text with a block-progression different to the rest
793 // * of the flow, the best thing to do is to detect them in advance and
794 // * create child TextFlow objects with just the rotated text. In the
795 // * parent we then effectively use ARBITRARY_GAP fields during the
796 // * flowing (because we don't allow wrapping when the block-progression
797 // * changes) and copy the actual text in during the output phase.
798 // *
799 // * NB: this code not enabled yet.
800 // */
801 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
802 //{
803 //    Direction prev_block_progression = _block_progression;
804 //    int run_start_input_index = para->first_input_index;
805 //
806 //    para->free_sequence(para->input_items);
807 //    for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
808 //        InputItemInfo input_item;
809 //
810 //        input_item.in_sub_flow = false;
811 //        input_item.sub_flow = NULL;
812 //        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
813 //            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
814 //            if (   control_code->code == SHAPE_BREAK
815 //                   || control_code->code == PARAGRAPH_BREAK)
816 //                break;                                    // stop at the end of the paragraph
817 //            // all other control codes we'll pick up later
818 //
819 //        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
820 //            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
821 //            Direction this_block_progression = text_source->styleGetBlockProgression();
822 //            if (this_block_progression != prev_block_progression) {
823 //                if (prev_block_progression != _block_progression) {
824 //                    // need to back up so that control codes belong outside the block-progression change
825 //                    int run_end_input_index = input_index - 1;
826 //                    while (run_end_input_index > run_start_input_index
827 //                           && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
828 //                        run_end_input_index--;
829 //                    // now create the sub-flow
830 //                    input_item.sub_flow = new Layout;
831 //                    for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
832 //                        input_item.in_sub_flow = true;
833 //                        if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
834 //                            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
835 //                            input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
836 //                        } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
837 //                            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
838 //                            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);
839 //                            Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
840 //                            sub_flow_text_source->x = text_source->x;    // this is easier than going via optionalattrs for the appendText() call
841 //                            sub_flow_text_source->y = text_source->y;    // should these actually be allowed anyway? You'll almost never get the results you expect
842 //                            sub_flow_text_source->dx = text_source->dx;  // (not that it's very clear what you should expect, anyway)
843 //                            sub_flow_text_source->dy = text_source->dy;
844 //                            sub_flow_text_source->rotate = text_source->rotate;
845 //                        }
846 //                    }
847 //                    input_item.sub_flow->calculateFlow();
848 //                }
849 //                run_start_input_index = input_index;
850 //            }
851 //            prev_block_progression = this_block_progression;
852 //        }
853 //        para->input_items.push_back(input_item);
854 //    }
855 //}
857 /**
858  * Take all the text from \a _para.first_input_index to the end of the
859  * paragraph and stitch it together so that pango_itemize() can be called on
860  * the whole thing.
861  *
862  * Input: para.first_input_index.
863  * Output: para.direction, para.pango_items, para.char_attributes.
864  */
865 void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
867     Glib::ustring para_text;
868     PangoAttrList *attributes_list;
869     unsigned input_index;
871     para->free_sequence(para->pango_items);
872     para->char_attributes.clear();
874     TRACE(("itemizing para, first input %d\n", para->first_input_index));
876     attributes_list = pango_attr_list_new();
877     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
878         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
879             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
880             if (   control_code->code == SHAPE_BREAK
881                    || control_code->code == PARAGRAPH_BREAK)
882                 break;                                    // stop at the end of the paragraph
883             // all other control codes we'll pick up later
885         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
886             Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
888                         // create the font_instance
889                         font_instance *font = text_source->styleGetFontInstance();
890                         if (font == NULL)
891                                 continue;  // bad news: we'll have to ignore all this text because we know of no font to render it
893             PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
894             attribute_font_description->start_index = para_text.bytes();
895             para_text.append(&*text_source->text_begin.base(), text_source->text_length);     // build the combined text
896             attribute_font_description->end_index = para_text.bytes();
897             pango_attr_list_insert(attributes_list, attribute_font_description);
898             // ownership of attribute is assumed by the list
899         }
900     }
902     TRACE(("whole para: \"%s\"\n", para_text.data()));
903     TRACE(("%d input sources used\n", input_index - para->first_input_index));
905     // do the pango_itemize()
906     GList *pango_items_glist = NULL;
907     if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
908         Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
909         if (text_source->style->direction.set) {
910             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]));
911             pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
912             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]));
913         }
914     }
915     if (pango_items_glist == NULL) {  // no direction specified, guess it
916         pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
918         // I think according to the css spec this is wrong and we're never allowed to guess the directionality
919         // of a paragraph. Need to talk to an rtl speaker.
920         if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
921         else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
922     }
923     pango_attr_list_unref(attributes_list);
925     // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
926     para->pango_items.reserve(g_list_length(pango_items_glist));
927     TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
928     for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
929         PangoItemInfo new_item;
930         new_item.item = (PangoItem*)current_pango_item->data;
931         PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
932         new_item.font = (font_factory::Default())->Face(font_description);
933         pango_font_description_free(font_description);   // Face() makes a copy
934         para->pango_items.push_back(new_item);
935     }
936     g_list_free(pango_items_glist);
938     // and get the character attributes on everything
939     para->char_attributes.resize(para_text.length() + 1);
940     pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
942     TRACE(("end para itemize, direction = %d\n", para->direction));
945 /**
946  * Gets the ascent, descent and leading for a font and the alteration that has to be performed
947  * according to the value specified by the line-height css property. The result of multiplying
948  * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
949  * section 10.8.
950  */
951 void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
952                                                 SPStyle const *style, LineHeight *line_height,
953                                                 double *line_height_multiplier)
955     if (font == NULL) {
956         line_height->setZero();
957         *line_height_multiplier = 1.0;
958     }
959     else
960         font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
961     *line_height *= font_size;
963     // yet another borked SPStyle member that we're going to have to fix ourselves
964     for ( ; ; ) {
965         if (style->line_height.set && !style->line_height.inherit) {
966             if (style->line_height.normal)
967                 break;
968             switch (style->line_height.unit) {
969                 case SP_CSS_UNIT_NONE:
970                     *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
971                     return;
972                 case SP_CSS_UNIT_EX:
973                     *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
974                     // 0.5 is an approximation of the x-height. Fixme.
975                     return;
976                 case SP_CSS_UNIT_EM:
977                 case SP_CSS_UNIT_PERCENT:
978                     *line_height_multiplier = style->line_height.value * font_size / line_height->total();
979                     return;
980                 default:  // absolute values
981                     *line_height_multiplier = style->line_height.computed / line_height->total();
982                     return;
983             }
984             break;
985         }
986         if (style->object == NULL || style->object->parent == NULL) break;
987         style = style->object->parent->style;
988         if (style == NULL) break;
989     }
990     *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
994 /**
995  * Split the paragraph into spans. Also call pango_shape() on them.
996  *
997  * Input: para->first_input_index, para->pango_items
998  * Output: para->spans
999  * Returns: the index of the beginning of the following paragraph in _flow._input_stream
1000  */
1001 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
1003     unsigned pango_item_index = 0;
1004     unsigned char_index_in_para = 0;
1005     unsigned byte_index_in_para = 0;
1006     unsigned input_index;
1008     TRACE(("build spans\n"));
1009     para->free_sequence(para->unbroken_spans);
1011     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1012         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1013             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1014             if (   control_code->code == SHAPE_BREAK
1015                    || control_code->code == PARAGRAPH_BREAK)
1016                 break;                                    // stop at the end of the paragraph
1017             else if (control_code->code == ARBITRARY_GAP) {
1018                 UnbrokenSpan new_span;
1019                 new_span.pango_item_index = -1;
1020                 new_span.input_index = input_index;
1021                 new_span.line_height.ascent = control_code->ascent;
1022                 new_span.line_height.descent = control_code->descent;
1023                 new_span.line_height.leading = 0.0;
1024                 new_span.text_bytes = 0;
1025                 new_span.char_index_in_para = char_index_in_para;
1026                 para->unbroken_spans.push_back(new_span);
1027                 TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
1028             }
1029         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
1030             Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
1032             unsigned char_index_in_source = 0;
1034             unsigned span_start_byte_in_source = 0;
1035             // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
1036             for ( ; ; ) {
1037                 /* we need to change spans at every change of PangoItem, source stream change,
1038                    or change in one of the attributes altering position/rotation. */
1040                 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
1041                                                     ? 0
1042                                                     : ( para->pango_items[pango_item_index].item->offset
1043                                                         + para->pango_items[pango_item_index].item->length
1044                                                         - byte_index_in_para ) );
1045                 unsigned const text_source_bytes = ( text_source->text_end.base()
1046                                                      - text_source->text_begin.base()
1047                                                      - span_start_byte_in_source );
1048                 TRACE(("New Span\n"));
1049                 UnbrokenSpan new_span;
1050                 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
1051                 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
1052                 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
1053                 new_span.input_index = input_index;
1055                 // cut at <tspan> attribute changes as well
1056                 new_span.x._set = false;
1057                 new_span.y._set = false;
1058                 new_span.dx._set = false;
1059                 new_span.dy._set = false;
1060                 new_span.rotate._set = false;
1061                 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1062                     if (text_source->x.size()  > char_index_in_source) new_span.x  = text_source->x[char_index_in_source];
1063                     if (text_source->y.size()  > char_index_in_source) new_span.y  = text_source->y[char_index_in_source];
1064                     if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
1065                     if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
1066                 } else {
1067                     if (text_source->x.size()  > char_index_in_source) new_span.y  = text_source->x[char_index_in_source];
1068                     if (text_source->y.size()  > char_index_in_source) new_span.x  = text_source->y[char_index_in_source];
1069                     if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
1070                     if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
1071                 }
1072                 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1073                 else if (char_index_in_source == 0) new_span.rotate = 0.f;
1074                 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1075                     // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1076                     // so that the top of the letters is at zero, not the baseline
1077                     new_span.y = 0.0;
1078                 }
1079                 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1080                 iter_text++;
1081                 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1082                     if (iter_text >= text_source->text_end) break;
1083                     if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1084                     if (   i >= text_source->x.size() && i >= text_source->y.size()
1085                         && i >= text_source->dx.size() && i >= text_source->dy.size()
1086                         && i >= text_source->rotate.size()) break;
1087                     if (   (text_source->x.size()  > i && text_source->x[i]._set)
1088                         || (text_source->y.size()  > i && text_source->y[i]._set)
1089                         || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1090                         || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1091                         || (text_source->rotate.size() > i && text_source->rotate[i]._set
1092                             && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1093                         new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1094                         break;
1095                     }
1096                 }
1098                 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1099                 new_span.font_size = text_source->styleComputeFontSize();
1100                 if (new_span.text_bytes) {
1101                     new_span.glyph_string = pango_glyph_string_new();
1102                     /* Some assertions intended to help diagnose bug #1277746. */
1103                     g_assert( 0 < new_span.text_bytes );
1104                     g_assert( span_start_byte_in_source < text_source->text->bytes() );
1105                     g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1106                     g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1107                               == NULL );
1108                     pango_shape(text_source->text->data() + span_start_byte_in_source,
1109                                 new_span.text_bytes,
1110                                 &para->pango_items[pango_item_index].item->analysis,
1111                                 new_span.glyph_string);
1113                     if (para->pango_items[pango_item_index].item->analysis.level & 1) {
1114                         // pango_shape() will reorder glyphs in rtl sections into visual order which messes
1115                         // us up because the svg spec requires us to draw glyphs in logical order
1116                         // let's reverse the glyphstring on a cluster-by-cluster basis
1117                         const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1118                         std::vector<PangoGlyphInfo> infos(nglyphs);
1119                         std::vector<gint> clusters(nglyphs);
1120                         unsigned i, cluster_start = 0;
1122                         for (i = 0 ; i < nglyphs ; ++i) {
1123                             if (new_span.glyph_string->glyphs[i].attr.is_cluster_start) {
1124                                 if (i != cluster_start) {
1125                                     std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
1126                                     std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
1127                                 }
1128                                 cluster_start = i;
1129                             }
1130                         }
1131                         if (i != cluster_start) {
1132                             std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
1133                             std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
1134                         }
1135                         std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
1136                         std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
1137                     }
1138                     new_span.pango_item_index = pango_item_index;
1139                     _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1141                     // At some point we may want to calculate baseline_shift here (to take advantage
1142                     // of otm features like superscript baseline), but for now we use style baseline_shift.
1143                     new_span.baseline_shift = text_source->style->baseline_shift.computed;
1145                     // TODO: metrics for vertical text
1146                     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()));
1147                     TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
1148                 } else {
1149                     // if there's no text we still need to initialise the styles
1150                     new_span.pango_item_index = -1;
1151                     font_instance *font = text_source->styleGetFontInstance();
1152                     if (font) {
1153                         _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1154                         font->Unref();
1155                     } else {
1156                         new_span.line_height.setZero();
1157                         new_span.line_height_multiplier = 1.0;
1158                     }
1159                     TRACE(("add style init span %d\n", para->unbroken_spans.size()));
1160                 }
1161                 para->unbroken_spans.push_back(new_span);
1163                 // calculations for moving to the next UnbrokenSpan
1164                 byte_index_in_para += new_span.text_bytes;
1165                 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1167                 if (new_span.text_bytes >= pango_item_bytes) {   // end of pango item
1168                     pango_item_index++;
1169                     if (pango_item_index == para->pango_items.size()) break;  // end of paragraph
1170                 }
1171                 if (new_span.text_bytes == text_source_bytes)
1172                     break;    // end of source
1173                 // else <tspan> attribute changed
1174                 span_start_byte_in_source += new_span.text_bytes;
1175             }
1176             char_index_in_para += char_index_in_source;
1177         }
1178     }
1179     TRACE(("end build spans\n"));
1180     return input_index;
1183 /**
1184  * Reinitialises the variables required on completion of one shape and
1185  * moving on to the next. Returns false if there are no more shapes to wrap
1186  * in to.
1187  */
1188 bool Layout::Calculator::_goToNextWrapShape()
1190     delete _scanline_maker;
1191     _scanline_maker = NULL;
1192     _current_shape_index++;
1193     if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
1194     _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1195     TRACE(("begin wrap shape %d\n", _current_shape_index));
1196     return true;
1199 /**
1200  * Given \a para filled in and \a start_span_pos set, keeps trying to
1201  * find somewhere it can fit the next line of text. The process of finding
1202  * the text that fits will involve creating one or more entries in
1203  * \a chunk_info describing the bounds of the fitted text and several
1204  * bits of information that will prove useful when we come to output the
1205  * line to #_flow. Returns with \a start_span_pos set to the end of the
1206  * text that was fitted, \a chunk_info completely filled out and
1207  * \a line_height set to the largest line box on the line. The return
1208  * value is false only if we've run out of shapes to wrap inside (and
1209  * hence couldn't create any chunks).
1210  */
1211 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
1212                                             UnbrokenSpanPosition *start_span_pos,
1213                                             std::vector<ChunkInfo> *chunk_info,
1214                                             LineHeight *line_height)
1216     // init the initial line_height
1217     if (start_span_pos->iter_span == para.unbroken_spans.end()) {
1218         if (_flow._spans.empty()) {
1219             // empty first para: create a font for the sole purpose of measuring it
1220             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1221             font_instance *font = text_source->styleGetFontInstance();
1222             if (font) {
1223                 double font_size = text_source->styleComputeFontSize();
1224                 double multiplier;
1225                 _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
1226                 font->Unref();
1227                 *line_height *= multiplier;
1228                 _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
1229             }
1230         }
1231         // else empty subsequent para: keep the old line height
1232     } else {
1233         if (_flow._input_wrap_shapes.empty()) {
1234             // if we're not wrapping set the line_height big and negative so we can use negative line height
1235             line_height->ascent = -1.0e10;
1236             line_height->descent = -1.0e10;
1237             line_height->leading = -1.0e10;
1238         }
1239         else
1240             line_height->setZero();
1241     }
1243     UnbrokenSpanPosition span_pos;
1244     for( ; ; ) {
1245         std::vector<ScanlineMaker::ScanRun> scan_runs;
1246         scan_runs = _scanline_maker->makeScanline(*line_height);
1247         while (scan_runs.empty()) {
1248             if (!_goToNextWrapShape()) return false;  // no more shapes to wrap in to
1249             scan_runs = _scanline_maker->makeScanline(*line_height);
1250         }
1252         TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
1253         chunk_info->clear();
1254         chunk_info->reserve(scan_runs.size());
1255         if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1256         unsigned scan_run_index;
1257         span_pos = *start_span_pos;
1258         for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1259             if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
1260                 break;
1261             if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
1262                 span_pos = chunk_info->back().broken_spans.back().end;
1263         }
1264         if (scan_run_index == scan_runs.size()) break;  // ie when buildChunksInScanRun() succeeded
1265     }
1266     *start_span_pos = span_pos;
1267     return true;
1270 /**
1271  * Given a scan run and a first character, append one or more chunks to
1272  * the \a chunk_info vector that describe all the spans and other detail
1273  * necessary to output the greatest amount of text that will fit on this scan
1274  * line (greedy line breaking algorithm). Each chunk contains one or more
1275  * BrokenSpan structures that link back to UnbrokenSpan structures that link
1276  * to the text itself. Normally there will be either one or zero (if the
1277  * scanrun is too short to fit any text) chunk added to \a chunk_info by
1278  * each call to this method, but we will add more than one if an x or y
1279  * attribute has been set on a tspan. \a line_height must be set on input,
1280  * and if it needs to be made larger and the #_scanline_maker can't do
1281  * an in-situ resize then it will be set to the required value and the
1282  * method will return false.
1283  */
1284 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
1285                                                UnbrokenSpanPosition const &start_span_pos,
1286                                                ScanlineMaker::ScanRun const &scan_run,
1287                                                std::vector<ChunkInfo> *chunk_info,
1288                                                LineHeight *line_height) const
1290     ChunkInfo new_chunk;
1291     new_chunk.text_width = 0.0;
1292     new_chunk.whitespace_count = 0;
1293     new_chunk.scanrun_width = scan_run.width();
1294     new_chunk.x = scan_run.x_start;
1296     // we haven't done anything yet so the last valid break position is the beginning
1297     BrokenSpan last_span_at_break, last_span_at_emergency_break;
1298     last_span_at_break.start = start_span_pos;
1299     last_span_at_break.setZero();
1300     last_span_at_emergency_break.start = start_span_pos;
1301     last_span_at_emergency_break.setZero();
1303     TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1304     BrokenSpan new_span;
1305     new_span.end = start_span_pos;
1306     while (new_span.end.iter_span != para.unbroken_spans.end()) {    // this loops once for each UnbrokenSpan
1308         new_span.start = new_span.end;
1310         // force a chunk change at x or y attribute change
1311         if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1313             if (new_span.start.iter_span != start_span_pos.iter_span)
1314                 chunk_info->push_back(new_chunk);
1316             new_chunk.x += new_chunk.text_width;
1317             new_chunk.text_width = 0.0;
1318             new_chunk.whitespace_count = 0;
1319             new_chunk.broken_spans.clear();
1320             if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1321             // y doesn't need to be done until output time
1322         }
1324         // see if this span is too tall to fit on the current line
1325         LineHeight total_height = new_span.start.iter_span->line_height;
1326         total_height *= new_span.start.iter_span->line_height_multiplier;
1327         /* floating point 80-bit/64-bit rounding problems require epsilon. See
1328            discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1329         if (   total_height.ascent  > line_height->ascent  + FLT_EPSILON
1330                || total_height.descent > line_height->descent + FLT_EPSILON
1331                || total_height.leading > line_height->leading + FLT_EPSILON) {
1332             line_height->max(total_height);
1333             if (!_scanline_maker->canExtendCurrentScanline(*line_height))
1334                 return false;
1335         }
1337         bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1339         new_chunk.text_width += new_span.width;
1340         new_chunk.whitespace_count += new_span.whitespace_count;
1341         new_chunk.broken_spans.push_back(new_span);   // if !span_fitted we'll correct ourselves below
1343         if (!span_fitted) break;
1345         if (new_span.end.iter_span == para.unbroken_spans.end()) {
1346             last_span_at_break = new_span;
1347             break;
1348         }
1349     }
1351     TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1352     chunk_info->push_back(new_chunk);
1354     if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
1355         /* **non-SVG spec bit**: See bug #1191102
1356            If the user types a very long line with no spaces, the way the spec
1357            is written at the moment means that when the length of the text
1358            exceeds the available width of all remaining areas, the text is
1359            completely hidden. This condition alters that behaviour so that if
1360            the length of the line is greater than four times the line-height
1361            and there are no spaces, it'll be emergency-wrapped at the last
1362            character. One could read the SVG Tiny 1.2 draft as permitting this
1363            sort of behaviour, but it's still a bit dodgy. The hard-coding of
1364            4x is not nice, either. */
1365         last_span_at_break = last_span_at_emergency_break;
1366     }
1368     if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
1369         // need to back out spans until we come to the one with the last break in it
1370         while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
1371             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1372             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1373             chunk_info->back().broken_spans.pop_back();
1374             if (chunk_info->back().broken_spans.empty())
1375                 chunk_info->pop_back();
1376         }
1377         if (!chunk_info->empty()) {
1378             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1379             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1380             if (last_span_at_break.start == last_span_at_break.end) {
1381                 chunk_info->back().broken_spans.pop_back();   // last break was at an existing boundary
1382                 if (chunk_info->back().broken_spans.empty())
1383                     chunk_info->pop_back();
1384             } else {
1385                 chunk_info->back().broken_spans.back() = last_span_at_break;
1386                 chunk_info->back().text_width += last_span_at_break.width;
1387                 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
1388             }
1389             TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
1390         }
1391     }
1393     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
1394         // for justification we need to discard space occupied by the single whitespace at the end of the chunk
1395         chunk_info->back().broken_spans.back().ends_with_whitespace = false;
1396         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1397         chunk_info->back().broken_spans.back().whitespace_count--;
1398         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1399         chunk_info->back().whitespace_count--;
1400     }
1402     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
1403         // for justification we need to discard line-spacing and word-spacing at end of the chunk
1404         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().letter_spacing;
1405         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().letter_spacing;
1406         TRACE(("width after subtracting last letter_spacing: %f\n", chunk_info->back().broken_spans.back().width));
1407     }
1409     return true;
1412 /** The management function to start the whole thing off. */
1413 bool Layout::Calculator::calculate()
1415     if (_flow._input_stream.empty())
1416         return false;
1417     /**
1418     * hm, why do we want assert (crash) the application, now do simply return false
1419     * \todo check if this is the correct behaviour
1420     * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
1421     */
1422     if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
1423     {
1424         g_warning("flow text is not of type TEXT_SOURCE. Abort.");
1425         return false;
1426     }
1427     TRACE(("begin calculateFlow()\n"));
1429     _flow._clearOutputObjects();
1431     _pango_context = (font_factory::Default())->fontContext;
1432     _font_factory_size_multiplier = (font_factory::Default())->fontSize;
1434     _block_progression = _flow._blockProgression();
1435     _y_offset = 0.0;
1436     _createFirstScanlineMaker();
1438     ParagraphInfo para;
1439     LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras
1440     for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
1441         // jump to the next wrap shape if this is a SHAPE_BREAK control code
1442         if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
1443             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
1444             if (control_code->code == SHAPE_BREAK) {
1445                 TRACE(("shape break control code\n"));
1446                 if (!_goToNextWrapShape()) break;
1447                 continue;
1448             }
1449         }
1450         if (_scanline_maker == NULL)
1451             break;       // we're trying to flow past the last wrap shape
1453         _buildPangoItemizationForPara(&para);
1454         unsigned para_end_input_index = _buildSpansForPara(&para);
1456         if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
1457             para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
1458         else
1459             para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
1461         TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
1462         Layout::Paragraph new_paragraph;
1463         new_paragraph.base_direction = para.direction;
1464         new_paragraph.alignment = para.alignment;
1465         _flow._paragraphs.push_back(new_paragraph);
1467         // start scanning lines
1468         UnbrokenSpanPosition span_pos;
1469         span_pos.iter_span = para.unbroken_spans.begin();
1470         span_pos.char_byte = 0;
1471         span_pos.char_index = 0;
1473         do {   // for each line in the paragraph
1474             TRACE(("begin line\n"));
1475             std::vector<ChunkInfo> line_chunk_info;
1476             if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
1477                 break;   // out of shapes to wrap in to
1479             _outputLine(para, line_height, line_chunk_info);
1480             _scanline_maker->completeLine(); // Increments y by line height
1481         } while (span_pos.iter_span != para.unbroken_spans.end());
1483         TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
1484         if (_scanline_maker != NULL) {
1485             bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
1486             if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
1487                 || para_end_input_index + 1 < _flow._input_stream.size()) {
1488                 // we need a span just for the para if it's either an empty last para or a break in the middle
1489                 Layout::Span new_span;
1490                 if (_flow._spans.empty()) {
1491                     new_span.font = NULL;
1492                     new_span.font_size = line_height.ascent + line_height.descent;
1493                     new_span.line_height = line_height;
1494                     new_span.x_end = 0.0;
1495                 } else {
1496                     new_span = _flow._spans.back();
1497                     if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
1498                         new_span.x_end = 0.0;
1499                 }
1500                 new_span.in_chunk = _flow._chunks.size() - 1;
1501                 if (new_span.font)
1502                     new_span.font->Ref();
1503                 new_span.x_start = new_span.x_end;
1504                 new_span.baseline_shift = 0.0;
1505                 new_span.direction = para.direction;
1506                 new_span.block_progression = _block_progression;
1507                 if (para_end_input_index == _flow._input_stream.size())
1508                     new_span.in_input_stream_item = _flow._input_stream.size() - 1;
1509                 else
1510                     new_span.in_input_stream_item = para_end_input_index;
1511                 _flow._spans.push_back(new_span);
1512             }
1513             if (para_end_input_index + 1 < _flow._input_stream.size()) {
1514                 // we've got to add an invisible character between paragraphs so that we can position iterators
1515                 // (and hence cursors) both before and after the paragraph break
1516                 Layout::Character new_character;
1517                 new_character.in_span = _flow._spans.size() - 1;
1518                 new_character.char_attributes.is_line_break = 1;
1519                 new_character.char_attributes.is_mandatory_break = 1;
1520                 new_character.char_attributes.is_char_break = 1;
1521                 new_character.char_attributes.is_white = 1;
1522                 new_character.char_attributes.is_cursor_position = 1;
1523                 new_character.char_attributes.is_word_start = 0;
1524                 new_character.char_attributes.is_word_end = 1;
1525                 new_character.char_attributes.is_sentence_start = 0;
1526                 new_character.char_attributes.is_sentence_end = 1;
1527                 new_character.char_attributes.is_sentence_boundary = 1;
1528                 new_character.char_attributes.backspace_deletes_character = 1;
1529                 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
1530                 new_character.in_glyph = -1;
1531                 _flow._characters.push_back(new_character);
1532             }
1533         }
1534         para.free();
1535         para.first_input_index = para_end_input_index + 1;
1536     }
1538     para.free();
1539     if (_scanline_maker) {
1540         delete _scanline_maker;
1541         _flow._input_truncated = false;
1542     } else {
1543         _flow._input_truncated = true;
1544     }
1546     return true;
1549 void Layout::_calculateCursorShapeForEmpty()
1551     _empty_cursor_shape.position = Geom::Point(0, 0);
1552     _empty_cursor_shape.height = 0.0;
1553     _empty_cursor_shape.rotation = 0.0;
1554     if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
1555         return;
1557     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
1559     font_instance *font = text_source->styleGetFontInstance();
1560     double font_size = text_source->styleComputeFontSize();
1561     double caret_slope_run = 0.0, caret_slope_rise = 1.0;
1562     LineHeight line_height;
1563     if (font) {
1564         const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
1565         font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
1566         line_height *= font_size;
1567         font->Unref();
1568     } else {
1569         line_height.ascent = font_size * 0.85;      // random guesses
1570         line_height.descent = font_size * 0.15;
1571         line_height.leading = 0.0;
1572     }
1573     double caret_slope = atan2(caret_slope_run, caret_slope_rise);
1574     _empty_cursor_shape.height = font_size / cos(caret_slope);
1575     _empty_cursor_shape.rotation = caret_slope;
1577     if (_input_wrap_shapes.empty()) {
1578         _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
1579                                                  text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
1580     } else {
1581         Direction block_progression = text_source->styleGetBlockProgression();
1582         ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
1583         std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
1584         if (!scan_runs.empty()) {
1585             if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
1586                 _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
1587             else
1588                 _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
1589         }
1590     }
1593 bool Layout::calculateFlow()
1595     bool result = Calculator(this).calculate();
1596     if (_characters.empty())
1597         _calculateCursorShapeForEmpty();
1598     return result;
1601 }//namespace Text
1602 }//namespace Inkscape
1605 /*
1606   Local Variables:
1607   mode:c++
1608   c-file-style:"stroustrup"
1609   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1610   indent-tabs-mode:nil
1611   fill-column:99
1612   End:
1613 */
1614 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :