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