Layout-TNG-OutIter.cpp revision f360281ed928daee6e4f43a1a593c28b0b0719f9
/*
* 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 "libnr/nr-matrix-translate-ops.h"
#include "libnr/nr-translate-rotate-ops.h"
#include "style.h"
namespace Inkscape {
namespace Text {
{
int best_char_index = -1;
double best_x_difference = DBL_MAX;
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);
}
{
double chunk_width = 0.0;
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(NR::Point(x, y));
}
local_x = y;
local_y = x;
}
// stage 1:
double span_left, span_right;
} 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;
int best_chunk_index = -1;
double best_y_range = DBL_MAX;
double best_x_range = DBL_MAX;
double chunk_width = 0.0;
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);
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
}
if (iter_text == text_iterator)
return iterator(this, char_index);
iter_text++;
}
return end(); // never happens
}
{
return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL)));
}
{
}
{
if (_characters.empty())
return _empty_cursor_shape.position;
return NR::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
} else {
}
}
{
unsigned chunk_index;
if (_characters.empty())
chunk_index = 0;
//centre
}
{
if (_path_fitted) {
double cluster_half_width = 0.0;
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[NR::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;
double span_height = _spans[_characters[char_index].in_span].line_height.ascent + _spans[_characters[char_index].in_span].line_height.descent;
} else {
}
if (rotation) {
*rotation = 0.0;
else
}
}
}
std::vector<NR::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix 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
double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent;
} else {
}
}
continue;
NR::Matrix total_transform = NR::translate(-center_of_rotation) * NR::rotate(char_rotation) * NR::translate(center_of_rotation) * transform;
for(int i = 0; i < 4; i ++)
}
return quads;
}
void Layout::queryCursorShape(iterator const &it, NR::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 {
// text is not on a path
} else {
(*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
// 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
} else {
*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];
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) {
char_index--;
}
*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()))));
// the caller owns the string, so they're going to want a non-const 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 ;
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;
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];
}
}
}
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) {
}
}
}
#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; \
_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
#define NEXT_START_OF_ITEM(item_getter) \
{ \
_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;
if (_parent_layout->_lines[line_index + 1].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 + 1)].in_chunk].left_x
- _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
}
return true;
}
{
unsigned line_index;
else
if (line_index == 0) return false;
if (_parent_layout->_lines[line_index - 1].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 - 1)].in_chunk].left_x
- _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
}
return true;
}
#define NEXT_WITH_ATTRIBUTE_SET(attr) \
{ \
_cursor_moving_vertically = false; \
for ( ; ; ) { \
return false; \
} \
_char_index++; \
} \
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--; \
} \
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;
unsigned old_char_index = _char_index;
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
}
unsigned new_span_index = old_span_index;
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();
else if(block_progression == BOTTOM_TO_TOP)
return nextLineCursor();
else
return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
}
{
if(block_progression == TOP_TO_BOTTOM)
return nextLineCursor();
else if(block_progression == BOTTOM_TO_TOP)
return prevLineCursor();
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