text-editing.cpp revision 950ccc6c98dbab31603a66a04b6101ab5a4752fc
/*
* Parent class for text and flowtext
*
* Authors:
* bulia byak
* Richard Hughes
*
* Copyright (C) 2004-5 authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstring>
#include <string>
#include "desktop.h"
#include "inkscape.h"
#include "message-stack.h"
#include "style.h"
#include "unit-constants.h"
#include "document.h"
#include "xml/attribute-record.h"
#include "sp-textpath.h"
#include "sp-flowtext.h"
#include "sp-flowdiv.h"
#include "sp-flowregion.h"
#include "sp-tref.h"
#include "sp-tspan.h"
#include "text-editing.h"
{
if (SP_IS_TEXT(item)) {
} else if (SP_IS_FLOWTEXT (item)) {
}
return NULL;
}
{
if (SP_IS_TEXT(item))
else if (SP_IS_FLOWTEXT (item))
}
/** Returns true if there are no visible characters on the canvas */
bool
{
}
/** Returns true if the user has typed nothing in the text box */
bool
{
if (!sp_te_input_is_empty(child)) return false;
return true;
}
{
return layout->getNearestCursorPositionTo(p);
}
std::vector<Geom::Point> sp_te_create_selection_quads(SPItem const *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, Geom::Matrix const &transform)
{
}
void
sp_te_get_cursor_coords (SPItem const *item, Inkscape::Text::Layout::iterator const &position, Geom::Point &p0, Geom::Point &p1)
{
}
SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
{
if (pos_obj)
return SP_OBJECT_STYLE(pos_obj);
return NULL;
}
SPObject const * sp_te_object_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
{
return NULL;
void *rawptr = 0;
return pos_obj;
}
/*
* for debugging input
*
char * dump_hexy(const gchar * utf8)
{
static char buffer[1024];
buffer[0]='\0';
for (const char *ptr=utf8; *ptr; ptr++) {
sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
}
return buffer;
}
*/
Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
{
}
/* ***************************************************************************************************/
// I N S E R T I N G T E X T
{
bool is_line_break = false;
if (object) {
if (SP_IS_TEXT(object)
|| SP_IS_TEXTPATH(object)
|| SP_IS_FLOWDIV(object)
|| SP_IS_FLOWPARA(object)
|| SP_IS_FLOWLINE(object)
|| SP_IS_FLOWREGIONBREAK(object)) {
is_line_break = true;
}
}
return is_line_break;
}
/** returns the attributes for an object, or NULL if it isn't a text,
tspan, tref, or textpath. */
{
if (SP_IS_TSPAN(object))
if (SP_IS_TEXT(object))
if (SP_IS_TREF(object))
if (SP_IS_TEXTPATH(object))
return NULL;
}
{
return NULL;
}
/** Recursively gets the length of all the SPStrings at or below the given
\a item. Also adds 1 for each line break encountered. */
{
unsigned length = 0;
}
return length;
}
/** Recursively gets the length of all the SPStrings at or below the given
\a item, before and not including \a upto. Also adds 1 for each line break encountered. */
{
unsigned length = 0;
// The string is the lowest level and the length can be counted directly.
if (SP_IS_STRING(item)) {
}
// Take care of new lines...
// add 1 for each newline
length++;
}
}
// Count the length of the children
// hit upto, return immediately
return length;
}
if (SP_IS_STRING(child)) {
}
else {
// upto is below us, recurse and break loop
return length;
} else {
// recurse and go to the next sibling
}
}
}
return length;
}
static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
{
for ( ; attributes ; attributes++) {
}
return new_node;
}
return NULL; // this had better never happen
}
return NULL;
}
/** returns the sum of the (recursive) lengths of all the SPStrings prior
to \a item at the same level. */
{
unsigned char_index = 0;
for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
return char_index;
}
/** splits the attributes for the first object at the given \a char_index
and moves the ones after that point into \a second_item. */
{
if (first_attrs && second_attrs)
}
/** recursively divides the XML node tree into two objects: the original will
contain all objects up to and including \a split_obj and the returned value
will be the new leaf which represents the copy of \a split_obj and extends
down the tree with new elements all the way to the common root which is the
parent of the first line break node encountered.
*/
{
if (is_line_break_object(split_obj)) {
Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
return SP_OBJECT_NEXT(split_obj);
}
SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
// copy the split node
Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
// then move all the subsequent nodes
while (split_obj) {
SPObject *next_obj = SP_OBJECT_NEXT(split_obj); // this is about to become invalidated by removeChild()
}
return duplicate_obj->firstChild();
}
/** inserts a new line break at the given position in a text or flowtext
object. If the position is in the middle of a span, the XML tree must be
chopped in two such that the line can be created at the root of the text
element. Returns an iterator pointing just after the inserted break. */
Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
{
// Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
// texpaths attached to the same path, with a vertical shift
return position;
void *rawptr = 0;
}
if (SP_IS_TREF(split_obj)) {
return position;
}
if (split_obj) {
Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
}
} else if (SP_IS_STRING(split_obj)) {
// If the parent is a tref, editing on this particular string is disallowed.
return position;
}
unsigned char_index = 0;
char_index++;
// we need to split the entire text tree into two
// TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
} else {
// TODO
// I think the only case to put here is arbitrary gaps, which nobody uses yet
}
item->updateRepr();
}
/** finds the first SPString after the given position, including children, excluding parents */
{
while (start_obj) {
if (start_obj->hasChildren()) {
if (found_string) return found_string;
}
break; // don't cross line breaks
}
return NULL;
}
/** inserts the given characters into the given string and inserts
static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
{
unsigned char_index = 0;
char_index++;
for ( ; ; ) {
if (!attributes) break;
}
}
/** Inserts the given text into a text or flowroot object. Line breaks
cannot be inserted using this function, see sp_te_insert_line(). Returns
an iterator pointing just after the inserted text. */
{
g_warning("Trying to insert invalid utf8");
return position;
}
SPObject *source_obj = 0;
void *rawptr = 0;
// we want to insert after the previous char, not before the current char.
// it makes a difference at span boundaries
if (SP_IS_STRING(source_obj)) {
// If the parent is a tref, editing on this particular string is disallowed.
return position;
}
// Now the simple case can begin...
if (!cursor_at_start) iter_text++;
} else {
// the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
if (cursor_at_start) {
source_obj = item;
if (source_obj->hasChildren()) {
if (SP_IS_FLOWTEXT(item)) {
if (source_obj == NULL)
source_obj = item;
}
}
}
} else
if (source_obj) { // never fails
if (string_item == NULL) {
// need to add an SPString in this (pathological) case
}
// If the parent is a tref, editing on this particular string is disallowed.
return position;
}
insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
}
}
item->updateRepr();
}
/* ***************************************************************************************************/
// D E L E T I N G T E X T
/** moves all the children of \a from_repr to \a to_repr, either before
the existing children or after them. Order is maintained. The empty
\a from_repr is not deleted. */
static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
{
while (from_repr->childCount()) {
}
}
/** returns the object in the tree which is the closest ancestor of both
\a one and \a two. It will never return anything higher than \a text. */
{
return text;
if (SP_IS_STRING(common_ancestor))
}
return common_ancestor;
}
/** positions \a para_obj and \a text_iter to be pointing at the end
of the last string in the last leaf object of \a para_obj. If the last
leaf is not an SPString then \a text_iter will be unchanged. */
{
while ((*para_obj)->hasChildren())
if (SP_IS_STRING(*para_obj))
}
/** delete the line break pointed to by \a item by merging its children into
the next suitable object and deleting \a item. Returns the object after the
ones that have just been moved and sets \a next_is_sibling accordingly. */
{
unsigned moved_char_count = sp_text_get_length(item) - 1; // the -1 is because it's going to count the line break
/* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
<div></div><p>*text</p>
<p><div></div>*text</p>
<p><div></div></p><p>*text</p>
*/
}
if (SP_IS_STRING(following_item)) {
SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
*next_is_sibling = true;
} else {
*next_is_sibling = true;
*next_is_sibling = false;
}
}
// work around a bug in sp_style_write_difference() which causes the difference
// not to be written if the second param has a style set which the first does not
// by causing the first param to have everything set
Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
}
if (attributes)
return next_item;
}
/** erases the given characters from the given string and deletes the
static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
{
unsigned char_index = 0;
unsigned char_count = 0;
char_index++;
char_count++;
for ( ; ; ) {
if (attributes == NULL) break;
}
}
/* Deletes the given characters from a text or flowroot object. This is
quite a complicated operation, partly due to the cleanup that is done if all
the text in a subobject has been deleted, and partly due to the difficulty
of figuring out what is a line break and how to delete one. Returns the
real start and ending iterators based on the situation. */
bool
{
bool success = false;
}
void *rawptr = 0;
if (start_item == 0)
return success; // start is at end of text
if (end_item == 0) {
}
else if (is_line_break_object(end_item))
if (start_item == end_item) {
// the quick case where we're deleting stuff all from the same string
// If the parent is a tref, editing on this particular string is disallowed.
} else {
success = true;
}
}
} else {
// walk the tree from start_item to end_item, deleting as we go
if (SP_IS_STRING(sub_item)) {
// If the parent is a tref, editing on this particular string is disallowed.
break;
}
success = true;
}
break;
}
if (SP_IS_STRING(sub_item)) {
if (sub_item == start_item)
else
success = true;
}
// walk to the next item in the tree
if (sub_item->hasChildren())
else {
do {
bool is_sibling = true;
is_sibling = false;
}
if (is_line_break_object(sub_item))
if (is_sibling) break;
// no more siblings, go up a parent
}
}
}
while (tidy_xml_tree_recursively(common_ancestor)){};
return success;
}
/* ***************************************************************************************************/
// P L A I N T E X T F U N C T I O N S
/** Gets a text-only representation of the given text or flowroot object,
replacing line break elements with '\n'. */
static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
{
if (*pending_line_break)
*string += '\n';
if (SP_IS_STRING(child))
else
}
*pending_line_break = true;
}
/** Gets a text-only representation of the given text or flowroot object,
replacing line break elements with '\n'. The return value must be free()d. */
gchar *
{
bool pending_line_break = false;
}
/** Gets a text-only representation of the characters in a text or flowroot
object from \a start to \a end only. Line break elements are replaced with
'\n'. */
sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
{
} else {
}
// not a particularly fast piece of code. I'll optimise it if people start to notice.
void *rawptr = 0;
if (SP_IS_STRING(char_item))
else
result += '\n';
}
return result;
}
void
{
bool is_textpath = false;
if (SP_IS_TEXT_TEXTPATH (text)) {
is_textpath = true;
} else {
}
while (child) {
}
while (p) {
if (is_textpath) {
if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
} else {
if (e) *e = '\0';
} else { // create a flowPara for each line
}
}
p = (e) ? e + 1 : NULL;
}
if (is_textpath) {
}
}
/* ***************************************************************************************************/
// K E R N I N G A N D S P A C I N G
/** Returns the attributes block and the character index within that block
which represents the iterator \a position. */
static TextTagAttributes*
text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
{
return NULL; // flowtext doesn't support kerning yet
SPObject *source_item = 0;
void *rawptr = 0;
++*char_index;
}
void
sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, Geom::Point by)
{
// divide increment by zoom
// divide increment by matrix expansion
unsigned char_index;
TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
}
item->updateRepr();
}
void
sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
{
// divide increment by zoom
// divide increment by matrix expansion
SPObject *source_item = 0;
void *rawptr = 0;
if (source_item == 0) return;
gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
}
void
sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
{
unsigned char_index;
TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
if (attributes == NULL) return;
for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
}
} else
text->updateRepr();
}
void
sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
{
SPObject *source_obj = 0;
void *rawptr = 0;
unsigned nb_let;
if (source_obj == 0) { // end of text
}
if (SP_IS_STRING(source_obj)) {
}
// calculate real value
/* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
get rid of the `else val = 0.0'. Similarly below and in sp-string.cpp. */
} else { // unknown unit - should not happen
val = 0.0;
}
} else { // there's a real value in .computed, or it's zero
}
} else {
}
// divide increment by zoom and by the number of characters in the line,
// so that the entire line is expanded by by pixels, no matter what its length
// set back value to entire paragraph
}
} else {
}
} else {
// apply to selection only
char string_val[40];
}
text->updateRepr();
}
double
{
if (!layout)
return 0;
double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
return average_line_height;
}
void
sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
{
// TODO: use start and end iterators to delineate the area to be affected
style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
}
double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
// divide increment by zoom and by the number of lines,
// so that the entire object is expanded by by pixels
// divide increment by matrix expansion
case SP_CSS_UNIT_NONE:
default:
// multiplier-type units, stored in computed
if (fabs(style->line_height.computed) < 0.001) style->line_height.computed = by < 0.0 ? -0.001 : 0.001; // the formula below could get stuck at zero
break;
case SP_CSS_UNIT_EM:
case SP_CSS_UNIT_EX:
case SP_CSS_UNIT_PERCENT:
// multiplier-type units, stored in value
break;
// absolute-type units
case SP_CSS_UNIT_PX:
break;
case SP_CSS_UNIT_PT:
break;
case SP_CSS_UNIT_PC:
break;
case SP_CSS_UNIT_MM:
break;
case SP_CSS_UNIT_CM:
break;
case SP_CSS_UNIT_IN:
break;
}
text->updateRepr();
}
/* ***************************************************************************************************/
// S T Y L E A P P L I C A T I O N
/** converts an iterator to a character index, mainly because ustring::substr()
doesn't have a version that takes iterators as parameters. */
static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
{
unsigned n = 0;
for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
n++;
return n;
}
/** applies the given style string on top of the existing styles for \a item,
as opposed to sp_style_merge_from_style_string which merges its parameter
underneath the existing styles (ie ignoring already set properties). */
{
if (item_style_string && *item_style_string)
SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
}
/** Returns true if the style of \a parent and the style of \a child are
equivalent (and hence the children of both will appear the same). It is a
limitation of the current implementation that \a parent must be a (not
necessarily immediate) ancestor of \a child. */
{
// the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
// will not work. Firstly because of an inheritance bug in style.cpp that has
// implications too large for me to feel safe fixing, but mainly because the css spec
// requires that the computed value is inherited, not the specified value.
// we have to write parent_style then read it again, because some properties format their values
// differently depending on whether they're set or not (*cough*dash-offset*cough*)
// FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
if (style_text && *style_text) {
child_style_construction += ';';
}
}
return equal;
}
/** returns true if \a first and \a second contain all the same attributes
with the same values as each other. Note that we have to compare both
forwards and backwards to make sure we don't miss any attributes that are
in one but not the other. */
{
return false;
}
return false;
}
return true;
}
/** sets the given css attribute on this object and all its descendants.
Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
transform stuff. */
{
for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
// Unset properties which are accumulating and thus should not be set recursively.
// For example, setting opacity 0.5 on a group recursively would result in the visible opacity of 0.25 for an item in the group.
} else {
}
}
}
/** applies the given style to all the objects at the given level and below
which are between \a start_item and \a end_item, creating spans as necessary.
If \a start_item or \a end_item are NULL then the style is applied to all
objects to the beginning or end respectively. \a span_object_name is the
name of the xml for a text span (ie tspan or flowspan). */
static void recursively_apply_style(SPObject *common_ancestor, SPCSSAttr const *css, SPObject *start_item, Glib::ustring::iterator start_text_iter, SPObject *end_item, Glib::ustring::iterator end_text_iter, char const *span_object_name)
{
for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
if (start_item == child)
passed_start = true;
if (passed_start) {
recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
break;
}
// apply style
// note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
// often this means that new spans are created before child and child is modified only
if (SP_IS_STRING(child)) {
bool surround_entire_string = true;
sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style"); // better hope that prototype wasn't nonconst for a good reason
surround_entire_string = false;
// eg "abcDEFghi" -> "abc"<span>"DEF"</span>"ghi"
Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
// eg "ABCdef" -> <span>"ABC"</span>"def"
// (includes case where start_text_iter == begin())
// NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
// eg "abcDEF" -> "abc"<span>"DEF"</span>
Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
child->deleteObject();
} else
surround_entire_string = true;
}
if (surround_entire_string) {
}
} else if (child != end_item) { // not a string and we're applying to the entire object. This is easy
}
} else { // !passed_start
recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
break; // only happens when start_item == end_item (I think)
passed_start = true;
}
}
break;
}
}
/* if item is at the beginning of a tree it doesn't matter which element
it points to so for neatness we would like it to point to the highest
possible child of \a common_ancestor. There is no iterator return because
a string can never be an ancestor.
eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
like * to point to the inner span because we can apply style to that whole
span. */
static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
{
if (item == common_ancestor)
return item;
if (SP_IS_STRING(item))
return item;
for ( ; ; ) {
if (parent == common_ancestor)
break;
break;
}
return item;
}
/** empty spans: abc<span></span>def
-> abcdef */
{
if ((*item)->hasChildren()) return false;
if (is_line_break_object(*item)) return false;
(*item)->deleteObject();
return true;
}
/** inexplicable spans: abc<span style="">def</span>ghi
-> "abc""def""ghi"
the repeated strings will be merged by another operator. */
{
if (SP_IS_STRING(*item)) return false;
if (is_line_break_object(*item)) return false;
while ((*item)->hasChildren()) {
}
(*item)->deleteObject();
return true;
}
/** repeated spans: <font a>abc</font><font a>def</font>
-> <font a>abcdef</font> */
{
// also amalgamate consecutive SPStrings into one
return true;
}
// merge consecutive spans with identical styles into one
if (is_line_break_object(second)) return false;
return false;
// all our tests passed: do the merge
}
return true;
// *item is still the next object to process
}
/** redundant nesting: <font a><font b>abc</font></font>
-> <font b>abc</font>
excessive nesting: <font a><size 1>abc</size></font>
-> <font a,size 1>abc</font> */
{
if (!(*item)->hasChildren()) return false;
return false;
if (child_style && *child_style)
return true;
}
/** helper for tidy_operator_redundant_double_nesting() */
{
return false;
if (SP_IS_STRING(child)) return false;
if (is_line_break_object(child)) return false;
if (is_line_break_object(*item)) return false;
else insert_after_repr = NULL;
insert_after_repr = move_repr; // I think this will stay valid long enough. It's garbage collected these days.
}
child->deleteObject();
return true;
}
/** redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
-> <font b>abc<font a>def</font>ghi</font>
this function does its work when the parameter is the <font a> tag in the
example. You may note that this only does its work when the doubly-nested
child is the first or last. The other cases are called 'style inversion'
below, and I'm not yet convinced that the result of that operation will be
tidier in all cases. */
{
if (!(*item)->hasChildren()) return false;
if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is excessive nesting, done above
return true;
return true;
return false;
}
/** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
then compares the styles for item+child versus just child. If they're equal,
tidying is possible. */
{
return false;
if (SP_IS_STRING(child)) return false;
if (is_line_break_object(child)) return false;
if (is_line_break_object(*item)) return false;
if (child_style && *child_style) {
}
if (item_style && *item_style) {
}
if (!equal) return false;
if (prepend) {
} else
child->deleteObject();
return true;
}
/** redundant semi-nesting: <font a><font b>abc</font>def</font>
-> <font b>abc</font><font>def</font>
test this by applying a colour to a region, then a different colour to
a partially-overlapping region. */
{
if (!(*item)->hasChildren()) return false;
if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is redundant nesting, done above
return true;
return true;
return false;
}
/** helper for tidy_operator_styled_whitespace(), finds the last string object
in a paragraph which is not \a not_obj. */
{
{
if (child->hasChildren()) {
} else if (SP_IS_STRING(child))
}
return NULL;
}
/** whitespace-only spans: abc<font> </font>def
-> abc<font></font> def
abc<b><i>def</i> </b>ghi
-> abc<b><i>def</i></b> ghi */
{
if (!SP_IS_STRING(*item)) return false;
if (!g_unichar_isspace(*it)) return false;
for ( ; ; ) { // find the next string
if (next_string) {
break;
}
for ( ; ; ) { // go up one item in the xml
if (is_line_break_object(test_item)) break;
if (SP_IS_FLOWTEXT(test_item)) return false;
if (next) {
break;
}
}
break;
}
}
return true;
}
/* possible tidy operators that are not yet implemented, either because
they are difficult, occur infrequently, or because I'm not sure that the
output is tidier in all cases:
duplicate styles in line break elements: <div italic><para italic>abc</para></div>
-> <div italic><para>abc</para></div>
style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
-> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
-> <size 1><font a>abc</font>def</size>
*/
/** Recursively walks the xml tree calling a set of cleanup operations on
every child. Returns true if any changes were made to the tree.
All the tidy operators return true if they made changes, and alter their
parameter to point to the next object that should be processed, or NULL.
They must not significantly alter (ie delete) any ancestor elements of the
one they are passed.
It may be that some of the later tidy operators that I wrote are actually
general cases of the earlier operators, and hence the special-case-only
versions can be removed. I haven't analysed my work in detail to figure
out if this is so. */
{
static bool (* const tidy_operators[])(SPObject**) = {
};
bool changes = false;
continue;
}
if (child->hasChildren())
unsigned i;
for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
if (tidy_operators[i](&child)) {
changes = true;
break;
}
}
if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
}
return changes;
}
/** Applies the given CSS fragment to the characters of the given text or
flowtext object between \a start and \a end, creating or removing span
elements as necessary and optimal. */
void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
{
// in the comments in the code below, capital letters are inside the application region, lowercase are outside
} else {
}
void *rawptr = 0;
if (start_item == 0)
return; // start is at end of text
if (is_line_break_object(end_item))
/* Special case: With a tref, we only want to change its style when the whole
* string is selected, in which case the style can be applied directly to the
* tref node. If only part of the tref's string child is selected, just return. */
return;
}
/* stage 1: applying the style. Go up to the closest common ancestor of
start and end and then semi-recursively apply the style to all the
objects in between. The semi-recursion is because it's only necessary
at the beginning and end; the style can just be applied to the root
child in the middle.
eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
The recursion may involve creating new spans.
*/
// bug #168370 (consider parent transform and viewBox)
// snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
{
if ( ( ex != 0. )
&& ( ex != 1. ) ) {
}
}
recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
/* stage 2: cleanup the xml tree (of which there are multiple passes) */
/* discussion: this stage requires a certain level of inventiveness because
it's not clear what the best representation is in many cases. An ideal
implementation would provide some sort of scoring function to rate the
ugliness of a given xml tree and try to reduce said function, but providing
the various possibilities to be rated is non-trivial. Instead, I have opted
for a multi-pass technique which simply recognises known-ugly patterns and
has matching routines for optimising the patterns it finds. It's reasonably
easy to add new pattern matching processors. If everything gets disastrous
and neither option can be made to work, a fallback could be to reduce
everything to a single level of nesting and drop all pretence of
roundtrippability. */
while (tidy_xml_tree_recursively(common_ancestor)){};
// if we only modified subobjects this won't have been automatically sent
}
{
return (SP_IS_TSPAN(obj)
|| SP_IS_TEXT(obj)
|| SP_IS_FLOWTEXT(obj)
|| SP_IS_FLOWTSPAN(obj)
|| SP_IS_FLOWDIV(obj)
|| SP_IS_FLOWPARA(obj)
|| SP_IS_FLOWLINE(obj)
|| SP_IS_FLOWREGIONBREAK(obj));
}
{
return (SP_IS_TEXT(obj)
|| SP_IS_FLOWTEXT(obj));
}
{
return true; // maybe we should also check that it's not all whitespace?
return true;
}
return false;
}
/*
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 :