f6b9688bb17ca6659b97a54be8239a73b9fe01a1
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 ¶, UnbrokenSpanPosition *start_span_pos,
208 std::vector<ChunkInfo> *chunk_info, LineHeight *line_height);
210 static inline PangoLogAttr const &_charAttributes(ParagraphInfo const ¶,
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 ¶,
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 ¶, 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 ¶, 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 ¶, 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()
682 {
683 if (sub_flow) {
684 delete sub_flow;
685 sub_flow = NULL;
686 }
687 }
689 void Layout::Calculator::PangoItemInfo::free()
690 {
691 if (item) {
692 pango_item_free(item);
693 item = NULL;
694 }
695 if (font) {
696 font->Unref();
697 font = NULL;
698 }
699 }
701 void Layout::Calculator::UnbrokenSpanPosition::increment()
702 {
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 }
710 }
712 void Layout::Calculator::BrokenSpan::setZero()
713 {
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;
720 }
722 template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
723 {
724 for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
725 it->free();
726 }
727 seq.clear();
728 }
730 void Layout::Calculator::ParagraphInfo::free()
731 {
732 free_sequence(input_items);
733 free_sequence(pango_items);
734 free_sequence(unbroken_spans);
735 }
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
812 {
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));
889 }
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)
900 {
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();
937 }
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
947 {
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 ¶->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;
1119 }
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()
1127 {
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;
1135 }
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 ¶,
1150 UnbrokenSpanPosition *start_span_pos,
1151 std::vector<ChunkInfo> *chunk_info,
1152 LineHeight *line_height)
1153 {
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;
1206 }
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 ¶,
1223 UnbrokenSpanPosition const &start_span_pos,
1224 ScanlineMaker::ScanRun const &scan_run,
1225 std::vector<ChunkInfo> *chunk_info,
1226 LineHeight *line_height) const
1227 {
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;
1341 }
1343 /** The management function to start the whole thing off. */
1344 bool Layout::Calculator::calculate()
1345 {
1346 if (_flow._input_stream.empty())
1347 return false;
1348 /**
1349 * hm, why do we want assert (crash) the application, now do simply return false
1350 * \todo check if this is the correct behaviour
1351 * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
1352 */
1353 if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
1354 {
1355 g_warning("flow text is not of type TEXT_SOURCE. Abort.");
1356 return false;
1357 }
1358 TRACE(("begin calculateFlow()\n"));
1360 _flow._clearOutputObjects();
1362 _pango_context = (font_factory::Default())->fontContext;
1363 _font_factory_size_multiplier = (font_factory::Default())->fontSize;
1365 _block_progression = _flow._blockProgression();
1366 _y_offset = 0.0;
1367 _createFirstScanlineMaker();
1369 ParagraphInfo para;
1370 LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
1371 for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
1372 // jump to the next wrap shape if this is a SHAPE_BREAK control code
1373 if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
1374 InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
1375 if (control_code->code == SHAPE_BREAK) {
1376 TRACE(("shape break control code\n"));
1377 if (!_goToNextWrapShape()) break;
1378 continue;
1379 }
1380 }
1381 if (_scanline_maker == NULL)
1382 break; // we're trying to flow past the last wrap shape
1384 _buildPangoItemizationForPara(¶);
1385 unsigned para_end_input_index = _buildSpansForPara(¶);
1387 if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
1388 para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
1389 else
1390 para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
1392 TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
1393 Layout::Paragraph new_paragraph;
1394 new_paragraph.base_direction = para.direction;
1395 new_paragraph.alignment = para.alignment;
1396 _flow._paragraphs.push_back(new_paragraph);
1398 // start scanning lines
1399 UnbrokenSpanPosition span_pos;
1400 span_pos.iter_span = para.unbroken_spans.begin();
1401 span_pos.char_byte = 0;
1402 span_pos.char_index = 0;
1404 do { // for each line in the paragraph
1405 TRACE(("begin line\n"));
1406 std::vector<ChunkInfo> line_chunk_info;
1407 if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
1408 break; // out of shapes to wrap in to
1410 _outputLine(para, line_height, line_chunk_info);
1411 _scanline_maker->completeLine();
1412 } while (span_pos.iter_span != para.unbroken_spans.end());
1414 TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
1415 if (_scanline_maker != NULL) {
1416 bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
1417 if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
1418 || para_end_input_index + 1 < _flow._input_stream.size()) {
1419 // we need a span just for the para if it's either an empty last para or a break in the middle
1420 Layout::Span new_span;
1421 if (_flow._spans.empty()) {
1422 new_span.font = NULL;
1423 new_span.font_size = line_height.ascent + line_height.descent;
1424 new_span.line_height = line_height;
1425 new_span.x_end = 0.0;
1426 } else {
1427 new_span = _flow._spans.back();
1428 if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
1429 new_span.x_end = 0.0;
1430 }
1431 new_span.in_chunk = _flow._chunks.size() - 1;
1432 if (new_span.font)
1433 new_span.font->Ref();
1434 new_span.x_start = new_span.x_end;
1435 new_span.baseline_shift = 0.0;
1436 new_span.direction = para.direction;
1437 new_span.block_progression = _block_progression;
1438 if (para_end_input_index == _flow._input_stream.size())
1439 new_span.in_input_stream_item = _flow._input_stream.size() - 1;
1440 else
1441 new_span.in_input_stream_item = para_end_input_index;
1442 _flow._spans.push_back(new_span);
1443 }
1444 if (para_end_input_index + 1 < _flow._input_stream.size()) {
1445 // we've got to add an invisible character between paragraphs so that we can position iterators
1446 // (and hence cursors) both before and after the paragraph break
1447 Layout::Character new_character;
1448 new_character.in_span = _flow._spans.size() - 1;
1449 new_character.char_attributes.is_line_break = 1;
1450 new_character.char_attributes.is_mandatory_break = 1;
1451 new_character.char_attributes.is_char_break = 1;
1452 new_character.char_attributes.is_white = 1;
1453 new_character.char_attributes.is_cursor_position = 1;
1454 new_character.char_attributes.is_word_start = 0;
1455 new_character.char_attributes.is_word_end = 1;
1456 new_character.char_attributes.is_sentence_start = 0;
1457 new_character.char_attributes.is_sentence_end = 1;
1458 new_character.char_attributes.is_sentence_boundary = 1;
1459 new_character.char_attributes.backspace_deletes_character = 1;
1460 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
1461 new_character.in_glyph = -1;
1462 _flow._characters.push_back(new_character);
1463 }
1464 }
1465 para.free();
1466 para.first_input_index = para_end_input_index + 1;
1467 }
1469 para.free();
1470 if (_scanline_maker) {
1471 delete _scanline_maker;
1472 _flow._input_truncated = false;
1473 } else {
1474 _flow._input_truncated = true;
1475 }
1477 return true;
1478 }
1480 void Layout::_calculateCursorShapeForEmpty()
1481 {
1482 _empty_cursor_shape.position = Geom::Point(0, 0);
1483 _empty_cursor_shape.height = 0.0;
1484 _empty_cursor_shape.rotation = 0.0;
1485 if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
1486 return;
1488 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
1490 font_instance *font = text_source->styleGetFontInstance();
1491 double font_size = text_source->styleComputeFontSize();
1492 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
1493 LineHeight line_height;
1494 if (font) {
1495 const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
1496 font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
1497 line_height *= font_size;
1498 font->Unref();
1499 } else {
1500 line_height.ascent = font_size * 0.85; // random guesses
1501 line_height.descent = font_size * 0.15;
1502 line_height.leading = 0.0;
1503 }
1504 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
1505 _empty_cursor_shape.height = font_size / cos(caret_slope);
1506 _empty_cursor_shape.rotation = caret_slope;
1508 if (_input_wrap_shapes.empty()) {
1509 _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
1510 text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
1511 } else {
1512 Direction block_progression = text_source->styleGetBlockProgression();
1513 ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
1514 std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
1515 if (!scan_runs.empty()) {
1516 if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
1517 _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
1518 else
1519 _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
1520 }
1521 }
1522 }
1524 bool Layout::calculateFlow()
1525 {
1526 bool result = Calculator(this).calculate();
1527 if (_characters.empty())
1528 _calculateCursorShapeForEmpty();
1529 return result;
1530 }
1532 }//namespace Text
1533 }//namespace Inkscape
1536 /*
1537 Local Variables:
1538 mode:c++
1539 c-file-style:"stroustrup"
1540 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1541 indent-tabs-mode:nil
1542 fill-column:99
1543 End:
1544 */
1545 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :