35#define TRACE(_args) IFTRACE(g_print _args)
62class Layout::Calculator
65 friend class SpanPosition;
67 ScanlineMaker *_scanline_maker;
68 unsigned _current_shape_index;
69 PangoContext *_pango_context;
87 double _font_factory_size_multiplier;
90 struct InputItemInfo {
94 InputItemInfo() : in_sub_flow(false), sub_flow(nullptr) {}
113 struct PangoItemInfo {
115 std::shared_ptr<FontInstance> font;
117 PangoItemInfo() :
item(nullptr) {}
128 pango_item_free(item);
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;
149 FontMetrics line_height;
150 double line_height_multiplier;
151 double baseline_shift;
154 unsigned char_index_in_para;
157 UnbrokenSpan() : glyph_string(nullptr) {}
161 pango_glyph_string_free(glyph_string);
162 glyph_string =
nullptr;
171 struct ParagraphInfo {
173 unsigned first_input_index;
176 std::vector<InputItemInfo> input_items;
177 std::vector<PangoItemInfo> pango_items;
178 std::vector<PangoLogAttr> char_attributes;
179 std::vector<UnbrokenSpan> unbroken_spans;
181 template<
typename T>
static void free_sequence(T &seq)
183 for (
typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
192 free_sequence(input_items);
193 free_sequence(pango_items);
194 free_sequence(unbroken_spans);
202 struct UnbrokenSpanPosition {
203 std::vector<UnbrokenSpan>::iterator iter_span;
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;}
221 UnbrokenSpanPosition
start;
222 UnbrokenSpanPosition
end;
223 unsigned start_glyph_index;
224 unsigned end_glyph_index;
226 unsigned whitespace_count;
227 bool ends_with_whitespace;
228 double each_whitespace_width;
229 double letter_spacing;
238 std::vector<BrokenSpan> broken_spans;
239 double scanrun_width;
242 int whitespace_count;
245 void _buildPangoItemizationForPara(ParagraphInfo *para)
const;
246 static double _computeFontLineHeight(
SPStyle const *style );
247 unsigned _buildSpansForPara(ParagraphInfo *para)
const;
248 bool _goToNextWrapShape();
249 void _createFirstScanlineMaker();
251 bool _findChunksForLine(ParagraphInfo
const ¶,
252 UnbrokenSpanPosition *start_span_pos,
253 std::vector<ChunkInfo> *chunk_info,
254 FontMetrics *line_box_height,
255 FontMetrics
const *strut_height);
257 bool _buildChunksInScanRun(ParagraphInfo
const ¶,
258 UnbrokenSpanPosition
const &start_span_pos,
259 ScanlineMaker::ScanRun
const &scan_run,
260 std::vector<ChunkInfo> *chunk_info,
261 FontMetrics *line_height)
const;
263 bool _measureUnbrokenSpan(ParagraphInfo
const ¶,
265 BrokenSpan *last_break_span,
266 BrokenSpan *last_emergency_break_span,
267 double maximum_width)
const;
269 double _getChunkLeftWithAlignment(ParagraphInfo
const ¶,
270 std::vector<ChunkInfo>::const_iterator it_chunk,
271 double *add_to_each_whitespace)
const;
273 void _outputLine(ParagraphInfo
const ¶,
274 FontMetrics
const &line_height,
275 std::vector<ChunkInfo>
const &chunk_info,
278 static inline PangoLogAttr
const &_charAttributes(ParagraphInfo
const ¶,
279 UnbrokenSpanPosition
const &span_pos)
281 return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
284#ifdef DEBUG_LAYOUT_TNG_COMPUTE
285 static void dumpPangoItemsOut(ParagraphInfo *para);
286 static void dumpUnbrokenSpans(ParagraphInfo *para);
290 Calculator(
Layout *text_flow)
291 : _flow(*text_flow) {}
312bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo
const ¶,
314 BrokenSpan *last_break_span,
315 BrokenSpan *last_emergency_break_span,
316 double maximum_width)
const
318 TRACE((
" start _measureUnbrokenSpan %g\n", maximum_width));
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;
325 span->width += span->start.iter_span->dx.computed;
329 if (span->start.iter_span->pango_item_index == -1) {
332 span->end.iter_span++;
336 if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
338 InputStreamControlCode
const *control_code =
static_cast<InputStreamControlCode
const *
>(_flow._input_stream[span->start.iter_span->input_index]);
340 if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
341 *last_emergency_break_span = *last_break_span = *span;
345 if (control_code->code == ARBITRARY_GAP) {
346 if (span->width + control_code->width > maximum_width)
348 TRACE((
" fitted control code, width = %f\n", control_code->width));
349 span->width += control_code->width;
350 span->end.increment();
355 if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
358 InputStreamTextSource
const *text_source =
static_cast<InputStreamTextSource
const *
>(_flow._input_stream[span->start.iter_span->input_index]);
360 if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
363 span->end.iter_span++;
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);
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;
383 PangoLogAttr
const &char_attributes = _charAttributes(para, span->end);
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));
401 if (char_attributes.is_line_break) {
402 TRACE((
" is_line_break ************\n"));
404 *last_emergency_break_span = *last_break_span = *span;
405 if (soft_hyphen_in_word) {
407 span->width -= soft_hyphen_glyph_width;
409 soft_hyphen_in_word =
false;
411 }
else if (char_attributes.is_char_break) {
412 *last_emergency_break_span = *span;
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) {
421 PangoGlyphInfo *info = &(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index]);
422 double glyph_width = font_size_multiplier * info->geometry.width;
426 auto font = para.pango_items[span->end.iter_span->pango_item_index].font;
427 double font_size = span->start.iter_span->font_size;
429 double glyph_v_advance = font_size * font->Advance(info->glyph,
true );
431 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
436 para.pango_items[span->end.iter_span->pango_item_index].item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
438 char_width += glyph_width;
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) {
444 if (pango_version_check(1,44,0) !=
nullptr) {
446 char_width += glyph_width;
449 char_width += glyph_v_advance;
455 char_width += glyph_width;
457 span->end_glyph_index++;
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++);
468 if (char_attributes.is_white) {
469 span->whitespace_count++;
470 span->each_whitespace_width = char_width;
472 span->ends_with_whitespace = char_attributes.is_white;
474 is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
476 soft_hyphen_glyph_width = char_width;
479 span->end.increment();
483 double test_width = span->width - text_source->style->letter_spacing.computed;
486 span->letter_spacing = text_source->style->letter_spacing.computed;
487 span->word_spacing = text_source->style->word_spacing.computed;
489 if (test_width > maximum_width && !char_attributes.is_white) {
490 TRACE((
" span %ld exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
494 }
while (span->end.char_byte != 0);
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));
509double Layout::Calculator::_getChunkLeftWithAlignment(ParagraphInfo
const ¶,
510 std::vector<ChunkInfo>::const_iterator it_chunk,
511 double *add_to_each_whitespace)
const
513 *add_to_each_whitespace = 0.0;
514 if (_flow._input_wrap_shapes.empty()) {
515 switch (para.alignment) {
521 return it_chunk->x - it_chunk->text_width;
523 return it_chunk->x - it_chunk->text_width/ 2;
527 switch (para.alignment) {
530 if (!it_chunk->broken_spans.empty() && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) {
533 PangoLogAttr
const &char_attributes = _charAttributes(para, it_chunk->broken_spans.back().end);
534 if (it_chunk->whitespace_count && !char_attributes.is_mandatory_break) {
538 *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
546 return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
548 return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
557void Layout::Calculator::_outputLine(ParagraphInfo
const ¶,
558 FontMetrics
const &line_height,
559 std::vector<ChunkInfo>
const &chunk_info,
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"));
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;
578 if( _block_progression == RIGHT_TO_LEFT ) {
580 new_line.baseline_y -= 0.5 * line_height.emSize();
581 }
else if ( _block_progression == LEFT_TO_RIGHT ) {
583 new_line.baseline_y += 0.5 * line_height.emSize();
585 new_line.baseline_y += line_height.getTypoAscent();
589 TRACE((
" initial new_line.baseline_y: %f\n", new_line.baseline_y ));
591 new_line.in_shape = _current_shape_index;
592 _flow._lines.push_back(new_line);
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;
597 Layout::Chunk new_chunk;
598 new_chunk.in_line = _flow._lines.size() - 1;
600 TRACE((
" New chunk: in_line: %d\n", new_chunk.in_line));
602 new_chunk.left_x = it_chunk->x;
604 new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
623 if (!it_chunk->broken_spans.empty()
624 && it_chunk->broken_spans.front().start.char_byte == 0 ) {
627 if( _flow._characters.empty() ||
628 _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
636 if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
639 new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
641 TRACE((
" chunk new_line.baseline_y: %f\n", new_line.baseline_y ));
644 _flow._lines.back().baseline_y = new_line.baseline_y;
647 double top_of_line_box = new_line.baseline_y;
648 if( _block_progression == RIGHT_TO_LEFT ) {
650 top_of_line_box += 0.5 * line_height.emSize();
651 }
else if (_block_progression == LEFT_TO_RIGHT ) {
653 top_of_line_box -= 0.5 * line_height.emSize();
655 top_of_line_box -= line_height.getTypoAscent();
657 TRACE((
" y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
659 _scanline_maker->setNewYCoordinate(top_of_line_box);
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;
676 _flow._chunks.push_back(new_chunk);
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;
687 direction_sign = -1.0;
688 if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){
689 current_x = it_chunk->scanrun_width;
692 current_x = it_chunk->text_width;
697 for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
700 UnbrokenSpan
const &unbroken_span = *it_span->start.iter_span;
701 double x_in_span_last = 0.0;
702 double x_in_span = 0.0;
708 if (it_span->start.char_byte == 0) {
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);
716 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
717 && unbroken_span.pango_item_index == -1) {
722 Layout::Span new_span;
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);
735 new_span.font =
nullptr;
736 new_span.font_size = new_span.line_height.emSize();
737 new_span.direction = para.direction;
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) {
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();
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;
753 counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
755 current_x += counter_directional_width_remaining;
756 counter_directional_width_remaining = 0.0;
758 new_span.x_start = current_x;
759 new_span.y_offset = _y_offset;
761 if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
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;
769 int log_cluster_size_chars = 0;
770 unsigned end_byte = 0;
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;
777 double x_offset_cluster = 0.0;
778 double x_offset_center = 0.0;
779 double x_offset_advance = 0.0;
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;
789 for (
unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
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) {
795 x_in_span = x_in_span_last;
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) {
803 Layout::Character new_character;
804 new_character.the_char = *iter_source_text;
805 new_character.in_span = _flow._spans.size();
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);
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)
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;
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;
827 new_glyph.hidden = hidden;
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 );
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;
849 new_glyph.vertical_scale = 1.0 / _flow.getTextLengthMultiplierDue();
851 new_glyph.vertical_scale = 1.0;
854 new_glyph.x = current_x;
855 new_glyph.y =_y_offset;
856 new_glyph.advance = glyph_width;
858 if (*iter_source_text ==
'\n') {
860 new_glyph.advance = 0.0;
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;
869 if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
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
894 new_glyph.orientation = ORIENTATION_SIDEWAYS;
896 new_glyph.x += delta_x;
897 new_glyph.y -= delta_y;
902 new_glyph.y -= new_span.font_size * font->GetBaselines()[ dominant_baseline ];
907 auto hb_font = pango_font_get_hb_font(font->get_font());
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;
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;
921 if (pango_version_check(1,44,0) !=
nullptr) {
923 new_glyph.x += delta_x;
924 new_glyph.y -= delta_y;
927 double scale_factor = PANGO_SCALE * _font_factory_size_multiplier;
928 if (!font->has_vertical()) {
933 PangoRectangle ink_rect;
934 PangoRectangle logical_rect;
935 pango_font_get_glyph_extents (font->get_font(),
943 font->GetTypoAscent() +
944 ink_rect.y / scale_factor +
945 (ink_rect.height / scale_factor / 2.0) -
950 if (g_unichar_type(*iter_source_text) != G_UNICODE_NON_SPACING_MARK) {
952 x_offset_advance = new_glyph.advance - glyph_v_advance;
953 new_glyph.advance = glyph_v_advance;
955 x_offset_center =
shift;
958 if (!font->has_vertical()) {
962 new_glyph.advance = 0;
965 new_glyph.x += (x_offset_center -
shift) * new_span.font_size;
969 new_glyph.x += x_offset_advance;
974 new_glyph.x += font->GetTypoAscent() * new_span.font_size;
975 new_glyph.y -= glyph_h_advance/2.0;
977 }
else if (pango_version_check(1,48,1) !=
nullptr) {
980 new_glyph.x += (glyph_width - delta_x);
981 new_glyph.y -= delta_y;
982 }
else if (pango_version_check(1,48,4) !=
nullptr) {
985 new_glyph.x += delta_x;
986 new_glyph.y -= delta_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;
997 new_glyph.x += delta_x;
998 new_glyph.y -= delta_y;
1006 hb_font_extents_t hb_font_extents_not_used;
1007 if (!hb_font_get_v_extents(hb_font, &hb_font_extents_not_used)) {
1010 if (g_unichar_type(*iter_source_text) !=
1011 G_UNICODE_NON_SPACING_MARK) {
1012 hb_glyph_extents_t glyph_extents;
1013 if (hb_font_get_glyph_extents(hb_font, new_glyph.glyph, &glyph_extents)) {
1019 double baseline_adjust = new_span.line_height.ascent / new_span.font_size;
1022 hb_font_get_scale(hb_font, &hb_x_scale, &hb_y_scale);
1024 ((glyph_extents.y_bearing / (double)hb_y_scale) - baseline_adjust) *
1027 x_offset_cluster = 0.0;
1034 new_glyph.advance = 0;
1037 new_glyph.x -= x_offset_cluster;
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
1055 new_glyph.x += delta_x;
1056 new_glyph.y += delta_y;
1058 new_glyph.y += new_span.font_size * font->GetBaselines()[ dominant_baseline ];
1062 if (new_span.direction == RIGHT_TO_LEFT) {
1082 new_glyph.x -= font_size_multiplier * unbroken_span.glyph_string->glyphs[glyph_index].geometry.width;
1086 _flow._glyphs.push_back(new_glyph);
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;
1100 end_byte = it_span->start.iter_span->text_bytes;
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];
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++;
1115 lcb = lclist.base() - unbroken_span.input_stream_first_character.base();
1119 double advance_width = new_glyph.advance;
1120 while (char_byte < end_byte) {
1125 if (log_cluster_size_chars < log_cluster_size_glyphs) {
1126 log_cluster_size_glyphs--;
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);
1140 if (new_character.char_attributes.is_white)
1141 advance_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue() + add_to_each_whitespace;
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();
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--;
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;
1160 current_x += advance_width;
1161 x_in_span_last += advance_width;
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;
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;
1175 TRACE((
" End _outputLine\n"));
1182void Layout::Calculator::_createFirstScanlineMaker()
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()) {
1188 double initial_x = 0, initial_y = 0;
1189 if (!text_source->x.empty()) {
1190 initial_x = text_source->x.front().computed;
1192 if (!text_source->y.empty()) {
1193 initial_y = text_source->y.front().computed;
1195 _scanline_maker =
new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
1196 TRACE((
" wrapping disabled\n"));
1200 new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape.get(), _block_progression);
1201 TRACE((
" begin wrap shape 0\n"));
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 ) {
1209 if (!text_source->x.empty()) {
1210 double initial_x = text_source->x.front().computed;
1211 _scanline_maker->setNewYCoordinate(initial_x);
1213 std::cerr <<
"Layout::Calculator::_createFirstScanlineMaker: no x value with 'inline-size'!" << std::endl;
1214 _scanline_maker->setNewYCoordinate(0);
1218 if (!text_source->y.empty()) {
1219 double initial_y = text_source->y.front().computed;
1220 _scanline_maker->setNewYCoordinate(initial_y);
1222 std::cerr <<
"Layout::Calculator::_createFirstScanlineMaker: no y value with 'inline-size'!" << std::endl;
1223 _scanline_maker->setNewYCoordinate(0);
1230void Layout::Calculator::UnbrokenSpanPosition::increment()
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;
1235 if (char_byte == iter_span->text_bytes) {
1237 char_index = char_byte = 0;
1241void Layout::Calculator::BrokenSpan::setZero()
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;
1328void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para)
const
1330 TRACE((
"pango version string: %s\n", pango_version_string() ));
1331 TRACE((
" ... compiled for font features\n"));
1333 TRACE((
"itemizing para, first input %d\n", para->first_input_index));
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)
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]);
1348 auto font = text_source->styleGetFontInstance();
1353 PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->get_descr());
1354 attribute_font_description->start_index = para->text.bytes();
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);
1361 attribute_font_description->end_index = para->text.bytes();
1362 pango_attr_list_insert(attributes_list, attribute_font_description);
1364 attribute_font_features->end_index = para->text.bytes();
1365 pango_attr_list_insert(attributes_list, attribute_font_features);
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);
1377 TRACE((
"whole para: \"%s\"\n", para->text.data()));
1381 GList *pango_items_glist =
nullptr;
1382 para->direction = LEFT_TO_RIGHT;
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]);
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);
1391 if( pango_items_glist ==
nullptr ) {
1393 pango_items_glist = pango_itemize(_pango_context, para->text.data(), 0, para->text.bytes(), attributes_list,
nullptr);
1396 pango_attr_list_unref(attributes_list);
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;
1406 pango_font_description_free(font_description);
1407 para->pango_items.push_back(new_item);
1409 g_list_free(pango_items_glist);
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());
1417 para->char_attributes[para->text.length()].is_mandatory_break = 0;
1419 TRACE((
"end para itemize, direction = %d\n", para->direction));
1430double Layout::Calculator::_computeFontLineHeight(
SPStyle const *style )
1435 return (LINE_HEIGHT_NORMAL);
1447 bool retval =
false;
1448 if ( b.geometry.width == 0 && (a.geometry.width > 0))retval =
true;
1460unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para)
const
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;
1468 TRACE((
"build spans\n"));
1469 para->free_sequence(para->unbroken_spans);
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]);
1475 if ( control_code->code == SHAPE_BREAK
1476 || control_code->code == PARAGRAPH_BREAK) {
1479 UnbrokenSpan new_span;
1480 new_span.pango_item_index = -1;
1481 new_span.input_index = input_index;
1484 SPObject *
object = control_code->source;
1486 SPStyle * style =
object->style;
1490 if (is<SPFlowpara>(
object)) {
1491 style =
object->parent->style;
1494 new_span.font_size = style->
font_size.computed * _flow.getTextLengthMultiplierDue();
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;
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));
1507 }
else if (control_code->code == ARBITRARY_GAP) {
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));
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;
1529 unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
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;
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) {
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();
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();
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()) {
1570 Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
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();
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();
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))
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);
1614 pango_shape_full(para->text.data() + para_text_index,
1615 new_span.text_bytes,
1618 ¶->pango_items[pango_item_index].item->analysis,
1619 new_span.glyph_string);
1621 if (para->pango_items[pango_item_index].item->analysis.level & 1) {
1629 const unsigned nglyphs = new_span.glyph_string->num_glyphs;
1630 std::vector<PangoGlyphInfo> infos(nglyphs);
1631 std::vector<gint> clusters(nglyphs);
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);
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);
1642 for (
int i = 0; i < nglyphs; ++i) {
1645 new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
1649 while( (j < nglyphs) &&
1650 (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
1652 new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0;
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;
1728 new_span.baseline_shift = text_source->style->baseline_shift.computed;
1729 new_span.text_orientation = (
SPCSSTextOrientation)text_source->style->text_orientation.computed;
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));
1736 new_span.pango_item_index = -1;
1737 auto font = text_source->styleGetFontInstance();
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;
1743 new_span.line_height *= 0.0;
1744 new_span.line_height_multiplier = LINE_HEIGHT_NORMAL;
1746 TRACE((
"add style init span %lu\n", para->unbroken_spans.size()));
1748 para->unbroken_spans.push_back(new_span);
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);
1755 if (new_span.text_bytes >= pango_item_bytes) {
1757 if (pango_item_index == para->pango_items.size())
break;
1759 if (new_span.text_bytes == text_source_bytes)
1762 span_start_byte_in_source += new_span.text_bytes;
1764 char_index_in_para += char_index_in_source;
1767 TRACE((
"end build spans\n"));
1776bool Layout::Calculator::_goToNextWrapShape()
1778 if (_flow._input_wrap_shapes.size() == 0) {
1780 std::cerr <<
"Layout::Calculator::_goToNextWrapShape() called for text without shapes!" << std::endl;
1784 if (_current_shape_index >= _flow._input_wrap_shapes.size()) {
1786 std::cerr <<
"Layout::Calculator::_goToNextWrapShape(): shape index too large!" << std::endl;
1789 _current_shape_index++;
1791 delete _scanline_maker;
1792 _scanline_maker =
nullptr;
1794 if (_current_shape_index < _flow._input_wrap_shapes.size()) {
1796 new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape.get(), _block_progression);
1797 TRACE((
"begin wrap shape %u\n", _current_shape_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;
1806 _scanline_maker =
new InfiniteScanlineMaker(x, y, _block_progression);
1807 TRACE((
"out of wrap shapes, stash leftover\n"));
1829bool Layout::Calculator::_findChunksForLine(ParagraphInfo
const ¶,
1830 UnbrokenSpanPosition *start_span_pos,
1831 std::vector<ChunkInfo> *chunk_info,
1832 FontMetrics *line_box_height,
1833 FontMetrics
const *strut_height)
1835 TRACE((
" begin _findChunksForLine: chunks: %lu, em size: %f\n", chunk_info->size(), line_box_height->emSize() ));
1839 *line_box_height = *strut_height;
1840 TRACE((
" initial line_box_height (em size): %f\n", line_box_height->emSize() ));
1842 bool truncated =
false;
1844 UnbrokenSpanPosition span_pos;
1848 std::vector<ScanlineMaker::ScanRun> scan_runs;
1849 scan_runs = _scanline_maker->makeScanline(*line_box_height);
1852 while (scan_runs.empty()) {
1854 *line_box_height = *strut_height;
1857 if (!_goToNextWrapShape()) {
1862 scan_runs = _scanline_maker->makeScanline(*line_box_height);
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++) {
1875 if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_box_height)) {
1879 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty()) {
1880 span_pos = chunk_info->back().broken_spans.back().end;
1884 if (scan_run_index == scan_runs.size())
break;
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"));
1908bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo
const ¶,
1909 UnbrokenSpanPosition
const &start_span_pos,
1910 ScanlineMaker::ScanRun
const &scan_run,
1911 std::vector<ChunkInfo> *chunk_info,
1912 FontMetrics *line_height)
const
1914 TRACE((
" begin _buildChunksInScanRun: chunks: %lu, em size: %f\n", chunk_info->size(), line_height->emSize() ));
1916 FontMetrics line_height_saved = *line_height;
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;
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();
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()) {
1935 new_span.start = new_span.end;
1938 if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
1940 if (new_span.start.iter_span != start_span_pos.iter_span)
1941 chunk_info->push_back(new_chunk);
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;
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 );
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() ) {
1960 line_height->max(new_span_height);
1963 if (!_scanline_maker->canExtendCurrentScanline(*line_height)) {
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);
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);
1974 if (!span_fitted)
break;
1976 if (new_span.end.iter_span == para.unbroken_spans.end()) {
1977 last_span_at_break = new_span;
1981 PangoLogAttr
const &char_attributes = _charAttributes(para, new_span.end);
1982 if (char_attributes.is_mandatory_break) {
1983 last_span_at_break = new_span;
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);
1991 if (scan_run.width() >= 4.0 * line_height->emSize() && last_span_at_break.end == start_span_pos) {
2002 last_span_at_break = last_span_at_emergency_break;
2005 if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
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();
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();
2019 if (chunk_info->back().broken_spans.empty())
2020 chunk_info->pop_back();
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;
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));
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 );
2040 TRACE((
" line_box_height: %f\n", line_height->emSize()));
2042 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
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--;
2052 if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
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));
2059 TRACE((
" end _buildChunksInScanRun: chunks: %lu\n", chunk_info->size()));
2063#ifdef DEBUG_LAYOUT_TNG_COMPUTE
2069void Layout::Calculator::dumpPangoItemsOut(ParagraphInfo *para){
2070 std::cerr <<
"Pango items: " << para->pango_items.size() << std::endl;
2072 for(
unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){
2076 << para->pango_items[pidx].item->offset
2078 << para->pango_items[pidx].item->length
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++){
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
2105bool Layout::Calculator::calculate()
2107 if (_flow._input_stream.empty())
2114 if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
2116 g_warning(
"flow text is not of type TEXT_SOURCE. Abort.");
2119 TRACE((
"begin calculate()\n"));
2121 _flow._clearOutputObjects();
2127 _block_progression = _flow._blockProgression();
2128 if( _block_progression == RIGHT_TO_LEFT || _block_progression == LEFT_TO_RIGHT ) {
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);
2136 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
2137 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
2140 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_SOUTH);
2141 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
2144 std::cerr <<
"Layout::Calculator: Unhandled text orientation!" << std::endl;
2148 pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_AUTO);
2149 pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
2153 FontMetrics strut_height = _flow.strut;
2155 _createFirstScanlineMaker();
2158 FontMetrics line_box_height;
2159 bool keep_going =
true;
2160 for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
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;
2175 _buildPangoItemizationForPara(¶);
2178 unsigned para_end_input_index = _buildSpansForPara(¶);
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());
2183 para.alignment = para.direction == LEFT_TO_RIGHT ?
LEFT :
RIGHT;
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);
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;
2198 TRACE((
"begin line\n"));
2200 std::vector<ChunkInfo> line_chunk_info;
2206 bool flowed =_findChunksForLine(para, &span_pos, &line_chunk_info, &line_box_height, &strut_height );
2211 if (line_box_height.emSize() < 0.001 && line_chunk_info.empty()) {
2213 std::cerr <<
"Layout::Calculator::calculate: No room for text and line advance is very small" << std::endl;
2226 if (para.first_input_index == 0 && (_flow.wrap_mode == WRAP_NONE)) {
2229 double top_of_line_box = _scanline_maker->yCoordinate();
2230 if( _block_progression == RIGHT_TO_LEFT ) {
2232 top_of_line_box += 0.5 * line_box_height.emSize();
2233 }
else if (_block_progression == LEFT_TO_RIGHT ) {
2235 top_of_line_box -= 0.5 * line_box_height.emSize();
2237 top_of_line_box -= line_box_height.getTypoAscent();
2239 TRACE((
" y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
2241 _scanline_maker->setNewYCoordinate(top_of_line_box);
2245 _outputLine(para, line_box_height, line_chunk_info, !keep_going);
2247 _scanline_maker->setLineHeight( line_box_height );
2248 _scanline_maker->completeLine();
2249 TRACE((
"end line\n"));
2250 }
while (span_pos.iter_span != para.unbroken_spans.end());
2252 TRACE((
"para %lu end\n\n", _flow._paragraphs.size() - 1));
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()) {
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;
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;
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;
2278 new_span.in_input_stream_item = para_end_input_index;
2279 _flow._spans.push_back(new_span);
2281 if (para_end_input_index + 1 < _flow._input_stream.size()) {
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);
2307 para.first_input_index = para_end_input_index + 1;
2311 if (_scanline_maker) {
2312 delete _scanline_maker;
2315 _flow._input_truncated = !keep_going;
2317 if (_flow.textLength._set) {
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);
2339 double font_size = text_source->style->font_size.computed;
2340 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
2343 font->FontSlope(caret_slope_run, caret_slope_rise);
2345 line_height *= font_size;
2348 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
2354 text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
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;
2365 Direction block_progression = text_source->styleGetBlockProgression();
2367 std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.
makeScanline(line_height);
2368 if (!scan_runs.empty()) {
2382 TRACE((
"begin calculateFlow()\n"));
2384 bool result = calc.calculate();
2387 TRACE((
"Recalculating layout the second time to fit textLength!\n"));
2388 result = calc.calculate();
_PangoFontDescription PangoFontDescription
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.
Keep track of font metrics.
Represents a text item in the input stream.
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.
struct Inkscape::Text::Layout::CursorShape _empty_cursor_shape
double textLengthIncrement
This one is used by letterspacing strategy: to each glyph width, this is added.
void _calculateBaselines()
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
std::vector< InputStreamItem * > _input_stream
This is our internal storage for all the stuff passed to the appendText() and appendControlCode() fun...
std::vector< Character > _characters
std::vector< InputWrapShape > _input_wrap_shapes
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.
@ LENGTHADJUST_SPACINGANDGLYPHS
static FontFactory & get(Args &&... args)
SPObject is an abstract base class of all of the document nodes at the SVG document level.
T< SPAttr::LINE_HEIGHT, SPILengthOrNormal > line_height
Line height (css2 10.8.1)
T< SPAttr::FONT_SIZE, SPIFontSize > font_size
Size of the font.
TODO: insert short description here.
The data describing a single loaded font.
void shift(T &a, T &b, T const &c)
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.
@ SP_CSS_BASELINE_CENTRAL
@ SP_CSS_BASELINE_ALPHABETIC
@ SP_CSS_TEXT_ORIENTATION_SIDEWAYS
@ SP_CSS_TEXT_ORIENTATION_UPRIGHT
@ SP_CSS_TEXT_ORIENTATION_MIXED
SPStyle - a style object for SPItem objects.