Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
Layout-TNG-OutIter.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 using iterators
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#include "Layout-TNG.h"
13
14#include <utility>
15
16#include <2geom/transforms.h>
17#include <2geom/line.h>
18
19#include "style.h"
20#include "font-instance.h"
21
22#include "livarot/Path.h"
23#include "svg/svg-length.h"
24
25namespace Inkscape {
26namespace Text {
27
28// Comment 18 Sept 2019:
29// Cursor code might be simpler if Character was turned into a proper
30// class and kept track of its absolute position and extent. This would
31// make handling multi-line text (including multi-line text using
32// 'white-space:pre') easier. This would also avoid problems where
33// 'dx','dy' moved the character a long distance from its nominal
34// position.
35
36Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x, double local_y) const
37{
38 unsigned char_index = _lineToCharacter(line_index);
39 int best_char_index = -1;
40 double best_difference = DBL_MAX;
41
42 if (char_index == _characters.size()) return end();
43 for ( ; char_index < _characters.size() ; char_index++) {
44 if (_characters[char_index].chunk(this).in_line != line_index) break;
45 //if (_characters[char_index].char_attributes.is_mandatory_break) break;
46 if (!_characters[char_index].char_attributes.is_cursor_position) continue;
47
48 double delta_x =
49 _characters[char_index].x +
50 _characters[char_index].span(this).x_start +
51 _characters[char_index].chunk(this).left_x -
52 local_x;
53
54 double delta_y =
55 _characters[char_index].span(this).y_offset +
56 _characters[char_index].line(this).baseline_y -
57 local_y;
58
59 double this_difference = std::sqrt(delta_x*delta_x + delta_y*delta_y);
60
61 if (this_difference < best_difference) {
62 best_difference = this_difference;
63 best_char_index = char_index;
64 }
65 }
66
67 // also try the very end of a para (not lines though because the space wraps)
68 if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) {
69
70 double delta_x = 0.0;
71 double delta_y = 0.0;
72
73 if (char_index == 0) {
74 delta_x = _spans.front().x_end + _chunks.front().left_x - local_x;
75 delta_y = _spans.front().y_offset + _spans.front().line(this).baseline_y - local_y;
76 } else {
77 delta_x = _characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x;
78 delta_y = _characters[char_index - 1].span(this).y_offset + _characters[char_index - 1].line(this).baseline_y - local_y;
79 }
80
81 double this_difference = std::sqrt(delta_x*delta_x + delta_y*delta_y);
82
83 if (this_difference < best_difference) {
84 best_char_index = char_index;
85 best_difference = this_difference;
86 }
87 }
88
89
90 if (best_char_index == -1) {
91 best_char_index = char_index;
92 }
93
94 if (best_char_index == _characters.size()) {
95 return end();
96 }
97
98 return iterator(this, best_char_index);
99}
100
101double Layout::_getChunkWidth(unsigned chunk_index) const
102{
103 double chunk_width = 0.0;
104 unsigned span_index;
105 if (chunk_index) {
106 span_index = _lineToSpan(_chunks[chunk_index].in_line);
107 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++){};
108 } else {
109 span_index = 0;
110 }
111
112 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
113 chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
114 }
115
116 return chunk_width;
117}
118
119/* getting the cursor position for a mouse click is not as simple as it might
120seem. The two major problems are flows set up in multiple columns and large
121dy adjustments such that text does not belong to the line it appears to. In
122the worst case it's possible to have two characters on top of each other, in
123which case the one we pick is arbitrary.
124
125This is a 3-stage (2 pass) algorithm:
1261) search all the spans to see if the point is contained in one, if so take
127 that. Note that this will collect all clicks from the current UI because
128 of how the hit detection of nrarena objects works.
1292) if that fails, run through all the chunks finding a best guess of the one
130 the user wanted. This is the one whose y coordinate is nearest, or if
131 there's a tie, the x.
1323) search in that chunk using x-coordinate only to find the position.
133*/
135{
136 if (_lines.empty()) return begin();
137 double local_x = x;
138 double local_y = y;
139
140 if (_path_fitted) {
141 Path::cut_position position = const_cast<Path*>(_path_fitted)->PointToCurvilignPosition(Geom::Point(x, y));
142 local_x = const_cast<Path*>(_path_fitted)->PositionToLength(position.piece, position.t);
143 return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x);
144 }
145
147 local_x = y;
148 local_y = x;
149 }
150
151 // stage 1:
152 for (const auto & _span : _spans) {
153 double span_left, span_right;
154 if (_span.x_start < _span.x_end) {
155 span_left = _span.x_start;
156 span_right = _span.x_end;
157 } else {
158 span_left = _span.x_end;
159 span_right = _span.x_start;
160 }
161
162 double y_line = _span.line(this).baseline_y + _span.baseline_shift + _span.y_offset;
163 if ( local_x >= _chunks[_span.in_chunk].left_x + span_left
164 && local_x <= _chunks[_span.in_chunk].left_x + span_right
165 && local_y >= y_line - _span.line_height.ascent
166 && local_y <= y_line + _span.line_height.descent) {
167 return _cursorXOnLineToIterator(_chunks[_span.in_chunk].in_line, local_x, local_y);
168 }
169 }
170
171 // stage 2:
172 unsigned span_index = 0;
173 unsigned chunk_index;
174 int best_chunk_index = -1;
175 double best_y_range = DBL_MAX;
176 double best_x_range = DBL_MAX;
177 for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) {
178 FontMetrics line_height;
179 line_height *= 0.0; // Set all metrics to zero.
180 double chunk_width = 0.0;
181 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
182 line_height.max(_spans[span_index].line_height);
183 chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
184 }
185 double this_y_range;
186 if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent)
187 this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y;
188 else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent)
189 this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent);
190 else
191 this_y_range = 0.0;
192 if (this_y_range <= best_y_range) {
193 if (this_y_range < best_y_range) best_x_range = DBL_MAX;
194 double this_x_range;
195 if (local_x < _chunks[chunk_index].left_x)
196 this_x_range = _chunks[chunk_index].left_x - local_y;
197 else if (local_x > _chunks[chunk_index].left_x + chunk_width)
198 this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width);
199 else
200 this_x_range = 0.0;
201 if (this_x_range < best_x_range) {
202 best_y_range = this_y_range;
203 best_x_range = this_x_range;
204 best_chunk_index = chunk_index;
205 }
206 }
207 }
208
209 // stage 3:
210 if (best_chunk_index == -1) return begin(); // never happens
211 return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x, local_y);
212}
213
214Layout::iterator Layout::getLetterAt(double x, double y) const
215{
216 Geom::Point point(x, y);
217
218 double rotation;
219 for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
220 Geom::Rect box = characterBoundingBox(it, &rotation);
221 // todo: rotation
222 if (box.contains(point)) return it;
223 }
224 return end();
225}
226
227Layout::iterator Layout::sourceToIterator(SPObject *source /*, Glib::ustring::const_iterator text_iterator*/) const
228{
229 unsigned source_index;
230 if (_characters.empty()) return end();
231 for (source_index = 0 ; source_index < _input_stream.size() ; source_index++)
232 if (_input_stream[source_index]->source == source) break;
233 if (source_index == _input_stream.size()) return end();
234
235 unsigned char_index = _sourceToCharacter(source_index);
236
237 // Fix a bug when hidding content in flow box element
238 if (char_index >= _characters.size())
239 return end();
240
241 if (_input_stream[source_index]->Type() != TEXT_SOURCE)
242 return iterator(this, char_index);
243
244 return iterator(this, char_index);
245 /* This code was never used, the text_iterator argument was "NULL" in all calling code
246 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
247
248 if (text_iterator <= text_source->text_begin) return iterator(this, char_index);
249 if (text_iterator >= text_source->text_end) {
250 if (source_index == _input_stream.size() - 1) return end();
251 return iterator(this, _sourceToCharacter(source_index + 1));
252 }
253 Glib::ustring::const_iterator iter_text = text_source->text_begin;
254 for ( ; char_index < _characters.size() ; char_index++) {
255 if (iter_text == text_iterator)
256 return iterator(this, char_index);
257 iter_text++;
258 }
259 return end(); // never happens
260 */
261}
262
263Geom::OptRect Layout::glyphBoundingBox(iterator const &it, double *rotation) const
264{
265 if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
266 return _glyphs[it._glyph_index].span(this).font->BBoxExact(_glyphs[it._glyph_index].glyph);
267}
268
270{
271 if (_characters.empty())
273
274 Geom::Point res;
275 if (it._char_index == _characters.size()) {
276 res = Geom::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
277 } else {
278 res = Geom::Point(_characters[it._char_index].chunk(this).left_x
279 + _spans[_characters[it._char_index].in_span].x_start
280 + _characters[it._char_index].x,
281 _characters[it._char_index].line(this).baseline_y
282 + _characters[it._char_index].span(this).baseline_shift);
283 }
285 std::swap(res[Geom::X], res[Geom::Y]);
286 }
287 return res;
288}
289
290std::optional<Geom::Point> Layout::baselineAnchorPoint() const
291{
292 iterator pos = this->begin();
293 Geom::Point left_pt = this->characterAnchorPoint(pos);
294 pos.thisEndOfLine();
295 Geom::Point right_pt = this->characterAnchorPoint(pos);
296 Direction direction = _spans.begin()->direction;
297 if (direction == RIGHT_TO_LEFT) {
298 std::swap(right_pt, left_pt);
299 }
300 switch (this->paragraphAlignment(pos)) {
301 case LEFT:
302 case FULL:
303 return left_pt;
304 break;
305 case CENTER:
306 return (left_pt + right_pt)/2; // middle point
307 break;
308 case RIGHT:
309 return right_pt;
310 break;
311 default:
312 return std::optional<Geom::Point>();
313 break;
314 }
315}
316
318{
319 iterator pos = this->begin();
320 Geom::Point left_pt = this->characterAnchorPoint(pos);
321 pos.thisEndOfLine();
322 Geom::Point right_pt = this->characterAnchorPoint(pos);
323
325 baseline.start(left_pt);
327
328 return baseline;
329}
330
332{
334 auto pos = begin();
335
336 while (pos != end()) {
337 auto const start_anchor_pt = characterAnchorPoint(pos);
338 pos.thisEndOfLine();
339 auto const end_anchor_pt = characterAnchorPoint(pos);
340
341 _baselines.emplace_back(start_anchor_pt, end_anchor_pt);
342 pos.nextCharacter();
343 }
344}
345
347{
348 unsigned chunk_index;
349
350 if (_chunks.empty())
351 return Geom::Point(0.0, 0.0);
352
353 if (_characters.empty())
354 chunk_index = 0;
355 else if (it._char_index == _characters.size())
356 chunk_index = _chunks.size() - 1;
357 else chunk_index = _characters[it._char_index].span(this).in_chunk;
358
359 Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
360 double x = _chunks[chunk_index].left_x;
361 double y = _lines[_chunks[chunk_index].in_line].baseline_y;
362 double chunk_width = _getChunkWidth(chunk_index);
363 if (alignment == RIGHT) {
364 x += chunk_width;
365 } else if (alignment == CENTER) {
366 x += chunk_width * 0.5;
367 }
368
370 return Geom::Point(y, x);
371 } else {
372 return Geom::Point(x, y);
373 }
374}
375
376Geom::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
377{
378 Geom::Point top_left, bottom_right;
379 unsigned char_index = it._char_index;
380
381 if (_path_fitted) {
382 double cluster_half_width = 0.0;
383 for (int glyph_index = _characters[char_index].in_glyph ; _glyphs.size() != glyph_index ; glyph_index++) {
384 if (_glyphs[glyph_index].in_character != char_index) break;
385 cluster_half_width += _glyphs[glyph_index].advance;
386 }
387 cluster_half_width *= 0.5;
388
389 double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
390 int unused = 0;
391 Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
392 if (midpoint_offset >= 0.0 && midpoint_otp != nullptr && midpoint_otp[0].piece >= 0) {
393 Geom::Point midpoint;
394 Geom::Point tangent;
395 Span const &span = _characters[char_index].span(this);
396
397 const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
398 top_left[Geom::X] = midpoint[Geom::X] - cluster_half_width;
399 top_left[Geom::Y] = midpoint[Geom::Y] - span.line_height.ascent;
400 bottom_right[Geom::X] = midpoint[Geom::X] + cluster_half_width;
401 bottom_right[Geom::Y] = midpoint[Geom::Y] + span.line_height.descent;
402 Geom::Point normal = tangent.cw();
403 top_left += span.baseline_shift * normal;
404 bottom_right += span.baseline_shift * normal;
405 if (rotation)
406 *rotation = atan2(tangent[1], tangent[0]);
407 }
408 g_free(midpoint_otp);
409 } else {
410 if (it._char_index == _characters.size()) {
411 top_left[Geom::X] = bottom_right[Geom::X] = _chunks.back().left_x + _spans.back().x_end;
412 char_index--;
413 } else {
414 double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
415 top_left[Geom::X] = span_x + _characters[it._char_index].x;
416 if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
417 bottom_right[Geom::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
418 else
419 bottom_right[Geom::X] = span_x + _characters[it._char_index + 1].x;
420 }
421
422 double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
424 double span_height = _spans[_characters[char_index].in_span].line_height.emSize();
425 top_left[Geom::Y] = top_left[Geom::X];
426 top_left[Geom::X] = baseline_y - span_height * 0.5;
427 bottom_right[Geom::Y] = bottom_right[Geom::X];
428 bottom_right[Geom::X] = baseline_y + span_height * 0.5;
429 } else {
430 top_left[Geom::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
431 bottom_right[Geom::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
432 }
433
434 if (rotation) {
435 if (it._glyph_index == -1)
436 *rotation = 0.0;
437 else if (it._glyph_index == (int)_glyphs.size())
438 *rotation = _glyphs.back().rotation;
439 else
440 *rotation = _glyphs[it._glyph_index].rotation;
441 }
442 }
443
444 return Geom::Rect(top_left, bottom_right);
445}
446
447std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const
448{
449 std::vector<Geom::Point> quads;
450 unsigned char_index;
451 unsigned end_char_index;
452
453 if (it_start._char_index < it_end._char_index) {
454 char_index = it_start._char_index;
455 end_char_index = std::min((size_t)it_end._char_index, _characters.size());
456 } else {
457 char_index = it_end._char_index;
458 end_char_index = std::min((size_t)it_start._char_index, _characters.size());
459 }
460 for ( ; char_index < end_char_index ; ) {
461 if (_characters[char_index].in_glyph == -1) {
462 char_index++;
463 continue;
464 }
465 double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
466 unsigned span_index = _characters[char_index].in_span;
467
468 Geom::Point top_left, bottom_right;
469 if (_path_fitted || char_rotation != 0.0) {
470 Geom::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
471 top_left = box.min();
472 bottom_right = box.max();
473 char_index++;
474 } else { // for straight text we can be faster by combining all the character boxes in a span into one box
475 double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
476 top_left[Geom::X] = span_x + _characters[char_index].x;
477 while (char_index < end_char_index && _characters[char_index].in_span == span_index)
478 char_index++;
479 if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
480 bottom_right[Geom::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
481 else
482 bottom_right[Geom::X] = span_x + _characters[char_index].x;
483
484 double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
485 double vertical_scale = _glyphs.back().vertical_scale;
486 double offset_y = _spans[span_index].y_offset;
487
489 double span_height = vertical_scale * _spans[span_index].line_height.emSize();
490 top_left[Geom::Y] = top_left[Geom::X];
491 top_left[Geom::X] = offset_y + baseline_y - span_height * 0.5;
492 bottom_right[Geom::Y] = bottom_right[Geom::X];
493 bottom_right[Geom::X] = offset_y + baseline_y + span_height * 0.5;
494 } else {
495 top_left[Geom::Y] = offset_y + baseline_y - vertical_scale * _spans[span_index].line_height.ascent;
496 bottom_right[Geom::Y] = offset_y + baseline_y + vertical_scale * _spans[span_index].line_height.descent;
497 }
498 }
499
500 Geom::Rect char_box(top_left, bottom_right);
501 if (char_box.dimensions()[Geom::X] == 0.0 || char_box.dimensions()[Geom::Y] == 0.0)
502 continue;
503 Geom::Point center_of_rotation((top_left[Geom::X] + bottom_right[Geom::X]) * 0.5,
504 top_left[Geom::Y] + _spans[span_index].line_height.ascent);
505 Geom::Affine total_transform = Geom::Translate(-center_of_rotation) * Geom::Rotate(char_rotation) * Geom::Translate(center_of_rotation) * transform;
506 for(int i = 0; i < 4; i ++)
507 quads.push_back(char_box.corner(i) * total_transform);
508 }
509 return quads;
510}
511
512void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
513{
514 if (_characters.empty()) {
515 position = _empty_cursor_shape.position;
517 rotation = _empty_cursor_shape.rotation;
518 } else {
519 // we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
520 // this means x & rotation are the current values but y & height belong to the previous character.
521 // this isn't quite right because dx attributes will be moved along, but it's good enough
522 Span const *span;
524 if (_path_fitted) {
525 // text on a path
526 double x;
527 if (it._char_index >= _characters.size()) {
528 span = &_spans.back();
529 x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
530 } else {
531 span = &_spans[_characters[it._char_index].in_span];
532 x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
533 if (vertical_text)
534 x -= span->line_height.descent;
535 if (it._char_index != 0)
536 span = &_spans[_characters[it._char_index - 1].in_span];
537 }
538 double path_length = const_cast<Path*>(_path_fitted)->Length();
539 double x_on_path = x;
540 if (x_on_path < 0.0) x_on_path = 0.0;
541
542 int unused = 0;
543 // as far as I know these functions are const, they're just not marked as such
544 Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
545 Path::cut_position path_parameter;
546 if (path_parameter_list != nullptr && path_parameter_list[0].piece >= 0)
547 path_parameter = path_parameter_list[0];
548 else {
549 path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
550 path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
551 }
552 g_free(path_parameter_list);
553
554 Geom::Point point;
555 Geom::Point tangent;
556 const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
557 if (x < 0.0)
558 point += x * tangent;
559 if (x > path_length )
560 point += (x - path_length) * tangent;
561 if (vertical_text) {
562 rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
563 position[Geom::X] = point[Geom::Y] - tangent[Geom::X] * span->baseline_shift;
564 position[Geom::Y] = point[Geom::X] + tangent[Geom::Y] * span->baseline_shift;
565 } else {
566 rotation = atan2(tangent);
567 position[Geom::X] = point[Geom::X] - tangent[Geom::Y] * span->baseline_shift;
568 position[Geom::Y] = point[Geom::Y] + tangent[Geom::X] * span->baseline_shift;
569 }
570
571 } else {
572 // text is not on a path
573
574 bool last_char_is_newline = false;
575 if (it._char_index >= _characters.size()) {
576 span = &_spans.back();
577 position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_end;
578 rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
579
580 // Check if last character is new line.
581 if (_characters.back().the_char == '\n') {
582 last_char_is_newline = true;
583 position[Geom::X] = chunkAnchorPoint(it)[vertical_text ? Geom::Y : Geom::X];
584 }
585 } else {
586 span = &_spans[_characters[it._char_index].in_span];
587 position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
588 if (it._glyph_index == -1) {
589 rotation = 0.0;
590 } else if(it._glyph_index == 0) {
591 rotation = _glyphs.empty() ? 0.0 : _glyphs[0].rotation;
592 } else{
593 rotation = _glyphs[it._glyph_index - 1].rotation;
594 }
595 // the first char in a line wants to have the y of the new line, so in that case we don't switch to the previous span
596 if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
597 span = &_spans[_characters[it._char_index - 1].in_span];
598 }
599 position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift + span->y_offset;
600
601 if (last_char_is_newline) {
602 // Move cursor to empty new line.
603 double vertical_scale = _glyphs.empty() ? 1.0 : _glyphs.back().vertical_scale;
604 if (vertical_text) {
605 // Vertical text
606 position[Geom::Y] -= vertical_scale * span->line_height.emSize();
607 } else {
608 position[Geom::Y] += vertical_scale * span->line_height.emSize();
609 }
610 }
611 }
612
613 // up to now *position is the baseline point, not the final point which will be the bottom of the descent
614 double vertical_scale = _glyphs.empty() ? 1.0 : _glyphs.back().vertical_scale;
615
616 if (vertical_text) {
617 // Vertical text
618 height = vertical_scale * span->line_height.emSize();
619 rotation += M_PI / 2;
620 std::swap(position[Geom::X], position[Geom::Y]);
621 position[Geom::X] -= vertical_scale * sin(rotation) * height * 0.5;
622 position[Geom::Y] += vertical_scale * cos(rotation) * height * 0.5;
623 } else {
624 // Horizontal text
625 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
626 if (span->font) {
627 span->font->FontSlope(caret_slope_run, caret_slope_rise);
628 }
629 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
630 height = vertical_scale * (span->line_height.emSize()) / cos(caret_slope);
631 rotation += caret_slope;
632 position[Geom::X] -= sin(rotation) * vertical_scale * span->line_height.descent;
633 position[Geom::Y] += cos(rotation) * vertical_scale * span->line_height.descent;
634 }
635 }
636}
637
638bool Layout::isHidden(iterator const &it) const
639{
640 return _characters[it._char_index].line(this).hidden;
641}
642
643
644void Layout::getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator) const
645{
646 if (it._char_index >= _characters.size()) {
647 *source = nullptr;
648 return;
649 }
650 InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
651 *source = stream_item->source;
652 if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
653 InputStreamTextSource *text_source = dynamic_cast<InputStreamTextSource *>(stream_item);
654
655 // In order to return a non-const iterator in text_iterator, do the const_cast here.
656 // Note that, although ugly, it is safe because we do not write to *iterator anywhere.
657 Glib::ustring::iterator text_iter = const_cast<Glib::ustring *>(text_source->text)->begin();
658
659 unsigned char_index = it._char_index;
660 unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
661 // confusing algorithm because the iterator goes forwards while the index goes backwards.
662 // It's just that it's faster doing it that way
663 while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
664 ++text_iter;
665 char_index--;
666 }
667
668 if (text_iterator) {
669 *text_iterator = text_iter;
670 }
671 }
672}
673
675{
676 SVGLength zero_length;
677 zero_length = 0.0;
678
679 result->x.clear();
680 result->y.clear();
681 result->dx.clear();
682 result->dy.clear();
683 result->rotate.clear();
684 if (to._char_index <= from._char_index)
685 return;
686 result->dx.reserve(to._char_index - from._char_index);
687 result->dy.reserve(to._char_index - from._char_index);
688 result->rotate.reserve(to._char_index - from._char_index);
689 for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
690 if (!_characters[char_index].char_attributes.is_char_break)
691 continue;
692 if (char_index == 0)
693 continue;
694 if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
695 continue;
696
697 unsigned prev_cluster_char_index;
698 for (prev_cluster_char_index = char_index - 1 ;
699 prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
700 prev_cluster_char_index--){};
701 if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
702 // dx is zero for the first char in a chunk
703 // this algorithm works by comparing the summed widths of the glyphs with the observed
704 // difference in x coordinates of characters, and subtracting the two to produce the x kerning.
705 double glyphs_width = 0.0;
706 if (_characters[prev_cluster_char_index].in_glyph != -1)
707 for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
708 glyphs_width += _glyphs[glyph_index].advance;
709 if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
710 glyphs_width = -glyphs_width;
711
712 double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
713 - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
714 - glyphs_width;
715
716
717 InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
718 if (input_item->Type() == TEXT_SOURCE) {
719 SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
720 if (_characters[char_index].char_attributes.is_white)
721 dx -= style->word_spacing.computed * getTextLengthMultiplierDue();
722 if (_characters[char_index].char_attributes.is_cursor_position)
723 dx -= style->letter_spacing.computed * getTextLengthMultiplierDue();
725 }
726
727 if (fabs(dx) > 0.0001) {
728 result->dx.resize(char_index - from._char_index + 1, zero_length);
729 result->dx.back() = dx;
730 }
731 }
732 double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
733 if (fabs(dy) > 0.0001) {
734 result->dy.resize(char_index - from._char_index + 1, zero_length);
735 result->dy.back() = dy;
736 }
737 if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
738 result->rotate.resize(char_index - from._char_index + 1, zero_length);
739 result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
740 }
741 }
742}
743
744#define PREV_START_OF_ITEM(this_func) \
745 { \
746 _cursor_moving_vertically = false; \
747 if (_char_index == 0) return false; \
748 _char_index--; \
749 return this_func(); \
750 }
751// end of macro
752
753#define THIS_START_OF_ITEM(item_getter) \
754 { \
755 _cursor_moving_vertically = false; \
756 if (_char_index == 0) return false; \
757 unsigned original_item; \
758 if (_char_index >= _parent_layout->_characters.size()) { \
759 _char_index = _parent_layout->_characters.size() - 1; \
760 original_item = item_getter; \
761 } else { \
762 original_item = item_getter; \
763 _char_index--; \
764 } \
765 while (item_getter == original_item) { \
766 if (_char_index == 0) { \
767 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
768 return true; \
769 } \
770 _char_index--; \
771 } \
772 _char_index++; \
773 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
774 return true; \
775 }
776// end of macro
777
778#define NEXT_START_OF_ITEM(item_getter) \
779 { \
780 _cursor_moving_vertically = false; \
781 if (_char_index >= _parent_layout->_characters.size()) return false; \
782 unsigned original_item = item_getter; \
783 for( ; ; ) { \
784 _char_index++; \
785 if (_char_index == _parent_layout->_characters.size()) { \
786 _glyph_index = _parent_layout->_glyphs.size(); \
787 return false; \
788 } \
789 if (item_getter != original_item) break; \
790 } \
791 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
792 return true; \
793 }
794// end of macro
795
797 PREV_START_OF_ITEM(thisStartOfSpan);
798
799bool Layout::iterator::thisStartOfSpan()
800 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
801
802bool Layout::iterator::nextStartOfSpan()
803 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
804
805
806bool Layout::iterator::prevStartOfChunk()
807 PREV_START_OF_ITEM(thisStartOfChunk);
808
809bool Layout::iterator::thisStartOfChunk()
810 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
811
812bool Layout::iterator::nextStartOfChunk()
813 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
814
815
816bool Layout::iterator::prevStartOfLine()
817 PREV_START_OF_ITEM(thisStartOfLine);
818
819bool Layout::iterator::thisStartOfLine()
820 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
821
822bool Layout::iterator::nextStartOfLine()
823 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
824
825
826bool Layout::iterator::prevStartOfShape()
827 PREV_START_OF_ITEM(thisStartOfShape);
828
829bool Layout::iterator::thisStartOfShape()
830 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
831
832bool Layout::iterator::nextStartOfShape()
833 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
834
835
836bool Layout::iterator::prevStartOfParagraph()
837 PREV_START_OF_ITEM(thisStartOfParagraph);
838
839bool Layout::iterator::thisStartOfParagraph()
840 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
841
842bool Layout::iterator::nextStartOfParagraph()
843 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
844
845
846bool Layout::iterator::prevStartOfSource()
847 PREV_START_OF_ITEM(thisStartOfSource);
848
849bool Layout::iterator::thisStartOfSource()
850 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
851
852bool Layout::iterator::nextStartOfSource()
853 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
854
855
856bool Layout::iterator::thisEndOfLine()
857{
858 if (_char_index >= _parent_layout->_characters.size()) return false;
859 if (nextStartOfLine())
860 {
861 if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
862 return prevCursorPosition();
863 return true;
864 }
865 if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
866 return prevCursorPosition(); // for when the last paragraph is empty
867 return false;
868}
869
878
880{
881 if (!_cursor_moving_vertically)
882 beginCursorUpDown();
883 if (_char_index >= _parent_layout->_characters.size())
884 return false;
885 unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
886 if (line_index == _parent_layout->_lines.size() - 1)
887 return false; // nowhere to go
888 else
889 n = MIN (n, static_cast<int>(_parent_layout->_lines.size() - 1 - line_index));
890 if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) {
891 // switching between shapes: adjust the stored x to compensate
892 _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + n)].in_chunk].left_x
893 - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
894 }
895 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
896 if (_char_index >= _parent_layout->_characters.size())
897 _glyph_index = _parent_layout->_glyphs.size();
898 else
899 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
900 return true;
901}
902
904{
905 if (!_cursor_moving_vertically)
906 beginCursorUpDown();
907 int line_index;
908 if (_char_index >= _parent_layout->_characters.size())
909 line_index = _parent_layout->_lines.size() - 1;
910 else
911 line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
912 if (line_index <= 0)
913 return false; // nowhere to go
914 else
915 n = MIN (n, static_cast<int>(line_index));
916 if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) {
917 // switching between shapes: adjust the stored x to compensate
918 _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - n)].in_chunk].left_x
919 - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
920 }
921 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
922 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
923 return true;
924}
925
926#define NEXT_WITH_ATTRIBUTE_SET(attr) \
927 { \
928 _cursor_moving_vertically = false; \
929 for ( ; ; ) { \
930 if (_char_index + 1 >= _parent_layout->_characters.size()) { \
931 _char_index = _parent_layout->_characters.size(); \
932 _glyph_index = _parent_layout->_glyphs.size(); \
933 return false; \
934 } \
935 _char_index++; \
936 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
937 } \
938 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
939 return true; \
940 }
941// end of macro
942
943#define PREV_WITH_ATTRIBUTE_SET(attr) \
944 { \
945 _cursor_moving_vertically = false; \
946 for ( ; ; ) { \
947 if (_char_index == 0) { \
948 _glyph_index = 0; \
949 return false; \
950 } \
951 _char_index--; \
952 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
953 } \
954 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
955 return true; \
956 }
957// end of macro
958
960 NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
961
962bool Layout::iterator::prevCursorPosition()
963 PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
964
965bool Layout::iterator::nextStartOfWord()
966 NEXT_WITH_ATTRIBUTE_SET(is_word_start);
967
968bool Layout::iterator::prevStartOfWord()
969 PREV_WITH_ATTRIBUTE_SET(is_word_start);
970
971bool Layout::iterator::nextEndOfWord()
972 NEXT_WITH_ATTRIBUTE_SET(is_word_end);
973
974bool Layout::iterator::prevEndOfWord()
975 PREV_WITH_ATTRIBUTE_SET(is_word_end);
976
977bool Layout::iterator::nextStartOfSentence()
978 NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
979
980bool Layout::iterator::prevStartOfSentence()
981 PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
982
983bool Layout::iterator::nextEndOfSentence()
984 NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
985
986bool Layout::iterator::prevEndOfSentence()
987 PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
988
989bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
990{
991 // the only reason this function is so complicated is to enable visual cursor
992 // movement moving in to or out of counterdirectional runs
993 if (_parent_layout->_characters.empty()) return false;
994 unsigned old_span_index;
995 Direction old_span_direction;
996 if (_char_index >= _parent_layout->_characters.size())
997 old_span_index = _parent_layout->_spans.size() - 1;
998 else
999 old_span_index = _parent_layout->_characters[_char_index].in_span;
1000 old_span_direction = _parent_layout->_spans[old_span_index].direction;
1001 Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
1002
1003 int scan_direction;
1004 unsigned old_char_index = _char_index;
1005 if (old_span_direction != para_direction
1006 && ((_char_index == 0 && direction == para_direction)
1007 || (_char_index >= _parent_layout->_characters.size() && direction != para_direction))) {
1008 // the end of the text is actually in the middle because of reordering. Do cleverness
1009 scan_direction = direction == para_direction ? +1 : -1;
1010 } else {
1011 if (direction == old_span_direction) {
1012 if (!nextCursorPosition()) return false;
1013 } else {
1014 if (!prevCursorPosition()) return false;
1015 }
1016
1017 unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
1018 if (new_span_index == old_span_index) return true;
1019 if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
1020 // we must jump to the other end of a counterdirectional run
1021 scan_direction = direction == para_direction ? +1 : -1;
1022 } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
1023 // we might have to do a weird jump when we would have crossed a chunk/line break
1024 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
1025 return true;
1026 if (old_span_direction == para_direction)
1027 return true;
1028 scan_direction = direction == para_direction ? +1 : -1;
1029 } else
1030 return true; // same direction, same chunk: no cleverness required
1031 }
1032
1033 unsigned new_span_index = old_span_index;
1034 for ( ; ; ) {
1035 if (scan_direction > 0) {
1036 if (new_span_index == _parent_layout->_spans.size() - 1) {
1037 if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
1038 _char_index = old_char_index;
1039 return false; // the visual end is in the logical middle
1040 }
1041 break;
1042 }
1043 new_span_index++;
1044 } else {
1045 if (new_span_index == 0) {
1046 if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
1047 _char_index = old_char_index;
1048 return false; // the visual end is in the logical middle
1049 }
1050 break;
1051 }
1052 new_span_index--;
1053 }
1054 if (_parent_layout->_spans[new_span_index].direction == para_direction) {
1055 if (para_direction == old_span_direction)
1056 new_span_index -= scan_direction;
1057 break;
1058 }
1059 if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
1060 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
1061 && para_direction == old_span_direction)
1062 new_span_index -= scan_direction;
1063 break;
1064 }
1065 }
1066
1067 // found the correct span, now find the correct character
1068 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
1069 if (new_span_index > old_span_index)
1070 _char_index = _parent_layout->_spanToCharacter(new_span_index);
1071 else
1072 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
1073 } else {
1074 if (_parent_layout->_spans[new_span_index].direction != direction) {
1075 if (new_span_index >= _parent_layout->_spans.size() - 1)
1076 _char_index = _parent_layout->_characters.size();
1077 else
1078 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
1079 } else
1080 _char_index = _parent_layout->_spanToCharacter(new_span_index);
1081 }
1082 if (_char_index >= _parent_layout->_characters.size()) {
1083 _glyph_index = _parent_layout->_glyphs.size();
1084 return false;
1085 }
1086 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
1087 return _char_index != 0;
1088}
1089
1091{
1092 bool r;
1093 while ((r = _cursorLeftOrRightLocalX(direction))
1094 && !_parent_layout->_characters[_char_index].char_attributes.is_word_start){};
1095 return r;
1096}
1097
1099{
1100 Direction block_progression = _parent_layout->_blockProgression();
1101 if(block_progression == TOP_TO_BOTTOM)
1102 return prevLineCursor(n);
1103 else if(block_progression == BOTTOM_TO_TOP)
1104 return nextLineCursor(n);
1105 else
1106 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
1107}
1108
1110{
1111 Direction block_progression = _parent_layout->_blockProgression();
1112 if(block_progression == TOP_TO_BOTTOM)
1113 return nextLineCursor(n);
1114 else if(block_progression == BOTTOM_TO_TOP)
1115 return prevLineCursor(n);
1116 else
1117 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
1118}
1119
1121{
1122 Direction block_progression = _parent_layout->_blockProgression();
1123 if(block_progression == LEFT_TO_RIGHT)
1124 return prevLineCursor();
1125 else if(block_progression == RIGHT_TO_LEFT)
1126 return nextLineCursor();
1127 else
1128 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
1129}
1130
1132{
1133 Direction block_progression = _parent_layout->_blockProgression();
1134 if(block_progression == LEFT_TO_RIGHT)
1135 return nextLineCursor();
1136 else if(block_progression == RIGHT_TO_LEFT)
1137 return prevLineCursor();
1138 else
1139 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
1140}
1141
1143{
1144 Direction block_progression = _parent_layout->_blockProgression();
1145 if(block_progression == TOP_TO_BOTTOM)
1146 return prevStartOfParagraph();
1147 else if(block_progression == BOTTOM_TO_TOP)
1148 return nextStartOfParagraph();
1149 else
1150 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
1151}
1152
1154{
1155 Direction block_progression = _parent_layout->_blockProgression();
1156 if(block_progression == TOP_TO_BOTTOM)
1157 return nextStartOfParagraph();
1158 else if(block_progression == BOTTOM_TO_TOP)
1159 return prevStartOfParagraph();
1160 else
1161 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
1162}
1163
1165{
1166 Direction block_progression = _parent_layout->_blockProgression();
1167 if(block_progression == LEFT_TO_RIGHT)
1168 return prevStartOfParagraph();
1169 else if(block_progression == RIGHT_TO_LEFT)
1170 return nextStartOfParagraph();
1171 else
1172 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
1173}
1174
1176{
1177 Direction block_progression = _parent_layout->_blockProgression();
1178 if(block_progression == LEFT_TO_RIGHT)
1179 return nextStartOfParagraph();
1180 else if(block_progression == RIGHT_TO_LEFT)
1181 return prevStartOfParagraph();
1182 else
1183 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
1184}
1185
1186}//namespace Text
1187}//namespace Inkscape
1188
1189/*
1190 Local Variables:
1191 mode:c++
1192 c-file-style:"stroustrup"
1193 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1194 indent-tabs-mode:nil
1195 fill-column:99
1196 End:
1197*/
1198// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
TODO: insert short description here.
3x3 matrix representing an affine transformation.
Definition affine.h:70
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
CPoint corner(unsigned i) const
Return the n-th corner of the rectangle.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of contiguous curves, aka spline.
Definition path.h:353
void clear()
Remove all curves from the path.
Definition path.cpp:337
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
void start(Point const &p)
Definition path.cpp:426
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord x() const noexcept
Definition point.h:104
constexpr Point cw() const
Return a point like this point but rotated +90 degrees.
Definition point.h:137
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Translation by a vector.
Definition transforms.h:115
Keep track of font metrics.
Definition Layout-TNG.h:624
void max(FontMetrics const &other)
Save the larger values of ascent and descent between this and other.
virtual InputStreamItemType Type() const =0
Represents a text item in the input stream.
Definition Layout-TNG.h:700
Holds a position within the glyph output of Layout.
Definition Layout-TNG.h:981
bool _cursor_moving_vertically
index into Layout::character
unsigned _char_index
index into Layout::glyphs, or -1
double _x_coordinate
for cursor up/down movement we must maintain the x position where we started so the cursor doesn't 'd...
bool _cursorLeftOrRightLocalXByWord(Direction direction)
moves forward or backwards by until the next character with is_word_start according to the directiona...
Generates the layout for either wrapped or non-wrapped text and stores the result.
Definition Layout-TNG.h:144
iterator getLetterAt(double x, double y) const
Returns an iterator pointing to the letter whose bounding box contains the given coordinates.
void transform(Geom::Affine const &transform)
Apply the given transform to all the output presently stored in this object.
std::vector< Geom::Point > createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const
Basically uses characterBoundingBox() on all the characters from start to end and returns the union o...
void simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
Returns kerning information which could cause the current output to be exactly reproduced if the lett...
std::vector< Line > _lines
Definition Layout-TNG.h:904
void queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
Gets the ideal cursor shape for a given iterator.
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:908
iterator sourceToIterator(SPObject *source) const
Returns an iterator pointing to the first character in the output which was created from the given so...
bool isHidden(iterator const &it) const
Returns true if the text at it is hidden (i.e.
unsigned _lineToSpan(unsigned line_index) const
Definition Layout-TNG.h:911
std::vector< Chunk > _chunks
Definition Layout-TNG.h:905
Geom::Point characterAnchorPoint(iterator const &it) const
For latin text, the left side of the character, on the baseline.
iterator _cursorXOnLineToIterator(unsigned line_index, double local_x, double local_y=0) const
given an x and y coordinate and a line number, returns an iterator pointing to the closest cursor pos...
std::vector< Geom::LineSegment > _baselines
Definition Layout-TNG.h:909
Direction _blockProgression() const
The overall block-progression of the whole flow.
Definition Layout-TNG.h:754
struct Inkscape::Text::Layout::CursorShape _empty_cursor_shape
std::optional< Geom::Point > baselineAnchorPoint() const
For left aligned text, the leftmost end of the baseline For rightmost text, the rightmost....
unsigned _sourceToCharacter(unsigned source_index) const
Definition Layout-TNG.h:938
Geom::Point chunkAnchorPoint(iterator const &it) const
This is that value to apply to the x,y attributes of tspan role=line elements, and hence it takes ali...
std::vector< Paragraph > _paragraphs
Definition Layout-TNG.h:903
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 ...
Geom::Rect characterBoundingBox(iterator const &it, double *rotation=nullptr) const
Returns the box extents (not ink extents) of the given character.
void getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator=nullptr) const
Discovers where the character pointed to by it came from, by retrieving the object that was passed to...
Alignment paragraphAlignment(iterator const &it) const
Returns the actual alignment used for the paragraph containing the character pointed to by it.
double getTextLengthIncrementDue() const
Get the actual spacing increment if it's due with the current values of above stuff,...
std::vector< InputStreamItem * > _input_stream
This is our internal storage for all the stuff passed to the appendText() and appendControlCode() fun...
Definition Layout-TNG.h:745
unsigned _lineToCharacter(unsigned line_index) const
Definition Layout-TNG.h:920
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:907
Geom::OptRect glyphBoundingBox(iterator const &it, double *rotation) const
Returns the bounding box of the given glyph, and its rotation.
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:803
iterator getNearestCursorPositionTo(double x, double y) const
Returns an iterator pointing to the cursor position for a mouse click at the given coordinates.
iterator begin() const
Returns an iterator pointing at the first glyph of the flowed output.
std::vector< Span > _spans
Definition Layout-TNG.h:906
Path and its polyline approximation.
Definition Path.h:93
std::vector< PathDescr * > descr_cmd
Definition Path.h:108
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
An SVG style object.
Definition style.h:45
T< SPAttr::LETTER_SPACING, SPILengthOrNormal > letter_spacing
letter spacing (css2 16.4)
Definition style.h:153
T< SPAttr::WORD_SPACING, SPILengthOrNormal > word_spacing
word spacing (also css2 16.4)
Definition style.h:155
SVG length type.
Definition svg-length.h:22
Css & result
The data describing a single loaded font.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Infinite straight line.
Helper class to stream background task notifications as a series of messages.
The optional attributes which can be applied to a SVG text or related tag.
Definition Layout-TNG.h:184
float y_offset
relative to the start of the chunk
Definition Layout-TNG.h:861
std::shared_ptr< FontInstance > font
Definition Layout-TNG.h:857
float x_end
relative to the start of the chunk
Definition Layout-TNG.h:860
Line const & line(Layout const *l) const
Definition Layout-TNG.h:871
SPStyle - a style object for SPItem objects.
double height
std::vector< Texture > unused
Affine transformation classes.