Code

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