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