Skip to content

Instantly share code, notes, and snippets.

@su-v
Last active August 29, 2015 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save su-v/79a91029f184864a350d to your computer and use it in GitHub Desktop.
Save su-v/79a91029f184864a350d to your computer and use it in GitHub Desktop.
8701: rwst 2006-01-11 conditionally include config.h
8693: rwst 2006-01-10 removed superfluous includes
8640: buliabyak 2005-12-26 fixme comment
8469: cyreve 2005-12-08 bug 1356210: deleting characters after a nested tspan element adjusts kerning attributes incorrectly
8322: cyreve 2005-11-27 bug 1366029: kerning and rotation attributes are lost on the first line when deleting a line break
8315: swingincelt 2005-11-26 Replaced inline functions sp_repr_ref, sp_repr_unref and sp_repr_set_attr with their direct call equivalents
8298: swingincelt 2005-11-24 Replaced inline functions sp_repr_add_child, sp_repr_remove_child and sp_repr_change_order with their direct call equivalents
8227: pjrm 2005-11-15 Split off new file sp-textpath.h from existing sp-tspan.h.
8223: cyreve 2005-11-14 bug 1356210: kerning marks get adjusted wrongly when inserting characters after a sub-tspan
8060: pjrm 2005-11-05 #include <algorithm>, for std::min/std::max.
7953: cyreve 2005-10-04 bug 1290662: do better style management when deleting line breaks so that the resultant single paragraph takes the right styles from the right bits of the previous two paras
7880: cyreve 2005-09-14 bug 1290662: crash when deleting the entire last paragraph in one go
7801: ishmal 2005-09-08 add default null return
7785: cyreve 2005-09-06 bug 1233702: elide whitespace-only string elements in the xml in favour of placing their content in the next following string
7741: rwst 2005-09-04 minor adapations due to SPDesktop move to C++
7720: cyreve 2005-09-02 bug 1280241: new flowtext boxes no longer allow text to be entered
7684: cyreve 2005-08-29 rearrange text deletion code so that it no longer tries to do its own xml tree cleanup (badly), but instead calls the same routines that the style application stuff uses (also fixes bug 1260518 with different style spans being incorrectly merged on deletion)
7682: cyreve 2005-08-29 bug 1275318: can't type text into blank lines in flowtext
7596: cyreve 2005-08-20 bug 1261377: crash in one case of tidying up xml tree after applying style. Also fixes bug where style wouldn't get applied when excessive nesting existed
7559: cyreve 2005-08-15 bug 1259205: applying style to selected text where there's a transform attribute does double transform in some cases
7407: cyreve 2005-07-29 1247158: crash applying style which doesn't affect root text elements
7406: cyreve 2005-07-29 apply text style relied on style strings being interpreted backwards, a bug which was fixed in style.cpp rev 1.112
7396: buliabyak 2005-07-28 1247066.patch
7227: cyreve 2005-07-11 crash if you try to rotate flowtext characters, or the last character in normal text
7196: buliabyak 2005-07-09 never create flowdiv in flowtext, only flowpara
7192: cyreve 2005-07-09 bug 1224754: can't alter flowtext text via text&font dialog
7183: cyreve 2005-07-08 fix to last checkin for flowtext, which has two nested line breaking elements
7182: cyreve 2005-07-08 extract line breaks from text correctly in the case of abnormally-formed text groups
7135: buliabyak 2005-07-05 fix bug eating the first char of a text with non-line tspan in it
7010: cyreve 2005-06-20 bug 1223826: when flowtext overflows, characters added to the end get put in in reverse order
6981: cyreve 2005-06-18 flowtext would get deleted if region was resized so that no characters were visible
6924: cyreve 2005-06-10 bug 1217328: characters after rotate= attribute has run out of fields should not continue last character's rotation; also fixes related bug 1218026 and makes selection highlight work better with rotated chars
6904: buliabyak 2005-06-10 separate function to rotate by degrees
6538: pjrm 2005-04-18 #include new header file libnr/nr-point-matrix-ops.h.
6528: cyreve 2005-04-17 do sensible things for kerning, rotating and letterspacing when there is a non-empty text selection
6523: cyreve 2005-04-17 hook up keys for adding rotate= attribute to text elements
6496: cyreve 2005-04-12 deleting text selection crossing para break leaves first span intact
6494: cyreve 2005-04-12 cut and copy selected text (bug 1175441)
6340: cyreve 2005-03-26 apply letterspacing shortcut keys to the whole of the current paragraph, not just the current span
6337: cyreve 2005-03-26 convert interfaces to spcssstyle from style strings and fix more bugs in the xml tidying
6335: cyreve 2005-03-25 text xml tidy creating new line break spans sometimes
6334: cyreve 2005-03-25 crash when applying style to text elements without a style attribute, don't tidy up flowregion elements, and overrule inline style on leaf elements with user-applied style
6332: cyreve 2005-03-25 overenthusiastic comparisons of text style leading to style lossage in xml tree cleaning
6328: cyreve 2005-03-24 additional xml tidying function for common cases with overlaying different colours on text
6326: cyreve 2005-03-24 various bugs with apply style to text at the end of the string
6322: cyreve 2005-03-24 applying style to text covered by an existing span overwrites rather than appends new style
6320: cyreve 2005-03-24 functions for applying style to selected text, and wire them up to ctrl-b/ctrl-i for bold/italic
6297: cyreve 2005-03-22 make text selection inverse
6295: cyreve 2005-03-21 text selection doesn't show when it starts at the end of a line
6294: cyreve 2005-03-21 crash on overtyping selection, and displayed selection not removed on overtype
6293: cyreve 2005-03-21 kerning information placed wrongly in text after child span element
6280: cyreve 2005-03-21 optimisation of text selection and fixing some bugs in it
6272: cyreve 2005-03-20 initial implementation of text selection (keyboard only)
6260: cyreve 2005-03-19 glue kerning info back together on deleting line breaks
6254: cyreve 2005-03-19 use Layout::iterator for storing cursor position. Fixes bug 1093660 related to rtl cursor movement
6247: cyreve 2005-03-19 line breaks not being put before strings immediately following para elements, and deleting line breaks copies style wrongly
6245: cyreve 2005-03-18 spurious line break added before text in text and font dialog (bug 1166016)
6236: cyreve 2005-03-18 define a value for line-height:normal and use it consistently
6234: cyreve 2005-03-18 adjust kerning attributes on adding/removing line breaks
6205: cyreve 2005-03-16 kerning: add/remove kerns with insertion/deletion of characters, and only apply kerns to the topmost xml object
6199: cyreve 2005-03-16 move functions applying to both text and flowtext to text-editing & rename them. Also fixes crash on letter/linespacing adjustment for flowtext
6197: cyreve 2005-03-16 fix crash on pressing delete at the end of text
6195: cyreve 2005-03-16 position cursor correctly in empty text objects
6192: cyreve 2005-03-16 text editing fixes: track line breaks with preceding para tag, finish abstracting line break/remove functions to work on flowtext
6124: cyreve 2005-03-12 add several new text properties to SPStyle and rearrange some of the existing ones, also remove spurious warning on applying kerning
6117: cyreve 2005-03-11 combine the common attribute handling for text, tspan and textpath into one class
6099: cyreve 2005-03-11 fix line endings munged due to my stupid cvs client
6093: cyreve 2005-03-10 clean up xml tree properly on deleting line break, & fix various compile issues
6080: cyreve 2005-03-10 huge rewrite of text layout (and some text editing) code
5913: swingincelt 2005-02-21 Replaced wrapper functions sp_repr_attr and sp_repr_document_unref with their respective direct repr method calls.
5899: mental 2005-02-20 renamed most remaining SPRepr stuff into Inkscape::XML namespace
5816: buliabyak 2005-02-14 switched to SP_OBJECT_PREV
5780: nemies 2005-02-12 inline function removal patches from Robert Crosbie
5714: nemies 2005-02-04 preedit modes don't work correctly for multi-byte characters. See bug 1086769.
5572: mental 2005-01-23 got rid of repr-private.h
5393: ishmal 2004-12-28 change gettext() include to <glibmm/i18n.h>
5197: buliabyak 2004-12-08 first shot at flowtext editability (crashy\!)
5197 buliaby | /*
| * Parent class for text and flowtext
| *
6320 cyreve | * Authors:
5197 buliaby | * bulia byak
6320 cyreve | * Richard Hughes
5197 buliaby | *
6320 cyreve | * Copyright (C) 2004-5 authors
5197 buliaby | *
| * Released under GNU GPL, read the file 'COPYING' for more information
| */
|
8701 rwst | #ifdef HAVE_CONFIG_H
| # include "config.h"
| #endif
8693 rwst |
5197 buliaby | #include "desktop.h"
| #include "style.h"
6199 cyreve | #include "unit-constants.h"
5572 mental |
| #include "xml/repr.h"
6099 cyreve | #include "xml/attribute-record.h"
5197 buliaby |
8227 pjrm | #include "sp-textpath.h"
6099 cyreve | #include "sp-flowtext.h"
6093 cyreve | #include "sp-flowdiv.h"
6334 cyreve | #include "sp-flowregion.h"
5197 buliaby | #include "sp-tspan.h"
|
| #include "text-editing.h"
|
7684 cyreve | static bool tidy_xml_tree_recursively(SPObject *root);
|
6494 cyreve | Inkscape::Text::Layout const * te_get_layout (SPItem const *item)
6099 cyreve | {
| if (SP_IS_TEXT(item)) {
| return &(SP_TEXT(item)->layout);
| } else if (SP_IS_FLOWTEXT (item)) {
| return &(SP_FLOWTEXT(item)->layout);
| }
| return NULL;
5197 buliaby | }
|
6254 cyreve | static void te_update_layout_now (SPItem *item)
| {
| if (SP_IS_TEXT(item))
| SP_TEXT(item)->rebuildLayout();
| else if (SP_IS_FLOWTEXT (item))
| SP_FLOWTEXT(item)->rebuildLayout();
| }
|
6981 cyreve | /** Returns true if there are no visible characters on the canvas */
5197 buliaby | bool
6981 cyreve | sp_te_output_is_empty (SPItem const *item)
5197 buliaby | {
6099 cyreve | Inkscape::Text::Layout const *layout = te_get_layout(item);
6254 cyreve | return layout->begin() == layout->end();
| }
|
6981 cyreve | /** Returns true if the user has typed nothing in the text box */
| bool
| sp_te_input_is_empty (SPObject const *item)
| {
| if (SP_IS_STRING(item)) return SP_STRING(item)->string.empty();
| for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child))
| if (!sp_te_input_is_empty(child)) return false;
| return true;
| }
6254 cyreve |
| Inkscape::Text::Layout::iterator
6494 cyreve | sp_te_get_position_by_coords (SPItem const *item, NR::Point &i_p)
5197 buliaby | {
6099 cyreve | NR::Matrix im=sp_item_i2d_affine (item);
| im = im.inverse();
|
| NR::Point p = i_p * im;
| Inkscape::Text::Layout const *layout = te_get_layout(item);
6254 cyreve | return layout->getNearestCursorPositionTo(p);
6272 cyreve | }
|
6494 cyreve | std::vector<NR::Point> sp_te_create_selection_quads(SPItem const *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, NR::Matrix const &transform)
6272 cyreve | {
| if (start == end)
6297 cyreve | return std::vector<NR::Point>();
6272 cyreve | Inkscape::Text::Layout const *layout = te_get_layout(item);
| if (layout == NULL)
6297 cyreve | return std::vector<NR::Point>();
|
| return layout->createSelectionShape(start, end, transform);
5197 buliaby | }
|
6320 cyreve | void
6494 cyreve | sp_te_get_cursor_coords (SPItem const *item, Inkscape::Text::Layout::iterator const &position, NR::Point &p0, NR::Point &p1)
6320 cyreve | {
| Inkscape::Text::Layout const *layout = te_get_layout(item);
| double height, rotation;
| layout->queryCursorShape(position, &p0, &height, &rotation);
| p1 = NR::Point(p0[NR::X] + height * sin(rotation), p0[NR::Y] - height * cos(rotation));
| }
|
6494 cyreve | SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
6320 cyreve | {
| Inkscape::Text::Layout const *layout = te_get_layout(text);
| if (layout == NULL)
| return NULL;
6494 cyreve | SPObject const *pos_obj = NULL;
6320 cyreve | layout->getSourceOfCharacter(position, (void**)&pos_obj);
| if (pos_obj == NULL) pos_obj = text;
| while (SP_OBJECT_STYLE(pos_obj) == NULL)
| pos_obj = SP_OBJECT_PARENT(pos_obj); // SPStrings don't have style
| return SP_OBJECT_STYLE(pos_obj);
| }
|
5714 nemies | /*
| * 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;
| }
| */
6254 cyreve |
| Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
| {
| Inkscape::Text::Layout::iterator new_start = sp_te_delete(item, start, end);
| return sp_te_insert(item, new_start, utf8);
| }
6320 cyreve |
|
| /* ***************************************************************************************************/
| // I N S E R T I N G T E X T
6099 cyreve |
6494 cyreve | static bool is_line_break_object(SPObject const *object)
6099 cyreve | {
| return SP_IS_TEXT(object)
| || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
| || SP_IS_TEXTPATH(object)
| || SP_IS_FLOWDIV(object)
| || SP_IS_FLOWPARA(object)
| || SP_IS_FLOWLINE(object)
| || SP_IS_FLOWREGIONBREAK(object);
7684 cyreve | }
|
| /** returns the attributes for an object, or NULL if it isn't a text,
| tspan or textpath. */
| static TextTagAttributes* attributes_for_object(SPObject *object)
| {
| if (SP_IS_TSPAN(object))
| return &SP_TSPAN(object)->attributes;
| if (SP_IS_TEXT(object))
| return &SP_TEXT(object)->attributes;
| if (SP_IS_TEXTPATH(object))
| return &SP_TEXTPATH(object)->attributes;
| return NULL;
6192 cyreve | }
|
6494 cyreve | static const char * span_name_for_text_object(SPObject const *object)
6192 cyreve | {
| if (SP_IS_TEXT(object)) return "svg:tspan";
| else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
| return NULL;
| }
6099 cyreve |
6320 cyreve | /** Recursively gets the length of all the SPStrings at or below the given
| \a item. Also adds 1 for each line break encountered. */
6494 cyreve | unsigned sp_text_get_length(SPObject const *item)
6117 cyreve | {
| unsigned length = 0;
|
| if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
| if (is_line_break_object(item)) length++;
6494 cyreve | for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
6117 cyreve | if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
6124 cyreve | else length += sp_text_get_length(child);
6117 cyreve | }
| return length;
6099 cyreve | }
|
| static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Node const *old_node)
| {
| switch (old_node->type()) {
| case Inkscape::XML::ELEMENT_NODE: {
| Inkscape::XML::Node *new_node = sp_repr_new(old_node->name());
| Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
| GQuark const id_key = g_quark_from_string("id");
| for ( ; attributes ; attributes++) {
| if (attributes->key == id_key) continue;
| new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
| }
| return new_node;
| }
|
| case Inkscape::XML::TEXT_NODE:
| return sp_repr_new_text(old_node->content());
|
| case Inkscape::XML::COMMENT_NODE:
| return sp_repr_new_comment(old_node->content());
|
| case Inkscape::XML::DOCUMENT_NODE:
| return NULL; // this had better never happen
| }
| return NULL;
| }
|
6234 cyreve | /** returns the sum of the (recursive) lengths of all the SPStrings prior
| to \a item at the same level. */
6494 cyreve | static unsigned sum_sibling_text_lengths_before(SPObject const *item)
6234 cyreve | {
| unsigned char_index = 0;
| for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
| char_index += sp_text_get_length(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. */
| static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
| {
7684 cyreve | TextTagAttributes *first_attrs = attributes_for_object(first_item);
| TextTagAttributes *second_attrs = attributes_for_object(second_item);
| if (first_attrs && second_attrs)
| first_attrs->split(char_index, second_attrs);
6234 cyreve | }
|
6099 cyreve | /** 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.
| */
6234 cyreve | static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
6099 cyreve | {
| if (is_line_break_object(split_obj)) {
| Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
| SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
8315 swingin | Inkscape::GC::release(new_node);
6234 cyreve | split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
6099 cyreve | return SP_OBJECT_NEXT(split_obj);
| }
|
6234 cyreve | unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
| SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
6099 cyreve | // copy the split node
| Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
| SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
8315 swingin | Inkscape::GC::release(new_node);
6234 cyreve |
| // sort out the copied attributes (x/y/dx/dy/rotate)
| split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
6099 cyreve |
| // then move all the subsequent nodes
| split_obj = SP_OBJECT_NEXT(split_obj);
| while (split_obj) {
| Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
| SPObject *next_obj = SP_OBJECT_NEXT(split_obj); // this is about to become invalidated by removeChild()
8315 swingin | Inkscape::GC::anchor(move_repr);
6099 cyreve | SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
| SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
8315 swingin | Inkscape::GC::release(move_repr);
6099 cyreve |
| split_obj = next_obj;
| }
| return duplicate_obj->firstChild();
| }
|
6320 cyreve | /** 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. */
6254 cyreve | Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
6099 cyreve | {
| // 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
7801 ishmal | if (SP_IS_TEXT_TEXTPATH (item))
6254 cyreve | return position;
6099 cyreve |
6192 cyreve | Inkscape::Text::Layout const *layout = te_get_layout(item);
6099 cyreve | SPObject *split_obj;
| Glib::ustring::iterator split_text_iter;
6254 cyreve | if (position == layout->end())
6117 cyreve | split_obj = NULL;
| else
6254 cyreve | layout->getSourceOfCharacter(position, (void**)&split_obj, &split_text_iter);
6117 cyreve |
| if (split_obj == NULL || is_line_break_object(split_obj)) {
6192 cyreve | if (split_obj == NULL) split_obj = item->lastChild();
6117 cyreve | if (split_obj) {
| Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
| SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
8315 swingin | Inkscape::GC::release(new_node);
6117 cyreve | }
| } else if (SP_IS_STRING(split_obj)) {
6099 cyreve | Glib::ustring *string = &SP_STRING(split_obj)->string;
6234 cyreve | unsigned char_index = 0;
| for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
| char_index++;
6099 cyreve | // we need to split the entire text tree into two
6234 cyreve | SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
6099 cyreve | SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base()); // a little ugly
| string->erase(split_text_iter, string->end());
| SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
| // 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
| }
6254 cyreve | item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
| unsigned char_index = layout->iteratorToCharIndex(position);
| te_update_layout_now(item);
| item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
| return layout->charIndexToIterator(char_index + 1);
6099 cyreve | }
|
| /** finds the first SPString after the given position, including children, excluding parents */
| static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
| {
| while (start_obj) {
| if (start_obj->hasChildren()) {
| SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
| if (found_string) return found_string;
| }
| if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
| start_obj = SP_OBJECT_NEXT(start_obj);
7682 cyreve | if (is_line_break_object(start_obj))
6099 cyreve | break; // don't cross line breaks
| }
| return NULL;
| }
5197 buliaby |
6205 cyreve | /** inserts the given characters into the given string and inserts
| corresponding new x/y/dx/dy/rotate attributes into all its parents. */
| static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
| {
| unsigned char_index = 0;
| unsigned char_count = g_utf8_strlen(utf8, -1);
| Glib::ustring *string = &SP_STRING(string_item)->string;
|
| for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
| char_index++;
| string->replace(iter_at, iter_at, utf8);
|
| SPObject *parent_item = string_item;
| for ( ; ; ) {
8223 cyreve | char_index += sum_sibling_text_lengths_before(parent_item);
6205 cyreve | parent_item = SP_OBJECT_PARENT(parent_item);
7684 cyreve | TextTagAttributes *attributes = attributes_for_object(parent_item);
| if (!attributes) break;
6205 cyreve | attributes->insert(char_index, char_count);
| }
| }
|
6320 cyreve | /** 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. */
6254 cyreve | Inkscape::Text::Layout::iterator
| sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
5197 buliaby | {
7010 cyreve | if (!g_utf8_validate(utf8,-1,NULL)) {
5197 buliaby | g_warning("Trying to insert invalid utf8");
6254 cyreve | return position;
5197 buliaby | }
6099 cyreve |
| Inkscape::Text::Layout const *layout = te_get_layout(item);
| SPObject *source_obj;
| Glib::ustring::iterator iter_text;
| // we want to insert after the previous char, not before the current char.
| // it makes a difference at span boundaries
6254 cyreve | Inkscape::Text::Layout::iterator it_prev_char = position;
| bool cursor_at_start = !it_prev_char.prevCharacter();
7010 cyreve | bool cursor_at_end = position == layout->end();
6254 cyreve | layout->getSourceOfCharacter(it_prev_char, (void**)&source_obj, &iter_text);
6099 cyreve | if (SP_IS_STRING(source_obj)) {
| // the simple case
| if (!cursor_at_start) iter_text++;
7010 cyreve | SPString *string_item = SP_STRING(source_obj);
| insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
6099 cyreve | } 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;
7720 cyreve | if (source_obj->hasChildren()) {
6099 cyreve | source_obj = source_obj->firstChild();
7720 cyreve | if (SP_IS_FLOWTEXT(item)) {
| while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
| source_obj = SP_OBJECT_NEXT(source_obj);
| if (source_obj == NULL)
| source_obj = item;
| }
| }
| if (source_obj == item && SP_IS_FLOWTEXT(item)) {
| Inkscape::XML::Node *para = sp_repr_new("svg:flowPara");
| SP_OBJECT_REPR(item)->appendChild(para);
| source_obj = item->lastChild();
| }
6192 cyreve | } else
| source_obj = SP_OBJECT_NEXT(source_obj);
|
| if (source_obj) { // never fails
| SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
| if (string_item == NULL) {
| // need to add an SPString in this (pathological) case
| Inkscape::XML::Node *rstring = sp_repr_new_text("");
| SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
8315 swingin | Inkscape::GC::release(rstring);
6192 cyreve | g_assert(SP_IS_STRING(source_obj->firstChild()));
| string_item = SP_STRING(source_obj->firstChild());
| }
7010 cyreve | insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
6192 cyreve | }
6099 cyreve | }
|
6254 cyreve | item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
| unsigned char_index = layout->iteratorToCharIndex(position);
| te_update_layout_now(item);
| item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
| return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
| }
|
6320 cyreve |
| /* ***************************************************************************************************/
| // D E L E T I N G T E X T
|
6254 cyreve | /** 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. */
6192 cyreve | static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
| {
| while (from_repr->childCount()) {
| Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
8315 swingin | Inkscape::GC::anchor(child);
6192 cyreve | from_repr->removeChild(child);
| if (prepend) to_repr->addChild(child, NULL);
| else to_repr->appendChild(child);
8315 swingin | Inkscape::GC::release(child);
6192 cyreve | }
5197 buliaby | }
6099 cyreve |
7684 cyreve | /** 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. */
| static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
| {
| if (one == NULL || two == NULL)
| return text;
| SPObject *common_ancestor = one;
| if (SP_IS_STRING(common_ancestor))
| common_ancestor = SP_OBJECT_PARENT(common_ancestor);
| while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
| g_assert(common_ancestor != text);
| common_ancestor = SP_OBJECT_PARENT(common_ancestor);
| }
| return common_ancestor;
6192 cyreve | }
|
| /** 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. */
7684 cyreve | static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
6192 cyreve | {
| while ((*para_obj)->hasChildren())
| *para_obj = (*para_obj)->lastChild();
| if (SP_IS_STRING(*para_obj))
| *text_iter = SP_STRING(*para_obj)->string.end();
| }
|
7684 cyreve | /** delete the line break pointed to by \a item by merging its children into
6192 cyreve | 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. */
| static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
| {
| Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
| SPObject *next_item = NULL;
6234 cyreve | unsigned moved_char_count = sp_text_get_length(item) - 1; // the -1 is because it's going to count the line break
|
7684 cyreve | /* 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>
| */
| Inkscape::XML::Node *new_span_repr = sp_repr_new(span_name_for_text_object(root));
8322 cyreve |
| if (gchar const *a = this_repr->attribute("dx"))
| new_span_repr->setAttribute("dx", a);
| if (gchar const *a = this_repr->attribute("dy"))
| new_span_repr->setAttribute("dy", a);
| if (gchar const *a = this_repr->attribute("rotate"))
| new_span_repr->setAttribute("rotate", a);
7684 cyreve |
| SPObject *following_item = item;
| while (SP_OBJECT_NEXT(following_item) == NULL) {
| following_item = SP_OBJECT_PARENT(following_item);
| g_assert(following_item != root);
| }
| following_item = SP_OBJECT_NEXT(following_item);
|
| SPObject *new_parent_item;
| if (SP_IS_STRING(following_item)) {
| new_parent_item = SP_OBJECT_PARENT(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_item = following_item;
| *next_is_sibling = true;
| } else {
| new_parent_item = following_item;
| next_item = new_parent_item->firstChild();
| *next_is_sibling = true;
| if (next_item == NULL) {
| next_item = new_parent_item;
| *next_is_sibling = false;
6192 cyreve | }
7684 cyreve | SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
| }
|
| // 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
7953 cyreve | SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
| SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
| SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
| Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
| for ( ; attrs ; attrs++) {
| gchar const *key = g_quark_to_string(attrs->key);
| gchar const *this_attr = this_node_attrs_inherited->attribute(key);
| if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
| this_node_attrs->setAttribute(key, this_attr);
| }
| sp_repr_css_attr_unref(this_node_attrs_inherited);
| sp_repr_css_attr_unref(this_node_attrs);
| sp_repr_css_attr_unref(dest_node_attrs);
| sp_repr_css_change(new_span_repr, this_node_attrs, "style");
7684 cyreve |
| TextTagAttributes *attributes = attributes_for_object(new_parent_item);
| if (attributes)
| attributes->insert(0, moved_char_count);
| move_child_nodes(this_repr, new_span_repr);
| this_repr->parent()->removeChild(this_repr);
6192 cyreve | return next_item;
6099 cyreve | }
5197 buliaby |
6205 cyreve | /** erases the given characters from the given string and deletes the
| corresponding x/y/dx/dy/rotate attributes from all its parents. */
| 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;
| Glib::ustring *string = &SP_STRING(string_item)->string;
|
| for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
| char_index++;
| for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
| char_count++;
| string->erase(iter_from, iter_to);
6496 cyreve | SP_OBJECT_REPR(string_item)->setContent(string->c_str());
6205 cyreve |
| SPObject *parent_item = string_item;
| for ( ; ; ) {
8469 cyreve | char_index += sum_sibling_text_lengths_before(parent_item);
6205 cyreve | parent_item = SP_OBJECT_PARENT(parent_item);
6234 cyreve | TextTagAttributes *attributes = attributes_for_object(parent_item);
| if (attributes == NULL) break;
6205 cyreve |
| attributes->erase(char_index, char_count);
6496 cyreve | attributes->writeTo(SP_OBJECT_REPR(parent_item));
6205 cyreve | }
| }
|
6320 cyreve | /* 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
| lesser of \a start and \a end, because that is where the cursor should be
| put after the deletion is done. */
6254 cyreve | Inkscape::Text::Layout::iterator
| sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
| {
| if (start == end) return start;
6294 cyreve | Inkscape::Text::Layout::iterator first, last;
| if (start < end) {
| first = start;
| last = end;
| } else {
| first = end;
| last = start;
| }
6099 cyreve | Inkscape::Text::Layout const *layout = te_get_layout(item);
| SPObject *start_item, *end_item;
| Glib::ustring::iterator start_text_iter, end_text_iter;
6294 cyreve | layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
| layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
6197 cyreve | if (start_item == NULL)
6294 cyreve | return first; // start is at end of text
6192 cyreve | if (is_line_break_object(start_item))
| move_to_end_of_paragraph(&start_item, &start_text_iter);
| if (end_item == NULL) {
| end_item = item->lastChild();
| move_to_end_of_paragraph(&end_item, &end_text_iter);
| }
| else if (is_line_break_object(end_item))
| move_to_end_of_paragraph(&end_item, &end_text_iter);
6099 cyreve |
7880 cyreve | SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
|
6099 cyreve | if (start_item == end_item) {
| // the quick case where we're deleting stuff all from the same string
| if (SP_IS_STRING(start_item)) { // always true (if it_start != it_end anyway)
6205 cyreve | erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
6099 cyreve | }
| } else {
| SPObject *sub_item = start_item;
| // walk the tree from start_item to end_item, deleting as we go
| while (sub_item != item) {
| if (sub_item == end_item) {
| if (SP_IS_STRING(sub_item)) {
| Glib::ustring *string = &SP_STRING(sub_item)->string;
6205 cyreve | erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
6099 cyreve | }
| break;
| }
| if (SP_IS_STRING(sub_item)) {
6205 cyreve | SPString *string = SP_STRING(sub_item);
6099 cyreve | if (sub_item == start_item)
6205 cyreve | erase_from_spstring(string, start_text_iter, string->string.end());
6099 cyreve | else
6205 cyreve | erase_from_spstring(string, string->string.begin(), string->string.end());
6099 cyreve | }
| // walk to the next item in the tree
| if (sub_item->hasChildren())
| sub_item = sub_item->firstChild();
| else {
| SPObject *next_item;
| do {
| bool is_sibling = true;
| next_item = SP_OBJECT_NEXT(sub_item);
| if (next_item == NULL) {
| next_item = SP_OBJECT_PARENT(sub_item);
| is_sibling = false;
| }
|
6192 cyreve | if (is_line_break_object(sub_item))
| next_item = delete_line_break(item, sub_item, &is_sibling);
6099 cyreve |
| sub_item = next_item;
| if (is_sibling) break;
| // no more siblings, go up a parent
| } while (sub_item != item && sub_item != end_item);
| }
| }
| }
5197 buliaby |
7684 cyreve | while (tidy_xml_tree_recursively(common_ancestor));
6254 cyreve | te_update_layout_now(item);
| item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
6294 cyreve | layout->validateIterator(&first);
| return first;
5197 buliaby | }
|
6320 cyreve |
| /* ***************************************************************************************************/
| // 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,
7183 cyreve | 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';
6199 cyreve | for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
7183 cyreve | if (SP_IS_STRING(child))
6199 cyreve | *string += SP_STRING(child)->string;
7183 cyreve | else
| sp_te_get_ustring_multiline(child, string, pending_line_break);
| }
| if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
| *pending_line_break = true;
6199 cyreve | }
|
6320 cyreve | /** 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. */
6199 cyreve | gchar *
6494 cyreve | sp_te_get_string_multiline (SPItem const *text)
6199 cyreve | {
| Glib::ustring string;
7183 cyreve | bool pending_line_break = false;
6199 cyreve |
| if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
7183 cyreve | sp_te_get_ustring_multiline(text, &string, &pending_line_break);
6199 cyreve | if (string.empty()) return NULL;
7182 cyreve | return strdup(string.data());
6494 cyreve | }
|
| /** 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'. */
| Glib::ustring
| sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
| {
| if (start == end) return "";
| Inkscape::Text::Layout::iterator first, last;
| if (start < end) {
| first = start;
| last = end;
| } else {
| first = end;
| last = start;
| }
| Inkscape::Text::Layout const *layout = te_get_layout(text);
| Glib::ustring result;
| // not a particularly fast piece of code. I'll optimise it if people start to notice.
| for ( ; first < last ; first.nextCharacter()) {
| SPObject *char_item;
| Glib::ustring::iterator text_iter;
| layout->getSourceOfCharacter(first, (void**)&char_item, &text_iter);
| if (SP_IS_STRING(char_item))
| result += *text_iter;
| else
| result += '\n';
| }
| return result;
6199 cyreve | }
|
| void
| sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
| {
| g_return_if_fail (text != NULL);
| g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
|
| Inkscape::XML::Node *repr;
7396 buliaby | SPObject *object;
6199 cyreve | bool is_textpath = false;
| if (SP_IS_TEXT_TEXTPATH (text)) {
| repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
7396 buliaby | object = sp_object_first_child(SP_OBJECT (text));
6199 cyreve | is_textpath = true;
| } else {
| repr = SP_OBJECT_REPR (text);
7396 buliaby | object = SP_OBJECT (text);
6199 cyreve | }
|
| if (!str) str = "";
| gchar *content = g_strdup (str);
|
7396 buliaby | repr->setContent("");
| SPObject *child = object->firstChild();
7192 cyreve | while (child) {
| SPObject *next = SP_OBJECT_NEXT(child);
| if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
| repr->removeChild(SP_OBJECT_REPR(child));
| child = next;
6199 cyreve | }
|
| gchar *p = content;
| while (p) {
| gchar *e = strchr (p, '\n');
| if (is_textpath) {
| if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
| } else {
7801 ishmal | if (e) *e = '\0';
6199 cyreve | Inkscape::XML::Node *rtspan;
7196 buliaby | if (SP_IS_TEXT(text)) { // create a tspan for each line
6199 cyreve | rtspan = sp_repr_new ("svg:tspan");
8315 swingin | rtspan->setAttribute("sodipodi:role", "line");
7196 buliaby | } else { // create a flowPara for each line
| rtspan = sp_repr_new ("svg:flowPara");
| }
6199 cyreve | Inkscape::XML::Node *rstr = sp_repr_new_text(p);
8298 swingin | rtspan->addChild(rstr, NULL);
8315 swingin | Inkscape::GC::release(rstr);
6199 cyreve | repr->appendChild(rtspan);
8315 swingin | Inkscape::GC::release(rtspan);
6199 cyreve | }
| p = (e) ? e + 1 : NULL;
| }
7801 ishmal | if (is_textpath) {
6199 cyreve | Inkscape::XML::Node *rstr = sp_repr_new_text(content);
8298 swingin | repr->addChild(rstr, NULL);
8315 swingin | Inkscape::GC::release(rstr);
6199 cyreve | }
|
| g_free (content);
| }
6320 cyreve |
| /* ***************************************************************************************************/
| // K E R N I N G A N D S P A C I N G
6199 cyreve |
6528 cyreve | /** 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)
| {
| if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
| return NULL; // flowtext doesn't support kerning yet
6199 cyreve | SPText *text = SP_TEXT(item);
|
| SPObject *source_item;
| Glib::ustring::iterator source_text_iter;
6254 cyreve | text->layout.getSourceOfCharacter(position, (void**)&source_item, &source_text_iter);
6199 cyreve |
6528 cyreve | if (!SP_IS_STRING(source_item)) return NULL;
6199 cyreve | Glib::ustring *string = &SP_STRING(source_item)->string;
6528 cyreve | *char_index = sum_sibling_text_lengths_before(source_item);
6199 cyreve | for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
6528 cyreve | ++*char_index;
7801 ishmal |
6528 cyreve | return attributes_for_object(SP_OBJECT_PARENT(source_item));
| }
|
| void
| sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
| {
| // divide increment by zoom
| // divide increment by matrix expansion
7741 rwst | gdouble factor = 1 / desktop->current_zoom();
6528 cyreve | NR::Matrix t = sp_item_i2doc_affine(item);
| factor = factor / NR::expansion(t);
| by = factor * by;
|
| unsigned char_index;
| TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
| if (attributes) attributes->addToDxDy(char_index, by);
| if (start != end) {
| attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
| if (attributes) attributes->addToDxDy(char_index, -by);
| }
|
| item->updateRepr();
| item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
| }
|
| void
6904 buliaby | sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
| {
6199 cyreve | // divide increment by zoom
| // divide increment by matrix expansion
7741 rwst | gdouble factor = 1 / desktop->current_zoom();
6199 cyreve | NR::Matrix t = sp_item_i2doc_affine(text);
| factor = factor / NR::expansion(t);
6528 cyreve | SPObject *source_item;
7227 cyreve | Inkscape::Text::Layout const *layout = te_get_layout(text);
| if (layout == NULL) return;
| layout->getSourceOfCharacter(std::min(start, end), (void**)&source_item);
| if (source_item == NULL) return;
6904 buliaby | gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
|
| sp_te_adjust_rotation(text, start, end, desktop, degrees);
| }
|
| 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;
6528 cyreve |
| if (start != end) {
| for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
| attributes = text_tag_attributes_at_position(text, it, &char_index);
6904 buliaby | if (attributes) attributes->addToRotate(char_index, degrees);
6528 cyreve | }
| } else
6904 buliaby | attributes->addToRotate(char_index, degrees);
6205 cyreve |
6199 cyreve | text->updateRepr();
| text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
| }
|
| void
6528 cyreve | sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
6199 cyreve | {
| g_return_if_fail (text != NULL);
| g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
|
| Inkscape::Text::Layout const *layout = te_get_layout(text);
|
| gdouble val;
| SPObject *source_obj;
| unsigned nb_let;
6528 cyreve | layout->getSourceOfCharacter(std::min(start, end), (void**)&source_obj);
6340 cyreve | if (source_obj == NULL) // end of text
6199 cyreve | source_obj = text->lastChild();
6340 cyreve | else if (SP_IS_STRING(source_obj))
6199 cyreve | source_obj = source_obj->parent;
6340 cyreve |
6199 cyreve | SPStyle *style = SP_OBJECT_STYLE (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. */
| if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
| if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
| val = style->font_size.computed * style->letter_spacing.value;
| } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
| val = style->font_size.computed * style->letter_spacing.value * 0.5;
| } else { // unknown unit - should not happen
| val = 0.0;
| }
| } else { // there's a real value in .computed, or it's zero
| val = style->letter_spacing.computed;
| }
|
6528 cyreve | if (start == end) {
| while (!is_line_break_object(source_obj)) // move up the tree so we apply to the closest paragraph
| source_obj = SP_OBJECT_PARENT(source_obj);
| nb_let = sp_text_get_length(source_obj);
| } else {
| nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
| }
|
6199 cyreve | // 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
7741 rwst | gdouble const zoom = desktop->current_zoom();
6199 cyreve | gdouble const zby = (by
| / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
| / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
| val += zby;
|
6528 cyreve | if (start == end) {
| // set back value to entire paragraph
| style->letter_spacing.normal = FALSE;
| if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
| if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
| style->letter_spacing.value = val / style->font_size.computed;
| } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
| style->letter_spacing.value = val / style->font_size.computed * 2;
| }
| } else {
| style->letter_spacing.computed = val;
| }
|
| style->letter_spacing.set = TRUE;
6199 cyreve | } else {
6528 cyreve | // apply to selection only
| SPCSSAttr *css = sp_repr_css_attr_new();
| char string_val[40];
| g_snprintf(string_val, sizeof(string_val), "%f", val);
| sp_repr_css_set_property(css, "letter-spacing", string_val);
| sp_te_apply_style(text, start, end, css);
| sp_repr_css_attr_unref(css);
| }
6199 cyreve |
| text->updateRepr();
| text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
| }
|
| void
6528 cyreve | 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
6199 cyreve | g_return_if_fail (text != NULL);
| g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
|
| Inkscape::Text::Layout const *layout = te_get_layout(text);
| SPStyle *style = SP_OBJECT_STYLE (text);
|
| if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
| style->line_height.set = TRUE;
| style->line_height.inherit = FALSE;
| style->line_height.normal = FALSE;
| style->line_height.unit = SP_CSS_UNIT_PERCENT;
6236 cyreve | style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
6199 cyreve | }
|
| unsigned line_count = layout->lineIndex(layout->end());
| double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
| double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
| if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
|
| // divide increment by zoom and by the number of lines,
| // so that the entire object is expanded by by pixels
7741 rwst | gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
6199 cyreve |
| // divide increment by matrix expansion
| NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
| zby = zby / NR::expansion(t);
|
| switch (style->line_height.unit) {
| 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
| else style->line_height.computed *= (average_line_height + zby) / average_line_height;
| style->line_height.value = style->line_height.computed;
| break;
| case SP_CSS_UNIT_EM:
| case SP_CSS_UNIT_EX:
| case SP_CSS_UNIT_PERCENT:
| // multiplier-type units, stored in value
| if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
| else style->line_height.value *= (average_line_height + zby) / average_line_height;
| break;
| // absolute-type units
| case SP_CSS_UNIT_PX:
| style->line_height.computed += zby;
| style->line_height.value = style->line_height.computed;
| break;
| case SP_CSS_UNIT_PT:
| style->line_height.computed += zby * PT_PER_PX;
| style->line_height.value = style->line_height.computed;
| break;
| case SP_CSS_UNIT_PC:
| style->line_height.computed += zby * (PT_PER_PX / 12);
| style->line_height.value = style->line_height.computed;
| break;
| case SP_CSS_UNIT_MM:
| style->line_height.computed += zby * MM_PER_PX;
| style->line_height.value = style->line_height.computed;
| break;
| case SP_CSS_UNIT_CM:
| style->line_height.computed += zby * CM_PER_PX;
| style->line_height.value = style->line_height.computed;
| break;
| case SP_CSS_UNIT_IN:
| style->line_height.computed += zby * IN_PER_PX;
| style->line_height.value = style->line_height.computed;
| break;
| }
| text->updateRepr();
| text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
| }
|
6320 cyreve |
| /* ***************************************************************************************************/
| // 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). */
| static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
| {
| SPStyle *new_style = sp_style_new();
| sp_style_merge_from_style_string(new_style, style_string);
6322 cyreve | gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
| if (item_style_string && *item_style_string)
| sp_style_merge_from_style_string(new_style, item_style_string);
6320 cyreve | gchar *new_style_string = sp_style_write_string(new_style);
| sp_style_unref(new_style);
| SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
| g_free(new_style_string);
| }
|
6332 cyreve | /** 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. */
| static bool objects_have_equal_style(SPObject const *parent, SPObject const *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.
| g_assert(parent->isAncestorOf(child));
| gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
| // 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*)
| SPStyle *parent_spstyle = sp_style_new();
| sp_style_merge_from_style_string(parent_spstyle, parent_style);
| g_free(parent_style);
| parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
| sp_style_unref(parent_spstyle);
|
7406 cyreve | Glib::ustring child_style_construction(parent_style);
6332 cyreve | while (child != parent) {
8640 buliaby | // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
6334 cyreve | char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
| if (style_text && *style_text) {
7406 cyreve | child_style_construction += ';';
6334 cyreve | child_style_construction += style_text;
| }
6332 cyreve | child = SP_OBJECT_PARENT(child);
| }
| SPStyle *child_spstyle = sp_style_new();
| sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
| gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
| sp_style_unref(child_spstyle);
| bool equal = !strcmp(child_style, parent_style);
| g_free(child_style);
| g_free(parent_style);
| return equal;
6334 cyreve | }
|
6337 cyreve | /** 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. */
| static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
| {
| Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
| for ( ; attrs ; attrs++) {
| gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
| if (other_attr == NULL || strcmp(attrs->value, other_attr))
| return false;
| }
| attrs = second->attributeList();
| for ( ; attrs ; attrs++) {
| gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
| if (other_attr == NULL || strcmp(attrs->value, other_attr))
| return false;
| }
| return true;
6332 cyreve | }
|
7596 cyreve | /** 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. */
| static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
| {
| sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
|
| for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
| if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
| // 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.
| SPCSSAttr *css_recurse = sp_repr_css_attr_new();
| sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
| sp_repr_css_set_property(css_recurse, "opacity", NULL);
| apply_css_recursive(child, css_recurse);
| sp_repr_css_attr_unref(css_recurse);
| } else {
| apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
| }
| }
| }
|
6320 cyreve | /** 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). */
6337 cyreve | 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)
6320 cyreve | {
| bool passed_start = start_item == NULL ? true : false;
|
| for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
| if (start_item == child)
| passed_start = true;
|
| if (passed_start) {
| if (end_item && child->isAncestorOf(end_item)) {
6337 cyreve | recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
6320 cyreve | 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)) {
| SPString *string_item = SP_STRING(child);
| bool surround_entire_string = true;
|
| Inkscape::XML::Node *child_span = sp_repr_new(span_object_name);
6337 cyreve | sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style"); // better hope that prototype wasn't nonconst for a good reason
6320 cyreve | SPObject *prev_item = SP_OBJECT_PREV(child);
| Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
|
| if (child == start_item || child == end_item) {
| surround_entire_string = false;
| if (start_item == end_item && start_text_iter != string_item->string.begin()) {
| // eg "abcDEFghi" -> "abc"<span>"DEF"</span>"ghi"
| unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
| unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
|
| Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
| SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
| SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
8315 swingin | Inkscape::GC::release(text_before);
6320 cyreve | Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
| child_span->appendChild(text_in_span);
8315 swingin | Inkscape::GC::release(text_in_span);
6320 cyreve | SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
|
| } else if (child == end_item) {
| // eg "ABCdef" -> <span>"ABC"</span>"def"
| // (includes case where start_text_iter == begin())
6332 cyreve | // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
6320 cyreve | unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
|
| SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
| Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(0, end_char_index).c_str());
| child_span->appendChild(text_in_span);
8315 swingin | Inkscape::GC::release(text_in_span);
6320 cyreve | SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
|
| } else if (start_text_iter != string_item->string.begin()) {
| // eg "abcDEF" -> "abc"<span>"DEF"</span>
| unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
|
| Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
| SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
| SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
8315 swingin | Inkscape::GC::release(text_before);
6320 cyreve | Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index).c_str());
| child_span->appendChild(text_in_span);
8315 swingin | Inkscape::GC::release(text_in_span);
6320 cyreve | child->deleteObject();
| child = sp_object_get_child_by_repr(common_ancestor, child_span);
|
| } else
| surround_entire_string = true;
| }
| if (surround_entire_string) {
| Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
| SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
8315 swingin | Inkscape::GC::anchor(child_repr);
6320 cyreve | SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
| child_span->appendChild(child_repr);
8315 swingin | Inkscape::GC::release(child_repr);
6320 cyreve | child = sp_object_get_child_by_repr(common_ancestor, child_span);
| }
8315 swingin | Inkscape::GC::release(child_span);
6320 cyreve |
| } else if (child != end_item) { // not a string and we're applying to the entire object. This is easy
7596 cyreve | apply_css_recursive(child, css);
6320 cyreve | }
|
| } else { // !passed_start
| if (child->isAncestorOf(start_item)) {
6337 cyreve | recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
6320 cyreve | if (end_item && child->isAncestorOf(end_item))
| break; // only happens when start_item == end_item (I think)
| passed_start = true;
| }
| }
|
| if (end_item == child)
| 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)
| {
6326 cyreve | if (item == common_ancestor)
| return item;
6320 cyreve | if (SP_IS_STRING(item))
| if (text_iter != SP_STRING(item)->string.begin())
| return item;
| for ( ; ; ) {
| SPObject *parent = SP_OBJECT_PARENT(item);
| if (parent == common_ancestor)
| break;
| if (item != parent->firstChild())
| break;
| item = parent;
| }
| return item;
| }
|
|
| /** empty spans: abc<span></span>def
| -> abcdef */
| static bool tidy_operator_empty_spans(SPObject **item)
| {
| if ((*item)->hasChildren()) return false;
| if (is_line_break_object(*item)) return false;
| if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
| SPObject *next = SP_OBJECT_NEXT(*item);
| (*item)->deleteObject();
| *item = next;
| return true;
| }
|
| /** inexplicable spans: abc<span style="">def</span>ghi
| -> "abc""def""ghi"
| the repeated strings will be merged by another operator. */
| static bool tidy_operator_inexplicable_spans(SPObject **item)
| {
| if (SP_IS_STRING(*item)) return false;
| if (is_line_break_object(*item)) return false;
| TextTagAttributes *attrs = attributes_for_object(*item);
| if (attrs && attrs->anyAttributesSet()) return false;
6332 cyreve | if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
6320 cyreve | SPObject *next = *item;
| while ((*item)->hasChildren()) {
| Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
8315 swingin | Inkscape::GC::anchor(repr);
6320 cyreve | SP_OBJECT_REPR(*item)->removeChild(repr);
| SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
8315 swingin | Inkscape::GC::release(repr);
6320 cyreve | next = SP_OBJECT_NEXT(next);
| }
| (*item)->deleteObject();
| *item = next;
| return true;
| }
|
| /** repeated spans: <font a>abc</font><font a>def</font>
| -> <font a>abcdef</font> */
| static bool tidy_operator_repeated_spans(SPObject **item)
| {
7684 cyreve | SPObject *first = *item;
| SPObject *second = SP_OBJECT_NEXT(first);
| if (second == NULL) return false;
|
| Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
| Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
|
| if (first_repr->type() != second_repr->type()) return false;
|
| if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
| // also amalgamate consecutive SPStrings into one
| Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
| SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
| second_repr->parent()->removeChild(second_repr);
| return true;
| }
|
| // merge consecutive spans with identical styles into one
| if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
| if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
| if (is_line_break_object(second)) return false;
| gchar const *first_style = first_repr->attribute("style");
| gchar const *second_style = second_repr->attribute("style");
| if (!((first_style == NULL && second_style == NULL)
| || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
| return false;
|
| // all our tests passed: do the merge
| TextTagAttributes *attributes_first = attributes_for_object(first);
| TextTagAttributes *attributes_second = attributes_for_object(second);
| if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
| TextTagAttributes attributes_first_copy = *attributes_first;
| attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
| }
| move_child_nodes(second_repr, first_repr);
| second_repr->parent()->removeChild(second_repr);
| return true;
| // *item is still the next object to process
6320 cyreve | }
|
| /** 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> */
| static bool tidy_operator_excessive_nesting(SPObject **item)
| {
| if (!(*item)->hasChildren()) return false;
| if ((*item)->firstChild() != (*item)->lastChild()) return false;
6334 cyreve | if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
| return false;
6320 cyreve | if (SP_IS_STRING((*item)->firstChild())) return false;
| if (is_line_break_object((*item)->firstChild())) return false;
| TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
| if (attrs && attrs->anyAttributesSet()) return false;
| gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
| if (child_style && *child_style)
| overwrite_style_with_string(*item, child_style);
| move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
| (*item)->firstChild()->deleteObject();
| return true;
| }
|
6326 cyreve | /** helper for tidy_operator_redundant_double_nesting() */
| static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
| {
6334 cyreve | if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
| return false;
6326 cyreve | if (SP_IS_STRING(child)) return false;
| if (is_line_break_object(child)) return false;
6335 cyreve | if (is_line_break_object(*item)) return false;
6326 cyreve | TextTagAttributes *attrs = attributes_for_object(child);
| if (attrs && attrs->anyAttributesSet()) return false;
6332 cyreve | if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
6326 cyreve |
| Inkscape::XML::Node *insert_after_repr;
| if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
| else insert_after_repr = SP_OBJECT_REPR(*item);
| while (SP_OBJECT_REPR(child)->childCount()) {
| Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
8315 swingin | Inkscape::GC::anchor(move_repr);
6326 cyreve | SP_OBJECT_REPR(child)->removeChild(move_repr);
| SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
8315 swingin | Inkscape::GC::release(move_repr);
6326 cyreve | insert_after_repr = move_repr; // I think this will stay valid long enough. It's garbage collected these days.
| }
| child->deleteObject();
6328 cyreve | return true;
6326 cyreve | }
|
| /** 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. */
| static bool tidy_operator_redundant_double_nesting(SPObject **item)
| {
| if (!(*item)->hasChildren()) return false;
| if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is excessive nesting, done above
| if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
| return true;
| if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
| return true;
| return false;
| }
|
6328 cyreve | /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
6337 cyreve | then compares the styles for item+child versus just child. If they're equal,
| tidying is possible. */
6328 cyreve | static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
| {
6334 cyreve | if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
| return false;
6328 cyreve | if (SP_IS_STRING(child)) return false;
| if (is_line_break_object(child)) return false;
6335 cyreve | if (is_line_break_object(*item)) return false;
6328 cyreve | TextTagAttributes *attrs = attributes_for_object(child);
| if (attrs && attrs->anyAttributesSet()) return false;
| attrs = attributes_for_object(*item);
| if (attrs && attrs->anyAttributesSet()) return false;
6337 cyreve |
| SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
| SPCSSAttr *css_child_only = sp_repr_css_attr_new();
6328 cyreve | gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
6337 cyreve | if (child_style && *child_style) {
| sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
| sp_repr_css_attr_add_from_string(css_child_only, child_style);
| }
| gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
| if (item_style && *item_style) {
| sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
| }
| bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
| sp_repr_css_attr_unref(css_child_and_item);
| sp_repr_css_attr_unref(css_child_only);
| if (!equal) return false;
6328 cyreve |
| Inkscape::XML::Node *new_span = sp_repr_new(SP_OBJECT_REPR(*item)->name());
7596 cyreve | if (prepend) {
| SPObject *prev = SP_OBJECT_PREV(*item);
| SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
| } else
6328 cyreve | SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
| new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
| move_child_nodes(SP_OBJECT_REPR(child), new_span);
8315 swingin | Inkscape::GC::release(new_span);
6328 cyreve | child->deleteObject();
| return true;
| }
|
| /** redundant semi-nesting: <font a><font b>abc</font>def</font>
6332 cyreve | -> <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. */
6328 cyreve | static bool tidy_operator_redundant_semi_nesting(SPObject **item)
| {
| if (!(*item)->hasChildren()) return false;
| if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is redundant nesting, done above
| if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
| return true;
| if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
| return true;
| return false;
| }
|
7785 cyreve | /** helper for tidy_operator_styled_whitespace(), finds the last string object
| in a paragraph which is not \a not_obj. */
| static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
| {
| for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
| {
| if (child == not_obj) continue;
| if (child->hasChildren()) {
| SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
| if (ret) return ret;
| } else if (SP_IS_STRING(child))
| return SP_STRING(child);
| }
7801 ishmal | return NULL;
7785 cyreve | }
|
| /** 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 */
| static bool tidy_operator_styled_whitespace(SPObject **item)
| {
| if (!SP_IS_STRING(*item)) return false;
| Glib::ustring const &str = SP_STRING(*item)->string;
| for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
| if (!g_unichar_isspace(*it)) return false;
7801 ishmal |
7785 cyreve | SPObject *test_item = *item;
| SPString *next_string;
| for ( ; ; ) { // find the next string
| next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
| if (next_string) {
| next_string->string.insert(0, str);
| break;
| }
| for ( ; ; ) { // go up one item in the xml
| test_item = SP_OBJECT_PARENT(test_item);
| if (is_line_break_object(test_item)) break;
| SPObject *next = SP_OBJECT_NEXT(test_item);
| if (next) {
| test_item = next;
| break;
| }
| }
| if (is_line_break_object(test_item)) { // no next string, see if there's a prev string
| next_string = find_last_string_child_not_equal_to(test_item, *item);
| if (next_string == NULL) return false; // an empty paragraph
| next_string->string += str;
| break;
| }
| }
| SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
| SPObject *delete_obj = *item;
| *item = SP_OBJECT_NEXT(*item);
| delete_obj->deleteObject();
| return true;
| }
|
6326 cyreve | /* 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:
6334 cyreve | duplicate styles in line break elements: <div italic><para italic>abc</para></div>
| -> <div italic><para>abc</para></div>
6320 cyreve | 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>
6326 cyreve | mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
6320 cyreve | -> <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
6328 cyreve | 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. */
6320 cyreve | static bool tidy_xml_tree_recursively(SPObject *root)
| {
| static bool (* const tidy_operators[])(SPObject**) = {
| tidy_operator_empty_spans,
| tidy_operator_inexplicable_spans,
| tidy_operator_repeated_spans,
6326 cyreve | tidy_operator_excessive_nesting,
6328 cyreve | tidy_operator_redundant_double_nesting,
7785 cyreve | tidy_operator_redundant_semi_nesting,
| tidy_operator_styled_whitespace
6320 cyreve | };
| bool changes = false;
|
| for (SPObject *child = root->firstChild() ; child != NULL ; ) {
6334 cyreve | if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child)) {
| child = SP_OBJECT_NEXT(child);
| continue;
| }
6320 cyreve | if (child->hasChildren())
| changes |= tidy_xml_tree_recursively(child);
|
| 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]))
| child = SP_OBJECT_NEXT(child);
| }
| 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. */
6337 cyreve | void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
6320 cyreve | {
| // in the comments in the code below, capital letters are inside the application region, lowercase are outside
| if (start == end) return;
| Inkscape::Text::Layout::iterator first, last;
| if (start < end) {
| first = start;
| last = end;
| } else {
| first = end;
| last = start;
| }
| Inkscape::Text::Layout const *layout = te_get_layout(text);
| SPObject *start_item, *end_item;
| Glib::ustring::iterator start_text_iter, end_text_iter;
| layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
| layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
| if (start_item == NULL)
| return; // start is at end of text
| if (is_line_break_object(start_item))
| start_item = SP_OBJECT_NEXT(start_item);
| if (is_line_break_object(end_item))
7684 cyreve | end_item = SP_OBJECT_NEXT(end_item);
| if (end_item == NULL) end_item = text;
6320 cyreve |
| /* 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
7801 ishmal | child in the middle.
6320 cyreve | eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
| The recursion may involve creating new spans.
| */
7684 cyreve | SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
6320 cyreve | start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
| end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
6337 cyreve | recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
6320 cyreve |
| /* 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));
7407 cyreve |
| // if we only modified subobjects this won't have been automatically sent
| text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
6320 cyreve | }
|
5197 buliaby | /*
| 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 :
------------------------------------------------------------
revno: 7785
committer: cyreve
timestamp: Tue 2005-09-06 20:36:57 +0000
message:
bug 1233702: elide whitespace-only string elements in the xml in favour of placing their content in the next following string
------------------------------------------------------------
Added by su_v: current URL for bug report:
https://bugs.launchpad.net/inkscape/+bug/sf1233702
https://bugs.launchpad.net/inkscape/+bug/166680
------------------------------------------------------------
=== modified file 'src/text-editing.cpp'
--- src/text-editing.cpp 2005-09-04 14:30:06 +0000
+++ src/text-editing.cpp 2005-09-06 20:36:57 +0000
@@ -1466,6 +1466,63 @@
return false;
}
+/** helper for tidy_operator_styled_whitespace(), finds the last string object
+in a paragraph which is not \a not_obj. */
+static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
+{
+ for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
+ {
+ if (child == not_obj) continue;
+ if (child->hasChildren()) {
+ SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
+ if (ret) return ret;
+ } else if (SP_IS_STRING(child))
+ return SP_STRING(child);
+ }
+}
+
+/** 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 */
+static bool tidy_operator_styled_whitespace(SPObject **item)
+{
+ if (!SP_IS_STRING(*item)) return false;
+ Glib::ustring const &str = SP_STRING(*item)->string;
+ for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
+ if (!g_unichar_isspace(*it)) return false;
+
+ SPObject *test_item = *item;
+ SPString *next_string;
+ for ( ; ; ) { // find the next string
+ next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
+ if (next_string) {
+ next_string->string.insert(0, str);
+ break;
+ }
+ for ( ; ; ) { // go up one item in the xml
+ test_item = SP_OBJECT_PARENT(test_item);
+ if (is_line_break_object(test_item)) break;
+ SPObject *next = SP_OBJECT_NEXT(test_item);
+ if (next) {
+ test_item = next;
+ break;
+ }
+ }
+ if (is_line_break_object(test_item)) { // no next string, see if there's a prev string
+ next_string = find_last_string_child_not_equal_to(test_item, *item);
+ if (next_string == NULL) return false; // an empty paragraph
+ next_string->string += str;
+ break;
+ }
+ }
+ SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
+ SPObject *delete_obj = *item;
+ *item = SP_OBJECT_NEXT(*item);
+ delete_obj->deleteObject();
+ 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:
@@ -1497,7 +1554,8 @@
tidy_operator_repeated_spans,
tidy_operator_excessive_nesting,
tidy_operator_redundant_double_nesting,
- tidy_operator_redundant_semi_nesting
+ tidy_operator_redundant_semi_nesting,
+ tidy_operator_styled_whitespace
};
bool changes = false;
@su-v
Copy link
Author

su-v commented Jul 24, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment