Code

fix recurstion gaffe, comment
[inkscape.git] / src / text-editing.cpp
1 /*
2  * Parent class for text and flowtext
3  *
4  * Authors:
5  *   bulia byak
6  *   Richard Hughes
7  *
8  * Copyright (C) 2004-5 authors
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include "desktop.h"
18 #include "style.h"
19 #include "unit-constants.h"
21 #include "xml/repr.h"
22 #include "xml/attribute-record.h"
24 #include "sp-textpath.h"
25 #include "sp-flowtext.h"
26 #include "sp-flowdiv.h"
27 #include "sp-flowregion.h"
28 #include "sp-tspan.h"
30 #include "text-editing.h"
32 static bool tidy_xml_tree_recursively(SPObject *root);
34 Inkscape::Text::Layout const * te_get_layout (SPItem const *item)
35 {
36     if (SP_IS_TEXT(item)) {
37         return &(SP_TEXT(item)->layout);
38     } else if (SP_IS_FLOWTEXT (item)) {
39         return &(SP_FLOWTEXT(item)->layout);
40     }
41     return NULL;
42 }
44 static void te_update_layout_now (SPItem *item)
45 {
46     if (SP_IS_TEXT(item))
47         SP_TEXT(item)->rebuildLayout();
48     else if (SP_IS_FLOWTEXT (item))
49         SP_FLOWTEXT(item)->rebuildLayout();
50 }
52 /** Returns true if there are no visible characters on the canvas */
53 bool
54 sp_te_output_is_empty (SPItem const *item)
55 {
56     Inkscape::Text::Layout const *layout = te_get_layout(item);
57     return layout->begin() == layout->end();
58 }
60 /** Returns true if the user has typed nothing in the text box */
61 bool
62 sp_te_input_is_empty (SPObject const *item)
63 {
64     if (SP_IS_STRING(item)) return SP_STRING(item)->string.empty();
65     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child))
66         if (!sp_te_input_is_empty(child)) return false;
67     return true;
68 }
70 Inkscape::Text::Layout::iterator
71 sp_te_get_position_by_coords (SPItem const *item, NR::Point &i_p)
72 {
73     NR::Matrix  im=sp_item_i2d_affine (item);
74     im = im.inverse();
76     NR::Point p = i_p * im;
77     Inkscape::Text::Layout const *layout = te_get_layout(item);
78     return layout->getNearestCursorPositionTo(p);
79 }
81 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)
82 {
83     if (start == end)
84         return std::vector<NR::Point>();
85     Inkscape::Text::Layout const *layout = te_get_layout(item);
86     if (layout == NULL)
87         return std::vector<NR::Point>();
89     return layout->createSelectionShape(start, end, transform);
90 }
92 void
93 sp_te_get_cursor_coords (SPItem const *item, Inkscape::Text::Layout::iterator const &position, NR::Point &p0, NR::Point &p1)
94 {
95     Inkscape::Text::Layout const *layout = te_get_layout(item);
96     double height, rotation;
97     layout->queryCursorShape(position, &p0, &height, &rotation);
98     p1 = NR::Point(p0[NR::X] + height * sin(rotation), p0[NR::Y] - height * cos(rotation));
99 }
101 SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
103     Inkscape::Text::Layout const *layout = te_get_layout(text);
104     if (layout == NULL)
105         return NULL;
106     SPObject const *pos_obj = NULL;
107     layout->getSourceOfCharacter(position, (void**)&pos_obj);
108     if (pos_obj == NULL) pos_obj = text;
109     while (SP_OBJECT_STYLE(pos_obj) == NULL)
110         pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
111     return SP_OBJECT_STYLE(pos_obj);
114 /*
115  * for debugging input
116  *
117 char * dump_hexy(const gchar * utf8)
119     static char buffer[1024];
121     buffer[0]='\0';
122     for (const char *ptr=utf8; *ptr; ptr++) {
123         sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
124     }
125     return buffer;
127 */
129 Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
131     Inkscape::Text::Layout::iterator new_start = sp_te_delete(item, start, end);
132     return sp_te_insert(item, new_start, utf8);
136 /* ***************************************************************************************************/
137 //                             I N S E R T I N G   T E X T
139 static bool is_line_break_object(SPObject const *object)
141     return    SP_IS_TEXT(object)
142            || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
143            || SP_IS_TEXTPATH(object)
144            || SP_IS_FLOWDIV(object)
145            || SP_IS_FLOWPARA(object)
146            || SP_IS_FLOWLINE(object)
147            || SP_IS_FLOWREGIONBREAK(object);
150 /** returns the attributes for an object, or NULL if it isn't a text,
151 tspan or textpath. */
152 static TextTagAttributes* attributes_for_object(SPObject *object)
154     if (SP_IS_TSPAN(object))
155         return &SP_TSPAN(object)->attributes;
156     if (SP_IS_TEXT(object))
157         return &SP_TEXT(object)->attributes;
158     if (SP_IS_TEXTPATH(object))
159         return &SP_TEXTPATH(object)->attributes;
160     return NULL;
163 static const char * span_name_for_text_object(SPObject const *object)
165     if (SP_IS_TEXT(object)) return "svg:tspan";
166     else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
167     return NULL;
170 /** Recursively gets the length of all the SPStrings at or below the given
171 \a item. Also adds 1 for each line break encountered. */
172 unsigned sp_text_get_length(SPObject const *item)
174     unsigned length = 0;
176     if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
177     if (is_line_break_object(item)) length++;
178     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
179         if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
180         else length += sp_text_get_length(child);
181     }
182     return length;
185 /** Recursively gets the length of all the SPStrings at or below the given
186 \a item, before and not including \a upto. Also adds 1 for each line break encountered. */
187 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
189     unsigned length = 0;
191     if (SP_IS_STRING(item)) {
192         return SP_STRING(item)->string.length();
193     }
194     if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
195         if (item != SP_OBJECT_PARENT(item)->firstChild()) {
196             // add 1 for each newline
197             length++;
198         }
199     }
200     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
201         if (upto && child == upto) {
202             // hit upto, return immediately
203             return length;
204         }
205         if (SP_IS_STRING(child)) {
206             length += SP_STRING(child)->string.length();
207         }
208         else {
209             if (upto && child->isAncestorOf(upto)) {
210                 // upto is below us, recurse and break loop
211                 length += sp_text_get_length_upto(child, upto);
212                 return length;
213             } else {
214                 // recurse and go to the next sibling
215                 length += sp_text_get_length_upto(child, upto);
216             }
217         }
218     }
219     return length;
222 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Node const *old_node)
224     switch (old_node->type()) {
225         case Inkscape::XML::ELEMENT_NODE: {
226             Inkscape::XML::Node *new_node = sp_repr_new(old_node->name());
227             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
228             GQuark const id_key = g_quark_from_string("id");
229             for ( ; attributes ; attributes++) {
230                 if (attributes->key == id_key) continue;
231                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
232             }
233             return new_node;
234         }
236         case Inkscape::XML::TEXT_NODE:
237             return sp_repr_new_text(old_node->content());
239         case Inkscape::XML::COMMENT_NODE:
240             return sp_repr_new_comment(old_node->content());
242         case Inkscape::XML::DOCUMENT_NODE:
243             return NULL;   // this had better never happen
244     }
245     return NULL;
248 /** returns the sum of the (recursive) lengths of all the SPStrings prior
249 to \a item at the same level. */
250 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
252     unsigned char_index = 0;
253     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
254         char_index += sp_text_get_length(sibling);
255     return char_index;
258 /** splits the attributes for the first object at the given \a char_index
259 and moves the ones after that point into \a second_item. */
260 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
262     TextTagAttributes *first_attrs = attributes_for_object(first_item);
263     TextTagAttributes *second_attrs = attributes_for_object(second_item);
264     if (first_attrs && second_attrs)
265         first_attrs->split(char_index, second_attrs);
268 /** recursively divides the XML node tree into two objects: the original will
269 contain all objects up to and including \a split_obj and the returned value
270 will be the new leaf which represents the copy of \a split_obj and extends
271 down the tree with new elements all the way to the common root which is the
272 parent of the first line break node encountered.
273 */
274 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
276     if (is_line_break_object(split_obj)) {
277         Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
278         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
279         Inkscape::GC::release(new_node);
280         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
281         return SP_OBJECT_NEXT(split_obj);
282     }
284     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
285     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
286     // copy the split node
287     Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
288     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
289     Inkscape::GC::release(new_node);
291     // sort out the copied attributes (x/y/dx/dy/rotate)
292     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
294     // then move all the subsequent nodes
295     split_obj = SP_OBJECT_NEXT(split_obj);
296     while (split_obj) {
297         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
298         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
299         Inkscape::GC::anchor(move_repr);
300         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
301         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
302         Inkscape::GC::release(move_repr);
304         split_obj = next_obj;
305     }
306     return duplicate_obj->firstChild();
309 /** inserts a new line break at the given position in a text or flowtext
310 object. If the position is in the middle of a span, the XML tree must be
311 chopped in two such that the line can be created at the root of the text
312 element. Returns an iterator pointing just after the inserted break. */
313 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
315     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
316     // texpaths attached to the same path, with a vertical shift
317     if (SP_IS_TEXT_TEXTPATH (item))
318         return position;
320     Inkscape::Text::Layout const *layout = te_get_layout(item);
321     SPObject *split_obj;
322     Glib::ustring::iterator split_text_iter;
323     if (position == layout->end())
324         split_obj = NULL;
325     else
326         layout->getSourceOfCharacter(position, (void**)&split_obj, &split_text_iter);
328     if (split_obj == NULL || is_line_break_object(split_obj)) {
329         if (split_obj == NULL) split_obj = item->lastChild();
330         if (split_obj) {
331             Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
332             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
333             Inkscape::GC::release(new_node);
334         }
335     } else if (SP_IS_STRING(split_obj)) {
336         Glib::ustring *string = &SP_STRING(split_obj)->string;
337         unsigned char_index = 0;
338         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
339             char_index++;
340         // we need to split the entire text tree into two
341         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
342         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
343         string->erase(split_text_iter, string->end());
344         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
345         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
346     } else {
347         // TODO
348         // I think the only case to put here is arbitrary gaps, which nobody uses yet
349     }
350     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
351     unsigned char_index = layout->iteratorToCharIndex(position);
352     te_update_layout_now(item);
353     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
354     return layout->charIndexToIterator(char_index + 1);
357 /** finds the first SPString after the given position, including children, excluding parents */
358 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
360     while (start_obj) {
361         if (start_obj->hasChildren()) {
362             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
363             if (found_string) return found_string;
364         }
365         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
366         start_obj = SP_OBJECT_NEXT(start_obj);
367         if (is_line_break_object(start_obj))
368             break;   // don't cross line breaks
369     }
370     return NULL;
373 /** inserts the given characters into the given string and inserts
374 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
375 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
377     unsigned char_index = 0;
378     unsigned char_count = g_utf8_strlen(utf8, -1);
379     Glib::ustring *string = &SP_STRING(string_item)->string;
381     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
382         char_index++;
383     string->replace(iter_at, iter_at, utf8);
385     SPObject *parent_item = string_item;
386     for ( ; ; ) {
387         char_index += sum_sibling_text_lengths_before(parent_item);
388         parent_item = SP_OBJECT_PARENT(parent_item);
389         TextTagAttributes *attributes = attributes_for_object(parent_item);
390         if (!attributes) break;
391         attributes->insert(char_index, char_count);
392     }
395 /** Inserts the given text into a text or flowroot object. Line breaks
396 cannot be inserted using this function, see sp_te_insert_line(). Returns
397 an iterator pointing just after the inserted text. */
398 Inkscape::Text::Layout::iterator
399 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
401     if (!g_utf8_validate(utf8,-1,NULL)) {
402         g_warning("Trying to insert invalid utf8");
403         return position;
404     }
406     Inkscape::Text::Layout const *layout = te_get_layout(item);
407     SPObject *source_obj;
408     Glib::ustring::iterator iter_text;
409     // we want to insert after the previous char, not before the current char.
410     // it makes a difference at span boundaries
411     Inkscape::Text::Layout::iterator it_prev_char = position;
412     bool cursor_at_start = !it_prev_char.prevCharacter();
413     bool cursor_at_end = position == layout->end();
414     layout->getSourceOfCharacter(it_prev_char, (void**)&source_obj, &iter_text);
415     if (SP_IS_STRING(source_obj)) {
416         // the simple case
417         if (!cursor_at_start) iter_text++;
418         SPString *string_item = SP_STRING(source_obj);
419         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
420     } else {
421         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
422         if (cursor_at_start) {
423             source_obj = item;
424             if (source_obj->hasChildren()) {
425                 source_obj = source_obj->firstChild();
426                 if (SP_IS_FLOWTEXT(item)) {
427                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
428                         source_obj = SP_OBJECT_NEXT(source_obj);
429                     if (source_obj == NULL)
430                         source_obj = item;
431                 }
432             }
433             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
434                 Inkscape::XML::Node *para = sp_repr_new("svg:flowPara");
435                 SP_OBJECT_REPR(item)->appendChild(para);
436                 source_obj = item->lastChild();
437             }
438         } else
439             source_obj = SP_OBJECT_NEXT(source_obj);
441         if (source_obj) {  // never fails
442             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
443             if (string_item == NULL) {
444                 // need to add an SPString in this (pathological) case
445                 Inkscape::XML::Node *rstring = sp_repr_new_text("");
446                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
447                 Inkscape::GC::release(rstring);
448                 g_assert(SP_IS_STRING(source_obj->firstChild()));
449                 string_item = SP_STRING(source_obj->firstChild());
450             }
451             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
452         }
453     }
455     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
456     unsigned char_index = layout->iteratorToCharIndex(position);
457     te_update_layout_now(item);
458     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
459     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
463 /* ***************************************************************************************************/
464 //                            D E L E T I N G   T E X T
466 /** moves all the children of \a from_repr to \a to_repr, either before
467 the existing children or after them. Order is maintained. The empty
468 \a from_repr is not deleted. */
469 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
471     while (from_repr->childCount()) {
472         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
473         Inkscape::GC::anchor(child);
474         from_repr->removeChild(child);
475         if (prepend) to_repr->addChild(child, NULL);
476         else to_repr->appendChild(child);
477         Inkscape::GC::release(child);
478     }
481 /** returns the object in the tree which is the closest ancestor of both
482 \a one and \a two. It will never return anything higher than \a text. */
483 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
485     if (one == NULL || two == NULL)
486         return text;
487     SPObject *common_ancestor = one;
488     if (SP_IS_STRING(common_ancestor))
489         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
490     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
491         g_assert(common_ancestor != text);
492         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
493     }
494     return common_ancestor;
497 /** positions \a para_obj and \a text_iter to be pointing at the end
498 of the last string in the last leaf object of \a para_obj. If the last
499 leaf is not an SPString then \a text_iter will be unchanged. */
500 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
502     while ((*para_obj)->hasChildren())
503         *para_obj = (*para_obj)->lastChild();
504     if (SP_IS_STRING(*para_obj))
505         *text_iter = SP_STRING(*para_obj)->string.end();
508 /** delete the line break pointed to by \a item by merging its children into
509 the next suitable object and deleting \a item. Returns the object after the
510 ones that have just been moved and sets \a next_is_sibling accordingly. */
511 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
513     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
514     SPObject *next_item = NULL;
515     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
517     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
518       <div></div><p>*text</p>
519       <p><div></div>*text</p>
520       <p><div></div></p><p>*text</p>
521     */
522     Inkscape::XML::Node *new_span_repr = sp_repr_new(span_name_for_text_object(root));
524     if (gchar const *a = this_repr->attribute("dx"))
525         new_span_repr->setAttribute("dx", a);
526     if (gchar const *a = this_repr->attribute("dy"))
527         new_span_repr->setAttribute("dy", a);
528     if (gchar const *a = this_repr->attribute("rotate"))
529         new_span_repr->setAttribute("rotate", a);
531     SPObject *following_item = item;
532     while (SP_OBJECT_NEXT(following_item) == NULL) {
533         following_item = SP_OBJECT_PARENT(following_item);
534         g_assert(following_item != root);
535     }
536     following_item = SP_OBJECT_NEXT(following_item);
538     SPObject *new_parent_item;
539     if (SP_IS_STRING(following_item)) {
540         new_parent_item = SP_OBJECT_PARENT(following_item);
541         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
542         next_item = following_item;
543         *next_is_sibling = true;
544     } else {
545         new_parent_item = following_item;
546         next_item = new_parent_item->firstChild();
547         *next_is_sibling = true;
548         if (next_item == NULL) {
549             next_item = new_parent_item;
550             *next_is_sibling = false;
551         }
552         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
553     }
555     // work around a bug in sp_style_write_difference() which causes the difference
556     // not to be written if the second param has a style set which the first does not
557     // by causing the first param to have everything set
558     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
559     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
560     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
561     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
562     for ( ; attrs ; attrs++) {
563         gchar const *key = g_quark_to_string(attrs->key);
564         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
565         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
566             this_node_attrs->setAttribute(key, this_attr);
567     }
568     sp_repr_css_attr_unref(this_node_attrs_inherited);
569     sp_repr_css_attr_unref(this_node_attrs);
570     sp_repr_css_attr_unref(dest_node_attrs);
571     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
573     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
574     if (attributes)
575         attributes->insert(0, moved_char_count);
576     move_child_nodes(this_repr, new_span_repr);
577     this_repr->parent()->removeChild(this_repr);
578     return next_item;
581 /** erases the given characters from the given string and deletes the
582 corresponding x/y/dx/dy/rotate attributes from all its parents. */
583 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
585     unsigned char_index = 0;
586     unsigned char_count = 0;
587     Glib::ustring *string = &SP_STRING(string_item)->string;
589     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
590         char_index++;
591     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
592         char_count++;
593     string->erase(iter_from, iter_to);
594     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
596     SPObject *parent_item = string_item;
597     for ( ; ; ) {
598         char_index += sum_sibling_text_lengths_before(parent_item);
599         parent_item = SP_OBJECT_PARENT(parent_item);
600         TextTagAttributes *attributes = attributes_for_object(parent_item);
601         if (attributes == NULL) break;
603         attributes->erase(char_index, char_count);
604         attributes->writeTo(SP_OBJECT_REPR(parent_item));
605     }
608 /* Deletes the given characters from a text or flowroot object. This is
609 quite a complicated operation, partly due to the cleanup that is done if all
610 the text in a subobject has been deleted, and partly due to the difficulty
611 of figuring out what is a line break and how to delete one. Returns the
612 lesser of \a start and \a end, because that is where the cursor should be
613 put after the deletion is done. */
614 Inkscape::Text::Layout::iterator
615 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
617     if (start == end) return start;
618     Inkscape::Text::Layout::iterator first, last;
619     if (start < end) {
620         first = start;
621         last = end;
622     } else {
623         first = end;
624         last = start;
625     }
626     Inkscape::Text::Layout const *layout = te_get_layout(item);
627     SPObject *start_item, *end_item;
628     Glib::ustring::iterator start_text_iter, end_text_iter;
629     layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
630     layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
631     if (start_item == NULL)
632         return first;   // start is at end of text
633     if (is_line_break_object(start_item))
634         move_to_end_of_paragraph(&start_item, &start_text_iter);
635     if (end_item == NULL) {
636         end_item = item->lastChild();
637         move_to_end_of_paragraph(&end_item, &end_text_iter);
638     }
639     else if (is_line_break_object(end_item))
640         move_to_end_of_paragraph(&end_item, &end_text_iter);
642     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
644     if (start_item == end_item) {
645         // the quick case where we're deleting stuff all from the same string
646         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
647             erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
648         }
649     } else {
650         SPObject *sub_item = start_item;
651         // walk the tree from start_item to end_item, deleting as we go
652         while (sub_item != item) {
653             if (sub_item == end_item) {
654                 if (SP_IS_STRING(sub_item)) {
655                     Glib::ustring *string = &SP_STRING(sub_item)->string;
656                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
657                 }
658                 break;
659             }
660             if (SP_IS_STRING(sub_item)) {
661                 SPString *string = SP_STRING(sub_item);
662                 if (sub_item == start_item)
663                     erase_from_spstring(string, start_text_iter, string->string.end());
664                 else
665                     erase_from_spstring(string, string->string.begin(), string->string.end());
666             }
667             // walk to the next item in the tree
668             if (sub_item->hasChildren())
669                 sub_item = sub_item->firstChild();
670             else {
671                 SPObject *next_item;
672                 do {
673                     bool is_sibling = true;
674                     next_item = SP_OBJECT_NEXT(sub_item);
675                     if (next_item == NULL) {
676                         next_item = SP_OBJECT_PARENT(sub_item);
677                         is_sibling = false;
678                     }
680                     if (is_line_break_object(sub_item))
681                         next_item = delete_line_break(item, sub_item, &is_sibling);
683                     sub_item = next_item;
684                     if (is_sibling) break;
685                     // no more siblings, go up a parent
686                 } while (sub_item != item && sub_item != end_item);
687             }
688         }
689     }
691     while (tidy_xml_tree_recursively(common_ancestor));
692     te_update_layout_now(item);
693     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
694     layout->validateIterator(&first);
695     return first;
699 /* ***************************************************************************************************/
700 //                            P L A I N   T E X T   F U N C T I O N S
702 /** Gets a text-only representation of the given text or flowroot object,
703 replacing line break elements with '\n'. */
704 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
706     if (*pending_line_break)
707         *string += '\n';
708     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
709         if (SP_IS_STRING(child))
710             *string += SP_STRING(child)->string;
711         else
712             sp_te_get_ustring_multiline(child, string, pending_line_break);
713     }
714     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
715         *pending_line_break = true;
718 /** Gets a text-only representation of the given text or flowroot object,
719 replacing line break elements with '\n'. The return value must be free()d. */
720 gchar *
721 sp_te_get_string_multiline (SPItem const *text)
723     Glib::ustring string;
724     bool pending_line_break = false;
726     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
727     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
728     if (string.empty()) return NULL;
729     return strdup(string.data());
732 /** Gets a text-only representation of the characters in a text or flowroot
733 object from \a start to \a end only. Line break elements are replaced with
734 '\n'. */
735 Glib::ustring
736 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
738     if (start == end) return "";
739     Inkscape::Text::Layout::iterator first, last;
740     if (start < end) {
741         first = start;
742         last = end;
743     } else {
744         first = end;
745         last = start;
746     }
747     Inkscape::Text::Layout const *layout = te_get_layout(text);
748     Glib::ustring result;
749     // not a particularly fast piece of code. I'll optimise it if people start to notice.
750     for ( ; first < last ; first.nextCharacter()) {
751         SPObject *char_item;
752         Glib::ustring::iterator text_iter;
753         layout->getSourceOfCharacter(first, (void**)&char_item, &text_iter);
754         if (SP_IS_STRING(char_item))
755             result += *text_iter;
756         else
757             result += '\n';
758     }
759     return result;
762 void
763 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
765     g_return_if_fail (text != NULL);
766     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
768     Inkscape::XML::Node *repr;
769     SPObject *object;
770     bool is_textpath = false;
771     if (SP_IS_TEXT_TEXTPATH (text)) {
772         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
773         object = sp_object_first_child(SP_OBJECT (text));
774         is_textpath = true;
775     } else {
776         repr = SP_OBJECT_REPR (text);
777         object = SP_OBJECT (text);
778     }
780     if (!str) str = "";
781     gchar *content = g_strdup (str);
783     repr->setContent("");
784     SPObject *child = object->firstChild();
785     while (child) {
786         SPObject *next = SP_OBJECT_NEXT(child);
787         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
788             repr->removeChild(SP_OBJECT_REPR(child));
789         child = next;
790     }
792     gchar *p = content;
793     while (p) {
794         gchar *e = strchr (p, '\n');
795         if (is_textpath) {
796             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
797         } else {
798             if (e) *e = '\0';
799             Inkscape::XML::Node *rtspan;
800             if (SP_IS_TEXT(text)) { // create a tspan for each line
801                 rtspan = sp_repr_new ("svg:tspan");
802                 rtspan->setAttribute("sodipodi:role", "line");
803             } else { // create a flowPara for each line
804                 rtspan = sp_repr_new ("svg:flowPara");
805             }
806             Inkscape::XML::Node *rstr = sp_repr_new_text(p);
807             rtspan->addChild(rstr, NULL);
808             Inkscape::GC::release(rstr);
809             repr->appendChild(rtspan);
810             Inkscape::GC::release(rtspan);
811         }
812         p = (e) ? e + 1 : NULL;
813     }
814     if (is_textpath) {
815         Inkscape::XML::Node *rstr = sp_repr_new_text(content);
816         repr->addChild(rstr, NULL);
817         Inkscape::GC::release(rstr);
818     }
820     g_free (content);
823 /* ***************************************************************************************************/
824 //                           K E R N I N G   A N D   S P A C I N G
826 /** Returns the attributes block and the character index within that block
827 which represents the iterator \a position. */
828 static TextTagAttributes*
829 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
831     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
832         return NULL;   // flowtext doesn't support kerning yet
833     SPText *text = SP_TEXT(item);
835     SPObject *source_item;
836     Glib::ustring::iterator source_text_iter;
837     text->layout.getSourceOfCharacter(position, (void**)&source_item, &source_text_iter);
839     if (!SP_IS_STRING(source_item)) return NULL;
840     Glib::ustring *string = &SP_STRING(source_item)->string;
841     *char_index = sum_sibling_text_lengths_before(source_item);
842     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
843         ++*char_index;
845     return attributes_for_object(SP_OBJECT_PARENT(source_item));
848 void
849 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
851     // divide increment by zoom
852     // divide increment by matrix expansion
853     gdouble factor = 1 / desktop->current_zoom();
854     NR::Matrix t = sp_item_i2doc_affine(item);
855     factor = factor / NR::expansion(t);
856     by = factor * by;
858     unsigned char_index;
859     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
860     if (attributes) attributes->addToDxDy(char_index, by);
861     if (start != end) {
862         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
863         if (attributes) attributes->addToDxDy(char_index, -by);
864     }
866     item->updateRepr();
867     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
870 void
871 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
873     // divide increment by zoom
874     // divide increment by matrix expansion
875     gdouble factor = 1 / desktop->current_zoom();
876     NR::Matrix t = sp_item_i2doc_affine(text);
877     factor = factor / NR::expansion(t);
878     SPObject *source_item;
879     Inkscape::Text::Layout const *layout = te_get_layout(text);
880     if (layout == NULL) return;
881     layout->getSourceOfCharacter(std::min(start, end), (void**)&source_item);
882     if (source_item == NULL) return;
883     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
885     sp_te_adjust_rotation(text, start, end, desktop, degrees);
888 void
889 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble degrees)
891     unsigned char_index;
892     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
893     if (attributes == NULL) return;
895     if (start != end) {
896         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
897             attributes = text_tag_attributes_at_position(text, it, &char_index);
898             if (attributes) attributes->addToRotate(char_index, degrees);
899         }
900     } else
901         attributes->addToRotate(char_index, degrees);
903     text->updateRepr();
904     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
907 void
908 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
910     g_return_if_fail (text != NULL);
911     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
913     Inkscape::Text::Layout const *layout = te_get_layout(text);
915     gdouble val;
916     SPObject *source_obj;
917     unsigned nb_let;
918     layout->getSourceOfCharacter(std::min(start, end), (void**)&source_obj);
920     if (source_obj == NULL) {   // end of text
921         source_obj = text->lastChild();
922     }
923     if (SP_IS_STRING(source_obj)) {
924         source_obj = source_obj->parent;
925     }
927     SPStyle *style = SP_OBJECT_STYLE (source_obj);
929     // calculate real value
930     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
931        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
932     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
933         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
934             val = style->font_size.computed * style->letter_spacing.value;
935         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
936             val = style->font_size.computed * style->letter_spacing.value * 0.5;
937         } else { // unknown unit - should not happen
938             val = 0.0;
939         }
940     } else { // there's a real value in .computed, or it's zero
941         val = style->letter_spacing.computed;
942     }
944     if (start == end) {
945         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
946             source_obj = SP_OBJECT_PARENT(source_obj);
947         nb_let = sp_text_get_length(source_obj);
948     } else {
949         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
950     }
952     // divide increment by zoom and by the number of characters in the line,
953     // so that the entire line is expanded by by pixels, no matter what its length
954     gdouble const zoom = desktop->current_zoom();
955     gdouble const zby = (by
956                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
957                          / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
958     val += zby;
960     if (start == end) {
961         // set back value to entire paragraph
962         style->letter_spacing.normal = FALSE;
963         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
964             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
965                 style->letter_spacing.value = val / style->font_size.computed;
966             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
967                 style->letter_spacing.value = val / style->font_size.computed * 2;
968             }
969         } else {
970             style->letter_spacing.computed = val;
971         }
973         style->letter_spacing.set = TRUE;
974     } else {
975         // apply to selection only
976         SPCSSAttr *css = sp_repr_css_attr_new();
977         char string_val[40];
978         g_snprintf(string_val, sizeof(string_val), "%f", val);
979         sp_repr_css_set_property(css, "letter-spacing", string_val);
980         sp_te_apply_style(text, start, end, css);
981         sp_repr_css_attr_unref(css);
982     }
984     text->updateRepr();
985     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
988 void
989 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
991     // TODO: use start and end iterators to delineate the area to be affected
992     g_return_if_fail (text != NULL);
993     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
995     Inkscape::Text::Layout const *layout = te_get_layout(text);
996     SPStyle *style = SP_OBJECT_STYLE (text);
998     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
999         style->line_height.set = TRUE;
1000         style->line_height.inherit = FALSE;
1001         style->line_height.normal = FALSE;
1002         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1003         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1004     }
1006     unsigned line_count = layout->lineIndex(layout->end());
1007     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
1008     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1009     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1011     // divide increment by zoom and by the number of lines,
1012     // so that the entire object is expanded by by pixels
1013     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1015     // divide increment by matrix expansion
1016     NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
1017     zby = zby / NR::expansion(t);
1019     switch (style->line_height.unit) {
1020         case SP_CSS_UNIT_NONE:
1021         default:
1022             // multiplier-type units, stored in computed
1023             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
1024             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1025             style->line_height.value = style->line_height.computed;
1026             break;
1027         case SP_CSS_UNIT_EM:
1028         case SP_CSS_UNIT_EX:
1029         case SP_CSS_UNIT_PERCENT:
1030             // multiplier-type units, stored in value
1031             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1032             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1033             break;
1034             // absolute-type units
1035             case SP_CSS_UNIT_PX:
1036             style->line_height.computed += zby;
1037             style->line_height.value = style->line_height.computed;
1038             break;
1039             case SP_CSS_UNIT_PT:
1040             style->line_height.computed += zby * PT_PER_PX;
1041             style->line_height.value = style->line_height.computed;
1042             break;
1043             case SP_CSS_UNIT_PC:
1044             style->line_height.computed += zby * (PT_PER_PX / 12);
1045             style->line_height.value = style->line_height.computed;
1046             break;
1047             case SP_CSS_UNIT_MM:
1048             style->line_height.computed += zby * MM_PER_PX;
1049             style->line_height.value = style->line_height.computed;
1050             break;
1051             case SP_CSS_UNIT_CM:
1052             style->line_height.computed += zby * CM_PER_PX;
1053             style->line_height.value = style->line_height.computed;
1054             break;
1055             case SP_CSS_UNIT_IN:
1056             style->line_height.computed += zby * IN_PER_PX;
1057             style->line_height.value = style->line_height.computed;
1058             break;
1059     }
1060     text->updateRepr();
1061     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1065 /* ***************************************************************************************************/
1066 //                           S T Y L E   A P P L I C A T I O N
1069 /** converts an iterator to a character index, mainly because ustring::substr()
1070 doesn't have a version that takes iterators as parameters. */
1071 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1073     unsigned n = 0;
1074     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1075         n++;
1076     return n;
1079 /** applies the given style string on top of the existing styles for \a item,
1080 as opposed to sp_style_merge_from_style_string which merges its parameter
1081 underneath the existing styles (ie ignoring already set properties). */
1082 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1084     SPStyle *new_style = sp_style_new();
1085     sp_style_merge_from_style_string(new_style, style_string);
1086     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1087     if (item_style_string && *item_style_string)
1088         sp_style_merge_from_style_string(new_style, item_style_string);
1089     gchar *new_style_string = sp_style_write_string(new_style);
1090     sp_style_unref(new_style);
1091     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1092     g_free(new_style_string);
1095 /** Returns true if the style of \a parent and the style of \a child are
1096 equivalent (and hence the children of both will appear the same). It is a
1097 limitation of the current implementation that \a parent must be a (not
1098 necessarily immediate) ancestor of \a child. */
1099 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1101     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1102     // will not work. Firstly because of an inheritance bug in style.cpp that has
1103     // implications too large for me to feel safe fixing, but mainly because the css spec
1104     // requires that the computed value is inherited, not the specified value.
1105     g_assert(parent->isAncestorOf(child));
1106     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1107     // we have to write parent_style then read it again, because some properties format their values
1108     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1109     SPStyle *parent_spstyle = sp_style_new();
1110     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1111     g_free(parent_style);
1112     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1113     sp_style_unref(parent_spstyle);
1115     Glib::ustring child_style_construction(parent_style);
1116     while (child != parent) {
1117         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1118         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1119         if (style_text && *style_text) {
1120             child_style_construction += ';';
1121             child_style_construction += style_text;
1122         }
1123         child = SP_OBJECT_PARENT(child);
1124     }
1125     SPStyle *child_spstyle = sp_style_new();
1126     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1127     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1128     sp_style_unref(child_spstyle);
1129     bool equal = !strcmp(child_style, parent_style);
1130     g_free(child_style);
1131     g_free(parent_style);
1132     return equal;
1135 /** returns true if \a first and \a second contain all the same attributes
1136 with the same values as each other. Note that we have to compare both
1137 forwards and backwards to make sure we don't miss any attributes that are
1138 in one but not the other. */
1139 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1141     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1142     for ( ; attrs ; attrs++) {
1143         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1144         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1145             return false;
1146     }
1147     attrs = second->attributeList();
1148     for ( ; attrs ; attrs++) {
1149         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1150         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1151             return false;
1152     }
1153     return true;
1156 /** sets the given css attribute on this object and all its descendants.
1157 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1158 transform stuff. */
1159 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1161     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1163     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1164         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1165             // Unset properties which are accumulating and thus should not be set recursively.
1166             // 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.
1167             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1168             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1169             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1170             apply_css_recursive(child, css_recurse);
1171             sp_repr_css_attr_unref(css_recurse);
1172         } else {
1173             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1174         }
1175     }
1178 /** applies the given style to all the objects at the given level and below
1179 which are between \a start_item and \a end_item, creating spans as necessary.
1180 If \a start_item or \a end_item are NULL then the style is applied to all
1181 objects to the beginning or end respectively. \a span_object_name is the
1182 name of the xml for a text span (ie tspan or flowspan). */
1183 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)
1185     bool passed_start = start_item == NULL ? true : false;
1187     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1188         if (start_item == child)
1189             passed_start = true;
1191         if (passed_start) {
1192             if (end_item && child->isAncestorOf(end_item)) {
1193                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1194                 break;
1195             }
1196             // apply style
1198             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1199             // often this means that new spans are created before child and child is modified only
1200             if (SP_IS_STRING(child)) {
1201                 SPString *string_item = SP_STRING(child);
1202                 bool surround_entire_string = true;
1204                 Inkscape::XML::Node *child_span = sp_repr_new(span_object_name);
1205                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1206                 SPObject *prev_item = SP_OBJECT_PREV(child);
1207                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1209                 if (child == start_item || child == end_item) {
1210                     surround_entire_string = false;
1211                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1212                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1213                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1214                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1216                         Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1217                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1218                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1219                         Inkscape::GC::release(text_before);
1220                         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());
1221                         child_span->appendChild(text_in_span);
1222                         Inkscape::GC::release(text_in_span);
1223                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1225                     } else if (child == end_item) {
1226                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1227                         //  (includes case where start_text_iter == begin())
1228                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1229                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1231                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1232                         Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(0, end_char_index).c_str());
1233                         child_span->appendChild(text_in_span);
1234                         Inkscape::GC::release(text_in_span);
1235                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1237                     } else if (start_text_iter != string_item->string.begin()) {
1238                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1239                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1241                         Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1242                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1243                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1244                         Inkscape::GC::release(text_before);
1245                         Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index).c_str());
1246                         child_span->appendChild(text_in_span);
1247                         Inkscape::GC::release(text_in_span);
1248                         child->deleteObject();
1249                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1251                     } else
1252                         surround_entire_string = true;
1253                 }
1254                 if (surround_entire_string) {
1255                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1256                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1257                     Inkscape::GC::anchor(child_repr);
1258                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1259                     child_span->appendChild(child_repr);
1260                     Inkscape::GC::release(child_repr);
1261                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1262                 }
1263                 Inkscape::GC::release(child_span);
1265             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1266                 apply_css_recursive(child, css);
1267             }
1269         } else {  // !passed_start
1270             if (child->isAncestorOf(start_item)) {
1271                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1272                 if (end_item && child->isAncestorOf(end_item))
1273                     break;   // only happens when start_item == end_item (I think)
1274                 passed_start = true;
1275             }
1276         }
1278         if (end_item == child)
1279             break;
1280     }
1283 /* if item is at the beginning of a tree it doesn't matter which element
1284 it points to so for neatness we would like it to point to the highest
1285 possible child of \a common_ancestor. There is no iterator return because
1286 a string can never be an ancestor.
1288 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1289 like * to point to the inner span because we can apply style to that whole
1290 span. */
1291 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1293     if (item == common_ancestor)
1294         return item;
1295     if (SP_IS_STRING(item))
1296         if (text_iter != SP_STRING(item)->string.begin())
1297             return item;
1298     for ( ; ; ) {
1299         SPObject *parent = SP_OBJECT_PARENT(item);
1300         if (parent == common_ancestor)
1301             break;
1302         if (item != parent->firstChild())
1303             break;
1304         item = parent;
1305     }
1306     return item;
1310 /**     empty spans: abc<span></span>def
1311                       -> abcdef                  */
1312 static bool tidy_operator_empty_spans(SPObject **item)
1314     if ((*item)->hasChildren()) return false;
1315     if (is_line_break_object(*item)) return false;
1316     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1317     SPObject *next = SP_OBJECT_NEXT(*item);
1318     (*item)->deleteObject();
1319     *item = next;
1320     return true;
1323 /**    inexplicable spans: abc<span style="">def</span>ghi
1324                             -> "abc""def""ghi"
1325 the repeated strings will be merged by another operator. */
1326 static bool tidy_operator_inexplicable_spans(SPObject **item)
1328     if (SP_IS_STRING(*item)) return false;
1329     if (is_line_break_object(*item)) return false;
1330     TextTagAttributes *attrs = attributes_for_object(*item);
1331     if (attrs && attrs->anyAttributesSet()) return false;
1332     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1333     SPObject *next = *item;
1334     while ((*item)->hasChildren()) {
1335         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1336         Inkscape::GC::anchor(repr);
1337         SP_OBJECT_REPR(*item)->removeChild(repr);
1338         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1339         Inkscape::GC::release(repr);
1340         next = SP_OBJECT_NEXT(next);
1341     }
1342     (*item)->deleteObject();
1343     *item = next;
1344     return true;
1347 /**    repeated spans: <font a>abc</font><font a>def</font>
1348                         -> <font a>abcdef</font>            */
1349 static bool tidy_operator_repeated_spans(SPObject **item)
1351     SPObject *first = *item;
1352     SPObject *second = SP_OBJECT_NEXT(first);
1353     if (second == NULL) return false;
1355     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1356     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1358     if (first_repr->type() != second_repr->type()) return false;
1360     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1361         // also amalgamate consecutive SPStrings into one
1362         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1363         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1364         second_repr->parent()->removeChild(second_repr);
1365         return true;
1366     }
1368     // merge consecutive spans with identical styles into one
1369     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1370     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1371     if (is_line_break_object(second)) return false;
1372     gchar const *first_style = first_repr->attribute("style");
1373     gchar const *second_style = second_repr->attribute("style");
1374     if (!((first_style == NULL && second_style == NULL)
1375           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1376         return false;
1378     // all our tests passed: do the merge
1379     TextTagAttributes *attributes_first = attributes_for_object(first);
1380     TextTagAttributes *attributes_second = attributes_for_object(second);
1381     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1382         TextTagAttributes attributes_first_copy = *attributes_first;
1383         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1384     }
1385     move_child_nodes(second_repr, first_repr);
1386     second_repr->parent()->removeChild(second_repr);
1387     return true;
1388     // *item is still the next object to process
1391 /**    redundant nesting: <font a><font b>abc</font></font>
1392                            -> <font b>abc</font>
1393        excessive nesting: <font a><size 1>abc</size></font>
1394                            -> <font a,size 1>abc</font>      */
1395 static bool tidy_operator_excessive_nesting(SPObject **item)
1397     if (!(*item)->hasChildren()) return false;
1398     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1399     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1400         return false;
1401     if (SP_IS_STRING((*item)->firstChild())) return false;
1402     if (is_line_break_object((*item)->firstChild())) return false;
1403     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1404     if (attrs && attrs->anyAttributesSet()) return false;
1405     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1406     if (child_style && *child_style)
1407         overwrite_style_with_string(*item, child_style);
1408     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1409     (*item)->firstChild()->deleteObject();
1410     return true;
1413 /** helper for tidy_operator_redundant_double_nesting() */
1414 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1416     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1417         return false;
1418     if (SP_IS_STRING(child)) return false;
1419     if (is_line_break_object(child)) return false;
1420     if (is_line_break_object(*item)) return false;
1421     TextTagAttributes *attrs = attributes_for_object(child);
1422     if (attrs && attrs->anyAttributesSet()) return false;
1423     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1425     Inkscape::XML::Node *insert_after_repr;
1426     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1427     else insert_after_repr = SP_OBJECT_REPR(*item);
1428     while (SP_OBJECT_REPR(child)->childCount()) {
1429         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1430         Inkscape::GC::anchor(move_repr);
1431         SP_OBJECT_REPR(child)->removeChild(move_repr);
1432         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1433         Inkscape::GC::release(move_repr);
1434         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1435     }
1436     child->deleteObject();
1437     return true;
1440 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1441                                 -> <font b>abc<font a>def</font>ghi</font>
1442 this function does its work when the parameter is the <font a> tag in the
1443 example. You may note that this only does its work when the doubly-nested
1444 child is the first or last. The other cases are called 'style inversion'
1445 below, and I'm not yet convinced that the result of that operation will be
1446 tidier in all cases. */
1447 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1449     if (!(*item)->hasChildren()) return false;
1450     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1451     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1452         return true;
1453     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1454         return true;
1455     return false;
1458 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1459 then compares the styles for item+child versus just child. If they're equal,
1460 tidying is possible. */
1461 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1463     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1464         return false;
1465     if (SP_IS_STRING(child)) return false;
1466     if (is_line_break_object(child)) return false;
1467     if (is_line_break_object(*item)) return false;
1468     TextTagAttributes *attrs = attributes_for_object(child);
1469     if (attrs && attrs->anyAttributesSet()) return false;
1470     attrs = attributes_for_object(*item);
1471     if (attrs && attrs->anyAttributesSet()) return false;
1473     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1474     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1475     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1476     if (child_style && *child_style) {
1477         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1478         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1479     }
1480     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1481     if (item_style && *item_style) {
1482         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1483     }
1484     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1485     sp_repr_css_attr_unref(css_child_and_item);
1486     sp_repr_css_attr_unref(css_child_only);
1487     if (!equal) return false;
1489     Inkscape::XML::Node *new_span = sp_repr_new(SP_OBJECT_REPR(*item)->name());
1490     if (prepend) {
1491         SPObject *prev = SP_OBJECT_PREV(*item);
1492         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1493     } else
1494         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1495     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1496     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1497     Inkscape::GC::release(new_span);
1498     child->deleteObject();
1499     return true;
1502 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1503                                 -> <font b>abc</font><font>def</font>
1504 test this by applying a colour to a region, then a different colour to
1505 a partially-overlapping region. */
1506 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1508     if (!(*item)->hasChildren()) return false;
1509     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1510     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1511         return true;
1512     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1513         return true;
1514     return false;
1517 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1518 in a paragraph which is not \a not_obj. */
1519 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1521     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1522     {
1523         if (child == not_obj) continue;
1524         if (child->hasChildren()) {
1525             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1526             if (ret) return ret;
1527         } else if (SP_IS_STRING(child))
1528             return SP_STRING(child);
1529     }
1530     return NULL;
1533 /** whitespace-only spans: abc<font> </font>def
1534                             -> abc<font></font> def
1535                            abc<b><i>def</i> </b>ghi
1536                             -> abc<b><i>def</i></b> ghi   */
1537 static bool tidy_operator_styled_whitespace(SPObject **item)
1539     if (!SP_IS_STRING(*item)) return false;
1540     Glib::ustring const &str = SP_STRING(*item)->string;
1541     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1542         if (!g_unichar_isspace(*it)) return false;
1544     SPObject *test_item = *item;
1545     SPString *next_string;
1546     for ( ; ; ) {  // find the next string
1547         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1548         if (next_string) {
1549             next_string->string.insert(0, str);
1550             break;
1551         }
1552         for ( ; ; ) {   // go up one item in the xml
1553             test_item = SP_OBJECT_PARENT(test_item);
1554             if (is_line_break_object(test_item)) break;
1555             SPObject *next = SP_OBJECT_NEXT(test_item);
1556             if (next) {
1557                 test_item = next;
1558                 break;
1559             }
1560         }
1561         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1562             next_string = find_last_string_child_not_equal_to(test_item, *item);
1563             if (next_string == NULL) return false;   // an empty paragraph
1564             next_string->string += str;
1565             break;
1566         }
1567     }
1568     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1569     SPObject *delete_obj = *item;
1570     *item = SP_OBJECT_NEXT(*item);
1571     delete_obj->deleteObject();
1572     return true;
1575 /* possible tidy operators that are not yet implemented, either because
1576 they are difficult, occur infrequently, or because I'm not sure that the
1577 output is tidier in all cases:
1578     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1579                                               -> <div italic><para>abc</para></div>
1580     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1581                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1582     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1583                           -> <size 1><font a>abc</font>def</size>
1584 */
1586 /** Recursively walks the xml tree calling a set of cleanup operations on
1587 every child. Returns true if any changes were made to the tree.
1589 All the tidy operators return true if they made changes, and alter their
1590 parameter to point to the next object that should be processed, or NULL.
1591 They must not significantly alter (ie delete) any ancestor elements of the
1592 one they are passed.
1594 It may be that some of the later tidy operators that I wrote are actually
1595 general cases of the earlier operators, and hence the special-case-only
1596 versions can be removed. I haven't analysed my work in detail to figure
1597 out if this is so. */
1598 static bool tidy_xml_tree_recursively(SPObject *root)
1600     static bool (* const tidy_operators[])(SPObject**) = {
1601         tidy_operator_empty_spans,
1602         tidy_operator_inexplicable_spans,
1603         tidy_operator_repeated_spans,
1604         tidy_operator_excessive_nesting,
1605         tidy_operator_redundant_double_nesting,
1606         tidy_operator_redundant_semi_nesting,
1607         tidy_operator_styled_whitespace
1608     };
1609     bool changes = false;
1611     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1612         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child)) {
1613             child = SP_OBJECT_NEXT(child);
1614             continue;
1615         }
1616         if (child->hasChildren())
1617             changes |= tidy_xml_tree_recursively(child);
1619         unsigned i;
1620         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1621             if (tidy_operators[i](&child)) {
1622                 changes = true;
1623                 break;
1624             }
1625         }
1626         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1627             child = SP_OBJECT_NEXT(child);
1628     }
1629     return changes;
1632 /** Applies the given CSS fragment to the characters of the given text or
1633 flowtext object between \a start and \a end, creating or removing span
1634 elements as necessary and optimal. */
1635 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1637     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1638     if (start == end) return;
1639     Inkscape::Text::Layout::iterator first, last;
1640     if (start < end) {
1641         first = start;
1642         last = end;
1643     } else {
1644         first = end;
1645         last = start;
1646     }
1647     Inkscape::Text::Layout const *layout = te_get_layout(text);
1648     SPObject *start_item, *end_item;
1649     Glib::ustring::iterator start_text_iter, end_text_iter;
1650     layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
1651     layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
1652     if (start_item == NULL)
1653         return;   // start is at end of text
1654     if (is_line_break_object(start_item))
1655         start_item = SP_OBJECT_NEXT(start_item);
1656     if (is_line_break_object(end_item))
1657         end_item = SP_OBJECT_NEXT(end_item);
1658     if (end_item == NULL) end_item = text;
1660     /* stage 1: applying the style. Go up to the closest common ancestor of
1661     start and end and then semi-recursively apply the style to all the
1662     objects in between. The semi-recursion is because it's only necessary
1663     at the beginning and end; the style can just be applied to the root
1664     child in the middle.
1665     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1666     The recursion may involve creating new spans.
1667     */
1668     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1669     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1670     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1671     recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1673     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1674     /* discussion: this stage requires a certain level of inventiveness because
1675     it's not clear what the best representation is in many cases. An ideal
1676     implementation would provide some sort of scoring function to rate the
1677     ugliness of a given xml tree and try to reduce said function, but providing
1678     the various possibilities to be rated is non-trivial. Instead, I have opted
1679     for a multi-pass technique which simply recognises known-ugly patterns and
1680     has matching routines for optimising the patterns it finds. It's reasonably
1681     easy to add new pattern matching processors. If everything gets disastrous
1682     and neither option can be made to work, a fallback could be to reduce
1683     everything to a single level of nesting and drop all pretence of
1684     roundtrippability. */
1685     while (tidy_xml_tree_recursively(common_ancestor));
1687     // if we only modified subobjects this won't have been automatically sent
1688     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1691 /*
1692   Local Variables:
1693   mode:c++
1694   c-file-style:"stroustrup"
1695   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1696   indent-tabs-mode:nil
1697   fill-column:99
1698   End:
1699 */
1700 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :