Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
Layout-TNG-Output.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Inkscape::Text::Layout - text layout engine output functions
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 <glib.h>
14#include "Layout-TNG.h"
15#include "style-attachments.h"
17#include "style.h"
18#include "print.h"
19#include "extension/print.h"
20#include "livarot/Path.h"
21#include "font-instance.h"
22#include "svg/svg-length.h"
24#include "display/curve.h"
25#include <2geom/pathvector.h>
28
29
32
33namespace Inkscape {
34namespace Text {
35
36/*
37 dx array (character widths) and
38 ky (vertical kerning for entire span)
39 rtl (+1 for LTR, -1 RTL)
40
41 are smuggled through to the EMF (ignored by others) as:
42 text<nul>N w1 w2 w3 ...wN<nul>y1 y2 y3 .. yN<nul><nul>
43 The ndx, widths, y kern, and rtl are all 7 characters wide. ndx and rtl are ints, the widths and ky are
44 formatted as ' 6f'.
45*/
46char *smuggle_adxkyrtl_in(const char *string, int ndx, float *adx, float ky, float rtl){
47 int slen = strlen(string);
48 /* holds: string
49 fake terminator (one \0)
50 Number of widths (ndx)
51 series of widths (ndx entries)
52 fake terminator (one \0)
53 y kern value (one float)
54 rtl value (one float)
55 real terminator (two \0)
56 */
57 int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 7 + 2;
58 newsize = 8*((7 + newsize)/8); // suppress valgrind messages if it is a multiple of 8 bytes???
59 char *smuggle=(char *)malloc(newsize);
60 strcpy(smuggle,string); // text to pass, includes the first fake terminator
61 char *cptr = smuggle + slen + 1; // immediately after the first fake terminator
62 sprintf(cptr,"%07d",ndx); // number of widths to pass
63 cptr+=7; // advance over ndx
64 for(int i=0; i<ndx ; i++){ // all the widths
65 sprintf(cptr," %6f",adx[i]);
66 cptr+=7; // advance over space + width
67 }
68 *cptr='\0';
69 cptr++; // second fake terminator
70 sprintf(cptr," %6f",ky); // y kern for span
71 cptr+=7; // advance over space + ky
72 sprintf(cptr," %6d",(int) rtl); // rtl multiplier for span
73 cptr+=7; // advance over rtl
74 *cptr++ = '\0'; // Set the real terminators
75 *cptr = '\0';
76 return(smuggle);
77}
78
80{
81 _paragraphs.clear();
82 _lines.clear();
83 _chunks.clear();
84 _spans.clear();
85 _characters.clear();
86 _glyphs.clear();
87 _path_fitted = nullptr;
88}
89
91{
92 if (font) {
93 ascent = font->GetTypoAscent();
94 descent = font->GetTypoDescent();
95 xheight = font->GetXHeight();
96 ascent_max = font->GetMaxAscent();
97 descent_max = font->GetMaxDescent();
98 }
99}
100
102{
103 if (other.ascent > ascent ) ascent = other.ascent;
104 if (other.descent > descent ) descent = other.descent;
105 if( other.xheight > xheight ) xheight = other.xheight;
106 if( other.ascent_max > ascent_max ) ascent_max = other.ascent_max;
107 if( other.descent_max > descent_max ) descent_max = other.descent_max;
108}
109
110void Layout::FontMetrics::computeEffective( const double &line_height_multiplier ) {
111 double half_leading = 0.5 * (line_height_multiplier - 1.0) * emSize();
112 ascent += half_leading;
113 descent += half_leading;
114}
115
116// TODO: Refactor this so it can work without passing layout around
118{
119 Geom::Affine matrix;
120
121 auto const &glyph_span = span(&layout);
122 double r = rotation;
123 if ( (glyph_span.block_progression == LEFT_TO_RIGHT || glyph_span.block_progression == RIGHT_TO_LEFT) &&
124 orientation == ORIENTATION_SIDEWAYS ) {
125 // Vertical sideways text
126 r += M_PI/2.0;
127 }
128 double sin_rotation = sin(r);
129 double cos_rotation = cos(r);
130 matrix[0] = glyph_span.font_size * cos_rotation;
131 matrix[1] = glyph_span.font_size * sin_rotation;
132 matrix[2] = glyph_span.font_size * sin_rotation;
133 matrix[3] = -glyph_span.font_size * cos_rotation * vertical_scale; // unscale vertically so the specified text height is preserved if lengthAdjust=spacingAndGlyphs
134 if (glyph_span.block_progression == LEFT_TO_RIGHT || glyph_span.block_progression == RIGHT_TO_LEFT) {
135 // Vertical text
136 // This effectively swaps x for y which changes handedness of coordinate system. This is a bit strange
137 // and not what one would expect but the compute code already reverses y so OK.
138 matrix[4] = line(&layout).baseline_y + y;
139 matrix[5] = chunk(&layout).left_x + x;
140 } else {
141 // Horizontal text
142 matrix[4] = chunk(&layout).left_x + x;
143 matrix[5] = line(&layout).baseline_y + y;
144 }
145 return matrix;
146}
147
148void Layout::show(DrawingGroup *parent, StyleAttachments &style_attachments, Geom::OptRect const &paintbox) const
149{
150 int glyph_index = 0;
151 double phase0 = 0.0;
152
153 for (int i = 0; i < _spans.size(); i++) {
154 if (_input_stream[_spans[i].in_input_stream_item]->Type() != TEXT_SOURCE) {
155 continue;
156 }
157
158 if (_spans[i].line(this).hidden) {
159 continue; // Line corresponds to text overflow. Don't show!
160 }
161
162 auto text_source = static_cast<InputStreamTextSource const *>(_input_stream[_spans[i].in_input_stream_item]);
163 auto style = text_source->style;
164
165 style->text_decoration_data.tspan_width = _spans[i].width();
166 style->text_decoration_data.ascender = _spans[i].line_height.getTypoAscent();
167 style->text_decoration_data.descender = _spans[i].line_height.getTypoDescent();
168
169 auto line_of_span = [this] (int i) { return _chunks[_spans[i].in_chunk].in_line; };
170 style->text_decoration_data.tspan_line_start = i == 0 || line_of_span(i) != line_of_span(i - 1);
171 style->text_decoration_data.tspan_line_end = i == _spans.size() - 1 || line_of_span(i) != line_of_span(i + 1);
172
173 if (_spans[i].font) {
174 double underline_thickness, underline_position, line_through_thickness, line_through_position;
175 _spans[i].font->FontDecoration(underline_position, underline_thickness, line_through_position, line_through_thickness);
176 style->text_decoration_data.underline_thickness = underline_thickness;
177 style->text_decoration_data.underline_position = underline_position;
178 style->text_decoration_data.line_through_thickness = line_through_thickness;
179 style->text_decoration_data.line_through_position = line_through_position;
180 } else { // can this case ever occur?
181 style->text_decoration_data.underline_thickness = 0.0;
182 style->text_decoration_data.underline_position = 0.0;
183 style->text_decoration_data.line_through_thickness = 0.0;
184 style->text_decoration_data.line_through_position = 0.0;
185 }
186
187 auto drawing_text = new DrawingText(parent->drawing());
188
189 if (style->filter.set) {
190 if (auto filter = style->getFilter()) {
191 style_attachments.attachFilter(drawing_text, filter);
192 }
193 }
194
195 if (style->fill.isPaintserver()) {
196 if (auto fill = style->getFillPaintServer()) {
197 style_attachments.attachFill(drawing_text, fill, paintbox);
198 }
199 }
200
201 if (style->stroke.isPaintserver()) {
202 if (auto stroke = style->getStrokePaintServer()) {
203 style_attachments.attachStroke(drawing_text, stroke, paintbox);
204 }
205 }
206
207 bool first_line_glyph = true;
208 while (glyph_index < _glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == i) {
209 if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) {
210 Geom::Affine glyph_matrix = _glyphs[glyph_index].transform(*this);
211 if (first_line_glyph && style->text_decoration_data.tspan_line_start) {
212 first_line_glyph = false;
213 phase0 = glyph_matrix.translation().x();
214 }
215 // Save the starting coordinates for the line - these are needed for figuring out dot/dash/wave phase.
216 // Use maximum ascent and descent to ensure glyphs that extend outside the embox are fully drawn.
217 drawing_text->addComponent(_spans[i].font,
218 _glyphs[glyph_index].glyph,
219 glyph_matrix,
220 _glyphs[glyph_index].advance,
221 _spans[i].line_height.getMaxAscent(),
222 _spans[i].line_height.getMaxDescent(),
223 glyph_matrix.translation().x() - phase0
224 );
225 }
226 glyph_index++;
227 }
228 drawing_text->setStyle(style);
229 drawing_text->setItemBounds(paintbox);
230 // Text spans must be painted in the right order (see inkscape/685)
231 parent->appendChild(drawing_text);
232 // Set item bounds without filter enlargement
233 parent->setItemBounds(paintbox);
234 }
235}
236
237Geom::OptRect Layout::bounds(Geom::Affine const &transform, bool with_stroke, int start, int length) const
238{
239 Geom::OptRect bbox;
240 for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
241 if (_glyphs[glyph_index].hidden) continue; // To do: This and the next line should represent the same thing, use on or the other.
242 if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) continue;
243 if (start != -1 && (int) _glyphs[glyph_index].in_character < start) continue;
244 if (length != -1) {
245 if (start == -1)
246 start = 0;
247 if ((int) _glyphs[glyph_index].in_character > start + length) continue;
248 }
249 // this could be faster
250 Geom::Affine glyph_matrix = _glyphs[glyph_index].transform(*this);
251 Geom::Affine total_transform = glyph_matrix;
252 total_transform *= transform;
253 if(_glyphs[glyph_index].span(this).font) {
254 Geom::OptRect glyph_rect = _glyphs[glyph_index].span(this).font->BBoxExact(_glyphs[glyph_index].glyph);
255 if (glyph_rect) {
256 auto glyph_box = *glyph_rect * total_transform;
257 // FIXME: Expand rectangle by half stroke width, this doesn't include meters
258 // and so is not the most ideal calculation, we could use the glyph Path here.
259 if (with_stroke) {
260 Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
261 auto text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
262 if (!text_source->style->stroke.isNone()) {
263 double scale = transform.descrim();
264 glyph_box.expandBy(0.5 * text_source->style->stroke_width.computed * scale);
265 }
266 }
267 bbox.unionWith(glyph_box);
268 }
269 }
270 }
271 return bbox;
272}
273
274/* This version is much simpler than the old one
275*/
277 Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox,
278 Geom::Affine const &ctm) const
279{
280bool text_to_path = ctx->module->textToPath();
281#define MAX_DX 2048
282float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this.
283
284Geom::Affine glyph_matrix;
285
286 if (_input_stream.empty()) return;
287 if (!_glyphs.size()) return; // yes, this can happen.
288 if (text_to_path || _path_fitted) {
289 for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
290 if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1)continue; //invisible glyphs
291 Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
292 Geom::PathVector const *pv = span.font->PathVector(_glyphs[glyph_index].glyph);
293 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
294 if (pv) {
295 glyph_matrix = _glyphs[glyph_index].transform(*this);
296 Geom::PathVector temp_pv = (*pv) * glyph_matrix;
297 if (!text_source->style->fill.isNone())
298 ctx->fill(temp_pv, ctm, text_source->style, pbox, dbox, bbox);
299 if (!text_source->style->stroke.isNone())
300 ctx->stroke(temp_pv, ctm, text_source->style, pbox, dbox, bbox);
301 }
302 }
303 }
304 else {
305 /* index by characters, referencing glyphs and spans only as needed */
306 double char_x;
307 int doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true
308 Direction block_progression = _blockProgression();
309 int oldtarget = 0;
310 int ndx = 0;
311 double rtl = 1.0; // 1 L->R, -1 R->L, constant across a span. 1.0 for t->b b->t???
312
313 for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
314 Glib::ustring text_string; // accumulate text for record in this
315 Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix
316 int glyph_index = _characters[char_index].in_glyph;
317 if(glyph_index == -1){ // if the character maps to an invisible glyph we cannot know its geometry, so skip it and move on
318 char_index++;
319 continue;
320 }
321 float ky = _glyphs[glyph_index].y; // For smuggling y kern value for span // same value for all positions in a span
322 unsigned span_index = _characters[char_index].in_span;
323 Span const &span = _spans[span_index];
324 char_x = 0.0;
325 Glib::ustring::const_iterator text_iter = span.input_stream_first_character;
326 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
327 glyph_matrix = Geom::Scale(1.0, -1.0) * (Geom::Affine)Geom::Rotate(_glyphs[glyph_index].rotation);
328 if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
329 glyph_matrix[4] = span.line(this).baseline_y + span.baseline_shift;
330 // since we're outputting character codes, not glyphs, we want the character x
331 glyph_matrix[5] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
332 } else {
333 glyph_matrix[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
334 glyph_matrix[5] = span.line(this).baseline_y + span.baseline_shift;
335 }
336 switch(span.direction){
339 case Layout::LEFT_TO_RIGHT: rtl = 1.0; break;
340 case Layout::RIGHT_TO_LEFT: rtl = -1.0; break;
341 }
342 if(doUTN){
343 oldtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation
344 }
345
346 // accumulate a record to write
347
348 unsigned lc_index = char_index;
349 unsigned hold_iisi = _spans[span_index].in_input_stream_item;
350 int newtarget = 0;
351 while(true){
352 glyph_index = _characters[lc_index].in_glyph;
353 if(glyph_index == -1){ // end of a line within a paragraph, for instance
354 lc_index++;
355 break;
356 }
357
358 // always append if here
359 text_string += *text_iter;
360
361 // figure out char widths, used by EMF, not currently used elsewhere
362 double cwidth;
363 if(lc_index == _glyphs[glyph_index].in_character){ // Glyph width is used only for the first character, these may be 0
364 cwidth = rtl * _glyphs[glyph_index].advance; // advance might be zero
365 }
366 else {
367 cwidth = 0;
368 }
369 char_x += cwidth;
370/*
371std:: cout << "DEBUG Layout::print in while "
372<< " char_index " << char_index
373<< " lc_index " << lc_index
374<< " character " << std::hex << (int) *text_iter << std::dec
375<< " glyph_index " << glyph_index
376<< " glyph_xy " << _glyphs[glyph_index].x << " , " << _glyphs[glyph_index].y
377<< " span_index " << span_index
378<< " hold_iisi " << hold_iisi
379<< std::endl; //DEBUG
380*/
381 if(ndx < MAX_DX){
382 hold_dx[ndx++] = fabs(cwidth);
383 }
384 else { // silently truncate any text line silly enough to be longer than MAX_DX
385 lc_index = _characters.size();
386 break;
387 }
388
389
390 // conditions that prevent this character from joining the record
391 lc_index++;
392 if(lc_index >= _characters.size()) break; // nothing more to process, so it must be the end of the record
393 ++text_iter;
394 if(doUTN)newtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation
395 if(newtarget != oldtarget)break; // change in unicode to nonunicode translation status
396 // MUST exit on any major span change, but not on some little events, like a font substitution event irrelevant for the file save
397 unsigned next_span_index = _characters[lc_index].in_span;
398 if(span_index != next_span_index){
399 /* on major changes break out of loop.
400 1st case usually indicates an entire input line has been processed (out of several in a paragraph)
401 2nd case usually indicates that a format change within a line (font/size/color/etc) is present.
402 */
403/*
404std:: cout << "DEBUG Layout::print in while --- "
405<< " char_index " << char_index
406<< " lc_index " << lc_index
407<< " cwidth " << cwidth
408<< " _char.x (next) " << (lc_index < _characters.size() ? _characters[lc_index].x : -1)
409<< " char_x (end this)" << char_x
410<< " diff " << fabs(char_x - _characters[lc_index].x)
411<< " oldy " << ky
412<< " nexty " << _glyphs[_characters[lc_index].in_glyph].y
413<< std::endl; //DEBUG
414*/
415 if(hold_iisi != _spans[next_span_index].in_input_stream_item)break; // major change, font, size, color, etc, must exit
416 if(fabs(char_x - _spans[next_span_index].x_start) >= 1e-4)break; // xkerning change
417 if(ky != _glyphs[_characters[lc_index].in_glyph].y)break; // ykerning change
418 /*
419 None of the above? Then this is a minor "pangito", update span_index and keep going.
420 The font used by the display may have failed over, but print does not care and can continue to use
421 whatever was specified in the XML.
422 */
423 span_index = next_span_index;
424 text_iter = _spans[span_index].input_stream_first_character;
425 }
426
427 }
428 // write it
429 ctx->bind(glyph_matrix, 1.0);
430
431 // the dx array is smuggled through to the EMF driver (ignored by others) as:
432 // text<nul>w1 w2 w3 ...wn<nul><nul>
433 // where the widths are floats 7 characters wide, including the space
434
435 char *smuggle_string=smuggle_adxkyrtl_in(text_string.c_str(),ndx, &hold_dx[0], ky, rtl);
436 ctx->text(smuggle_string, g_pos, text_source->style);
437 free(smuggle_string);
438 ctx->release();
439 ndx = 0;
440 char_index = lc_index;
441 }
442 }
443}
444
445
447{
448 if (_input_stream.empty()) return;
449 std::vector<CairoGlyphInfo> glyphtext;
450
451 // The second pass is used to draw fill over stroke in a way that doesn't
452 // cause some glyph chunks to be painted over others.
453 bool second_pass = false;
454
455 for (unsigned pass = 0; pass <= second_pass; pass++) {
456 for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; ) {
457 if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) {
458 // invisible glyphs
459 unsigned same_character = _glyphs[glyph_index].in_character;
460 while (_glyphs[glyph_index].in_character == same_character) {
461 glyph_index++;
462 if (glyph_index == _glyphs.size())
463 return;
464 }
465 continue;
466 }
467 Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
468 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
469
470 Geom::Affine glyph_matrix = _glyphs[glyph_index].transform(*this);
471 Geom::Affine font_matrix = glyph_matrix;
472 font_matrix[4] = 0;
473 font_matrix[5] = 0;
474
475 Glib::ustring::const_iterator span_iter = span.input_stream_first_character;
476 unsigned char_index = _glyphs[glyph_index].in_character;
477 unsigned original_span = _characters[char_index].in_span;
478 while (char_index && _characters[char_index - 1].in_span == original_span) {
479 char_index--;
480 ++span_iter;
481 }
482
483 // try to output as many characters as possible in one go
484 Glib::ustring span_string;
485 unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span;
486 unsigned int first_index = glyph_index;
487 glyphtext.clear();
488 do {
489 span_string += *span_iter;
490 ++span_iter;
491
492 unsigned same_character = _glyphs[glyph_index].in_character;
493 while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) {
494 if (glyph_index != first_index)
495 glyph_matrix = _glyphs[glyph_index].transform(*this);
496
497 CairoGlyphInfo info;
498 info.index = _glyphs[glyph_index].glyph;
499 // this is the translation for x,y-offset
500 info.x = glyph_matrix[4];
501 info.y = glyph_matrix[5];
502
503 glyphtext.push_back(info);
504
505 glyph_index++;
506 }
507 } while (glyph_index < _glyphs.size()
508 && _path_fitted == nullptr
509 && (font_matrix * glyph_matrix.inverse()).isIdentity()
510 && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index);
511
512 // remove vertical flip
513 Geom::Affine flip_matrix;
514 flip_matrix.setIdentity();
515 flip_matrix[3] = -1.0;
516 font_matrix = flip_matrix * font_matrix;
517
518 SPStyle const *style = text_source->style;
519 float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
520
521 if (opacity != 1.0) {
522 ctx->pushState();
523 ctx->setStateForStyle(style);
524 ctx->pushLayer();
525 }
526 if (glyph_index - first_index > 0) {
527 second_pass |= ctx->renderGlyphtext(span.font->get_font(), font_matrix, glyphtext, style, pass);
528 }
529 if (opacity != 1.0) {
530 ctx->popLayer();
531 ctx->popState();
532 }
533 }
534 }
535}
536
537#if DEBUG_TEXTLAYOUT_DUMPASTEXT
538// these functions are for dumpAsText() only. No need to translate
540{
541 switch (d) {
542 case Layout::LEFT_TO_RIGHT: return "ltr";
543 case Layout::RIGHT_TO_LEFT: return "rtl";
544 case Layout::TOP_TO_BOTTOM: return "ttb";
545 case Layout::BOTTOM_TO_TOP: return "btt";
546 }
547 return "???";
548}
549
550static char const *style_to_text(PangoStyle s)
551{
552 switch (s) {
553 case PANGO_STYLE_NORMAL: return "upright";
554 case PANGO_STYLE_ITALIC: return "italic";
555 case PANGO_STYLE_OBLIQUE: return "oblique";
556 }
557 return "???";
558}
559
560static std::string weight_to_text(PangoWeight w)
561{
562 switch (w) {
563 case PANGO_WEIGHT_THIN : return "thin";
564 case PANGO_WEIGHT_ULTRALIGHT: return "ultralight";
565 case PANGO_WEIGHT_LIGHT : return "light";
566 case PANGO_WEIGHT_SEMILIGHT : return "semilight";
567 case PANGO_WEIGHT_BOOK : return "book";
568 case PANGO_WEIGHT_NORMAL : return "normalweight";
569 case PANGO_WEIGHT_MEDIUM : return "medium";
570 case PANGO_WEIGHT_SEMIBOLD : return "semibold";
571 case PANGO_WEIGHT_BOLD : return "bold";
572 case PANGO_WEIGHT_ULTRABOLD : return "ultrabold";
573 case PANGO_WEIGHT_HEAVY : return "heavy";
574 case PANGO_WEIGHT_ULTRAHEAVY: return "ultraheavy";
575 }
576 return std::to_string(w);
577}
578#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT
579
580Glib::ustring Layout::getFontFamily(unsigned span_index) const
581{
582 if (span_index >= _spans.size())
583 return "";
584
585 if (_spans[span_index].font) {
586 return sp_font_description_get_family(_spans[span_index].font->get_descr());
587 }
588
589 return "";
590}
591
592#if DEBUG_TEXTLAYOUT_DUMPASTEXT
593Glib::ustring Layout::dumpAsText() const
594{
595 Glib::ustring result;
596 Glib::ustring::const_iterator icc;
597 char line[256];
598
599 result = Glib::ustring::compose("spans %1\nchars %2\nglyphs %3\n", _spans.size(), _characters.size(), _glyphs.size());
600 if(_characters.size() > 1){
601 unsigned lastspan=5000;
602 for(unsigned j = 0; j < _characters.size() ; j++){
603 if(lastspan != _characters[j].in_span){
604 lastspan = _characters[j].in_span;
605 icc = _spans[lastspan].input_stream_first_character;
606 }
607 snprintf(line, sizeof(line), "char %4u: '%c' 0x%4.4x x=%8.4f glyph=%3d span=%3d\n", j, *icc, *icc, _characters[j].x, _characters[j].in_glyph, _characters[j].in_span);
608 result += line;
609 ++icc;
610 }
611 }
612 if(_glyphs.size()){
613 for(unsigned j = 0; j < _glyphs.size() ; j++){
614 snprintf(line, sizeof(line), "glyph %4u: %4d (%8.4f,%8.4f) rot=%8.4f cx=%8.4f char=%4d\n",
615 j, _glyphs[j].glyph, _glyphs[j].x, _glyphs[j].y, _glyphs[j].rotation, _glyphs[j].width, _glyphs[j].in_character);
616 result += line;
617 }
618 }
619
620 for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
621 result += Glib::ustring::compose("==== span %1 \n", span_index)
622 + Glib::ustring::compose(" in para %1 (direction=%2)\n", _lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph,
623 direction_to_text(_paragraphs[_lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph].base_direction))
624 + Glib::ustring::compose(" in source %1 (type=%2, cookie=%3)\n", _spans[span_index].in_input_stream_item,
625 _input_stream[_spans[span_index].in_input_stream_item]->Type(),
626 _input_stream[_spans[span_index].in_input_stream_item]->source)
627 + Glib::ustring::compose(" in line %1 (baseline=%2, shape=%3)\n", _chunks[_spans[span_index].in_chunk].in_line,
628 _lines[_chunks[_spans[span_index].in_chunk].in_line].baseline_y,
629 _lines[_chunks[_spans[span_index].in_chunk].in_line].in_shape)
630 + Glib::ustring::compose(" in chunk %1 (x=%2, baselineshift=%3)\n", _spans[span_index].in_chunk, _chunks[_spans[span_index].in_chunk].left_x, _spans[span_index].baseline_shift);
631
632 if (_spans[span_index].font) {
633 const char* variations = pango_font_description_get_variations(_spans[span_index].font->descr);
634 result += Glib::ustring::compose(
635 " font '%1' %2 %3 %4 %5\n",
636 sp_font_description_get_family(_spans[span_index].font->descr),
637 _spans[span_index].font_size,
638 style_to_text( pango_font_description_get_style(_spans[span_index].font->descr) ),
639 weight_to_text( pango_font_description_get_weight(_spans[span_index].font->descr) ),
640 (variations?variations:"")
641 );
642 }
643 result += Glib::ustring::compose(" x_start = %1, x_end = %2\n", _spans[span_index].x_start, _spans[span_index].x_end)
644 + Glib::ustring::compose(" line height: ascent %1, descent %2\n", _spans[span_index].line_height.ascent, _spans[span_index].line_height.descent)
645 + Glib::ustring::compose(" direction %1, block-progression %2\n", direction_to_text(_spans[span_index].direction), direction_to_text(_spans[span_index].block_progression))
646 + " ** characters:\n";
647 Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character;
648 // very inefficient code. what the hell, it's only debug stuff.
649 for (unsigned char_index = 0 ; char_index < _characters.size() ; char_index++) {
650 union {const PangoLogAttr* pattr; const unsigned* uattr;} u;
651 u.pattr = &_characters[char_index].char_attributes;
652 if (_characters[char_index].in_span != span_index) continue;
653 if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) {
654 snprintf(line, sizeof(line), " %u: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph);
655 } else { // some text has empty tspans, iter_char cannot be dereferenced
656 snprintf(line, sizeof(line), " %u: '%c' 0x%4.4x x=%f flags=%03x glyph=%d\n", char_index, *iter_char, *iter_char, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph);
657 ++iter_char;
658 }
659 result += line;
660 }
661 result += " ** glyphs:\n";
662 for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
663 if (_characters[_glyphs[glyph_index].in_character].in_span != span_index) continue;
664 snprintf(line, sizeof(line), " %u: %d (%f,%f) rot=%f cx=%f char=%d\n", glyph_index, _glyphs[glyph_index].glyph, _glyphs[glyph_index].x, _glyphs[glyph_index].y, _glyphs[glyph_index].rotation, _glyphs[glyph_index].width, _glyphs[glyph_index].in_character);
665 result += line;
666 }
667 result += "\n";
668 }
669 result += "EOT\n";
670 return result;
671}
672#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT
673
674void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path)
675{
676 double offset = 0.0;
677
678 if (startOffset._set) {
679 if (startOffset.unit == SVGLength::PERCENT)
680 offset = startOffset.computed * const_cast<Path&>(path).Length();
681 else
682 offset = startOffset.computed;
683 }
684
685 Alignment alignment = _paragraphs.empty() ? LEFT : _paragraphs.front().alignment;
686 switch (alignment) {
687 case CENTER:
688 offset -= _getChunkWidth(0) * 0.5;
689 break;
690 case RIGHT:
692 break;
693 default:
694 break;
695 }
696
697 if (_characters.empty()) {
698 int unused = 0;
699 Path::cut_position *point_otp = const_cast<Path&>(path).CurvilignToPosition(1, &offset, unused);
700 if (offset >= 0.0 && point_otp != nullptr && point_otp[0].piece >= 0) {
701 Geom::Point point;
702 Geom::Point tangent;
703 const_cast<Path&>(path).PointAndTangentAt(point_otp[0].piece, point_otp[0].t, point, tangent);
706 _empty_cursor_shape.rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
707 } else {
708 _empty_cursor_shape.rotation = atan2(tangent[Geom::Y], tangent[Geom::X]);
709 }
710 }
711 }
712
713 for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
714 Span const &span = _characters[char_index].span(this);
715
716 size_t next_cluster_char_index = 0; // TODO refactor to not bump via for loops
717 for (next_cluster_char_index = char_index + 1 ; next_cluster_char_index < _characters.size() ; next_cluster_char_index++) {
718 if (_characters[next_cluster_char_index].in_glyph != -1 && _characters[next_cluster_char_index].char_attributes.is_cursor_position)
719 {
720 break;
721 }
722 }
723
724 size_t next_cluster_glyph_index = 0;
725 if (next_cluster_char_index == _characters.size()) {
726 next_cluster_glyph_index = _glyphs.size();
727 } else {
728 next_cluster_glyph_index = _characters[next_cluster_char_index].in_glyph;
729 }
730
731 double start_offset = offset + span.x_start + _characters[char_index].x;
732 double cluster_width = 0.0;
733 size_t const current_cluster_glyph_index = _characters[char_index].in_glyph;
734 for (size_t glyph_index = current_cluster_glyph_index ; glyph_index < next_cluster_glyph_index ; glyph_index++)
735 {
736 cluster_width += _glyphs[glyph_index].advance;
737 }
738 // TODO block progression?
739 if (span.direction == RIGHT_TO_LEFT)
740 {
741 start_offset -= cluster_width;
742 }
743 double end_offset = start_offset + cluster_width;
744
745 int unused = 0;
746 double midpoint_offset = (start_offset + end_offset) * 0.5;
747 // as far as I know these functions are const, they're just not marked as such
748 Path::cut_position *midpoint_otp = const_cast<Path&>(path).CurvilignToPosition(1, &midpoint_offset, unused);
749 if (midpoint_offset >= 0.0 && midpoint_otp != nullptr && midpoint_otp[0].piece >= 0) {
750 Geom::Point midpoint;
751 Geom::Point tangent;
752 const_cast<Path&>(path).PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
753
754 if (start_offset >= 0.0 && end_offset >= 0.0) {
755 Path::cut_position *start_otp = const_cast<Path&>(path).CurvilignToPosition(1, &start_offset, unused);
756 if (start_otp != nullptr && start_otp[0].piece >= 0) {
757 Path::cut_position *end_otp = const_cast<Path&>(path).CurvilignToPosition(1, &end_offset, unused);
758 if (end_otp != nullptr && end_otp[0].piece >= 0) {
759 bool on_same_subpath = true;
760 for (const auto & pt : path.pts) {
761 if (pt.piece <= start_otp[0].piece) continue;
762 if (pt.piece >= end_otp[0].piece) break;
763 if (pt.isMoveTo == polyline_moveto) {
764 on_same_subpath = false;
765 break;
766 }
767 }
768 if (on_same_subpath) {
769 // both points were on the same subpath (without this test the angle is very weird)
770 Geom::Point startpoint, endpoint;
771 const_cast<Path&>(path).PointAt(start_otp[0].piece, start_otp[0].t, startpoint);
772 const_cast<Path&>(path).PointAt(end_otp[0].piece, end_otp[0].t, endpoint);
773 if (endpoint != startpoint) {
774 tangent = endpoint - startpoint;
775 tangent.normalize();
776 }
777 }
778 g_free(end_otp);
779 }
780 g_free(start_otp);
781 }
782 }
783
785 double rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
786 for (size_t glyph_index = current_cluster_glyph_index; glyph_index < next_cluster_glyph_index ; glyph_index++) {
787 _glyphs[glyph_index].x = midpoint[Geom::Y] - tangent[Geom::X] * _glyphs[glyph_index].y - span.chunk(this).left_x;
788 _glyphs[glyph_index].y = midpoint[Geom::X] + tangent[Geom::Y] * _glyphs[glyph_index].y - _lines.front().baseline_y;
789 _glyphs[glyph_index].rotation += rotation;
790 }
791 } else {
792 double rotation = atan2(tangent[Geom::Y], tangent[Geom::X]);
793 for (size_t glyph_index = current_cluster_glyph_index; glyph_index < next_cluster_glyph_index ; glyph_index++) {
794 double tangent_shift = -cluster_width * 0.5 + _glyphs[glyph_index].x - (_characters[char_index].x + span.x_start);
795 if (span.direction == RIGHT_TO_LEFT)
796 {
797 tangent_shift += cluster_width;
798 }
799 _glyphs[glyph_index].x = midpoint[Geom::X] + tangent[Geom::X] * tangent_shift - tangent[Geom::Y] * _glyphs[glyph_index].y - span.chunk(this).left_x;
800 _glyphs[glyph_index].y = midpoint[Geom::Y] + tangent[Geom::Y] * tangent_shift + tangent[Geom::X] * _glyphs[glyph_index].y - _lines.front().baseline_y;
801 _glyphs[glyph_index].rotation += rotation;
802 }
803 }
804 _input_truncated = false;
805 } else { // outside the bounds of the path: hide the glyphs
806 _characters[char_index].in_glyph = -1;
807 _input_truncated = true;
808 }
809 g_free(midpoint_otp);
810
811 char_index = next_cluster_char_index;
812 }
813
814 for (auto & _span : _spans) {
815 _span.x_start += offset;
816 _span.x_end += offset;
817 }
818
819 _path_fitted = &path;
820}
821
823{
824 return convertToCurves(begin(), end());
825}
826
827SPCurve Layout::convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const
828{
830
831 for (int glyph_index = from_glyph._glyph_index ; glyph_index < to_glyph._glyph_index ; glyph_index++) {
832 Span const &span = _glyphs[glyph_index].span(this);
833 Geom::Affine glyph_matrix = _glyphs[glyph_index].transform(*this);
834 Geom::PathVector const *pathv = span.font->PathVector(_glyphs[glyph_index].glyph);
835 if (pathv) {
836 Geom::PathVector pathv_trans = (*pathv) * glyph_matrix;
837 curve.append(SPCurve(std::move(pathv_trans)));
838 }
839 }
840
841 return curve;
842}
843
845{
846 // this is all massively oversimplified
847 // I can't actually think of anybody who'll want to use it at the moment, so it'll stay simple
848 for (auto & _glyph : _glyphs) {
849 Geom::Point point(_glyph.x, _glyph.y);
850 point *= transform;
851 _glyph.x = point[0];
852 _glyph.y = point[1];
853 }
854}
855
863
864
872
874{
875 double length = 0;
876 for (std::vector<Span>::const_iterator it_span = _spans.begin() ; it_span != _spans.end() ; it_span++) {
877 // take x_end of the last span of each chunk
878 if (it_span == _spans.end() - 1 || (it_span + 1)->in_chunk != it_span->in_chunk)
879 length += it_span->x_end;
880 }
881 return length;
882
883
884}
885
886}//namespace Text
887}//namespace Inkscape
888
889std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics &f) {
890 out << " emSize: " << f.emSize()
891 << " ascent: " << f.ascent
892 << " descent: " << f.descent
893 << " xheight: " << f.xheight;
894 return out;
895}
896
897std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics *f) {
898 out << " emSize: " << f->emSize()
899 << " ascent: " << f->ascent
900 << " descent: " << f->descent
901 << " xheight: " << f->xheight;
902 return out;
903}
904
905
906
907/*
908 Local Variables:
909 mode:c++
910 c-file-style:"stroustrup"
911 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
912 indent-tabs-mode:nil
913 fill-column:99
914 End:
915*/
916// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
TODO: insert short description here.
@ polyline_moveto
Definition Path.h:47
double scale
Definition aa.cpp:228
Declaration of CairoRenderContext, a class used for rendering with Cairo.
FontInstance provides metrics, OpenType data, and glyph curves/pixbufs for a font.
double GetXHeight() const
double GetTypoDescent() const
double GetMaxAscent() const
double GetMaxDescent() const
double GetTypoAscent() const
3x3 matrix representing an affine transformation.
Definition affine.h:70
Point translation() const
Gets the translation imparted by the Affine.
Definition affine.cpp:41
void setIdentity()
Sets this matrix to be the Identity Affine.
Definition affine.cpp:96
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
Two-dimensional point that doubles as a vector.
Definition point.h:66
void normalize()
Normalize the vector representing the point.
Definition point.cpp:96
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
bool renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix, std::vector< CairoGlyphInfo > const &glyphtext, SPStyle const *style, bool second_pass=false)
Called by Layout-TNG-Output, this function decides how to apply styles and write out the final shapes...
void popLayer(cairo_operator_t composite=CAIRO_OPERATOR_CLEAR)
Keep track of font metrics.
Definition Layout-TNG.h:619
void set(FontInstance const *font)
void max(FontMetrics const &other)
Save the larger values of ascent and descent between this and other.
void computeEffective(const double &line_height)
Calculate the effective ascent and descent including half "leading".
Represents a text item in the input stream.
Definition Layout-TNG.h:695
SPStyle * style
in characters, from text_start to text_end only
Definition Layout-TNG.h:702
Holds a position within the glyph output of Layout.
Definition Layout-TNG.h:973
Generates the layout for either wrapped or non-wrapped text and stores the result.
Definition Layout-TNG.h:144
void transform(Geom::Affine const &transform)
Apply the given transform to all the output presently stored in this object.
LengthAdjust lengthAdjust
How do we meet textLength if specified: by letterspacing or by scaling horizontally.
Definition Layout-TNG.h:312
Geom::OptRect bounds(Geom::Affine const &transform, bool with_stroke=false, int start=-1, int length=-1) const
Calculates the smallest rectangle completely enclosing all the glyphs.
std::vector< Line > _lines
Definition Layout-TNG.h:899
Alignment
For expressing paragraph alignment.
Definition Layout-TNG.h:205
double getTextLengthMultiplierDue() const
Get the actual scale multiplier if it's due with the current values of above stuff,...
std::vector< Glyph > _glyphs
Definition Layout-TNG.h:903
std::vector< Chunk > _chunks
Definition Layout-TNG.h:900
void _clearOutputObjects()
Erases all the stuff output by computeFlow().
Direction _blockProgression() const
The overall block-progression of the whole flow.
Definition Layout-TNG.h:749
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
Glib::ustring dumpAsText() const
debug and unit test method.
void show(DrawingGroup *in_arena, StyleAttachments &style_attachments, Geom::OptRect const &paintbox) const
Adds all the output glyphs to in_arena using the given paintbox.
Glib::ustring getFontFamily(unsigned span_index) const
Returns the font family of the indexed span.
void fitToPathAlign(SVGLength const &startOffset, Path const &path)
Moves all the glyphs in the structure so that the baseline of all the characters sits neatly along th...
std::vector< Paragraph > _paragraphs
Definition Layout-TNG.h:898
static bool _directions_are_orthogonal(Direction d1, Direction d2)
so that LEFT_TO_RIGHT == RIGHT_TO_LEFT but != TOP_TO_BOTTOM
iterator end() const
Returns an iterator pointing just past the end of the last glyph, which is also just past the end of ...
double textLengthMultiplier
By how much each character needs to be wider or narrower, using the specified lengthAdjust strategy,...
Definition Layout-TNG.h:318
double getActualLength() const
Get actual length of layout, by summing span lengths.
double getTextLengthIncrementDue() const
Get the actual spacing increment if it's due with the current values of above stuff,...
SVGLength textLength
Gives the length target of this layout, as given by textLength attribute.
Definition Layout-TNG.h:309
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:740
void print(SPPrintContext *ctx, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox, Geom::Affine const &ctm) const
Sends all the glyphs to the given print context.
double _getChunkWidth(unsigned chunk_index) const
calculates the width of a chunk, which is the largest x coordinate (start or end) of the spans contai...
std::vector< Character > _characters
Definition Layout-TNG.h:902
void showGlyphs(CairoRenderContext *ctx) const
Renders all the glyphs to the given Cairo rendering context.
Direction
Used to specify any particular text direction required.
Definition Layout-TNG.h:159
Path const * _path_fitted
as passed to fitToPathAlign()
Definition Layout-TNG.h:798
iterator begin() const
Returns an iterator pointing at the first glyph of the flowed output.
std::vector< Span > _spans
Definition Layout-TNG.h:901
void attachFill(DrawingText *item, SPPaintServer *paintserver, Geom::OptRect const &bbox)
void attachStroke(DrawingText *item, SPPaintServer *paintserver, Geom::OptRect const &bbox)
void attachFilter(DrawingText *item, SPFilter *filter)
Path and its polyline approximation.
Definition Path.h:93
std::vector< path_lineto > pts
Definition Path.h:128
Wrapper around a Geom::PathVector object.
Definition curve.h:26
An SVG style object.
Definition style.h:45
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
SPITextDecorationData text_decoration_data
Definition style.h:197
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Definition style.h:216
SVG length type.
Definition svg-length.h:22
bool _set
Definition svg-length.h:41
Unit unit
Definition svg-length.h:44
float computed
Definition svg-length.h:50
const double w
Definition conic-4.cpp:19
Css & result
static char const *const parent
Definition dir-util.cpp:70
Group belonging to an SVG drawing element.
char const * sp_font_description_get_family(PangoFontDescription const *fontDescr)
TODO: insert short description here.
The data describing a single loaded font.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
double offset
Geom::Point start
char * smuggle_adxkyrtl_in(const char *string, int ndx, float *adx, float ky, float rtl)
static char const * direction_to_text(Layout::Direction d)
static char const * style_to_text(PangoStyle s)
static std::string weight_to_text(PangoWeight w)
Helper class to stream background task notifications as a series of messages.
PathVector - a sequence of subpaths.
Geom::Affine transform(Layout const &layout) const
unsigned in_input_stream_item
See CSS3 section 3.2. The direction in which lines go.
Definition Layout-TNG.h:863
Chunk const & chunk(Layout const *l) const
Definition Layout-TNG.h:865
std::shared_ptr< FontInstance > font
Definition Layout-TNG.h:852
Glib::ustring::const_iterator input_stream_first_character
Definition Layout-TNG.h:864
Line const & line(Layout const *l) const
Definition Layout-TNG.h:866
unsigned int bind(Geom::Affine const &transform, float opacity)
Definition print.cpp:39
unsigned int release()
Definition print.cpp:45
unsigned int fill(Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox)
Definition print.cpp:51
unsigned int stroke(Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox)
Definition print.cpp:58
unsigned int text(char const *text, Geom::Point p, SPStyle const *style)
Definition print.cpp:71
Definition curve.h:24
Creates and maintains display tree needed for text styling.
SPStyle - a style object for SPItem objects.
Enhanced Metafile Input/Output.
int CanUTN(void)
int SingleUnicodeToNon(uint16_t text)
double width
std::vector< Texture > unused