1 /*
2 * Inkscape::Text::Layout - text layout engine output functions using iterators
3 *
4 * Authors:
5 * Richard Hughes <cyreve@users.sf.net>
6 *
7 * Copyright (C) 2005 Richard Hughes
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 */
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>
16 #include "style.h"
18 namespace Inkscape {
19 namespace Text {
21 Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x) const
22 {
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;
36 }
37 }
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;
46 }
47 }
48 if (best_char_index == -1) return iterator(this, char_index);
49 return iterator(this, best_char_index);
50 }
52 double Layout::_getChunkWidth(unsigned chunk_index) const
53 {
54 double chunk_width = 0.0;
55 unsigned span_index;
56 if (chunk_index) {
57 span_index = _lineToSpan(_chunks[chunk_index].in_line);
58 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++){};
59 } else
60 span_index = 0;
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));
63 return chunk_width;
64 }
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
78 there's a tie, the x.
79 3) search in that chunk using x-coordinate only to find the position.
80 */
81 Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const
82 {
83 if (_lines.empty()) return begin();
84 double local_x = x;
85 double local_y = y;
87 if (_path_fitted) {
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);
91 }
93 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
94 local_x = y;
95 local_y = x;
96 }
97 // stage 1:
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;
103 } else {
104 span_left = _spans[span_index].x_end;
105 span_right = _spans[span_index].x_start;
106 }
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);
112 }
113 }
115 // stage 2:
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));
127 }
128 double this_y_range;
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);
133 else
134 this_y_range = 0.0;
135 if (this_y_range <= best_y_range) {
136 if (this_y_range < best_y_range) best_x_range = DBL_MAX;
137 double this_x_range;
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);
142 else
143 this_x_range = 0.0;
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;
148 }
149 }
150 }
152 // stage 3:
153 if (best_chunk_index == -1) return begin(); // never happens
154 return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x);
155 }
157 Layout::iterator Layout::getLetterAt(double x, double y) const
158 {
159 Geom::Point point(x, y);
161 double rotation;
162 for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
163 Geom::Rect box = characterBoundingBox(it, &rotation);
164 // todo: rotation
165 if (box.contains(point)) return it;
166 }
167 return end();
168 }
170 Layout::iterator Layout::sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const
171 {
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));
188 }
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);
193 iter_text++;
194 }
195 return end(); // never happens
196 }
198 Layout::iterator Layout::sourceToIterator(void *source_cookie) const
199 {
200 return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL)));
201 }
203 Geom::OptRect Layout::glyphBoundingBox(iterator const &it, double *rotation) const
204 {
205 if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
206 return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph);
207 }
209 Geom::Point Layout::characterAnchorPoint(iterator const &it) const
210 {
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);
215 } else {
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);
221 }
222 }
224 Geom::Point Layout::chunkAnchorPoint(iterator const &it) const
225 {
226 unsigned chunk_index;
228 if (_chunks.empty())
229 return Geom::Point(0.0, 0.0);
231 if (_characters.empty())
232 chunk_index = 0;
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);
244 //centre
245 return Geom::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
246 }
248 Geom::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
249 {
250 Geom::Point top_left, bottom_right;
251 unsigned char_index = it._char_index;
253 if (_path_fitted) {
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;
260 int unused = 0;
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;
264 Geom::Point tangent;
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;
275 if (rotation)
276 *rotation = atan2(tangent[1], tangent[0]);
277 }
278 g_free(midpoint_otp);
279 } else {
280 if (it._char_index == _characters.size()) {
281 top_left[Geom::X] = bottom_right[Geom::X] = _chunks.back().left_x + _spans.back().x_end;
282 char_index--;
283 } else {
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;
288 else
289 bottom_right[Geom::X] = span_x + _characters[it._char_index + 1].x;
290 }
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;
299 } else {
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;
302 }
304 if (rotation) {
305 if (it._glyph_index == -1)
306 *rotation = 0.0;
307 else if (it._glyph_index == (int)_glyphs.size())
308 *rotation = _glyphs.back().rotation;
309 else
310 *rotation = _glyphs[it._glyph_index].rotation;
311 }
312 }
314 return Geom::Rect(top_left, bottom_right);
315 }
317 std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Matrix const &transform) const
318 {
319 std::vector<Geom::Point> quads;
320 unsigned char_index;
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;
326 } else {
327 char_index = it_end._char_index;
328 end_char_index = it_start._char_index;
329 }
330 for ( ; char_index < end_char_index ; ) {
331 if (_characters[char_index].in_glyph == -1) {
332 char_index++;
333 continue;
334 }
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();
343 char_index++;
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)
348 char_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;
351 else
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;
361 } else {
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;
364 }
365 }
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)
369 continue;
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);
375 }
376 return quads;
377 }
379 void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
380 {
381 if (_characters.empty()) {
382 position = _empty_cursor_shape.position;
383 height = _empty_cursor_shape.height;
384 rotation = _empty_cursor_shape.rotation;
385 } else {
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
389 Span const *span;
390 if (_path_fitted) {
391 // text on a path
392 double x;
393 if (it._char_index >= _characters.size()) {
394 span = &_spans.back();
395 x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
396 } else {
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];
401 }
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;
406 int unused = 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];
412 else {
413 path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
414 path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
415 }
416 g_free(path_parameter_list);
418 Geom::Point point;
419 Geom::Point tangent;
420 const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
421 if (x < 0.0)
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;
428 } else {
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;
434 } else {
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) {
438 rotation = 0.0;
439 } else if(it._glyph_index == 0) {
440 rotation = _glyphs[0].rotation;
441 } else{
442 rotation = _glyphs[it._glyph_index - 1].rotation;
443 }
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];
447 }
448 position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift;
449 }
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;
457 } else {
458 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
459 if (span->font)
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;
466 }
467 }
468 }
470 void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
471 {
472 if (it._char_index == _characters.size()) {
473 *source_cookie = NULL;
474 return;
475 }
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) {
486 ++text_iter_const;
487 char_index--;
488 }
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
492 }
493 }
495 void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
496 {
497 SVGLength zero_length;
498 zero_length = 0.0;
500 result->x.clear();
501 result->y.clear();
502 result->dx.clear();
503 result->dy.clear();
504 result->rotate.clear();
505 if (to._char_index <= from._char_index)
506 return;
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)
512 continue;
513 if (char_index == 0)
514 continue;
515 if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
516 continue;
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)
535 - glyphs_width;
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;
545 }
547 if (fabs(dx) > 0.0001) {
548 result->dx.resize(char_index - from._char_index + 1, zero_length);
549 result->dx.back() = dx;
550 }
551 }
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;
556 }
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;
560 }
561 }
562 }
564 #define PREV_START_OF_ITEM(this_func) \
565 { \
566 _cursor_moving_vertically = false; \
567 if (_char_index == 0) return false; \
568 _char_index--; \
569 return this_func(); \
570 }
571 // end of macro
573 #define THIS_START_OF_ITEM(item_getter) \
574 { \
575 _cursor_moving_vertically = false; \
576 if (_char_index == 0) return false; \
577 unsigned original_item; \
578 if (_char_index == _parent_layout->_characters.size()) { \
579 _char_index--; \
580 original_item = item_getter; \
581 } else { \
582 original_item = item_getter; \
583 _char_index--; \
584 } \
585 while (item_getter == original_item) { \
586 if (_char_index == 0) { \
587 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
588 return true; \
589 } \
590 _char_index--; \
591 } \
592 _char_index++; \
593 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
594 return true; \
595 }
596 // end of macro
598 #define NEXT_START_OF_ITEM(item_getter) \
599 { \
600 _cursor_moving_vertically = false; \
601 if (_char_index == _parent_layout->_characters.size()) return false; \
602 unsigned original_item = item_getter; \
603 for( ; ; ) { \
604 _char_index++; \
605 if (_char_index == _parent_layout->_characters.size()) { \
606 _glyph_index = _parent_layout->_glyphs.size(); \
607 return false; \
608 } \
609 if (item_getter != original_item) break; \
610 } \
611 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
612 return true; \
613 }
614 // end of macro
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()
677 {
678 if (_char_index == _parent_layout->_characters.size()) return false;
679 if (nextStartOfLine())
680 {
681 if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
682 return prevCursorPosition();
683 return true;
684 }
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
687 return false;
688 }
690 void Layout::iterator::beginCursorUpDown()
691 {
692 if (_char_index == _parent_layout->_characters.size())
693 _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
694 else
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;
697 }
699 bool Layout::iterator::nextLineCursor(int n)
700 {
701 if (!_cursor_moving_vertically)
702 beginCursorUpDown();
703 if (_char_index == _parent_layout->_characters.size())
704 return false;
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
708 else
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;
714 }
715 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
716 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
717 return true;
718 }
720 bool Layout::iterator::prevLineCursor(int n)
721 {
722 if (!_cursor_moving_vertically)
723 beginCursorUpDown();
724 unsigned line_index;
725 if (_char_index == _parent_layout->_characters.size())
726 line_index = _parent_layout->_lines.size() - 1;
727 else
728 line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
729 if (line_index == 0)
730 return false; // nowhere to go
731 else
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;
737 }
738 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
739 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
740 return true;
741 }
743 #define NEXT_WITH_ATTRIBUTE_SET(attr) \
744 { \
745 _cursor_moving_vertically = false; \
746 for ( ; ; ) { \
747 if (_char_index + 1 >= _parent_layout->_characters.size()) { \
748 _char_index = _parent_layout->_characters.size(); \
749 _glyph_index = _parent_layout->_glyphs.size(); \
750 return false; \
751 } \
752 _char_index++; \
753 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
754 } \
755 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
756 return true; \
757 }
758 // end of macro
760 #define PREV_WITH_ATTRIBUTE_SET(attr) \
761 { \
762 _cursor_moving_vertically = false; \
763 for ( ; ; ) { \
764 if (_char_index == 0) { \
765 _glyph_index = 0; \
766 return false; \
767 } \
768 _char_index--; \
769 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
770 } \
771 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
772 return true; \
773 }
774 // end of macro
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)
807 {
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;
815 else
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;
820 int scan_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;
827 } else {
828 if (direction == old_span_direction) {
829 if (!nextCursorPosition()) return false;
830 } else {
831 if (!prevCursorPosition()) return false;
832 }
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)
842 return true;
843 if (old_span_direction == para_direction)
844 return true;
845 scan_direction = direction == para_direction ? +1 : -1;
846 } else
847 return true; // same direction, same chunk: no cleverness required
848 }
850 unsigned new_span_index = old_span_index;
851 for ( ; ; ) {
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
857 }
858 break;
859 }
860 new_span_index++;
861 } else {
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
866 }
867 break;
868 }
869 new_span_index--;
870 }
871 if (_parent_layout->_spans[new_span_index].direction == para_direction) {
872 if (para_direction == old_span_direction)
873 new_span_index -= scan_direction;
874 break;
875 }
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;
880 break;
881 }
882 }
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);
888 else
889 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
890 } else {
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();
894 else
895 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
896 } else
897 _char_index = _parent_layout->_spanToCharacter(new_span_index);
898 }
899 if (_char_index == _parent_layout->_characters.size()) {
900 _glyph_index = _parent_layout->_glyphs.size();
901 return false;
902 }
903 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
904 return _char_index != 0;
905 }
907 bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
908 {
909 bool r;
910 while ((r = _cursorLeftOrRightLocalX(direction))
911 && !_parent_layout->_characters[_char_index].char_attributes.is_word_start){};
912 return r;
913 }
915 bool Layout::iterator::cursorUp(int n)
916 {
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);
922 else
923 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
924 }
926 bool Layout::iterator::cursorDown(int n)
927 {
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);
933 else
934 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
935 }
937 bool Layout::iterator::cursorLeft()
938 {
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();
944 else
945 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
946 }
948 bool Layout::iterator::cursorRight()
949 {
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();
955 else
956 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
957 }
959 bool Layout::iterator::cursorUpWithControl()
960 {
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();
966 else
967 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
968 }
970 bool Layout::iterator::cursorDownWithControl()
971 {
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();
977 else
978 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
979 }
981 bool Layout::iterator::cursorLeftWithControl()
982 {
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();
988 else
989 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
990 }
992 bool Layout::iterator::cursorRightWithControl()
993 {
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();
999 else
1000 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
1001 }
1003 }//namespace Text
1004 }//namespace Inkscape