Code

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