/*
* 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, read the file 'COPYING' for more information
*/
#include "Layout-TNG.h"
#include "font-instance.h"
#include "svg/svg-length.h"
#include "style.h"
namespace Inkscape {
namespace Text {
{
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);
if (this_x_difference < best_x_difference) {
}
}
// 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 this_x_difference;
if (char_index == 0) this_x_difference = fabs(_spans.front().x_end + _chunks.front().left_x - local_x);
else this_x_difference = fabs(_characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x);
if (this_x_difference < best_x_difference) {
}
}
return iterator(this, best_char_index);
}
{
unsigned span_index;
if (chunk_index) {
} else
span_index = 0;
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.
*/
{
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 = y;
local_y = x;
}
// stage 1:
} else {
}
&& local_y >= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift - _spans[span_index].line_height.ascent
&& local_y <= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift + _spans[span_index].line_height.descent) {
}
}
// stage 2:
unsigned span_index = 0;
unsigned chunk_index;
chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
}
double this_y_range;
else
this_y_range = 0.0;
if (this_y_range <= best_y_range) {
double this_x_range;
else
this_x_range = 0.0;
if (this_x_range < best_x_range) {
}
}
}
// stage 3:
}
{
double rotation;
// todo: rotation
}
return end();
}
Layout::iterator Layout::sourceToIterator(void *source_cookie /*, Glib::ustring::const_iterator text_iterator*/) const
{
unsigned source_index;
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
*/
}
{
}
{
if (_characters.empty())
return _empty_cursor_shape.position;
return Geom::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
} else {
}
}
{
pos.thisEndOfLine();
}
switch (this->paragraphAlignment(pos)) {
case LEFT:
case FULL:
return left_pt;
break;
case CENTER:
break;
case RIGHT:
return right_pt;
break;
default:
break;
}
}
{
pos.thisEndOfLine();
}
return baseline;
}
{
unsigned chunk_index;
if (_characters.empty())
chunk_index = 0;
//centre
return Geom::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
}
{
if (_path_fitted) {
for (int glyph_index = _characters[char_index].in_glyph ; _glyphs[glyph_index].in_character == char_index ; glyph_index++)
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);
const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
if (rotation)
}
} else {
char_index--;
} else {
double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_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
}
double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
} else {
}
if (rotation) {
*rotation = 0.0;
else
}
}
}
std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const
{
unsigned char_index;
unsigned end_char_index;
} else {
}
for ( ; char_index < end_char_index ; ) {
char_index++;
continue;
}
char_index++;
} else { // for straight text we can be faster by combining all the character boxes in a span into one box
char_index++;
else
} else {
}
}
continue;
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 ++)
}
return quads;
}
void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
{
if (_characters.empty()) {
} 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
if (_path_fitted) {
// text on a path
double x;
} else {
x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
if (it._char_index != 0)
}
double x_on_path = x;
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);
else {
}
const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
if (x < 0.0)
if (x > path_length )
} else {
}
} else {
// text is not on a path
} else {
rotation = 0.0;
} else if(it._glyph_index == 0) {
} else{
}
// 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)
}
}
// up to now *position is the baseline point, not the final point which will be the bottom of the descent
// Vertical text
} else {
// Horizontal text
rotation += caret_slope;
}
}
}
void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
{
*source_cookie = NULL;
return;
}
InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_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.
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) {
}
}
}
void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
{
zero_length = 0.0;
return;
continue;
if (char_index == 0)
continue;
continue;
unsigned prev_cluster_char_index;
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.
for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
- glyphs_width;
InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
dx -= getTextLengthIncrementDue();
}
}
}
double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
}
if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
}
}
}
{ \
_cursor_moving_vertically = false; \
if (_char_index == 0) return false; \
_char_index--; \
return this_func(); \
}
// end of macro
{ \
_cursor_moving_vertically = false; \
if (_char_index == 0) return false; \
unsigned original_item; \
_char_index--; \
original_item = item_getter; \
} else { \
original_item = item_getter; \
_char_index--; \
} \
while (item_getter == original_item) { \
if (_char_index == 0) { \
return true; \
} \
_char_index--; \
} \
_char_index++; \
return true; \
}
// end of macro
{ \
_cursor_moving_vertically = false; \
unsigned original_item = item_getter; \
for( ; ; ) { \
_char_index++; \
return false; \
} \
if (item_getter != original_item) break; \
} \
return true; \
}
// end of macro
THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
{
if (nextStartOfLine())
{
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;
}
{
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;
}
{
return false;
return false; // nowhere to go
else
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;
}
return true;
}
{
int line_index;
else
if (line_index <= 0)
return false; // nowhere to go
else
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;
}
return true;
}
{ \
_cursor_moving_vertically = false; \
for ( ; ; ) { \
return false; \
} \
_char_index++; \
} \
return true; \
}
// end of macro
{ \
_cursor_moving_vertically = false; \
for ( ; ; ) { \
if (_char_index == 0) { \
_glyph_index = 0; \
return false; \
} \
_char_index--; \
} \
return true; \
}
// end of macro
{
// the only reason this function is so complicated is to enable visual cursor
// movement moving in to or out of counterdirectional runs
unsigned old_span_index;
else
Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
int scan_direction;
if (old_span_direction != para_direction
// the end of the text is actually in the middle because of reordering. Do cleverness
} else {
if (direction == old_span_direction) {
if (!nextCursorPosition()) return false;
} else {
if (!prevCursorPosition()) return false;
}
if (new_span_index == old_span_index) return true;
// we must jump to the other end of a counterdirectional run
} else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_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)
return true;
if (old_span_direction == para_direction)
return true;
} else
return true; // same direction, same chunk: no cleverness required
}
for ( ; ; ) {
if (scan_direction > 0) {
return false; // the visual end is in the logical middle
}
break;
}
} else {
if (new_span_index == 0) {
return false; // the visual end is in the logical middle
}
break;
}
}
if (para_direction == old_span_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)
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)
else
} else {
else
} else
}
return false;
}
return _char_index != 0;
}
{
bool r;
while ((r = _cursorLeftOrRightLocalX(direction))
return r;
}
{
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);
}
{
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);
}
{
if(block_progression == LEFT_TO_RIGHT)
return prevLineCursor();
else if(block_progression == RIGHT_TO_LEFT)
return nextLineCursor();
else
return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
}
{
if(block_progression == LEFT_TO_RIGHT)
return nextLineCursor();
else if(block_progression == RIGHT_TO_LEFT)
return prevLineCursor();
else
return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
}
{
if(block_progression == TOP_TO_BOTTOM)
return prevStartOfParagraph();
else if(block_progression == BOTTOM_TO_TOP)
return nextStartOfParagraph();
else
}
{
if(block_progression == TOP_TO_BOTTOM)
return nextStartOfParagraph();
else if(block_progression == BOTTOM_TO_TOP)
return prevStartOfParagraph();
else
}
{
if(block_progression == LEFT_TO_RIGHT)
return prevStartOfParagraph();
else if(block_progression == RIGHT_TO_LEFT)
return nextStartOfParagraph();
else
}
{
if(block_progression == LEFT_TO_RIGHT)
return nextStartOfParagraph();
else if(block_progression == RIGHT_TO_LEFT)
return prevStartOfParagraph();
else
}
}//namespace Text
}//namespace Inkscape