2
* Inkscape::Text::Layout - text layout engine output functions using iterators
5
* Richard Hughes <cyreve@users.sf.net>
7
* Copyright (C) 2005 Richard Hughes
9
* Released under GNU GPL, read the file 'COPYING' for more information
11
#include "Layout-TNG.h"
12
#include "livarot/Path.h"
13
#include "font-instance.h"
14
#include "svg/svg-length.h"
15
#include <2geom/transforms.h>
21
Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x) const
23
unsigned char_index = _lineToCharacter(line_index);
24
int best_char_index = -1;
25
double best_x_difference = DBL_MAX;
27
if (char_index == _characters.size()) return end();
28
for ( ; char_index < _characters.size() ; char_index++) {
29
if (_characters[char_index].chunk(this).in_line != line_index) break;
30
if (_characters[char_index].char_attributes.is_mandatory_break) break;
31
if (!_characters[char_index].char_attributes.is_cursor_position) continue;
32
double this_x_difference = fabs(_characters[char_index].x + _characters[char_index].span(this).x_start + _characters[char_index].chunk(this).left_x - local_x);
33
if (this_x_difference < best_x_difference) {
34
best_char_index = char_index;
35
best_x_difference = this_x_difference;
38
// also try the very end of a para (not lines though because the space wraps)
39
if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) {
40
double this_x_difference;
41
if (char_index == 0) this_x_difference = fabs(_spans.front().x_end + _chunks.front().left_x - local_x);
42
else this_x_difference = fabs(_characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x);
43
if (this_x_difference < best_x_difference) {
44
best_char_index = char_index;
45
best_x_difference = this_x_difference;
48
if (best_char_index == -1) return iterator(this, char_index);
49
return iterator(this, best_char_index);
52
double Layout::_getChunkWidth(unsigned chunk_index) const
54
double chunk_width = 0.0;
57
span_index = _lineToSpan(_chunks[chunk_index].in_line);
58
for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++){};
61
for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++)
62
chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
66
/* getting the cursor position for a mouse click is not as simple as it might
67
seem. The two major problems are flows set up in multiple columns and large
68
dy adjustments such that text does not belong to the line it appears to. In
69
the worst case it's possible to have two characters on top of each other, in
70
which case the one we pick is arbitrary.
72
This is a 3-stage (2 pass) algorithm:
73
1) search all the spans to see if the point is contained in one, if so take
74
that. Note that this will collect all clicks from the current UI because
75
of how the hit detection of nrarena objects works.
76
2) if that fails, run through all the chunks finding a best guess of the one
77
the user wanted. This is the one whose y coordinate is nearest, or if
79
3) search in that chunk using x-coordinate only to find the position.
81
Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const
83
if (_lines.empty()) return begin();
88
Path::cut_position position = const_cast<Path*>(_path_fitted)->PointToCurvilignPosition(Geom::Point(x, y));
89
local_x = const_cast<Path*>(_path_fitted)->PositionToLength(position.piece, position.t);
90
return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x);
93
if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
98
for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
99
double span_left, span_right;
100
if (_spans[span_index].x_start < _spans[span_index].x_end) {
101
span_left = _spans[span_index].x_start;
102
span_right = _spans[span_index].x_end;
104
span_left = _spans[span_index].x_end;
105
span_right = _spans[span_index].x_start;
107
if ( local_x >= _chunks[_spans[span_index].in_chunk].left_x + span_left
108
&& local_x <= _chunks[_spans[span_index].in_chunk].left_x + span_right
109
&& local_y >= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift - _spans[span_index].line_height.ascent
110
&& local_y <= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift + _spans[span_index].line_height.descent) {
111
return _cursorXOnLineToIterator(_chunks[_spans[span_index].in_chunk].in_line, local_x);
116
unsigned span_index = 0;
117
unsigned chunk_index;
118
int best_chunk_index = -1;
119
double best_y_range = DBL_MAX;
120
double best_x_range = DBL_MAX;
121
for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) {
122
LineHeight line_height = {0.0, 0.0, 0.0};
123
double chunk_width = 0.0;
124
for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
125
line_height.max(_spans[span_index].line_height);
126
chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
129
if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent)
130
this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y;
131
else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent)
132
this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent);
135
if (this_y_range <= best_y_range) {
136
if (this_y_range < best_y_range) best_x_range = DBL_MAX;
138
if (local_x < _chunks[chunk_index].left_x)
139
this_x_range = _chunks[chunk_index].left_x - local_y;
140
else if (local_x > _chunks[chunk_index].left_x + chunk_width)
141
this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width);
144
if (this_x_range < best_x_range) {
145
best_y_range = this_y_range;
146
best_x_range = this_x_range;
147
best_chunk_index = chunk_index;
153
if (best_chunk_index == -1) return begin(); // never happens
154
return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x);
157
Layout::iterator Layout::getLetterAt(double x, double y) const
159
Geom::Point point(x, y);
162
for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
163
Geom::Rect box = characterBoundingBox(it, &rotation);
165
if (box.contains(point)) return it;
170
Layout::iterator Layout::sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const
172
unsigned source_index;
173
if (_characters.empty()) return end();
174
for (source_index = 0 ; source_index < _input_stream.size() ; source_index++)
175
if (_input_stream[source_index]->source_cookie == source_cookie) break;
176
if (source_index == _input_stream.size()) return end();
178
unsigned char_index = _sourceToCharacter(source_index);
180
if (_input_stream[source_index]->Type() != TEXT_SOURCE)
181
return iterator(this, char_index);
183
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
184
if (text_iterator <= text_source->text_begin) return iterator(this, char_index);
185
if (text_iterator >= text_source->text_end) {
186
if (source_index == _input_stream.size() - 1) return end();
187
return iterator(this, _sourceToCharacter(source_index + 1));
189
Glib::ustring::const_iterator iter_text = text_source->text_begin;
190
for ( ; char_index < _characters.size() ; char_index++) {
191
if (iter_text == text_iterator)
192
return iterator(this, char_index);
195
return end(); // never happens
198
Layout::iterator Layout::sourceToIterator(void *source_cookie) const
200
return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL)));
203
Geom::OptRect Layout::glyphBoundingBox(iterator const &it, double *rotation) const
205
if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
206
return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph);
209
Geom::Point Layout::characterAnchorPoint(iterator const &it) const
211
if (_characters.empty())
212
return _empty_cursor_shape.position;
213
if (it._char_index == _characters.size()) {
214
return Geom::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
216
return Geom::Point(_characters[it._char_index].chunk(this).left_x
217
+ _spans[_characters[it._char_index].in_span].x_start
218
+ _characters[it._char_index].x,
219
_characters[it._char_index].line(this).baseline_y
220
+ _characters[it._char_index].span(this).baseline_shift);
224
Geom::Point Layout::chunkAnchorPoint(iterator const &it) const
226
unsigned chunk_index;
229
return Geom::Point(0.0, 0.0);
231
if (_characters.empty())
233
else if (it._char_index == _characters.size())
234
chunk_index = _chunks.size() - 1;
235
else chunk_index = _characters[it._char_index].span(this).in_chunk;
237
Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
238
if (alignment == LEFT || alignment == FULL)
239
return Geom::Point(_chunks[chunk_index].left_x, _lines[chunk_index].baseline_y);
241
double chunk_width = _getChunkWidth(chunk_index);
242
if (alignment == RIGHT)
243
return Geom::Point(_chunks[chunk_index].left_x + chunk_width, _lines[chunk_index].baseline_y);
245
return Geom::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
248
Geom::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
250
Geom::Point top_left, bottom_right;
251
unsigned char_index = it._char_index;
254
double cluster_half_width = 0.0;
255
for (int glyph_index = _characters[char_index].in_glyph ; _glyphs[glyph_index].in_character == char_index ; glyph_index++)
256
cluster_half_width += _glyphs[glyph_index].width;
257
cluster_half_width *= 0.5;
259
double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
261
Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
262
if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) {
263
Geom::Point midpoint;
265
Span const &span = _characters[char_index].span(this);
267
const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
268
top_left[Geom::X] = midpoint[Geom::X] - cluster_half_width;
269
top_left[Geom::Y] = midpoint[Geom::Y] - span.line_height.ascent;
270
bottom_right[Geom::X] = midpoint[Geom::X] + cluster_half_width;
271
bottom_right[Geom::Y] = midpoint[Geom::Y] + span.line_height.descent;
272
Geom::Point normal = tangent.cw();
273
top_left += span.baseline_shift * normal;
274
bottom_right += span.baseline_shift * normal;
276
*rotation = atan2(tangent[1], tangent[0]);
278
g_free(midpoint_otp);
280
if (it._char_index == _characters.size()) {
281
top_left[Geom::X] = bottom_right[Geom::X] = _chunks.back().left_x + _spans.back().x_end;
284
double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
285
top_left[Geom::X] = span_x + _characters[it._char_index].x;
286
if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
287
bottom_right[Geom::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
289
bottom_right[Geom::X] = span_x + _characters[it._char_index + 1].x;
292
double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
293
if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
294
double span_height = _spans[_characters[char_index].in_span].line_height.ascent + _spans[_characters[char_index].in_span].line_height.descent;
295
top_left[Geom::Y] = top_left[Geom::X];
296
top_left[Geom::X] = baseline_y - span_height * 0.5;
297
bottom_right[Geom::Y] = bottom_right[Geom::X];
298
bottom_right[Geom::X] = baseline_y + span_height * 0.5;
300
top_left[Geom::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
301
bottom_right[Geom::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
305
if (it._glyph_index == -1)
307
else if (it._glyph_index == (int)_glyphs.size())
308
*rotation = _glyphs.back().rotation;
310
*rotation = _glyphs[it._glyph_index].rotation;
314
return Geom::Rect(top_left, bottom_right);
317
std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Matrix const &transform) const
319
std::vector<Geom::Point> quads;
321
unsigned end_char_index;
323
if (it_start._char_index < it_end._char_index) {
324
char_index = it_start._char_index;
325
end_char_index = it_end._char_index;
327
char_index = it_end._char_index;
328
end_char_index = it_start._char_index;
330
for ( ; char_index < end_char_index ; ) {
331
if (_characters[char_index].in_glyph == -1) {
335
double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
336
unsigned span_index = _characters[char_index].in_span;
338
Geom::Point top_left, bottom_right;
339
if (_path_fitted || char_rotation != 0.0) {
340
Geom::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
341
top_left = box.min();
342
bottom_right = box.max();
344
} else { // for straight text we can be faster by combining all the character boxes in a span into one box
345
double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
346
top_left[Geom::X] = span_x + _characters[char_index].x;
347
while (char_index < end_char_index && _characters[char_index].in_span == span_index)
349
if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
350
bottom_right[Geom::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
352
bottom_right[Geom::X] = span_x + _characters[char_index].x;
354
double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
355
if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
356
double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent;
357
top_left[Geom::Y] = top_left[Geom::X];
358
top_left[Geom::X] = baseline_y - span_height * 0.5;
359
bottom_right[Geom::Y] = bottom_right[Geom::X];
360
bottom_right[Geom::X] = baseline_y + span_height * 0.5;
362
top_left[Geom::Y] = baseline_y - _spans[span_index].line_height.ascent;
363
bottom_right[Geom::Y] = baseline_y + _spans[span_index].line_height.descent;
367
Geom::Rect char_box(top_left, bottom_right);
368
if (char_box.dimensions()[Geom::X] == 0.0 || char_box.dimensions()[Geom::Y] == 0.0)
370
Geom::Point center_of_rotation((top_left[Geom::X] + bottom_right[Geom::X]) * 0.5,
371
top_left[Geom::Y] + _spans[span_index].line_height.ascent);
372
Geom::Matrix total_transform = Geom::Translate(-center_of_rotation) * Geom::Rotate(char_rotation) * Geom::Translate(center_of_rotation) * transform;
373
for(int i = 0; i < 4; i ++)
374
quads.push_back(char_box.corner(i) * total_transform);
379
void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
381
if (_characters.empty()) {
382
position = _empty_cursor_shape.position;
383
height = _empty_cursor_shape.height;
384
rotation = _empty_cursor_shape.rotation;
386
// we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
387
// this means x & rotation are the current values but y & height belong to the previous character.
388
// this isn't quite right because dx attributes will be moved along, but it's good enough
393
if (it._char_index >= _characters.size()) {
394
span = &_spans.back();
395
x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
397
span = &_spans[_characters[it._char_index].in_span];
398
x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
399
if (it._char_index != 0)
400
span = &_spans[_characters[it._char_index - 1].in_span];
402
double path_length = const_cast<Path*>(_path_fitted)->Length();
403
double x_on_path = x;
404
if (x_on_path < 0.0) x_on_path = 0.0;
407
// as far as I know these functions are const, they're just not marked as such
408
Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
409
Path::cut_position path_parameter;
410
if (path_parameter_list != NULL && path_parameter_list[0].piece >= 0)
411
path_parameter = path_parameter_list[0];
413
path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
414
path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
416
g_free(path_parameter_list);
420
const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
422
point += x * tangent;
423
if (x > path_length )
424
point += (x - path_length) * tangent;
425
rotation = atan2(tangent);
426
position[Geom::X] = point[Geom::X] - tangent[Geom::Y] * span->baseline_shift;
427
position[Geom::Y] = point[Geom::Y] + tangent[Geom::X] * span->baseline_shift;
429
// text is not on a path
430
if (it._char_index >= _characters.size()) {
431
span = &_spans.back();
432
position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_end;
433
rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
435
span = &_spans[_characters[it._char_index].in_span];
436
position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
437
if (it._glyph_index == -1) {
439
} else if(it._glyph_index == 0) {
440
rotation = _glyphs[0].rotation;
442
rotation = _glyphs[it._glyph_index - 1].rotation;
444
// 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
445
if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
446
span = &_spans[_characters[it._char_index - 1].in_span];
448
position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift;
450
// up to now *position is the baseline point, not the final point which will be the bottom of the descent
451
if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
452
height = span->line_height.ascent + span->line_height.descent;
453
rotation += M_PI / 2;
454
std::swap(position[Geom::X], position[Geom::Y]);
455
position[Geom::X] -= sin(rotation) * height * 0.5;
456
position[Geom::Y] += cos(rotation) * height * 0.5;
458
double caret_slope_run = 0.0, caret_slope_rise = 1.0;
460
const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise);
461
double caret_slope = atan2(caret_slope_run, caret_slope_rise);
462
height = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope);
463
rotation += caret_slope;
464
position[Geom::X] -= sin(rotation) * span->line_height.descent;
465
position[Geom::Y] += cos(rotation) * span->line_height.descent;
470
void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
472
if (it._char_index == _characters.size()) {
473
*source_cookie = NULL;
476
InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
477
*source_cookie = stream_item->source_cookie;
478
if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
479
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(stream_item);
480
Glib::ustring::const_iterator text_iter_const = text_source->text_begin;
481
unsigned char_index = it._char_index;
482
unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
483
// confusing algorithm because the iterator goes forwards while the index goes backwards.
484
// It's just that it's faster doing it that way
485
while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
489
text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base());
490
*text_iterator = Glib::ustring::iterator(std::string::iterator(const_cast<char*>(&*text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base()))));
491
// the caller owns the string, so they're going to want a non-const iterator
495
void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
497
SVGLength zero_length;
504
result->rotate.clear();
505
if (to._char_index <= from._char_index)
507
result->dx.reserve(to._char_index - from._char_index);
508
result->dy.reserve(to._char_index - from._char_index);
509
result->rotate.reserve(to._char_index - from._char_index);
510
for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
511
if (!_characters[char_index].char_attributes.is_char_break)
515
if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
518
unsigned prev_cluster_char_index;
519
for (prev_cluster_char_index = char_index - 1 ;
520
prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
521
prev_cluster_char_index--){};
522
if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
523
// dx is zero for the first char in a chunk
524
// this algorithm works by comparing the summed widths of the glyphs with the observed
525
// difference in x coordinates of characters, and subtracting the two to produce the x kerning.
526
double glyphs_width = 0.0;
527
if (_characters[prev_cluster_char_index].in_glyph != -1)
528
for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
529
glyphs_width += _glyphs[glyph_index].width;
530
if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
531
glyphs_width = -glyphs_width;
533
double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
534
- _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
538
InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
539
if (input_item->Type() == TEXT_SOURCE) {
540
SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
541
if (_characters[char_index].char_attributes.is_white)
542
dx -= style->word_spacing.computed;
543
if (_characters[char_index].char_attributes.is_cursor_position)
544
dx -= style->letter_spacing.computed;
547
if (fabs(dx) > 0.0001) {
548
result->dx.resize(char_index - from._char_index + 1, zero_length);
549
result->dx.back() = dx;
552
double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
553
if (fabs(dy) > 0.0001) {
554
result->dy.resize(char_index - from._char_index + 1, zero_length);
555
result->dy.back() = dy;
557
if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
558
result->rotate.resize(char_index - from._char_index + 1, zero_length);
559
result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
564
#define PREV_START_OF_ITEM(this_func) \
566
_cursor_moving_vertically = false; \
567
if (_char_index == 0) return false; \
569
return this_func(); \
573
#define THIS_START_OF_ITEM(item_getter) \
575
_cursor_moving_vertically = false; \
576
if (_char_index == 0) return false; \
577
unsigned original_item; \
578
if (_char_index == _parent_layout->_characters.size()) { \
580
original_item = item_getter; \
582
original_item = item_getter; \
585
while (item_getter == original_item) { \
586
if (_char_index == 0) { \
587
_glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
593
_glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
598
#define NEXT_START_OF_ITEM(item_getter) \
600
_cursor_moving_vertically = false; \
601
if (_char_index == _parent_layout->_characters.size()) return false; \
602
unsigned original_item = item_getter; \
605
if (_char_index == _parent_layout->_characters.size()) { \
606
_glyph_index = _parent_layout->_glyphs.size(); \
609
if (item_getter != original_item) break; \
611
_glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
616
bool Layout::iterator::prevStartOfSpan()
617
PREV_START_OF_ITEM(thisStartOfSpan);
619
bool Layout::iterator::thisStartOfSpan()
620
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
622
bool Layout::iterator::nextStartOfSpan()
623
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
626
bool Layout::iterator::prevStartOfChunk()
627
PREV_START_OF_ITEM(thisStartOfChunk);
629
bool Layout::iterator::thisStartOfChunk()
630
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
632
bool Layout::iterator::nextStartOfChunk()
633
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
636
bool Layout::iterator::prevStartOfLine()
637
PREV_START_OF_ITEM(thisStartOfLine);
639
bool Layout::iterator::thisStartOfLine()
640
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
642
bool Layout::iterator::nextStartOfLine()
643
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
646
bool Layout::iterator::prevStartOfShape()
647
PREV_START_OF_ITEM(thisStartOfShape);
649
bool Layout::iterator::thisStartOfShape()
650
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
652
bool Layout::iterator::nextStartOfShape()
653
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
656
bool Layout::iterator::prevStartOfParagraph()
657
PREV_START_OF_ITEM(thisStartOfParagraph);
659
bool Layout::iterator::thisStartOfParagraph()
660
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
662
bool Layout::iterator::nextStartOfParagraph()
663
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
666
bool Layout::iterator::prevStartOfSource()
667
PREV_START_OF_ITEM(thisStartOfSource);
669
bool Layout::iterator::thisStartOfSource()
670
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
672
bool Layout::iterator::nextStartOfSource()
673
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
676
bool Layout::iterator::thisEndOfLine()
678
if (_char_index == _parent_layout->_characters.size()) return false;
679
if (nextStartOfLine())
681
if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
682
return prevCursorPosition();
685
if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
686
return prevCursorPosition(); // for when the last paragraph is empty
690
void Layout::iterator::beginCursorUpDown()
692
if (_char_index == _parent_layout->_characters.size())
693
_x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
695
_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;
696
_cursor_moving_vertically = true;
699
bool Layout::iterator::nextLineCursor(int n)
701
if (!_cursor_moving_vertically)
703
if (_char_index == _parent_layout->_characters.size())
705
unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
706
if (line_index == _parent_layout->_lines.size() - 1)
707
return false; // nowhere to go
709
n = MIN (n, static_cast<int>(_parent_layout->_lines.size() - 1 - line_index));
710
if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) {
711
// switching between shapes: adjust the stored x to compensate
712
_x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + n)].in_chunk].left_x
713
- _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
715
_char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
716
_glyph_index = _parent_layout->_characters[_char_index].in_glyph;
720
bool Layout::iterator::prevLineCursor(int n)
722
if (!_cursor_moving_vertically)
725
if (_char_index == _parent_layout->_characters.size())
726
line_index = _parent_layout->_lines.size() - 1;
728
line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
730
return false; // nowhere to go
732
n = MIN (n, static_cast<int>(line_index));
733
if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) {
734
// switching between shapes: adjust the stored x to compensate
735
_x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - n)].in_chunk].left_x
736
- _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
738
_char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
739
_glyph_index = _parent_layout->_characters[_char_index].in_glyph;
743
#define NEXT_WITH_ATTRIBUTE_SET(attr) \
745
_cursor_moving_vertically = false; \
747
if (_char_index + 1 >= _parent_layout->_characters.size()) { \
748
_char_index = _parent_layout->_characters.size(); \
749
_glyph_index = _parent_layout->_glyphs.size(); \
753
if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
755
_glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
760
#define PREV_WITH_ATTRIBUTE_SET(attr) \
762
_cursor_moving_vertically = false; \
764
if (_char_index == 0) { \
769
if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
771
_glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
776
bool Layout::iterator::nextCursorPosition()
777
NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
779
bool Layout::iterator::prevCursorPosition()
780
PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
782
bool Layout::iterator::nextStartOfWord()
783
NEXT_WITH_ATTRIBUTE_SET(is_word_start);
785
bool Layout::iterator::prevStartOfWord()
786
PREV_WITH_ATTRIBUTE_SET(is_word_start);
788
bool Layout::iterator::nextEndOfWord()
789
NEXT_WITH_ATTRIBUTE_SET(is_word_end);
791
bool Layout::iterator::prevEndOfWord()
792
PREV_WITH_ATTRIBUTE_SET(is_word_end);
794
bool Layout::iterator::nextStartOfSentence()
795
NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
797
bool Layout::iterator::prevStartOfSentence()
798
PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
800
bool Layout::iterator::nextEndOfSentence()
801
NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
803
bool Layout::iterator::prevEndOfSentence()
804
PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
806
bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
808
// the only reason this function is so complicated is to enable visual cursor
809
// movement moving in to or out of counterdirectional runs
810
if (_parent_layout->_characters.empty()) return false;
811
unsigned old_span_index;
812
Direction old_span_direction;
813
if (_char_index == _parent_layout->_characters.size())
814
old_span_index = _parent_layout->_spans.size() - 1;
816
old_span_index = _parent_layout->_characters[_char_index].in_span;
817
old_span_direction = _parent_layout->_spans[old_span_index].direction;
818
Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
821
unsigned old_char_index = _char_index;
822
if (old_span_direction != para_direction
823
&& ((_char_index == 0 && direction == para_direction)
824
|| (_char_index == _parent_layout->_characters.size() && direction != para_direction))) {
825
// the end of the text is actually in the middle because of reordering. Do cleverness
826
scan_direction = direction == para_direction ? +1 : -1;
828
if (direction == old_span_direction) {
829
if (!nextCursorPosition()) return false;
831
if (!prevCursorPosition()) return false;
834
unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
835
if (new_span_index == old_span_index) return true;
836
if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
837
// we must jump to the other end of a counterdirectional run
838
scan_direction = direction == para_direction ? +1 : -1;
839
} else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
840
// we might have to do a weird jump when we would have crossed a chunk/line break
841
if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
843
if (old_span_direction == para_direction)
845
scan_direction = direction == para_direction ? +1 : -1;
847
return true; // same direction, same chunk: no cleverness required
850
unsigned new_span_index = old_span_index;
852
if (scan_direction > 0) {
853
if (new_span_index == _parent_layout->_spans.size() - 1) {
854
if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
855
_char_index = old_char_index;
856
return false; // the visual end is in the logical middle
862
if (new_span_index == 0) {
863
if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
864
_char_index = old_char_index;
865
return false; // the visual end is in the logical middle
871
if (_parent_layout->_spans[new_span_index].direction == para_direction) {
872
if (para_direction == old_span_direction)
873
new_span_index -= scan_direction;
876
if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
877
if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
878
&& para_direction == old_span_direction)
879
new_span_index -= scan_direction;
884
// found the correct span, now find the correct character
885
if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
886
if (new_span_index > old_span_index)
887
_char_index = _parent_layout->_spanToCharacter(new_span_index);
889
_char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
891
if (_parent_layout->_spans[new_span_index].direction != direction) {
892
if (new_span_index >= _parent_layout->_spans.size() - 1)
893
_char_index = _parent_layout->_characters.size();
895
_char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
897
_char_index = _parent_layout->_spanToCharacter(new_span_index);
899
if (_char_index == _parent_layout->_characters.size()) {
900
_glyph_index = _parent_layout->_glyphs.size();
903
_glyph_index = _parent_layout->_characters[_char_index].in_glyph;
904
return _char_index != 0;
907
bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
910
while ((r = _cursorLeftOrRightLocalX(direction))
911
&& !_parent_layout->_characters[_char_index].char_attributes.is_word_start){};
915
bool Layout::iterator::cursorUp(int n)
917
Direction block_progression = _parent_layout->_blockProgression();
918
if(block_progression == TOP_TO_BOTTOM)
919
return prevLineCursor(n);
920
else if(block_progression == BOTTOM_TO_TOP)
921
return nextLineCursor(n);
923
return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
926
bool Layout::iterator::cursorDown(int n)
928
Direction block_progression = _parent_layout->_blockProgression();
929
if(block_progression == TOP_TO_BOTTOM)
930
return nextLineCursor(n);
931
else if(block_progression == BOTTOM_TO_TOP)
932
return prevLineCursor(n);
934
return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
937
bool Layout::iterator::cursorLeft()
939
Direction block_progression = _parent_layout->_blockProgression();
940
if(block_progression == LEFT_TO_RIGHT)
941
return prevLineCursor();
942
else if(block_progression == RIGHT_TO_LEFT)
943
return nextLineCursor();
945
return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
948
bool Layout::iterator::cursorRight()
950
Direction block_progression = _parent_layout->_blockProgression();
951
if(block_progression == LEFT_TO_RIGHT)
952
return nextLineCursor();
953
else if(block_progression == RIGHT_TO_LEFT)
954
return prevLineCursor();
956
return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
959
bool Layout::iterator::cursorUpWithControl()
961
Direction block_progression = _parent_layout->_blockProgression();
962
if(block_progression == TOP_TO_BOTTOM)
963
return prevStartOfParagraph();
964
else if(block_progression == BOTTOM_TO_TOP)
965
return nextStartOfParagraph();
967
return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
970
bool Layout::iterator::cursorDownWithControl()
972
Direction block_progression = _parent_layout->_blockProgression();
973
if(block_progression == TOP_TO_BOTTOM)
974
return nextStartOfParagraph();
975
else if(block_progression == BOTTOM_TO_TOP)
976
return prevStartOfParagraph();
978
return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
981
bool Layout::iterator::cursorLeftWithControl()
983
Direction block_progression = _parent_layout->_blockProgression();
984
if(block_progression == LEFT_TO_RIGHT)
985
return prevStartOfParagraph();
986
else if(block_progression == RIGHT_TO_LEFT)
987
return nextStartOfParagraph();
989
return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
992
bool Layout::iterator::cursorRightWithControl()
994
Direction block_progression = _parent_layout->_blockProgression();
995
if(block_progression == LEFT_TO_RIGHT)
996
return nextStartOfParagraph();
997
else if(block_progression == RIGHT_TO_LEFT)
998
return prevStartOfParagraph();
1000
return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
1004
}//namespace Inkscape