1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Inkscape::Text::Layout::Calculator - text layout engine meaty bits
4 *
5 * Authors:
6 * Richard Hughes <cyreve@users.sf.net>
7 *
8 * Copyright (C) 2005 Richard Hughes
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13 #include <iomanip>
14
15 #include "Layout-TNG.h"
16 #include "style.h"
17 #include "font-instance.h"
18 #include "svg/svg-length.h"
19 #include "object/sp-object.h"
20 #include "Layout-TNG-Scanline-Maker.h"
21 #include <limits>
22 #include "livarot/Shape.h"
23
24 namespace Inkscape {
25 namespace Text {
26
27 //#define DEBUG_LAYOUT_TNG_COMPUTE
28 //#define DEBUG_GLYPH
29
30 //#define IFTRACE(_code) _code
31 #define IFTRACE(_code)
32
33 #define TRACE(_args) IFTRACE(g_print _args)
34
35 /** \brief private to Layout. Does the real work of text flowing.
36
37 This class does a standard greedy paragraph wrapping algorithm.
38
39 Very high-level overview:
40
41 <pre>
42 foreach(paragraph) {
43 call pango_itemize() (_buildPangoItemizationForPara())
44 break into spans, without dealing with wrapping (_buildSpansForPara())
45 foreach(line in flow shape) {
46 foreach(chunk in flow shape) { (in _buildChunksInScanRun())
47 // this inner loop in _measureUnbrokenSpan()
48 if the line height changed discard the line and start again
49 keep adding characters until we run out of space in the chunk, then back up to the last word boundary
50 (do sensible things if there is no previous word break)
51 }
52 push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
53 }
54 push the paragraph (in calculate())
55 }
56 </pre>
57
58 ...and all of that needs to work vertically too, and with all the little details that make life annoying
59 */
60 class Layout::Calculator
61 {
62 class SpanPosition;
63 friend class SpanPosition;
64 Layout &_flow;
65 ScanlineMaker *_scanline_maker;
66 unsigned _current_shape_index; /// index into Layout::_input_wrap_shapes
67 PangoContext *_pango_context;
68 Direction _block_progression;
69
70 /**
71 * For y= attributes in tspan elements et al, we do the adjustment by moving each
72 * glyph individually by this number. The spec means that this is maintained across
73 * paragraphs.
74 *
75 * To do non-flow text layout, only the first "y" attribute is normally used. If there is only one
76 * "y" attribute in a <tspan> other than the first <tspan>, it is ignored. This allows Inkscape to
77 * insert a new line anywhere. On output, the Inkscape determined "y" is written out so other SVG
78 * viewers know where to place the <tspans>.
79 */
80 double _y_offset;
81
82 /** to stop pango from hinting its output, the font factory creates all fonts very large.
83 All numbers returned from pango have to be divided by this number \em and divided by
84 PANGO_SCALE. See font_factory::font_factory(). */
85 double _font_factory_size_multiplier;
86
87 /** Temporary storage associated with each item in Layout::_input_stream. */
88 struct InputItemInfo {
89 bool in_sub_flow;
90 Layout *sub_flow; // this is only set for the first input item in a sub-flow
91
InputItemInfoInkscape::Text::Layout::Calculator::InputItemInfo92 InputItemInfo() : in_sub_flow(false), sub_flow(nullptr) {}
93
94 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
95 * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
96 * that does delete or Unref.
97 *
98 * I suggest using the garbage collector to manage deletion.
99 */
freeInkscape::Text::Layout::Calculator::InputItemInfo100 void free()
101 {
102 if (sub_flow) {
103 delete sub_flow;
104 sub_flow = nullptr;
105 }
106 }
107 };
108
109 /** Temporary storage associated with each item returned by the call to
110 pango_itemize(). */
111 struct PangoItemInfo {
112 PangoItem *item;
113 font_instance *font;
114
PangoItemInfoInkscape::Text::Layout::Calculator::PangoItemInfo115 PangoItemInfo() : item(nullptr), font(nullptr) {}
116
117 /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
118 * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
119 * that does delete or Unref.
120 *
121 * I suggest using the garbage collector to manage deletion.
122 */
freeInkscape::Text::Layout::Calculator::PangoItemInfo123 void free()
124 {
125 if (item) {
126 pango_item_free(item);
127 item = nullptr;
128 }
129 if (font) {
130 font->Unref();
131 font = nullptr;
132 }
133 }
134 };
135
136
137 /** These spans have approximately the same definition as that used for
138 * Layout::Span (constant font, direction, etc), except that they are from
139 * before we have located the line breaks, so bear no relation to chunks.
140 * They are guaranteed to be in at most one PangoItem (spans with no text in
141 * them will not have an associated PangoItem), exactly one input object and
142 * will only have one change of x, y, dx, dy or rotate attribute, which will
143 * be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
144 * BrokenSpan.
145 */
146 struct UnbrokenSpan {
147 PangoGlyphString *glyph_string;
148 int pango_item_index; /// index into _para.pango_items, or -1 if this is style only
149 unsigned input_index; /// index into Layout::_input_stream
150 Glib::ustring::const_iterator input_stream_first_character;
151 double font_size;
152 FontMetrics line_height; /// This is not the CSS line-height attribute!
153 double line_height_multiplier; /// calculated from the font-height css property
154 double baseline_shift; /// calculated from the baseline-shift css property
155 SPCSSTextOrientation text_orientation;
156 unsigned text_bytes;
157 unsigned char_index_in_para; /// the index of the first character in this span in the paragraph, for looking up char_attributes
158 SVGLength x, y, dx, dy, rotate; // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
159
UnbrokenSpanInkscape::Text::Layout::Calculator::UnbrokenSpan160 UnbrokenSpan() : glyph_string(nullptr) {}
freeInkscape::Text::Layout::Calculator::UnbrokenSpan161 void free()
162 {
163 if (glyph_string)
164 pango_glyph_string_free(glyph_string);
165 glyph_string = nullptr;
166 }
167 };
168
169
170 /** Used to provide storage for anything that applies to the current
171 paragraph only. Since we're only processing one paragraph at a time,
172 there's only one instantiation of this struct, on the stack of
173 calculate(). */
174 struct ParagraphInfo {
175 Glib::ustring text;
176 unsigned first_input_index; ///< Index into Layout::_input_stream.
177 Direction direction;
178 Alignment alignment;
179 std::vector<InputItemInfo> input_items;
180 std::vector<PangoItemInfo> pango_items;
181 std::vector<PangoLogAttr> char_attributes; ///< For every character in the paragraph.
182 std::vector<UnbrokenSpan> unbroken_spans;
183
free_sequenceInkscape::Text::Layout::Calculator::ParagraphInfo184 template<typename T> static void free_sequence(T &seq)
185 {
186 for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
187 it->free();
188 }
189 seq.clear();
190 }
191
freeInkscape::Text::Layout::Calculator::ParagraphInfo192 void free()
193 {
194 text = "";
195 free_sequence(input_items);
196 free_sequence(pango_items);
197 free_sequence(unbroken_spans);
198 }
199 };
200
201
202 /**
203 * A useful little iterator for moving char-by-char across spans.
204 */
205 struct UnbrokenSpanPosition {
206 std::vector<UnbrokenSpan>::iterator iter_span;
207 unsigned char_byte;
208 unsigned char_index;
209
210 void increment(); ///< Step forward by one character.
211
operator ==Inkscape::Text::Layout::Calculator::UnbrokenSpanPosition212 inline bool operator== (UnbrokenSpanPosition const &other) const
213 {return char_byte == other.char_byte && iter_span == other.iter_span;}
operator !=Inkscape::Text::Layout::Calculator::UnbrokenSpanPosition214 inline bool operator!= (UnbrokenSpanPosition const &other) const
215 {return char_byte != other.char_byte || iter_span != other.iter_span;}
216 };
217
218 /**
219 * The line breaking algorithm will convert each UnbrokenSpan into one
220 * or more of these. A BrokenSpan will never cross a chunk boundary,
221 * c.f. UnbrokenSpan.
222 */
223 struct BrokenSpan {
224 UnbrokenSpanPosition start;
225 UnbrokenSpanPosition end; // the end of this will always be the same as the start of the next
226 unsigned start_glyph_index;
227 unsigned end_glyph_index;
228 double width;
229 unsigned whitespace_count;
230 bool ends_with_whitespace;
231 double each_whitespace_width;
232 double letter_spacing; // Save so we can subtract from width at end of line (for center justification)
233 double word_spacing;
234 void setZero();
235 };
236
237 /** The definition of a chunk used here is the same as that used in Layout:
238 A collection of contiguous broken spans on the same line. (One chunk per line
239 unless shape splits line into several sections... then one chunk per section. */
240 struct ChunkInfo {
241 std::vector<BrokenSpan> broken_spans;
242 double scanrun_width;
243 double text_width; ///< Total width used by the text (excluding justification).
244 double x;
245 int whitespace_count;
246 };
247
248 void _buildPangoItemizationForPara(ParagraphInfo *para) const;
249 static double _computeFontLineHeight( SPStyle const *style ); // Returns line_height_multiplier
250 unsigned _buildSpansForPara(ParagraphInfo *para) const;
251 bool _goToNextWrapShape();
252 void _createFirstScanlineMaker();
253
254 bool _findChunksForLine(ParagraphInfo const ¶,
255 UnbrokenSpanPosition *start_span_pos,
256 std::vector<ChunkInfo> *chunk_info,
257 FontMetrics *line_box_height,
258 FontMetrics const *strut_height);
259
260 bool _buildChunksInScanRun(ParagraphInfo const ¶,
261 UnbrokenSpanPosition const &start_span_pos,
262 ScanlineMaker::ScanRun const &scan_run,
263 std::vector<ChunkInfo> *chunk_info,
264 FontMetrics *line_height) const;
265
266 bool _measureUnbrokenSpan(ParagraphInfo const ¶,
267 BrokenSpan *span,
268 BrokenSpan *last_break_span,
269 BrokenSpan *last_emergency_break_span,
270 double maximum_width) const;
271
272 double _getChunkLeftWithAlignment(ParagraphInfo const ¶,
273 std::vector<ChunkInfo>::const_iterator it_chunk,
274 double *add_to_each_whitespace) const;
275
276 void _outputLine(ParagraphInfo const ¶,
277 FontMetrics const &line_height,
278 std::vector<ChunkInfo> const &chunk_info,
279 bool hidden);
280
_charAttributes(ParagraphInfo const & para,UnbrokenSpanPosition const & span_pos)281 static inline PangoLogAttr const &_charAttributes(ParagraphInfo const ¶,
282 UnbrokenSpanPosition const &span_pos)
283 {
284 return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
285 }
286
287 #ifdef DEBUG_LAYOUT_TNG_COMPUTE
288 static void dumpPangoItemsOut(ParagraphInfo *para);
289 static void dumpUnbrokenSpans(ParagraphInfo *para);
290 #endif //DEBUG_LAYOUT_TNG_COMPUTE
291
292 public:
Calculator(Layout * text_flow)293 Calculator(Layout *text_flow)
294 : _flow(*text_flow) {}
295
296 bool calculate();
297 };
298
299
300 /**
301 * Computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
302 * and outputs its vital statistics into the other fields of \a span.
303 * Measuring will stop if maximum_width is reached and in that case the
304 * function will return false. In other cases where a line break must be
305 * done immediately the function will also return false. On return
306 * \a last_break_span will contain the vital statistics for the span only
307 * up to the last line breaking change. If there are no line breaking
308 * characters in the span then \a last_break_span will not be altered.
309 * Similarly, \a last_emergency_break_span will contain the vital
310 * statistics for the span up to the last inter-character boundary,
311 * or will be unaltered if there is none.
312 *
313 * An unbroken span corresponds to at most one PangoItem
314 */
_measureUnbrokenSpan(ParagraphInfo const & para,BrokenSpan * span,BrokenSpan * last_break_span,BrokenSpan * last_emergency_break_span,double maximum_width) const315 bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo const ¶,
316 BrokenSpan *span,
317 BrokenSpan *last_break_span,
318 BrokenSpan *last_emergency_break_span,
319 double maximum_width) const
320 {
321 TRACE((" start _measureUnbrokenSpan %g\n", maximum_width));
322 span->setZero();
323
324 if (span->start.iter_span->dx._set && span->start.char_byte == 0){
325 if(para.direction == RIGHT_TO_LEFT){
326 span->width -= span->start.iter_span->dx.computed;
327 } else {
328 span->width += span->start.iter_span->dx.computed;
329 }
330 }
331
332 if (span->start.iter_span->pango_item_index == -1) {
333 // if this is a style-only span there's no text in it
334 // so we don't need to do very much at all
335 span->end.iter_span++;
336 return true;
337 }
338
339 if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
340
341 InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
342
343 if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
344 *last_emergency_break_span = *last_break_span = *span;
345 return false;
346 }
347
348 if (control_code->code == ARBITRARY_GAP) { // Not used!
349 if (span->width + control_code->width > maximum_width)
350 return false;
351 TRACE((" fitted control code, width = %f\n", control_code->width));
352 span->width += control_code->width;
353 span->end.increment();
354 }
355 return true;
356 }
357
358 if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
359 return true; // never happens
360
361 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
362
363 if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
364 // TODO: block-progression altered in the middle
365 // Measure the precomputed flow from para.input_items
366 span->end.iter_span++; // for now, skip to the next span
367 return true;
368 }
369
370 // a normal span going with a normal block-progression
371 double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
372 double soft_hyphen_glyph_width = 0.0;
373 bool soft_hyphen_in_word = false;
374 bool is_soft_hyphen = false;
375 IFTRACE(int char_count = 0);
376
377 // if we're not at the start of the span we need to pre-init glyph_index
378 span->start_glyph_index = 0;
379 while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
380 && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
381 span->start_glyph_index++;
382 span->end_glyph_index = span->start_glyph_index;
383
384 // go char-by-char summing the width, while keeping track of the previous break point
385 do {
386 PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
387
388 // guint32 c = *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte);
389 // std::cout << " char_byte: " << span->end.char_byte
390 // << " char_index: " << span->end.char_index
391 // << " c: " << c << " " << char(c==10 ? '' : c)
392 // << " line: " << std::boolalpha << char_attributes.is_line_break
393 // << " mandatory: " << std::boolalpha << char_attributes.is_mandatory_break // Note, break is before character!
394 // << " char: " << std::boolalpha << char_attributes.is_char_break
395 // << std::endl;
396
397 if (char_attributes.is_mandatory_break && span->end != span->start) {
398 TRACE((" is_mandatory_break ************\n"));
399 *last_emergency_break_span = *last_break_span = *span;
400 TRACE((" span %ld end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
401 return false;
402 }
403
404 if (char_attributes.is_line_break) {
405 TRACE((" is_line_break ************\n"));
406 // a suitable position to break at, record where we are
407 *last_emergency_break_span = *last_break_span = *span;
408 if (soft_hyphen_in_word) {
409 // if there was a previous soft hyphen we're not going to need it any more so we can remove it
410 span->width -= soft_hyphen_glyph_width;
411 if (!is_soft_hyphen)
412 soft_hyphen_in_word = false;
413 }
414 } else if (char_attributes.is_char_break) {
415 *last_emergency_break_span = *span;
416 }
417 // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
418
419 // sum the glyph widths, letter spacing, word spacing, and textLength adjustment to get the character width
420 double char_width = 0.0;
421 while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
422 && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
423
424 PangoGlyphInfo *info = &(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index]);
425 double glyph_width = font_size_multiplier * info->geometry.width;
426
427 // Advance does not include kerning but Pango gives wrong advances for vertical text
428 // with upright orientation (pre 1.44.0).
429 font_instance *font = para.pango_items[span->end.iter_span->pango_item_index].font;
430 double font_size = span->start.iter_span->font_size;
431 //double glyph_h_advance = font_size * font->Advance(info->glyph, false);
432 double glyph_v_advance = font_size * font->Advance(info->glyph, true );
433
434 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
435 // Vertical text
436
437 if( text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS ||
438 (text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED &&
439 para.pango_items[span->end.iter_span->pango_item_index].item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
440 // Sideways orientation
441 char_width += glyph_width;
442 } else {
443 // Upright orientation
444 guint32 c = *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte);
445 if (g_unichar_type (c) != G_UNICODE_NON_SPACING_MARK) {
446 // Non-spacing marks should not contribute to width. Fonts may not report the correct advance, especially if the 'vmtx' table is missing.
447 if (pango_version_check(1,44,0) != nullptr) {
448 // Pango >= 1.44.0
449 char_width += glyph_width;
450 } else {
451 // Pango < 1.44.0 glyph_width returned is horizontal width, not vertical.
452 char_width += glyph_v_advance;
453 }
454 }
455 }
456 } else {
457 // Horizontal text
458 char_width += glyph_width;
459 }
460 span->end_glyph_index++;
461 }
462
463 if (char_attributes.is_cursor_position)
464 char_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue();
465 if (char_attributes.is_white)
466 char_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue();
467 char_width += _flow.getTextLengthIncrementDue();
468 span->width += char_width;
469 IFTRACE(char_count++);
470
471 if (char_attributes.is_white) {
472 span->whitespace_count++;
473 span->each_whitespace_width = char_width;
474 }
475 span->ends_with_whitespace = char_attributes.is_white;
476
477 is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
478 if (is_soft_hyphen)
479 soft_hyphen_glyph_width = char_width;
480
481 // Go to next character (resets end.char_byte to zero if at end)
482 span->end.increment();
483
484 // Width should not include letter_spacing (or word_spacing) after last letter at end of line.
485 // word_spacing is attached to white space that is already removed from line end (?)
486 double test_width = span->width - text_source->style->letter_spacing.computed;
487
488 // Save letter_spacing and word_spacing for subtraction later if span is last span in line.
489 span->letter_spacing = text_source->style->letter_spacing.computed;
490 span->word_spacing = text_source->style->word_spacing.computed;
491
492 if (test_width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
493 TRACE((" span %ld exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
494 return false;
495 }
496
497 } while (span->end.char_byte != 0); // while we haven't wrapped to the next span
498
499 TRACE((" fitted span %ld width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
500 TRACE((" end _measureUnbrokenSpan %g\n", maximum_width));
501 return true;
502 }
503
504 /* *********************************************************************************************************/
505 // Per-line functions (output)
506
507 /** Uses the paragraph alignment and the chunk information to work out
508 * where the actual left of the final chunk must be. Also sets
509 * \a add_to_each_whitespace to be the amount of x to add at each
510 * whitespace character to make full justification work.
511 */
_getChunkLeftWithAlignment(ParagraphInfo const & para,std::vector<ChunkInfo>::const_iterator it_chunk,double * add_to_each_whitespace) const512 double Layout::Calculator::_getChunkLeftWithAlignment(ParagraphInfo const ¶,
513 std::vector<ChunkInfo>::const_iterator it_chunk,
514 double *add_to_each_whitespace) const
515 {
516 *add_to_each_whitespace = 0.0;
517 if (_flow._input_wrap_shapes.empty()) {
518 switch (para.alignment) {
519 case FULL:
520 case LEFT:
521 default:
522 return it_chunk->x;
523 case RIGHT:
524 return it_chunk->x - it_chunk->text_width;
525 case CENTER:
526 return it_chunk->x - it_chunk->text_width/ 2;
527 }
528 }
529
530 switch (para.alignment) {
531 case FULL:
532 if (!it_chunk->broken_spans.empty()
533 && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) { // don't justify the last chunk in the para
534 if (it_chunk->whitespace_count)
535 *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
536 //else
537 //add_to_each_charspace = something
538 }
539 return it_chunk->x;
540 case LEFT:
541 default:
542 return it_chunk->x;
543 case RIGHT:
544 return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
545 case CENTER:
546 return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
547 }
548 }
549
550 /**
551 * Once we've got here we have finished making changes to the line
552 * and are ready to output the final result to #_flow.
553 * This method takes its input parameters and does that.
554 */
_outputLine(ParagraphInfo const & para,FontMetrics const & line_height,std::vector<ChunkInfo> const & chunk_info,bool hidden)555 void Layout::Calculator::_outputLine(ParagraphInfo const ¶,
556 FontMetrics const &line_height,
557 std::vector<ChunkInfo> const &chunk_info,
558 bool hidden)
559 {
560 TRACE((" Start _outputLine: ascent %f, descent %f, top of box %f\n", line_height.ascent, line_height.descent, _scanline_maker->yCoordinate() ));
561 if (chunk_info.empty()) {
562 TRACE((" line too short to fit anything on it, go to next\n"));
563 return;
564 }
565
566 // we've finished fiddling about with ascents and descents: create the output
567 TRACE((" found line fit; creating output\n"));
568 Layout::Line new_line;
569 new_line.in_paragraph = _flow._paragraphs.size() - 1;
570 new_line.baseline_y = _scanline_maker->yCoordinate();
571 new_line.hidden = hidden;
572
573 // The y coordinate is at the beginning edge of the line box (top for horizontal text, left
574 // edge for vertical lr text, right edge for vertical rl text. We align, by default to the
575 // alphabetic baseline for horizontal text and the central baseline for vertical text.
576 if( _block_progression == RIGHT_TO_LEFT ) {
577 // Vertical text, use em box center as baseline
578 new_line.baseline_y -= 0.5 * line_height.emSize();
579 } else if ( _block_progression == LEFT_TO_RIGHT ) {
580 // Vertical text, use em box center as baseline
581 new_line.baseline_y += 0.5 * line_height.emSize();
582 } else {
583 new_line.baseline_y += line_height.getTypoAscent();
584 }
585
586
587 TRACE((" initial new_line.baseline_y: %f\n", new_line.baseline_y ));
588
589 new_line.in_shape = _current_shape_index;
590 _flow._lines.push_back(new_line);
591
592 for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
593 double add_to_each_whitespace;
594 // add the chunk to the list
595 Layout::Chunk new_chunk;
596 new_chunk.in_line = _flow._lines.size() - 1;
597
598 TRACE((" New chunk: in_line: %d\n", new_chunk.in_line));
599 if (hidden) {
600 new_chunk.left_x = it_chunk->x; // Don't align. We'll place below last shape.
601 } else {
602 new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
603 }
604
605 // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
606
607 // Comment updated: 23 July 2010:
608 // We must handle two cases:
609 //
610 // 1. Inkscape SVG where the first line is placed by the read-in "y" value and the
611 // rest are determined by 'font-size' and 'line-height' (and not by any
612 // y-kerning). <tspan>s in this case are marked by sodipodi:role="line". This
613 // allows new lines to be inserted in the middle of a <text> object. On output,
614 // new "y" values are calculated for each <tspan> that represents a new line. Line
615 // spacing is already handled by the calling routine.
616 //
617 // 2. Plain SVG where each <text> or <tspan> is placed by its own "x" and "y" values.
618 // Note that in this case Inkscape treats each <text> object with any included
619 // <tspan>s as a single line of text. This can be confusing in the code below.
620
621 if (!it_chunk->broken_spans.empty() // Not empty paragraph
622 && it_chunk->broken_spans.front().start.char_byte == 0 ) { // Beginning of unbroken span
623
624 // If empty or new line (sodipode:role="line")
625 if( _flow._characters.empty() ||
626 _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
627
628 // This is the Inkscape SVG case.
629 //
630 // If <tspan> "y" attribute is set, use it (initial "y" attributes in
631 // <tspans> other than the first have already been stripped for <tspans>
632 // marked with role="line", see sp-text.cpp: SPText::_buildLayoutInput).
633 // NOTE: for vertical text, "y" is the user-space "x" value.
634 if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
635
636 // Use set "y" attribute for baseline
637 new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
638
639 TRACE((" chunk new_line.baseline_y: %f\n", new_line.baseline_y ));
640
641 // Save baseline
642 _flow._lines.back().baseline_y = new_line.baseline_y;
643
644 // Calculate new top of box... given specified baseline.
645 double top_of_line_box = new_line.baseline_y;
646 if( _block_progression == RIGHT_TO_LEFT ) {
647 // Vertical text, use em box center as baseline
648 top_of_line_box += 0.5 * line_height.emSize();
649 } else if (_block_progression == LEFT_TO_RIGHT ) {
650 // Vertical text, use em box center as baseline
651 top_of_line_box -= 0.5 * line_height.emSize();
652 } else {
653 top_of_line_box -= line_height.getTypoAscent();
654 }
655 TRACE((" y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
656 // Set the initial y coordinate of the for this line (see above).
657 _scanline_maker->setNewYCoordinate(top_of_line_box);
658 }
659
660 // Reset relative y_offset ("dy" attribute is relative but should be reset at
661 // the beginning of each line since each line will have a new "y" written out.)
662 _y_offset = 0.0;
663
664 } else {
665
666 // This is the plain SVG case
667 //
668 // "x" and "y" are used to place text, simulating lines as necessary
669 if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
670 _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
671 }
672 }
673 }
674 _flow._chunks.push_back(new_chunk);
675
676 double current_x;
677 double direction_sign;
678 Direction previous_direction = para.direction;
679 double counter_directional_width_remaining = 0.0;
680 float glyph_rotate = 0.0;
681 if (para.direction == LEFT_TO_RIGHT) {
682 direction_sign = +1.0;
683 current_x = 0.0;
684 } else {
685 direction_sign = -1.0;
686 if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){
687 current_x = it_chunk->scanrun_width;
688 }
689 else {
690 current_x = it_chunk->text_width;
691 }
692 }
693
694 // Loop over broken spans; a broken span is part of no more than one PangoItem.
695 for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
696
697 // begin adding spans to the list
698 UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
699 double x_in_span_last = 0.0; // set at the END when a new cluster starts
700 double x_in_span = 0.0; // set from the preceding at the START when a new cluster starts.
701
702 // for (int i = 0; i < unbroken_span.glyph_string->num_glyphs; ++i) {
703 // std::cout << "Unbroken span: " << unbroken_span.glyph_string->glyphs[i].glyph << std::endl;
704 // }
705
706 if (it_span->start.char_byte == 0) {
707 // Start of an unbroken span, we might have dx, dy or rotate still to process
708 // (x and y are done per chunk)
709 if (unbroken_span.dx._set) current_x += unbroken_span.dx.computed;
710 if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
711 if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
712 }
713
714 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
715 && unbroken_span.pango_item_index == -1) {
716 // style only, nothing to output
717 continue;
718 }
719
720 Layout::Span new_span;
721
722 new_span.in_chunk = _flow._chunks.size() - 1;
723 new_span.line_height = unbroken_span.line_height;
724 new_span.in_input_stream_item = unbroken_span.input_index;
725 new_span.baseline_shift = 0.0;
726 new_span.block_progression = _block_progression;
727 new_span.text_orientation = unbroken_span.text_orientation;
728 if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
729 {
730 new_span.font->Ref();
731 new_span.font_size = unbroken_span.font_size;
732 new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
733 new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
734 } else { // a control code
735 new_span.font = nullptr;
736 new_span.font_size = new_span.line_height.emSize();
737 new_span.direction = para.direction;
738 }
739
740 if (new_span.direction == para.direction) {
741 current_x -= counter_directional_width_remaining;
742 counter_directional_width_remaining = 0.0;
743 } else if (new_span.direction != previous_direction) {
744 // measure width of spans we need to switch round
745 counter_directional_width_remaining = 0.0;
746 std::vector<BrokenSpan>::const_iterator it_following_span;
747 for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
748 if (it_following_span->start.iter_span->pango_item_index == -1) break;
749 Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
750 if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
751 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;
752 }
753 counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
754 }
755 current_x += counter_directional_width_remaining;
756 counter_directional_width_remaining = 0.0; // we want to go increasingly negative
757 }
758 new_span.x_start = current_x;
759 new_span.y_offset = _y_offset; // Offset from baseline due to 'y' and 'dy' attributes (used to simulate multiline text).
760
761 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
762 // the span is set up, push the glyphs and chars
763
764 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
765 Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
766 unsigned char_index_in_unbroken_span = it_span->start.char_index;
767 double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
768 int log_cluster_size_glyphs = 0; // Number of glyphs in this log_cluster
769 int log_cluster_size_chars = 0; // Number of characters in this log_cluster
770 unsigned end_byte = 0;
771
772 // Get some pointers (constant for an unbroken span).
773 font_instance *font = para.pango_items[unbroken_span.pango_item_index].font;
774 PangoItem *pango_item = para.pango_items[unbroken_span.pango_item_index].item;
775
776 // Loop over glyphs in span
777 #if PANGO_VERSION_CHECK(1,44,0)
778 double x_offset_cluster = 0.0; // Handle wrong glyph positioning post-1.44 Pango.
779 #endif
780 double x_offset_center = 0.0; // Handle wrong glyph positioning in pre-1.44 Pango.
781 double x_offset_advance = 0.0; // Handle wrong advance in pre-1.44 Pango.
782
783 #ifdef DEBUG_GLYPH
784 std::cout << "\nGlyphs in span: x_start: " << new_span.x_start << " y_offset: " << new_span.y_offset
785 << " PangoItem flags: " << (int)pango_item->analysis.flags << " Gravity: " << (int)pango_item->analysis.gravity << std::endl;
786 std::cout << " Unicode Glyph h_advance v_advance width cluster orientation new_glyph delta" << std::endl;
787 std::cout << " (hex) No. start x y x y" << std::endl;
788 std::cout << " -------------------------------------------------------------------------------------------------" << std::endl;
789 #endif
790
791 for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
792
793 unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
794 bool newcluster = false;
795 if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
796 newcluster = true;
797 x_in_span = x_in_span_last;
798 }
799
800 if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
801 && *iter_source_text == UNICODE_SOFT_HYPHEN
802 && glyph_index + 1 != it_span->end_glyph_index) {
803 // if we're looking at a soft hyphen and it's not the last glyph in the
804 // chunk we don't draw the glyph but we still need to add to _characters
805 Layout::Character new_character;
806 new_character.the_char = *iter_source_text;
807 new_character.in_span = _flow._spans.size(); // the span hasn't been added yet, so no -1
808 new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
809 new_character.in_glyph = -1;
810 _flow._characters.push_back(new_character);
811 iter_source_text++;
812 char_index_in_unbroken_span++;
813 while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
814 && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
815 glyph_index++;
816 glyph_index--;
817 continue;
818 }
819
820 // create the Layout::Glyph
821 PangoGlyphInfo *unbroken_span_glyph_info = &unbroken_span.glyph_string->glyphs[glyph_index];
822 double glyph_width = font_size_multiplier * unbroken_span_glyph_info->geometry.width;
823
824 Layout::Glyph new_glyph;
825 new_glyph.glyph = unbroken_span_glyph_info->glyph;
826 new_glyph.in_character = _flow._characters.size();
827 new_glyph.rotation = glyph_rotate;
828 new_glyph.orientation = ORIENTATION_UPRIGHT; // Only effects vertical text
829 new_glyph.hidden = hidden; // SVG 2 overflow
830
831 // Advance does not include kerning but Pango <= 1.43 gives wrong advances for verical upright text.
832 double glyph_h_advance = new_span.font_size * font->Advance(new_glyph.glyph, false);
833 double glyph_v_advance = new_span.font_size * font->Advance(new_glyph.glyph, true );
834
835 #ifdef DEBUG_GLYPH
836
837 bool is_cluster_start = unbroken_span_glyph_info->attr.is_cluster_start;
838 std::cout << " " << std::hex << std::setw(6) << *iter_source_text << std::dec
839 << " " << std::setw(6) << new_glyph.glyph
840 << std::fixed << std::showpoint << std::setprecision(2)
841 << " " << std::setw(6) << glyph_h_advance
842 << " " << std::setw(6) << glyph_v_advance
843 << " " << std::setw(6) << glyph_width
844 << " " << std::setw(6) << std::boolalpha << is_cluster_start; // << std::endl;
845 #endif
846
847 // We may have scaled font size to fit textLength; now, if
848 // @lengthAdjust=spacingAndGlyphs, this scaling must be only horizontal,
849 // not vertical, so we unscale it back vertically during output
850 if (_flow.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS)
851 new_glyph.vertical_scale = 1.0 / _flow.getTextLengthMultiplierDue();
852 else
853 new_glyph.vertical_scale = 1.0;
854
855 // Position glyph --------------------
856 new_glyph.x = current_x;
857 new_glyph.y =_y_offset;
858 new_glyph.advance = glyph_width;
859
860 if (*iter_source_text == '\n') {
861 // Line feeds should take zero space but they are given 'space' width.
862 new_glyph.advance = 0.0;
863 }
864
865 // y-coordinate is flipped between vertical and horizontal text...
866 // delta_y is common offset but applied with opposite sign
867 double delta_x = unbroken_span_glyph_info->geometry.x_offset * font_size_multiplier;
868 double delta_y = unbroken_span_glyph_info->geometry.y_offset * font_size_multiplier - unbroken_span.baseline_shift;
869 SPCSSBaseline dominant_baseline = _flow._blockBaseline();
870
871 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
872 // Vertical text
873
874 // Default dominant baseline is determined by overall block (i.e. <text>) 'text-orientation' value.
875 if( _flow._blockTextOrientation() != SP_CSS_TEXT_ORIENTATION_SIDEWAYS ) {
876 if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_CENTRAL;
877 } else {
878 if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC;
879 }
880
881 // TODO: Should also check 'glyph_orientation_vertical' if 'text-orientation' is unset...
882 if( new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_SIDEWAYS ||
883 (new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_MIXED && pango_item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
884
885 // Sideways orientation (Latin characters, CJK punctuation), 90deg rotation done at output stage.
886
887 #ifdef DEBUG_GLYPH
888 std::cout << " Sideways"
889 << " " << std::setw(6) << new_glyph.x
890 << " " << std::setw(6) << new_glyph.y
891 << " " << std::setw(6) << delta_x
892 << " " << std::setw(6) << delta_y
893 << std::endl;
894 #endif
895
896 new_glyph.orientation = ORIENTATION_SIDEWAYS;
897
898 new_glyph.x += delta_x;
899 new_glyph.y -= delta_y;
900
901 // Multiplying by font-size could cause slight differences in
902 // positioning for different baselines if the font size varies within a
903 // line of text (e.g. sub-scripts and super-scripts).
904 new_glyph.y -= new_span.font_size * font->GetBaselines()[ dominant_baseline ];
905
906 } else {
907 // Upright orientation
908
909 #if PANGO_VERSION_CHECK(1,44,0)
910 auto hb_font = pango_font_get_hb_font(font->pFont);
911 #endif
912
913 #ifdef DEBUG_GLYPH
914 std::cout << " Upright"
915 << " " << std::setw(6) << new_glyph.x
916 << " " << std::setw(6) << new_glyph.y
917 << " " << std::setw(6) << delta_x
918 << " " << std::setw(6) << delta_y;
919 #if PANGO_VERSION_CHECK(1,44,0)
920 char glyph_name[32];
921 hb_font_get_glyph_name(hb_font, new_glyph.glyph, glyph_name, sizeof (glyph_name));
922 std::cout << " " << (glyph_name ? glyph_name : "");
923 #endif
924 std::cout << std::endl;
925 #endif
926
927 if (pango_version_check(1,44,0) != nullptr) {
928 // Pango < 1.44.0 (pre HarfBuzz)
929 new_glyph.x += delta_x;
930 new_glyph.y -= delta_y;
931
932 double shift = 0;
933 double scale_factor = PANGO_SCALE * _font_factory_size_multiplier;
934 if (!FT_HAS_VERTICAL(font->theFace)) {
935
936 // If there are no vertical metrics, glyphs are vertically
937 // centered before base anchor to mark anchor distance is
938 // calculated by shaper. We must undo this!
939 PangoRectangle ink_rect;
940 PangoRectangle logical_rect;
941 pango_font_get_glyph_extents (font->pFont,
942 new_glyph.glyph,
943 &ink_rect,
944 &logical_rect);
945
946 // Shift required to move centered glyph back to proper position
947 // relative to baseline.
948 shift =
949 font->GetTypoAscent() +
950 ink_rect.y / scale_factor + // negative
951 (ink_rect.height / scale_factor / 2.0) -
952 0.5;
953 }
954
955 // Advance is wrong (horizontal width used instead of vertical)...
956 if (g_unichar_type(*iter_source_text) != G_UNICODE_NON_SPACING_MARK) {
957
958 x_offset_advance = new_glyph.advance - glyph_v_advance;
959 new_glyph.advance = glyph_v_advance;
960
961 x_offset_center = shift;
962 } else {
963 // Is non-spacing mark!
964 if (!FT_HAS_VERTICAL(font->theFace)) {
965
966 // If font lacks vertical metrics, all glyphs have em-box advance
967 // but non-spacing marks should have zero advance.
968 new_glyph.advance = 0;
969
970 // Correct for base anchor to mark anchor shift.
971 new_glyph.x += (x_offset_center - shift) * new_span.font_size;
972 }
973
974 // Correct for advance error.
975 new_glyph.x += x_offset_advance;
976 }
977
978 // Need to shift by horizontal to vertical origin (as we don't load glyph
979 // with vertical metrics).
980 new_glyph.x += font->GetTypoAscent() * new_span.font_size;
981 new_glyph.y -= glyph_h_advance/2.0;
982
983 } else if (pango_version_check(1,48,1) != nullptr) {
984 // 1.44.0 <= Pango < 1.48.1 (minus sign error, mismatch between Cairo/Harfbuzz glyph
985 // placement)
986 new_glyph.x += (glyph_width - delta_x);
987 new_glyph.y -= delta_y;
988 } else if (pango_version_check(1,48,4) != nullptr) {
989 // 1.48.1 <= Pango < 1.48.4 (minus sign fix, partial fix for Cairo/Harfbuzz mismatch,
990 // but bad mark positioning)
991 new_glyph.x += delta_x;
992 new_glyph.y -= delta_y;
993
994 #if PANGO_VERSION_CHECK(1,44,0)
995 // Need to shift by horizontal to vertical origin. Recall Pango lays out vertical text
996 // as horizontal text then rotates by 90 degress so y_origin -> x, x_origin -> -y.
997 hb_position_t x_origin = 0.0;
998 hb_position_t y_origin = 0.0;
999 hb_font_get_glyph_v_origin(hb_font, new_glyph.glyph, &x_origin, &y_origin);
1000 new_glyph.x += y_origin * font_size_multiplier;
1001 new_glyph.y -= x_origin * font_size_multiplier;
1002 #endif
1003 } else {
1004 // 1.48.4 <= Pango (good mark positioning)
1005 new_glyph.x += delta_x;
1006 new_glyph.y -= delta_y;
1007 }
1008
1009 #if PANGO_VERSION_CHECK(1,44,0)
1010 // If a font has no vertical metrics, HarfBuzz using OpenType functions
1011 // (which Pango uses by default from 1.44.0) to position glyphs so that
1012 // the top of their "ink rectangle" is at the top of the "em-box". This
1013 // section of code moves each cluster (base glyph with marks) down to
1014 // match fonts with vertical metrics.
1015 hb_font_extents_t hb_font_extents_not_used;
1016 if (!hb_font_get_v_extents(hb_font, &hb_font_extents_not_used)) {
1017 // Font does not have vertical metrics!
1018
1019 if (g_unichar_type(*iter_source_text) !=
1020 G_UNICODE_NON_SPACING_MARK) { // Probably should include other marks!
1021 hb_glyph_extents_t glyph_extents;
1022 if (hb_font_get_glyph_extents(hb_font, new_glyph.glyph, &glyph_extents)) {
1023
1024 // double baseline_adjust =
1025 // font_instance->get_baseline(BASELINE_TEXT_BEFORE_EDGE) -
1026 // font_instance->get_baseline(BASELINE_ALPHABETIC);
1027 // std::cout << "baseline_adjust: " << baseline_adjust << std::endl;
1028 double baseline_adjust = new_span.line_height.ascent / new_span.font_size;
1029 int hb_x_scale = 0;
1030 int hb_y_scale = 0;
1031 hb_font_get_scale(hb_font, &hb_x_scale, &hb_y_scale);
1032 x_offset_cluster =
1033 ((glyph_extents.y_bearing / (double)hb_y_scale) - baseline_adjust) *
1034 new_span.font_size;
1035 } else {
1036 x_offset_cluster = 0.0; // Failed to find extents.
1037 }
1038 } else {
1039 // Is non-spacing mark!
1040
1041 // Many fonts report a non-zero vertical advance for marks, especially if the 'vmtx'
1042 // table is missing.
1043 new_glyph.advance = 0;
1044 }
1045
1046 new_glyph.x -= x_offset_cluster;
1047 }
1048 #endif
1049
1050 }
1051 } else {
1052 // Horizontal text
1053
1054 #ifdef DEBUG_GLYPH
1055 std::cout << " Horizontal"
1056 << " " << std::setw(6) << new_glyph.x
1057 << " " << std::setw(6) << new_glyph.y
1058 << " " << std::setw(6) << delta_x
1059 << " " << std::setw(6) << delta_y
1060 << std::endl;
1061 #endif
1062
1063 if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC;
1064
1065 new_glyph.x += delta_x;
1066 new_glyph.y += delta_y;
1067
1068 new_glyph.y += new_span.font_size * font->GetBaselines()[ dominant_baseline ];
1069 }
1070
1071 // Correct for right to left text
1072 if (new_span.direction == RIGHT_TO_LEFT) {
1073
1074 // The following commented out code is from 2005. Subtracting cluster width gives wrong placement if more
1075 // than one glyph has a horizontal advance. See GitHub issue 469. I leave the old code here in case switching to
1076 // subtracting only the glyph width causes unforseen bugs.
1077
1078 // // pango wanted to give us glyphs in visual order but we refused, so we need to work
1079 // // out where the cluster start is ourselves
1080
1081 // // Add up widths of remaining glyphs in span.
1082 // double cluster_width = 0.0;
1083 // std::cout << " glyph_index: " << glyph_index << " end_glyph_index: " << it_span->end_glyph_index << std::endl;
1084 // for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
1085 // if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index) {
1086 // break;
1087 // }
1088 // cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
1089 // }
1090 // new_glyph.x -= cluster_width;
1091
1092 new_glyph.x -= font_size_multiplier * unbroken_span.glyph_string->glyphs[glyph_index].geometry.width;
1093 }
1094
1095 // Store glyph data
1096 _flow._glyphs.push_back(new_glyph);
1097
1098 // Create the Layout::Character(s)
1099 if (newcluster) {
1100 newcluster = false;
1101
1102 // Figure out how many glyphs are in the log_cluster.
1103 log_cluster_size_glyphs = 0;
1104 for (; log_cluster_size_glyphs + glyph_index < it_span->end_glyph_index; log_cluster_size_glyphs++){
1105 if(unbroken_span.glyph_string->log_clusters[glyph_index ] !=
1106 unbroken_span.glyph_string->log_clusters[glyph_index + log_cluster_size_glyphs]) break;
1107 }
1108
1109 // Find where the text ends for this log_cluster.
1110 end_byte = it_span->start.iter_span->text_bytes; // Upper limit
1111 for(int next_glyph_index = glyph_index+1; next_glyph_index < unbroken_span.glyph_string->num_glyphs; next_glyph_index++){
1112 if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){
1113 end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index];
1114 break;
1115 }
1116 }
1117
1118 // Figure out how many characters are in the log_cluster.
1119 log_cluster_size_chars = 0;
1120 Glib::ustring::const_iterator lclist = iter_source_text;
1121 unsigned lcb = char_byte;
1122 while (lcb < end_byte){
1123 log_cluster_size_chars++;
1124 lclist++;
1125 lcb = lclist.base() - unbroken_span.input_stream_first_character.base();
1126 }
1127 }
1128
1129 double advance_width = new_glyph.advance;
1130 while (char_byte < end_byte) {
1131
1132 /* Hack to survive ligatures: in log_cluster keep the number of available chars >= number of glyphs remaining.
1133 When there are no ligatures these two sizes are always the same.
1134 */
1135 if (log_cluster_size_chars < log_cluster_size_glyphs) {
1136 log_cluster_size_glyphs--;
1137 break;
1138 }
1139
1140 // Store character info
1141 Layout::Character new_character;
1142 new_character.the_char = *iter_source_text;
1143 new_character.in_span = _flow._spans.size();
1144 new_character.x = x_in_span;
1145 new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
1146 new_character.in_glyph = (hidden ? -1 : _flow._glyphs.size() - 1);
1147 _flow._characters.push_back(new_character);
1148
1149 // Letter/word spacing and justification
1150 if (new_character.char_attributes.is_white)
1151 advance_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue() + add_to_each_whitespace; // justification
1152 if (new_character.char_attributes.is_cursor_position)
1153 advance_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue();
1154 advance_width += _flow.getTextLengthIncrementDue();
1155
1156 // Update counters
1157 iter_source_text++;
1158 char_index_in_unbroken_span++;
1159 char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
1160 log_cluster_size_chars--;
1161 }
1162
1163 // Update x position variables
1164 advance_width *= direction_sign;
1165 if (new_span.direction != para.direction) {
1166 counter_directional_width_remaining -= advance_width;
1167 current_x -= advance_width;
1168 x_in_span_last -= advance_width;
1169 } else {
1170 current_x += advance_width;
1171 x_in_span_last += advance_width;
1172 }
1173 } // Loop over glyphs in span
1174
1175 } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
1176 current_x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
1177 }
1178
1179 new_span.x_end = new_span.x_start + x_in_span_last;
1180 _flow._spans.push_back(new_span);
1181 previous_direction = new_span.direction;
1182 }
1183 // end adding spans to the list, on to the next chunk...
1184 }
1185 TRACE((" End _outputLine\n"));
1186 }
1187
1188 /**
1189 * Initialises the ScanlineMaker for the first shape in the flow,
1190 * or the infinite version if we're not doing wrapping.
1191 */
_createFirstScanlineMaker()1192 void Layout::Calculator::_createFirstScanlineMaker()
1193 {
1194 _current_shape_index = 0;
1195 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
1196 if (_flow._input_wrap_shapes.empty()) {
1197 // create the special no-wrapping infinite scanline maker
1198 double initial_x = 0, initial_y = 0;
1199 if (!text_source->x.empty()) {
1200 initial_x = text_source->x.front().computed;
1201 }
1202 if (!text_source->y.empty()) {
1203 initial_y = text_source->y.front().computed;
1204 }
1205 _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
1206 TRACE((" wrapping disabled\n"));
1207 }
1208 else {
1209 _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1210 TRACE((" begin wrap shape 0\n"));
1211
1212 // 'inline-size' uses an infinitely high (wide) shape. We must set initial y. (We only need to do it here as there is only one shape.)
1213 if (_flow.wrap_mode == WRAP_INLINE_SIZE) {
1214 _block_progression = _flow._blockProgression();
1215 if( _block_progression == RIGHT_TO_LEFT ||
1216 _block_progression == LEFT_TO_RIGHT ) {
1217 // Vertical text, CJK
1218 if (!text_source->x.empty()) {
1219 double initial_x = text_source->x.front().computed;
1220 _scanline_maker->setNewYCoordinate(initial_x);
1221 } else {
1222 std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no x value with 'inline-size'!" << std::endl;
1223 _scanline_maker->setNewYCoordinate(0);
1224 }
1225 } else {
1226 // Horizontal text
1227 if (!text_source->y.empty()) {
1228 double initial_y = text_source->y.front().computed;
1229 _scanline_maker->setNewYCoordinate(initial_y);
1230 } else {
1231 std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no y value with 'inline-size'!" << std::endl;
1232 _scanline_maker->setNewYCoordinate(0);
1233 }
1234 }
1235 }
1236 }
1237 }
1238
increment()1239 void Layout::Calculator::UnbrokenSpanPosition::increment()
1240 {
1241 gchar const *text_base = &*iter_span->input_stream_first_character.base();
1242 char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
1243 char_index++;
1244 if (char_byte == iter_span->text_bytes) {
1245 iter_span++;
1246 char_index = char_byte = 0;
1247 }
1248 }
1249
setZero()1250 void Layout::Calculator::BrokenSpan::setZero()
1251 {
1252 end = start;
1253 width = 0.0;
1254 whitespace_count = 0;
1255 end_glyph_index = start_glyph_index = 0;
1256 ends_with_whitespace = false;
1257 each_whitespace_width = 0.0;
1258 letter_spacing = 0.0;
1259 word_spacing = 0.0;
1260 }
1261
1262 ///**
1263 // * For sections of text with a block-progression different to the rest
1264 // * of the flow, the best thing to do is to detect them in advance and
1265 // * create child TextFlow objects with just the rotated text. In the
1266 // * parent we then effectively use ARBITRARY_GAP fields during the
1267 // * flowing (because we don't allow wrapping when the block-progression
1268 // * changes) and copy the actual text in during the output phase.
1269 // *
1270 // * NB: this code not enabled yet.
1271 // */
1272 //void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
1273 //{
1274 // Direction prev_block_progression = _block_progression;
1275 // int run_start_input_index = para->first_input_index;
1276 //
1277 // para->free_sequence(para->input_items);
1278 // for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
1279 // InputItemInfo input_item;
1280 //
1281 // input_item.in_sub_flow = false;
1282 // input_item.sub_flow = NULL;
1283 // if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1284 // Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1285 // if ( control_code->code == SHAPE_BREAK
1286 // || control_code->code == PARAGRAPH_BREAK)
1287 // break; // stop at the end of the paragraph
1288 // // all other control codes we'll pick up later
1289 //
1290 // } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
1291 // Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
1292 // Direction this_block_progression = text_source->styleGetBlockProgression();
1293 // if (this_block_progression != prev_block_progression) {
1294 // if (prev_block_progression != _block_progression) {
1295 // // need to back up so that control codes belong outside the block-progression change
1296 // int run_end_input_index = input_index - 1;
1297 // while (run_end_input_index > run_start_input_index
1298 // && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
1299 // run_end_input_index--;
1300 // // now create the sub-flow
1301 // input_item.sub_flow = new Layout;
1302 // for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
1303 // input_item.in_sub_flow = true;
1304 // if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
1305 // Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
1306 // input_item.sub_flow->appendControlCode(control_code->code, control_code->source, control_code->width, control_code->ascent, control_code->descent);
1307 // } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
1308 // Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
1309 // input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source, NULL, 0, text_source->text_begin, text_source->text_end);
1310 // Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
1311 // sub_flow_text_source->x = text_source->x; // this is easier than going via optionalattrs for the appendText() call
1312 // sub_flow_text_source->y = text_source->y; // should these actually be allowed anyway? You'll almost never get the results you expect
1313 // sub_flow_text_source->dx = text_source->dx; // (not that it's very clear what you should expect, anyway)
1314 // sub_flow_text_source->dy = text_source->dy;
1315 // sub_flow_text_source->rotate = text_source->rotate;
1316 // }
1317 // }
1318 // input_item.sub_flow->calculateFlow();
1319 // }
1320 // run_start_input_index = input_index;
1321 // }
1322 // prev_block_progression = this_block_progression;
1323 // }
1324 // para->input_items.push_back(input_item);
1325 // }
1326 //}
1327
1328 /**
1329 * Take all the text from \a _para.first_input_index to the end of the
1330 * paragraph and stitch it together so that pango_itemize() can be called on
1331 * the whole thing.
1332 *
1333 * Input: para.first_input_index.
1334 * Output: para.direction, para.pango_items, para.char_attributes.
1335 * Returns: the number of spans created by pango_itemize
1336 */
_buildPangoItemizationForPara(ParagraphInfo * para) const1337 void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
1338 {
1339 TRACE(("pango version string: %s\n", pango_version_string() ));
1340 #if PANGO_VERSION_CHECK(1,37,1)
1341 TRACE((" ... compiled for font features\n"));
1342 #endif
1343
1344 TRACE(("itemizing para, first input %d\n", para->first_input_index));
1345
1346 PangoAttrList *attributes_list = pango_attr_list_new();
1347 for (unsigned input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1348 if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1349 Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1350 if ( control_code->code == SHAPE_BREAK
1351 || control_code->code == PARAGRAPH_BREAK)
1352 break; // stop at the end of the paragraph
1353 // all other control codes we'll pick up later
1354
1355 } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
1356 Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
1357
1358 // create the font_instance
1359 font_instance *font = text_source->styleGetFontInstance();
1360 if (font == nullptr)
1361 continue; // bad news: we'll have to ignore all this text because we know of no font to render it
1362
1363 PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
1364 attribute_font_description->start_index = para->text.bytes();
1365
1366 #if PANGO_VERSION_CHECK(1,37,1)
1367 PangoAttribute *attribute_font_features =
1368 pango_attr_font_features_new( text_source->style->getFontFeatureString().c_str());
1369 attribute_font_features->start_index = para->text.bytes();
1370 #endif
1371 para->text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text
1372
1373 attribute_font_description->end_index = para->text.bytes();
1374 pango_attr_list_insert(attributes_list, attribute_font_description);
1375
1376 #if PANGO_VERSION_CHECK(1,37,1)
1377 attribute_font_features->end_index = para->text.bytes();
1378 pango_attr_list_insert(attributes_list, attribute_font_features);
1379 #endif
1380
1381 // Set language
1382 SPObject * object = text_source->source;
1383 if (!object->lang.empty()) {
1384 PangoLanguage* language = pango_language_from_string(object->lang.c_str());
1385 PangoAttribute *attribute_language = pango_attr_language_new( language );
1386 pango_attr_list_insert(attributes_list, attribute_language);
1387 }
1388
1389 // ownership of attribute is assumed by the list
1390 font->Unref();
1391 }
1392 }
1393
1394 TRACE(("whole para: \"%s\"\n", para->text.data()));
1395 TRACE(("%d input sources used\n", input_index - para->first_input_index));
1396
1397 // Pango Itemize
1398 GList *pango_items_glist = nullptr;
1399 para->direction = LEFT_TO_RIGHT; // CSS default
1400 if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
1401 Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
1402
1403 para->direction = (text_source->style->direction.computed == SP_CSS_DIRECTION_LTR) ? LEFT_TO_RIGHT : RIGHT_TO_LEFT;
1404 PangoDirection pango_direction = (text_source->style->direction.computed == SP_CSS_DIRECTION_LTR) ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
1405 pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para->text.data(), 0, para->text.bytes(), attributes_list, nullptr);
1406 }
1407
1408 if( pango_items_glist == nullptr ) {
1409 // Type wasn't TEXT_SOURCE or direction was not set.
1410 pango_items_glist = pango_itemize(_pango_context, para->text.data(), 0, para->text.bytes(), attributes_list, nullptr);
1411 }
1412
1413 pango_attr_list_unref(attributes_list);
1414
1415 // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
1416 para->pango_items.reserve(g_list_length(pango_items_glist));
1417 TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
1418 for (GList *current_pango_item = pango_items_glist ; current_pango_item != nullptr ; current_pango_item = current_pango_item->next) {
1419 PangoItemInfo new_item;
1420 new_item.item = (PangoItem*)current_pango_item->data;
1421 PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
1422 new_item.font = (font_factory::Default())->Face(font_description);
1423 pango_font_description_free(font_description); // Face() makes a copy
1424 para->pango_items.push_back(new_item);
1425 }
1426 g_list_free(pango_items_glist);
1427
1428 // and get the character attributes on everything
1429 para->char_attributes.resize(para->text.length() + 1);
1430 pango_get_log_attrs(para->text.data(), para->text.bytes(), -1, nullptr, &*para->char_attributes.begin(), para->char_attributes.size());
1431
1432 TRACE(("end para itemize, direction = %d\n", para->direction));
1433 }
1434
1435 /**
1436 * Finds the value of line_height_multiplier given the 'line-height' property. The result of
1437 * multiplying \a l by \a line_height_multiplier is the inline box height as specified in css2
1438 * section 10.8. http://www.w3.org/TR/CSS2/visudet.html#line-height
1439 *
1440 * The 'computed' value of 'line-height' does not have a consistent meaning. We need to find the
1441 * 'used' value and divide that by the font size.
1442 */
_computeFontLineHeight(SPStyle const * style)1443 double Layout::Calculator::_computeFontLineHeight( SPStyle const *style )
1444 {
1445 // This is a bit backwards... we should be returning the absolute height
1446 // but as the code expects line_height_multiplier we return that.
1447 if (style->line_height.normal) {
1448 return (LINE_HEIGHT_NORMAL);
1449 } else if (style->line_height.unit == SP_CSS_UNIT_NONE) {
1450 // Special case per CSS, computed value is multiplier
1451 return style->line_height.computed;
1452 } else {
1453 // Normal case, computed value is absolute height. Turn it into multiplier.
1454 return style->line_height.computed / style->font_size.computed;
1455 }
1456 }
1457
compareGlyphWidth(const PangoGlyphInfo & a,const PangoGlyphInfo & b)1458 bool compareGlyphWidth(const PangoGlyphInfo &a, const PangoGlyphInfo &b)
1459 {
1460 bool retval = false;
1461 if ( b.geometry.width == 0 && (a.geometry.width > 0))retval = true;
1462 return (retval);
1463 }
1464
1465
1466 /**
1467 * Split the paragraph into spans. Also call pango_shape() on them.
1468 *
1469 * Input: para->first_input_index, para->pango_items
1470 * Output: para->spans
1471 * Returns: the index of the beginning of the following paragraph in _flow._input_stream
1472 */
_buildSpansForPara(ParagraphInfo * para) const1473 unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
1474 {
1475 unsigned pango_item_index = 0;
1476 unsigned char_index_in_para = 0;
1477 unsigned byte_index_in_para = 0;
1478 unsigned input_index;
1479 unsigned para_text_index = 0;
1480
1481 TRACE(("build spans\n"));
1482 para->free_sequence(para->unbroken_spans);
1483
1484 for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
1485 if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
1486 Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
1487
1488 if ( control_code->code == SHAPE_BREAK
1489 || control_code->code == PARAGRAPH_BREAK) {
1490
1491 // Add span to be used to calculate line spacing of blank lines.
1492 UnbrokenSpan new_span;
1493 new_span.pango_item_index = -1;
1494 new_span.input_index = input_index;
1495
1496 // No pango object, so find font and line height ourselves.
1497 SPObject * object = control_code->source;
1498 if (object) {
1499 SPStyle * style = object->style;
1500 if (style) {
1501 new_span.font_size = style->font_size.computed * _flow.getTextLengthMultiplierDue();
1502 font_factory * factory = font_factory::Default();
1503 font_instance * font = factory->FaceFromStyle( style );
1504 new_span.line_height_multiplier = _computeFontLineHeight( object->style );
1505 new_span.line_height.set( font );
1506 new_span.line_height *= new_span.font_size;
1507 }
1508 }
1509 new_span.text_bytes = 0;
1510 new_span.char_index_in_para = char_index_in_para;
1511 para->unbroken_spans.push_back(new_span);
1512 TRACE(("add empty span for break %lu\n", para->unbroken_spans.size() - 1));
1513 break; // stop at the end of the paragraph
1514
1515 } else if (control_code->code == ARBITRARY_GAP) { // Not used!
1516
1517 UnbrokenSpan new_span;
1518 new_span.pango_item_index = -1;
1519 new_span.input_index = input_index;
1520 new_span.line_height.ascent = control_code->ascent * _flow.getTextLengthMultiplierDue();
1521 new_span.line_height.descent = control_code->descent * _flow.getTextLengthMultiplierDue();
1522 new_span.text_bytes = 0;
1523 new_span.char_index_in_para = char_index_in_para;
1524 para->unbroken_spans.push_back(new_span);
1525 TRACE(("add gap span %lu\n", para->unbroken_spans.size() - 1));
1526 }
1527 } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
1528 Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
1529 unsigned char_index_in_source = 0;
1530 unsigned span_start_byte_in_source = 0;
1531
1532 // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
1533 for ( ; ; ) {
1534 /* we need to change spans at every change of PangoItem, source stream change,
1535 or change in one of the attributes altering position/rotation. */
1536
1537 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
1538 ? 0
1539 : ( para->pango_items[pango_item_index].item->offset
1540 + para->pango_items[pango_item_index].item->length
1541 - byte_index_in_para ) );
1542 unsigned const text_source_bytes = ( text_source->text_end.base()
1543 - text_source->text_begin.base()
1544 - span_start_byte_in_source );
1545 TRACE(("New Span\n"));
1546 UnbrokenSpan new_span;
1547 new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
1548 new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
1549 new_span.char_index_in_para = char_index_in_para + char_index_in_source;
1550 new_span.input_index = input_index;
1551
1552 // cut at <tspan> attribute changes as well
1553 new_span.x._set = false;
1554 new_span.y._set = false;
1555 new_span.dx._set = false;
1556 new_span.dy._set = false;
1557 new_span.rotate._set = false;
1558 if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
1559 // Horizontal text
1560 if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source];
1561 if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source];
1562 if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1563 if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1564 } else {
1565 // Vertical text
1566 if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source];
1567 if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source];
1568 if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1569 if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
1570 }
1571 if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
1572 else if (char_index_in_source == 0) new_span.rotate = 0.f;
1573 if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
1574 // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
1575 // so that the top of the letters is at zero, not the baseline
1576 new_span.y = 0.0;
1577 }
1578 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
1579 iter_text++;
1580 for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
1581 if (iter_text >= text_source->text_end) break;
1582 if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
1583 if ( i >= text_source->x.size() && i >= text_source->y.size()
1584 && i >= text_source->dx.size() && i >= text_source->dy.size()
1585 && i >= text_source->rotate.size()) break;
1586 if ( (text_source->x.size() > i && text_source->x[i]._set)
1587 || (text_source->y.size() > i && text_source->y[i]._set)
1588 || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
1589 || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
1590 || (text_source->rotate.size() > i && text_source->rotate[i]._set
1591 && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
1592 new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
1593 break;
1594 }
1595 }
1596
1597 // now we know the length, do some final calculations and add the UnbrokenSpan to the list
1598 new_span.font_size = text_source->style->font_size.computed * _flow.getTextLengthMultiplierDue();
1599 if (new_span.text_bytes) {
1600 new_span.glyph_string = pango_glyph_string_new();
1601 /* Some assertions intended to help diagnose bug #1277746. */
1602 g_assert( 0 < new_span.text_bytes );
1603 g_assert( span_start_byte_in_source < text_source->text->bytes() );
1604 g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
1605 g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
1606 == nullptr );
1607
1608 /* Notes as of 4/29/13. Pango_shape is not generating English language ligatures, but it is generating
1609 them for Hebrew (and probably other similar languages). In the case observed 3 unicode characters (a base
1610 and 2 Mark, nonspacings) are merged into two glyphs (the base + first Mn, the 2nd Mn). All of these map
1611 from glyph to first character of the log_cluster range. This destroys the 1:1 correspondence between
1612 characters and glyphs. A big chunk of the conditional code which immediately follows this call
1613 is there to clean up the resulting mess.
1614 */
1615
1616 // Assumption: old and new arguments are the same.
1617 auto gold = std::string_view(text_source->text->data() + span_start_byte_in_source, new_span.text_bytes);
1618 auto gnew = std::string_view(para->text.data() + para_text_index, new_span.text_bytes);
1619 assert (gold == gnew);
1620
1621 // Convert characters to glyphs
1622 pango_shape_full(para->text.data() + para_text_index,
1623 new_span.text_bytes,
1624 para->text.data(),
1625 -1,
1626 ¶->pango_items[pango_item_index].item->analysis,
1627 new_span.glyph_string);
1628
1629 if (para->pango_items[pango_item_index].item->analysis.level & 1) {
1630 // Right to left text (Arabic, Hebrew, etc.)
1631
1632 // pango_shape() will reorder glyphs in rtl sections into visual order
1633 // (start offsets in accending order) which messes us up because the svg
1634 // spec requires us to draw glyphs in logical order so let's reverse the
1635 // glyphstring.
1636
1637 const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1638 std::vector<PangoGlyphInfo> infos(nglyphs);
1639 std::vector<gint> clusters(nglyphs);
1640
1641 for (int i = 0; i < nglyphs; ++i) {
1642 std::copy(&new_span.glyph_string->glyphs[i], &new_span.glyph_string->glyphs[i+1], infos.end() - i - 1);
1643 std::copy(&new_span.glyph_string->log_clusters[i], &new_span.glyph_string->log_clusters[i+1], clusters.end() - i - 1);
1644 }
1645
1646 std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
1647 std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
1648
1649 // We've messed up the flag that tells a glyph it is first in a cluster.
1650 for (int i = 0; i < nglyphs; ++i) {
1651
1652 // Set flag for start of cluster, we skip all other glyphs in cluster below.
1653 new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
1654
1655 // Find index of first glyph in next cluster
1656 int j = i + 1;
1657 while( (j < nglyphs) &&
1658 (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
1659 ) {
1660 new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0; // Zero
1661 j++;
1662 }
1663
1664 // Move on to next cluster.
1665 i = j;
1666 }
1667
1668 } // End right to left text.
1669
1670 // The following sorting doesn't seem to be necessary, and causes
1671 // https://gitlab.com/inkscape/inkscape/-/issues/394 ...
1672
1673 /*
1674 CAREFUL, within a log_cluster the order of glyphs may not map 1:1, or
1675 even in the same order, to the original unicode characters!!! Among
1676 other things, diacritical mark glyphs can end up sequentially in front of the base
1677 character glyph. That makes determining kerning, even approximately, difficult
1678 later on.
1679
1680 To resolve this to the extent possible sort the glyphs within the same
1681 log_cluster into descending order by width in a special manner before copying. Diacritical marks
1682 and similar have zero width and the glyph they modify has nonzero width. The order
1683 of the zero width ones does not matter. A logical cluster is sorted into sequential order
1684 [base] [zw_modifier1] [zw_modifier2]
1685 where all the modifiers have zero width and the base does not. This works for languages like Hebrew.
1686
1687 Pango also creates log clusters for languages like Telugu having many glyphs with nonzero widths.
1688 Since these are nonzero, their order is not modified.
1689
1690 If some language mixes these modes, having a log cluster having something like
1691 [base1] [zw_modifier1] [base2] [zw_modifier2]
1692 the result will be incorrect:
1693 base1] [base2] [zw_modifier1] [zw_modifier2]
1694
1695 If ligatures other than with Mark, nonspacing are ever implemented in Pango this will screw up, for instance
1696 changing "fi" to "if".
1697 */
1698
1699 // If it is necessary to move zero width glyphs.. then it applies to both right-to-left and left-to-right text.
1700 // const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1701 // for (int i = 0; i < nglyphs; ++i) {
1702
1703 // // Zero flag for start of cluster, we zero the rest below, and then reset it after sorting.
1704 // new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0;
1705
1706 // // Find index of first glyph in next cluster
1707 // int j = i + 1;
1708 // while( (j < nglyphs) &&
1709 // (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
1710 // ) {
1711 // new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0; // Zero
1712 // j++;
1713 // }
1714
1715 // if (j - i) {
1716 // // More than one glyph in cluster -> sort.
1717 // std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j]), compareGlyphWidth);
1718 // }
1719
1720 // // Now we're sorted, set flag for start of cluster.
1721 // new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
1722
1723 // // Move on to next cluster.
1724 // i = j;
1725 // }
1726 /* glyphs[].x_offset values are probably out of order within any log_clusters, apparently harmless */
1727
1728
1729 new_span.pango_item_index = pango_item_index;
1730 new_span.line_height_multiplier = _computeFontLineHeight( text_source->style );
1731 new_span.line_height.set( para->pango_items[pango_item_index].font );
1732 new_span.line_height *= new_span.font_size;
1733
1734 // At some point we may want to calculate baseline_shift here (to take advantage
1735 // of otm features like superscript baseline), but for now we use style baseline_shift.
1736 new_span.baseline_shift = text_source->style->baseline_shift.computed;
1737 new_span.text_orientation = (SPCSSTextOrientation)text_source->style->text_orientation.computed;
1738
1739 // TODO: metrics for vertical text
1740 TRACE(("add text span %lu \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
1741 TRACE((" %d glyphs\n", new_span.glyph_string->num_glyphs));
1742 } else {
1743 // if there's no text we still need to initialise the styles
1744 new_span.pango_item_index = -1;
1745 font_instance *font = text_source->styleGetFontInstance();
1746 if (font) {
1747 new_span.line_height_multiplier = _computeFontLineHeight( text_source->style );
1748 new_span.line_height.set( font );
1749 new_span.line_height *= new_span.font_size;
1750 font->Unref();
1751 } else {
1752 new_span.line_height *= 0.0; // Set all to zero
1753 new_span.line_height_multiplier = LINE_HEIGHT_NORMAL;
1754 }
1755 TRACE(("add style init span %lu\n", para->unbroken_spans.size()));
1756 }
1757 para->unbroken_spans.push_back(new_span);
1758
1759 // calculations for moving to the next UnbrokenSpan
1760 byte_index_in_para += new_span.text_bytes;
1761 para_text_index += new_span.text_bytes;
1762 char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
1763
1764 if (new_span.text_bytes >= pango_item_bytes) { // end of pango item
1765 pango_item_index++;
1766 if (pango_item_index == para->pango_items.size()) break; // end of paragraph
1767 }
1768 if (new_span.text_bytes == text_source_bytes)
1769 break; // end of source
1770 // else <tspan> attribute changed
1771 span_start_byte_in_source += new_span.text_bytes;
1772 }
1773 char_index_in_para += char_index_in_source; // This seems wrong. Probably should be inside loop.
1774 }
1775 }
1776 TRACE(("end build spans\n"));
1777 return input_index;
1778 }
1779
1780 /**
1781 * Moves onto next shape with a new scanline_maker.
1782 * If there is no next shape, creates an infinite scanline maker to stash remaining text.
1783 * Returns false if an infinite scanline maker is created.
1784 */
_goToNextWrapShape()1785 bool Layout::Calculator::_goToNextWrapShape()
1786 {
1787 if (_flow._input_wrap_shapes.size() == 0) {
1788 // Shouldn't happen.
1789 std::cerr << "Layout::Calculator::_goToNextWrapShape() called for text without shapes!" << std::endl;
1790 return false;
1791 }
1792
1793 if (_current_shape_index >= _flow._input_wrap_shapes.size()) {
1794 // Shouldn't happen.
1795 std::cerr << "Layout::Calculator::_goToNextWrapShape(): shape index too large!" << std::endl;
1796 }
1797
1798 _current_shape_index++;
1799
1800 delete _scanline_maker;
1801 _scanline_maker = nullptr;
1802
1803 if (_current_shape_index < _flow._input_wrap_shapes.size()) {
1804 _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
1805 TRACE(("begin wrap shape %u\n", _current_shape_index));
1806 return true;
1807 } else {
1808 // Out of shapes, create infinite scanline maker to stash overflow.
1809
1810 // First find a suitable position for overflow text. (index - 1 exists since we just incremented index)
1811 double x = _flow._input_wrap_shapes[_current_shape_index - 1].shape->leftX;
1812 double y = _flow._input_wrap_shapes[_current_shape_index - 1].shape->bottomY;
1813
1814 _scanline_maker = new InfiniteScanlineMaker(x, y, _block_progression);
1815 TRACE(("out of wrap shapes, stash leftover\n"));
1816 return false;
1817 }
1818
1819 // Shouldn't reach
1820 }
1821
1822 /**
1823 * Given \a para filled in and \a start_span_pos set, keeps trying to
1824 * find somewhere it can fit the next line of text. The process of finding
1825 * the text that fits will involve creating one or more entries in
1826 * \a chunk_info describing the bounds of the fitted text and several
1827 * bits of information that will prove useful when we come to output the
1828 * line to #_flow. Returns with \a start_span_pos set to the end of the
1829 * text that was fitted, \a chunk_info completely filled out and
1830 * \a line_box_height set with the largest ascent and the largest
1831 * descent (individually per CSS) on the line. The line_box_height
1832 * can never be smaller than the line_box_strut (which is determined
1833 * by the block level value of line_height). The return
1834 * value is false only if we've run out of shapes to wrap inside (and
1835 * hence stashed overflow).
1836 */
_findChunksForLine(ParagraphInfo const & para,UnbrokenSpanPosition * start_span_pos,std::vector<ChunkInfo> * chunk_info,FontMetrics * line_box_height,FontMetrics const * strut_height)1837 bool Layout::Calculator::_findChunksForLine(ParagraphInfo const ¶,
1838 UnbrokenSpanPosition *start_span_pos,
1839 std::vector<ChunkInfo> *chunk_info,
1840 FontMetrics *line_box_height,
1841 FontMetrics const *strut_height)
1842 {
1843 TRACE((" begin _findChunksForLine: chunks: %lu, em size: %f\n", chunk_info->size(), line_box_height->emSize() ));
1844
1845 // CSS 2.1 dictates that the minimum line height (i.e. the strut height)
1846 // is found from the block element.
1847 *line_box_height = *strut_height;
1848 TRACE((" initial line_box_height (em size): %f\n", line_box_height->emSize() ));
1849
1850 bool truncated = false;
1851
1852 UnbrokenSpanPosition span_pos;
1853 for( ; ; ) {
1854 // Get regions where one can place one line of text (can be more than one, if filling a
1855 // donut for example).
1856 std::vector<ScanlineMaker::ScanRun> scan_runs;
1857 scan_runs = _scanline_maker->makeScanline(*line_box_height); // 1 scan run with "InfiniteScanlineMaker"
1858
1859 // If scan_runs is empty, we must have reached the bottom of a shape. Go to next shape.
1860 while (scan_runs.empty()) {
1861 // Reset for new shape.
1862 *line_box_height = *strut_height;
1863
1864 // Only used by ShapeScanlineMaker
1865 if (!_goToNextWrapShape()) {
1866 truncated = true;
1867 }
1868
1869 // If we've run out of shapes, this will be the infinite line scanline maker with one scan_run).
1870 scan_runs = _scanline_maker->makeScanline(*line_box_height);
1871 }
1872
1873
1874 TRACE((" finding line fit y=%f, %lu scan runs\n", scan_runs.front().y, scan_runs.size()));
1875 chunk_info->clear();
1876 chunk_info->reserve(scan_runs.size());
1877 if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
1878 unsigned scan_run_index;
1879 span_pos = *start_span_pos;
1880 for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
1881 // Returns false if some text in line requires a taller line_box_height.
1882 // (We try again with a larger line_box_height.)
1883 if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_box_height)) {
1884 break;
1885 }
1886
1887 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty()) {
1888 span_pos = chunk_info->back().broken_spans.back().end;
1889 }
1890 }
1891
1892 if (scan_run_index == scan_runs.size()) break; // ie when buildChunksInScanRun() succeeded
1893
1894 } // End for loop
1895
1896 *start_span_pos = span_pos;
1897 TRACE((" final line_box_height: %f\n", line_box_height->emSize() ));
1898 TRACE((" end _findChunksForLine: chunks: %lu, truncated: %s\n", chunk_info->size(), truncated ? "true" : "false"));
1899 return !truncated;
1900 }
1901
1902 /**
1903 * Given a scan run and a first character, append one or more chunks to
1904 * the \a chunk_info vector that describe all the spans and other detail
1905 * necessary to output the greatest amount of text that will fit on this scan
1906 * line (greedy line breaking algorithm). Each chunk contains one or more
1907 * BrokenSpan structures that link back to UnbrokenSpan structures that link
1908 * to the text itself. Normally there will be either one or zero (if the
1909 * scanrun is too short to fit any text) chunk added to \a chunk_info by
1910 * each call to this method, but we will add more than one if an x or y
1911 * attribute has been set on a tspan. \a line_height must be set on input,
1912 * and if it needs to be made larger and the #_scanline_maker can't do
1913 * an in-situ resize then it will be set to the required value and the
1914 * method will return false.
1915 */
_buildChunksInScanRun(ParagraphInfo const & para,UnbrokenSpanPosition const & start_span_pos,ScanlineMaker::ScanRun const & scan_run,std::vector<ChunkInfo> * chunk_info,FontMetrics * line_height) const1916 bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const ¶,
1917 UnbrokenSpanPosition const &start_span_pos,
1918 ScanlineMaker::ScanRun const &scan_run,
1919 std::vector<ChunkInfo> *chunk_info,
1920 FontMetrics *line_height) const
1921 {
1922 TRACE((" begin _buildChunksInScanRun: chunks: %lu, em size: %f\n", chunk_info->size(), line_height->emSize() ));
1923
1924 FontMetrics line_height_saved = *line_height; // Store for recalculating line height if chunks are backed out
1925
1926 ChunkInfo new_chunk;
1927 new_chunk.text_width = 0.0;
1928 new_chunk.whitespace_count = 0;
1929 new_chunk.scanrun_width = scan_run.width();
1930 new_chunk.x = scan_run.x_start;
1931
1932 // we haven't done anything yet so the last valid break position is the beginning
1933 BrokenSpan last_span_at_break, last_span_at_emergency_break;
1934 last_span_at_break.start = start_span_pos;
1935 last_span_at_break.setZero();
1936 last_span_at_emergency_break.start = start_span_pos;
1937 last_span_at_emergency_break.setZero();
1938
1939 TRACE((" trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
1940 BrokenSpan new_span;
1941 new_span.end = start_span_pos;
1942 while (new_span.end.iter_span != para.unbroken_spans.end()) { // this loops once for each UnbrokenSpan
1943 new_span.start = new_span.end;
1944
1945 // force a chunk change at x or y attribute change
1946 if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1947
1948 if (new_span.start.iter_span != start_span_pos.iter_span)
1949 chunk_info->push_back(new_chunk);
1950
1951 new_chunk.x += new_chunk.text_width;
1952 new_chunk.text_width = 0.0;
1953 new_chunk.whitespace_count = 0;
1954 new_chunk.broken_spans.clear();
1955 if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
1956 // y doesn't need to be done until output time
1957 }
1958
1959 // see if this span is too tall to fit on the current line
1960 FontMetrics new_span_height = new_span.start.iter_span->line_height;
1961 new_span_height.computeEffective( new_span.start.iter_span->line_height_multiplier );
1962
1963 /* floating point 80-bit/64-bit rounding problems require epsilon. See
1964 discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
1965 if ( new_span_height.ascent > line_height->ascent + std::numeric_limits<float>::epsilon() ||
1966 new_span_height.descent > line_height->descent + std::numeric_limits<float>::epsilon() ) {
1967 // Take larger of each of the two ascents and two descents per CSS
1968 line_height->max(new_span_height);
1969
1970 // Currently always true for flowed text and false for Inkscape multiline text.
1971 if (!_scanline_maker->canExtendCurrentScanline(*line_height)) {
1972 return false;
1973 }
1974 }
1975
1976 bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
1977
1978 new_chunk.text_width += new_span.width;
1979 new_chunk.whitespace_count += new_span.whitespace_count;
1980 new_chunk.broken_spans.push_back(new_span); // if !span_fitted we'll correct ourselves below
1981
1982 if (!span_fitted) break;
1983
1984 if (new_span.end.iter_span == para.unbroken_spans.end()) {
1985 last_span_at_break = new_span;
1986 break;
1987 }
1988
1989 PangoLogAttr const &char_attributes = _charAttributes(para, new_span.end);
1990 if (char_attributes.is_mandatory_break) {
1991 last_span_at_break = new_span;
1992 break;
1993 }
1994 }
1995
1996 TRACE((" chunk complete, used %f width (%d whitespaces, %lu brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
1997 chunk_info->push_back(new_chunk);
1998
1999 if (scan_run.width() >= 4.0 * line_height->emSize() && last_span_at_break.end == start_span_pos) {
2000 /* **non-SVG spec bit**: See bug #1191102
2001 If the user types a very long line with no spaces, the way the spec
2002 is written at the moment means that when the length of the text
2003 exceeds the available width of all remaining areas, the text is
2004 completely hidden. This condition alters that behaviour so that if
2005 the length of the line is greater than four times the line-height
2006 and there are no spaces, it'll be emergency-wrapped at the last
2007 character. One could read the SVG Tiny 1.2 draft as permitting this
2008 sort of behaviour, but it's still a bit dodgy. The hard-coding of
2009 4x is not nice, either. */
2010 last_span_at_break = last_span_at_emergency_break;
2011 }
2012
2013 if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
2014 // need to back out spans until we come to the one with the last break in it
2015 while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
2016 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
2017 chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
2018 chunk_info->back().broken_spans.pop_back();
2019 if (chunk_info->back().broken_spans.empty())
2020 chunk_info->pop_back();
2021 }
2022 if (!chunk_info->empty()) {
2023 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
2024 chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
2025 if (last_span_at_break.start == last_span_at_break.end) {
2026 chunk_info->back().broken_spans.pop_back(); // last break was at an existing boundary
2027 if (chunk_info->back().broken_spans.empty())
2028 chunk_info->pop_back();
2029 } else {
2030 chunk_info->back().broken_spans.back() = last_span_at_break;
2031 chunk_info->back().text_width += last_span_at_break.width;
2032 chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
2033 }
2034 TRACE((" correction: fitted span %lu width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
2035 }
2036 }
2037
2038 // Recalculate line_box_height after backing out chunks
2039 *line_height = line_height_saved;
2040 for (const auto & it_chunk : *chunk_info) {
2041 for (const auto & broken_span : it_chunk.broken_spans) {
2042 FontMetrics span_height = broken_span.start.iter_span->line_height;
2043 TRACE((" brokenspan line_height: %f\n", span_height.emSize() ));
2044 span_height.computeEffective( broken_span.start.iter_span->line_height_multiplier );
2045 line_height->max( span_height );
2046 }
2047 }
2048 TRACE((" line_box_height: %f\n", line_height->emSize()));
2049
2050 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
2051 // for justification we need to discard space occupied by the single whitespace at the end of the chunk
2052 TRACE((" backing out whitespace\n"));
2053 chunk_info->back().broken_spans.back().ends_with_whitespace = false;
2054 chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
2055 chunk_info->back().broken_spans.back().whitespace_count--;
2056 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
2057 chunk_info->back().whitespace_count--;
2058 }
2059
2060 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
2061 // for justification we need to discard line-spacing and word-spacing at end of the chunk
2062 chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().letter_spacing;
2063 chunk_info->back().text_width -= chunk_info->back().broken_spans.back().letter_spacing;
2064 TRACE((" width after subtracting last letter_spacing: %f\n", chunk_info->back().broken_spans.back().width));
2065 }
2066
2067 TRACE((" end _buildChunksInScanRun: chunks: %lu\n", chunk_info->size()));
2068 return true;
2069 }
2070
2071 #ifdef DEBUG_LAYOUT_TNG_COMPUTE
2072 /**
2073 * For debugging, not called in distributed code
2074 *
2075 * Input: para->first_input_index, para->pango_items
2076 */
dumpPangoItemsOut(ParagraphInfo * para)2077 void Layout::Calculator::dumpPangoItemsOut(ParagraphInfo *para){
2078 std::cout << "Pango items: " << para->pango_items.size() << std::endl;
2079 font_factory * factory = font_factory::Default();
2080 for(unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){
2081 std::cout
2082 << "idx: " << pidx
2083 << " offset: "
2084 << para->pango_items[pidx].item->offset
2085 << " length: "
2086 << para->pango_items[pidx].item->length
2087 << " font: "
2088 << factory->ConstructFontSpecification( para->pango_items[pidx].font )
2089 << std::endl;
2090 }
2091 }
2092
2093 /**
2094 * For debugging, not called in distributed code
2095 *
2096 * Input: para->first_input_index, para->pango_items
2097 */
dumpUnbrokenSpans(ParagraphInfo * para)2098 void Layout::Calculator::dumpUnbrokenSpans(ParagraphInfo *para){
2099 std::cout << "Unbroken Spans: " << para->unbroken_spans.size() << std::endl;
2100 for(unsigned uidx = 0 ; uidx < para->unbroken_spans.size(); uidx++){
2101 std::cout
2102 << "idx: " << uidx
2103 << " pango_item_index: " << para->unbroken_spans[uidx].pango_item_index
2104 << " input_index: " << para->unbroken_spans[uidx].input_index
2105 << " char_index_in_para: " << para->unbroken_spans[uidx].char_index_in_para
2106 << " text_bytes: " << para->unbroken_spans[uidx].text_bytes
2107 << std::endl;
2108 }
2109 }
2110 #endif //DEBUG_LAYOUT_TNG_COMPUTE
2111
2112 /** The management function to start the whole thing off. */
calculate()2113 bool Layout::Calculator::calculate()
2114 {
2115 if (_flow._input_stream.empty())
2116 return false;
2117 /**
2118 * hm, why do we want assert (crash) the application, now do simply return false
2119 * \todo check if this is the correct behaviour
2120 * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
2121 */
2122 if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
2123 {
2124 g_warning("flow text is not of type TEXT_SOURCE. Abort.");
2125 return false;
2126 }
2127 TRACE(("begin calculate()\n"));
2128
2129 _flow._clearOutputObjects();
2130
2131 _pango_context = (font_factory::Default())->fontContext;
2132
2133 _font_factory_size_multiplier = (font_factory::Default())->fontSize;
2134
2135 _block_progression = _flow._blockProgression();
2136 if( _block_progression == RIGHT_TO_LEFT || _block_progression == LEFT_TO_RIGHT ) {
2137 // Vertical text, CJK
2138 switch (_flow._blockTextOrientation()) {
2139 case SP_CSS_TEXT_ORIENTATION_MIXED:
2140 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
2141 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
2142 break;
2143 case SP_CSS_TEXT_ORIENTATION_UPRIGHT:
2144 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
2145 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
2146 break;
2147 case SP_CSS_TEXT_ORIENTATION_SIDEWAYS:
2148 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_SOUTH);
2149 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
2150 break;
2151 default:
2152 std::cerr << "Layout::Calculator: Unhandled text orientation!" << std::endl;
2153 }
2154 } else {
2155 // Horizontal text
2156 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_AUTO);
2157 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
2158 }
2159
2160 // Minimum line box height determined by block container.
2161 FontMetrics strut_height = _flow.strut;
2162 _y_offset = 0.0;
2163 _createFirstScanlineMaker();
2164
2165 ParagraphInfo para;
2166 FontMetrics line_box_height; // Current value of line box height for line.
2167 bool keep_going = true; // Set false if we ran out of space and had to stash overflow.
2168 for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
2169
2170 // jump to the next wrap shape if this is a SHAPE_BREAK control code
2171 if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
2172 InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
2173 if (control_code->code == SHAPE_BREAK) {
2174 TRACE(("shape break control code\n"));
2175 if (!_goToNextWrapShape()) {
2176 std::cerr << "Layout::Calculator::calculate: Found SHAPE_BREAK but out of shapes!" << std::endl;
2177 }
2178 continue; // Go to next paragraph (paragraph only contained control code).
2179 }
2180 }
2181
2182 // Break things up into little pango units with unique direction, gravity, etc.
2183 _buildPangoItemizationForPara(¶);
2184
2185 // Do shaping (convert characters to glyphs)
2186 unsigned para_end_input_index = _buildSpansForPara(¶);
2187
2188 if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
2189 para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
2190 else
2191 para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
2192
2193 TRACE(("para prepared, adding as #%lu\n", _flow._paragraphs.size()));
2194 Layout::Paragraph new_paragraph;
2195 new_paragraph.base_direction = para.direction;
2196 new_paragraph.alignment = para.alignment;
2197 _flow._paragraphs.push_back(new_paragraph);
2198
2199 // start scanning lines
2200 UnbrokenSpanPosition span_pos;
2201 span_pos.iter_span = para.unbroken_spans.begin();
2202 span_pos.char_byte = 0;
2203 span_pos.char_index = 0;
2204
2205 do { // Until end of paragraph
2206 TRACE(("begin line\n"));
2207
2208 std::vector<ChunkInfo> line_chunk_info;
2209
2210 // Fill line.
2211 // If we've run out of space, we've put the remaining text in a single line and
2212 // returned false. If we ran out of space on previous paragraph, we continue with
2213 // single-line scan-line maker.
2214 bool flowed =_findChunksForLine(para, &span_pos, &line_chunk_info, &line_box_height, &strut_height );
2215 if (!flowed) {
2216 keep_going = false;
2217 }
2218
2219 if (line_box_height.emSize() < 0.001 && line_chunk_info.empty()) {
2220 // We need to avoid an infinite (or semi-infinite) loop.
2221 std::cerr << "Layout::Calculator::calculate: No room for text and line advance is very small" << std::endl;
2222 return false; // For the moment
2223 }
2224
2225
2226 // For Inkscape multi-line text (using role="line") we run into a problem if the first
2227 // line is empty - namely, there is no character to attach a 'y' attribute value. The
2228 // result is that the code that takes a baseline position (e.g. 'y') and finds the top
2229 // of the layout box is bypassed resulting in wrongly placed text (we layout the text
2230 // relative to the top of the box as this is required for text-in-a-shape). We don't
2231 // know how to find the top of the box from the 'y' position until we have found the
2232 // line height parameters for the given line (after calling _findChunksForLine() just
2233 // above).
2234 if (para.first_input_index == 0 && (_flow.wrap_mode == WRAP_NONE)) {
2235
2236 // Calculate new top of box... given specified baseline.
2237 double top_of_line_box = _scanline_maker->yCoordinate(); // Set in constructor.
2238 if( _block_progression == RIGHT_TO_LEFT ) {
2239 // Vertical text, use em box center as baseline
2240 top_of_line_box += 0.5 * line_box_height.emSize();
2241 } else if (_block_progression == LEFT_TO_RIGHT ) {
2242 // Vertical text, use em box center as baseline
2243 top_of_line_box -= 0.5 * line_box_height.emSize();
2244 } else {
2245 top_of_line_box -= line_box_height.getTypoAscent();
2246 }
2247 TRACE((" y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
2248 // Set the initial y coordinate of the for this line (see above).
2249 _scanline_maker->setNewYCoordinate(top_of_line_box);
2250 }
2251
2252 // !keep_going --> truncated --> hidden
2253 _outputLine(para, line_box_height, line_chunk_info, !keep_going);
2254
2255 _scanline_maker->setLineHeight( line_box_height );
2256 _scanline_maker->completeLine(); // Increments y by line height
2257 TRACE(("end line\n"));
2258 } while (span_pos.iter_span != para.unbroken_spans.end());
2259
2260 TRACE(("para %lu end\n\n", _flow._paragraphs.size() - 1));
2261 if (keep_going) {
2262 // We have more to do, setup next section.
2263 bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
2264 if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
2265 || para_end_input_index + 1 < _flow._input_stream.size()) {
2266 // we need a span just for the para if it's either an empty last para or a break in the middle
2267 Layout::Span new_span;
2268 if (_flow._spans.empty()) {
2269 new_span.font = nullptr;
2270 new_span.font_size = line_box_height.emSize();
2271 new_span.line_height = line_box_height;
2272 new_span.x_end = 0.0;
2273 } else {
2274 new_span = _flow._spans.back();
2275 if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
2276 new_span.x_end = 0.0;
2277 }
2278 new_span.in_chunk = _flow._chunks.size() - 1;
2279 if (new_span.font)
2280 new_span.font->Ref();
2281 new_span.x_start = new_span.x_end;
2282 new_span.baseline_shift = 0.0;
2283 new_span.direction = para.direction;
2284 new_span.block_progression = _block_progression;
2285 if (para_end_input_index == _flow._input_stream.size())
2286 new_span.in_input_stream_item = _flow._input_stream.size() - 1;
2287 else
2288 new_span.in_input_stream_item = para_end_input_index;
2289 _flow._spans.push_back(new_span);
2290 }
2291 if (para_end_input_index + 1 < _flow._input_stream.size()) {
2292 // we've got to add an invisible character between paragraphs so that we can position iterators
2293 // (and hence cursors) both before and after the paragraph break
2294 Layout::Character new_character;
2295 new_character.the_char = '@';
2296 new_character.in_span = _flow._spans.size() - 1;
2297 new_character.char_attributes.is_line_break = 1;
2298 new_character.char_attributes.is_mandatory_break = 1;
2299 new_character.char_attributes.is_char_break = 1;
2300 new_character.char_attributes.is_white = 1;
2301 new_character.char_attributes.is_cursor_position = 1;
2302 new_character.char_attributes.is_word_start = 0;
2303 new_character.char_attributes.is_word_end = 1;
2304 new_character.char_attributes.is_sentence_start = 0;
2305 new_character.char_attributes.is_sentence_end = 1;
2306 new_character.char_attributes.is_sentence_boundary = 1;
2307 new_character.char_attributes.backspace_deletes_character = 1;
2308 new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
2309 new_character.in_glyph = -1;
2310 _flow._characters.push_back(new_character);
2311 }
2312 }
2313 // dumpPangoItemsOut(¶);
2314 // dumpUnbrokenSpans(¶);
2315
2316 para.free();
2317 para.first_input_index = para_end_input_index + 1;
2318 } // Loop over paras
2319
2320 para.free();
2321 if (_scanline_maker) {
2322 delete _scanline_maker;
2323 }
2324
2325 _flow._input_truncated = !keep_going;
2326
2327 if (_flow.textLength._set) {
2328 // Calculate the adjustment needed to meet the textLength
2329 double actual_length = _flow.getActualLength();
2330 double difference = _flow.textLength.computed - actual_length;
2331 _flow.textLengthMultiplier = (actual_length + difference) / actual_length;
2332 _flow.textLengthIncrement = difference / (_flow._characters.size() == 1? 1 : _flow._characters.size() - 1);
2333 }
2334
2335 return true;
2336 }
2337
_calculateCursorShapeForEmpty()2338 void Layout::_calculateCursorShapeForEmpty()
2339 {
2340 _empty_cursor_shape.position = Geom::Point(0, 0);
2341 _empty_cursor_shape.height = 0.0;
2342 _empty_cursor_shape.rotation = 0.0;
2343 if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
2344 return;
2345
2346 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
2347
2348 font_instance *font = text_source->styleGetFontInstance();
2349 double font_size = text_source->style->font_size.computed;
2350 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
2351 FontMetrics line_height;
2352 if (font) {
2353 const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
2354 font->FontMetrics(line_height.ascent, line_height.descent, line_height.xheight);
2355 line_height *= font_size;
2356 font->Unref();
2357 }
2358
2359 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
2360 _empty_cursor_shape.height = font_size / cos(caret_slope);
2361 _empty_cursor_shape.rotation = caret_slope;
2362
2363 if (_input_wrap_shapes.empty()) {
2364 _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
2365 text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
2366 } else if (wrap_mode == WRAP_INLINE_SIZE) {
2367 // 'inline-size' has a wrap shape of an "infinite" rectangle, we need the place where the text should begin.
2368 double x = 0;
2369 double y = 0;
2370 if (!text_source->x.empty())
2371 x = text_source->x.front().computed;
2372 if (!text_source->y.empty())
2373 y = text_source->y.front().computed;
2374 _empty_cursor_shape.position = Geom::Point(x, y);
2375 } else {
2376 Direction block_progression = text_source->styleGetBlockProgression();
2377 ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
2378 std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
2379 if (!scan_runs.empty()) {
2380 if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
2381 // Vertical text
2382 _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
2383 } else {
2384 // Horizontal text
2385 _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
2386 }
2387 }
2388 }
2389 }
2390
calculateFlow()2391 bool Layout::calculateFlow()
2392 {
2393 TRACE(("begin calculateFlow()\n"));
2394 Layout::Calculator calc = Calculator(this);
2395 bool result = calc.calculate();
2396
2397 if (textLengthIncrement != 0) {
2398 TRACE(("Recalculating layout the second time to fit textLength!\n"));
2399 result = calc.calculate();
2400 }
2401
2402 if (_characters.empty()) {
2403 _calculateCursorShapeForEmpty();
2404 }
2405 return result;
2406 }
2407
2408 }//namespace Text
2409 }//namespace Inkscape
2410
2411
2412 /*
2413 Local Variables:
2414 mode:c++
2415 c-file-style:"stroustrup"
2416 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2417 indent-tabs-mode:nil
2418 fill-column:99
2419 End:
2420 */
2421 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
2422