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