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 boost::optional<Geom::Point> Layout::baselineAnchorPoint() const
225 {
226 iterator pos = this->begin();
227 Geom::Point left_pt = this->characterAnchorPoint(pos);
228 pos.thisEndOfLine();
229 Geom::Point right_pt = this->characterAnchorPoint(pos);
231 if (this->_blockProgression() == LEFT_TO_RIGHT || this->_blockProgression() == RIGHT_TO_LEFT) {
232 left_pt = Geom::Point(left_pt[Geom::Y], left_pt[Geom::X]);
233 right_pt = Geom::Point(right_pt[Geom::Y], right_pt[Geom::X]);
234 }
236 switch (this->paragraphAlignment(pos)) {
237 case LEFT:
238 case FULL:
239 return left_pt;
240 break;
241 case CENTER:
242 return (left_pt + right_pt)/2; // middle point
243 break;
244 case RIGHT:
245 return right_pt;
246 break;
247 default:
248 return boost::optional<Geom::Point>();
249 break;
250 }
251 }
253 Geom::Point Layout::chunkAnchorPoint(iterator const &it) const
254 {
255 unsigned chunk_index;
257 if (_chunks.empty())
258 return Geom::Point(0.0, 0.0);
260 if (_characters.empty())
261 chunk_index = 0;
262 else if (it._char_index == _characters.size())
263 chunk_index = _chunks.size() - 1;
264 else chunk_index = _characters[it._char_index].span(this).in_chunk;
266 Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
267 if (alignment == LEFT || alignment == FULL)
268 return Geom::Point(_chunks[chunk_index].left_x, _lines[chunk_index].baseline_y);
270 double chunk_width = _getChunkWidth(chunk_index);
271 if (alignment == RIGHT)
272 return Geom::Point(_chunks[chunk_index].left_x + chunk_width, _lines[chunk_index].baseline_y);
273 //centre
274 return Geom::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
275 }
277 Geom::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
278 {
279 Geom::Point top_left, bottom_right;
280 unsigned char_index = it._char_index;
282 if (_path_fitted) {
283 double cluster_half_width = 0.0;
284 for (int glyph_index = _characters[char_index].in_glyph ; _glyphs[glyph_index].in_character == char_index ; glyph_index++)
285 cluster_half_width += _glyphs[glyph_index].width;
286 cluster_half_width *= 0.5;
288 double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
289 int unused = 0;
290 Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
291 if (midpoint_offset >= 0.0 && midpoint_otp != NULL && midpoint_otp[0].piece >= 0) {
292 Geom::Point midpoint;
293 Geom::Point tangent;
294 Span const &span = _characters[char_index].span(this);
296 const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
297 top_left[Geom::X] = midpoint[Geom::X] - cluster_half_width;
298 top_left[Geom::Y] = midpoint[Geom::Y] - span.line_height.ascent;
299 bottom_right[Geom::X] = midpoint[Geom::X] + cluster_half_width;
300 bottom_right[Geom::Y] = midpoint[Geom::Y] + span.line_height.descent;
301 Geom::Point normal = tangent.cw();
302 top_left += span.baseline_shift * normal;
303 bottom_right += span.baseline_shift * normal;
304 if (rotation)
305 *rotation = atan2(tangent[1], tangent[0]);
306 }
307 g_free(midpoint_otp);
308 } else {
309 if (it._char_index == _characters.size()) {
310 top_left[Geom::X] = bottom_right[Geom::X] = _chunks.back().left_x + _spans.back().x_end;
311 char_index--;
312 } else {
313 double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
314 top_left[Geom::X] = span_x + _characters[it._char_index].x;
315 if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
316 bottom_right[Geom::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
317 else
318 bottom_right[Geom::X] = span_x + _characters[it._char_index + 1].x;
319 }
321 double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
322 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
323 double span_height = _spans[_characters[char_index].in_span].line_height.ascent + _spans[_characters[char_index].in_span].line_height.descent;
324 top_left[Geom::Y] = top_left[Geom::X];
325 top_left[Geom::X] = baseline_y - span_height * 0.5;
326 bottom_right[Geom::Y] = bottom_right[Geom::X];
327 bottom_right[Geom::X] = baseline_y + span_height * 0.5;
328 } else {
329 top_left[Geom::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
330 bottom_right[Geom::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
331 }
333 if (rotation) {
334 if (it._glyph_index == -1)
335 *rotation = 0.0;
336 else if (it._glyph_index == (int)_glyphs.size())
337 *rotation = _glyphs.back().rotation;
338 else
339 *rotation = _glyphs[it._glyph_index].rotation;
340 }
341 }
343 return Geom::Rect(top_left, bottom_right);
344 }
346 std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Matrix const &transform) const
347 {
348 std::vector<Geom::Point> quads;
349 unsigned char_index;
350 unsigned end_char_index;
352 if (it_start._char_index < it_end._char_index) {
353 char_index = it_start._char_index;
354 end_char_index = it_end._char_index;
355 } else {
356 char_index = it_end._char_index;
357 end_char_index = it_start._char_index;
358 }
359 for ( ; char_index < end_char_index ; ) {
360 if (_characters[char_index].in_glyph == -1) {
361 char_index++;
362 continue;
363 }
364 double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
365 unsigned span_index = _characters[char_index].in_span;
367 Geom::Point top_left, bottom_right;
368 if (_path_fitted || char_rotation != 0.0) {
369 Geom::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
370 top_left = box.min();
371 bottom_right = box.max();
372 char_index++;
373 } else { // for straight text we can be faster by combining all the character boxes in a span into one box
374 double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
375 top_left[Geom::X] = span_x + _characters[char_index].x;
376 while (char_index < end_char_index && _characters[char_index].in_span == span_index)
377 char_index++;
378 if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
379 bottom_right[Geom::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
380 else
381 bottom_right[Geom::X] = span_x + _characters[char_index].x;
383 double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
384 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
385 double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent;
386 top_left[Geom::Y] = top_left[Geom::X];
387 top_left[Geom::X] = baseline_y - span_height * 0.5;
388 bottom_right[Geom::Y] = bottom_right[Geom::X];
389 bottom_right[Geom::X] = baseline_y + span_height * 0.5;
390 } else {
391 top_left[Geom::Y] = baseline_y - _spans[span_index].line_height.ascent;
392 bottom_right[Geom::Y] = baseline_y + _spans[span_index].line_height.descent;
393 }
394 }
396 Geom::Rect char_box(top_left, bottom_right);
397 if (char_box.dimensions()[Geom::X] == 0.0 || char_box.dimensions()[Geom::Y] == 0.0)
398 continue;
399 Geom::Point center_of_rotation((top_left[Geom::X] + bottom_right[Geom::X]) * 0.5,
400 top_left[Geom::Y] + _spans[span_index].line_height.ascent);
401 Geom::Matrix total_transform = Geom::Translate(-center_of_rotation) * Geom::Rotate(char_rotation) * Geom::Translate(center_of_rotation) * transform;
402 for(int i = 0; i < 4; i ++)
403 quads.push_back(char_box.corner(i) * total_transform);
404 }
405 return quads;
406 }
408 void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
409 {
410 if (_characters.empty()) {
411 position = _empty_cursor_shape.position;
412 height = _empty_cursor_shape.height;
413 rotation = _empty_cursor_shape.rotation;
414 } else {
415 // we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
416 // this means x & rotation are the current values but y & height belong to the previous character.
417 // this isn't quite right because dx attributes will be moved along, but it's good enough
418 Span const *span;
419 if (_path_fitted) {
420 // text on a path
421 double x;
422 if (it._char_index >= _characters.size()) {
423 span = &_spans.back();
424 x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
425 } else {
426 span = &_spans[_characters[it._char_index].in_span];
427 x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
428 if (it._char_index != 0)
429 span = &_spans[_characters[it._char_index - 1].in_span];
430 }
431 double path_length = const_cast<Path*>(_path_fitted)->Length();
432 double x_on_path = x;
433 if (x_on_path < 0.0) x_on_path = 0.0;
435 int unused = 0;
436 // as far as I know these functions are const, they're just not marked as such
437 Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
438 Path::cut_position path_parameter;
439 if (path_parameter_list != NULL && path_parameter_list[0].piece >= 0)
440 path_parameter = path_parameter_list[0];
441 else {
442 path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
443 path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
444 }
445 g_free(path_parameter_list);
447 Geom::Point point;
448 Geom::Point tangent;
449 const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
450 if (x < 0.0)
451 point += x * tangent;
452 if (x > path_length )
453 point += (x - path_length) * tangent;
454 rotation = atan2(tangent);
455 position[Geom::X] = point[Geom::X] - tangent[Geom::Y] * span->baseline_shift;
456 position[Geom::Y] = point[Geom::Y] + tangent[Geom::X] * span->baseline_shift;
457 } else {
458 // text is not on a path
459 if (it._char_index >= _characters.size()) {
460 span = &_spans.back();
461 position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_end;
462 rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
463 } else {
464 span = &_spans[_characters[it._char_index].in_span];
465 position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
466 if (it._glyph_index == -1) {
467 rotation = 0.0;
468 } else if(it._glyph_index == 0) {
469 rotation = _glyphs[0].rotation;
470 } else{
471 rotation = _glyphs[it._glyph_index - 1].rotation;
472 }
473 // 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
474 if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
475 span = &_spans[_characters[it._char_index - 1].in_span];
476 }
477 position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift;
478 }
479 // up to now *position is the baseline point, not the final point which will be the bottom of the descent
480 if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
481 height = span->line_height.ascent + span->line_height.descent;
482 rotation += M_PI / 2;
483 std::swap(position[Geom::X], position[Geom::Y]);
484 position[Geom::X] -= sin(rotation) * height * 0.5;
485 position[Geom::Y] += cos(rotation) * height * 0.5;
486 } else {
487 double caret_slope_run = 0.0, caret_slope_rise = 1.0;
488 if (span->font)
489 const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise);
490 double caret_slope = atan2(caret_slope_run, caret_slope_rise);
491 height = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope);
492 rotation += caret_slope;
493 position[Geom::X] -= sin(rotation) * span->line_height.descent;
494 position[Geom::Y] += cos(rotation) * span->line_height.descent;
495 }
496 }
497 }
499 void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
500 {
501 if (it._char_index == _characters.size()) {
502 *source_cookie = NULL;
503 return;
504 }
505 InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
506 *source_cookie = stream_item->source_cookie;
507 if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
508 InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(stream_item);
509 Glib::ustring::const_iterator text_iter_const = text_source->text_begin;
510 unsigned char_index = it._char_index;
511 unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
512 // confusing algorithm because the iterator goes forwards while the index goes backwards.
513 // It's just that it's faster doing it that way
514 while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
515 ++text_iter_const;
516 char_index--;
517 }
518 text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base());
519 *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()))));
520 // the caller owns the string, so they're going to want a non-const iterator
521 }
522 }
524 void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
525 {
526 SVGLength zero_length;
527 zero_length = 0.0;
529 result->x.clear();
530 result->y.clear();
531 result->dx.clear();
532 result->dy.clear();
533 result->rotate.clear();
534 if (to._char_index <= from._char_index)
535 return;
536 result->dx.reserve(to._char_index - from._char_index);
537 result->dy.reserve(to._char_index - from._char_index);
538 result->rotate.reserve(to._char_index - from._char_index);
539 for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
540 if (!_characters[char_index].char_attributes.is_char_break)
541 continue;
542 if (char_index == 0)
543 continue;
544 if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
545 continue;
547 unsigned prev_cluster_char_index;
548 for (prev_cluster_char_index = char_index - 1 ;
549 prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
550 prev_cluster_char_index--){};
551 if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
552 // dx is zero for the first char in a chunk
553 // this algorithm works by comparing the summed widths of the glyphs with the observed
554 // difference in x coordinates of characters, and subtracting the two to produce the x kerning.
555 double glyphs_width = 0.0;
556 if (_characters[prev_cluster_char_index].in_glyph != -1)
557 for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
558 glyphs_width += _glyphs[glyph_index].width;
559 if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
560 glyphs_width = -glyphs_width;
562 double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
563 - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
564 - glyphs_width;
567 InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
568 if (input_item->Type() == TEXT_SOURCE) {
569 SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
570 if (_characters[char_index].char_attributes.is_white)
571 dx -= style->word_spacing.computed;
572 if (_characters[char_index].char_attributes.is_cursor_position)
573 dx -= style->letter_spacing.computed;
574 }
576 if (fabs(dx) > 0.0001) {
577 result->dx.resize(char_index - from._char_index + 1, zero_length);
578 result->dx.back() = dx;
579 }
580 }
581 double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
582 if (fabs(dy) > 0.0001) {
583 result->dy.resize(char_index - from._char_index + 1, zero_length);
584 result->dy.back() = dy;
585 }
586 if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
587 result->rotate.resize(char_index - from._char_index + 1, zero_length);
588 result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
589 }
590 }
591 }
593 #define PREV_START_OF_ITEM(this_func) \
594 { \
595 _cursor_moving_vertically = false; \
596 if (_char_index == 0) return false; \
597 _char_index--; \
598 return this_func(); \
599 }
600 // end of macro
602 #define THIS_START_OF_ITEM(item_getter) \
603 { \
604 _cursor_moving_vertically = false; \
605 if (_char_index == 0) return false; \
606 unsigned original_item; \
607 if (_char_index == _parent_layout->_characters.size()) { \
608 _char_index--; \
609 original_item = item_getter; \
610 } else { \
611 original_item = item_getter; \
612 _char_index--; \
613 } \
614 while (item_getter == original_item) { \
615 if (_char_index == 0) { \
616 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
617 return true; \
618 } \
619 _char_index--; \
620 } \
621 _char_index++; \
622 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
623 return true; \
624 }
625 // end of macro
627 #define NEXT_START_OF_ITEM(item_getter) \
628 { \
629 _cursor_moving_vertically = false; \
630 if (_char_index == _parent_layout->_characters.size()) return false; \
631 unsigned original_item = item_getter; \
632 for( ; ; ) { \
633 _char_index++; \
634 if (_char_index == _parent_layout->_characters.size()) { \
635 _glyph_index = _parent_layout->_glyphs.size(); \
636 return false; \
637 } \
638 if (item_getter != original_item) break; \
639 } \
640 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
641 return true; \
642 }
643 // end of macro
645 bool Layout::iterator::prevStartOfSpan()
646 PREV_START_OF_ITEM(thisStartOfSpan);
648 bool Layout::iterator::thisStartOfSpan()
649 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
651 bool Layout::iterator::nextStartOfSpan()
652 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
655 bool Layout::iterator::prevStartOfChunk()
656 PREV_START_OF_ITEM(thisStartOfChunk);
658 bool Layout::iterator::thisStartOfChunk()
659 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
661 bool Layout::iterator::nextStartOfChunk()
662 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
665 bool Layout::iterator::prevStartOfLine()
666 PREV_START_OF_ITEM(thisStartOfLine);
668 bool Layout::iterator::thisStartOfLine()
669 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
671 bool Layout::iterator::nextStartOfLine()
672 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
675 bool Layout::iterator::prevStartOfShape()
676 PREV_START_OF_ITEM(thisStartOfShape);
678 bool Layout::iterator::thisStartOfShape()
679 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
681 bool Layout::iterator::nextStartOfShape()
682 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
685 bool Layout::iterator::prevStartOfParagraph()
686 PREV_START_OF_ITEM(thisStartOfParagraph);
688 bool Layout::iterator::thisStartOfParagraph()
689 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
691 bool Layout::iterator::nextStartOfParagraph()
692 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
695 bool Layout::iterator::prevStartOfSource()
696 PREV_START_OF_ITEM(thisStartOfSource);
698 bool Layout::iterator::thisStartOfSource()
699 THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
701 bool Layout::iterator::nextStartOfSource()
702 NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
705 bool Layout::iterator::thisEndOfLine()
706 {
707 if (_char_index == _parent_layout->_characters.size()) return false;
708 if (nextStartOfLine())
709 {
710 if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
711 return prevCursorPosition();
712 return true;
713 }
714 if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
715 return prevCursorPosition(); // for when the last paragraph is empty
716 return false;
717 }
719 void Layout::iterator::beginCursorUpDown()
720 {
721 if (_char_index == _parent_layout->_characters.size())
722 _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
723 else
724 _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;
725 _cursor_moving_vertically = true;
726 }
728 bool Layout::iterator::nextLineCursor(int n)
729 {
730 if (!_cursor_moving_vertically)
731 beginCursorUpDown();
732 if (_char_index == _parent_layout->_characters.size())
733 return false;
734 unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
735 if (line_index == _parent_layout->_lines.size() - 1)
736 return false; // nowhere to go
737 else
738 n = MIN (n, static_cast<int>(_parent_layout->_lines.size() - 1 - line_index));
739 if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) {
740 // switching between shapes: adjust the stored x to compensate
741 _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + n)].in_chunk].left_x
742 - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
743 }
744 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
745 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
746 return true;
747 }
749 bool Layout::iterator::prevLineCursor(int n)
750 {
751 if (!_cursor_moving_vertically)
752 beginCursorUpDown();
753 unsigned line_index;
754 if (_char_index == _parent_layout->_characters.size())
755 line_index = _parent_layout->_lines.size() - 1;
756 else
757 line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
758 if (line_index == 0)
759 return false; // nowhere to go
760 else
761 n = MIN (n, static_cast<int>(line_index));
762 if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) {
763 // switching between shapes: adjust the stored x to compensate
764 _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - n)].in_chunk].left_x
765 - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
766 }
767 _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
768 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
769 return true;
770 }
772 #define NEXT_WITH_ATTRIBUTE_SET(attr) \
773 { \
774 _cursor_moving_vertically = false; \
775 for ( ; ; ) { \
776 if (_char_index + 1 >= _parent_layout->_characters.size()) { \
777 _char_index = _parent_layout->_characters.size(); \
778 _glyph_index = _parent_layout->_glyphs.size(); \
779 return false; \
780 } \
781 _char_index++; \
782 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
783 } \
784 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
785 return true; \
786 }
787 // end of macro
789 #define PREV_WITH_ATTRIBUTE_SET(attr) \
790 { \
791 _cursor_moving_vertically = false; \
792 for ( ; ; ) { \
793 if (_char_index == 0) { \
794 _glyph_index = 0; \
795 return false; \
796 } \
797 _char_index--; \
798 if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
799 } \
800 _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
801 return true; \
802 }
803 // end of macro
805 bool Layout::iterator::nextCursorPosition()
806 NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
808 bool Layout::iterator::prevCursorPosition()
809 PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
811 bool Layout::iterator::nextStartOfWord()
812 NEXT_WITH_ATTRIBUTE_SET(is_word_start);
814 bool Layout::iterator::prevStartOfWord()
815 PREV_WITH_ATTRIBUTE_SET(is_word_start);
817 bool Layout::iterator::nextEndOfWord()
818 NEXT_WITH_ATTRIBUTE_SET(is_word_end);
820 bool Layout::iterator::prevEndOfWord()
821 PREV_WITH_ATTRIBUTE_SET(is_word_end);
823 bool Layout::iterator::nextStartOfSentence()
824 NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
826 bool Layout::iterator::prevStartOfSentence()
827 PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
829 bool Layout::iterator::nextEndOfSentence()
830 NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
832 bool Layout::iterator::prevEndOfSentence()
833 PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
835 bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
836 {
837 // the only reason this function is so complicated is to enable visual cursor
838 // movement moving in to or out of counterdirectional runs
839 if (_parent_layout->_characters.empty()) return false;
840 unsigned old_span_index;
841 Direction old_span_direction;
842 if (_char_index == _parent_layout->_characters.size())
843 old_span_index = _parent_layout->_spans.size() - 1;
844 else
845 old_span_index = _parent_layout->_characters[_char_index].in_span;
846 old_span_direction = _parent_layout->_spans[old_span_index].direction;
847 Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
849 int scan_direction;
850 unsigned old_char_index = _char_index;
851 if (old_span_direction != para_direction
852 && ((_char_index == 0 && direction == para_direction)
853 || (_char_index == _parent_layout->_characters.size() && direction != para_direction))) {
854 // the end of the text is actually in the middle because of reordering. Do cleverness
855 scan_direction = direction == para_direction ? +1 : -1;
856 } else {
857 if (direction == old_span_direction) {
858 if (!nextCursorPosition()) return false;
859 } else {
860 if (!prevCursorPosition()) return false;
861 }
863 unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
864 if (new_span_index == old_span_index) return true;
865 if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
866 // we must jump to the other end of a counterdirectional run
867 scan_direction = direction == para_direction ? +1 : -1;
868 } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
869 // we might have to do a weird jump when we would have crossed a chunk/line break
870 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
871 return true;
872 if (old_span_direction == para_direction)
873 return true;
874 scan_direction = direction == para_direction ? +1 : -1;
875 } else
876 return true; // same direction, same chunk: no cleverness required
877 }
879 unsigned new_span_index = old_span_index;
880 for ( ; ; ) {
881 if (scan_direction > 0) {
882 if (new_span_index == _parent_layout->_spans.size() - 1) {
883 if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
884 _char_index = old_char_index;
885 return false; // the visual end is in the logical middle
886 }
887 break;
888 }
889 new_span_index++;
890 } else {
891 if (new_span_index == 0) {
892 if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
893 _char_index = old_char_index;
894 return false; // the visual end is in the logical middle
895 }
896 break;
897 }
898 new_span_index--;
899 }
900 if (_parent_layout->_spans[new_span_index].direction == para_direction) {
901 if (para_direction == old_span_direction)
902 new_span_index -= scan_direction;
903 break;
904 }
905 if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
906 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
907 && para_direction == old_span_direction)
908 new_span_index -= scan_direction;
909 break;
910 }
911 }
913 // found the correct span, now find the correct character
914 if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
915 if (new_span_index > old_span_index)
916 _char_index = _parent_layout->_spanToCharacter(new_span_index);
917 else
918 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
919 } else {
920 if (_parent_layout->_spans[new_span_index].direction != direction) {
921 if (new_span_index >= _parent_layout->_spans.size() - 1)
922 _char_index = _parent_layout->_characters.size();
923 else
924 _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
925 } else
926 _char_index = _parent_layout->_spanToCharacter(new_span_index);
927 }
928 if (_char_index == _parent_layout->_characters.size()) {
929 _glyph_index = _parent_layout->_glyphs.size();
930 return false;
931 }
932 _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
933 return _char_index != 0;
934 }
936 bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
937 {
938 bool r;
939 while ((r = _cursorLeftOrRightLocalX(direction))
940 && !_parent_layout->_characters[_char_index].char_attributes.is_word_start){};
941 return r;
942 }
944 bool Layout::iterator::cursorUp(int n)
945 {
946 Direction block_progression = _parent_layout->_blockProgression();
947 if(block_progression == TOP_TO_BOTTOM)
948 return prevLineCursor(n);
949 else if(block_progression == BOTTOM_TO_TOP)
950 return nextLineCursor(n);
951 else
952 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
953 }
955 bool Layout::iterator::cursorDown(int n)
956 {
957 Direction block_progression = _parent_layout->_blockProgression();
958 if(block_progression == TOP_TO_BOTTOM)
959 return nextLineCursor(n);
960 else if(block_progression == BOTTOM_TO_TOP)
961 return prevLineCursor(n);
962 else
963 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
964 }
966 bool Layout::iterator::cursorLeft()
967 {
968 Direction block_progression = _parent_layout->_blockProgression();
969 if(block_progression == LEFT_TO_RIGHT)
970 return prevLineCursor();
971 else if(block_progression == RIGHT_TO_LEFT)
972 return nextLineCursor();
973 else
974 return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
975 }
977 bool Layout::iterator::cursorRight()
978 {
979 Direction block_progression = _parent_layout->_blockProgression();
980 if(block_progression == LEFT_TO_RIGHT)
981 return nextLineCursor();
982 else if(block_progression == RIGHT_TO_LEFT)
983 return prevLineCursor();
984 else
985 return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
986 }
988 bool Layout::iterator::cursorUpWithControl()
989 {
990 Direction block_progression = _parent_layout->_blockProgression();
991 if(block_progression == TOP_TO_BOTTOM)
992 return prevStartOfParagraph();
993 else if(block_progression == BOTTOM_TO_TOP)
994 return nextStartOfParagraph();
995 else
996 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
997 }
999 bool Layout::iterator::cursorDownWithControl()
1000 {
1001 Direction block_progression = _parent_layout->_blockProgression();
1002 if(block_progression == TOP_TO_BOTTOM)
1003 return nextStartOfParagraph();
1004 else if(block_progression == BOTTOM_TO_TOP)
1005 return prevStartOfParagraph();
1006 else
1007 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
1008 }
1010 bool Layout::iterator::cursorLeftWithControl()
1011 {
1012 Direction block_progression = _parent_layout->_blockProgression();
1013 if(block_progression == LEFT_TO_RIGHT)
1014 return prevStartOfParagraph();
1015 else if(block_progression == RIGHT_TO_LEFT)
1016 return nextStartOfParagraph();
1017 else
1018 return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
1019 }
1021 bool Layout::iterator::cursorRightWithControl()
1022 {
1023 Direction block_progression = _parent_layout->_blockProgression();
1024 if(block_progression == LEFT_TO_RIGHT)
1025 return nextStartOfParagraph();
1026 else if(block_progression == RIGHT_TO_LEFT)
1027 return prevStartOfParagraph();
1028 else
1029 return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
1030 }
1032 }//namespace Text
1033 }//namespace Inkscape