Code

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