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