Code

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