Code

Super duper mega (fun!) commit: replaced encoding=utf-8 with fileencoding=utf-8 in...
[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 updated:  23 July 2010:
449             // We must handle two cases:
450             //
451             //   1. Inkscape SVG where the first line is placed by the read-in "y" value and the
452             //      rest are determined by 'font-size' and 'line-height' (and not by any
453             //      y-kerning). <tspan>s in this case are marked by sodipodi:role="line". This
454             //      allows new lines to be inserted in the middle of a <text> object. On output,
455             //      new "y" values are calculated for each <tspan> that represents a new line. Line
456             //      spacing is already handled by the calling routine.
457             //
458             //   2. Plain SVG where each <text> or <tspan> is placed by its own "x" and "y" values.
459             //      Note that in this case Inkscape treats each <text> object with any included
460             //      <tspan>s as a single line of text. This can be confusing in the code below.
462             if (!it_chunk->broken_spans.empty()                               // Not empty paragraph
463                 && it_chunk->broken_spans.front().start.char_byte == 0 ) {    // Beginning of unbroken span
465                 // If empty or new line (sodipode:role="line")
466                 if( _flow._characters.empty() ||
467                     _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
469                     // This is the Inkscape SVG case.
470                     //
471                     // If <tspan> "y" attribute is set, use it (initial "y" attributes in
472                     // <tspans> other than the first have already been stripped for <tspans>
473                     // marked with role="line", see sp-text.cpp: SPText::_buildLayoutInput).
474                     if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
476                         // Use set "y" attribute
477                         new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
479                         // Save baseline
480                         _flow._lines.back().baseline_y = new_line.baseline_y;
482                         // Save new <tspan> y coordinate
483                         _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
485                     }
487                     // Reset relative y_offset ("dy" attribute is relative but should be reset at
488                     // the beginning of each line since each line will have a new "y" written out.)
489                     _y_offset = 0.0;
491                 } else {
493                     // This is the plain SVG case
494                     //
495                     // "x" and "y" are used to place text, simulating lines as necessary
496                     if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
497                         _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
498                     }
499                 }
500             }
501             _flow._chunks.push_back(new_chunk);
503             double x;
504             double direction_sign;
505             Direction previous_direction = para.direction;
506             double counter_directional_width_remaining = 0.0;
507             float glyph_rotate = 0.0;
508             if (para.direction == LEFT_TO_RIGHT) {
509                 direction_sign = +1.0;
510                 x = 0.0;
511             } else {
512                 direction_sign = -1.0;
513                 if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
514                     x = it_chunk->scanrun_width;
515                 else
516                     x = it_chunk->text_width;
517             }
519             for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
520                 // begin adding spans to the list
521                 UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
523                 if (it_span->start.char_byte == 0) {
524                     // Start of an unbroken span, we might have dx, dy or rotate still to process
525                     // (x and y are done per chunk)
526                     if (unbroken_span.dx._set) x += unbroken_span.dx.computed;
527                     if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
528                     if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
529                 }
531                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
532                     && unbroken_span.pango_item_index == -1) {
533                     // style only, nothing to output
534                     continue;
535                 }
537                 Layout::Span new_span;
538                 double x_in_span = 0.0;
540                 new_span.in_chunk = _flow._chunks.size() - 1;
541                 new_span.line_height = unbroken_span.line_height;
542                 new_span.in_input_stream_item = unbroken_span.input_index;
543                 new_span.baseline_shift = 0.0;
544                 new_span.block_progression = _block_progression;
545                 if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
546                     {
547                     new_span.font->Ref();
548                     new_span.font_size = unbroken_span.font_size;
549                     new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
550                     new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
551                 } else {  // a control code
552                     new_span.font = NULL;
553                     new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
554                     new_span.direction = para.direction;
555                 }
557                 if (new_span.direction == para.direction) {
558                     x -= counter_directional_width_remaining;
559                     counter_directional_width_remaining = 0.0;
560                 } else if (new_span.direction != previous_direction) {
561                     // measure width of spans we need to switch round
562                     counter_directional_width_remaining = 0.0;
563                     std::vector<BrokenSpan>::const_iterator it_following_span;
564                     for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
565                         Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
566                         if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
567                             if (it_following_span->start.iter_span->pango_item_index == -1) {   // when the span came from a control code
568                                 if (new_span.direction != para.direction) break;
569                             } else
570                                 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;
571                         }
572                         counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
573                     }
574                     x += counter_directional_width_remaining;
575                     counter_directional_width_remaining = 0.0;    // we want to go increasingly negative
576                 }
577                 new_span.x_start = x;
579                 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
580                     // the span is set up, push the glyphs and chars
581                     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
582                     Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
583                     unsigned char_index_in_unbroken_span = it_span->start.char_index;
584                     unsigned cluster_start_char_index = _flow._characters.size();
585                     double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
587                     for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
588                         unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
589                         if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
590                             cluster_start_char_index = _flow._characters.size();
592                         if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
593                             && *iter_source_text == UNICODE_SOFT_HYPHEN
594                             && glyph_index + 1 != it_span->end_glyph_index) {
595                             // if we're looking at a soft hyphen and it's not the last glyph in the
596                             // chunk we don't draw the glyph but we still need to add to _characters
597                             Layout::Character new_character;
598                             new_character.in_span = _flow._spans.size();     // the span hasn't been added yet, so no -1
599                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
600                             new_character.in_glyph = -1;
601                             _flow._characters.push_back(new_character);
602                             iter_source_text++;
603                             char_index_in_unbroken_span++;
604                             while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
605                                    && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
606                                 glyph_index++;
607                             glyph_index--;
608                             continue;
609                         }
611                         // create the Layout::Glyph
612                         Layout::Glyph new_glyph;
613                         new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
614                         new_glyph.in_character = cluster_start_char_index;
615                         new_glyph.rotation = glyph_rotate;
617                         /* put something like this back in when we do glyph-rotation-horizontal/vertical
618                         if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
619                             new_glyph.x += new_span.line_height.ascent;
620                             new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
621                             new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
622                         } else */
624                         if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
625                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
626                             new_glyph.y = _y_offset -
627                                 unbroken_span.baseline_shift +
628                                 (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset -
629                                  unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
630                             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);
631                         } else {
632                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
633                             new_glyph.y = _y_offset -
634                                 unbroken_span.baseline_shift +
635                                 unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
636                             new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
637                             if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font))
638                                 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);
639                                 // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
640                         }
641                         if (new_span.direction == RIGHT_TO_LEFT) {
642                             // pango wanted to give us glyphs in visual order but we refused, so we need to work
643                             // out where the cluster start is ourselves
644                             double cluster_width = 0.0;
645                             for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
646                                 if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
647                                     break;
648                                 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
649                                     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);
650                                 else
651                                     cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
652                             }
653                             new_glyph.x -= cluster_width;
654                         }
655                         _flow._glyphs.push_back(new_glyph);
657                         // create the Layout::Character(s)
658                         double advance_width = new_glyph.width;
659                         unsigned end_byte;
660                         if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
661                             end_byte = it_span->start.iter_span->text_bytes;
662                         else {
663                             // output chars for the whole cluster that is commenced by this glyph
664                             if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
665                                 int next_cluster_glyph_index = glyph_index + 1;
666                                 while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
667                                        && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
668                                     next_cluster_glyph_index++;
669                                 if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
670                                     end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
671                                 else
672                                     end_byte = it_span->start.iter_span->text_bytes;
673                             } else
674                                 end_byte = char_byte;    // don't output any chars if we're not at the start of a cluster
675                         }
676                         while (char_byte < end_byte) {
677                             Layout::Character new_character;
678                             new_character.in_span = _flow._spans.size();
679                             new_character.x = x_in_span;
680                             new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
681                             new_character.in_glyph = _flow._glyphs.size() - 1;
682                             _flow._characters.push_back(new_character);
683                             if (new_character.char_attributes.is_white)
684                                 advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace;    // justification
685                             if (new_character.char_attributes.is_cursor_position)
686                                 advance_width += text_source->style->letter_spacing.computed;
687                             iter_source_text++;
688                             char_index_in_unbroken_span++;
689                             char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
690                         }
692                         advance_width *= direction_sign;
693                         if (new_span.direction != para.direction) {
694                             counter_directional_width_remaining -= advance_width;
695                             x -= advance_width;
696                             x_in_span -= advance_width;
697                         } else {
698                             x += advance_width;
699                             x_in_span += advance_width;
700                         }
701                     }
702                 } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
703                     x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
704                 }
706                 new_span.x_end = new_span.x_start + x_in_span;
707                 _flow._spans.push_back(new_span);
708                 previous_direction = new_span.direction;
709             }
710             // end adding spans to the list, on to the next chunk...
711         }
712         TRACE(("output done\n"));
713     }
715 /* *********************************************************************************************************/
716 //                             Setup and top-level functions
718     /** initialises the ScanlineMaker for the first shape in the flow, or
719     the infinite version if we're not doing wrapping. */
720     void _createFirstScanlineMaker()
721     {
722         _current_shape_index = 0;
723         if (_flow._input_wrap_shapes.empty()) {
724             // create the special no-wrapping infinite scanline maker
725             double initial_x = 0, initial_y = 0;
726             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
727             if (!text_source->x.empty())
728                 initial_x = text_source->x.front().computed;
729             if (!text_source->y.empty())
730                 initial_y = text_source->y.front().computed;
731             _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
732             TRACE(("  wrapping disabled\n"));
733         }
734         else {
735             _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
736             TRACE(("  begin wrap shape 0\n"));
737         }
738     }
740 public:
741     Calculator(Layout *text_flow)
742         : _flow(*text_flow) {}
744     bool calculate();
745 };
747 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
748  * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
749  * that does delete or Unref.
750  *
751  * I suggest using the garbage collector to manage deletion.
752  */
753 void Layout::Calculator::InputItemInfo::free()
755     if (sub_flow) {
756         delete sub_flow;
757         sub_flow = NULL;
758     }
761 void Layout::Calculator::PangoItemInfo::free()
763     if (item) {
764         pango_item_free(item);
765         item = NULL;
766     }
767     if (font) {
768         font->Unref();
769         font = NULL;
770     }
773 void Layout::Calculator::UnbrokenSpanPosition::increment()
775     gchar const *text_base = &*iter_span->input_stream_first_character.base();
776     char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
777     char_index++;
778     if (char_byte == iter_span->text_bytes) {
779         iter_span++;
780         char_index = char_byte = 0;
781     }
784 void Layout::Calculator::BrokenSpan::setZero()
786     end = start;
787     width = 0.0;
788     whitespace_count = 0;
789     end_glyph_index = start_glyph_index = 0;
790     ends_with_whitespace = false;
791     each_whitespace_width = 0.0;
794 template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
796     for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
797         it->free();
798     }
799     seq.clear();
802 void Layout::Calculator::ParagraphInfo::free()
804     free_sequence(input_items);
805     free_sequence(pango_items);
806     free_sequence(unbroken_spans);
809 ///**
810 // * For sections of text with a block-progression different to the rest
811 // * of the flow, the best thing to do is to detect them in advance and
812 // * create child TextFlow objects with just the rotated text. In the
813 // * parent we then effectively use ARBITRARY_GAP fields during the
814 // * flowing (because we don't allow wrapping when the block-progression
815 // * changes) and copy the actual text in during the output phase.
816 // *
817 // * NB: this code not enabled yet.
818 // */
819 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
820 //{
821 //    Direction prev_block_progression = _block_progression;
822 //    int run_start_input_index = para->first_input_index;
823 //
824 //    para->free_sequence(para->input_items);
825 //    for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
826 //        InputItemInfo input_item;
827 //
828 //        input_item.in_sub_flow = false;
829 //        input_item.sub_flow = NULL;
830 //        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
831 //            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
832 //            if (   control_code->code == SHAPE_BREAK
833 //                   || control_code->code == PARAGRAPH_BREAK)
834 //                break;                                    // stop at the end of the paragraph
835 //            // all other control codes we'll pick up later
836 //
837 //        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
838 //            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
839 //            Direction this_block_progression = text_source->styleGetBlockProgression();
840 //            if (this_block_progression != prev_block_progression) {
841 //                if (prev_block_progression != _block_progression) {
842 //                    // need to back up so that control codes belong outside the block-progression change
843 //                    int run_end_input_index = input_index - 1;
844 //                    while (run_end_input_index > run_start_input_index
845 //                           && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
846 //                        run_end_input_index--;
847 //                    // now create the sub-flow
848 //                    input_item.sub_flow = new Layout;
849 //                    for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
850 //                        input_item.in_sub_flow = true;
851 //                        if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
852 //                            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
853 //                            input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
854 //                        } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
855 //                            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
856 //                            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);
857 //                            Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
858 //                            sub_flow_text_source->x = text_source->x;    // this is easier than going via optionalattrs for the appendText() call
859 //                            sub_flow_text_source->y = text_source->y;    // should these actually be allowed anyway? You'll almost never get the results you expect
860 //                            sub_flow_text_source->dx = text_source->dx;  // (not that it's very clear what you should expect, anyway)
861 //                            sub_flow_text_source->dy = text_source->dy;
862 //                            sub_flow_text_source->rotate = text_source->rotate;
863 //                        }
864 //                    }
865 //                    input_item.sub_flow->calculateFlow();
866 //                }
867 //                run_start_input_index = input_index;
868 //            }
869 //            prev_block_progression = this_block_progression;
870 //        }
871 //        para->input_items.push_back(input_item);
872 //    }
873 //}
875 /**
876  * Take all the text from \a _para.first_input_index to the end of the
877  * paragraph and stitch it together so that pango_itemize() can be called on
878  * the whole thing.
879  *
880  * Input: para.first_input_index.
881  * Output: para.direction, para.pango_items, para.char_attributes.
882  */
883 void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
885     Glib::ustring para_text;
886     PangoAttrList *attributes_list;
887     unsigned input_index;
889     para->free_sequence(para->pango_items);
890     para->char_attributes.clear();
892     TRACE(("itemizing para, first input %d\n", para->first_input_index));
894     attributes_list = pango_attr_list_new();
895     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
896         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
897             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
898             if (   control_code->code == SHAPE_BREAK
899                    || control_code->code == PARAGRAPH_BREAK)
900                 break;                                    // stop at the end of the paragraph
901             // all other control codes we'll pick up later
903         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
904             Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
906                         // create the font_instance
907                         font_instance *font = text_source->styleGetFontInstance();
908                         if (font == NULL)
909                                 continue;  // bad news: we'll have to ignore all this text because we know of no font to render it
911             PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
912             attribute_font_description->start_index = para_text.bytes();
913             para_text.append(&*text_source->text_begin.base(), text_source->text_length);     // build the combined text
914             attribute_font_description->end_index = para_text.bytes();
915             pango_attr_list_insert(attributes_list, attribute_font_description);
916             // ownership of attribute is assumed by the list
917         }
918     }
920     TRACE(("whole para: \"%s\"\n", para_text.data()));
921     TRACE(("%d input sources used\n", input_index - para->first_input_index));
923     // do the pango_itemize()
924     GList *pango_items_glist = NULL;
925     if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
926         Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
927         if (text_source->style->direction.set) {
928             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]));
929             pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
930             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]));
931         }
932     }
933     if (pango_items_glist == NULL) {  // no direction specified, guess it
934         pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
936         // I think according to the css spec this is wrong and we're never allowed to guess the directionality
937         // of a paragraph. Need to talk to an rtl speaker.
938         if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
939         else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
940     }
941     pango_attr_list_unref(attributes_list);
943     // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
944     para->pango_items.reserve(g_list_length(pango_items_glist));
945     TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
946     for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
947         PangoItemInfo new_item;
948         new_item.item = (PangoItem*)current_pango_item->data;
949         PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
950         new_item.font = (font_factory::Default())->Face(font_description);
951         pango_font_description_free(font_description);   // Face() makes a copy
952         para->pango_items.push_back(new_item);
953     }
954     g_list_free(pango_items_glist);
956     // and get the character attributes on everything
957     para->char_attributes.resize(para_text.length() + 1);
958     pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
960     TRACE(("end para itemize, direction = %d\n", para->direction));
963 /**
964  * Gets the ascent, descent and leading for a font and the alteration that has to be performed
965  * according to the value specified by the line-height css property. The result of multiplying
966  * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
967  * section 10.8.
968  */
969 void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
970                                                 SPStyle const *style, LineHeight *line_height,
971                                                 double *line_height_multiplier)
973     if (font == NULL) {
974         line_height->setZero();
975         *line_height_multiplier = 1.0;
976     }
977     else
978         font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
979     *line_height *= font_size;
981     // yet another borked SPStyle member that we're going to have to fix ourselves
982     for ( ; ; ) {
983         if (style->line_height.set && !style->line_height.inherit) {
984             if (style->line_height.normal)
985                 break;
986             switch (style->line_height.unit) {
987                 case SP_CSS_UNIT_NONE:
988                     *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
989                     return;
990                 case SP_CSS_UNIT_EX:
991                     *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
992                     // 0.5 is an approximation of the x-height. Fixme.
993                     return;
994                 case SP_CSS_UNIT_EM:
995                 case SP_CSS_UNIT_PERCENT:
996                     *line_height_multiplier = style->line_height.value * font_size / line_height->total();
997                     return;
998                 default:  // absolute values
999                     *line_height_multiplier = style->line_height.computed / line_height->total();
1000                     return;
1001             }
1002             break;
1003         }
1004         if (style->object == NULL || style->object->parent == NULL) break;
1005         style = style->object->parent->style;
1006         if (style == NULL) break;
1007     }
1008     *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
1012 /**
1013  * Split the paragraph into spans. Also call pango_shape() on them.
1014  *
1015  * Input: para->first_input_index, para->pango_items
1016  * Output: para->spans
1017  * Returns: the index of the beginning of the following paragraph in _flow._input_stream
1018  */
1019 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
1021     unsigned pango_item_index = 0;
1022     unsigned char_index_in_para = 0;
1023     unsigned byte_index_in_para = 0;
1024     unsigned input_index;
1026     TRACE(("build spans\n"));
1027     para->free_sequence(para->unbroken_spans);
1029     for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1030         if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1031             Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1032             if (   control_code->code == SHAPE_BREAK
1033                    || control_code->code == PARAGRAPH_BREAK)
1034                 break;                                    // stop at the end of the paragraph
1035             else if (control_code->code == ARBITRARY_GAP) {
1036                 UnbrokenSpan new_span;
1037                 new_span.pango_item_index = -1;
1038                 new_span.input_index = input_index;
1039                 new_span.line_height.ascent = control_code->ascent;
1040                 new_span.line_height.descent = control_code->descent;
1041                 new_span.line_height.leading = 0.0;
1042                 new_span.text_bytes = 0;
1043                 new_span.char_index_in_para = char_index_in_para;
1044                 para->unbroken_spans.push_back(new_span);
1045                 TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
1046             }
1047         } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
1048             Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
1050             unsigned char_index_in_source = 0;
1052             unsigned span_start_byte_in_source = 0;
1053             // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
1054             for ( ; ; ) {
1055                 /* we need to change spans at every change of PangoItem, source stream change,
1056                    or change in one of the attributes altering position/rotation. */
1058                 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
1059                                                     ? 0
1060                                                     : ( para->pango_items[pango_item_index].item->offset
1061                                                         + para->pango_items[pango_item_index].item->length
1062                                                         - byte_index_in_para ) );
1063                 unsigned const text_source_bytes = ( text_source->text_end.base()
1064                                                      - text_source->text_begin.base()
1065                                                      - span_start_byte_in_source );
1066                 TRACE(("New Span\n"));
1067                 UnbrokenSpan new_span;
1068                 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
1069                 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
1070                 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
1071                 new_span.input_index = input_index;
1073                 // cut at <tspan> attribute changes as well
1074                 new_span.x._set = false;
1075                 new_span.y._set = false;
1076                 new_span.dx._set = false;
1077                 new_span.dy._set = false;
1078                 new_span.rotate._set = false;
1079                 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1080                     if (text_source->x.size()  > char_index_in_source) new_span.x  = text_source->x[char_index_in_source];
1081                     if (text_source->y.size()  > char_index_in_source) new_span.y  = text_source->y[char_index_in_source];
1082                     if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
1083                     if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
1084                 } else {
1085                     if (text_source->x.size()  > char_index_in_source) new_span.y  = text_source->x[char_index_in_source];
1086                     if (text_source->y.size()  > char_index_in_source) new_span.x  = text_source->y[char_index_in_source];
1087                     if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
1088                     if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
1089                 }
1090                 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1091                 else if (char_index_in_source == 0) new_span.rotate = 0.f;
1092                 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1093                     // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1094                     // so that the top of the letters is at zero, not the baseline
1095                     new_span.y = 0.0;
1096                 }
1097                 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1098                 iter_text++;
1099                 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1100                     if (iter_text >= text_source->text_end) break;
1101                     if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1102                     if (   i >= text_source->x.size() && i >= text_source->y.size()
1103                         && i >= text_source->dx.size() && i >= text_source->dy.size()
1104                         && i >= text_source->rotate.size()) break;
1105                     if (   (text_source->x.size()  > i && text_source->x[i]._set)
1106                         || (text_source->y.size()  > i && text_source->y[i]._set)
1107                         || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1108                         || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1109                         || (text_source->rotate.size() > i && text_source->rotate[i]._set
1110                             && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1111                         new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1112                         break;
1113                     }
1114                 }
1116                 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1117                 new_span.font_size = text_source->styleComputeFontSize();
1118                 if (new_span.text_bytes) {
1119                     new_span.glyph_string = pango_glyph_string_new();
1120                     /* Some assertions intended to help diagnose bug #1277746. */
1121                     g_assert( 0 < new_span.text_bytes );
1122                     g_assert( span_start_byte_in_source < text_source->text->bytes() );
1123                     g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1124                     g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1125                               == NULL );
1126                     pango_shape(text_source->text->data() + span_start_byte_in_source,
1127                                 new_span.text_bytes,
1128                                 &para->pango_items[pango_item_index].item->analysis,
1129                                 new_span.glyph_string);
1131                     if (para->pango_items[pango_item_index].item->analysis.level & 1) {
1132                         // pango_shape() will reorder glyphs in rtl sections into visual order which messes
1133                         // us up because the svg spec requires us to draw glyphs in logical order
1134                         // let's reverse the glyphstring on a cluster-by-cluster basis
1135                         const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1136                         std::vector<PangoGlyphInfo> infos(nglyphs);
1137                         std::vector<gint> clusters(nglyphs);
1138                         unsigned i, cluster_start = 0;
1140                         for (i = 0 ; i < nglyphs ; ++i) {
1141                             if (new_span.glyph_string->glyphs[i].attr.is_cluster_start) {
1142                                 if (i != cluster_start) {
1143                                     std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
1144                                     std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
1145                                 }
1146                                 cluster_start = i;
1147                             }
1148                         }
1149                         if (i != cluster_start) {
1150                             std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
1151                             std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
1152                         }
1153                         std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
1154                         std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
1155                     }
1156                     new_span.pango_item_index = pango_item_index;
1157                     _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1159                     // At some point we may want to calculate baseline_shift here (to take advantage
1160                     // of otm features like superscript baseline), but for now we use style baseline_shift.
1161                     new_span.baseline_shift = text_source->style->baseline_shift.computed;
1163                     // TODO: metrics for vertical text
1164                     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()));
1165                     TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
1166                 } else {
1167                     // if there's no text we still need to initialise the styles
1168                     new_span.pango_item_index = -1;
1169                     font_instance *font = text_source->styleGetFontInstance();
1170                     if (font) {
1171                         _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1172                         font->Unref();
1173                     } else {
1174                         new_span.line_height.setZero();
1175                         new_span.line_height_multiplier = 1.0;
1176                     }
1177                     TRACE(("add style init span %d\n", para->unbroken_spans.size()));
1178                 }
1179                 para->unbroken_spans.push_back(new_span);
1181                 // calculations for moving to the next UnbrokenSpan
1182                 byte_index_in_para += new_span.text_bytes;
1183                 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1185                 if (new_span.text_bytes >= pango_item_bytes) {   // end of pango item
1186                     pango_item_index++;
1187                     if (pango_item_index == para->pango_items.size()) break;  // end of paragraph
1188                 }
1189                 if (new_span.text_bytes == text_source_bytes)
1190                     break;    // end of source
1191                 // else <tspan> attribute changed
1192                 span_start_byte_in_source += new_span.text_bytes;
1193             }
1194             char_index_in_para += char_index_in_source;
1195         }
1196     }
1197     TRACE(("end build spans\n"));
1198     return input_index;
1201 /**
1202  * Reinitialises the variables required on completion of one shape and
1203  * moving on to the next. Returns false if there are no more shapes to wrap
1204  * in to.
1205  */
1206 bool Layout::Calculator::_goToNextWrapShape()
1208     delete _scanline_maker;
1209     _scanline_maker = NULL;
1210     _current_shape_index++;
1211     if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
1212     _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1213     TRACE(("begin wrap shape %d\n", _current_shape_index));
1214     return true;
1217 /**
1218  * Given \a para filled in and \a start_span_pos set, keeps trying to
1219  * find somewhere it can fit the next line of text. The process of finding
1220  * the text that fits will involve creating one or more entries in
1221  * \a chunk_info describing the bounds of the fitted text and several
1222  * bits of information that will prove useful when we come to output the
1223  * line to #_flow. Returns with \a start_span_pos set to the end of the
1224  * text that was fitted, \a chunk_info completely filled out and
1225  * \a line_height set to the largest line box on the line. The return
1226  * value is false only if we've run out of shapes to wrap inside (and
1227  * hence couldn't create any chunks).
1228  */
1229 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
1230                                             UnbrokenSpanPosition *start_span_pos,
1231                                             std::vector<ChunkInfo> *chunk_info,
1232                                             LineHeight *line_height)
1234     // init the initial line_height
1235     if (start_span_pos->iter_span == para.unbroken_spans.end()) {
1236         if (_flow._spans.empty()) {
1237             // empty first para: create a font for the sole purpose of measuring it
1238             InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1239             font_instance *font = text_source->styleGetFontInstance();
1240             if (font) {
1241                 double font_size = text_source->styleComputeFontSize();
1242                 double multiplier;
1243                 _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
1244                 font->Unref();
1245                 *line_height *= multiplier;
1246                 _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
1247             }
1248         }
1249         // else empty subsequent para: keep the old line height
1250     } else {
1251         if (_flow._input_wrap_shapes.empty()) {
1252             // if we're not wrapping set the line_height big and negative so we can use negative line height
1253             line_height->ascent = -1.0e10;
1254             line_height->descent = -1.0e10;
1255             line_height->leading = -1.0e10;
1256         }
1257         else
1258             line_height->setZero();
1259     }
1261     UnbrokenSpanPosition span_pos;
1262     for( ; ; ) {
1263         std::vector<ScanlineMaker::ScanRun> scan_runs;
1264         scan_runs = _scanline_maker->makeScanline(*line_height); // Only one line with "InfiniteScanlineMaker
1265         while (scan_runs.empty()) {
1266             // Only used by ShapeScanlineMaker
1267             if (!_goToNextWrapShape()) return false;  // no more shapes to wrap in to
1268             scan_runs = _scanline_maker->makeScanline(*line_height);
1269         }
1271         TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
1272         chunk_info->clear();
1273         chunk_info->reserve(scan_runs.size());
1274         if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1275         unsigned scan_run_index;
1276         span_pos = *start_span_pos;
1277         for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1278             if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
1279                 break;
1280             if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
1281                 span_pos = chunk_info->back().broken_spans.back().end;
1282         }
1283         if (scan_run_index == scan_runs.size()) break;  // ie when buildChunksInScanRun() succeeded
1284     }
1285     *start_span_pos = span_pos;
1286     return true;
1289 /**
1290  * Given a scan run and a first character, append one or more chunks to
1291  * the \a chunk_info vector that describe all the spans and other detail
1292  * necessary to output the greatest amount of text that will fit on this scan
1293  * line (greedy line breaking algorithm). Each chunk contains one or more
1294  * BrokenSpan structures that link back to UnbrokenSpan structures that link
1295  * to the text itself. Normally there will be either one or zero (if the
1296  * scanrun is too short to fit any text) chunk added to \a chunk_info by
1297  * each call to this method, but we will add more than one if an x or y
1298  * attribute has been set on a tspan. \a line_height must be set on input,
1299  * and if it needs to be made larger and the #_scanline_maker can't do
1300  * an in-situ resize then it will be set to the required value and the
1301  * method will return false.
1302  */
1303 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
1304                                                UnbrokenSpanPosition const &start_span_pos,
1305                                                ScanlineMaker::ScanRun const &scan_run,
1306                                                std::vector<ChunkInfo> *chunk_info,
1307                                                LineHeight *line_height) const
1309     ChunkInfo new_chunk;
1310     new_chunk.text_width = 0.0;
1311     new_chunk.whitespace_count = 0;
1312     new_chunk.scanrun_width = scan_run.width();
1313     new_chunk.x = scan_run.x_start;
1315     // we haven't done anything yet so the last valid break position is the beginning
1316     BrokenSpan last_span_at_break, last_span_at_emergency_break;
1317     last_span_at_break.start = start_span_pos;
1318     last_span_at_break.setZero();
1319     last_span_at_emergency_break.start = start_span_pos;
1320     last_span_at_emergency_break.setZero();
1322     TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1323     BrokenSpan new_span;
1324     new_span.end = start_span_pos;
1325     while (new_span.end.iter_span != para.unbroken_spans.end()) {    // this loops once for each UnbrokenSpan
1327         new_span.start = new_span.end;
1329         // force a chunk change at x or y attribute change
1330         if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1332             if (new_span.start.iter_span != start_span_pos.iter_span)
1333                 chunk_info->push_back(new_chunk);
1335             new_chunk.x += new_chunk.text_width;
1336             new_chunk.text_width = 0.0;
1337             new_chunk.whitespace_count = 0;
1338             new_chunk.broken_spans.clear();
1339             if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1340             // y doesn't need to be done until output time
1341         }
1343         // see if this span is too tall to fit on the current line
1344         LineHeight total_height = new_span.start.iter_span->line_height;
1345         total_height *= new_span.start.iter_span->line_height_multiplier;
1346         /* floating point 80-bit/64-bit rounding problems require epsilon. See
1347            discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1348         if (   total_height.ascent  > line_height->ascent  + FLT_EPSILON
1349                || total_height.descent > line_height->descent + FLT_EPSILON
1350                || total_height.leading > line_height->leading + FLT_EPSILON) {
1351             line_height->max(total_height);
1352             if (!_scanline_maker->canExtendCurrentScanline(*line_height))
1353                 return false;
1354         }
1356         bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1358         new_chunk.text_width += new_span.width;
1359         new_chunk.whitespace_count += new_span.whitespace_count;
1360         new_chunk.broken_spans.push_back(new_span);   // if !span_fitted we'll correct ourselves below
1362         if (!span_fitted) break;
1364         if (new_span.end.iter_span == para.unbroken_spans.end()) {
1365             last_span_at_break = new_span;
1366             break;
1367         }
1368     }
1370     TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1371     chunk_info->push_back(new_chunk);
1373     if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
1374         /* **non-SVG spec bit**: See bug #1191102
1375            If the user types a very long line with no spaces, the way the spec
1376            is written at the moment means that when the length of the text
1377            exceeds the available width of all remaining areas, the text is
1378            completely hidden. This condition alters that behaviour so that if
1379            the length of the line is greater than four times the line-height
1380            and there are no spaces, it'll be emergency-wrapped at the last
1381            character. One could read the SVG Tiny 1.2 draft as permitting this
1382            sort of behaviour, but it's still a bit dodgy. The hard-coding of
1383            4x is not nice, either. */
1384         last_span_at_break = last_span_at_emergency_break;
1385     }
1387     if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
1388         // need to back out spans until we come to the one with the last break in it
1389         while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
1390             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1391             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1392             chunk_info->back().broken_spans.pop_back();
1393             if (chunk_info->back().broken_spans.empty())
1394                 chunk_info->pop_back();
1395         }
1396         if (!chunk_info->empty()) {
1397             chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1398             chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1399             if (last_span_at_break.start == last_span_at_break.end) {
1400                 chunk_info->back().broken_spans.pop_back();   // last break was at an existing boundary
1401                 if (chunk_info->back().broken_spans.empty())
1402                     chunk_info->pop_back();
1403             } else {
1404                 chunk_info->back().broken_spans.back() = last_span_at_break;
1405                 chunk_info->back().text_width += last_span_at_break.width;
1406                 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
1407             }
1408             TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
1409         }
1410     }
1412     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
1413         // for justification we need to discard space occupied by the single whitespace at the end of the chunk
1414         chunk_info->back().broken_spans.back().ends_with_whitespace = false;
1415         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1416         chunk_info->back().broken_spans.back().whitespace_count--;
1417         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1418         chunk_info->back().whitespace_count--;
1419     }
1421     if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
1422         // for justification we need to discard line-spacing and word-spacing at end of the chunk
1423         chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().letter_spacing;
1424         chunk_info->back().text_width -= chunk_info->back().broken_spans.back().letter_spacing;
1425         TRACE(("width after subtracting last letter_spacing: %f\n", chunk_info->back().broken_spans.back().width));
1426     }
1428     return true;
1431 /** The management function to start the whole thing off. */
1432 bool Layout::Calculator::calculate()
1434     if (_flow._input_stream.empty())
1435         return false;
1436     /**
1437     * hm, why do we want assert (crash) the application, now do simply return false
1438     * \todo check if this is the correct behaviour
1439     * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
1440     */
1441     if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
1442     {
1443         g_warning("flow text is not of type TEXT_SOURCE. Abort.");
1444         return false;
1445     }
1446     TRACE(("begin calculateFlow()\n"));
1448     _flow._clearOutputObjects();
1450     _pango_context = (font_factory::Default())->fontContext;
1451     _font_factory_size_multiplier = (font_factory::Default())->fontSize;
1453     _block_progression = _flow._blockProgression();
1454     _y_offset = 0.0;
1455     _createFirstScanlineMaker();
1457     ParagraphInfo para;
1458     LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras
1459     for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
1460         // jump to the next wrap shape if this is a SHAPE_BREAK control code
1461         if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
1462             InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
1463             if (control_code->code == SHAPE_BREAK) {
1464                 TRACE(("shape break control code\n"));
1465                 if (!_goToNextWrapShape()) break;
1466                 continue;
1467             }
1468         }
1469         if (_scanline_maker == NULL)
1470             break;       // we're trying to flow past the last wrap shape
1472         _buildPangoItemizationForPara(&para);
1473         unsigned para_end_input_index = _buildSpansForPara(&para);
1475         if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
1476             para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
1477         else
1478             para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
1480         TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
1481         Layout::Paragraph new_paragraph;
1482         new_paragraph.base_direction = para.direction;
1483         new_paragraph.alignment = para.alignment;
1484         _flow._paragraphs.push_back(new_paragraph);
1486         // start scanning lines
1487         UnbrokenSpanPosition span_pos;
1488         span_pos.iter_span = para.unbroken_spans.begin();
1489         span_pos.char_byte = 0;
1490         span_pos.char_index = 0;
1492         do {   // for each line in the paragraph
1493             TRACE(("begin line\n"));
1494             std::vector<ChunkInfo> line_chunk_info;
1495             if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
1496                 break;   // out of shapes to wrap in to
1498             _outputLine(para, line_height, line_chunk_info);
1499             _scanline_maker->completeLine(); // Increments y by line height
1500         } while (span_pos.iter_span != para.unbroken_spans.end());
1502         TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
1503         if (_scanline_maker != NULL) {
1504             bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
1505             if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
1506                 || para_end_input_index + 1 < _flow._input_stream.size()) {
1507                 // we need a span just for the para if it's either an empty last para or a break in the middle
1508                 Layout::Span new_span;
1509                 if (_flow._spans.empty()) {
1510                     new_span.font = NULL;
1511                     new_span.font_size = line_height.ascent + line_height.descent;
1512                     new_span.line_height = line_height;
1513                     new_span.x_end = 0.0;
1514                 } else {
1515                     new_span = _flow._spans.back();
1516                     if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
1517                         new_span.x_end = 0.0;
1518                 }
1519                 new_span.in_chunk = _flow._chunks.size() - 1;
1520                 if (new_span.font)
1521                     new_span.font->Ref();
1522                 new_span.x_start = new_span.x_end;
1523                 new_span.baseline_shift = 0.0;
1524                 new_span.direction = para.direction;
1525                 new_span.block_progression = _block_progression;
1526                 if (para_end_input_index == _flow._input_stream.size())
1527                     new_span.in_input_stream_item = _flow._input_stream.size() - 1;
1528                 else
1529                     new_span.in_input_stream_item = para_end_input_index;
1530                 _flow._spans.push_back(new_span);
1531             }
1532             if (para_end_input_index + 1 < _flow._input_stream.size()) {
1533                 // we've got to add an invisible character between paragraphs so that we can position iterators
1534                 // (and hence cursors) both before and after the paragraph break
1535                 Layout::Character new_character;
1536                 new_character.in_span = _flow._spans.size() - 1;
1537                 new_character.char_attributes.is_line_break = 1;
1538                 new_character.char_attributes.is_mandatory_break = 1;
1539                 new_character.char_attributes.is_char_break = 1;
1540                 new_character.char_attributes.is_white = 1;
1541                 new_character.char_attributes.is_cursor_position = 1;
1542                 new_character.char_attributes.is_word_start = 0;
1543                 new_character.char_attributes.is_word_end = 1;
1544                 new_character.char_attributes.is_sentence_start = 0;
1545                 new_character.char_attributes.is_sentence_end = 1;
1546                 new_character.char_attributes.is_sentence_boundary = 1;
1547                 new_character.char_attributes.backspace_deletes_character = 1;
1548                 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
1549                 new_character.in_glyph = -1;
1550                 _flow._characters.push_back(new_character);
1551             }
1552         }
1553         para.free();
1554         para.first_input_index = para_end_input_index + 1;
1555     }
1557     para.free();
1558     if (_scanline_maker) {
1559         delete _scanline_maker;
1560         _flow._input_truncated = false;
1561     } else {
1562         _flow._input_truncated = true;
1563     }
1565     return true;
1568 void Layout::_calculateCursorShapeForEmpty()
1570     _empty_cursor_shape.position = Geom::Point(0, 0);
1571     _empty_cursor_shape.height = 0.0;
1572     _empty_cursor_shape.rotation = 0.0;
1573     if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
1574         return;
1576     InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
1578     font_instance *font = text_source->styleGetFontInstance();
1579     double font_size = text_source->styleComputeFontSize();
1580     double caret_slope_run = 0.0, caret_slope_rise = 1.0;
1581     LineHeight line_height;
1582     if (font) {
1583         const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
1584         font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
1585         line_height *= font_size;
1586         font->Unref();
1587     } else {
1588         line_height.ascent = font_size * 0.85;      // random guesses
1589         line_height.descent = font_size * 0.15;
1590         line_height.leading = 0.0;
1591     }
1592     double caret_slope = atan2(caret_slope_run, caret_slope_rise);
1593     _empty_cursor_shape.height = font_size / cos(caret_slope);
1594     _empty_cursor_shape.rotation = caret_slope;
1596     if (_input_wrap_shapes.empty()) {
1597         _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
1598                                                  text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
1599     } else {
1600         Direction block_progression = text_source->styleGetBlockProgression();
1601         ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
1602         std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
1603         if (!scan_runs.empty()) {
1604             if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
1605                 _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
1606             else
1607                 _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
1608         }
1609     }
1612 bool Layout::calculateFlow()
1614     bool result = Calculator(this).calculate();
1615     if (_characters.empty())
1616         _calculateCursorShapeForEmpty();
1617     return result;
1620 }//namespace Text
1621 }//namespace Inkscape
1624 /*
1625   Local Variables:
1626   mode:c++
1627   c-file-style:"stroustrup"
1628   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1629   indent-tabs-mode:nil
1630   fill-column:99
1631   End:
1632 */
1633 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :