Code

b92d85b15f03c8428464a3da065489f696335eb4
[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 <cstring>
18 #include <string>
19 #include <glibmm/i18n.h>
21 #include "desktop.h"
22 #include "inkscape.h"
23 #include "message-stack.h"
24 #include "style.h"
25 #include "unit-constants.h"
27 #include "document.h"
28 #include "xml/repr.h"
29 #include "xml/attribute-record.h"
31 #include "sp-textpath.h"
32 #include "sp-flowtext.h"
33 #include "sp-flowdiv.h"
34 #include "sp-flowregion.h"
35 #include "sp-tref.h"
36 #include "sp-tspan.h"
38 #include "text-editing.h"
40 static const gchar *tref_edit_message = _("You cannot edit <b>cloned character data</b>.");
42 static bool tidy_xml_tree_recursively(SPObject *root);
44 Inkscape::Text::Layout const * te_get_layout (SPItem const *item)
45 {
46     if (SP_IS_TEXT(item)) {
47         return &(SP_TEXT(item)->layout);
48     } else if (SP_IS_FLOWTEXT (item)) {
49         return &(SP_FLOWTEXT(item)->layout);
50     }
51     return NULL;
52 }
54 static void te_update_layout_now (SPItem *item)
55 {
56     if (SP_IS_TEXT(item))
57         SP_TEXT(item)->rebuildLayout();
58     else if (SP_IS_FLOWTEXT (item))
59         SP_FLOWTEXT(item)->rebuildLayout();
60 }
62 /** Returns true if there are no visible characters on the canvas */
63 bool
64 sp_te_output_is_empty (SPItem const *item)
65 {
66     Inkscape::Text::Layout const *layout = te_get_layout(item);
67     return layout->begin() == layout->end();
68 }
70 /** Returns true if the user has typed nothing in the text box */
71 bool
72 sp_te_input_is_empty (SPObject const *item)
73 {
74     if (SP_IS_STRING(item)) return SP_STRING(item)->string.empty();
75     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child))
76         if (!sp_te_input_is_empty(child)) return false;
77     return true;
78 }
80 Inkscape::Text::Layout::iterator
81 sp_te_get_position_by_coords (SPItem const *item, NR::Point &i_p)
82 {
83     NR::Matrix im = from_2geom(sp_item_i2d_affine (item));
84     im = im.inverse();
86     NR::Point p = i_p * im;
87     Inkscape::Text::Layout const *layout = te_get_layout(item);
88     return layout->getNearestCursorPositionTo(p);
89 }
91 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)
92 {
93     if (start == end)
94         return std::vector<NR::Point>();
95     Inkscape::Text::Layout const *layout = te_get_layout(item);
96     if (layout == NULL)
97         return std::vector<NR::Point>();
99     return layout->createSelectionShape(start, end, transform);
102 void
103 sp_te_get_cursor_coords (SPItem const *item, Inkscape::Text::Layout::iterator const &position, NR::Point &p0, NR::Point &p1)
105     Inkscape::Text::Layout const *layout = te_get_layout(item);
106     double height, rotation;
107     layout->queryCursorShape(position, &p0, &height, &rotation);
108     p1 = NR::Point(p0[NR::X] + height * sin(rotation), p0[NR::Y] - height * cos(rotation));
111 SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
113     Inkscape::Text::Layout const *layout = te_get_layout(text);
114     if (layout == NULL)
115         return NULL;
116     SPObject const *pos_obj = 0;
117     void *rawptr = 0;
118     layout->getSourceOfCharacter(position, &rawptr);
119     pos_obj = SP_OBJECT(rawptr);
120     if (pos_obj == 0) pos_obj = text;
121     while (SP_OBJECT_STYLE(pos_obj) == NULL)
122         pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
123     return SP_OBJECT_STYLE(pos_obj);
126 /*
127  * for debugging input
128  *
129 char * dump_hexy(const gchar * utf8)
131     static char buffer[1024];
133     buffer[0]='\0';
134     for (const char *ptr=utf8; *ptr; ptr++) {
135         sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
136     }
137     return buffer;
139 */
141 Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
143     iterator_pair pair;
144     sp_te_delete(item, start, end, pair);
145     return sp_te_insert(item, pair.first, utf8);
149 /* ***************************************************************************************************/
150 //                             I N S E R T I N G   T E X T
152 static bool is_line_break_object(SPObject const *object)
154     bool is_line_break = false;
155     
156     if (object) {
157         if (SP_IS_TEXT(object)
158                 || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
159                 || SP_IS_TEXTPATH(object)
160                 || SP_IS_FLOWDIV(object)
161                 || SP_IS_FLOWPARA(object)
162                 || SP_IS_FLOWLINE(object)
163                 || SP_IS_FLOWREGIONBREAK(object)) {
164                     
165             is_line_break = true;
166         }
167     }
168     
169     return is_line_break;
172 /** returns the attributes for an object, or NULL if it isn't a text,
173 tspan, tref, or textpath. */
174 static TextTagAttributes* attributes_for_object(SPObject *object)
176     if (SP_IS_TSPAN(object))
177         return &SP_TSPAN(object)->attributes;
178     if (SP_IS_TEXT(object))
179         return &SP_TEXT(object)->attributes;
180     if (SP_IS_TREF(object))
181         return &SP_TREF(object)->attributes;
182     if (SP_IS_TEXTPATH(object))
183         return &SP_TEXTPATH(object)->attributes;
184     return NULL;
187 static const char * span_name_for_text_object(SPObject const *object)
189     if (SP_IS_TEXT(object)) return "svg:tspan";
190     else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
191     return NULL;
194 /** Recursively gets the length of all the SPStrings at or below the given
195 \a item. Also adds 1 for each line break encountered. */
196 unsigned sp_text_get_length(SPObject const *item)
198     unsigned length = 0;
200     if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
201     
202     if (is_line_break_object(item)) length++;
203     
204     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
205         if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
206         else length += sp_text_get_length(child);
207     }
208     return length;
211 /** Recursively gets the length of all the SPStrings at or below the given
212 \a item, before and not including \a upto. Also adds 1 for each line break encountered. */
213 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
215     unsigned length = 0;
217     // The string is the lowest level and the length can be counted directly. 
218     if (SP_IS_STRING(item)) {
219         return SP_STRING(item)->string.length();
220     }
221     
222     // Take care of new lines...
223     if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
224         if (item != SP_OBJECT_PARENT(item)->firstChild()) {
225             // add 1 for each newline
226             length++;
227         }
228     }
229     
230     // Count the length of the children
231     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
232         if (upto && child == upto) {
233             // hit upto, return immediately
234             return length;
235         }
236         if (SP_IS_STRING(child)) {
237             length += SP_STRING(child)->string.length();
238         }
239         else {
240             if (upto && child->isAncestorOf(upto)) {
241                 // upto is below us, recurse and break loop
242                 length += sp_text_get_length_upto(child, upto);
243                 return length;
244             } else {
245                 // recurse and go to the next sibling
246                 length += sp_text_get_length_upto(child, upto);
247             }
248         }
249     }
250     return length;
253 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
255     switch (old_node->type()) {
256         case Inkscape::XML::ELEMENT_NODE: {
257             Inkscape::XML::Node *new_node = xml_doc->createElement(old_node->name());
258             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
259             GQuark const id_key = g_quark_from_string("id");
260             for ( ; attributes ; attributes++) {
261                 if (attributes->key == id_key) continue;
262                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
263             }
264             return new_node;
265         }
267         case Inkscape::XML::TEXT_NODE:
268             return xml_doc->createTextNode(old_node->content());
270         case Inkscape::XML::COMMENT_NODE:
271             return xml_doc->createComment(old_node->content());
273         case Inkscape::XML::PI_NODE:
274             return xml_doc->createPI(old_node->name(), old_node->content());
276         case Inkscape::XML::DOCUMENT_NODE:
277             return NULL;   // this had better never happen
278     }
279     return NULL;
282 /** returns the sum of the (recursive) lengths of all the SPStrings prior
283 to \a item at the same level. */
284 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
286     unsigned char_index = 0;
287     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
288         char_index += sp_text_get_length(sibling);
289     return char_index;
292 /** splits the attributes for the first object at the given \a char_index
293 and moves the ones after that point into \a second_item. */
294 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
296     TextTagAttributes *first_attrs = attributes_for_object(first_item);
297     TextTagAttributes *second_attrs = attributes_for_object(second_item);
298     if (first_attrs && second_attrs)
299         first_attrs->split(char_index, second_attrs);
302 /** recursively divides the XML node tree into two objects: the original will
303 contain all objects up to and including \a split_obj and the returned value
304 will be the new leaf which represents the copy of \a split_obj and extends
305 down the tree with new elements all the way to the common root which is the
306 parent of the first line break node encountered.
307 */
308 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
310     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
311     if (is_line_break_object(split_obj)) {
312         Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
313         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
314         Inkscape::GC::release(new_node);
315         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
316         return SP_OBJECT_NEXT(split_obj);
317     }
319     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
320     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
321     // copy the split node
322     Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
323     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
324     Inkscape::GC::release(new_node);
326     // sort out the copied attributes (x/y/dx/dy/rotate)
327     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
329     // then move all the subsequent nodes
330     split_obj = SP_OBJECT_NEXT(split_obj);
331     while (split_obj) {
332         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
333         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
334         Inkscape::GC::anchor(move_repr);
335         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
336         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
337         Inkscape::GC::release(move_repr);
339         split_obj = next_obj;
340     }
341     return duplicate_obj->firstChild();
344 /** inserts a new line break at the given position in a text or flowtext
345 object. If the position is in the middle of a span, the XML tree must be
346 chopped in two such that the line can be created at the root of the text
347 element. Returns an iterator pointing just after the inserted break. */
348 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
350     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
351     // texpaths attached to the same path, with a vertical shift
352     if (SP_IS_TEXT_TEXTPATH (item) || SP_IS_TREF(item))
353         return position;
354         
355     SPDesktop *desktop = SP_ACTIVE_DESKTOP; 
357     Inkscape::Text::Layout const *layout = te_get_layout(item);
358     SPObject *split_obj = 0;
359     Glib::ustring::iterator split_text_iter;
360     if (position != layout->end()) {
361         void *rawptr = 0;
362         layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
363         split_obj = SP_OBJECT(rawptr);
364     }
366     if (split_obj == 0 || is_line_break_object(split_obj)) {
367         if (split_obj == 0) split_obj = item->lastChild();
368         
369         if (SP_IS_TREF(split_obj)) {
370                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
371             return position;
372         }
373         
374         if (split_obj) {
375             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
376             Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
377             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
378             Inkscape::GC::release(new_node);
379         }
380     } else if (SP_IS_STRING(split_obj)) {
381         // If the parent is a tref, editing on this particular string is disallowed.
382         if (SP_IS_TREF(SP_OBJECT_PARENT(split_obj))) {
383             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
384             return position;
385         }
386         
387         Glib::ustring *string = &SP_STRING(split_obj)->string;
388         unsigned char_index = 0;
389         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
390             char_index++;
391         // we need to split the entire text tree into two
392         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
393         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
394         string->erase(split_text_iter, string->end());
395         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
396         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
397     } else {
398         // TODO
399         // I think the only case to put here is arbitrary gaps, which nobody uses yet
400     }
401     item->updateRepr();
402     unsigned char_index = layout->iteratorToCharIndex(position);
403     te_update_layout_now(item);
404     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
405     return layout->charIndexToIterator(char_index + 1);
408 /** finds the first SPString after the given position, including children, excluding parents */
409 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
411     while (start_obj) {
412         if (start_obj->hasChildren()) {
413             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
414             if (found_string) return found_string;
415         }
416         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
417         start_obj = SP_OBJECT_NEXT(start_obj);
418         if (is_line_break_object(start_obj))
419             break;   // don't cross line breaks
420     }
421     return NULL;
424 /** inserts the given characters into the given string and inserts
425 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
426 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
428     unsigned char_index = 0;
429     unsigned char_count = g_utf8_strlen(utf8, -1);
430     Glib::ustring *string = &SP_STRING(string_item)->string;
432     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
433         char_index++;
434     string->replace(iter_at, iter_at, utf8);
436     SPObject *parent_item = string_item;
437     for ( ; ; ) {
438         char_index += sum_sibling_text_lengths_before(parent_item);
439         parent_item = SP_OBJECT_PARENT(parent_item);
440         TextTagAttributes *attributes = attributes_for_object(parent_item);
441         if (!attributes) break;
442         attributes->insert(char_index, char_count);
443     }
446 /** Inserts the given text into a text or flowroot object. Line breaks
447 cannot be inserted using this function, see sp_te_insert_line(). Returns
448 an iterator pointing just after the inserted text. */
449 Inkscape::Text::Layout::iterator
450 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
452     if (!g_utf8_validate(utf8,-1,NULL)) {
453         g_warning("Trying to insert invalid utf8");
454         return position;
455     }
456     
457     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
459     Inkscape::Text::Layout const *layout = te_get_layout(item);
460     SPObject *source_obj = 0;
461     void *rawptr = 0;
462     Glib::ustring::iterator iter_text;
463     // we want to insert after the previous char, not before the current char.
464     // it makes a difference at span boundaries
465     Inkscape::Text::Layout::iterator it_prev_char = position;
466     bool cursor_at_start = !it_prev_char.prevCharacter();
467     bool cursor_at_end = position == layout->end();
468     layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
469     source_obj = SP_OBJECT(rawptr);
470     if (SP_IS_STRING(source_obj)) {
471         // If the parent is a tref, editing on this particular string is disallowed.
472         if (SP_IS_TREF(SP_OBJECT_PARENT(source_obj))) {
473             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
474             return position;
475         }
476         
477         // Now the simple case can begin...
478         if (!cursor_at_start) iter_text++;
479         SPString *string_item = SP_STRING(source_obj);
480         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
481     } else {
482         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
483         Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
484         if (cursor_at_start) {
485             source_obj = item;
486             if (source_obj->hasChildren()) {
487                 source_obj = source_obj->firstChild();
488                 if (SP_IS_FLOWTEXT(item)) {
489                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
490                         source_obj = SP_OBJECT_NEXT(source_obj);
491                     if (source_obj == NULL)
492                         source_obj = item;
493                 }
494             }
495             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
496                 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
497                 SP_OBJECT_REPR(item)->appendChild(para);
498                 source_obj = item->lastChild();
499             }
500         } else
501             source_obj = SP_OBJECT_NEXT(source_obj);
503         if (source_obj) {  // never fails
504             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
505             if (string_item == NULL) {
506                 // need to add an SPString in this (pathological) case
507                 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
508                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
509                 Inkscape::GC::release(rstring);
510                 g_assert(SP_IS_STRING(source_obj->firstChild()));
511                 string_item = SP_STRING(source_obj->firstChild());
512             }
513             // If the parent is a tref, editing on this particular string is disallowed.
514             if (SP_IS_TREF(SP_OBJECT_PARENT(string_item))) {
515                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
516                 return position;
517             }
518             
519             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
520         }
521     }
523     item->updateRepr();
524     unsigned char_index = layout->iteratorToCharIndex(position);
525     te_update_layout_now(item);
526     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
527     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
531 /* ***************************************************************************************************/
532 //                            D E L E T I N G   T E X T
534 /** moves all the children of \a from_repr to \a to_repr, either before
535 the existing children or after them. Order is maintained. The empty
536 \a from_repr is not deleted. */
537 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
539     while (from_repr->childCount()) {
540         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
541         Inkscape::GC::anchor(child);
542         from_repr->removeChild(child);
543         if (prepend) to_repr->addChild(child, NULL);
544         else to_repr->appendChild(child);
545         Inkscape::GC::release(child);
546     }
549 /** returns the object in the tree which is the closest ancestor of both
550 \a one and \a two. It will never return anything higher than \a text. */
551 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
553     if (one == NULL || two == NULL)
554         return text;
555     SPObject *common_ancestor = one;
556     if (SP_IS_STRING(common_ancestor))
557         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
558     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
559         g_assert(common_ancestor != text);
560         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
561     }
562     return common_ancestor;
565 /** positions \a para_obj and \a text_iter to be pointing at the end
566 of the last string in the last leaf object of \a para_obj. If the last
567 leaf is not an SPString then \a text_iter will be unchanged. */
568 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
570     while ((*para_obj)->hasChildren())
571         *para_obj = (*para_obj)->lastChild();
572     if (SP_IS_STRING(*para_obj))
573         *text_iter = SP_STRING(*para_obj)->string.end();
576 /** delete the line break pointed to by \a item by merging its children into
577 the next suitable object and deleting \a item. Returns the object after the
578 ones that have just been moved and sets \a next_is_sibling accordingly. */
579 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
581     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
582     SPObject *next_item = NULL;
583     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
585     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
586       <div></div><p>*text</p>
587       <p><div></div>*text</p>
588       <p><div></div></p><p>*text</p>
589     */
590     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
591     Inkscape::XML::Node *new_span_repr = xml_doc->createElement(span_name_for_text_object(root));
593     if (gchar const *a = this_repr->attribute("dx"))
594         new_span_repr->setAttribute("dx", a);
595     if (gchar const *a = this_repr->attribute("dy"))
596         new_span_repr->setAttribute("dy", a);
597     if (gchar const *a = this_repr->attribute("rotate"))
598         new_span_repr->setAttribute("rotate", a);
600     SPObject *following_item = item;
601     while (SP_OBJECT_NEXT(following_item) == NULL) {
602         following_item = SP_OBJECT_PARENT(following_item);
603         g_assert(following_item != root);
604     }
605     following_item = SP_OBJECT_NEXT(following_item);
607     SPObject *new_parent_item;
608     if (SP_IS_STRING(following_item)) {
609         new_parent_item = SP_OBJECT_PARENT(following_item);
610         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
611         next_item = following_item;
612         *next_is_sibling = true;
613     } else {
614         new_parent_item = following_item;
615         next_item = new_parent_item->firstChild();
616         *next_is_sibling = true;
617         if (next_item == NULL) {
618             next_item = new_parent_item;
619             *next_is_sibling = false;
620         }
621         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
622     }
624     // work around a bug in sp_style_write_difference() which causes the difference
625     // not to be written if the second param has a style set which the first does not
626     // by causing the first param to have everything set
627     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
628     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
629     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
630     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
631     for ( ; attrs ; attrs++) {
632         gchar const *key = g_quark_to_string(attrs->key);
633         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
634         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
635             this_node_attrs->setAttribute(key, this_attr);
636     }
637     sp_repr_css_attr_unref(this_node_attrs_inherited);
638     sp_repr_css_attr_unref(this_node_attrs);
639     sp_repr_css_attr_unref(dest_node_attrs);
640     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
642     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
643     if (attributes)
644         attributes->insert(0, moved_char_count);
645     move_child_nodes(this_repr, new_span_repr);
646     this_repr->parent()->removeChild(this_repr);
647     return next_item;
650 /** erases the given characters from the given string and deletes the
651 corresponding x/y/dx/dy/rotate attributes from all its parents. */
652 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
654     unsigned char_index = 0;
655     unsigned char_count = 0;
656     Glib::ustring *string = &SP_STRING(string_item)->string;
658     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
659         char_index++;
660     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
661         char_count++;
662     string->erase(iter_from, iter_to);
663     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
665     SPObject *parent_item = string_item;
666     for ( ; ; ) {
667         char_index += sum_sibling_text_lengths_before(parent_item);
668         parent_item = SP_OBJECT_PARENT(parent_item);
669         TextTagAttributes *attributes = attributes_for_object(parent_item);
670         if (attributes == NULL) break;
672         attributes->erase(char_index, char_count);
673         attributes->writeTo(SP_OBJECT_REPR(parent_item));
674     }
677 /* Deletes the given characters from a text or flowroot object. This is
678 quite a complicated operation, partly due to the cleanup that is done if all
679 the text in a subobject has been deleted, and partly due to the difficulty
680 of figuring out what is a line break and how to delete one. Returns the
681 real start and ending iterators based on the situation. */
682 bool
683 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start,
684               Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
686     bool success = false;
688     iter_pair.first = start;
689     iter_pair.second = end;
690     
691     if (start == end) return success;
692     
693     if (start > end) {
694         iter_pair.first = end;
695         iter_pair.second = start;
696     }
697     
698     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
699     
700     Inkscape::Text::Layout const *layout = te_get_layout(item);
701     SPObject *start_item = 0, *end_item = 0;
702     void *rawptr = 0;
703     Glib::ustring::iterator start_text_iter, end_text_iter;
704     layout->getSourceOfCharacter(iter_pair.first, &rawptr, &start_text_iter);
705     start_item = SP_OBJECT(rawptr);
706     layout->getSourceOfCharacter(iter_pair.second, &rawptr, &end_text_iter);
707     end_item = SP_OBJECT(rawptr);
708     if (start_item == 0)
709         return success;   // start is at end of text
710     if (is_line_break_object(start_item))
711         move_to_end_of_paragraph(&start_item, &start_text_iter);
712     if (end_item == 0) {
713         end_item = item->lastChild();
714         move_to_end_of_paragraph(&end_item, &end_text_iter);
715     }
716     else if (is_line_break_object(end_item))
717         move_to_end_of_paragraph(&end_item, &end_text_iter);
719     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
721     if (start_item == end_item) {
722         // the quick case where we're deleting stuff all from the same string
723         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
724             // If the parent is a tref, editing on this particular string is disallowed.
725             if (SP_IS_TREF(SP_OBJECT_PARENT(start_item))) {
726                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
727             } else {
728                 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
729                 success = true;
730             }
731         }
732     } else {
733         SPObject *sub_item = start_item;
734         // walk the tree from start_item to end_item, deleting as we go
735         while (sub_item != item) {
736             if (sub_item == end_item) {
737                 if (SP_IS_STRING(sub_item)) {
738                     // If the parent is a tref, editing on this particular string is disallowed.
739                     if (SP_IS_TREF(SP_OBJECT_PARENT(sub_item))) {
740                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
741                         break;
742                     }
743             
744                     Glib::ustring *string = &SP_STRING(sub_item)->string;
745                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
746                     success = true;
747                 }
748                 break;
749             }
750             if (SP_IS_STRING(sub_item)) {
751                 SPString *string = SP_STRING(sub_item);
752                 if (sub_item == start_item)
753                     erase_from_spstring(string, start_text_iter, string->string.end());
754                 else
755                     erase_from_spstring(string, string->string.begin(), string->string.end());
756                 success = true;
757             }
758             // walk to the next item in the tree
759             if (sub_item->hasChildren())
760                 sub_item = sub_item->firstChild();
761             else {
762                 SPObject *next_item;
763                 do {
764                     bool is_sibling = true;
765                     next_item = SP_OBJECT_NEXT(sub_item);
766                     if (next_item == NULL) {
767                         next_item = SP_OBJECT_PARENT(sub_item);
768                         is_sibling = false;
769                     }
771                     if (is_line_break_object(sub_item))
772                         next_item = delete_line_break(item, sub_item, &is_sibling);
774                     sub_item = next_item;
775                     if (is_sibling) break;
776                     // no more siblings, go up a parent
777                 } while (sub_item != item && sub_item != end_item);
778             }
779         }
780     }
782     while (tidy_xml_tree_recursively(common_ancestor));
783     te_update_layout_now(item);
784     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
785     layout->validateIterator(&iter_pair.first);
786     layout->validateIterator(&iter_pair.second);
787     return success;
791 /* ***************************************************************************************************/
792 //                            P L A I N   T E X T   F U N C T I O N S
794 /** Gets a text-only representation of the given text or flowroot object,
795 replacing line break elements with '\n'. */
796 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
798     if (*pending_line_break)
799         *string += '\n';
800     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
801         if (SP_IS_STRING(child))
802             *string += SP_STRING(child)->string;
803         else
804             sp_te_get_ustring_multiline(child, string, pending_line_break);
805     }
806     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
807         *pending_line_break = true;
810 /** Gets a text-only representation of the given text or flowroot object,
811 replacing line break elements with '\n'. The return value must be free()d. */
812 gchar *
813 sp_te_get_string_multiline (SPItem const *text)
815     Glib::ustring string;
816     bool pending_line_break = false;
818     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
819     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
820     if (string.empty()) return NULL;
821     return strdup(string.data());
824 /** Gets a text-only representation of the characters in a text or flowroot
825 object from \a start to \a end only. Line break elements are replaced with
826 '\n'. */
827 Glib::ustring
828 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
830     if (start == end) return "";
831     Inkscape::Text::Layout::iterator first, last;
832     if (start < end) {
833         first = start;
834         last = end;
835     } else {
836         first = end;
837         last = start;
838     }
839     Inkscape::Text::Layout const *layout = te_get_layout(text);
840     Glib::ustring result;
841     // not a particularly fast piece of code. I'll optimise it if people start to notice.
842     for ( ; first < last ; first.nextCharacter()) {
843         SPObject *char_item = 0;
844         void *rawptr = 0;
845         Glib::ustring::iterator text_iter;
846         layout->getSourceOfCharacter(first, &rawptr, &text_iter);
847         char_item = SP_OBJECT(rawptr);
848         if (SP_IS_STRING(char_item))
849             result += *text_iter;
850         else
851             result += '\n';
852     }
853     return result;
856 void
857 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
859     g_return_if_fail (text != NULL);
860     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
862     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(text)->document();
863     Inkscape::XML::Node *repr;
864     SPObject *object;
865     bool is_textpath = false;
866     if (SP_IS_TEXT_TEXTPATH (text)) {
867         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
868         object = sp_object_first_child(SP_OBJECT (text));
869         is_textpath = true;
870     } else {
871         repr = SP_OBJECT_REPR (text);
872         object = SP_OBJECT (text);
873     }
875     if (!str) str = "";
876     gchar *content = g_strdup (str);
878     repr->setContent("");
879     SPObject *child = object->firstChild();
880     while (child) {
881         SPObject *next = SP_OBJECT_NEXT(child);
882         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
883             repr->removeChild(SP_OBJECT_REPR(child));
884         child = next;
885     }
887     gchar *p = content;
888     while (p) {
889         gchar *e = strchr (p, '\n');
890         if (is_textpath) {
891             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
892         } else {
893             if (e) *e = '\0';
894             Inkscape::XML::Node *rtspan;
895             if (SP_IS_TEXT(text)) { // create a tspan for each line
896                 rtspan = xml_doc->createElement("svg:tspan");
897                 rtspan->setAttribute("sodipodi:role", "line");
898             } else { // create a flowPara for each line
899                 rtspan = xml_doc->createElement("svg:flowPara");
900             }
901             Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
902             rtspan->addChild(rstr, NULL);
903             Inkscape::GC::release(rstr);
904             repr->appendChild(rtspan);
905             Inkscape::GC::release(rtspan);
906         }
907         p = (e) ? e + 1 : NULL;
908     }
909     if (is_textpath) {
910         Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
911         repr->addChild(rstr, NULL);
912         Inkscape::GC::release(rstr);
913     }
915     g_free (content);
918 /* ***************************************************************************************************/
919 //                           K E R N I N G   A N D   S P A C I N G
921 /** Returns the attributes block and the character index within that block
922 which represents the iterator \a position. */
923 static TextTagAttributes*
924 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
926     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
927         return NULL;   // flowtext doesn't support kerning yet
928     SPText *text = SP_TEXT(item);
930     SPObject *source_item = 0;
931     void *rawptr = 0;
932     Glib::ustring::iterator source_text_iter;
933     text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
934     source_item = SP_OBJECT(rawptr);
936     if (!SP_IS_STRING(source_item)) return NULL;
937     Glib::ustring *string = &SP_STRING(source_item)->string;
938     *char_index = sum_sibling_text_lengths_before(source_item);
939     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
940         ++*char_index;
942     return attributes_for_object(SP_OBJECT_PARENT(source_item));
945 void
946 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
948     // divide increment by zoom
949     // divide increment by matrix expansion
950     gdouble factor = 1 / desktop->current_zoom();
951     NR::Matrix t = from_2geom(sp_item_i2doc_affine(item));
952     factor = factor / NR::expansion(t);
953     by = factor * by;
955     unsigned char_index;
956     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
957     if (attributes) attributes->addToDxDy(char_index, by);
958     if (start != end) {
959         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
960         if (attributes) attributes->addToDxDy(char_index, -by);
961     }
963     item->updateRepr();
964     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
967 void
968 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
970     // divide increment by zoom
971     // divide increment by matrix expansion
972     gdouble factor = 1 / desktop->current_zoom();
973     NR::Matrix t = from_2geom(sp_item_i2doc_affine(text));
974     factor = factor / NR::expansion(t);
975     Inkscape::Text::Layout const *layout = te_get_layout(text);
976     if (layout == NULL) return;
977     SPObject *source_item = 0;
978     void *rawptr = 0;
979     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
980     source_item = SP_OBJECT(rawptr);
981     if (source_item == 0) return;
982     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
984     sp_te_adjust_rotation(text, start, end, desktop, degrees);
987 void
988 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
990     unsigned char_index;
991     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
992     if (attributes == NULL) return;
994     if (start != end) {
995         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
996             attributes = text_tag_attributes_at_position(text, it, &char_index);
997             if (attributes) attributes->addToRotate(char_index, degrees);
998         }
999     } else
1000         attributes->addToRotate(char_index, degrees);
1002     text->updateRepr();
1003     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1006 void
1007 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1009     g_return_if_fail (text != NULL);
1010     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1012     Inkscape::Text::Layout const *layout = te_get_layout(text);
1014     gdouble val;
1015     SPObject *source_obj = 0;
1016     void *rawptr = 0;
1017     unsigned nb_let;
1018     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1019     source_obj = SP_OBJECT(rawptr);
1021     if (source_obj == 0) {   // end of text
1022         source_obj = text->lastChild();
1023     }
1024     if (SP_IS_STRING(source_obj)) {
1025         source_obj = source_obj->parent;
1026     }
1028     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1030     // calculate real value
1031     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1032        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1033     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1034         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1035             val = style->font_size.computed * style->letter_spacing.value;
1036         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1037             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1038         } else { // unknown unit - should not happen
1039             val = 0.0;
1040         }
1041     } else { // there's a real value in .computed, or it's zero
1042         val = style->letter_spacing.computed;
1043     }
1045     if (start == end) {
1046         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1047             source_obj = SP_OBJECT_PARENT(source_obj);
1048         nb_let = sp_text_get_length(source_obj);
1049     } else {
1050         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1051     }
1053     // divide increment by zoom and by the number of characters in the line,
1054     // so that the entire line is expanded by by pixels, no matter what its length
1055     gdouble const zoom = desktop->current_zoom();
1056     gdouble const zby = (by
1057                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1058                          / NR::expansion(from_2geom(sp_item_i2doc_affine(SP_ITEM(source_obj)))));
1059     val += zby;
1061     if (start == end) {
1062         // set back value to entire paragraph
1063         style->letter_spacing.normal = FALSE;
1064         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1065             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1066                 style->letter_spacing.value = val / style->font_size.computed;
1067             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1068                 style->letter_spacing.value = val / style->font_size.computed * 2;
1069             }
1070         } else {
1071             style->letter_spacing.computed = val;
1072         }
1074         style->letter_spacing.set = TRUE;
1075     } else {
1076         // apply to selection only
1077         SPCSSAttr *css = sp_repr_css_attr_new();
1078         char string_val[40];
1079         g_snprintf(string_val, sizeof(string_val), "%f", val);
1080         sp_repr_css_set_property(css, "letter-spacing", string_val);
1081         sp_te_apply_style(text, start, end, css);
1082         sp_repr_css_attr_unref(css);
1083     }
1085     text->updateRepr();
1086     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1089 double
1090 sp_te_get_average_linespacing (SPItem *text)
1092     Inkscape::Text::Layout const *layout = te_get_layout(text);
1093     if (!layout)
1094         return 0;
1096     unsigned line_count = layout->lineIndex(layout->end());
1097     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
1098     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1099     return average_line_height;
1102 void
1103 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1105     // TODO: use start and end iterators to delineate the area to be affected
1106     g_return_if_fail (text != NULL);
1107     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1109     Inkscape::Text::Layout const *layout = te_get_layout(text);
1110     SPStyle *style = SP_OBJECT_STYLE (text);
1112     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1113         style->line_height.set = TRUE;
1114         style->line_height.inherit = FALSE;
1115         style->line_height.normal = FALSE;
1116         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1117         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1118     }
1120     unsigned line_count = layout->lineIndex(layout->end());
1121     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
1122     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1123     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1125     // divide increment by zoom and by the number of lines,
1126     // so that the entire object is expanded by by pixels
1127     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1129     // divide increment by matrix expansion
1130     NR::Matrix t = from_2geom(sp_item_i2doc_affine (SP_ITEM(text)));
1131     zby = zby / NR::expansion(t);
1133     switch (style->line_height.unit) {
1134         case SP_CSS_UNIT_NONE:
1135         default:
1136             // multiplier-type units, stored in computed
1137             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
1138             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1139             style->line_height.value = style->line_height.computed;
1140             break;
1141         case SP_CSS_UNIT_EM:
1142         case SP_CSS_UNIT_EX:
1143         case SP_CSS_UNIT_PERCENT:
1144             // multiplier-type units, stored in value
1145             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1146             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1147             break;
1148             // absolute-type units
1149         case SP_CSS_UNIT_PX:
1150             style->line_height.computed += zby;
1151             style->line_height.value = style->line_height.computed;
1152             break;
1153         case SP_CSS_UNIT_PT:
1154             style->line_height.computed += zby * PT_PER_PX;
1155             style->line_height.value = style->line_height.computed;
1156             break;
1157         case SP_CSS_UNIT_PC:
1158             style->line_height.computed += zby * (PT_PER_PX / 12);
1159             style->line_height.value = style->line_height.computed;
1160             break;
1161         case SP_CSS_UNIT_MM:
1162             style->line_height.computed += zby * MM_PER_PX;
1163             style->line_height.value = style->line_height.computed;
1164             break;
1165         case SP_CSS_UNIT_CM:
1166             style->line_height.computed += zby * CM_PER_PX;
1167             style->line_height.value = style->line_height.computed;
1168             break;
1169         case SP_CSS_UNIT_IN:
1170             style->line_height.computed += zby * IN_PER_PX;
1171             style->line_height.value = style->line_height.computed;
1172             break;
1173     }
1174     text->updateRepr();
1175     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1179 /* ***************************************************************************************************/
1180 //                           S T Y L E   A P P L I C A T I O N
1183 /** converts an iterator to a character index, mainly because ustring::substr()
1184 doesn't have a version that takes iterators as parameters. */
1185 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1187     unsigned n = 0;
1188     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1189         n++;
1190     return n;
1193 /** applies the given style string on top of the existing styles for \a item,
1194 as opposed to sp_style_merge_from_style_string which merges its parameter
1195 underneath the existing styles (ie ignoring already set properties). */
1196 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1198     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1199     sp_style_merge_from_style_string(new_style, style_string);
1200     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1201     if (item_style_string && *item_style_string)
1202         sp_style_merge_from_style_string(new_style, item_style_string);
1203     gchar *new_style_string = sp_style_write_string(new_style);
1204     sp_style_unref(new_style);
1205     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1206     g_free(new_style_string);
1209 /** Returns true if the style of \a parent and the style of \a child are
1210 equivalent (and hence the children of both will appear the same). It is a
1211 limitation of the current implementation that \a parent must be a (not
1212 necessarily immediate) ancestor of \a child. */
1213 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1215     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1216     // will not work. Firstly because of an inheritance bug in style.cpp that has
1217     // implications too large for me to feel safe fixing, but mainly because the css spec
1218     // requires that the computed value is inherited, not the specified value.
1219     g_assert(parent->isAncestorOf(child));
1220     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1221     // we have to write parent_style then read it again, because some properties format their values
1222     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1223     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1224     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1225     g_free(parent_style);
1226     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1227     sp_style_unref(parent_spstyle);
1229     Glib::ustring child_style_construction(parent_style);
1230     while (child != parent) {
1231         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1232         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1233         if (style_text && *style_text) {
1234             child_style_construction += ';';
1235             child_style_construction += style_text;
1236         }
1237         child = SP_OBJECT_PARENT(child);
1238     }
1239     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1240     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1241     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1242     sp_style_unref(child_spstyle);
1243     bool equal = !strcmp(child_style, parent_style);
1244     g_free(child_style);
1245     g_free(parent_style);
1246     return equal;
1249 /** returns true if \a first and \a second contain all the same attributes
1250 with the same values as each other. Note that we have to compare both
1251 forwards and backwards to make sure we don't miss any attributes that are
1252 in one but not the other. */
1253 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1255     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1256     for ( ; attrs ; attrs++) {
1257         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1258         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1259             return false;
1260     }
1261     attrs = second->attributeList();
1262     for ( ; attrs ; attrs++) {
1263         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1264         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1265             return false;
1266     }
1267     return true;
1270 /** sets the given css attribute on this object and all its descendants.
1271 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1272 transform stuff. */
1273 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1275     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1277     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1278         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1279             // Unset properties which are accumulating and thus should not be set recursively.
1280             // 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.
1281             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1282             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1283             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1284             apply_css_recursive(child, css_recurse);
1285             sp_repr_css_attr_unref(css_recurse);
1286         } else {
1287             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1288         }
1289     }
1292 /** applies the given style to all the objects at the given level and below
1293 which are between \a start_item and \a end_item, creating spans as necessary.
1294 If \a start_item or \a end_item are NULL then the style is applied to all
1295 objects to the beginning or end respectively. \a span_object_name is the
1296 name of the xml for a text span (ie tspan or flowspan). */
1297 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)
1299     bool passed_start = start_item == NULL ? true : false;
1300     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1301     
1302     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1303         if (start_item == child)
1304             passed_start = true;
1306         if (passed_start) {
1307             if (end_item && child->isAncestorOf(end_item)) {
1308                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1309                 break;
1310             }
1311             // apply style
1313             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1314             // often this means that new spans are created before child and child is modified only
1315             if (SP_IS_STRING(child)) {
1316                 SPString *string_item = SP_STRING(child);
1317                 bool surround_entire_string = true;
1319                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1320                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1321                 SPObject *prev_item = SP_OBJECT_PREV(child);
1322                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1324                 if (child == start_item || child == end_item) {
1325                     surround_entire_string = false;
1326                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1327                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1328                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1329                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1331                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1332                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1333                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1334                         Inkscape::GC::release(text_before);
1335                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1336                         child_span->appendChild(text_in_span);
1337                         Inkscape::GC::release(text_in_span);
1338                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1340                     } else if (child == end_item) {
1341                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1342                         //  (includes case where start_text_iter == begin())
1343                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1344                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1346                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1347                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1348                         child_span->appendChild(text_in_span);
1349                         Inkscape::GC::release(text_in_span);
1350                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1352                     } else if (start_text_iter != string_item->string.begin()) {
1353                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1354                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1356                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1357                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1358                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1359                         Inkscape::GC::release(text_before);
1360                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1361                         child_span->appendChild(text_in_span);
1362                         Inkscape::GC::release(text_in_span);
1363                         child->deleteObject();
1364                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1366                     } else
1367                         surround_entire_string = true;
1368                 }
1369                 if (surround_entire_string) {
1370                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1371                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1372                     Inkscape::GC::anchor(child_repr);
1373                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1374                     child_span->appendChild(child_repr);
1375                     Inkscape::GC::release(child_repr);
1376                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1377                 }
1378                 Inkscape::GC::release(child_span);
1380             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1381                 apply_css_recursive(child, css);
1382             }
1384         } else {  // !passed_start
1385             if (child->isAncestorOf(start_item)) {
1386                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1387                 if (end_item && child->isAncestorOf(end_item))
1388                     break;   // only happens when start_item == end_item (I think)
1389                 passed_start = true;
1390             }
1391         }
1393         if (end_item == child)
1394             break;
1395     }
1398 /* if item is at the beginning of a tree it doesn't matter which element
1399 it points to so for neatness we would like it to point to the highest
1400 possible child of \a common_ancestor. There is no iterator return because
1401 a string can never be an ancestor.
1403 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1404 like * to point to the inner span because we can apply style to that whole
1405 span. */
1406 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1408     if (item == common_ancestor)
1409         return item;
1410     if (SP_IS_STRING(item))
1411         if (text_iter != SP_STRING(item)->string.begin())
1412             return item;
1413     for ( ; ; ) {
1414         SPObject *parent = SP_OBJECT_PARENT(item);
1415         if (parent == common_ancestor)
1416             break;
1417         if (item != parent->firstChild())
1418             break;
1419         item = parent;
1420     }
1421     return item;
1425 /**     empty spans: abc<span></span>def
1426                       -> abcdef                  */
1427 static bool tidy_operator_empty_spans(SPObject **item)
1429     if ((*item)->hasChildren()) return false;
1430     if (is_line_break_object(*item)) return false;
1431     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1432     SPObject *next = SP_OBJECT_NEXT(*item);
1433     (*item)->deleteObject();
1434     *item = next;
1435     return true;
1438 /**    inexplicable spans: abc<span style="">def</span>ghi
1439                             -> "abc""def""ghi"
1440 the repeated strings will be merged by another operator. */
1441 static bool tidy_operator_inexplicable_spans(SPObject **item)
1443     if (SP_IS_STRING(*item)) return false;
1444     if (is_line_break_object(*item)) return false;
1445     TextTagAttributes *attrs = attributes_for_object(*item);
1446     if (attrs && attrs->anyAttributesSet()) return false;
1447     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1448     SPObject *next = *item;
1449     while ((*item)->hasChildren()) {
1450         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1451         Inkscape::GC::anchor(repr);
1452         SP_OBJECT_REPR(*item)->removeChild(repr);
1453         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1454         Inkscape::GC::release(repr);
1455         next = SP_OBJECT_NEXT(next);
1456     }
1457     (*item)->deleteObject();
1458     *item = next;
1459     return true;
1462 /**    repeated spans: <font a>abc</font><font a>def</font>
1463                         -> <font a>abcdef</font>            */
1464 static bool tidy_operator_repeated_spans(SPObject **item)
1466     SPObject *first = *item;
1467     SPObject *second = SP_OBJECT_NEXT(first);
1468     if (second == NULL) return false;
1470     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1471     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1473     if (first_repr->type() != second_repr->type()) return false;
1475     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1476         // also amalgamate consecutive SPStrings into one
1477         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1478         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1479         second_repr->parent()->removeChild(second_repr);
1480         return true;
1481     }
1483     // merge consecutive spans with identical styles into one
1484     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1485     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1486     if (is_line_break_object(second)) return false;
1487     gchar const *first_style = first_repr->attribute("style");
1488     gchar const *second_style = second_repr->attribute("style");
1489     if (!((first_style == NULL && second_style == NULL)
1490           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1491         return false;
1493     // all our tests passed: do the merge
1494     TextTagAttributes *attributes_first = attributes_for_object(first);
1495     TextTagAttributes *attributes_second = attributes_for_object(second);
1496     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1497         TextTagAttributes attributes_first_copy = *attributes_first;
1498         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1499     }
1500     move_child_nodes(second_repr, first_repr);
1501     second_repr->parent()->removeChild(second_repr);
1502     return true;
1503     // *item is still the next object to process
1506 /**    redundant nesting: <font a><font b>abc</font></font>
1507                            -> <font b>abc</font>
1508        excessive nesting: <font a><size 1>abc</size></font>
1509                            -> <font a,size 1>abc</font>      */
1510 static bool tidy_operator_excessive_nesting(SPObject **item)
1512     if (!(*item)->hasChildren()) return false;
1513     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1514     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1515         return false;
1516     if (SP_IS_STRING((*item)->firstChild())) return false;
1517     if (is_line_break_object((*item)->firstChild())) return false;
1518     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1519     if (attrs && attrs->anyAttributesSet()) return false;
1520     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1521     if (child_style && *child_style)
1522         overwrite_style_with_string(*item, child_style);
1523     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1524     (*item)->firstChild()->deleteObject();
1525     return true;
1528 /** helper for tidy_operator_redundant_double_nesting() */
1529 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1531     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1532         return false;
1533     if (SP_IS_STRING(child)) return false;
1534     if (is_line_break_object(child)) return false;
1535     if (is_line_break_object(*item)) return false;
1536     TextTagAttributes *attrs = attributes_for_object(child);
1537     if (attrs && attrs->anyAttributesSet()) return false;
1538     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1540     Inkscape::XML::Node *insert_after_repr;
1541     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1542     else insert_after_repr = SP_OBJECT_REPR(*item);
1543     while (SP_OBJECT_REPR(child)->childCount()) {
1544         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1545         Inkscape::GC::anchor(move_repr);
1546         SP_OBJECT_REPR(child)->removeChild(move_repr);
1547         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1548         Inkscape::GC::release(move_repr);
1549         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1550     }
1551     child->deleteObject();
1552     return true;
1555 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1556                                 -> <font b>abc<font a>def</font>ghi</font>
1557 this function does its work when the parameter is the <font a> tag in the
1558 example. You may note that this only does its work when the doubly-nested
1559 child is the first or last. The other cases are called 'style inversion'
1560 below, and I'm not yet convinced that the result of that operation will be
1561 tidier in all cases. */
1562 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1564     if (!(*item)->hasChildren()) return false;
1565     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1566     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1567         return true;
1568     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1569         return true;
1570     return false;
1573 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1574 then compares the styles for item+child versus just child. If they're equal,
1575 tidying is possible. */
1576 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1578     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1579         return false;
1580     if (SP_IS_STRING(child)) return false;
1581     if (is_line_break_object(child)) return false;
1582     if (is_line_break_object(*item)) return false;
1583     TextTagAttributes *attrs = attributes_for_object(child);
1584     if (attrs && attrs->anyAttributesSet()) return false;
1585     attrs = attributes_for_object(*item);
1586     if (attrs && attrs->anyAttributesSet()) return false;
1588     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1589     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1590     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1591     if (child_style && *child_style) {
1592         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1593         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1594     }
1595     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1596     if (item_style && *item_style) {
1597         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1598     }
1599     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1600     sp_repr_css_attr_unref(css_child_and_item);
1601     sp_repr_css_attr_unref(css_child_only);
1602     if (!equal) return false;
1604     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1605     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1606     if (prepend) {
1607         SPObject *prev = SP_OBJECT_PREV(*item);
1608         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1609     } else
1610         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1611     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1612     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1613     Inkscape::GC::release(new_span);
1614     child->deleteObject();
1615     return true;
1618 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1619                                 -> <font b>abc</font><font>def</font>
1620 test this by applying a colour to a region, then a different colour to
1621 a partially-overlapping region. */
1622 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1624     if (!(*item)->hasChildren()) return false;
1625     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1626     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1627         return true;
1628     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1629         return true;
1630     return false;
1633 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1634 in a paragraph which is not \a not_obj. */
1635 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1637     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1638     {
1639         if (child == not_obj) continue;
1640         if (child->hasChildren()) {
1641             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1642             if (ret) return ret;
1643         } else if (SP_IS_STRING(child))
1644             return SP_STRING(child);
1645     }
1646     return NULL;
1649 /** whitespace-only spans: abc<font> </font>def
1650                             -> abc<font></font> def
1651                            abc<b><i>def</i> </b>ghi
1652                             -> abc<b><i>def</i></b> ghi   */
1653 static bool tidy_operator_styled_whitespace(SPObject **item)
1655     if (!SP_IS_STRING(*item)) return false;
1656     Glib::ustring const &str = SP_STRING(*item)->string;
1657     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1658         if (!g_unichar_isspace(*it)) return false;
1660     SPObject *test_item = *item;
1661     SPString *next_string;
1662     for ( ; ; ) {  // find the next string
1663         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1664         if (next_string) {
1665             next_string->string.insert(0, str);
1666             break;
1667         }
1668         for ( ; ; ) {   // go up one item in the xml
1669             test_item = SP_OBJECT_PARENT(test_item);
1670             if (is_line_break_object(test_item)) break;
1671             SPObject *next = SP_OBJECT_NEXT(test_item);
1672             if (next) {
1673                 test_item = next;
1674                 break;
1675             }
1676         }
1677         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1678             next_string = find_last_string_child_not_equal_to(test_item, *item);
1679             if (next_string == NULL) return false;   // an empty paragraph
1680             next_string->string += str;
1681             break;
1682         }
1683     }
1684     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1685     SPObject *delete_obj = *item;
1686     *item = SP_OBJECT_NEXT(*item);
1687     delete_obj->deleteObject();
1688     return true;
1691 /* possible tidy operators that are not yet implemented, either because
1692 they are difficult, occur infrequently, or because I'm not sure that the
1693 output is tidier in all cases:
1694     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1695                                               -> <div italic><para>abc</para></div>
1696     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1697                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1698     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1699                           -> <size 1><font a>abc</font>def</size>
1700 */
1702 /** Recursively walks the xml tree calling a set of cleanup operations on
1703 every child. Returns true if any changes were made to the tree.
1705 All the tidy operators return true if they made changes, and alter their
1706 parameter to point to the next object that should be processed, or NULL.
1707 They must not significantly alter (ie delete) any ancestor elements of the
1708 one they are passed.
1710 It may be that some of the later tidy operators that I wrote are actually
1711 general cases of the earlier operators, and hence the special-case-only
1712 versions can be removed. I haven't analysed my work in detail to figure
1713 out if this is so. */
1714 static bool tidy_xml_tree_recursively(SPObject *root)
1716     static bool (* const tidy_operators[])(SPObject**) = {
1717         tidy_operator_empty_spans,
1718         tidy_operator_inexplicable_spans,
1719         tidy_operator_repeated_spans,
1720         tidy_operator_excessive_nesting,
1721         tidy_operator_redundant_double_nesting,
1722         tidy_operator_redundant_semi_nesting,
1723         tidy_operator_styled_whitespace
1724     };
1725     bool changes = false;
1727     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1728         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1729             child = SP_OBJECT_NEXT(child);
1730             continue;
1731         }
1732         if (child->hasChildren())
1733             changes |= tidy_xml_tree_recursively(child);
1735         unsigned i;
1736         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1737             if (tidy_operators[i](&child)) {
1738                 changes = true;
1739                 break;
1740             }
1741         }
1742         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1743             child = SP_OBJECT_NEXT(child);
1744     }
1745     return changes;
1748 /** Applies the given CSS fragment to the characters of the given text or
1749 flowtext object between \a start and \a end, creating or removing span
1750 elements as necessary and optimal. */
1751 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1753     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1754     if (start == end) return;
1755     Inkscape::Text::Layout::iterator first, last;
1756     if (start < end) {
1757         first = start;
1758         last = end;
1759     } else {
1760         first = end;
1761         last = start;
1762     }
1763     Inkscape::Text::Layout const *layout = te_get_layout(text);
1764     SPObject *start_item = 0, *end_item = 0;
1765     void *rawptr = 0;
1766     Glib::ustring::iterator start_text_iter, end_text_iter;
1767     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1768     start_item = SP_OBJECT(rawptr);
1769     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1770     end_item = SP_OBJECT(rawptr);
1771     if (start_item == 0)
1772         return;   // start is at end of text
1773     if (is_line_break_object(start_item))
1774         start_item = SP_OBJECT_NEXT(start_item);
1775     if (is_line_break_object(end_item))
1776         end_item = SP_OBJECT_NEXT(end_item);
1777     if (end_item == 0) end_item = text;
1778     
1779     
1780     /* Special case: With a tref, we only want to change its style when the whole
1781      * string is selected, in which case the style can be applied directly to the
1782      * tref node.  If only part of the tref's string child is selected, just return. */
1783      
1784     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1785         
1786         return;
1787     } 
1789     /* stage 1: applying the style. Go up to the closest common ancestor of
1790     start and end and then semi-recursively apply the style to all the
1791     objects in between. The semi-recursion is because it's only necessary
1792     at the beginning and end; the style can just be applied to the root
1793     child in the middle.
1794     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1795     The recursion may involve creating new spans.
1796     */
1797     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1798     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1799     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1800     recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1802     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1803     /* discussion: this stage requires a certain level of inventiveness because
1804     it's not clear what the best representation is in many cases. An ideal
1805     implementation would provide some sort of scoring function to rate the
1806     ugliness of a given xml tree and try to reduce said function, but providing
1807     the various possibilities to be rated is non-trivial. Instead, I have opted
1808     for a multi-pass technique which simply recognises known-ugly patterns and
1809     has matching routines for optimising the patterns it finds. It's reasonably
1810     easy to add new pattern matching processors. If everything gets disastrous
1811     and neither option can be made to work, a fallback could be to reduce
1812     everything to a single level of nesting and drop all pretence of
1813     roundtrippability. */
1814     while (tidy_xml_tree_recursively(common_ancestor));
1816     // if we only modified subobjects this won't have been automatically sent
1817     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1820 /*
1821   Local Variables:
1822   mode:c++
1823   c-file-style:"stroustrup"
1824   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1825   indent-tabs-mode:nil
1826   fill-column:99
1827   End:
1828 */
1829 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :