Code

44450f8fa7e39a7281c72b85f872f9cf84d44adc
[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 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Node const *old_node)
187     switch (old_node->type()) {
188         case Inkscape::XML::ELEMENT_NODE: {
189             Inkscape::XML::Node *new_node = sp_repr_new(old_node->name());
190             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
191             GQuark const id_key = g_quark_from_string("id");
192             for ( ; attributes ; attributes++) {
193                 if (attributes->key == id_key) continue;
194                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
195             }
196             return new_node;
197         }
199         case Inkscape::XML::TEXT_NODE:
200             return sp_repr_new_text(old_node->content());
202         case Inkscape::XML::COMMENT_NODE:
203             return sp_repr_new_comment(old_node->content());
205         case Inkscape::XML::DOCUMENT_NODE:
206             return NULL;   // this had better never happen
207     }
208     return NULL;
211 /** returns the sum of the (recursive) lengths of all the SPStrings prior
212 to \a item at the same level. */
213 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
215     unsigned char_index = 0;
216     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
217         char_index += sp_text_get_length(sibling);
218     return char_index;
221 /** splits the attributes for the first object at the given \a char_index
222 and moves the ones after that point into \a second_item. */
223 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
225     TextTagAttributes *first_attrs = attributes_for_object(first_item);
226     TextTagAttributes *second_attrs = attributes_for_object(second_item);
227     if (first_attrs && second_attrs)
228         first_attrs->split(char_index, second_attrs);
231 /** recursively divides the XML node tree into two objects: the original will
232 contain all objects up to and including \a split_obj and the returned value
233 will be the new leaf which represents the copy of \a split_obj and extends
234 down the tree with new elements all the way to the common root which is the
235 parent of the first line break node encountered.
236 */
237 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
239     if (is_line_break_object(split_obj)) {
240         Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
241         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
242         Inkscape::GC::release(new_node);
243         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
244         return SP_OBJECT_NEXT(split_obj);
245     }
247     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
248     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
249     // copy the split node
250     Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
251     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
252     Inkscape::GC::release(new_node);
254     // sort out the copied attributes (x/y/dx/dy/rotate)
255     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
257     // then move all the subsequent nodes
258     split_obj = SP_OBJECT_NEXT(split_obj);
259     while (split_obj) {
260         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
261         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
262         Inkscape::GC::anchor(move_repr);
263         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
264         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
265         Inkscape::GC::release(move_repr);
267         split_obj = next_obj;
268     }
269     return duplicate_obj->firstChild();
272 /** inserts a new line break at the given position in a text or flowtext
273 object. If the position is in the middle of a span, the XML tree must be
274 chopped in two such that the line can be created at the root of the text
275 element. Returns an iterator pointing just after the inserted break. */
276 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
278     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
279     // texpaths attached to the same path, with a vertical shift
280     if (SP_IS_TEXT_TEXTPATH (item))
281         return position;
283     Inkscape::Text::Layout const *layout = te_get_layout(item);
284     SPObject *split_obj;
285     Glib::ustring::iterator split_text_iter;
286     if (position == layout->end())
287         split_obj = NULL;
288     else
289         layout->getSourceOfCharacter(position, (void**)&split_obj, &split_text_iter);
291     if (split_obj == NULL || is_line_break_object(split_obj)) {
292         if (split_obj == NULL) split_obj = item->lastChild();
293         if (split_obj) {
294             Inkscape::XML::Node *new_node = duplicate_node_without_children(SP_OBJECT_REPR(split_obj));
295             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
296             Inkscape::GC::release(new_node);
297         }
298     } else if (SP_IS_STRING(split_obj)) {
299         Glib::ustring *string = &SP_STRING(split_obj)->string;
300         unsigned char_index = 0;
301         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
302             char_index++;
303         // we need to split the entire text tree into two
304         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
305         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
306         string->erase(split_text_iter, string->end());
307         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
308         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
309     } else {
310         // TODO
311         // I think the only case to put here is arbitrary gaps, which nobody uses yet
312     }
313     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
314     unsigned char_index = layout->iteratorToCharIndex(position);
315     te_update_layout_now(item);
316     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
317     return layout->charIndexToIterator(char_index + 1);
320 /** finds the first SPString after the given position, including children, excluding parents */
321 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
323     while (start_obj) {
324         if (start_obj->hasChildren()) {
325             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
326             if (found_string) return found_string;
327         }
328         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
329         start_obj = SP_OBJECT_NEXT(start_obj);
330         if (is_line_break_object(start_obj))
331             break;   // don't cross line breaks
332     }
333     return NULL;
336 /** inserts the given characters into the given string and inserts
337 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
338 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
340     unsigned char_index = 0;
341     unsigned char_count = g_utf8_strlen(utf8, -1);
342     Glib::ustring *string = &SP_STRING(string_item)->string;
344     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
345         char_index++;
346     string->replace(iter_at, iter_at, utf8);
348     SPObject *parent_item = string_item;
349     for ( ; ; ) {
350         char_index += sum_sibling_text_lengths_before(parent_item);
351         parent_item = SP_OBJECT_PARENT(parent_item);
352         TextTagAttributes *attributes = attributes_for_object(parent_item);
353         if (!attributes) break;
354         attributes->insert(char_index, char_count);
355     }
358 /** Inserts the given text into a text or flowroot object. Line breaks
359 cannot be inserted using this function, see sp_te_insert_line(). Returns
360 an iterator pointing just after the inserted text. */
361 Inkscape::Text::Layout::iterator
362 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
364     if (!g_utf8_validate(utf8,-1,NULL)) {
365         g_warning("Trying to insert invalid utf8");
366         return position;
367     }
369     Inkscape::Text::Layout const *layout = te_get_layout(item);
370     SPObject *source_obj;
371     Glib::ustring::iterator iter_text;
372     // we want to insert after the previous char, not before the current char.
373     // it makes a difference at span boundaries
374     Inkscape::Text::Layout::iterator it_prev_char = position;
375     bool cursor_at_start = !it_prev_char.prevCharacter();
376     bool cursor_at_end = position == layout->end();
377     layout->getSourceOfCharacter(it_prev_char, (void**)&source_obj, &iter_text);
378     if (SP_IS_STRING(source_obj)) {
379         // the simple case
380         if (!cursor_at_start) iter_text++;
381         SPString *string_item = SP_STRING(source_obj);
382         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
383     } else {
384         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
385         if (cursor_at_start) {
386             source_obj = item;
387             if (source_obj->hasChildren()) {
388                 source_obj = source_obj->firstChild();
389                 if (SP_IS_FLOWTEXT(item)) {
390                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
391                         source_obj = SP_OBJECT_NEXT(source_obj);
392                     if (source_obj == NULL)
393                         source_obj = item;
394                 }
395             }
396             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
397                 Inkscape::XML::Node *para = sp_repr_new("svg:flowPara");
398                 SP_OBJECT_REPR(item)->appendChild(para);
399                 source_obj = item->lastChild();
400             }
401         } else
402             source_obj = SP_OBJECT_NEXT(source_obj);
404         if (source_obj) {  // never fails
405             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
406             if (string_item == NULL) {
407                 // need to add an SPString in this (pathological) case
408                 Inkscape::XML::Node *rstring = sp_repr_new_text("");
409                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
410                 Inkscape::GC::release(rstring);
411                 g_assert(SP_IS_STRING(source_obj->firstChild()));
412                 string_item = SP_STRING(source_obj->firstChild());
413             }
414             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
415         }
416     }
418     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
419     unsigned char_index = layout->iteratorToCharIndex(position);
420     te_update_layout_now(item);
421     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
422     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
426 /* ***************************************************************************************************/
427 //                            D E L E T I N G   T E X T
429 /** moves all the children of \a from_repr to \a to_repr, either before
430 the existing children or after them. Order is maintained. The empty
431 \a from_repr is not deleted. */
432 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
434     while (from_repr->childCount()) {
435         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
436         Inkscape::GC::anchor(child);
437         from_repr->removeChild(child);
438         if (prepend) to_repr->addChild(child, NULL);
439         else to_repr->appendChild(child);
440         Inkscape::GC::release(child);
441     }
444 /** returns the object in the tree which is the closest ancestor of both
445 \a one and \a two. It will never return anything higher than \a text. */
446 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
448     if (one == NULL || two == NULL)
449         return text;
450     SPObject *common_ancestor = one;
451     if (SP_IS_STRING(common_ancestor))
452         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
453     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
454         g_assert(common_ancestor != text);
455         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
456     }
457     return common_ancestor;
460 /** positions \a para_obj and \a text_iter to be pointing at the end
461 of the last string in the last leaf object of \a para_obj. If the last
462 leaf is not an SPString then \a text_iter will be unchanged. */
463 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
465     while ((*para_obj)->hasChildren())
466         *para_obj = (*para_obj)->lastChild();
467     if (SP_IS_STRING(*para_obj))
468         *text_iter = SP_STRING(*para_obj)->string.end();
471 /** delete the line break pointed to by \a item by merging its children into
472 the next suitable object and deleting \a item. Returns the object after the
473 ones that have just been moved and sets \a next_is_sibling accordingly. */
474 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
476     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
477     SPObject *next_item = NULL;
478     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
480     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
481       <div></div><p>*text</p>
482       <p><div></div>*text</p>
483       <p><div></div></p><p>*text</p>
484     */
485     Inkscape::XML::Node *new_span_repr = sp_repr_new(span_name_for_text_object(root));
487     if (gchar const *a = this_repr->attribute("dx"))
488         new_span_repr->setAttribute("dx", a);
489     if (gchar const *a = this_repr->attribute("dy"))
490         new_span_repr->setAttribute("dy", a);
491     if (gchar const *a = this_repr->attribute("rotate"))
492         new_span_repr->setAttribute("rotate", a);
494     SPObject *following_item = item;
495     while (SP_OBJECT_NEXT(following_item) == NULL) {
496         following_item = SP_OBJECT_PARENT(following_item);
497         g_assert(following_item != root);
498     }
499     following_item = SP_OBJECT_NEXT(following_item);
501     SPObject *new_parent_item;
502     if (SP_IS_STRING(following_item)) {
503         new_parent_item = SP_OBJECT_PARENT(following_item);
504         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
505         next_item = following_item;
506         *next_is_sibling = true;
507     } else {
508         new_parent_item = following_item;
509         next_item = new_parent_item->firstChild();
510         *next_is_sibling = true;
511         if (next_item == NULL) {
512             next_item = new_parent_item;
513             *next_is_sibling = false;
514         }
515         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
516     }
518     // work around a bug in sp_style_write_difference() which causes the difference
519     // not to be written if the second param has a style set which the first does not
520     // by causing the first param to have everything set
521     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
522     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
523     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
524     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
525     for ( ; attrs ; attrs++) {
526         gchar const *key = g_quark_to_string(attrs->key);
527         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
528         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
529             this_node_attrs->setAttribute(key, this_attr);
530     }
531     sp_repr_css_attr_unref(this_node_attrs_inherited);
532     sp_repr_css_attr_unref(this_node_attrs);
533     sp_repr_css_attr_unref(dest_node_attrs);
534     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
536     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
537     if (attributes)
538         attributes->insert(0, moved_char_count);
539     move_child_nodes(this_repr, new_span_repr);
540     this_repr->parent()->removeChild(this_repr);
541     return next_item;
544 /** erases the given characters from the given string and deletes the
545 corresponding x/y/dx/dy/rotate attributes from all its parents. */
546 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
548     unsigned char_index = 0;
549     unsigned char_count = 0;
550     Glib::ustring *string = &SP_STRING(string_item)->string;
552     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
553         char_index++;
554     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
555         char_count++;
556     string->erase(iter_from, iter_to);
557     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
559     SPObject *parent_item = string_item;
560     for ( ; ; ) {
561         char_index += sum_sibling_text_lengths_before(parent_item);
562         parent_item = SP_OBJECT_PARENT(parent_item);
563         TextTagAttributes *attributes = attributes_for_object(parent_item);
564         if (attributes == NULL) break;
566         attributes->erase(char_index, char_count);
567         attributes->writeTo(SP_OBJECT_REPR(parent_item));
568     }
571 /* Deletes the given characters from a text or flowroot object. This is
572 quite a complicated operation, partly due to the cleanup that is done if all
573 the text in a subobject has been deleted, and partly due to the difficulty
574 of figuring out what is a line break and how to delete one. Returns the
575 lesser of \a start and \a end, because that is where the cursor should be
576 put after the deletion is done. */
577 Inkscape::Text::Layout::iterator
578 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
580     if (start == end) return start;
581     Inkscape::Text::Layout::iterator first, last;
582     if (start < end) {
583         first = start;
584         last = end;
585     } else {
586         first = end;
587         last = start;
588     }
589     Inkscape::Text::Layout const *layout = te_get_layout(item);
590     SPObject *start_item, *end_item;
591     Glib::ustring::iterator start_text_iter, end_text_iter;
592     layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
593     layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
594     if (start_item == NULL)
595         return first;   // start is at end of text
596     if (is_line_break_object(start_item))
597         move_to_end_of_paragraph(&start_item, &start_text_iter);
598     if (end_item == NULL) {
599         end_item = item->lastChild();
600         move_to_end_of_paragraph(&end_item, &end_text_iter);
601     }
602     else if (is_line_break_object(end_item))
603         move_to_end_of_paragraph(&end_item, &end_text_iter);
605     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
607     if (start_item == end_item) {
608         // the quick case where we're deleting stuff all from the same string
609         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
610             erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
611         }
612     } else {
613         SPObject *sub_item = start_item;
614         // walk the tree from start_item to end_item, deleting as we go
615         while (sub_item != item) {
616             if (sub_item == end_item) {
617                 if (SP_IS_STRING(sub_item)) {
618                     Glib::ustring *string = &SP_STRING(sub_item)->string;
619                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
620                 }
621                 break;
622             }
623             if (SP_IS_STRING(sub_item)) {
624                 SPString *string = SP_STRING(sub_item);
625                 if (sub_item == start_item)
626                     erase_from_spstring(string, start_text_iter, string->string.end());
627                 else
628                     erase_from_spstring(string, string->string.begin(), string->string.end());
629             }
630             // walk to the next item in the tree
631             if (sub_item->hasChildren())
632                 sub_item = sub_item->firstChild();
633             else {
634                 SPObject *next_item;
635                 do {
636                     bool is_sibling = true;
637                     next_item = SP_OBJECT_NEXT(sub_item);
638                     if (next_item == NULL) {
639                         next_item = SP_OBJECT_PARENT(sub_item);
640                         is_sibling = false;
641                     }
643                     if (is_line_break_object(sub_item))
644                         next_item = delete_line_break(item, sub_item, &is_sibling);
646                     sub_item = next_item;
647                     if (is_sibling) break;
648                     // no more siblings, go up a parent
649                 } while (sub_item != item && sub_item != end_item);
650             }
651         }
652     }
654     while (tidy_xml_tree_recursively(common_ancestor));
655     te_update_layout_now(item);
656     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
657     layout->validateIterator(&first);
658     return first;
662 /* ***************************************************************************************************/
663 //                            P L A I N   T E X T   F U N C T I O N S
665 /** Gets a text-only representation of the given text or flowroot object,
666 replacing line break elements with '\n'. */
667 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
669     if (*pending_line_break)
670         *string += '\n';
671     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
672         if (SP_IS_STRING(child))
673             *string += SP_STRING(child)->string;
674         else
675             sp_te_get_ustring_multiline(child, string, pending_line_break);
676     }
677     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
678         *pending_line_break = true;
681 /** Gets a text-only representation of the given text or flowroot object,
682 replacing line break elements with '\n'. The return value must be free()d. */
683 gchar *
684 sp_te_get_string_multiline (SPItem const *text)
686     Glib::ustring string;
687     bool pending_line_break = false;
689     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
690     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
691     if (string.empty()) return NULL;
692     return strdup(string.data());
695 /** Gets a text-only representation of the characters in a text or flowroot
696 object from \a start to \a end only. Line break elements are replaced with
697 '\n'. */
698 Glib::ustring
699 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
701     if (start == end) return "";
702     Inkscape::Text::Layout::iterator first, last;
703     if (start < end) {
704         first = start;
705         last = end;
706     } else {
707         first = end;
708         last = start;
709     }
710     Inkscape::Text::Layout const *layout = te_get_layout(text);
711     Glib::ustring result;
712     // not a particularly fast piece of code. I'll optimise it if people start to notice.
713     for ( ; first < last ; first.nextCharacter()) {
714         SPObject *char_item;
715         Glib::ustring::iterator text_iter;
716         layout->getSourceOfCharacter(first, (void**)&char_item, &text_iter);
717         if (SP_IS_STRING(char_item))
718             result += *text_iter;
719         else
720             result += '\n';
721     }
722     return result;
725 void
726 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
728     g_return_if_fail (text != NULL);
729     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
731     Inkscape::XML::Node *repr;
732     SPObject *object;
733     bool is_textpath = false;
734     if (SP_IS_TEXT_TEXTPATH (text)) {
735         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
736         object = sp_object_first_child(SP_OBJECT (text));
737         is_textpath = true;
738     } else {
739         repr = SP_OBJECT_REPR (text);
740         object = SP_OBJECT (text);
741     }
743     if (!str) str = "";
744     gchar *content = g_strdup (str);
746     repr->setContent("");
747     SPObject *child = object->firstChild();
748     while (child) {
749         SPObject *next = SP_OBJECT_NEXT(child);
750         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
751             repr->removeChild(SP_OBJECT_REPR(child));
752         child = next;
753     }
755     gchar *p = content;
756     while (p) {
757         gchar *e = strchr (p, '\n');
758         if (is_textpath) {
759             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
760         } else {
761             if (e) *e = '\0';
762             Inkscape::XML::Node *rtspan;
763             if (SP_IS_TEXT(text)) { // create a tspan for each line
764                 rtspan = sp_repr_new ("svg:tspan");
765                 rtspan->setAttribute("sodipodi:role", "line");
766             } else { // create a flowPara for each line
767                 rtspan = sp_repr_new ("svg:flowPara");
768             }
769             Inkscape::XML::Node *rstr = sp_repr_new_text(p);
770             rtspan->addChild(rstr, NULL);
771             Inkscape::GC::release(rstr);
772             repr->appendChild(rtspan);
773             Inkscape::GC::release(rtspan);
774         }
775         p = (e) ? e + 1 : NULL;
776     }
777     if (is_textpath) {
778         Inkscape::XML::Node *rstr = sp_repr_new_text(content);
779         repr->addChild(rstr, NULL);
780         Inkscape::GC::release(rstr);
781     }
783     g_free (content);
786 /* ***************************************************************************************************/
787 //                           K E R N I N G   A N D   S P A C I N G
789 /** Returns the attributes block and the character index within that block
790 which represents the iterator \a position. */
791 static TextTagAttributes*
792 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
794     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
795         return NULL;   // flowtext doesn't support kerning yet
796     SPText *text = SP_TEXT(item);
798     SPObject *source_item;
799     Glib::ustring::iterator source_text_iter;
800     text->layout.getSourceOfCharacter(position, (void**)&source_item, &source_text_iter);
802     if (!SP_IS_STRING(source_item)) return NULL;
803     Glib::ustring *string = &SP_STRING(source_item)->string;
804     *char_index = sum_sibling_text_lengths_before(source_item);
805     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
806         ++*char_index;
808     return attributes_for_object(SP_OBJECT_PARENT(source_item));
811 void
812 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
814     // divide increment by zoom
815     // divide increment by matrix expansion
816     gdouble factor = 1 / desktop->current_zoom();
817     NR::Matrix t = sp_item_i2doc_affine(item);
818     factor = factor / NR::expansion(t);
819     by = factor * by;
821     unsigned char_index;
822     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
823     if (attributes) attributes->addToDxDy(char_index, by);
824     if (start != end) {
825         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
826         if (attributes) attributes->addToDxDy(char_index, -by);
827     }
829     item->updateRepr();
830     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
833 void
834 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
836     // divide increment by zoom
837     // divide increment by matrix expansion
838     gdouble factor = 1 / desktop->current_zoom();
839     NR::Matrix t = sp_item_i2doc_affine(text);
840     factor = factor / NR::expansion(t);
841     SPObject *source_item;
842     Inkscape::Text::Layout const *layout = te_get_layout(text);
843     if (layout == NULL) return;
844     layout->getSourceOfCharacter(std::min(start, end), (void**)&source_item);
845     if (source_item == NULL) return;
846     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
848     sp_te_adjust_rotation(text, start, end, desktop, degrees);
851 void
852 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble degrees)
854     unsigned char_index;
855     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
856     if (attributes == NULL) return;
858     if (start != end) {
859         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
860             attributes = text_tag_attributes_at_position(text, it, &char_index);
861             if (attributes) attributes->addToRotate(char_index, degrees);
862         }
863     } else
864         attributes->addToRotate(char_index, degrees);
866     text->updateRepr();
867     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
870 void
871 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
873     g_return_if_fail (text != NULL);
874     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
876     Inkscape::Text::Layout const *layout = te_get_layout(text);
878     gdouble val;
879     SPObject *source_obj;
880     unsigned nb_let;
881     layout->getSourceOfCharacter(std::min(start, end), (void**)&source_obj);
883     if (source_obj == NULL) {   // end of text
884         source_obj = text->lastChild();
885     }
886     if (SP_IS_STRING(source_obj)) {
887         source_obj = source_obj->parent;
888     }
890     SPStyle *style = SP_OBJECT_STYLE (source_obj);
892     // calculate real value
893     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
894        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
895     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
896         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
897             val = style->font_size.computed * style->letter_spacing.value;
898         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
899             val = style->font_size.computed * style->letter_spacing.value * 0.5;
900         } else { // unknown unit - should not happen
901             val = 0.0;
902         }
903     } else { // there's a real value in .computed, or it's zero
904         val = style->letter_spacing.computed;
905     }
907     if (start == end) {
908         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
909             source_obj = SP_OBJECT_PARENT(source_obj);
910         nb_let = sp_text_get_length(source_obj);
911     } else {
912         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
913     }
915     // divide increment by zoom and by the number of characters in the line,
916     // so that the entire line is expanded by by pixels, no matter what its length
917     gdouble const zoom = desktop->current_zoom();
918     gdouble const zby = (by
919                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
920                          / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
921     val += zby;
923     if (start == end) {
924         // set back value to entire paragraph
925         style->letter_spacing.normal = FALSE;
926         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
927             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
928                 style->letter_spacing.value = val / style->font_size.computed;
929             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
930                 style->letter_spacing.value = val / style->font_size.computed * 2;
931             }
932         } else {
933             style->letter_spacing.computed = val;
934         }
936         style->letter_spacing.set = TRUE;
937     } else {
938         // apply to selection only
939         SPCSSAttr *css = sp_repr_css_attr_new();
940         char string_val[40];
941         g_snprintf(string_val, sizeof(string_val), "%f", val);
942         sp_repr_css_set_property(css, "letter-spacing", string_val);
943         sp_te_apply_style(text, start, end, css);
944         sp_repr_css_attr_unref(css);
945     }
947     text->updateRepr();
948     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
951 void
952 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
954     // TODO: use start and end iterators to delineate the area to be affected
955     g_return_if_fail (text != NULL);
956     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
958     Inkscape::Text::Layout const *layout = te_get_layout(text);
959     SPStyle *style = SP_OBJECT_STYLE (text);
961     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
962         style->line_height.set = TRUE;
963         style->line_height.inherit = FALSE;
964         style->line_height.normal = FALSE;
965         style->line_height.unit = SP_CSS_UNIT_PERCENT;
966         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
967     }
969     unsigned line_count = layout->lineIndex(layout->end());
970     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
971     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
972     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
974     // divide increment by zoom and by the number of lines,
975     // so that the entire object is expanded by by pixels
976     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
978     // divide increment by matrix expansion
979     NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
980     zby = zby / NR::expansion(t);
982     switch (style->line_height.unit) {
983         case SP_CSS_UNIT_NONE:
984         default:
985             // multiplier-type units, stored in computed
986             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
987             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
988             style->line_height.value = style->line_height.computed;
989             break;
990         case SP_CSS_UNIT_EM:
991         case SP_CSS_UNIT_EX:
992         case SP_CSS_UNIT_PERCENT:
993             // multiplier-type units, stored in value
994             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
995             else style->line_height.value *= (average_line_height + zby) / average_line_height;
996             break;
997             // absolute-type units
998             case SP_CSS_UNIT_PX:
999             style->line_height.computed += zby;
1000             style->line_height.value = style->line_height.computed;
1001             break;
1002             case SP_CSS_UNIT_PT:
1003             style->line_height.computed += zby * PT_PER_PX;
1004             style->line_height.value = style->line_height.computed;
1005             break;
1006             case SP_CSS_UNIT_PC:
1007             style->line_height.computed += zby * (PT_PER_PX / 12);
1008             style->line_height.value = style->line_height.computed;
1009             break;
1010             case SP_CSS_UNIT_MM:
1011             style->line_height.computed += zby * MM_PER_PX;
1012             style->line_height.value = style->line_height.computed;
1013             break;
1014             case SP_CSS_UNIT_CM:
1015             style->line_height.computed += zby * CM_PER_PX;
1016             style->line_height.value = style->line_height.computed;
1017             break;
1018             case SP_CSS_UNIT_IN:
1019             style->line_height.computed += zby * IN_PER_PX;
1020             style->line_height.value = style->line_height.computed;
1021             break;
1022     }
1023     text->updateRepr();
1024     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1028 /* ***************************************************************************************************/
1029 //                           S T Y L E   A P P L I C A T I O N
1032 /** converts an iterator to a character index, mainly because ustring::substr()
1033 doesn't have a version that takes iterators as parameters. */
1034 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1036     unsigned n = 0;
1037     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1038         n++;
1039     return n;
1042 /** applies the given style string on top of the existing styles for \a item,
1043 as opposed to sp_style_merge_from_style_string which merges its parameter
1044 underneath the existing styles (ie ignoring already set properties). */
1045 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1047     SPStyle *new_style = sp_style_new();
1048     sp_style_merge_from_style_string(new_style, style_string);
1049     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1050     if (item_style_string && *item_style_string)
1051         sp_style_merge_from_style_string(new_style, item_style_string);
1052     gchar *new_style_string = sp_style_write_string(new_style);
1053     sp_style_unref(new_style);
1054     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1055     g_free(new_style_string);
1058 /** Returns true if the style of \a parent and the style of \a child are
1059 equivalent (and hence the children of both will appear the same). It is a
1060 limitation of the current implementation that \a parent must be a (not
1061 necessarily immediate) ancestor of \a child. */
1062 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1064     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1065     // will not work. Firstly because of an inheritance bug in style.cpp that has
1066     // implications too large for me to feel safe fixing, but mainly because the css spec
1067     // requires that the computed value is inherited, not the specified value.
1068     g_assert(parent->isAncestorOf(child));
1069     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1070     // we have to write parent_style then read it again, because some properties format their values
1071     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1072     SPStyle *parent_spstyle = sp_style_new();
1073     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1074     g_free(parent_style);
1075     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1076     sp_style_unref(parent_spstyle);
1078     Glib::ustring child_style_construction(parent_style);
1079     while (child != parent) {
1080         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1081         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1082         if (style_text && *style_text) {
1083             child_style_construction += ';';
1084             child_style_construction += style_text;
1085         }
1086         child = SP_OBJECT_PARENT(child);
1087     }
1088     SPStyle *child_spstyle = sp_style_new();
1089     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1090     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1091     sp_style_unref(child_spstyle);
1092     bool equal = !strcmp(child_style, parent_style);
1093     g_free(child_style);
1094     g_free(parent_style);
1095     return equal;
1098 /** returns true if \a first and \a second contain all the same attributes
1099 with the same values as each other. Note that we have to compare both
1100 forwards and backwards to make sure we don't miss any attributes that are
1101 in one but not the other. */
1102 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1104     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1105     for ( ; attrs ; attrs++) {
1106         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1107         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1108             return false;
1109     }
1110     attrs = second->attributeList();
1111     for ( ; attrs ; attrs++) {
1112         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1113         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1114             return false;
1115     }
1116     return true;
1119 /** sets the given css attribute on this object and all its descendants.
1120 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1121 transform stuff. */
1122 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1124     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1126     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1127         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1128             // Unset properties which are accumulating and thus should not be set recursively.
1129             // 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.
1130             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1131             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1132             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1133             apply_css_recursive(child, css_recurse);
1134             sp_repr_css_attr_unref(css_recurse);
1135         } else {
1136             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1137         }
1138     }
1141 /** applies the given style to all the objects at the given level and below
1142 which are between \a start_item and \a end_item, creating spans as necessary.
1143 If \a start_item or \a end_item are NULL then the style is applied to all
1144 objects to the beginning or end respectively. \a span_object_name is the
1145 name of the xml for a text span (ie tspan or flowspan). */
1146 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)
1148     bool passed_start = start_item == NULL ? true : false;
1150     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1151         if (start_item == child)
1152             passed_start = true;
1154         if (passed_start) {
1155             if (end_item && child->isAncestorOf(end_item)) {
1156                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1157                 break;
1158             }
1159             // apply style
1161             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1162             // often this means that new spans are created before child and child is modified only
1163             if (SP_IS_STRING(child)) {
1164                 SPString *string_item = SP_STRING(child);
1165                 bool surround_entire_string = true;
1167                 Inkscape::XML::Node *child_span = sp_repr_new(span_object_name);
1168                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1169                 SPObject *prev_item = SP_OBJECT_PREV(child);
1170                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1172                 if (child == start_item || child == end_item) {
1173                     surround_entire_string = false;
1174                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1175                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1176                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1177                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1179                         Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1180                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1181                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1182                         Inkscape::GC::release(text_before);
1183                         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());
1184                         child_span->appendChild(text_in_span);
1185                         Inkscape::GC::release(text_in_span);
1186                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1188                     } else if (child == end_item) {
1189                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1190                         //  (includes case where start_text_iter == begin())
1191                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1192                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1194                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1195                         Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(0, end_char_index).c_str());
1196                         child_span->appendChild(text_in_span);
1197                         Inkscape::GC::release(text_in_span);
1198                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1200                     } else if (start_text_iter != string_item->string.begin()) {
1201                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1202                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1204                         Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1205                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1206                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1207                         Inkscape::GC::release(text_before);
1208                         Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index).c_str());
1209                         child_span->appendChild(text_in_span);
1210                         Inkscape::GC::release(text_in_span);
1211                         child->deleteObject();
1212                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1214                     } else
1215                         surround_entire_string = true;
1216                 }
1217                 if (surround_entire_string) {
1218                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1219                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1220                     Inkscape::GC::anchor(child_repr);
1221                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1222                     child_span->appendChild(child_repr);
1223                     Inkscape::GC::release(child_repr);
1224                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1225                 }
1226                 Inkscape::GC::release(child_span);
1228             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1229                 apply_css_recursive(child, css);
1230             }
1232         } else {  // !passed_start
1233             if (child->isAncestorOf(start_item)) {
1234                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1235                 if (end_item && child->isAncestorOf(end_item))
1236                     break;   // only happens when start_item == end_item (I think)
1237                 passed_start = true;
1238             }
1239         }
1241         if (end_item == child)
1242             break;
1243     }
1246 /* if item is at the beginning of a tree it doesn't matter which element
1247 it points to so for neatness we would like it to point to the highest
1248 possible child of \a common_ancestor. There is no iterator return because
1249 a string can never be an ancestor.
1251 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1252 like * to point to the inner span because we can apply style to that whole
1253 span. */
1254 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1256     if (item == common_ancestor)
1257         return item;
1258     if (SP_IS_STRING(item))
1259         if (text_iter != SP_STRING(item)->string.begin())
1260             return item;
1261     for ( ; ; ) {
1262         SPObject *parent = SP_OBJECT_PARENT(item);
1263         if (parent == common_ancestor)
1264             break;
1265         if (item != parent->firstChild())
1266             break;
1267         item = parent;
1268     }
1269     return item;
1273 /**     empty spans: abc<span></span>def
1274                       -> abcdef                  */
1275 static bool tidy_operator_empty_spans(SPObject **item)
1277     if ((*item)->hasChildren()) return false;
1278     if (is_line_break_object(*item)) return false;
1279     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1280     SPObject *next = SP_OBJECT_NEXT(*item);
1281     (*item)->deleteObject();
1282     *item = next;
1283     return true;
1286 /**    inexplicable spans: abc<span style="">def</span>ghi
1287                             -> "abc""def""ghi"
1288 the repeated strings will be merged by another operator. */
1289 static bool tidy_operator_inexplicable_spans(SPObject **item)
1291     if (SP_IS_STRING(*item)) return false;
1292     if (is_line_break_object(*item)) return false;
1293     TextTagAttributes *attrs = attributes_for_object(*item);
1294     if (attrs && attrs->anyAttributesSet()) return false;
1295     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1296     SPObject *next = *item;
1297     while ((*item)->hasChildren()) {
1298         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1299         Inkscape::GC::anchor(repr);
1300         SP_OBJECT_REPR(*item)->removeChild(repr);
1301         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1302         Inkscape::GC::release(repr);
1303         next = SP_OBJECT_NEXT(next);
1304     }
1305     (*item)->deleteObject();
1306     *item = next;
1307     return true;
1310 /**    repeated spans: <font a>abc</font><font a>def</font>
1311                         -> <font a>abcdef</font>            */
1312 static bool tidy_operator_repeated_spans(SPObject **item)
1314     SPObject *first = *item;
1315     SPObject *second = SP_OBJECT_NEXT(first);
1316     if (second == NULL) return false;
1318     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1319     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1321     if (first_repr->type() != second_repr->type()) return false;
1323     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1324         // also amalgamate consecutive SPStrings into one
1325         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1326         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1327         second_repr->parent()->removeChild(second_repr);
1328         return true;
1329     }
1331     // merge consecutive spans with identical styles into one
1332     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1333     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1334     if (is_line_break_object(second)) return false;
1335     gchar const *first_style = first_repr->attribute("style");
1336     gchar const *second_style = second_repr->attribute("style");
1337     if (!((first_style == NULL && second_style == NULL)
1338           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1339         return false;
1341     // all our tests passed: do the merge
1342     TextTagAttributes *attributes_first = attributes_for_object(first);
1343     TextTagAttributes *attributes_second = attributes_for_object(second);
1344     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1345         TextTagAttributes attributes_first_copy = *attributes_first;
1346         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1347     }
1348     move_child_nodes(second_repr, first_repr);
1349     second_repr->parent()->removeChild(second_repr);
1350     return true;
1351     // *item is still the next object to process
1354 /**    redundant nesting: <font a><font b>abc</font></font>
1355                            -> <font b>abc</font>
1356        excessive nesting: <font a><size 1>abc</size></font>
1357                            -> <font a,size 1>abc</font>      */
1358 static bool tidy_operator_excessive_nesting(SPObject **item)
1360     if (!(*item)->hasChildren()) return false;
1361     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1362     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1363         return false;
1364     if (SP_IS_STRING((*item)->firstChild())) return false;
1365     if (is_line_break_object((*item)->firstChild())) return false;
1366     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1367     if (attrs && attrs->anyAttributesSet()) return false;
1368     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1369     if (child_style && *child_style)
1370         overwrite_style_with_string(*item, child_style);
1371     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1372     (*item)->firstChild()->deleteObject();
1373     return true;
1376 /** helper for tidy_operator_redundant_double_nesting() */
1377 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1379     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1380         return false;
1381     if (SP_IS_STRING(child)) return false;
1382     if (is_line_break_object(child)) return false;
1383     if (is_line_break_object(*item)) return false;
1384     TextTagAttributes *attrs = attributes_for_object(child);
1385     if (attrs && attrs->anyAttributesSet()) return false;
1386     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1388     Inkscape::XML::Node *insert_after_repr;
1389     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1390     else insert_after_repr = SP_OBJECT_REPR(*item);
1391     while (SP_OBJECT_REPR(child)->childCount()) {
1392         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1393         Inkscape::GC::anchor(move_repr);
1394         SP_OBJECT_REPR(child)->removeChild(move_repr);
1395         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1396         Inkscape::GC::release(move_repr);
1397         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1398     }
1399     child->deleteObject();
1400     return true;
1403 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1404                                 -> <font b>abc<font a>def</font>ghi</font>
1405 this function does its work when the parameter is the <font a> tag in the
1406 example. You may note that this only does its work when the doubly-nested
1407 child is the first or last. The other cases are called 'style inversion'
1408 below, and I'm not yet convinced that the result of that operation will be
1409 tidier in all cases. */
1410 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1412     if (!(*item)->hasChildren()) return false;
1413     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1414     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1415         return true;
1416     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1417         return true;
1418     return false;
1421 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1422 then compares the styles for item+child versus just child. If they're equal,
1423 tidying is possible. */
1424 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1426     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1427         return false;
1428     if (SP_IS_STRING(child)) return false;
1429     if (is_line_break_object(child)) return false;
1430     if (is_line_break_object(*item)) return false;
1431     TextTagAttributes *attrs = attributes_for_object(child);
1432     if (attrs && attrs->anyAttributesSet()) return false;
1433     attrs = attributes_for_object(*item);
1434     if (attrs && attrs->anyAttributesSet()) return false;
1436     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1437     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1438     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1439     if (child_style && *child_style) {
1440         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1441         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1442     }
1443     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1444     if (item_style && *item_style) {
1445         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1446     }
1447     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1448     sp_repr_css_attr_unref(css_child_and_item);
1449     sp_repr_css_attr_unref(css_child_only);
1450     if (!equal) return false;
1452     Inkscape::XML::Node *new_span = sp_repr_new(SP_OBJECT_REPR(*item)->name());
1453     if (prepend) {
1454         SPObject *prev = SP_OBJECT_PREV(*item);
1455         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1456     } else
1457         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1458     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1459     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1460     Inkscape::GC::release(new_span);
1461     child->deleteObject();
1462     return true;
1465 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1466                                 -> <font b>abc</font><font>def</font>
1467 test this by applying a colour to a region, then a different colour to
1468 a partially-overlapping region. */
1469 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1471     if (!(*item)->hasChildren()) return false;
1472     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1473     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1474         return true;
1475     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1476         return true;
1477     return false;
1480 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1481 in a paragraph which is not \a not_obj. */
1482 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1484     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1485     {
1486         if (child == not_obj) continue;
1487         if (child->hasChildren()) {
1488             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1489             if (ret) return ret;
1490         } else if (SP_IS_STRING(child))
1491             return SP_STRING(child);
1492     }
1493     return NULL;
1496 /** whitespace-only spans: abc<font> </font>def
1497                             -> abc<font></font> def
1498                            abc<b><i>def</i> </b>ghi
1499                             -> abc<b><i>def</i></b> ghi   */
1500 static bool tidy_operator_styled_whitespace(SPObject **item)
1502     if (!SP_IS_STRING(*item)) return false;
1503     Glib::ustring const &str = SP_STRING(*item)->string;
1504     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1505         if (!g_unichar_isspace(*it)) return false;
1507     SPObject *test_item = *item;
1508     SPString *next_string;
1509     for ( ; ; ) {  // find the next string
1510         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1511         if (next_string) {
1512             next_string->string.insert(0, str);
1513             break;
1514         }
1515         for ( ; ; ) {   // go up one item in the xml
1516             test_item = SP_OBJECT_PARENT(test_item);
1517             if (is_line_break_object(test_item)) break;
1518             SPObject *next = SP_OBJECT_NEXT(test_item);
1519             if (next) {
1520                 test_item = next;
1521                 break;
1522             }
1523         }
1524         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1525             next_string = find_last_string_child_not_equal_to(test_item, *item);
1526             if (next_string == NULL) return false;   // an empty paragraph
1527             next_string->string += str;
1528             break;
1529         }
1530     }
1531     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1532     SPObject *delete_obj = *item;
1533     *item = SP_OBJECT_NEXT(*item);
1534     delete_obj->deleteObject();
1535     return true;
1538 /* possible tidy operators that are not yet implemented, either because
1539 they are difficult, occur infrequently, or because I'm not sure that the
1540 output is tidier in all cases:
1541     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1542                                               -> <div italic><para>abc</para></div>
1543     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1544                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1545     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1546                           -> <size 1><font a>abc</font>def</size>
1547 */
1549 /** Recursively walks the xml tree calling a set of cleanup operations on
1550 every child. Returns true if any changes were made to the tree.
1552 All the tidy operators return true if they made changes, and alter their
1553 parameter to point to the next object that should be processed, or NULL.
1554 They must not significantly alter (ie delete) any ancestor elements of the
1555 one they are passed.
1557 It may be that some of the later tidy operators that I wrote are actually
1558 general cases of the earlier operators, and hence the special-case-only
1559 versions can be removed. I haven't analysed my work in detail to figure
1560 out if this is so. */
1561 static bool tidy_xml_tree_recursively(SPObject *root)
1563     static bool (* const tidy_operators[])(SPObject**) = {
1564         tidy_operator_empty_spans,
1565         tidy_operator_inexplicable_spans,
1566         tidy_operator_repeated_spans,
1567         tidy_operator_excessive_nesting,
1568         tidy_operator_redundant_double_nesting,
1569         tidy_operator_redundant_semi_nesting,
1570         tidy_operator_styled_whitespace
1571     };
1572     bool changes = false;
1574     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1575         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child)) {
1576             child = SP_OBJECT_NEXT(child);
1577             continue;
1578         }
1579         if (child->hasChildren())
1580             changes |= tidy_xml_tree_recursively(child);
1582         unsigned i;
1583         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1584             if (tidy_operators[i](&child)) {
1585                 changes = true;
1586                 break;
1587             }
1588         }
1589         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1590             child = SP_OBJECT_NEXT(child);
1591     }
1592     return changes;
1595 /** Applies the given CSS fragment to the characters of the given text or
1596 flowtext object between \a start and \a end, creating or removing span
1597 elements as necessary and optimal. */
1598 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1600     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1601     if (start == end) return;
1602     Inkscape::Text::Layout::iterator first, last;
1603     if (start < end) {
1604         first = start;
1605         last = end;
1606     } else {
1607         first = end;
1608         last = start;
1609     }
1610     Inkscape::Text::Layout const *layout = te_get_layout(text);
1611     SPObject *start_item, *end_item;
1612     Glib::ustring::iterator start_text_iter, end_text_iter;
1613     layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
1614     layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
1615     if (start_item == NULL)
1616         return;   // start is at end of text
1617     if (is_line_break_object(start_item))
1618         start_item = SP_OBJECT_NEXT(start_item);
1619     if (is_line_break_object(end_item))
1620         end_item = SP_OBJECT_NEXT(end_item);
1621     if (end_item == NULL) end_item = text;
1623     /* stage 1: applying the style. Go up to the closest common ancestor of
1624     start and end and then semi-recursively apply the style to all the
1625     objects in between. The semi-recursion is because it's only necessary
1626     at the beginning and end; the style can just be applied to the root
1627     child in the middle.
1628     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1629     The recursion may involve creating new spans.
1630     */
1631     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1632     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1633     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1634     recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1636     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1637     /* discussion: this stage requires a certain level of inventiveness because
1638     it's not clear what the best representation is in many cases. An ideal
1639     implementation would provide some sort of scoring function to rate the
1640     ugliness of a given xml tree and try to reduce said function, but providing
1641     the various possibilities to be rated is non-trivial. Instead, I have opted
1642     for a multi-pass technique which simply recognises known-ugly patterns and
1643     has matching routines for optimising the patterns it finds. It's reasonably
1644     easy to add new pattern matching processors. If everything gets disastrous
1645     and neither option can be made to work, a fallback could be to reduce
1646     everything to a single level of nesting and drop all pretence of
1647     roundtrippability. */
1648     while (tidy_xml_tree_recursively(common_ancestor));
1650     // if we only modified subobjects this won't have been automatically sent
1651     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1654 /*
1655   Local Variables:
1656   mode:c++
1657   c-file-style:"stroustrup"
1658   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1659   indent-tabs-mode:nil
1660   fill-column:99
1661   End:
1662 */
1663 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :