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 "libnr/nr-matrix-translate-ops.h"
16 #include "libnr/nr-translate-rotate-ops.h"
17 #include "style.h"
19 namespace Inkscape {
20 namespace Text {
22 Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x) const
23 {
24 unsigned char_index = _lineToCharacter(line_index);
25 int best_char_index = -1;
26 double best_x_difference = DBL_MAX;
28 if (char_index == _characters.size()) return end();
29 for ( ; char_index < _characters.size() ; char_index++) {
30 if (_characters[char_index].chunk(this).in_line != line_index) break;
31 if (_characters[char_index].char_attributes.is_mandatory_break) break;
32 if (!_characters[char_index].char_attributes.is_cursor_position) continue;
33 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);
34 if (this_x_difference < best_x_difference) {
35 best_char_index = char_index;
36 best_x_difference = this_x_difference;
37 }
38 }
39 // also try the very end of a para (not lines though because the space wraps)
40 if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) {
41 double this_x_difference;
42 if (char_index == 0) this_x_difference = fabs(_spans.front().x_end + _chunks.front().left_x - local_x);
43 else this_x_difference = fabs(_characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x);
44 if (this_x_difference < best_x_difference) {
45 best_char_index = char_index;
46 best_x_difference = this_x_difference;
47 }
48 }
49 if (best_char_index == -1) return iterator(this, char_index);
50 return iterator(this, best_char_index);
51 }
53 double Layout::_getChunkWidth(unsigned chunk_index) const
54 {
55 double chunk_width = 0.0;
56 unsigned span_index;
57 if (chunk_index) {
58 span_index = _lineToSpan(_chunks[chunk_index].in_line);
59 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++);
60 } else
61 span_index = 0;
62 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++)
63 chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
64 return chunk_width;
65 }
67 /* getting the cursor position for a mouse click is not as simple as it might
68 seem. The two major problems are flows set up in multiple columns and large
69 dy adjustments such that text does not belong to the line it appears to. In
70 the worst case it's possible to have two characters on top of each other, in
71 which case the one we pick is arbitrary.
73 This is a 3-stage (2 pass) algorithm:
74 1) search all the spans to see if the point is contained in one, if so take
75 that. Note that this will collect all clicks from the current UI because
76 of how the hit detection of nrarena objects works.
77 2) if that fails, run through all the chunks finding a best guess of the one
78 the user wanted. This is the one whose y coordinate is nearest, or if
79 there's a tie, the x.
80 3) search in that chunk using x-coordinate only to find the position.
81 */
82 Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const
83 {
84 if (_lines.empty()) return begin();
85 double local_x = x;
86 double local_y = y;
88 if (_path_fitted) {
89 Path::cut_position position = const_cast<Path*>(_path_fitted)->PointToCurvilignPosition(NR::Point(x, y));
90 local_x = const_cast<Path*>(_path_fitted)->PositionToLength(position.piece, position.t);
91 return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x);
92 }
94 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
95 local_x = y;
96 local_y = x;
97 }
98 // stage 1:
99 for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
100 double span_left, span_right;
101 if (_spans[span_index].x_start < _spans[span_index].x_end) {
102 span_left = _spans[span_index].x_start;
103 span_right = _spans[span_index].x_end;
104 } else {
105 span_left = _spans[span_index].x_end;
106 span_right = _spans[span_index].x_start;
107 }
108 if ( local_x >= _chunks[_spans[span_index].in_chunk].left_x + span_left
109 && local_x <= _chunks[_spans[span_index].in_chunk].left_x + span_right
110 && local_y >= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift - _spans[span_index].line_height.ascent
111 && local_y <= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift + _spans[span_index].line_height.descent) {
112 return _cursorXOnLineToIterator(_chunks[_spans[span_index].in_chunk].in_line, local_x);
113 }
114 }
116 // stage 2:
117 unsigned span_index = 0;
118 unsigned chunk_index;
119 int best_chunk_index = -1;
120 double best_y_range = DBL_MAX;
121 double best_x_range = DBL_MAX;
122 for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) {
123 LineHeight line_height = {0.0, 0.0, 0.0};
124 double chunk_width = 0.0;
125 for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
126 line_height.max(_spans[span_index].line_height);
127 chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
128 }
129 double this_y_range;
130 if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent)
131 this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y;
132 else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent)
133 this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent);
134 else
135 this_y_range = 0.0;
136 if (this_y_range <= best_y_range) {
137 if (this_y_range < best_y_range) best_x_range = DBL_MAX;
138 double this_x_range;
139 if (local_x < _chunks[chunk_index].left_x)
140 this_x_range = _chunks[chunk_index].left_x - local_y;
141 else if (local_x > _chunks[chunk_index].left_x + chunk_width)
142 this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width);
143 else
144 this_x_range = 0.0;
145 if (this_x_range < best_x_range) {
146 best_y_range = this_y_range;
147 best_x_range = this_x_range;
148 best_chunk_index = chunk_index;
149 }
150 }
151 }
153 // stage 3:
154 if (best_chunk_index == -1) return begin(); // never happens
155 return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x);
156 }
158 Layout::iterator Layout::getLetterAt(double x, double y) const
159 {
160 NR::Point point(x, y);
162 double rotation;
163 for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
164 NR::Rect box = characterBoundingBox(it, &rotation);
165 // todo: rotation
166 if (box.contains(point)) return it;
167 }
168 return end();
169 }
171 Layout::iterator Layout::sourceToIterator(void *source_cookie, Glib::ustring::const_iterator text_iterator) const
172 {
173 unsigned source_index;
174 if (_characters.empty()) return end();
175 for (source_index = 0 ; source_index < _input_stream.size() ; source_index++)
176 if (_input_stream[source_index]->source_cookie == source_cookie) break;
177 if (source_index == _input_stream.size()) return end();
179 unsigned char_index = _sourceToCharacter(source_index);
181 if (_input_stream[source_index]->Type() != TEXT_SOURCE)
182 return iterator(this, char_index);
184 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
185 if (text_iterator <= text_source->text_begin) return iterator(this, char_index);
186 if (text_iterator >= text_source->text_end) {
187 if (source_index == _input_stream.size() - 1) return end();
188 return iterator(this, _sourceToCharacter(source_index + 1));
189 }
190 Glib::ustring::const_iterator iter_text = text_source->text_begin;
191 for ( ; char_index < _characters.size() ; char_index++) {
192 if (iter_text == text_iterator)
193 return iterator(this, char_index);
194 iter_text++;
195 }
196 return end(); // never happens
197 }
199 Layout::iterator Layout::sourceToIterator(void *source_cookie) const
200 {
201 return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL)));
202 }
204 NR::Maybe<NR::Rect> Layout::glyphBoundingBox(iterator const &it, double *rotation) const
205 {
206 if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
207 return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph);
208 }
210 NR::Point Layout::characterAnchorPoint(iterator const &it) const
211 {
212 if (_characters.empty())
213 return _empty_cursor_shape.position;
214 if (it._char_index == _characters.size()) {
215 return NR::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
216 } else {
217 return NR::Point(_characters[it._char_index].chunk(this).left_x
218 + _spans[_characters[it._char_index].in_span].x_start
219 + _characters[it._char_index].x,
220 _characters[it._char_index].line(this).baseline_y
221 + _characters[it._char_index].span(this).baseline_shift);
222 }
223 }
225 NR::Point Layout::chunkAnchorPoint(iterator const &it) const
226 {
227 unsigned chunk_index;
229 if (_chunks.empty())
230 return NR::Point(0.0, 0.0);
232 if (_characters.empty())
233 chunk_index = 0;
234 else if (it._char_index == _characters.size())
235 chunk_index = _chunks.size() - 1;
236 else chunk_index = _characters[it._char_index].span(this).in_chunk;
238 Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
239 if (alignment == LEFT || alignment == FULL)
240 return NR::Point(_chunks[chunk_index].left_x, _lines[chunk_index].baseline_y);
242 double chunk_width = _getChunkWidth(chunk_index);
243 if (alignment == RIGHT)
244 return NR::Point(_chunks[chunk_index].left_x + chunk_width, _lines[chunk_index].baseline_y);
245 //centre
246 return NR::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
247 }
249 NR::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
250 {
251 NR::Point top_left, bottom_right;
252 unsigned char_index = it._char_index;
254 if (_path_fitted) {
255 double cluster_half_width = 0.0;
256 for (int glyph_index = _characters[char_index].in_glyph ; _glyphs[glyph_index].in_character == char_index ; glyph_index++)
257 cluster_half_width += _glyphs[glyph_index].width;
258 cluster_half_width *= 0.5;
260 double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
261 int unused = 0;
262 Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
263 if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) {
264 NR::Point midpoint;
265 NR::Point tangent;
266 Span const &span = _characters[char_index].span(this);
268 const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
269 top_left[NR::X] = midpoint[NR::X] - cluster_half_width;
270 top_left[NR::Y] = midpoint[NR::Y] - span.line_height.ascent;
271 bottom_right[NR::X] = midpoint[NR::X] + cluster_half_width;
272 bottom_right[NR::Y] = midpoint[NR::Y] + span.line_height.descent;
273 NR::Point normal = tangent.cw();
274 top_left += span.baseline_shift * normal;
275 bottom_right += span.baseline_shift * normal;
276 if (rotation)
277 *rotation = atan2(tangent[1], tangent[0]);
278 }
279 g_free(midpoint_otp);
280 } else {
281 if (it._char_index == _characters.size()) {
282 top_left[NR::X] = bottom_right[NR::X] = _chunks.back().left_x + _spans.back().x_end;
283 char_index--;
284 } else {
285 double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
286 top_left[NR::X] = span_x + _characters[it._char_index].x;
287 if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
288 bottom_right[NR::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
289 else
290 bottom_right[NR::X] = span_x + _characters[it._char_index + 1].x;
291 }
293 double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
294 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
295 double span_height = _spans[_characters[char_index].in_span].line_height.ascent + _spans[_characters[char_index].in_span].line_height.descent;
296 top_left[NR::Y] = top_left[NR::X];
297 top_left[NR::X] = baseline_y - span_height * 0.5;
298 bottom_right[NR::Y] = bottom_right[NR::X];
299 bottom_right[NR::X] = baseline_y + span_height * 0.5;
300 } else {
301 top_left[NR::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
302 bottom_right[NR::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
303 }
305 if (rotation) {
306 if (it._glyph_index == -1)
307 *rotation = 0.0;
308 else if (it._glyph_index == (int)_glyphs.size())
309 *rotation = _glyphs.back().rotation;
310 else
311 *rotation = _glyphs[it._glyph_index].rotation;
312 }
313 }
315 return NR::Rect(top_left, bottom_right);
316 }
318 std::vector<NR::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix const &transform) const
319 {
320 std::vector<NR::Point> quads;
321 unsigned char_index;
322 unsigned end_char_index;
324 if (it_start._char_index < it_end._char_index) {
325 char_index = it_start._char_index;
326 end_char_index = it_end._char_index;
327 } else {
328 char_index = it_end._char_index;
329 end_char_index = it_start._char_index;
330 }
331 for ( ; char_index < end_char_index ; ) {
332 if (_characters[char_index].in_glyph == -1) {
333 char_index++;
334 continue;
335 }
336 double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
337 unsigned span_index = _characters[char_index].in_span;
339 NR::Point top_left, bottom_right;
340 if (_path_fitted || char_rotation != 0.0) {
341 NR::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
342 top_left = box.min();
343 bottom_right = box.max();
344 char_index++;
345 } else { // for straight text we can be faster by combining all the character boxes in a span into one box
346 double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
347 top_left[NR::X] = span_x + _characters[char_index].x;
348 while (char_index < end_char_index && _characters[char_index].in_span == span_index)
349 char_index++;
350 if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
351 bottom_right[NR::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
352 else
353 bottom_right[NR::X] = span_x + _characters[char_index].x;
355 double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
356 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
357 double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent;
358 top_left[NR::Y] = top_left[NR::X];
359 top_left[NR::X] = baseline_y - span_height * 0.5;
360 bottom_right[NR::Y] = bottom_right[NR::X];
361 bottom_right[NR::X] = baseline_y + span_height * 0.5;
362 } else {
363 top_left[NR::Y] = baseline_y - _spans[span_index].line_height.ascent;
364 bottom_right[NR::Y] = baseline_y + _spans[span_index].line_height.descent;
365 }
366 }
368 NR::Rect char_box(top_left, bottom_right);
369 if (char_box.extent(NR::X) == 0.0 || char_box.extent(NR::Y) == 0.0)
370 continue;
371 NR::Point center_of_rotation((top_left[NR::X] + bottom_right[NR::X]) * 0.5,
372 top_left[NR::Y] + _spans[span_index].line_height.ascent);
373 NR::Matrix total_transform = NR::translate(-center_of_rotation) * NR::rotate(char_rotation) * NR::translate(center_of_rotation) * transform;
374 for(int i = 0; i < 4; i ++)
375 quads.push_back(char_box.corner(i) * total_transform);
376 }
377 return quads;
378 }
380 void Layout::queryCursorShape(iterator const &it, NR::Point *position, double *height, double *rotation) const
381 {
382 if (_characters.empty()) {
383 *position = _empty_cursor_shape.position;
384 *height = _empty_cursor_shape.height;
385 *rotation = _empty_cursor_shape.rotation;
386 } else {
387 // we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
388 // this means x & rotation are the current values but y & height belong to the previous character.
389 // this isn't quite right because dx attributes will be moved along, but it's good enough
390 Span const *span;
391 if (_path_fitted) {
392 // text on a path
393 double x;
394 if (it._char_index >= _characters.size()) {
395 span = &_spans.back();
396 x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
397 } else {
398 span = &_spans[_characters[it._char_index].in_span];
399 x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
400 if (it._char_index != 0)
401 span = &_spans[_characters[it._char_index - 1].in_span];
402 }
403 double path_length = const_cast<Path*>(_path_fitted)->Length();
404 double x_on_path = x;
405 if (x_on_path < 0.0) x_on_path = 0.0;
407 int unused = 0;
408 // as far as I know these functions are const, they're just not marked as such
409 Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
410 Path::cut_position path_parameter;
411 if (path_parameter_list != NULL && path_parameter_list[0].piece >= 0)
412 path_parameter = path_parameter_list[0];
413 else {
414 path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
415 path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
416 }
417 g_free(path_parameter_list);
419 NR::Point point;
420 NR::Point tangent;
421 const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
422 if (x < 0.0)
423 point += x * tangent;
424 if (x > path_length )
425 point += (x - path_length) * tangent;
426 *rotation = atan2(tangent);
427 (*position)[NR::X] = point[NR::X] - tangent[NR::Y] * span->baseline_shift;
428 (*position)[NR::Y] = point[NR::Y] + tangent[NR::X] * span->baseline_shift;
429 } else {
430 // text is not on a path
431 if (it._char_index >= _characters.size()) {
432 span = &_spans.back();
433 (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_end;
434 *rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
435 } else {
436 span = &_spans[_characters[it._char_index].in_span];
437 (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
438 if (it._glyph_index == -1) *rotation = 0.0;
439 else if(it._glyph_index == 0) *rotation = _glyphs[0].rotation;
440 else *rotation = _glyphs[it._glyph_index - 1].rotation;
441 // 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
442 if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
443 span = &_spans[_characters[it._char_index - 1].in_span];
444 }
445 (*position)[NR::Y] = span->line(this).baseline_y + span->baseline_shift;
446 }
447 // up to now *position is the baseline point, not the final point which will be the bottom of the descent
448 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
449 *height = span->line_height.ascent + span->line_height.descent;
450 *rotation += M_PI / 2;
451 std::swap((*position)[NR::X], (*position)[NR::Y]);
452 (*position)[NR::X] -= sin(*rotation) * *height * 0.5;
453 (*position)[NR::Y] += cos(*rotation) * *height * 0.5;
454 } else {
455 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
456 if (span->font)
457 const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise);
458 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
459 *height = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope);
460 *rotation += caret_slope;
461 (*position)[NR::X] -= sin(*rotation) * span->line_height.descent;
462 (*position)[NR::Y] += cos(*rotation) * span->line_height.descent;
463 }
464 }
465 }
467 void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
468 {
469 if (it._char_index == _characters.size()) {
470 *source_cookie = NULL;
471 return;
472 }
473 InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
474 *source_cookie = stream_item->source_cookie;
475 if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
476 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(stream_item);
477 Glib::ustring::const_iterator text_iter_const = text_source->text_begin;
478 unsigned char_index = it._char_index;
479 unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
480 // confusing algorithm because the iterator goes forwards while the index goes backwards.
481 // It's just that it's faster doing it that way
482 while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
483 ++text_iter_const;
484 char_index--;
485 }
486 text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base());
487 *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()))));
488 // the caller owns the string, so they're going to want a non-const iterator
489 }
490 }
492 void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
493 {
494 SVGLength zero_length;
495 zero_length = 0.0;
497 result->x.clear();
498 result->y.clear();
499 result->dx.clear();
500 result->dy.clear();
501 result->rotate.clear();
502 if (to._char_index <= from._char_index)
503 return;
504 result->dx.reserve(to._char_index - from._char_index);
505 result->dy.reserve(to._char_index - from._char_index);
506 result->rotate.reserve(to._char_index - from._char_index);
507 for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
508 if (!_characters[char_index].char_attributes.is_char_break)
509 continue;
510 if (char_index == 0)
511 continue;
512 if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
513 continue;
515 unsigned prev_cluster_char_index;
516 for (prev_cluster_char_index = char_index - 1 ;
517 prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
518 prev_cluster_char_index--);
519 if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
520 // dx is zero for the first char in a chunk
521 // this algorithm works by comparing the summed widths of the glyphs with the observed
522 // difference in x coordinates of characters, and subtracting the two to produce the x kerning.
523 double glyphs_width = 0.0;
524 if (_characters[prev_cluster_char_index].in_glyph != -1)
525 for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
526 glyphs_width += _glyphs[glyph_index].width;
527 if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
528 glyphs_width = -glyphs_width;
530 double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
531 - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
532 - glyphs_width;
535 InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
536 if (input_item->Type() == TEXT_SOURCE) {
537 SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
538 if (_characters[char_index].char_attributes.is_white)
539 dx -= style->word_spacing.computed;
540 if (_characters[char_index].char_attributes.is_cursor_position)
541 dx -= style->letter_spacing.computed;
542 }
544 if (fabs(dx) > 0.0001) {
545 result->dx.resize(char_index - from._char_index + 1, zero_length);
546 result->dx.back() = dx;
547 }
548 }
549 double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
550 if (fabs(dy) > 0.0001) {
551 result->dy.resize(char_index - from._char_index + 1, zero_length);
552 result->dy.back() = dy;
553 }
554 if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
555 result->rotate.resize(char_index - from._char_index + 1, zero_length);
556 result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
557 }
558 }
559 }
561 #define PREV_START_OF_ITEM(this_func) \
562 { \
563 _cursor_moving_vertically = false; \
564 if (_char_index == 0) return false; \
565 _char_index--; \
566 return this_func(); \
567 }
568 // end of macro
570 #define THIS_START_OF_ITEM(item_getter) \
571 { \
572 _cursor_moving_vertically = false; \
573 if (_char_index == 0) return false; \
574 unsigned original_item; \
575 if (_char_index == _parent_layout->_characters.size()) { \
576 _char_index--; \
577 original_item = item_getter; \
578 } else { \
579 original_item = item_getter; \
580 _char_index--; \
581 } \
582 while (item_getter == original_item) { \
583 if (_char_index == 0) { \
584 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
585 return true; \
586 } \
587 _char_index--; \
588 } \
589 _char_index++; \
590 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
591 return true; \
592 }
593 // end of macro
595 #define NEXT_START_OF_ITEM(item_getter) \
596 { \
597 _cursor_moving_vertically = false; \
598 if (_char_index == _parent_layout->_characters.size()) return false; \
599 unsigned original_item = item_getter; \
600 for( ; ; ) { \
601 _char_index++; \
602 if (_char_index == _parent_layout->_characters.size()) { \
603 _glyph_index = _parent_layout->_glyphs.size(); \
604 return false; \
605 } \
606 if (item_getter != original_item) break; \
607 } \
608 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
609 return true; \
610 }
611 // end of macro
613 bool Layout::iterator::prevStartOfSpan()
614 PREV_START_OF_ITEM(thisStartOfSpan);
616 bool Layout::iterator::thisStartOfSpan()
617 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
619 bool Layout::iterator::nextStartOfSpan()
620 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
623 bool Layout::iterator::prevStartOfChunk()
624 PREV_START_OF_ITEM(thisStartOfChunk);
626 bool Layout::iterator::thisStartOfChunk()
627 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
629 bool Layout::iterator::nextStartOfChunk()
630 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
633 bool Layout::iterator::prevStartOfLine()
634 PREV_START_OF_ITEM(thisStartOfLine);
636 bool Layout::iterator::thisStartOfLine()
637 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
639 bool Layout::iterator::nextStartOfLine()
640 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
643 bool Layout::iterator::prevStartOfShape()
644 PREV_START_OF_ITEM(thisStartOfShape);
646 bool Layout::iterator::thisStartOfShape()
647 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
649 bool Layout::iterator::nextStartOfShape()
650 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
653 bool Layout::iterator::prevStartOfParagraph()
654 PREV_START_OF_ITEM(thisStartOfParagraph);
656 bool Layout::iterator::thisStartOfParagraph()
657 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
659 bool Layout::iterator::nextStartOfParagraph()
660 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
663 bool Layout::iterator::prevStartOfSource()
664 PREV_START_OF_ITEM(thisStartOfSource);
666 bool Layout::iterator::thisStartOfSource()
667 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
669 bool Layout::iterator::nextStartOfSource()
670 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
673 bool Layout::iterator::thisEndOfLine()
674 {
675 if (_char_index == _parent_layout->_characters.size()) return false;
676 if (nextStartOfLine())
677 {
678 if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
679 return prevCursorPosition();
680 return true;
681 }
682 if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
683 return prevCursorPosition(); // for when the last paragraph is empty
684 return false;
685 }
687 void Layout::iterator::beginCursorUpDown()
688 {
689 if (_char_index == _parent_layout->_characters.size())
690 _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
691 else
692 _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;
693 _cursor_moving_vertically = true;
694 }
696 bool Layout::iterator::nextLineCursor(int n)
697 {
698 if (!_cursor_moving_vertically)
699 beginCursorUpDown();
700 if (_char_index == _parent_layout->_characters.size())
701 return false;
702 unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
703 if (line_index == _parent_layout->_lines.size() - 1)
704 return false; // nowhere to go
705 else
706 n = MIN (n, _parent_layout->_lines.size() - 1 - line_index);
707 if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) {
708 // switching between shapes: adjust the stored x to compensate
709 _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + n)].in_chunk].left_x
710 - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
711 }
712 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
713 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
714 return true;
715 }
717 bool Layout::iterator::prevLineCursor(int n)
718 {
719 if (!_cursor_moving_vertically)
720 beginCursorUpDown();
721 unsigned line_index;
722 if (_char_index == _parent_layout->_characters.size())
723 line_index = _parent_layout->_lines.size() - 1;
724 else
725 line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
726 if (line_index == 0)
727 return false; // nowhere to go
728 else
729 n = MIN (n, line_index);
730 if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) {
731 // switching between shapes: adjust the stored x to compensate
732 _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - n)].in_chunk].left_x
733 - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
734 }
735 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
736 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
737 return true;
738 }
740 #define NEXT_WITH_ATTRIBUTE_SET(attr) \
741 { \
742 _cursor_moving_vertically = false; \
743 for ( ; ; ) { \
744 if (_char_index + 1 >= _parent_layout->_characters.size()) { \
745 _char_index = _parent_layout->_characters.size(); \
746 _glyph_index = _parent_layout->_glyphs.size(); \
747 return false; \
748 } \
749 _char_index++; \
750 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
751 } \
752 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
753 return true; \
754 }
755 // end of macro
757 #define PREV_WITH_ATTRIBUTE_SET(attr) \
758 { \
759 _cursor_moving_vertically = false; \
760 for ( ; ; ) { \
761 if (_char_index == 0) { \
762 _glyph_index = 0; \
763 return false; \
764 } \
765 _char_index--; \
766 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
767 } \
768 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
769 return true; \
770 }
771 // end of macro
773 bool Layout::iterator::nextCursorPosition()
774 NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
776 bool Layout::iterator::prevCursorPosition()
777 PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
779 bool Layout::iterator::nextStartOfWord()
780 NEXT_WITH_ATTRIBUTE_SET(is_word_start);
782 bool Layout::iterator::prevStartOfWord()
783 PREV_WITH_ATTRIBUTE_SET(is_word_start);
785 bool Layout::iterator::nextEndOfWord()
786 NEXT_WITH_ATTRIBUTE_SET(is_word_end);
788 bool Layout::iterator::prevEndOfWord()
789 PREV_WITH_ATTRIBUTE_SET(is_word_end);
791 bool Layout::iterator::nextStartOfSentence()
792 NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
794 bool Layout::iterator::prevStartOfSentence()
795 PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
797 bool Layout::iterator::nextEndOfSentence()
798 NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
800 bool Layout::iterator::prevEndOfSentence()
801 PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
803 bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
804 {
805 // the only reason this function is so complicated is to enable visual cursor
806 // movement moving in to or out of counterdirectional runs
807 if (_parent_layout->_characters.empty()) return false;
808 unsigned old_span_index;
809 Direction old_span_direction;
810 if (_char_index == _parent_layout->_characters.size())
811 old_span_index = _parent_layout->_spans.size() - 1;
812 else
813 old_span_index = _parent_layout->_characters[_char_index].in_span;
814 old_span_direction = _parent_layout->_spans[old_span_index].direction;
815 Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
817 int scan_direction;
818 unsigned old_char_index = _char_index;
819 if (old_span_direction != para_direction
820 && ((_char_index == 0 && direction == para_direction)
821 || (_char_index == _parent_layout->_characters.size() && direction != para_direction))) {
822 // the end of the text is actually in the middle because of reordering. Do cleverness
823 scan_direction = direction == para_direction ? +1 : -1;
824 } else {
825 if (direction == old_span_direction) {
826 if (!nextCursorPosition()) return false;
827 } else {
828 if (!prevCursorPosition()) return false;
829 }
831 unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
832 if (new_span_index == old_span_index) return true;
833 if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
834 // we must jump to the other end of a counterdirectional run
835 scan_direction = direction == para_direction ? +1 : -1;
836 } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
837 // we might have to do a weird jump when we would have crossed a chunk/line break
838 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
839 return true;
840 if (old_span_direction == para_direction)
841 return true;
842 scan_direction = direction == para_direction ? +1 : -1;
843 } else
844 return true; // same direction, same chunk: no cleverness required
845 }
847 unsigned new_span_index = old_span_index;
848 for ( ; ; ) {
849 if (scan_direction > 0) {
850 if (new_span_index == _parent_layout->_spans.size() - 1) {
851 if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
852 _char_index = old_char_index;
853 return false; // the visual end is in the logical middle
854 }
855 break;
856 }
857 new_span_index++;
858 } else {
859 if (new_span_index == 0) {
860 if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
861 _char_index = old_char_index;
862 return false; // the visual end is in the logical middle
863 }
864 break;
865 }
866 new_span_index--;
867 }
868 if (_parent_layout->_spans[new_span_index].direction == para_direction) {
869 if (para_direction == old_span_direction)
870 new_span_index -= scan_direction;
871 break;
872 }
873 if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
874 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
875 && para_direction == old_span_direction)
876 new_span_index -= scan_direction;
877 break;
878 }
879 }
881 // found the correct span, now find the correct character
882 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
883 if (new_span_index > old_span_index)
884 _char_index = _parent_layout->_spanToCharacter(new_span_index);
885 else
886 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
887 } else {
888 if (_parent_layout->_spans[new_span_index].direction != direction) {
889 if (new_span_index >= _parent_layout->_spans.size() - 1)
890 _char_index = _parent_layout->_characters.size();
891 else
892 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
893 } else
894 _char_index = _parent_layout->_spanToCharacter(new_span_index);
895 }
896 if (_char_index == _parent_layout->_characters.size()) {
897 _glyph_index = _parent_layout->_glyphs.size();
898 return false;
899 }
900 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
901 return _char_index != 0;
902 }
904 bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
905 {
906 bool r;
907 while ((r = _cursorLeftOrRightLocalX(direction))
908 && !_parent_layout->_characters[_char_index].char_attributes.is_word_start);
909 return r;
910 }
912 bool Layout::iterator::cursorUp(int n)
913 {
914 Direction block_progression = _parent_layout->_blockProgression();
915 if(block_progression == TOP_TO_BOTTOM)
916 return prevLineCursor(n);
917 else if(block_progression == BOTTOM_TO_TOP)
918 return nextLineCursor(n);
919 else
920 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
921 }
923 bool Layout::iterator::cursorDown(int n)
924 {
925 Direction block_progression = _parent_layout->_blockProgression();
926 if(block_progression == TOP_TO_BOTTOM)
927 return nextLineCursor(n);
928 else if(block_progression == BOTTOM_TO_TOP)
929 return prevLineCursor(n);
930 else
931 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
932 }
934 bool Layout::iterator::cursorLeft()
935 {
936 Direction block_progression = _parent_layout->_blockProgression();
937 if(block_progression == LEFT_TO_RIGHT)
938 return prevLineCursor();
939 else if(block_progression == RIGHT_TO_LEFT)
940 return nextLineCursor();
941 else
942 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
943 }
945 bool Layout::iterator::cursorRight()
946 {
947 Direction block_progression = _parent_layout->_blockProgression();
948 if(block_progression == LEFT_TO_RIGHT)
949 return nextLineCursor();
950 else if(block_progression == RIGHT_TO_LEFT)
951 return prevLineCursor();
952 else
953 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
954 }
956 bool Layout::iterator::cursorUpWithControl()
957 {
958 Direction block_progression = _parent_layout->_blockProgression();
959 if(block_progression == TOP_TO_BOTTOM)
960 return prevStartOfParagraph();
961 else if(block_progression == BOTTOM_TO_TOP)
962 return nextStartOfParagraph();
963 else
964 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
965 }
967 bool Layout::iterator::cursorDownWithControl()
968 {
969 Direction block_progression = _parent_layout->_blockProgression();
970 if(block_progression == TOP_TO_BOTTOM)
971 return nextStartOfParagraph();
972 else if(block_progression == BOTTOM_TO_TOP)
973 return prevStartOfParagraph();
974 else
975 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
976 }
978 bool Layout::iterator::cursorLeftWithControl()
979 {
980 Direction block_progression = _parent_layout->_blockProgression();
981 if(block_progression == LEFT_TO_RIGHT)
982 return prevStartOfParagraph();
983 else if(block_progression == RIGHT_TO_LEFT)
984 return nextStartOfParagraph();
985 else
986 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
987 }
989 bool Layout::iterator::cursorRightWithControl()
990 {
991 Direction block_progression = _parent_layout->_blockProgression();
992 if(block_progression == LEFT_TO_RIGHT)
993 return nextStartOfParagraph();
994 else if(block_progression == RIGHT_TO_LEFT)
995 return prevStartOfParagraph();
996 else
997 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
998 }
1000 }//namespace Text
1001 }//namespace Inkscape