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