summaryrefslogtreecommitdiffstats
path: root/src/libnrtype/Layout-TNG-OutIter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnrtype/Layout-TNG-OutIter.cpp')
-rw-r--r--src/libnrtype/Layout-TNG-OutIter.cpp1175
1 files changed, 1175 insertions, 0 deletions
diff --git a/src/libnrtype/Layout-TNG-OutIter.cpp b/src/libnrtype/Layout-TNG-OutIter.cpp
new file mode 100644
index 0000000..dd2123e
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-OutIter.cpp
@@ -0,0 +1,1175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine output functions using iterators
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "Layout-TNG.h"
+#include "livarot/Path.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include <2geom/transforms.h>
+#include <2geom/line.h>
+#include "style.h"
+
+namespace Inkscape {
+namespace Text {
+
+// Comment 18 Sept 2019:
+// Cursor code might be simpler if Character was turned into a proper
+// class and kept track of its absolute position and extent. This would
+// make handling multi-line text (including multi-line text using
+// 'white-space:pre') easier. This would also avoid problems where
+// 'dx','dy' moved the character a long distance from its nominal
+// position.
+
+Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x, double local_y) const
+{
+ unsigned char_index = _lineToCharacter(line_index);
+ int best_char_index = -1;
+ double best_difference = DBL_MAX;
+
+ if (char_index == _characters.size()) return end();
+ for ( ; char_index < _characters.size() ; char_index++) {
+ if (_characters[char_index].chunk(this).in_line != line_index) break;
+ //if (_characters[char_index].char_attributes.is_mandatory_break) break;
+ if (!_characters[char_index].char_attributes.is_cursor_position) continue;
+
+ double delta_x =
+ _characters[char_index].x +
+ _characters[char_index].span(this).x_start +
+ _characters[char_index].chunk(this).left_x -
+ local_x;
+
+ double delta_y =
+ _characters[char_index].span(this).y_offset +
+ _characters[char_index].line(this).baseline_y -
+ local_y;
+
+ double this_difference = std::sqrt(delta_x*delta_x + delta_y*delta_y);
+
+ if (this_difference < best_difference) {
+ best_difference = this_difference;
+ best_char_index = char_index;
+ }
+ }
+
+ // also try the very end of a para (not lines though because the space wraps)
+ if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) {
+
+ double delta_x = 0.0;
+ double delta_y = 0.0;
+
+ if (char_index == 0) {
+ delta_x = _spans.front().x_end + _chunks.front().left_x - local_x;
+ delta_y = _spans.front().y_offset + _spans.front().line(this).baseline_y - local_y;
+ } else {
+ delta_x = _characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x;
+ delta_y = _characters[char_index - 1].span(this).y_offset + _characters[char_index - 1].line(this).baseline_y - local_y;
+ }
+
+ double this_difference = std::sqrt(delta_x*delta_x + delta_y*delta_y);
+
+ if (this_difference < best_difference) {
+ best_char_index = char_index;
+ best_difference = this_difference;
+ }
+ }
+
+
+ if (best_char_index == -1) {
+ best_char_index = char_index;
+ }
+
+ if (best_char_index == _characters.size()) {
+ return end();
+ }
+
+ return iterator(this, best_char_index);
+}
+
+double Layout::_getChunkWidth(unsigned chunk_index) const
+{
+ double chunk_width = 0.0;
+ unsigned span_index;
+ if (chunk_index) {
+ span_index = _lineToSpan(_chunks[chunk_index].in_line);
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++){};
+ } else {
+ span_index = 0;
+ }
+
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
+ chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
+ }
+
+ return chunk_width;
+}
+
+/* getting the cursor position for a mouse click is not as simple as it might
+seem. The two major problems are flows set up in multiple columns and large
+dy adjustments such that text does not belong to the line it appears to. In
+the worst case it's possible to have two characters on top of each other, in
+which case the one we pick is arbitrary.
+
+This is a 3-stage (2 pass) algorithm:
+1) search all the spans to see if the point is contained in one, if so take
+ that. Note that this will collect all clicks from the current UI because
+ of how the hit detection of nrarena objects works.
+2) if that fails, run through all the chunks finding a best guess of the one
+ the user wanted. This is the one whose y coordinate is nearest, or if
+ there's a tie, the x.
+3) search in that chunk using x-coordinate only to find the position.
+*/
+Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const
+{
+ if (_lines.empty()) return begin();
+ double local_x = x;
+ double local_y = y;
+
+ if (_path_fitted) {
+ Path::cut_position position = const_cast<Path*>(_path_fitted)->PointToCurvilignPosition(Geom::Point(x, y));
+ local_x = const_cast<Path*>(_path_fitted)->PositionToLength(position.piece, position.t);
+ return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x);
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ local_x = y;
+ local_y = x;
+ }
+
+ // stage 1:
+ for (const auto & _span : _spans) {
+ double span_left, span_right;
+ if (_span.x_start < _span.x_end) {
+ span_left = _span.x_start;
+ span_right = _span.x_end;
+ } else {
+ span_left = _span.x_end;
+ span_right = _span.x_start;
+ }
+
+ double y_line = _span.line(this).baseline_y + _span.baseline_shift + _span.y_offset;
+ if ( local_x >= _chunks[_span.in_chunk].left_x + span_left
+ && local_x <= _chunks[_span.in_chunk].left_x + span_right
+ && local_y >= y_line - _span.line_height.ascent
+ && local_y <= y_line + _span.line_height.descent) {
+ return _cursorXOnLineToIterator(_chunks[_span.in_chunk].in_line, local_x, local_y);
+ }
+ }
+
+ // stage 2:
+ unsigned span_index = 0;
+ unsigned chunk_index;
+ int best_chunk_index = -1;
+ double best_y_range = DBL_MAX;
+ double best_x_range = DBL_MAX;
+ for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) {
+ FontMetrics line_height;
+ line_height *= 0.0; // Set all metrics to zero.
+ double chunk_width = 0.0;
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
+ line_height.max(_spans[span_index].line_height);
+ chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
+ }
+ double this_y_range;
+ if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent)
+ this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y;
+ else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent)
+ this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent);
+ else
+ this_y_range = 0.0;
+ if (this_y_range <= best_y_range) {
+ if (this_y_range < best_y_range) best_x_range = DBL_MAX;
+ double this_x_range;
+ if (local_x < _chunks[chunk_index].left_x)
+ this_x_range = _chunks[chunk_index].left_x - local_y;
+ else if (local_x > _chunks[chunk_index].left_x + chunk_width)
+ this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width);
+ else
+ this_x_range = 0.0;
+ if (this_x_range < best_x_range) {
+ best_y_range = this_y_range;
+ best_x_range = this_x_range;
+ best_chunk_index = chunk_index;
+ }
+ }
+ }
+
+ // stage 3:
+ if (best_chunk_index == -1) return begin(); // never happens
+ return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x, local_y);
+}
+
+Layout::iterator Layout::getLetterAt(double x, double y) const
+{
+ Geom::Point point(x, y);
+
+ double rotation;
+ for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
+ Geom::Rect box = characterBoundingBox(it, &rotation);
+ // todo: rotation
+ if (box.contains(point)) return it;
+ }
+ return end();
+}
+
+Layout::iterator Layout::sourceToIterator(SPObject *source /*, Glib::ustring::const_iterator text_iterator*/) const
+{
+ unsigned source_index;
+ if (_characters.empty()) return end();
+ for (source_index = 0 ; source_index < _input_stream.size() ; source_index++)
+ if (_input_stream[source_index]->source == source) break;
+ if (source_index == _input_stream.size()) return end();
+
+ unsigned char_index = _sourceToCharacter(source_index);
+
+ // Fix a bug when hidding content in flow box element
+ if (char_index >= _characters.size())
+ return end();
+
+ if (_input_stream[source_index]->Type() != TEXT_SOURCE)
+ return iterator(this, char_index);
+
+ return iterator(this, char_index);
+ /* This code was never used, the text_iterator argument was "NULL" in all calling code
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
+
+ if (text_iterator <= text_source->text_begin) return iterator(this, char_index);
+ if (text_iterator >= text_source->text_end) {
+ if (source_index == _input_stream.size() - 1) return end();
+ return iterator(this, _sourceToCharacter(source_index + 1));
+ }
+ Glib::ustring::const_iterator iter_text = text_source->text_begin;
+ for ( ; char_index < _characters.size() ; char_index++) {
+ if (iter_text == text_iterator)
+ return iterator(this, char_index);
+ iter_text++;
+ }
+ return end(); // never happens
+ */
+}
+
+Geom::OptRect Layout::glyphBoundingBox(iterator const &it, double *rotation) const
+{
+ if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
+ return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph);
+}
+
+Geom::Point Layout::characterAnchorPoint(iterator const &it) const
+{
+ if (_characters.empty())
+ return _empty_cursor_shape.position;
+
+ Geom::Point res;
+ if (it._char_index == _characters.size()) {
+ res = Geom::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
+ } else {
+ res = Geom::Point(_characters[it._char_index].chunk(this).left_x
+ + _spans[_characters[it._char_index].in_span].x_start
+ + _characters[it._char_index].x,
+ _characters[it._char_index].line(this).baseline_y
+ + _characters[it._char_index].span(this).baseline_shift);
+ }
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ std::swap(res[Geom::X], res[Geom::Y]);
+ }
+ return res;
+}
+
+std::optional<Geom::Point> Layout::baselineAnchorPoint() const
+{
+ iterator pos = this->begin();
+ Geom::Point left_pt = this->characterAnchorPoint(pos);
+ pos.thisEndOfLine();
+ Geom::Point right_pt = this->characterAnchorPoint(pos);
+
+ switch (this->paragraphAlignment(pos)) {
+ case LEFT:
+ case FULL:
+ return left_pt;
+ break;
+ case CENTER:
+ return (left_pt + right_pt)/2; // middle point
+ break;
+ case RIGHT:
+ return right_pt;
+ break;
+ default:
+ return std::optional<Geom::Point>();
+ break;
+ }
+}
+
+Geom::Path Layout::baseline() const
+{
+ iterator pos = this->begin();
+ Geom::Point left_pt = this->characterAnchorPoint(pos);
+ pos.thisEndOfLine();
+ Geom::Point right_pt = this->characterAnchorPoint(pos);
+
+ Geom::Path baseline;
+ baseline.start(left_pt);
+ baseline.appendNew<Geom::LineSegment>(right_pt);
+
+ return baseline;
+}
+
+
+Geom::Point Layout::chunkAnchorPoint(iterator const &it) const
+{
+ unsigned chunk_index;
+
+ if (_chunks.empty())
+ return Geom::Point(0.0, 0.0);
+
+ if (_characters.empty())
+ chunk_index = 0;
+ else if (it._char_index == _characters.size())
+ chunk_index = _chunks.size() - 1;
+ else chunk_index = _characters[it._char_index].span(this).in_chunk;
+
+ Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
+ double x = _chunks[chunk_index].left_x;
+ double y = _lines[_chunks[chunk_index].in_line].baseline_y;
+ double chunk_width = _getChunkWidth(chunk_index);
+ if (alignment == RIGHT) {
+ x += chunk_width;
+ } else if (alignment == CENTER) {
+ x += chunk_width * 0.5;
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ return Geom::Point(y, x);
+ } else {
+ return Geom::Point(x, y);
+ }
+}
+
+Geom::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
+{
+ Geom::Point top_left, bottom_right;
+ unsigned char_index = it._char_index;
+
+ if (_path_fitted) {
+ double cluster_half_width = 0.0;
+ for (int glyph_index = _characters[char_index].in_glyph ; _glyphs.size() != glyph_index ; glyph_index++) {
+ if (_glyphs[glyph_index].in_character != char_index) break;
+ cluster_half_width += _glyphs[glyph_index].advance;
+ }
+ cluster_half_width *= 0.5;
+
+ double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
+ int unused = 0;
+ Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
+ if (midpoint_offset >= 0.0 && midpoint_otp != nullptr && midpoint_otp[0].piece >= 0) {
+ Geom::Point midpoint;
+ Geom::Point tangent;
+ Span const &span = _characters[char_index].span(this);
+
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+ top_left[Geom::X] = midpoint[Geom::X] - cluster_half_width;
+ top_left[Geom::Y] = midpoint[Geom::Y] - span.line_height.ascent;
+ bottom_right[Geom::X] = midpoint[Geom::X] + cluster_half_width;
+ bottom_right[Geom::Y] = midpoint[Geom::Y] + span.line_height.descent;
+ Geom::Point normal = tangent.cw();
+ top_left += span.baseline_shift * normal;
+ bottom_right += span.baseline_shift * normal;
+ if (rotation)
+ *rotation = atan2(tangent[1], tangent[0]);
+ }
+ g_free(midpoint_otp);
+ } else {
+ if (it._char_index == _characters.size()) {
+ top_left[Geom::X] = bottom_right[Geom::X] = _chunks.back().left_x + _spans.back().x_end;
+ char_index--;
+ } else {
+ double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
+ top_left[Geom::X] = span_x + _characters[it._char_index].x;
+ if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
+ bottom_right[Geom::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
+ else
+ bottom_right[Geom::X] = span_x + _characters[it._char_index + 1].x;
+ }
+
+ double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = _spans[_characters[char_index].in_span].line_height.emSize();
+ top_left[Geom::Y] = top_left[Geom::X];
+ top_left[Geom::X] = baseline_y - span_height * 0.5;
+ bottom_right[Geom::Y] = bottom_right[Geom::X];
+ bottom_right[Geom::X] = baseline_y + span_height * 0.5;
+ } else {
+ top_left[Geom::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
+ bottom_right[Geom::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
+ }
+
+ if (rotation) {
+ if (it._glyph_index == -1)
+ *rotation = 0.0;
+ else if (it._glyph_index == (int)_glyphs.size())
+ *rotation = _glyphs.back().rotation;
+ else
+ *rotation = _glyphs[it._glyph_index].rotation;
+ }
+ }
+
+ return Geom::Rect(top_left, bottom_right);
+}
+
+std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const
+{
+ std::vector<Geom::Point> quads;
+ unsigned char_index;
+ unsigned end_char_index;
+
+ if (it_start._char_index < it_end._char_index) {
+ char_index = it_start._char_index;
+ end_char_index = it_end._char_index;
+ } else {
+ char_index = it_end._char_index;
+ end_char_index = it_start._char_index;
+ }
+ for ( ; char_index < end_char_index ; ) {
+ if (_characters[char_index].in_glyph == -1) {
+ char_index++;
+ continue;
+ }
+ double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
+ unsigned span_index = _characters[char_index].in_span;
+
+ Geom::Point top_left, bottom_right;
+ if (_path_fitted || char_rotation != 0.0) {
+ Geom::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
+ top_left = box.min();
+ bottom_right = box.max();
+ char_index++;
+ } else { // for straight text we can be faster by combining all the character boxes in a span into one box
+ double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
+ top_left[Geom::X] = span_x + _characters[char_index].x;
+ while (char_index < end_char_index && _characters[char_index].in_span == span_index)
+ char_index++;
+ if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
+ bottom_right[Geom::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
+ else
+ bottom_right[Geom::X] = span_x + _characters[char_index].x;
+
+ double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
+ double vertical_scale = _glyphs.back().vertical_scale;
+ double offset_y = _spans[span_index].y_offset;
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = vertical_scale * _spans[span_index].line_height.emSize();
+ top_left[Geom::Y] = top_left[Geom::X];
+ top_left[Geom::X] = offset_y + baseline_y - span_height * 0.5;
+ bottom_right[Geom::Y] = bottom_right[Geom::X];
+ bottom_right[Geom::X] = offset_y + baseline_y + span_height * 0.5;
+ } else {
+ top_left[Geom::Y] = offset_y + baseline_y - vertical_scale * _spans[span_index].line_height.ascent;
+ bottom_right[Geom::Y] = offset_y + baseline_y + vertical_scale * _spans[span_index].line_height.descent;
+ }
+ }
+
+ Geom::Rect char_box(top_left, bottom_right);
+ if (char_box.dimensions()[Geom::X] == 0.0 || char_box.dimensions()[Geom::Y] == 0.0)
+ continue;
+ Geom::Point center_of_rotation((top_left[Geom::X] + bottom_right[Geom::X]) * 0.5,
+ top_left[Geom::Y] + _spans[span_index].line_height.ascent);
+ Geom::Affine total_transform = Geom::Translate(-center_of_rotation) * Geom::Rotate(char_rotation) * Geom::Translate(center_of_rotation) * transform;
+ for(int i = 0; i < 4; i ++)
+ quads.push_back(char_box.corner(i) * total_transform);
+ }
+ return quads;
+}
+
+void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
+{
+ if (_characters.empty()) {
+ position = _empty_cursor_shape.position;
+ height = _empty_cursor_shape.height;
+ rotation = _empty_cursor_shape.rotation;
+ } else {
+ // we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
+ // this means x & rotation are the current values but y & height belong to the previous character.
+ // this isn't quite right because dx attributes will be moved along, but it's good enough
+ Span const *span;
+ bool vertical_text = _directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM);
+ if (_path_fitted) {
+ // text on a path
+ double x;
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
+ if (vertical_text)
+ x -= span->line_height.descent;
+ if (it._char_index != 0)
+ span = &_spans[_characters[it._char_index - 1].in_span];
+ }
+ double path_length = const_cast<Path*>(_path_fitted)->Length();
+ double x_on_path = x;
+ if (x_on_path < 0.0) x_on_path = 0.0;
+
+ int unused = 0;
+ // as far as I know these functions are const, they're just not marked as such
+ Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
+ Path::cut_position path_parameter;
+ if (path_parameter_list != nullptr && path_parameter_list[0].piece >= 0)
+ path_parameter = path_parameter_list[0];
+ else {
+ path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
+ path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
+ }
+ g_free(path_parameter_list);
+
+ Geom::Point point;
+ Geom::Point tangent;
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
+ if (x < 0.0)
+ point += x * tangent;
+ if (x > path_length )
+ point += (x - path_length) * tangent;
+ if (vertical_text) {
+ rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
+ position[Geom::X] = point[Geom::Y] - tangent[Geom::X] * span->baseline_shift;
+ position[Geom::Y] = point[Geom::X] + tangent[Geom::Y] * span->baseline_shift;
+ } else {
+ rotation = atan2(tangent);
+ position[Geom::X] = point[Geom::X] - tangent[Geom::Y] * span->baseline_shift;
+ position[Geom::Y] = point[Geom::Y] + tangent[Geom::X] * span->baseline_shift;
+ }
+
+ } else {
+ // text is not on a path
+
+ bool last_char_is_newline = false;
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_end;
+ rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
+
+ // Check if last character is new line.
+ if (_characters.back().the_char == '\n') {
+ last_char_is_newline = true;
+ position[Geom::X] = chunkAnchorPoint(it)[vertical_text ? Geom::Y : Geom::X];
+ }
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
+ if (it._glyph_index == -1) {
+ rotation = 0.0;
+ } else if(it._glyph_index == 0) {
+ rotation = _glyphs.empty() ? 0.0 : _glyphs[0].rotation;
+ } else{
+ rotation = _glyphs[it._glyph_index - 1].rotation;
+ }
+ // 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
+ if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
+ span = &_spans[_characters[it._char_index - 1].in_span];
+ }
+ position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift + span->y_offset;
+
+ if (last_char_is_newline) {
+ // Move cursor to empty new line.
+ double vertical_scale = _glyphs.empty() ? 1.0 : _glyphs.back().vertical_scale;
+ if (vertical_text) {
+ // Vertical text
+ position[Geom::Y] -= vertical_scale * span->line_height.emSize();
+ } else {
+ position[Geom::Y] += vertical_scale * span->line_height.emSize();
+ }
+ }
+ }
+
+ // up to now *position is the baseline point, not the final point which will be the bottom of the descent
+ double vertical_scale = _glyphs.empty() ? 1.0 : _glyphs.back().vertical_scale;
+
+ if (vertical_text) {
+ // Vertical text
+ height = vertical_scale * span->line_height.emSize();
+ rotation += M_PI / 2;
+ std::swap(position[Geom::X], position[Geom::Y]);
+ position[Geom::X] -= vertical_scale * sin(rotation) * height * 0.5;
+ position[Geom::Y] += vertical_scale * cos(rotation) * height * 0.5;
+ } else {
+ // Horizontal text
+ double caret_slope_run = 0.0, caret_slope_rise = 1.0;
+ if (span->font)
+ const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise);
+ double caret_slope = atan2(caret_slope_run, caret_slope_rise);
+ height = vertical_scale * (span->line_height.emSize()) / cos(caret_slope);
+ rotation += caret_slope;
+ position[Geom::X] -= sin(rotation) * vertical_scale * span->line_height.descent;
+ position[Geom::Y] += cos(rotation) * vertical_scale * span->line_height.descent;
+ }
+ }
+}
+
+bool Layout::isHidden(iterator const &it) const
+{
+ return _characters[it._char_index].line(this).hidden;
+}
+
+
+void Layout::getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator) const
+{
+ if (it._char_index >= _characters.size()) {
+ *source = nullptr;
+ return;
+ }
+ InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
+ *source = stream_item->source;
+ if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
+ InputStreamTextSource *text_source = dynamic_cast<InputStreamTextSource *>(stream_item);
+
+ // In order to return a non-const iterator in text_iterator, do the const_cast here.
+ // Note that, although ugly, it is safe because we do not write to *iterator anywhere.
+ Glib::ustring::iterator text_iter = const_cast<Glib::ustring *>(text_source->text)->begin();
+
+ unsigned char_index = it._char_index;
+ unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
+ // confusing algorithm because the iterator goes forwards while the index goes backwards.
+ // It's just that it's faster doing it that way
+ while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
+ ++text_iter;
+ char_index--;
+ }
+
+ if (text_iterator) {
+ *text_iterator = text_iter;
+ }
+ }
+}
+
+void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
+{
+ SVGLength zero_length;
+ zero_length = 0.0;
+
+ result->x.clear();
+ result->y.clear();
+ result->dx.clear();
+ result->dy.clear();
+ result->rotate.clear();
+ if (to._char_index <= from._char_index)
+ return;
+ result->dx.reserve(to._char_index - from._char_index);
+ result->dy.reserve(to._char_index - from._char_index);
+ result->rotate.reserve(to._char_index - from._char_index);
+ for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
+ if (!_characters[char_index].char_attributes.is_char_break)
+ continue;
+ if (char_index == 0)
+ continue;
+ if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
+ continue;
+
+ unsigned prev_cluster_char_index;
+ for (prev_cluster_char_index = char_index - 1 ;
+ prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
+ prev_cluster_char_index--){};
+ if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
+ // dx is zero for the first char in a chunk
+ // this algorithm works by comparing the summed widths of the glyphs with the observed
+ // difference in x coordinates of characters, and subtracting the two to produce the x kerning.
+ double glyphs_width = 0.0;
+ if (_characters[prev_cluster_char_index].in_glyph != -1)
+ for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
+ glyphs_width += _glyphs[glyph_index].advance;
+ if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
+ glyphs_width = -glyphs_width;
+
+ double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
+ - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
+ - glyphs_width;
+
+
+ InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
+ if (input_item->Type() == TEXT_SOURCE) {
+ SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
+ if (_characters[char_index].char_attributes.is_white)
+ dx -= style->word_spacing.computed * getTextLengthMultiplierDue();
+ if (_characters[char_index].char_attributes.is_cursor_position)
+ dx -= style->letter_spacing.computed * getTextLengthMultiplierDue();
+ dx -= getTextLengthIncrementDue();
+ }
+
+ if (fabs(dx) > 0.0001) {
+ result->dx.resize(char_index - from._char_index + 1, zero_length);
+ result->dx.back() = dx;
+ }
+ }
+ double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
+ if (fabs(dy) > 0.0001) {
+ result->dy.resize(char_index - from._char_index + 1, zero_length);
+ result->dy.back() = dy;
+ }
+ if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
+ result->rotate.resize(char_index - from._char_index + 1, zero_length);
+ result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
+ }
+ }
+}
+
+#define PREV_START_OF_ITEM(this_func) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == 0) return false; \
+ _char_index--; \
+ return this_func(); \
+ }
+// end of macro
+
+#define THIS_START_OF_ITEM(item_getter) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == 0) return false; \
+ unsigned original_item; \
+ if (_char_index == _parent_layout->_characters.size()) { \
+ _char_index--; \
+ original_item = item_getter; \
+ } else { \
+ original_item = item_getter; \
+ _char_index--; \
+ } \
+ while (item_getter == original_item) { \
+ if (_char_index == 0) { \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ } \
+ _char_index--; \
+ } \
+ _char_index++; \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+#define NEXT_START_OF_ITEM(item_getter) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == _parent_layout->_characters.size()) return false; \
+ unsigned original_item = item_getter; \
+ for( ; ; ) { \
+ _char_index++; \
+ if (_char_index == _parent_layout->_characters.size()) { \
+ _glyph_index = _parent_layout->_glyphs.size(); \
+ return false; \
+ } \
+ if (item_getter != original_item) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+bool Layout::iterator::prevStartOfSpan()
+ PREV_START_OF_ITEM(thisStartOfSpan);
+
+bool Layout::iterator::thisStartOfSpan()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
+
+bool Layout::iterator::nextStartOfSpan()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
+
+
+bool Layout::iterator::prevStartOfChunk()
+ PREV_START_OF_ITEM(thisStartOfChunk);
+
+bool Layout::iterator::thisStartOfChunk()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
+
+bool Layout::iterator::nextStartOfChunk()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
+
+
+bool Layout::iterator::prevStartOfLine()
+ PREV_START_OF_ITEM(thisStartOfLine);
+
+bool Layout::iterator::thisStartOfLine()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
+
+bool Layout::iterator::nextStartOfLine()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
+
+
+bool Layout::iterator::prevStartOfShape()
+ PREV_START_OF_ITEM(thisStartOfShape);
+
+bool Layout::iterator::thisStartOfShape()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
+
+bool Layout::iterator::nextStartOfShape()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
+
+
+bool Layout::iterator::prevStartOfParagraph()
+ PREV_START_OF_ITEM(thisStartOfParagraph);
+
+bool Layout::iterator::thisStartOfParagraph()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
+
+bool Layout::iterator::nextStartOfParagraph()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
+
+
+bool Layout::iterator::prevStartOfSource()
+ PREV_START_OF_ITEM(thisStartOfSource);
+
+bool Layout::iterator::thisStartOfSource()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
+
+bool Layout::iterator::nextStartOfSource()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
+
+
+bool Layout::iterator::thisEndOfLine()
+{
+ if (_char_index == _parent_layout->_characters.size()) return false;
+ if (nextStartOfLine())
+ {
+ if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
+ return prevCursorPosition();
+ return true;
+ }
+ if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
+ return prevCursorPosition(); // for when the last paragraph is empty
+ return false;
+}
+
+void Layout::iterator::beginCursorUpDown()
+{
+ if (_char_index == _parent_layout->_characters.size())
+ _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
+ else
+ _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;
+ _cursor_moving_vertically = true;
+}
+
+bool Layout::iterator::nextLineCursor(int n)
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ if (_char_index == _parent_layout->_characters.size())
+ return false;
+ unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
+ if (line_index == _parent_layout->_lines.size() - 1)
+ return false; // nowhere to go
+ else
+ n = MIN (n, static_cast<int>(_parent_layout->_lines.size() - 1 - line_index));
+ if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) {
+ // switching between shapes: adjust the stored x to compensate
+ _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + n)].in_chunk].left_x
+ - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
+ }
+ _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
+ if (_char_index == _parent_layout->_characters.size())
+ _glyph_index = _parent_layout->_glyphs.size();
+ else
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+bool Layout::iterator::prevLineCursor(int n)
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ int line_index;
+ if (_char_index == _parent_layout->_characters.size())
+ line_index = _parent_layout->_lines.size() - 1;
+ else
+ line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
+ if (line_index <= 0)
+ return false; // nowhere to go
+ else
+ n = MIN (n, static_cast<int>(line_index));
+ if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) {
+ // switching between shapes: adjust the stored x to compensate
+ _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - n)].in_chunk].left_x
+ - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
+ }
+ _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+#define NEXT_WITH_ATTRIBUTE_SET(attr) \
+ { \
+ _cursor_moving_vertically = false; \
+ for ( ; ; ) { \
+ if (_char_index + 1 >= _parent_layout->_characters.size()) { \
+ _char_index = _parent_layout->_characters.size(); \
+ _glyph_index = _parent_layout->_glyphs.size(); \
+ return false; \
+ } \
+ _char_index++; \
+ if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+#define PREV_WITH_ATTRIBUTE_SET(attr) \
+ { \
+ _cursor_moving_vertically = false; \
+ for ( ; ; ) { \
+ if (_char_index == 0) { \
+ _glyph_index = 0; \
+ return false; \
+ } \
+ _char_index--; \
+ if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+bool Layout::iterator::nextCursorPosition()
+ NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
+
+bool Layout::iterator::prevCursorPosition()
+ PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
+
+bool Layout::iterator::nextStartOfWord()
+ NEXT_WITH_ATTRIBUTE_SET(is_word_start);
+
+bool Layout::iterator::prevStartOfWord()
+ PREV_WITH_ATTRIBUTE_SET(is_word_start);
+
+bool Layout::iterator::nextEndOfWord()
+ NEXT_WITH_ATTRIBUTE_SET(is_word_end);
+
+bool Layout::iterator::prevEndOfWord()
+ PREV_WITH_ATTRIBUTE_SET(is_word_end);
+
+bool Layout::iterator::nextStartOfSentence()
+ NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
+
+bool Layout::iterator::prevStartOfSentence()
+ PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
+
+bool Layout::iterator::nextEndOfSentence()
+ NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
+
+bool Layout::iterator::prevEndOfSentence()
+ PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
+
+bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
+{
+ // the only reason this function is so complicated is to enable visual cursor
+ // movement moving in to or out of counterdirectional runs
+ if (_parent_layout->_characters.empty()) return false;
+ unsigned old_span_index;
+ Direction old_span_direction;
+ if (_char_index == _parent_layout->_characters.size())
+ old_span_index = _parent_layout->_spans.size() - 1;
+ else
+ old_span_index = _parent_layout->_characters[_char_index].in_span;
+ old_span_direction = _parent_layout->_spans[old_span_index].direction;
+ Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
+
+ int scan_direction;
+ unsigned old_char_index = _char_index;
+ if (old_span_direction != para_direction
+ && ((_char_index == 0 && direction == para_direction)
+ || (_char_index == _parent_layout->_characters.size() && direction != para_direction))) {
+ // the end of the text is actually in the middle because of reordering. Do cleverness
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else {
+ if (direction == old_span_direction) {
+ if (!nextCursorPosition()) return false;
+ } else {
+ if (!prevCursorPosition()) return false;
+ }
+
+ unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
+ if (new_span_index == old_span_index) return true;
+ if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
+ // we must jump to the other end of a counterdirectional run
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
+ // we might have to do a weird jump when we would have crossed a chunk/line break
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
+ return true;
+ if (old_span_direction == para_direction)
+ return true;
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else
+ return true; // same direction, same chunk: no cleverness required
+ }
+
+ unsigned new_span_index = old_span_index;
+ for ( ; ; ) {
+ if (scan_direction > 0) {
+ if (new_span_index == _parent_layout->_spans.size() - 1) {
+ if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
+ _char_index = old_char_index;
+ return false; // the visual end is in the logical middle
+ }
+ break;
+ }
+ new_span_index++;
+ } else {
+ if (new_span_index == 0) {
+ if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
+ _char_index = old_char_index;
+ return false; // the visual end is in the logical middle
+ }
+ break;
+ }
+ new_span_index--;
+ }
+ if (_parent_layout->_spans[new_span_index].direction == para_direction) {
+ if (para_direction == old_span_direction)
+ new_span_index -= scan_direction;
+ break;
+ }
+ if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
+ && para_direction == old_span_direction)
+ new_span_index -= scan_direction;
+ break;
+ }
+ }
+
+ // found the correct span, now find the correct character
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
+ if (new_span_index > old_span_index)
+ _char_index = _parent_layout->_spanToCharacter(new_span_index);
+ else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
+ } else {
+ if (_parent_layout->_spans[new_span_index].direction != direction) {
+ if (new_span_index >= _parent_layout->_spans.size() - 1)
+ _char_index = _parent_layout->_characters.size();
+ else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
+ } else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index);
+ }
+ if (_char_index == _parent_layout->_characters.size()) {
+ _glyph_index = _parent_layout->_glyphs.size();
+ return false;
+ }
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return _char_index != 0;
+}
+
+bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
+{
+ bool r;
+ while ((r = _cursorLeftOrRightLocalX(direction))
+ && !_parent_layout->_characters[_char_index].char_attributes.is_word_start){};
+ return r;
+}
+
+bool Layout::iterator::cursorUp(int n)
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevLineCursor(n);
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextLineCursor(n);
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDown(int n)
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextLineCursor(n);
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevLineCursor(n);
+ else
+ return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorLeft()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return prevLineCursor();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return nextLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorRight()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return nextLineCursor();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return prevLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorUpWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevStartOfParagraph();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDownWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextStartOfParagraph();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorLeftWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return prevStartOfParagraph();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return nextStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorRightWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return nextStartOfParagraph();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return prevStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :