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 added: 1 June 2010:
449 // The first line in a normal <text> object is placed by the read-in "y" value. The rest are
450 // determined by Inkscape. This is to allow insertation of new lines in the middle
451 // of a <text> object. New "y" values are then stored in each <tspan> that represents
452 // a new line. The line spacing should depend only on font-size and line-height (and not
453 // on any y-kerning). Line spacing is already handled by the calling routine. Note that
454 // this may render improperly any SVG with <tspan>s created/edited by other programs.
455 if (!it_chunk->broken_spans.empty() // Not empty paragraph
456 && it_chunk->broken_spans.front().start.char_byte == 0 ) { // Beginning of unbroken span
458 // If empty or new line
459 if( _flow._characters.empty() ||
460 _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
462 // If <tspan> "y" attribute is set, use it (initial "y" attributes in
463 // <tspans> other than the first have already been stripped).
464 if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
466 // Use set "y" attribute
467 new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
469 // Save baseline
470 _flow._lines.back().baseline_y = new_line.baseline_y;
472 // Save new <tspan> y coordinate
473 _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
475 }
477 // Reset relative y_offset ("dy" attribute is relative but should be reset at
478 // the beginning of each <tspan>.)
479 _y_offset = 0.0;
481 }
482 }
483 _flow._chunks.push_back(new_chunk);
485 double x;
486 double direction_sign;
487 Direction previous_direction = para.direction;
488 double counter_directional_width_remaining = 0.0;
489 float glyph_rotate = 0.0;
490 if (para.direction == LEFT_TO_RIGHT) {
491 direction_sign = +1.0;
492 x = 0.0;
493 } else {
494 direction_sign = -1.0;
495 if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
496 x = it_chunk->scanrun_width;
497 else
498 x = it_chunk->text_width;
499 }
501 for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
502 // begin adding spans to the list
503 UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
505 if (it_span->start.char_byte == 0) {
506 // Start of an unbroken span, we might have dx, dy or rotate still to process
507 // (x and y are done per chunk)
508 if (unbroken_span.dx._set) x += unbroken_span.dx.computed;
509 if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
510 if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
511 }
513 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
514 && unbroken_span.pango_item_index == -1) {
515 // style only, nothing to output
516 continue;
517 }
519 Layout::Span new_span;
520 double x_in_span = 0.0;
522 new_span.in_chunk = _flow._chunks.size() - 1;
523 new_span.line_height = unbroken_span.line_height;
524 new_span.in_input_stream_item = unbroken_span.input_index;
525 new_span.baseline_shift = 0.0;
526 new_span.block_progression = _block_progression;
527 if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
528 {
529 new_span.font->Ref();
530 new_span.font_size = unbroken_span.font_size;
531 new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
532 new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
533 } else { // a control code
534 new_span.font = NULL;
535 new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
536 new_span.direction = para.direction;
537 }
539 if (new_span.direction == para.direction) {
540 x -= counter_directional_width_remaining;
541 counter_directional_width_remaining = 0.0;
542 } else if (new_span.direction != previous_direction) {
543 // measure width of spans we need to switch round
544 counter_directional_width_remaining = 0.0;
545 std::vector<BrokenSpan>::const_iterator it_following_span;
546 for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
547 Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
548 if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
549 if (it_following_span->start.iter_span->pango_item_index == -1) { // when the span came from a control code
550 if (new_span.direction != para.direction) break;
551 } else
552 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;
553 }
554 counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
555 }
556 x += counter_directional_width_remaining;
557 counter_directional_width_remaining = 0.0; // we want to go increasingly negative
558 }
559 new_span.x_start = x;
561 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
562 // the span is set up, push the glyphs and chars
563 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
564 Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
565 unsigned char_index_in_unbroken_span = it_span->start.char_index;
566 unsigned cluster_start_char_index = _flow._characters.size();
567 double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
569 for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
570 unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
571 if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
572 cluster_start_char_index = _flow._characters.size();
574 if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
575 && *iter_source_text == UNICODE_SOFT_HYPHEN
576 && glyph_index + 1 != it_span->end_glyph_index) {
577 // if we're looking at a soft hyphen and it's not the last glyph in the
578 // chunk we don't draw the glyph but we still need to add to _characters
579 Layout::Character new_character;
580 new_character.in_span = _flow._spans.size(); // the span hasn't been added yet, so no -1
581 new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
582 new_character.in_glyph = -1;
583 _flow._characters.push_back(new_character);
584 iter_source_text++;
585 char_index_in_unbroken_span++;
586 while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
587 && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
588 glyph_index++;
589 glyph_index--;
590 continue;
591 }
593 // create the Layout::Glyph
594 Layout::Glyph new_glyph;
595 new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
596 new_glyph.in_character = cluster_start_char_index;
597 new_glyph.rotation = glyph_rotate;
599 /* put something like this back in when we do glyph-rotation-horizontal/vertical
600 if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
601 new_glyph.x += new_span.line_height.ascent;
602 new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
603 new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
604 } else */
606 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
607 new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
608 new_glyph.y = _y_offset -
609 unbroken_span.baseline_shift +
610 (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset -
611 unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
612 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);
613 } else {
614 new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
615 new_glyph.y = _y_offset -
616 unbroken_span.baseline_shift +
617 unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
618 new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
619 if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font))
620 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);
621 // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
622 }
623 if (new_span.direction == RIGHT_TO_LEFT) {
624 // pango wanted to give us glyphs in visual order but we refused, so we need to work
625 // out where the cluster start is ourselves
626 double cluster_width = 0.0;
627 for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
628 if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
629 break;
630 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
631 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);
632 else
633 cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
634 }
635 new_glyph.x -= cluster_width;
636 }
637 _flow._glyphs.push_back(new_glyph);
639 // create the Layout::Character(s)
640 double advance_width = new_glyph.width;
641 unsigned end_byte;
642 if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
643 end_byte = it_span->start.iter_span->text_bytes;
644 else {
645 // output chars for the whole cluster that is commenced by this glyph
646 if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
647 int next_cluster_glyph_index = glyph_index + 1;
648 while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
649 && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
650 next_cluster_glyph_index++;
651 if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
652 end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
653 else
654 end_byte = it_span->start.iter_span->text_bytes;
655 } else
656 end_byte = char_byte; // don't output any chars if we're not at the start of a cluster
657 }
658 while (char_byte < end_byte) {
659 Layout::Character new_character;
660 new_character.in_span = _flow._spans.size();
661 new_character.x = x_in_span;
662 new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
663 new_character.in_glyph = _flow._glyphs.size() - 1;
664 _flow._characters.push_back(new_character);
665 if (new_character.char_attributes.is_white)
666 advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace; // justification
667 if (new_character.char_attributes.is_cursor_position)
668 advance_width += text_source->style->letter_spacing.computed;
669 iter_source_text++;
670 char_index_in_unbroken_span++;
671 char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
672 }
674 advance_width *= direction_sign;
675 if (new_span.direction != para.direction) {
676 counter_directional_width_remaining -= advance_width;
677 x -= advance_width;
678 x_in_span -= advance_width;
679 } else {
680 x += advance_width;
681 x_in_span += advance_width;
682 }
683 }
684 } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
685 x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
686 }
688 new_span.x_end = new_span.x_start + x_in_span;
689 _flow._spans.push_back(new_span);
690 previous_direction = new_span.direction;
691 }
692 // end adding spans to the list, on to the next chunk...
693 }
694 TRACE(("output done\n"));
695 }
697 /* *********************************************************************************************************/
698 // Setup and top-level functions
700 /** initialises the ScanlineMaker for the first shape in the flow, or
701 the infinite version if we're not doing wrapping. */
702 void _createFirstScanlineMaker()
703 {
704 _current_shape_index = 0;
705 if (_flow._input_wrap_shapes.empty()) {
706 // create the special no-wrapping infinite scanline maker
707 double initial_x = 0, initial_y = 0;
708 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
709 if (!text_source->x.empty())
710 initial_x = text_source->x.front().computed;
711 if (!text_source->y.empty())
712 initial_y = text_source->y.front().computed;
713 _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
714 TRACE((" wrapping disabled\n"));
715 }
716 else {
717 _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
718 TRACE((" begin wrap shape 0\n"));
719 }
720 }
722 public:
723 Calculator(Layout *text_flow)
724 : _flow(*text_flow) {}
726 bool calculate();
727 };
729 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
730 * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
731 * that does delete or Unref.
732 *
733 * I suggest using the garbage collector to manage deletion.
734 */
735 void Layout::Calculator::InputItemInfo::free()
736 {
737 if (sub_flow) {
738 delete sub_flow;
739 sub_flow = NULL;
740 }
741 }
743 void Layout::Calculator::PangoItemInfo::free()
744 {
745 if (item) {
746 pango_item_free(item);
747 item = NULL;
748 }
749 if (font) {
750 font->Unref();
751 font = NULL;
752 }
753 }
755 void Layout::Calculator::UnbrokenSpanPosition::increment()
756 {
757 gchar const *text_base = &*iter_span->input_stream_first_character.base();
758 char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
759 char_index++;
760 if (char_byte == iter_span->text_bytes) {
761 iter_span++;
762 char_index = char_byte = 0;
763 }
764 }
766 void Layout::Calculator::BrokenSpan::setZero()
767 {
768 end = start;
769 width = 0.0;
770 whitespace_count = 0;
771 end_glyph_index = start_glyph_index = 0;
772 ends_with_whitespace = false;
773 each_whitespace_width = 0.0;
774 }
776 template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
777 {
778 for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
779 it->free();
780 }
781 seq.clear();
782 }
784 void Layout::Calculator::ParagraphInfo::free()
785 {
786 free_sequence(input_items);
787 free_sequence(pango_items);
788 free_sequence(unbroken_spans);
789 }
791 ///**
792 // * For sections of text with a block-progression different to the rest
793 // * of the flow, the best thing to do is to detect them in advance and
794 // * create child TextFlow objects with just the rotated text. In the
795 // * parent we then effectively use ARBITRARY_GAP fields during the
796 // * flowing (because we don't allow wrapping when the block-progression
797 // * changes) and copy the actual text in during the output phase.
798 // *
799 // * NB: this code not enabled yet.
800 // */
801 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
802 //{
803 // Direction prev_block_progression = _block_progression;
804 // int run_start_input_index = para->first_input_index;
805 //
806 // para->free_sequence(para->input_items);
807 // for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
808 // InputItemInfo input_item;
809 //
810 // input_item.in_sub_flow = false;
811 // input_item.sub_flow = NULL;
812 // if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
813 // Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
814 // if ( control_code->code == SHAPE_BREAK
815 // || control_code->code == PARAGRAPH_BREAK)
816 // break; // stop at the end of the paragraph
817 // // all other control codes we'll pick up later
818 //
819 // } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
820 // Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
821 // Direction this_block_progression = text_source->styleGetBlockProgression();
822 // if (this_block_progression != prev_block_progression) {
823 // if (prev_block_progression != _block_progression) {
824 // // need to back up so that control codes belong outside the block-progression change
825 // int run_end_input_index = input_index - 1;
826 // while (run_end_input_index > run_start_input_index
827 // && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
828 // run_end_input_index--;
829 // // now create the sub-flow
830 // input_item.sub_flow = new Layout;
831 // for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
832 // input_item.in_sub_flow = true;
833 // if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
834 // Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
835 // input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
836 // } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
837 // Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
838 // 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);
839 // Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
840 // sub_flow_text_source->x = text_source->x; // this is easier than going via optionalattrs for the appendText() call
841 // sub_flow_text_source->y = text_source->y; // should these actually be allowed anyway? You'll almost never get the results you expect
842 // sub_flow_text_source->dx = text_source->dx; // (not that it's very clear what you should expect, anyway)
843 // sub_flow_text_source->dy = text_source->dy;
844 // sub_flow_text_source->rotate = text_source->rotate;
845 // }
846 // }
847 // input_item.sub_flow->calculateFlow();
848 // }
849 // run_start_input_index = input_index;
850 // }
851 // prev_block_progression = this_block_progression;
852 // }
853 // para->input_items.push_back(input_item);
854 // }
855 //}
857 /**
858 * Take all the text from \a _para.first_input_index to the end of the
859 * paragraph and stitch it together so that pango_itemize() can be called on
860 * the whole thing.
861 *
862 * Input: para.first_input_index.
863 * Output: para.direction, para.pango_items, para.char_attributes.
864 */
865 void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
866 {
867 Glib::ustring para_text;
868 PangoAttrList *attributes_list;
869 unsigned input_index;
871 para->free_sequence(para->pango_items);
872 para->char_attributes.clear();
874 TRACE(("itemizing para, first input %d\n", para->first_input_index));
876 attributes_list = pango_attr_list_new();
877 for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
878 if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
879 Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
880 if ( control_code->code == SHAPE_BREAK
881 || control_code->code == PARAGRAPH_BREAK)
882 break; // stop at the end of the paragraph
883 // all other control codes we'll pick up later
885 } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
886 Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
888 // create the font_instance
889 font_instance *font = text_source->styleGetFontInstance();
890 if (font == NULL)
891 continue; // bad news: we'll have to ignore all this text because we know of no font to render it
893 PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
894 attribute_font_description->start_index = para_text.bytes();
895 para_text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text
896 attribute_font_description->end_index = para_text.bytes();
897 pango_attr_list_insert(attributes_list, attribute_font_description);
898 // ownership of attribute is assumed by the list
899 }
900 }
902 TRACE(("whole para: \"%s\"\n", para_text.data()));
903 TRACE(("%d input sources used\n", input_index - para->first_input_index));
905 // do the pango_itemize()
906 GList *pango_items_glist = NULL;
907 if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
908 Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
909 if (text_source->style->direction.set) {
910 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]));
911 pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
912 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]));
913 }
914 }
915 if (pango_items_glist == NULL) { // no direction specified, guess it
916 pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
918 // I think according to the css spec this is wrong and we're never allowed to guess the directionality
919 // of a paragraph. Need to talk to an rtl speaker.
920 if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
921 else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
922 }
923 pango_attr_list_unref(attributes_list);
925 // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
926 para->pango_items.reserve(g_list_length(pango_items_glist));
927 TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
928 for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
929 PangoItemInfo new_item;
930 new_item.item = (PangoItem*)current_pango_item->data;
931 PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
932 new_item.font = (font_factory::Default())->Face(font_description);
933 pango_font_description_free(font_description); // Face() makes a copy
934 para->pango_items.push_back(new_item);
935 }
936 g_list_free(pango_items_glist);
938 // and get the character attributes on everything
939 para->char_attributes.resize(para_text.length() + 1);
940 pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
942 TRACE(("end para itemize, direction = %d\n", para->direction));
943 }
945 /**
946 * Gets the ascent, descent and leading for a font and the alteration that has to be performed
947 * according to the value specified by the line-height css property. The result of multiplying
948 * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
949 * section 10.8.
950 */
951 void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
952 SPStyle const *style, LineHeight *line_height,
953 double *line_height_multiplier)
954 {
955 if (font == NULL) {
956 line_height->setZero();
957 *line_height_multiplier = 1.0;
958 }
959 else
960 font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
961 *line_height *= font_size;
963 // yet another borked SPStyle member that we're going to have to fix ourselves
964 for ( ; ; ) {
965 if (style->line_height.set && !style->line_height.inherit) {
966 if (style->line_height.normal)
967 break;
968 switch (style->line_height.unit) {
969 case SP_CSS_UNIT_NONE:
970 *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
971 return;
972 case SP_CSS_UNIT_EX:
973 *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
974 // 0.5 is an approximation of the x-height. Fixme.
975 return;
976 case SP_CSS_UNIT_EM:
977 case SP_CSS_UNIT_PERCENT:
978 *line_height_multiplier = style->line_height.value * font_size / line_height->total();
979 return;
980 default: // absolute values
981 *line_height_multiplier = style->line_height.computed / line_height->total();
982 return;
983 }
984 break;
985 }
986 if (style->object == NULL || style->object->parent == NULL) break;
987 style = style->object->parent->style;
988 if (style == NULL) break;
989 }
990 *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
991 }
994 /**
995 * Split the paragraph into spans. Also call pango_shape() on them.
996 *
997 * Input: para->first_input_index, para->pango_items
998 * Output: para->spans
999 * Returns: the index of the beginning of the following paragraph in _flow._input_stream
1000 */
1001 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
1002 {
1003 unsigned pango_item_index = 0;
1004 unsigned char_index_in_para = 0;
1005 unsigned byte_index_in_para = 0;
1006 unsigned input_index;
1008 TRACE(("build spans\n"));
1009 para->free_sequence(para->unbroken_spans);
1011 for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1012 if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1013 Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1014 if ( control_code->code == SHAPE_BREAK
1015 || control_code->code == PARAGRAPH_BREAK)
1016 break; // stop at the end of the paragraph
1017 else if (control_code->code == ARBITRARY_GAP) {
1018 UnbrokenSpan new_span;
1019 new_span.pango_item_index = -1;
1020 new_span.input_index = input_index;
1021 new_span.line_height.ascent = control_code->ascent;
1022 new_span.line_height.descent = control_code->descent;
1023 new_span.line_height.leading = 0.0;
1024 new_span.text_bytes = 0;
1025 new_span.char_index_in_para = char_index_in_para;
1026 para->unbroken_spans.push_back(new_span);
1027 TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
1028 }
1029 } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
1030 Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
1032 unsigned char_index_in_source = 0;
1034 unsigned span_start_byte_in_source = 0;
1035 // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
1036 for ( ; ; ) {
1037 /* we need to change spans at every change of PangoItem, source stream change,
1038 or change in one of the attributes altering position/rotation. */
1040 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
1041 ? 0
1042 : ( para->pango_items[pango_item_index].item->offset
1043 + para->pango_items[pango_item_index].item->length
1044 - byte_index_in_para ) );
1045 unsigned const text_source_bytes = ( text_source->text_end.base()
1046 - text_source->text_begin.base()
1047 - span_start_byte_in_source );
1048 TRACE(("New Span\n"));
1049 UnbrokenSpan new_span;
1050 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
1051 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
1052 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
1053 new_span.input_index = input_index;
1055 // cut at <tspan> attribute changes as well
1056 new_span.x._set = false;
1057 new_span.y._set = false;
1058 new_span.dx._set = false;
1059 new_span.dy._set = false;
1060 new_span.rotate._set = false;
1061 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1062 if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source];
1063 if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source];
1064 if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
1065 if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
1066 } else {
1067 if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source];
1068 if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source];
1069 if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
1070 if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
1071 }
1072 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1073 else if (char_index_in_source == 0) new_span.rotate = 0.f;
1074 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1075 // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1076 // so that the top of the letters is at zero, not the baseline
1077 new_span.y = 0.0;
1078 }
1079 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1080 iter_text++;
1081 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1082 if (iter_text >= text_source->text_end) break;
1083 if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1084 if ( i >= text_source->x.size() && i >= text_source->y.size()
1085 && i >= text_source->dx.size() && i >= text_source->dy.size()
1086 && i >= text_source->rotate.size()) break;
1087 if ( (text_source->x.size() > i && text_source->x[i]._set)
1088 || (text_source->y.size() > i && text_source->y[i]._set)
1089 || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1090 || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1091 || (text_source->rotate.size() > i && text_source->rotate[i]._set
1092 && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1093 new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1094 break;
1095 }
1096 }
1098 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1099 new_span.font_size = text_source->styleComputeFontSize();
1100 if (new_span.text_bytes) {
1101 new_span.glyph_string = pango_glyph_string_new();
1102 /* Some assertions intended to help diagnose bug #1277746. */
1103 g_assert( 0 < new_span.text_bytes );
1104 g_assert( span_start_byte_in_source < text_source->text->bytes() );
1105 g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1106 g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1107 == NULL );
1108 pango_shape(text_source->text->data() + span_start_byte_in_source,
1109 new_span.text_bytes,
1110 ¶->pango_items[pango_item_index].item->analysis,
1111 new_span.glyph_string);
1113 if (para->pango_items[pango_item_index].item->analysis.level & 1) {
1114 // pango_shape() will reorder glyphs in rtl sections into visual order which messes
1115 // us up because the svg spec requires us to draw glyphs in logical order
1116 // let's reverse the glyphstring on a cluster-by-cluster basis
1117 const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1118 std::vector<PangoGlyphInfo> infos(nglyphs);
1119 std::vector<gint> clusters(nglyphs);
1120 unsigned i, cluster_start = 0;
1122 for (i = 0 ; i < nglyphs ; ++i) {
1123 if (new_span.glyph_string->glyphs[i].attr.is_cluster_start) {
1124 if (i != cluster_start) {
1125 std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
1126 std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
1127 }
1128 cluster_start = i;
1129 }
1130 }
1131 if (i != cluster_start) {
1132 std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
1133 std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
1134 }
1135 std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
1136 std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
1137 }
1138 new_span.pango_item_index = pango_item_index;
1139 _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1141 // At some point we may want to calculate baseline_shift here (to take advantage
1142 // of otm features like superscript baseline), but for now we use style baseline_shift.
1143 new_span.baseline_shift = text_source->style->baseline_shift.computed;
1145 // TODO: metrics for vertical text
1146 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()));
1147 TRACE((" %d glyphs\n", new_span.glyph_string->num_glyphs));
1148 } else {
1149 // if there's no text we still need to initialise the styles
1150 new_span.pango_item_index = -1;
1151 font_instance *font = text_source->styleGetFontInstance();
1152 if (font) {
1153 _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
1154 font->Unref();
1155 } else {
1156 new_span.line_height.setZero();
1157 new_span.line_height_multiplier = 1.0;
1158 }
1159 TRACE(("add style init span %d\n", para->unbroken_spans.size()));
1160 }
1161 para->unbroken_spans.push_back(new_span);
1163 // calculations for moving to the next UnbrokenSpan
1164 byte_index_in_para += new_span.text_bytes;
1165 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1167 if (new_span.text_bytes >= pango_item_bytes) { // end of pango item
1168 pango_item_index++;
1169 if (pango_item_index == para->pango_items.size()) break; // end of paragraph
1170 }
1171 if (new_span.text_bytes == text_source_bytes)
1172 break; // end of source
1173 // else <tspan> attribute changed
1174 span_start_byte_in_source += new_span.text_bytes;
1175 }
1176 char_index_in_para += char_index_in_source;
1177 }
1178 }
1179 TRACE(("end build spans\n"));
1180 return input_index;
1181 }
1183 /**
1184 * Reinitialises the variables required on completion of one shape and
1185 * moving on to the next. Returns false if there are no more shapes to wrap
1186 * in to.
1187 */
1188 bool Layout::Calculator::_goToNextWrapShape()
1189 {
1190 delete _scanline_maker;
1191 _scanline_maker = NULL;
1192 _current_shape_index++;
1193 if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
1194 _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1195 TRACE(("begin wrap shape %d\n", _current_shape_index));
1196 return true;
1197 }
1199 /**
1200 * Given \a para filled in and \a start_span_pos set, keeps trying to
1201 * find somewhere it can fit the next line of text. The process of finding
1202 * the text that fits will involve creating one or more entries in
1203 * \a chunk_info describing the bounds of the fitted text and several
1204 * bits of information that will prove useful when we come to output the
1205 * line to #_flow. Returns with \a start_span_pos set to the end of the
1206 * text that was fitted, \a chunk_info completely filled out and
1207 * \a line_height set to the largest line box on the line. The return
1208 * value is false only if we've run out of shapes to wrap inside (and
1209 * hence couldn't create any chunks).
1210 */
1211 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const ¶,
1212 UnbrokenSpanPosition *start_span_pos,
1213 std::vector<ChunkInfo> *chunk_info,
1214 LineHeight *line_height)
1215 {
1216 // init the initial line_height
1217 if (start_span_pos->iter_span == para.unbroken_spans.end()) {
1218 if (_flow._spans.empty()) {
1219 // empty first para: create a font for the sole purpose of measuring it
1220 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1221 font_instance *font = text_source->styleGetFontInstance();
1222 if (font) {
1223 double font_size = text_source->styleComputeFontSize();
1224 double multiplier;
1225 _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
1226 font->Unref();
1227 *line_height *= multiplier;
1228 _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
1229 }
1230 }
1231 // else empty subsequent para: keep the old line height
1232 } else {
1233 if (_flow._input_wrap_shapes.empty()) {
1234 // if we're not wrapping set the line_height big and negative so we can use negative line height
1235 line_height->ascent = -1.0e10;
1236 line_height->descent = -1.0e10;
1237 line_height->leading = -1.0e10;
1238 }
1239 else
1240 line_height->setZero();
1241 }
1243 UnbrokenSpanPosition span_pos;
1244 for( ; ; ) {
1245 std::vector<ScanlineMaker::ScanRun> scan_runs;
1246 scan_runs = _scanline_maker->makeScanline(*line_height);
1247 while (scan_runs.empty()) {
1248 if (!_goToNextWrapShape()) return false; // no more shapes to wrap in to
1249 scan_runs = _scanline_maker->makeScanline(*line_height);
1250 }
1252 TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
1253 chunk_info->clear();
1254 chunk_info->reserve(scan_runs.size());
1255 if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1256 unsigned scan_run_index;
1257 span_pos = *start_span_pos;
1258 for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1259 if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
1260 break;
1261 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
1262 span_pos = chunk_info->back().broken_spans.back().end;
1263 }
1264 if (scan_run_index == scan_runs.size()) break; // ie when buildChunksInScanRun() succeeded
1265 }
1266 *start_span_pos = span_pos;
1267 return true;
1268 }
1270 /**
1271 * Given a scan run and a first character, append one or more chunks to
1272 * the \a chunk_info vector that describe all the spans and other detail
1273 * necessary to output the greatest amount of text that will fit on this scan
1274 * line (greedy line breaking algorithm). Each chunk contains one or more
1275 * BrokenSpan structures that link back to UnbrokenSpan structures that link
1276 * to the text itself. Normally there will be either one or zero (if the
1277 * scanrun is too short to fit any text) chunk added to \a chunk_info by
1278 * each call to this method, but we will add more than one if an x or y
1279 * attribute has been set on a tspan. \a line_height must be set on input,
1280 * and if it needs to be made larger and the #_scanline_maker can't do
1281 * an in-situ resize then it will be set to the required value and the
1282 * method will return false.
1283 */
1284 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const ¶,
1285 UnbrokenSpanPosition const &start_span_pos,
1286 ScanlineMaker::ScanRun const &scan_run,
1287 std::vector<ChunkInfo> *chunk_info,
1288 LineHeight *line_height) const
1289 {
1290 ChunkInfo new_chunk;
1291 new_chunk.text_width = 0.0;
1292 new_chunk.whitespace_count = 0;
1293 new_chunk.scanrun_width = scan_run.width();
1294 new_chunk.x = scan_run.x_start;
1296 // we haven't done anything yet so the last valid break position is the beginning
1297 BrokenSpan last_span_at_break, last_span_at_emergency_break;
1298 last_span_at_break.start = start_span_pos;
1299 last_span_at_break.setZero();
1300 last_span_at_emergency_break.start = start_span_pos;
1301 last_span_at_emergency_break.setZero();
1303 TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1304 BrokenSpan new_span;
1305 new_span.end = start_span_pos;
1306 while (new_span.end.iter_span != para.unbroken_spans.end()) { // this loops once for each UnbrokenSpan
1308 new_span.start = new_span.end;
1310 // force a chunk change at x or y attribute change
1311 if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1313 if (new_span.start.iter_span != start_span_pos.iter_span)
1314 chunk_info->push_back(new_chunk);
1316 new_chunk.x += new_chunk.text_width;
1317 new_chunk.text_width = 0.0;
1318 new_chunk.whitespace_count = 0;
1319 new_chunk.broken_spans.clear();
1320 if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1321 // y doesn't need to be done until output time
1322 }
1324 // see if this span is too tall to fit on the current line
1325 LineHeight total_height = new_span.start.iter_span->line_height;
1326 total_height *= new_span.start.iter_span->line_height_multiplier;
1327 /* floating point 80-bit/64-bit rounding problems require epsilon. See
1328 discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1329 if ( total_height.ascent > line_height->ascent + FLT_EPSILON
1330 || total_height.descent > line_height->descent + FLT_EPSILON
1331 || total_height.leading > line_height->leading + FLT_EPSILON) {
1332 line_height->max(total_height);
1333 if (!_scanline_maker->canExtendCurrentScanline(*line_height))
1334 return false;
1335 }
1337 bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1339 new_chunk.text_width += new_span.width;
1340 new_chunk.whitespace_count += new_span.whitespace_count;
1341 new_chunk.broken_spans.push_back(new_span); // if !span_fitted we'll correct ourselves below
1343 if (!span_fitted) break;
1345 if (new_span.end.iter_span == para.unbroken_spans.end()) {
1346 last_span_at_break = new_span;
1347 break;
1348 }
1349 }
1351 TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1352 chunk_info->push_back(new_chunk);
1354 if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
1355 /* **non-SVG spec bit**: See bug #1191102
1356 If the user types a very long line with no spaces, the way the spec
1357 is written at the moment means that when the length of the text
1358 exceeds the available width of all remaining areas, the text is
1359 completely hidden. This condition alters that behaviour so that if
1360 the length of the line is greater than four times the line-height
1361 and there are no spaces, it'll be emergency-wrapped at the last
1362 character. One could read the SVG Tiny 1.2 draft as permitting this
1363 sort of behaviour, but it's still a bit dodgy. The hard-coding of
1364 4x is not nice, either. */
1365 last_span_at_break = last_span_at_emergency_break;
1366 }
1368 if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
1369 // need to back out spans until we come to the one with the last break in it
1370 while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
1371 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1372 chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1373 chunk_info->back().broken_spans.pop_back();
1374 if (chunk_info->back().broken_spans.empty())
1375 chunk_info->pop_back();
1376 }
1377 if (!chunk_info->empty()) {
1378 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
1379 chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
1380 if (last_span_at_break.start == last_span_at_break.end) {
1381 chunk_info->back().broken_spans.pop_back(); // last break was at an existing boundary
1382 if (chunk_info->back().broken_spans.empty())
1383 chunk_info->pop_back();
1384 } else {
1385 chunk_info->back().broken_spans.back() = last_span_at_break;
1386 chunk_info->back().text_width += last_span_at_break.width;
1387 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
1388 }
1389 TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
1390 }
1391 }
1393 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
1394 // for justification we need to discard space occupied by the single whitespace at the end of the chunk
1395 chunk_info->back().broken_spans.back().ends_with_whitespace = false;
1396 chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1397 chunk_info->back().broken_spans.back().whitespace_count--;
1398 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
1399 chunk_info->back().whitespace_count--;
1400 }
1402 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
1403 // for justification we need to discard line-spacing and word-spacing at end of the chunk
1404 chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().letter_spacing;
1405 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().letter_spacing;
1406 TRACE(("width after subtracting last letter_spacing: %f\n", chunk_info->back().broken_spans.back().width));
1407 }
1409 return true;
1410 }
1412 /** The management function to start the whole thing off. */
1413 bool Layout::Calculator::calculate()
1414 {
1415 if (_flow._input_stream.empty())
1416 return false;
1417 /**
1418 * hm, why do we want assert (crash) the application, now do simply return false
1419 * \todo check if this is the correct behaviour
1420 * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
1421 */
1422 if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
1423 {
1424 g_warning("flow text is not of type TEXT_SOURCE. Abort.");
1425 return false;
1426 }
1427 TRACE(("begin calculateFlow()\n"));
1429 _flow._clearOutputObjects();
1431 _pango_context = (font_factory::Default())->fontContext;
1432 _font_factory_size_multiplier = (font_factory::Default())->fontSize;
1434 _block_progression = _flow._blockProgression();
1435 _y_offset = 0.0;
1436 _createFirstScanlineMaker();
1438 ParagraphInfo para;
1439 LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras
1440 for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
1441 // jump to the next wrap shape if this is a SHAPE_BREAK control code
1442 if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
1443 InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
1444 if (control_code->code == SHAPE_BREAK) {
1445 TRACE(("shape break control code\n"));
1446 if (!_goToNextWrapShape()) break;
1447 continue;
1448 }
1449 }
1450 if (_scanline_maker == NULL)
1451 break; // we're trying to flow past the last wrap shape
1453 _buildPangoItemizationForPara(¶);
1454 unsigned para_end_input_index = _buildSpansForPara(¶);
1456 if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
1457 para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
1458 else
1459 para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
1461 TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
1462 Layout::Paragraph new_paragraph;
1463 new_paragraph.base_direction = para.direction;
1464 new_paragraph.alignment = para.alignment;
1465 _flow._paragraphs.push_back(new_paragraph);
1467 // start scanning lines
1468 UnbrokenSpanPosition span_pos;
1469 span_pos.iter_span = para.unbroken_spans.begin();
1470 span_pos.char_byte = 0;
1471 span_pos.char_index = 0;
1473 do { // for each line in the paragraph
1474 TRACE(("begin line\n"));
1475 std::vector<ChunkInfo> line_chunk_info;
1476 if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
1477 break; // out of shapes to wrap in to
1479 _outputLine(para, line_height, line_chunk_info);
1480 _scanline_maker->completeLine(); // Increments y by line height
1481 } while (span_pos.iter_span != para.unbroken_spans.end());
1483 TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
1484 if (_scanline_maker != NULL) {
1485 bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
1486 if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
1487 || para_end_input_index + 1 < _flow._input_stream.size()) {
1488 // we need a span just for the para if it's either an empty last para or a break in the middle
1489 Layout::Span new_span;
1490 if (_flow._spans.empty()) {
1491 new_span.font = NULL;
1492 new_span.font_size = line_height.ascent + line_height.descent;
1493 new_span.line_height = line_height;
1494 new_span.x_end = 0.0;
1495 } else {
1496 new_span = _flow._spans.back();
1497 if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
1498 new_span.x_end = 0.0;
1499 }
1500 new_span.in_chunk = _flow._chunks.size() - 1;
1501 if (new_span.font)
1502 new_span.font->Ref();
1503 new_span.x_start = new_span.x_end;
1504 new_span.baseline_shift = 0.0;
1505 new_span.direction = para.direction;
1506 new_span.block_progression = _block_progression;
1507 if (para_end_input_index == _flow._input_stream.size())
1508 new_span.in_input_stream_item = _flow._input_stream.size() - 1;
1509 else
1510 new_span.in_input_stream_item = para_end_input_index;
1511 _flow._spans.push_back(new_span);
1512 }
1513 if (para_end_input_index + 1 < _flow._input_stream.size()) {
1514 // we've got to add an invisible character between paragraphs so that we can position iterators
1515 // (and hence cursors) both before and after the paragraph break
1516 Layout::Character new_character;
1517 new_character.in_span = _flow._spans.size() - 1;
1518 new_character.char_attributes.is_line_break = 1;
1519 new_character.char_attributes.is_mandatory_break = 1;
1520 new_character.char_attributes.is_char_break = 1;
1521 new_character.char_attributes.is_white = 1;
1522 new_character.char_attributes.is_cursor_position = 1;
1523 new_character.char_attributes.is_word_start = 0;
1524 new_character.char_attributes.is_word_end = 1;
1525 new_character.char_attributes.is_sentence_start = 0;
1526 new_character.char_attributes.is_sentence_end = 1;
1527 new_character.char_attributes.is_sentence_boundary = 1;
1528 new_character.char_attributes.backspace_deletes_character = 1;
1529 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
1530 new_character.in_glyph = -1;
1531 _flow._characters.push_back(new_character);
1532 }
1533 }
1534 para.free();
1535 para.first_input_index = para_end_input_index + 1;
1536 }
1538 para.free();
1539 if (_scanline_maker) {
1540 delete _scanline_maker;
1541 _flow._input_truncated = false;
1542 } else {
1543 _flow._input_truncated = true;
1544 }
1546 return true;
1547 }
1549 void Layout::_calculateCursorShapeForEmpty()
1550 {
1551 _empty_cursor_shape.position = Geom::Point(0, 0);
1552 _empty_cursor_shape.height = 0.0;
1553 _empty_cursor_shape.rotation = 0.0;
1554 if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
1555 return;
1557 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
1559 font_instance *font = text_source->styleGetFontInstance();
1560 double font_size = text_source->styleComputeFontSize();
1561 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
1562 LineHeight line_height;
1563 if (font) {
1564 const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
1565 font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
1566 line_height *= font_size;
1567 font->Unref();
1568 } else {
1569 line_height.ascent = font_size * 0.85; // random guesses
1570 line_height.descent = font_size * 0.15;
1571 line_height.leading = 0.0;
1572 }
1573 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
1574 _empty_cursor_shape.height = font_size / cos(caret_slope);
1575 _empty_cursor_shape.rotation = caret_slope;
1577 if (_input_wrap_shapes.empty()) {
1578 _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
1579 text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
1580 } else {
1581 Direction block_progression = text_source->styleGetBlockProgression();
1582 ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
1583 std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
1584 if (!scan_runs.empty()) {
1585 if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
1586 _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
1587 else
1588 _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
1589 }
1590 }
1591 }
1593 bool Layout::calculateFlow()
1594 {
1595 bool result = Calculator(this).calculate();
1596 if (_characters.empty())
1597 _calculateCursorShapeForEmpty();
1598 return result;
1599 }
1601 }//namespace Text
1602 }//namespace Inkscape
1605 /*
1606 Local Variables:
1607 mode:c++
1608 c-file-style:"stroustrup"
1609 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1610 indent-tabs-mode:nil
1611 fill-column:99
1612 End:
1613 */
1614 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :