Code

moving trunk for module inkscape
[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);
882     if (source_obj == NULL)    // end of text
883         source_obj = text->lastChild();
884     else if (SP_IS_STRING(source_obj))
885         source_obj = source_obj->parent;
887     SPStyle *style = SP_OBJECT_STYLE (source_obj);
889     // calculate real value
890     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
891        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
892     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
893         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
894             val = style->font_size.computed * style->letter_spacing.value;
895         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
896             val = style->font_size.computed * style->letter_spacing.value * 0.5;
897         } else { // unknown unit - should not happen
898             val = 0.0;
899         }
900     } else { // there's a real value in .computed, or it's zero
901         val = style->letter_spacing.computed;
902     }
904     if (start == end) {
905         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
906             source_obj = SP_OBJECT_PARENT(source_obj);
907         nb_let = sp_text_get_length(source_obj);
908     } else {
909         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
910     }
912     // divide increment by zoom and by the number of characters in the line,
913     // so that the entire line is expanded by by pixels, no matter what its length
914     gdouble const zoom = desktop->current_zoom();
915     gdouble const zby = (by
916                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
917                          / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
918     val += zby;
920     if (start == end) {
921         // set back value to entire paragraph
922         style->letter_spacing.normal = FALSE;
923         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
924             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
925                 style->letter_spacing.value = val / style->font_size.computed;
926             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
927                 style->letter_spacing.value = val / style->font_size.computed * 2;
928             }
929         } else {
930             style->letter_spacing.computed = val;
931         }
933         style->letter_spacing.set = TRUE;
934     } else {
935         // apply to selection only
936         SPCSSAttr *css = sp_repr_css_attr_new();
937         char string_val[40];
938         g_snprintf(string_val, sizeof(string_val), "%f", val);
939         sp_repr_css_set_property(css, "letter-spacing", string_val);
940         sp_te_apply_style(text, start, end, css);
941         sp_repr_css_attr_unref(css);
942     }
944     text->updateRepr();
945     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
948 void
949 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
951     // TODO: use start and end iterators to delineate the area to be affected
952     g_return_if_fail (text != NULL);
953     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
955     Inkscape::Text::Layout const *layout = te_get_layout(text);
956     SPStyle *style = SP_OBJECT_STYLE (text);
958     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
959         style->line_height.set = TRUE;
960         style->line_height.inherit = FALSE;
961         style->line_height.normal = FALSE;
962         style->line_height.unit = SP_CSS_UNIT_PERCENT;
963         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
964     }
966     unsigned line_count = layout->lineIndex(layout->end());
967     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
968     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
969     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
971     // divide increment by zoom and by the number of lines,
972     // so that the entire object is expanded by by pixels
973     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
975     // divide increment by matrix expansion
976     NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
977     zby = zby / NR::expansion(t);
979     switch (style->line_height.unit) {
980         case SP_CSS_UNIT_NONE:
981         default:
982             // multiplier-type units, stored in computed
983             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
984             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
985             style->line_height.value = style->line_height.computed;
986             break;
987         case SP_CSS_UNIT_EM:
988         case SP_CSS_UNIT_EX:
989         case SP_CSS_UNIT_PERCENT:
990             // multiplier-type units, stored in value
991             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
992             else style->line_height.value *= (average_line_height + zby) / average_line_height;
993             break;
994             // absolute-type units
995             case SP_CSS_UNIT_PX:
996             style->line_height.computed += zby;
997             style->line_height.value = style->line_height.computed;
998             break;
999             case SP_CSS_UNIT_PT:
1000             style->line_height.computed += zby * PT_PER_PX;
1001             style->line_height.value = style->line_height.computed;
1002             break;
1003             case SP_CSS_UNIT_PC:
1004             style->line_height.computed += zby * (PT_PER_PX / 12);
1005             style->line_height.value = style->line_height.computed;
1006             break;
1007             case SP_CSS_UNIT_MM:
1008             style->line_height.computed += zby * MM_PER_PX;
1009             style->line_height.value = style->line_height.computed;
1010             break;
1011             case SP_CSS_UNIT_CM:
1012             style->line_height.computed += zby * CM_PER_PX;
1013             style->line_height.value = style->line_height.computed;
1014             break;
1015             case SP_CSS_UNIT_IN:
1016             style->line_height.computed += zby * IN_PER_PX;
1017             style->line_height.value = style->line_height.computed;
1018             break;
1019     }
1020     text->updateRepr();
1021     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1025 /* ***************************************************************************************************/
1026 //                           S T Y L E   A P P L I C A T I O N
1029 /** converts an iterator to a character index, mainly because ustring::substr()
1030 doesn't have a version that takes iterators as parameters. */
1031 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1033     unsigned n = 0;
1034     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1035         n++;
1036     return n;
1039 /** applies the given style string on top of the existing styles for \a item,
1040 as opposed to sp_style_merge_from_style_string which merges its parameter
1041 underneath the existing styles (ie ignoring already set properties). */
1042 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1044     SPStyle *new_style = sp_style_new();
1045     sp_style_merge_from_style_string(new_style, style_string);
1046     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1047     if (item_style_string && *item_style_string)
1048         sp_style_merge_from_style_string(new_style, item_style_string);
1049     gchar *new_style_string = sp_style_write_string(new_style);
1050     sp_style_unref(new_style);
1051     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1052     g_free(new_style_string);
1055 /** Returns true if the style of \a parent and the style of \a child are
1056 equivalent (and hence the children of both will appear the same). It is a
1057 limitation of the current implementation that \a parent must be a (not
1058 necessarily immediate) ancestor of \a child. */
1059 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1061     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1062     // will not work. Firstly because of an inheritance bug in style.cpp that has
1063     // implications too large for me to feel safe fixing, but mainly because the css spec
1064     // requires that the computed value is inherited, not the specified value.
1065     g_assert(parent->isAncestorOf(child));
1066     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1067     // we have to write parent_style then read it again, because some properties format their values
1068     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1069     SPStyle *parent_spstyle = sp_style_new();
1070     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1071     g_free(parent_style);
1072     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1073     sp_style_unref(parent_spstyle);
1075     Glib::ustring child_style_construction(parent_style);
1076     while (child != parent) {
1077         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1078         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1079         if (style_text && *style_text) {
1080             child_style_construction += ';';
1081             child_style_construction += style_text;
1082         }
1083         child = SP_OBJECT_PARENT(child);
1084     }
1085     SPStyle *child_spstyle = sp_style_new();
1086     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1087     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1088     sp_style_unref(child_spstyle);
1089     bool equal = !strcmp(child_style, parent_style);
1090     g_free(child_style);
1091     g_free(parent_style);
1092     return equal;
1095 /** returns true if \a first and \a second contain all the same attributes
1096 with the same values as each other. Note that we have to compare both
1097 forwards and backwards to make sure we don't miss any attributes that are
1098 in one but not the other. */
1099 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1101     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1102     for ( ; attrs ; attrs++) {
1103         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1104         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1105             return false;
1106     }
1107     attrs = second->attributeList();
1108     for ( ; attrs ; attrs++) {
1109         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1110         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1111             return false;
1112     }
1113     return true;
1116 /** sets the given css attribute on this object and all its descendants.
1117 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1118 transform stuff. */
1119 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1121     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1123     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1124         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1125             // Unset properties which are accumulating and thus should not be set recursively.
1126             // 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.
1127             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1128             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1129             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1130             apply_css_recursive(child, css_recurse);
1131             sp_repr_css_attr_unref(css_recurse);
1132         } else {
1133             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1134         }
1135     }
1138 /** applies the given style to all the objects at the given level and below
1139 which are between \a start_item and \a end_item, creating spans as necessary.
1140 If \a start_item or \a end_item are NULL then the style is applied to all
1141 objects to the beginning or end respectively. \a span_object_name is the
1142 name of the xml for a text span (ie tspan or flowspan). */
1143 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)
1145     bool passed_start = start_item == NULL ? true : false;
1147     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1148         if (start_item == child)
1149             passed_start = true;
1151         if (passed_start) {
1152             if (end_item && child->isAncestorOf(end_item)) {
1153                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1154                 break;
1155             }
1156             // apply style
1158             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1159             // often this means that new spans are created before child and child is modified only
1160             if (SP_IS_STRING(child)) {
1161                 SPString *string_item = SP_STRING(child);
1162                 bool surround_entire_string = true;
1164                 Inkscape::XML::Node *child_span = sp_repr_new(span_object_name);
1165                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1166                 SPObject *prev_item = SP_OBJECT_PREV(child);
1167                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1169                 if (child == start_item || child == end_item) {
1170                     surround_entire_string = false;
1171                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1172                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1173                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1174                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1176                         Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1177                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1178                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1179                         Inkscape::GC::release(text_before);
1180                         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());
1181                         child_span->appendChild(text_in_span);
1182                         Inkscape::GC::release(text_in_span);
1183                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1185                     } else if (child == end_item) {
1186                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1187                         //  (includes case where start_text_iter == begin())
1188                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1189                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1191                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1192                         Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(0, end_char_index).c_str());
1193                         child_span->appendChild(text_in_span);
1194                         Inkscape::GC::release(text_in_span);
1195                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1197                     } else if (start_text_iter != string_item->string.begin()) {
1198                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1199                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1201                         Inkscape::XML::Node *text_before = sp_repr_new_text(string_item->string.substr(0, start_char_index).c_str());
1202                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1203                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1204                         Inkscape::GC::release(text_before);
1205                         Inkscape::XML::Node *text_in_span = sp_repr_new_text(string_item->string.substr(start_char_index).c_str());
1206                         child_span->appendChild(text_in_span);
1207                         Inkscape::GC::release(text_in_span);
1208                         child->deleteObject();
1209                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1211                     } else
1212                         surround_entire_string = true;
1213                 }
1214                 if (surround_entire_string) {
1215                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1216                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1217                     Inkscape::GC::anchor(child_repr);
1218                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1219                     child_span->appendChild(child_repr);
1220                     Inkscape::GC::release(child_repr);
1221                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1222                 }
1223                 Inkscape::GC::release(child_span);
1225             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1226                 apply_css_recursive(child, css);
1227             }
1229         } else {  // !passed_start
1230             if (child->isAncestorOf(start_item)) {
1231                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1232                 if (end_item && child->isAncestorOf(end_item))
1233                     break;   // only happens when start_item == end_item (I think)
1234                 passed_start = true;
1235             }
1236         }
1238         if (end_item == child)
1239             break;
1240     }
1243 /* if item is at the beginning of a tree it doesn't matter which element
1244 it points to so for neatness we would like it to point to the highest
1245 possible child of \a common_ancestor. There is no iterator return because
1246 a string can never be an ancestor.
1248 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1249 like * to point to the inner span because we can apply style to that whole
1250 span. */
1251 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1253     if (item == common_ancestor)
1254         return item;
1255     if (SP_IS_STRING(item))
1256         if (text_iter != SP_STRING(item)->string.begin())
1257             return item;
1258     for ( ; ; ) {
1259         SPObject *parent = SP_OBJECT_PARENT(item);
1260         if (parent == common_ancestor)
1261             break;
1262         if (item != parent->firstChild())
1263             break;
1264         item = parent;
1265     }
1266     return item;
1270 /**     empty spans: abc<span></span>def
1271                       -> abcdef                  */
1272 static bool tidy_operator_empty_spans(SPObject **item)
1274     if ((*item)->hasChildren()) return false;
1275     if (is_line_break_object(*item)) return false;
1276     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1277     SPObject *next = SP_OBJECT_NEXT(*item);
1278     (*item)->deleteObject();
1279     *item = next;
1280     return true;
1283 /**    inexplicable spans: abc<span style="">def</span>ghi
1284                             -> "abc""def""ghi"
1285 the repeated strings will be merged by another operator. */
1286 static bool tidy_operator_inexplicable_spans(SPObject **item)
1288     if (SP_IS_STRING(*item)) return false;
1289     if (is_line_break_object(*item)) return false;
1290     TextTagAttributes *attrs = attributes_for_object(*item);
1291     if (attrs && attrs->anyAttributesSet()) return false;
1292     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1293     SPObject *next = *item;
1294     while ((*item)->hasChildren()) {
1295         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1296         Inkscape::GC::anchor(repr);
1297         SP_OBJECT_REPR(*item)->removeChild(repr);
1298         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1299         Inkscape::GC::release(repr);
1300         next = SP_OBJECT_NEXT(next);
1301     }
1302     (*item)->deleteObject();
1303     *item = next;
1304     return true;
1307 /**    repeated spans: <font a>abc</font><font a>def</font>
1308                         -> <font a>abcdef</font>            */
1309 static bool tidy_operator_repeated_spans(SPObject **item)
1311     SPObject *first = *item;
1312     SPObject *second = SP_OBJECT_NEXT(first);
1313     if (second == NULL) return false;
1315     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1316     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1318     if (first_repr->type() != second_repr->type()) return false;
1320     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1321         // also amalgamate consecutive SPStrings into one
1322         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1323         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1324         second_repr->parent()->removeChild(second_repr);
1325         return true;
1326     }
1328     // merge consecutive spans with identical styles into one
1329     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1330     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1331     if (is_line_break_object(second)) return false;
1332     gchar const *first_style = first_repr->attribute("style");
1333     gchar const *second_style = second_repr->attribute("style");
1334     if (!((first_style == NULL && second_style == NULL)
1335           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1336         return false;
1338     // all our tests passed: do the merge
1339     TextTagAttributes *attributes_first = attributes_for_object(first);
1340     TextTagAttributes *attributes_second = attributes_for_object(second);
1341     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1342         TextTagAttributes attributes_first_copy = *attributes_first;
1343         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1344     }
1345     move_child_nodes(second_repr, first_repr);
1346     second_repr->parent()->removeChild(second_repr);
1347     return true;
1348     // *item is still the next object to process
1351 /**    redundant nesting: <font a><font b>abc</font></font>
1352                            -> <font b>abc</font>
1353        excessive nesting: <font a><size 1>abc</size></font>
1354                            -> <font a,size 1>abc</font>      */
1355 static bool tidy_operator_excessive_nesting(SPObject **item)
1357     if (!(*item)->hasChildren()) return false;
1358     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1359     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1360         return false;
1361     if (SP_IS_STRING((*item)->firstChild())) return false;
1362     if (is_line_break_object((*item)->firstChild())) return false;
1363     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1364     if (attrs && attrs->anyAttributesSet()) return false;
1365     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1366     if (child_style && *child_style)
1367         overwrite_style_with_string(*item, child_style);
1368     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1369     (*item)->firstChild()->deleteObject();
1370     return true;
1373 /** helper for tidy_operator_redundant_double_nesting() */
1374 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1376     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1377         return false;
1378     if (SP_IS_STRING(child)) return false;
1379     if (is_line_break_object(child)) return false;
1380     if (is_line_break_object(*item)) return false;
1381     TextTagAttributes *attrs = attributes_for_object(child);
1382     if (attrs && attrs->anyAttributesSet()) return false;
1383     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1385     Inkscape::XML::Node *insert_after_repr;
1386     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1387     else insert_after_repr = SP_OBJECT_REPR(*item);
1388     while (SP_OBJECT_REPR(child)->childCount()) {
1389         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1390         Inkscape::GC::anchor(move_repr);
1391         SP_OBJECT_REPR(child)->removeChild(move_repr);
1392         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1393         Inkscape::GC::release(move_repr);
1394         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1395     }
1396     child->deleteObject();
1397     return true;
1400 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1401                                 -> <font b>abc<font a>def</font>ghi</font>
1402 this function does its work when the parameter is the <font a> tag in the
1403 example. You may note that this only does its work when the doubly-nested
1404 child is the first or last. The other cases are called 'style inversion'
1405 below, and I'm not yet convinced that the result of that operation will be
1406 tidier in all cases. */
1407 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1409     if (!(*item)->hasChildren()) return false;
1410     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1411     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1412         return true;
1413     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1414         return true;
1415     return false;
1418 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1419 then compares the styles for item+child versus just child. If they're equal,
1420 tidying is possible. */
1421 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1423     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1424         return false;
1425     if (SP_IS_STRING(child)) return false;
1426     if (is_line_break_object(child)) return false;
1427     if (is_line_break_object(*item)) return false;
1428     TextTagAttributes *attrs = attributes_for_object(child);
1429     if (attrs && attrs->anyAttributesSet()) return false;
1430     attrs = attributes_for_object(*item);
1431     if (attrs && attrs->anyAttributesSet()) return false;
1433     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1434     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1435     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1436     if (child_style && *child_style) {
1437         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1438         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1439     }
1440     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1441     if (item_style && *item_style) {
1442         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1443     }
1444     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1445     sp_repr_css_attr_unref(css_child_and_item);
1446     sp_repr_css_attr_unref(css_child_only);
1447     if (!equal) return false;
1449     Inkscape::XML::Node *new_span = sp_repr_new(SP_OBJECT_REPR(*item)->name());
1450     if (prepend) {
1451         SPObject *prev = SP_OBJECT_PREV(*item);
1452         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1453     } else
1454         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1455     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1456     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1457     Inkscape::GC::release(new_span);
1458     child->deleteObject();
1459     return true;
1462 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1463                                 -> <font b>abc</font><font>def</font>
1464 test this by applying a colour to a region, then a different colour to
1465 a partially-overlapping region. */
1466 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1468     if (!(*item)->hasChildren()) return false;
1469     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1470     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1471         return true;
1472     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1473         return true;
1474     return false;
1477 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1478 in a paragraph which is not \a not_obj. */
1479 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1481     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1482     {
1483         if (child == not_obj) continue;
1484         if (child->hasChildren()) {
1485             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1486             if (ret) return ret;
1487         } else if (SP_IS_STRING(child))
1488             return SP_STRING(child);
1489     }
1490     return NULL;
1493 /** whitespace-only spans: abc<font> </font>def
1494                             -> abc<font></font> def
1495                            abc<b><i>def</i> </b>ghi
1496                             -> abc<b><i>def</i></b> ghi   */
1497 static bool tidy_operator_styled_whitespace(SPObject **item)
1499     if (!SP_IS_STRING(*item)) return false;
1500     Glib::ustring const &str = SP_STRING(*item)->string;
1501     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1502         if (!g_unichar_isspace(*it)) return false;
1504     SPObject *test_item = *item;
1505     SPString *next_string;
1506     for ( ; ; ) {  // find the next string
1507         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1508         if (next_string) {
1509             next_string->string.insert(0, str);
1510             break;
1511         }
1512         for ( ; ; ) {   // go up one item in the xml
1513             test_item = SP_OBJECT_PARENT(test_item);
1514             if (is_line_break_object(test_item)) break;
1515             SPObject *next = SP_OBJECT_NEXT(test_item);
1516             if (next) {
1517                 test_item = next;
1518                 break;
1519             }
1520         }
1521         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1522             next_string = find_last_string_child_not_equal_to(test_item, *item);
1523             if (next_string == NULL) return false;   // an empty paragraph
1524             next_string->string += str;
1525             break;
1526         }
1527     }
1528     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1529     SPObject *delete_obj = *item;
1530     *item = SP_OBJECT_NEXT(*item);
1531     delete_obj->deleteObject();
1532     return true;
1535 /* possible tidy operators that are not yet implemented, either because
1536 they are difficult, occur infrequently, or because I'm not sure that the
1537 output is tidier in all cases:
1538     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1539                                               -> <div italic><para>abc</para></div>
1540     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1541                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1542     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1543                           -> <size 1><font a>abc</font>def</size>
1544 */
1546 /** Recursively walks the xml tree calling a set of cleanup operations on
1547 every child. Returns true if any changes were made to the tree.
1549 All the tidy operators return true if they made changes, and alter their
1550 parameter to point to the next object that should be processed, or NULL.
1551 They must not significantly alter (ie delete) any ancestor elements of the
1552 one they are passed.
1554 It may be that some of the later tidy operators that I wrote are actually
1555 general cases of the earlier operators, and hence the special-case-only
1556 versions can be removed. I haven't analysed my work in detail to figure
1557 out if this is so. */
1558 static bool tidy_xml_tree_recursively(SPObject *root)
1560     static bool (* const tidy_operators[])(SPObject**) = {
1561         tidy_operator_empty_spans,
1562         tidy_operator_inexplicable_spans,
1563         tidy_operator_repeated_spans,
1564         tidy_operator_excessive_nesting,
1565         tidy_operator_redundant_double_nesting,
1566         tidy_operator_redundant_semi_nesting,
1567         tidy_operator_styled_whitespace
1568     };
1569     bool changes = false;
1571     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1572         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child)) {
1573             child = SP_OBJECT_NEXT(child);
1574             continue;
1575         }
1576         if (child->hasChildren())
1577             changes |= tidy_xml_tree_recursively(child);
1579         unsigned i;
1580         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1581             if (tidy_operators[i](&child)) {
1582                 changes = true;
1583                 break;
1584             }
1585         }
1586         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1587             child = SP_OBJECT_NEXT(child);
1588     }
1589     return changes;
1592 /** Applies the given CSS fragment to the characters of the given text or
1593 flowtext object between \a start and \a end, creating or removing span
1594 elements as necessary and optimal. */
1595 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1597     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1598     if (start == end) return;
1599     Inkscape::Text::Layout::iterator first, last;
1600     if (start < end) {
1601         first = start;
1602         last = end;
1603     } else {
1604         first = end;
1605         last = start;
1606     }
1607     Inkscape::Text::Layout const *layout = te_get_layout(text);
1608     SPObject *start_item, *end_item;
1609     Glib::ustring::iterator start_text_iter, end_text_iter;
1610     layout->getSourceOfCharacter(first, (void**)&start_item, &start_text_iter);
1611     layout->getSourceOfCharacter(last, (void**)&end_item, &end_text_iter);
1612     if (start_item == NULL)
1613         return;   // start is at end of text
1614     if (is_line_break_object(start_item))
1615         start_item = SP_OBJECT_NEXT(start_item);
1616     if (is_line_break_object(end_item))
1617         end_item = SP_OBJECT_NEXT(end_item);
1618     if (end_item == NULL) end_item = text;
1620     /* stage 1: applying the style. Go up to the closest common ancestor of
1621     start and end and then semi-recursively apply the style to all the
1622     objects in between. The semi-recursion is because it's only necessary
1623     at the beginning and end; the style can just be applied to the root
1624     child in the middle.
1625     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1626     The recursion may involve creating new spans.
1627     */
1628     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1629     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1630     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1631     recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1633     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1634     /* discussion: this stage requires a certain level of inventiveness because
1635     it's not clear what the best representation is in many cases. An ideal
1636     implementation would provide some sort of scoring function to rate the
1637     ugliness of a given xml tree and try to reduce said function, but providing
1638     the various possibilities to be rated is non-trivial. Instead, I have opted
1639     for a multi-pass technique which simply recognises known-ugly patterns and
1640     has matching routines for optimising the patterns it finds. It's reasonably
1641     easy to add new pattern matching processors. If everything gets disastrous
1642     and neither option can be made to work, a fallback could be to reduce
1643     everything to a single level of nesting and drop all pretence of
1644     roundtrippability. */
1645     while (tidy_xml_tree_recursively(common_ancestor));
1647     // if we only modified subobjects this won't have been automatically sent
1648     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1651 /*
1652   Local Variables:
1653   mode:c++
1654   c-file-style:"stroustrup"
1655   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1656   indent-tabs-mode:nil
1657   fill-column:99
1658   End:
1659 */
1660 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :