Code

gcc warning cleanup:
[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, Geom::Point const &i_p)
82 {
83     Geom::Matrix im (sp_item_i2d_affine (item));
84     im = im.inverse();
86     Geom::Point p = i_p * im;
87     Inkscape::Text::Layout const *layout = te_get_layout(item);
88     return layout->getNearestCursorPositionTo(p);
89 }
91 std::vector<Geom::Point> sp_te_create_selection_quads(SPItem const *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, Geom::Matrix const &transform)
92 {
93     if (start == end)
94         return std::vector<Geom::Point>();
95     Inkscape::Text::Layout const *layout = te_get_layout(item);
96     if (layout == NULL)
97         return std::vector<Geom::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, Geom::Point &p0, Geom::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 = Geom::Point(p0[Geom::X] + height * sin(rotation), p0[Geom::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, Geom::Point by)
948     // divide increment by zoom
949     // divide increment by matrix expansion
950     gdouble factor = 1 / desktop->current_zoom();
951     Geom::Matrix t (sp_item_i2doc_affine(item));
952     factor = factor / t.descrim();
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     Geom::Matrix t (sp_item_i2doc_affine(text));
974     factor = factor / t.descrim();
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                          / to_2geom(sp_item_i2doc_affine(SP_ITEM(source_obj))).descrim());
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())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::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())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::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     Geom::Matrix t (sp_item_i2doc_affine (SP_ITEM(text)));
1131     zby = zby / t.descrim();
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 (*item && sp_repr_is_meta_element((*item)->repr)) return false;
1444     if (SP_IS_STRING(*item)) return false;
1445     if (is_line_break_object(*item)) return false;
1446     TextTagAttributes *attrs = attributes_for_object(*item);
1447     if (attrs && attrs->anyAttributesSet()) return false;
1448     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1449     SPObject *next = *item;
1450     while ((*item)->hasChildren()) {
1451         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1452         Inkscape::GC::anchor(repr);
1453         SP_OBJECT_REPR(*item)->removeChild(repr);
1454         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1455         Inkscape::GC::release(repr);
1456         next = SP_OBJECT_NEXT(next);
1457     }
1458     (*item)->deleteObject();
1459     *item = next;
1460     return true;
1463 /**    repeated spans: <font a>abc</font><font a>def</font>
1464                         -> <font a>abcdef</font>            */
1465 static bool tidy_operator_repeated_spans(SPObject **item)
1467     SPObject *first = *item;
1468     SPObject *second = SP_OBJECT_NEXT(first);
1469     if (second == NULL) return false;
1471     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1472     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1474     if (first_repr->type() != second_repr->type()) return false;
1476     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1477         // also amalgamate consecutive SPStrings into one
1478         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1479         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1480         second_repr->parent()->removeChild(second_repr);
1481         return true;
1482     }
1484     // merge consecutive spans with identical styles into one
1485     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1486     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1487     if (is_line_break_object(second)) return false;
1488     gchar const *first_style = first_repr->attribute("style");
1489     gchar const *second_style = second_repr->attribute("style");
1490     if (!((first_style == NULL && second_style == NULL)
1491           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1492         return false;
1494     // all our tests passed: do the merge
1495     TextTagAttributes *attributes_first = attributes_for_object(first);
1496     TextTagAttributes *attributes_second = attributes_for_object(second);
1497     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1498         TextTagAttributes attributes_first_copy = *attributes_first;
1499         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1500     }
1501     move_child_nodes(second_repr, first_repr);
1502     second_repr->parent()->removeChild(second_repr);
1503     return true;
1504     // *item is still the next object to process
1507 /**    redundant nesting: <font a><font b>abc</font></font>
1508                            -> <font b>abc</font>
1509        excessive nesting: <font a><size 1>abc</size></font>
1510                            -> <font a,size 1>abc</font>      */
1511 static bool tidy_operator_excessive_nesting(SPObject **item)
1513     if (!(*item)->hasChildren()) return false;
1514     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1515     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1516         return false;
1517     if (SP_IS_STRING((*item)->firstChild())) return false;
1518     if (is_line_break_object((*item)->firstChild())) return false;
1519     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1520     if (attrs && attrs->anyAttributesSet()) return false;
1521     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1522     if (child_style && *child_style)
1523         overwrite_style_with_string(*item, child_style);
1524     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1525     (*item)->firstChild()->deleteObject();
1526     return true;
1529 /** helper for tidy_operator_redundant_double_nesting() */
1530 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1532     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1533         return false;
1534     if (SP_IS_STRING(child)) return false;
1535     if (is_line_break_object(child)) return false;
1536     if (is_line_break_object(*item)) return false;
1537     TextTagAttributes *attrs = attributes_for_object(child);
1538     if (attrs && attrs->anyAttributesSet()) return false;
1539     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1541     Inkscape::XML::Node *insert_after_repr;
1542     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1543     else insert_after_repr = SP_OBJECT_REPR(*item);
1544     while (SP_OBJECT_REPR(child)->childCount()) {
1545         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1546         Inkscape::GC::anchor(move_repr);
1547         SP_OBJECT_REPR(child)->removeChild(move_repr);
1548         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1549         Inkscape::GC::release(move_repr);
1550         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1551     }
1552     child->deleteObject();
1553     return true;
1556 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1557                                 -> <font b>abc<font a>def</font>ghi</font>
1558 this function does its work when the parameter is the <font a> tag in the
1559 example. You may note that this only does its work when the doubly-nested
1560 child is the first or last. The other cases are called 'style inversion'
1561 below, and I'm not yet convinced that the result of that operation will be
1562 tidier in all cases. */
1563 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1565     if (!(*item)->hasChildren()) return false;
1566     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1567     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1568         return true;
1569     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1570         return true;
1571     return false;
1574 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1575 then compares the styles for item+child versus just child. If they're equal,
1576 tidying is possible. */
1577 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1579     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1580         return false;
1581     if (SP_IS_STRING(child)) return false;
1582     if (is_line_break_object(child)) return false;
1583     if (is_line_break_object(*item)) return false;
1584     TextTagAttributes *attrs = attributes_for_object(child);
1585     if (attrs && attrs->anyAttributesSet()) return false;
1586     attrs = attributes_for_object(*item);
1587     if (attrs && attrs->anyAttributesSet()) return false;
1589     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1590     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1591     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1592     if (child_style && *child_style) {
1593         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1594         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1595     }
1596     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1597     if (item_style && *item_style) {
1598         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1599     }
1600     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1601     sp_repr_css_attr_unref(css_child_and_item);
1602     sp_repr_css_attr_unref(css_child_only);
1603     if (!equal) return false;
1605     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1606     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1607     if (prepend) {
1608         SPObject *prev = SP_OBJECT_PREV(*item);
1609         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1610     } else
1611         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1612     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1613     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1614     Inkscape::GC::release(new_span);
1615     child->deleteObject();
1616     return true;
1619 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1620                                 -> <font b>abc</font><font>def</font>
1621 test this by applying a colour to a region, then a different colour to
1622 a partially-overlapping region. */
1623 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1625     if (!(*item)->hasChildren()) return false;
1626     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1627     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1628         return true;
1629     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1630         return true;
1631     return false;
1634 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1635 in a paragraph which is not \a not_obj. */
1636 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1638     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1639     {
1640         if (child == not_obj) continue;
1641         if (child->hasChildren()) {
1642             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1643             if (ret) return ret;
1644         } else if (SP_IS_STRING(child))
1645             return SP_STRING(child);
1646     }
1647     return NULL;
1650 /** whitespace-only spans: abc<font> </font>def
1651                             -> abc<font></font> def
1652                            abc<b><i>def</i> </b>ghi
1653                             -> abc<b><i>def</i></b> ghi   */
1654 static bool tidy_operator_styled_whitespace(SPObject **item)
1656     if (!SP_IS_STRING(*item)) return false;
1657     Glib::ustring const &str = SP_STRING(*item)->string;
1658     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1659         if (!g_unichar_isspace(*it)) return false;
1661     SPObject *test_item = *item;
1662     SPString *next_string;
1663     for ( ; ; ) {  // find the next string
1664         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1665         if (next_string) {
1666             next_string->string.insert(0, str);
1667             break;
1668         }
1669         for ( ; ; ) {   // go up one item in the xml
1670             test_item = SP_OBJECT_PARENT(test_item);
1671             if (is_line_break_object(test_item)) break;
1672             if (SP_IS_FLOWTEXT(test_item)) return false;
1673             SPObject *next = SP_OBJECT_NEXT(test_item);
1674             if (next) {
1675                 test_item = next;
1676                 break;
1677             }
1678         }
1679         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1680             next_string = find_last_string_child_not_equal_to(test_item, *item);
1681             if (next_string == NULL) return false;   // an empty paragraph
1682             next_string->string += str;
1683             break;
1684         }
1685     }
1686     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1687     SPObject *delete_obj = *item;
1688     *item = SP_OBJECT_NEXT(*item);
1689     delete_obj->deleteObject();
1690     return true;
1693 /* possible tidy operators that are not yet implemented, either because
1694 they are difficult, occur infrequently, or because I'm not sure that the
1695 output is tidier in all cases:
1696     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1697                                               -> <div italic><para>abc</para></div>
1698     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1699                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1700     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1701                           -> <size 1><font a>abc</font>def</size>
1702 */
1704 /** Recursively walks the xml tree calling a set of cleanup operations on
1705 every child. Returns true if any changes were made to the tree.
1707 All the tidy operators return true if they made changes, and alter their
1708 parameter to point to the next object that should be processed, or NULL.
1709 They must not significantly alter (ie delete) any ancestor elements of the
1710 one they are passed.
1712 It may be that some of the later tidy operators that I wrote are actually
1713 general cases of the earlier operators, and hence the special-case-only
1714 versions can be removed. I haven't analysed my work in detail to figure
1715 out if this is so. */
1716 static bool tidy_xml_tree_recursively(SPObject *root)
1718     static bool (* const tidy_operators[])(SPObject**) = {
1719         tidy_operator_empty_spans,
1720         tidy_operator_inexplicable_spans,
1721         tidy_operator_repeated_spans,
1722         tidy_operator_excessive_nesting,
1723         tidy_operator_redundant_double_nesting,
1724         tidy_operator_redundant_semi_nesting,
1725         tidy_operator_styled_whitespace
1726     };
1727     bool changes = false;
1729     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1730         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1731             child = SP_OBJECT_NEXT(child);
1732             continue;
1733         }
1734         if (child->hasChildren())
1735             changes |= tidy_xml_tree_recursively(child);
1737         unsigned i;
1738         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1739             if (tidy_operators[i](&child)) {
1740                 changes = true;
1741                 break;
1742             }
1743         }
1744         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1745             child = SP_OBJECT_NEXT(child);
1746     }
1747     return changes;
1750 /** Applies the given CSS fragment to the characters of the given text or
1751 flowtext object between \a start and \a end, creating or removing span
1752 elements as necessary and optimal. */
1753 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1755     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1756     if (start == end) return;
1757     Inkscape::Text::Layout::iterator first, last;
1758     if (start < end) {
1759         first = start;
1760         last = end;
1761     } else {
1762         first = end;
1763         last = start;
1764     }
1765     Inkscape::Text::Layout const *layout = te_get_layout(text);
1766     SPObject *start_item = 0, *end_item = 0;
1767     void *rawptr = 0;
1768     Glib::ustring::iterator start_text_iter, end_text_iter;
1769     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1770     start_item = SP_OBJECT(rawptr);
1771     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1772     end_item = SP_OBJECT(rawptr);
1773     if (start_item == 0)
1774         return;   // start is at end of text
1775     if (is_line_break_object(start_item))
1776         start_item = SP_OBJECT_NEXT(start_item);
1777     if (is_line_break_object(end_item))
1778         end_item = SP_OBJECT_NEXT(end_item);
1779     if (end_item == 0) end_item = text;
1780     
1781     
1782     /* Special case: With a tref, we only want to change its style when the whole
1783      * string is selected, in which case the style can be applied directly to the
1784      * tref node.  If only part of the tref's string child is selected, just return. */
1785      
1786     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1787         
1788         return;
1789     } 
1791     /* stage 1: applying the style. Go up to the closest common ancestor of
1792     start and end and then semi-recursively apply the style to all the
1793     objects in between. The semi-recursion is because it's only necessary
1794     at the beginning and end; the style can just be applied to the root
1795     child in the middle.
1796     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1797     The recursion may involve creating new spans.
1798     */
1799     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1801     // bug #168370 (consider parent transform and viewBox)
1802     // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
1803     SPCSSAttr *css_set = sp_repr_css_attr_new();
1804     sp_repr_css_merge(css_set, (SPCSSAttr*) css);
1805     {
1806         Geom::Matrix const local(sp_item_i2doc_affine(SP_ITEM(common_ancestor)));
1807         double const ex(local.descrim());
1808         if ( ( ex != 0. )
1809              && ( ex != 1. ) ) {
1810             sp_css_attr_scale(css_set, 1/ex);
1811         }
1812     }
1814     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1815     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1816     recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1817     sp_repr_css_attr_unref(css_set);
1819     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1820     /* discussion: this stage requires a certain level of inventiveness because
1821     it's not clear what the best representation is in many cases. An ideal
1822     implementation would provide some sort of scoring function to rate the
1823     ugliness of a given xml tree and try to reduce said function, but providing
1824     the various possibilities to be rated is non-trivial. Instead, I have opted
1825     for a multi-pass technique which simply recognises known-ugly patterns and
1826     has matching routines for optimising the patterns it finds. It's reasonably
1827     easy to add new pattern matching processors. If everything gets disastrous
1828     and neither option can be made to work, a fallback could be to reduce
1829     everything to a single level of nesting and drop all pretence of
1830     roundtrippability. */
1831     while (tidy_xml_tree_recursively(common_ancestor)){};
1833     // if we only modified subobjects this won't have been automatically sent
1834     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1837 /*
1838   Local Variables:
1839   mode:c++
1840   c-file-style:"stroustrup"
1841   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1842   indent-tabs-mode:nil
1843   fill-column:99
1844   End:
1845 */
1846 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :