Code

Connector tool: make connectors avoid the convex hull of shapes.
[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     SPObject const *pos_obj = sp_te_object_at_position(text, position);
114     if (pos_obj)
115         return SP_OBJECT_STYLE(pos_obj);
116     return NULL;
119 SPObject const * sp_te_object_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
121     Inkscape::Text::Layout const *layout = te_get_layout(text);
122     if (layout == NULL)
123         return NULL;
124     SPObject const *pos_obj = 0;
125     void *rawptr = 0;
126     layout->getSourceOfCharacter(position, &rawptr);
127     pos_obj = SP_OBJECT(rawptr);
128     if (pos_obj == 0) pos_obj = text;
129     while (SP_OBJECT_STYLE(pos_obj) == NULL)
130         pos_obj = SP_OBJECT_PARENT(pos_obj);   // not interested in SPStrings 
131     return pos_obj;
134 /*
135  * for debugging input
136  *
137 char * dump_hexy(const gchar * utf8)
139     static char buffer[1024];
141     buffer[0]='\0';
142     for (const char *ptr=utf8; *ptr; ptr++) {
143         sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
144     }
145     return buffer;
147 */
149 Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
151     iterator_pair pair;
152     sp_te_delete(item, start, end, pair);
153     return sp_te_insert(item, pair.first, utf8);
157 /* ***************************************************************************************************/
158 //                             I N S E R T I N G   T E X T
160 static bool is_line_break_object(SPObject const *object)
162     bool is_line_break = false;
163     
164     if (object) {
165         if (SP_IS_TEXT(object)
166                 || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
167                 || SP_IS_TEXTPATH(object)
168                 || SP_IS_FLOWDIV(object)
169                 || SP_IS_FLOWPARA(object)
170                 || SP_IS_FLOWLINE(object)
171                 || SP_IS_FLOWREGIONBREAK(object)) {
172                     
173             is_line_break = true;
174         }
175     }
176     
177     return is_line_break;
180 /** returns the attributes for an object, or NULL if it isn't a text,
181 tspan, tref, or textpath. */
182 static TextTagAttributes* attributes_for_object(SPObject *object)
184     if (SP_IS_TSPAN(object))
185         return &SP_TSPAN(object)->attributes;
186     if (SP_IS_TEXT(object))
187         return &SP_TEXT(object)->attributes;
188     if (SP_IS_TREF(object))
189         return &SP_TREF(object)->attributes;
190     if (SP_IS_TEXTPATH(object))
191         return &SP_TEXTPATH(object)->attributes;
192     return NULL;
195 static const char * span_name_for_text_object(SPObject const *object)
197     if (SP_IS_TEXT(object)) return "svg:tspan";
198     else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
199     return NULL;
202 /** Recursively gets the length of all the SPStrings at or below the given
203 \a item. Also adds 1 for each line break encountered. */
204 unsigned sp_text_get_length(SPObject const *item)
206     unsigned length = 0;
208     if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
209     
210     if (is_line_break_object(item)) length++;
211     
212     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
213         if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
214         else length += sp_text_get_length(child);
215     }
216     return length;
219 /** Recursively gets the length of all the SPStrings at or below the given
220 \a item, before and not including \a upto. Also adds 1 for each line break encountered. */
221 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
223     unsigned length = 0;
225     // The string is the lowest level and the length can be counted directly. 
226     if (SP_IS_STRING(item)) {
227         return SP_STRING(item)->string.length();
228     }
229     
230     // Take care of new lines...
231     if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
232         if (item != SP_OBJECT_PARENT(item)->firstChild()) {
233             // add 1 for each newline
234             length++;
235         }
236     }
237     
238     // Count the length of the children
239     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
240         if (upto && child == upto) {
241             // hit upto, return immediately
242             return length;
243         }
244         if (SP_IS_STRING(child)) {
245             length += SP_STRING(child)->string.length();
246         }
247         else {
248             if (upto && child->isAncestorOf(upto)) {
249                 // upto is below us, recurse and break loop
250                 length += sp_text_get_length_upto(child, upto);
251                 return length;
252             } else {
253                 // recurse and go to the next sibling
254                 length += sp_text_get_length_upto(child, upto);
255             }
256         }
257     }
258     return length;
261 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
263     switch (old_node->type()) {
264         case Inkscape::XML::ELEMENT_NODE: {
265             Inkscape::XML::Node *new_node = xml_doc->createElement(old_node->name());
266             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
267             GQuark const id_key = g_quark_from_string("id");
268             for ( ; attributes ; attributes++) {
269                 if (attributes->key == id_key) continue;
270                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
271             }
272             return new_node;
273         }
275         case Inkscape::XML::TEXT_NODE:
276             return xml_doc->createTextNode(old_node->content());
278         case Inkscape::XML::COMMENT_NODE:
279             return xml_doc->createComment(old_node->content());
281         case Inkscape::XML::PI_NODE:
282             return xml_doc->createPI(old_node->name(), old_node->content());
284         case Inkscape::XML::DOCUMENT_NODE:
285             return NULL;   // this had better never happen
286     }
287     return NULL;
290 /** returns the sum of the (recursive) lengths of all the SPStrings prior
291 to \a item at the same level. */
292 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
294     unsigned char_index = 0;
295     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
296         char_index += sp_text_get_length(sibling);
297     return char_index;
300 /** splits the attributes for the first object at the given \a char_index
301 and moves the ones after that point into \a second_item. */
302 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
304     TextTagAttributes *first_attrs = attributes_for_object(first_item);
305     TextTagAttributes *second_attrs = attributes_for_object(second_item);
306     if (first_attrs && second_attrs)
307         first_attrs->split(char_index, second_attrs);
310 /** recursively divides the XML node tree into two objects: the original will
311 contain all objects up to and including \a split_obj and the returned value
312 will be the new leaf which represents the copy of \a split_obj and extends
313 down the tree with new elements all the way to the common root which is the
314 parent of the first line break node encountered.
315 */
316 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
318     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
319     if (is_line_break_object(split_obj)) {
320         Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
321         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
322         Inkscape::GC::release(new_node);
323         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
324         return SP_OBJECT_NEXT(split_obj);
325     }
327     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
328     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
329     // copy the split node
330     Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
331     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
332     Inkscape::GC::release(new_node);
334     // sort out the copied attributes (x/y/dx/dy/rotate)
335     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
337     // then move all the subsequent nodes
338     split_obj = SP_OBJECT_NEXT(split_obj);
339     while (split_obj) {
340         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
341         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
342         Inkscape::GC::anchor(move_repr);
343         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
344         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
345         Inkscape::GC::release(move_repr);
347         split_obj = next_obj;
348     }
349     return duplicate_obj->firstChild();
352 /** inserts a new line break at the given position in a text or flowtext
353 object. If the position is in the middle of a span, the XML tree must be
354 chopped in two such that the line can be created at the root of the text
355 element. Returns an iterator pointing just after the inserted break. */
356 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
358     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
359     // texpaths attached to the same path, with a vertical shift
360     if (SP_IS_TEXT_TEXTPATH (item) || SP_IS_TREF(item))
361         return position;
362         
363     SPDesktop *desktop = SP_ACTIVE_DESKTOP; 
365     Inkscape::Text::Layout const *layout = te_get_layout(item);
366     SPObject *split_obj = 0;
367     Glib::ustring::iterator split_text_iter;
368     if (position != layout->end()) {
369         void *rawptr = 0;
370         layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
371         split_obj = SP_OBJECT(rawptr);
372     }
374     if (split_obj == 0 || is_line_break_object(split_obj)) {
375         if (split_obj == 0) split_obj = item->lastChild();
376         
377         if (SP_IS_TREF(split_obj)) {
378                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
379             return position;
380         }
381         
382         if (split_obj) {
383             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
384             Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
385             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
386             Inkscape::GC::release(new_node);
387         }
388     } else if (SP_IS_STRING(split_obj)) {
389         // If the parent is a tref, editing on this particular string is disallowed.
390         if (SP_IS_TREF(SP_OBJECT_PARENT(split_obj))) {
391             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
392             return position;
393         }
394         
395         Glib::ustring *string = &SP_STRING(split_obj)->string;
396         unsigned char_index = 0;
397         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
398             char_index++;
399         // we need to split the entire text tree into two
400         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
401         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
402         string->erase(split_text_iter, string->end());
403         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
404         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
405     } else {
406         // TODO
407         // I think the only case to put here is arbitrary gaps, which nobody uses yet
408     }
409     item->updateRepr();
410     unsigned char_index = layout->iteratorToCharIndex(position);
411     te_update_layout_now(item);
412     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
413     return layout->charIndexToIterator(char_index + 1);
416 /** finds the first SPString after the given position, including children, excluding parents */
417 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
419     while (start_obj) {
420         if (start_obj->hasChildren()) {
421             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
422             if (found_string) return found_string;
423         }
424         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
425         start_obj = SP_OBJECT_NEXT(start_obj);
426         if (is_line_break_object(start_obj))
427             break;   // don't cross line breaks
428     }
429     return NULL;
432 /** inserts the given characters into the given string and inserts
433 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
434 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
436     unsigned char_index = 0;
437     unsigned char_count = g_utf8_strlen(utf8, -1);
438     Glib::ustring *string = &SP_STRING(string_item)->string;
440     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
441         char_index++;
442     string->replace(iter_at, iter_at, utf8);
444     SPObject *parent_item = string_item;
445     for ( ; ; ) {
446         char_index += sum_sibling_text_lengths_before(parent_item);
447         parent_item = SP_OBJECT_PARENT(parent_item);
448         TextTagAttributes *attributes = attributes_for_object(parent_item);
449         if (!attributes) break;
450         attributes->insert(char_index, char_count);
451     }
454 /** Inserts the given text into a text or flowroot object. Line breaks
455 cannot be inserted using this function, see sp_te_insert_line(). Returns
456 an iterator pointing just after the inserted text. */
457 Inkscape::Text::Layout::iterator
458 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
460     if (!g_utf8_validate(utf8,-1,NULL)) {
461         g_warning("Trying to insert invalid utf8");
462         return position;
463     }
464     
465     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
467     Inkscape::Text::Layout const *layout = te_get_layout(item);
468     SPObject *source_obj = 0;
469     void *rawptr = 0;
470     Glib::ustring::iterator iter_text;
471     // we want to insert after the previous char, not before the current char.
472     // it makes a difference at span boundaries
473     Inkscape::Text::Layout::iterator it_prev_char = position;
474     bool cursor_at_start = !it_prev_char.prevCharacter();
475     bool cursor_at_end = position == layout->end();
476     layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
477     source_obj = SP_OBJECT(rawptr);
478     if (SP_IS_STRING(source_obj)) {
479         // If the parent is a tref, editing on this particular string is disallowed.
480         if (SP_IS_TREF(SP_OBJECT_PARENT(source_obj))) {
481             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
482             return position;
483         }
484         
485         // Now the simple case can begin...
486         if (!cursor_at_start) iter_text++;
487         SPString *string_item = SP_STRING(source_obj);
488         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
489     } else {
490         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
491         Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
492         if (cursor_at_start) {
493             source_obj = item;
494             if (source_obj->hasChildren()) {
495                 source_obj = source_obj->firstChild();
496                 if (SP_IS_FLOWTEXT(item)) {
497                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
498                         source_obj = SP_OBJECT_NEXT(source_obj);
499                     if (source_obj == NULL)
500                         source_obj = item;
501                 }
502             }
503             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
504                 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
505                 SP_OBJECT_REPR(item)->appendChild(para);
506                 source_obj = item->lastChild();
507             }
508         } else
509             source_obj = SP_OBJECT_NEXT(source_obj);
511         if (source_obj) {  // never fails
512             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
513             if (string_item == NULL) {
514                 // need to add an SPString in this (pathological) case
515                 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
516                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
517                 Inkscape::GC::release(rstring);
518                 g_assert(SP_IS_STRING(source_obj->firstChild()));
519                 string_item = SP_STRING(source_obj->firstChild());
520             }
521             // If the parent is a tref, editing on this particular string is disallowed.
522             if (SP_IS_TREF(SP_OBJECT_PARENT(string_item))) {
523                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
524                 return position;
525             }
526             
527             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
528         }
529     }
531     item->updateRepr();
532     unsigned char_index = layout->iteratorToCharIndex(position);
533     te_update_layout_now(item);
534     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
535     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
539 /* ***************************************************************************************************/
540 //                            D E L E T I N G   T E X T
542 /** moves all the children of \a from_repr to \a to_repr, either before
543 the existing children or after them. Order is maintained. The empty
544 \a from_repr is not deleted. */
545 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
547     while (from_repr->childCount()) {
548         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
549         Inkscape::GC::anchor(child);
550         from_repr->removeChild(child);
551         if (prepend) to_repr->addChild(child, NULL);
552         else to_repr->appendChild(child);
553         Inkscape::GC::release(child);
554     }
557 /** returns the object in the tree which is the closest ancestor of both
558 \a one and \a two. It will never return anything higher than \a text. */
559 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
561     if (one == NULL || two == NULL)
562         return text;
563     SPObject *common_ancestor = one;
564     if (SP_IS_STRING(common_ancestor))
565         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
566     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
567         g_assert(common_ancestor != text);
568         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
569     }
570     return common_ancestor;
573 /** positions \a para_obj and \a text_iter to be pointing at the end
574 of the last string in the last leaf object of \a para_obj. If the last
575 leaf is not an SPString then \a text_iter will be unchanged. */
576 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
578     while ((*para_obj)->hasChildren())
579         *para_obj = (*para_obj)->lastChild();
580     if (SP_IS_STRING(*para_obj))
581         *text_iter = SP_STRING(*para_obj)->string.end();
584 /** delete the line break pointed to by \a item by merging its children into
585 the next suitable object and deleting \a item. Returns the object after the
586 ones that have just been moved and sets \a next_is_sibling accordingly. */
587 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
589     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
590     SPObject *next_item = NULL;
591     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
593     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
594       <div></div><p>*text</p>
595       <p><div></div>*text</p>
596       <p><div></div></p><p>*text</p>
597     */
598     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
599     Inkscape::XML::Node *new_span_repr = xml_doc->createElement(span_name_for_text_object(root));
601     if (gchar const *a = this_repr->attribute("dx"))
602         new_span_repr->setAttribute("dx", a);
603     if (gchar const *a = this_repr->attribute("dy"))
604         new_span_repr->setAttribute("dy", a);
605     if (gchar const *a = this_repr->attribute("rotate"))
606         new_span_repr->setAttribute("rotate", a);
608     SPObject *following_item = item;
609     while (SP_OBJECT_NEXT(following_item) == NULL) {
610         following_item = SP_OBJECT_PARENT(following_item);
611         g_assert(following_item != root);
612     }
613     following_item = SP_OBJECT_NEXT(following_item);
615     SPObject *new_parent_item;
616     if (SP_IS_STRING(following_item)) {
617         new_parent_item = SP_OBJECT_PARENT(following_item);
618         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
619         next_item = following_item;
620         *next_is_sibling = true;
621     } else {
622         new_parent_item = following_item;
623         next_item = new_parent_item->firstChild();
624         *next_is_sibling = true;
625         if (next_item == NULL) {
626             next_item = new_parent_item;
627             *next_is_sibling = false;
628         }
629         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
630     }
632     // work around a bug in sp_style_write_difference() which causes the difference
633     // not to be written if the second param has a style set which the first does not
634     // by causing the first param to have everything set
635     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
636     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
637     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
638     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
639     for ( ; attrs ; attrs++) {
640         gchar const *key = g_quark_to_string(attrs->key);
641         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
642         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
643             this_node_attrs->setAttribute(key, this_attr);
644     }
645     sp_repr_css_attr_unref(this_node_attrs_inherited);
646     sp_repr_css_attr_unref(this_node_attrs);
647     sp_repr_css_attr_unref(dest_node_attrs);
648     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
650     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
651     if (attributes)
652         attributes->insert(0, moved_char_count);
653     move_child_nodes(this_repr, new_span_repr);
654     this_repr->parent()->removeChild(this_repr);
655     return next_item;
658 /** erases the given characters from the given string and deletes the
659 corresponding x/y/dx/dy/rotate attributes from all its parents. */
660 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
662     unsigned char_index = 0;
663     unsigned char_count = 0;
664     Glib::ustring *string = &SP_STRING(string_item)->string;
666     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
667         char_index++;
668     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
669         char_count++;
670     string->erase(iter_from, iter_to);
671     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
673     SPObject *parent_item = string_item;
674     for ( ; ; ) {
675         char_index += sum_sibling_text_lengths_before(parent_item);
676         parent_item = SP_OBJECT_PARENT(parent_item);
677         TextTagAttributes *attributes = attributes_for_object(parent_item);
678         if (attributes == NULL) break;
680         attributes->erase(char_index, char_count);
681         attributes->writeTo(SP_OBJECT_REPR(parent_item));
682     }
685 /* Deletes the given characters from a text or flowroot object. This is
686 quite a complicated operation, partly due to the cleanup that is done if all
687 the text in a subobject has been deleted, and partly due to the difficulty
688 of figuring out what is a line break and how to delete one. Returns the
689 real start and ending iterators based on the situation. */
690 bool
691 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start,
692               Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
694     bool success = false;
696     iter_pair.first = start;
697     iter_pair.second = end;
698     
699     if (start == end) return success;
700     
701     if (start > end) {
702         iter_pair.first = end;
703         iter_pair.second = start;
704     }
705     
706     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
707     
708     Inkscape::Text::Layout const *layout = te_get_layout(item);
709     SPObject *start_item = 0, *end_item = 0;
710     void *rawptr = 0;
711     Glib::ustring::iterator start_text_iter, end_text_iter;
712     layout->getSourceOfCharacter(iter_pair.first, &rawptr, &start_text_iter);
713     start_item = SP_OBJECT(rawptr);
714     layout->getSourceOfCharacter(iter_pair.second, &rawptr, &end_text_iter);
715     end_item = SP_OBJECT(rawptr);
716     if (start_item == 0)
717         return success;   // start is at end of text
718     if (is_line_break_object(start_item))
719         move_to_end_of_paragraph(&start_item, &start_text_iter);
720     if (end_item == 0) {
721         end_item = item->lastChild();
722         move_to_end_of_paragraph(&end_item, &end_text_iter);
723     }
724     else if (is_line_break_object(end_item))
725         move_to_end_of_paragraph(&end_item, &end_text_iter);
727     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
729     if (start_item == end_item) {
730         // the quick case where we're deleting stuff all from the same string
731         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
732             // If the parent is a tref, editing on this particular string is disallowed.
733             if (SP_IS_TREF(SP_OBJECT_PARENT(start_item))) {
734                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
735             } else {
736                 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
737                 success = true;
738             }
739         }
740     } else {
741         SPObject *sub_item = start_item;
742         // walk the tree from start_item to end_item, deleting as we go
743         while (sub_item != item) {
744             if (sub_item == end_item) {
745                 if (SP_IS_STRING(sub_item)) {
746                     // If the parent is a tref, editing on this particular string is disallowed.
747                     if (SP_IS_TREF(SP_OBJECT_PARENT(sub_item))) {
748                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
749                         break;
750                     }
751             
752                     Glib::ustring *string = &SP_STRING(sub_item)->string;
753                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
754                     success = true;
755                 }
756                 break;
757             }
758             if (SP_IS_STRING(sub_item)) {
759                 SPString *string = SP_STRING(sub_item);
760                 if (sub_item == start_item)
761                     erase_from_spstring(string, start_text_iter, string->string.end());
762                 else
763                     erase_from_spstring(string, string->string.begin(), string->string.end());
764                 success = true;
765             }
766             // walk to the next item in the tree
767             if (sub_item->hasChildren())
768                 sub_item = sub_item->firstChild();
769             else {
770                 SPObject *next_item;
771                 do {
772                     bool is_sibling = true;
773                     next_item = SP_OBJECT_NEXT(sub_item);
774                     if (next_item == NULL) {
775                         next_item = SP_OBJECT_PARENT(sub_item);
776                         is_sibling = false;
777                     }
779                     if (is_line_break_object(sub_item))
780                         next_item = delete_line_break(item, sub_item, &is_sibling);
782                     sub_item = next_item;
783                     if (is_sibling) break;
784                     // no more siblings, go up a parent
785                 } while (sub_item != item && sub_item != end_item);
786             }
787         }
788     }
790     while (tidy_xml_tree_recursively(common_ancestor)){};
791     te_update_layout_now(item);
792     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
793     layout->validateIterator(&iter_pair.first);
794     layout->validateIterator(&iter_pair.second);
795     return success;
799 /* ***************************************************************************************************/
800 //                            P L A I N   T E X T   F U N C T I O N S
802 /** Gets a text-only representation of the given text or flowroot object,
803 replacing line break elements with '\n'. */
804 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
806     if (*pending_line_break)
807         *string += '\n';
808     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
809         if (SP_IS_STRING(child))
810             *string += SP_STRING(child)->string;
811         else
812             sp_te_get_ustring_multiline(child, string, pending_line_break);
813     }
814     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
815         *pending_line_break = true;
818 /** Gets a text-only representation of the given text or flowroot object,
819 replacing line break elements with '\n'. The return value must be free()d. */
820 gchar *
821 sp_te_get_string_multiline (SPItem const *text)
823     Glib::ustring string;
824     bool pending_line_break = false;
826     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
827     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
828     if (string.empty()) return NULL;
829     return strdup(string.data());
832 /** Gets a text-only representation of the characters in a text or flowroot
833 object from \a start to \a end only. Line break elements are replaced with
834 '\n'. */
835 Glib::ustring
836 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
838     if (start == end) return "";
839     Inkscape::Text::Layout::iterator first, last;
840     if (start < end) {
841         first = start;
842         last = end;
843     } else {
844         first = end;
845         last = start;
846     }
847     Inkscape::Text::Layout const *layout = te_get_layout(text);
848     Glib::ustring result;
849     // not a particularly fast piece of code. I'll optimise it if people start to notice.
850     for ( ; first < last ; first.nextCharacter()) {
851         SPObject *char_item = 0;
852         void *rawptr = 0;
853         Glib::ustring::iterator text_iter;
854         layout->getSourceOfCharacter(first, &rawptr, &text_iter);
855         char_item = SP_OBJECT(rawptr);
856         if (SP_IS_STRING(char_item))
857             result += *text_iter;
858         else
859             result += '\n';
860     }
861     return result;
864 void
865 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
867     g_return_if_fail (text != NULL);
868     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
870     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(text)->document();
871     Inkscape::XML::Node *repr;
872     SPObject *object;
873     bool is_textpath = false;
874     if (SP_IS_TEXT_TEXTPATH (text)) {
875         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
876         object = sp_object_first_child(SP_OBJECT (text));
877         is_textpath = true;
878     } else {
879         repr = SP_OBJECT_REPR (text);
880         object = SP_OBJECT (text);
881     }
883     if (!str) str = "";
884     gchar *content = g_strdup (str);
886     repr->setContent("");
887     SPObject *child = object->firstChild();
888     while (child) {
889         SPObject *next = SP_OBJECT_NEXT(child);
890         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
891             repr->removeChild(SP_OBJECT_REPR(child));
892         child = next;
893     }
895     gchar *p = content;
896     while (p) {
897         gchar *e = strchr (p, '\n');
898         if (is_textpath) {
899             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
900         } else {
901             if (e) *e = '\0';
902             Inkscape::XML::Node *rtspan;
903             if (SP_IS_TEXT(text)) { // create a tspan for each line
904                 rtspan = xml_doc->createElement("svg:tspan");
905                 rtspan->setAttribute("sodipodi:role", "line");
906             } else { // create a flowPara for each line
907                 rtspan = xml_doc->createElement("svg:flowPara");
908             }
909             Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
910             rtspan->addChild(rstr, NULL);
911             Inkscape::GC::release(rstr);
912             repr->appendChild(rtspan);
913             Inkscape::GC::release(rtspan);
914         }
915         p = (e) ? e + 1 : NULL;
916     }
917     if (is_textpath) {
918         Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
919         repr->addChild(rstr, NULL);
920         Inkscape::GC::release(rstr);
921     }
923     g_free (content);
926 /* ***************************************************************************************************/
927 //                           K E R N I N G   A N D   S P A C I N G
929 /** Returns the attributes block and the character index within that block
930 which represents the iterator \a position. */
931 static TextTagAttributes*
932 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
934     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
935         return NULL;   // flowtext doesn't support kerning yet
936     SPText *text = SP_TEXT(item);
938     SPObject *source_item = 0;
939     void *rawptr = 0;
940     Glib::ustring::iterator source_text_iter;
941     text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
942     source_item = SP_OBJECT(rawptr);
944     if (!SP_IS_STRING(source_item)) return NULL;
945     Glib::ustring *string = &SP_STRING(source_item)->string;
946     *char_index = sum_sibling_text_lengths_before(source_item);
947     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
948         ++*char_index;
950     return attributes_for_object(SP_OBJECT_PARENT(source_item));
953 void
954 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, Geom::Point by)
956     // divide increment by zoom
957     // divide increment by matrix expansion
958     gdouble factor = 1 / desktop->current_zoom();
959     Geom::Matrix t (sp_item_i2doc_affine(item));
960     factor = factor / t.descrim();
961     by = factor * by;
963     unsigned char_index;
964     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
965     if (attributes) attributes->addToDxDy(char_index, by);
966     if (start != end) {
967         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
968         if (attributes) attributes->addToDxDy(char_index, -by);
969     }
971     item->updateRepr();
972     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
975 void
976 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
978     // divide increment by zoom
979     // divide increment by matrix expansion
980     gdouble factor = 1 / desktop->current_zoom();
981     Geom::Matrix t (sp_item_i2doc_affine(text));
982     factor = factor / t.descrim();
983     Inkscape::Text::Layout const *layout = te_get_layout(text);
984     if (layout == NULL) return;
985     SPObject *source_item = 0;
986     void *rawptr = 0;
987     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
988     source_item = SP_OBJECT(rawptr);
989     if (source_item == 0) return;
990     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
992     sp_te_adjust_rotation(text, start, end, desktop, degrees);
995 void
996 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
998     unsigned char_index;
999     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1000     if (attributes == NULL) return;
1002     if (start != end) {
1003         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1004             attributes = text_tag_attributes_at_position(text, it, &char_index);
1005             if (attributes) attributes->addToRotate(char_index, degrees);
1006         }
1007     } else
1008         attributes->addToRotate(char_index, degrees);
1010     text->updateRepr();
1011     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1014 void
1015 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1017     g_return_if_fail (text != NULL);
1018     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1020     Inkscape::Text::Layout const *layout = te_get_layout(text);
1022     gdouble val;
1023     SPObject *source_obj = 0;
1024     void *rawptr = 0;
1025     unsigned nb_let;
1026     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1027     source_obj = SP_OBJECT(rawptr);
1029     if (source_obj == 0) {   // end of text
1030         source_obj = text->lastChild();
1031     }
1032     if (SP_IS_STRING(source_obj)) {
1033         source_obj = source_obj->parent;
1034     }
1036     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1038     // calculate real value
1039     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1040        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1041     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1042         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1043             val = style->font_size.computed * style->letter_spacing.value;
1044         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1045             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1046         } else { // unknown unit - should not happen
1047             val = 0.0;
1048         }
1049     } else { // there's a real value in .computed, or it's zero
1050         val = style->letter_spacing.computed;
1051     }
1053     if (start == end) {
1054         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1055             source_obj = SP_OBJECT_PARENT(source_obj);
1056         nb_let = sp_text_get_length(source_obj);
1057     } else {
1058         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1059     }
1061     // divide increment by zoom and by the number of characters in the line,
1062     // so that the entire line is expanded by by pixels, no matter what its length
1063     gdouble const zoom = desktop->current_zoom();
1064     gdouble const zby = (by
1065                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1066                          / to_2geom(sp_item_i2doc_affine(SP_ITEM(source_obj))).descrim());
1067     val += zby;
1069     if (start == end) {
1070         // set back value to entire paragraph
1071         style->letter_spacing.normal = FALSE;
1072         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1073             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1074                 style->letter_spacing.value = val / style->font_size.computed;
1075             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1076                 style->letter_spacing.value = val / style->font_size.computed * 2;
1077             }
1078         } else {
1079             style->letter_spacing.computed = val;
1080         }
1082         style->letter_spacing.set = TRUE;
1083     } else {
1084         // apply to selection only
1085         SPCSSAttr *css = sp_repr_css_attr_new();
1086         char string_val[40];
1087         g_snprintf(string_val, sizeof(string_val), "%f", val);
1088         sp_repr_css_set_property(css, "letter-spacing", string_val);
1089         sp_te_apply_style(text, start, end, css);
1090         sp_repr_css_attr_unref(css);
1091     }
1093     text->updateRepr();
1094     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1097 double
1098 sp_te_get_average_linespacing (SPItem *text)
1100     Inkscape::Text::Layout const *layout = te_get_layout(text);
1101     if (!layout)
1102         return 0;
1104     unsigned line_count = layout->lineIndex(layout->end());
1105     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1106     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1107     return average_line_height;
1110 void
1111 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1113     // TODO: use start and end iterators to delineate the area to be affected
1114     g_return_if_fail (text != NULL);
1115     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1117     Inkscape::Text::Layout const *layout = te_get_layout(text);
1118     SPStyle *style = SP_OBJECT_STYLE (text);
1120     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1121         style->line_height.set = TRUE;
1122         style->line_height.inherit = FALSE;
1123         style->line_height.normal = FALSE;
1124         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1125         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1126     }
1128     unsigned line_count = layout->lineIndex(layout->end());
1129     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1130     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1131     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1133     // divide increment by zoom and by the number of lines,
1134     // so that the entire object is expanded by by pixels
1135     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1137     // divide increment by matrix expansion
1138     Geom::Matrix t (sp_item_i2doc_affine (SP_ITEM(text)));
1139     zby = zby / t.descrim();
1141     switch (style->line_height.unit) {
1142         case SP_CSS_UNIT_NONE:
1143         default:
1144             // multiplier-type units, stored in computed
1145             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
1146             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1147             style->line_height.value = style->line_height.computed;
1148             break;
1149         case SP_CSS_UNIT_EM:
1150         case SP_CSS_UNIT_EX:
1151         case SP_CSS_UNIT_PERCENT:
1152             // multiplier-type units, stored in value
1153             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1154             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1155             break;
1156             // absolute-type units
1157         case SP_CSS_UNIT_PX:
1158             style->line_height.computed += zby;
1159             style->line_height.value = style->line_height.computed;
1160             break;
1161         case SP_CSS_UNIT_PT:
1162             style->line_height.computed += zby * PT_PER_PX;
1163             style->line_height.value = style->line_height.computed;
1164             break;
1165         case SP_CSS_UNIT_PC:
1166             style->line_height.computed += zby * (PT_PER_PX / 12);
1167             style->line_height.value = style->line_height.computed;
1168             break;
1169         case SP_CSS_UNIT_MM:
1170             style->line_height.computed += zby * MM_PER_PX;
1171             style->line_height.value = style->line_height.computed;
1172             break;
1173         case SP_CSS_UNIT_CM:
1174             style->line_height.computed += zby * CM_PER_PX;
1175             style->line_height.value = style->line_height.computed;
1176             break;
1177         case SP_CSS_UNIT_IN:
1178             style->line_height.computed += zby * IN_PER_PX;
1179             style->line_height.value = style->line_height.computed;
1180             break;
1181     }
1182     text->updateRepr();
1183     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1187 /* ***************************************************************************************************/
1188 //                           S T Y L E   A P P L I C A T I O N
1191 /** converts an iterator to a character index, mainly because ustring::substr()
1192 doesn't have a version that takes iterators as parameters. */
1193 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1195     unsigned n = 0;
1196     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1197         n++;
1198     return n;
1201 /** applies the given style string on top of the existing styles for \a item,
1202 as opposed to sp_style_merge_from_style_string which merges its parameter
1203 underneath the existing styles (ie ignoring already set properties). */
1204 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1206     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1207     sp_style_merge_from_style_string(new_style, style_string);
1208     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1209     if (item_style_string && *item_style_string)
1210         sp_style_merge_from_style_string(new_style, item_style_string);
1211     gchar *new_style_string = sp_style_write_string(new_style);
1212     sp_style_unref(new_style);
1213     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1214     g_free(new_style_string);
1217 /** Returns true if the style of \a parent and the style of \a child are
1218 equivalent (and hence the children of both will appear the same). It is a
1219 limitation of the current implementation that \a parent must be a (not
1220 necessarily immediate) ancestor of \a child. */
1221 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1223     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1224     // will not work. Firstly because of an inheritance bug in style.cpp that has
1225     // implications too large for me to feel safe fixing, but mainly because the css spec
1226     // requires that the computed value is inherited, not the specified value.
1227     g_assert(parent->isAncestorOf(child));
1228     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1229     // we have to write parent_style then read it again, because some properties format their values
1230     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1231     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1232     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1233     g_free(parent_style);
1234     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1235     sp_style_unref(parent_spstyle);
1237     Glib::ustring child_style_construction(parent_style);
1238     while (child != parent) {
1239         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1240         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1241         if (style_text && *style_text) {
1242             child_style_construction += ';';
1243             child_style_construction += style_text;
1244         }
1245         child = SP_OBJECT_PARENT(child);
1246     }
1247     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1248     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1249     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1250     sp_style_unref(child_spstyle);
1251     bool equal = !strcmp(child_style, parent_style);
1252     g_free(child_style);
1253     g_free(parent_style);
1254     return equal;
1257 /** returns true if \a first and \a second contain all the same attributes
1258 with the same values as each other. Note that we have to compare both
1259 forwards and backwards to make sure we don't miss any attributes that are
1260 in one but not the other. */
1261 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1263     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1264     for ( ; attrs ; attrs++) {
1265         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1266         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1267             return false;
1268     }
1269     attrs = second->attributeList();
1270     for ( ; attrs ; attrs++) {
1271         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1272         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1273             return false;
1274     }
1275     return true;
1278 /** sets the given css attribute on this object and all its descendants.
1279 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1280 transform stuff. */
1281 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1283     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1285     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1286         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1287             // Unset properties which are accumulating and thus should not be set recursively.
1288             // 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.
1289             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1290             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1291             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1292             apply_css_recursive(child, css_recurse);
1293             sp_repr_css_attr_unref(css_recurse);
1294         } else {
1295             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1296         }
1297     }
1300 /** applies the given style to all the objects at the given level and below
1301 which are between \a start_item and \a end_item, creating spans as necessary.
1302 If \a start_item or \a end_item are NULL then the style is applied to all
1303 objects to the beginning or end respectively. \a span_object_name is the
1304 name of the xml for a text span (ie tspan or flowspan). */
1305 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)
1307     bool passed_start = start_item == NULL ? true : false;
1308     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1309     
1310     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1311         if (start_item == child)
1312             passed_start = true;
1314         if (passed_start) {
1315             if (end_item && child->isAncestorOf(end_item)) {
1316                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1317                 break;
1318             }
1319             // apply style
1321             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1322             // often this means that new spans are created before child and child is modified only
1323             if (SP_IS_STRING(child)) {
1324                 SPString *string_item = SP_STRING(child);
1325                 bool surround_entire_string = true;
1327                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1328                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1329                 SPObject *prev_item = SP_OBJECT_PREV(child);
1330                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1332                 if (child == start_item || child == end_item) {
1333                     surround_entire_string = false;
1334                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1335                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1336                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1337                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1339                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1340                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1341                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1342                         Inkscape::GC::release(text_before);
1343                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1344                         child_span->appendChild(text_in_span);
1345                         Inkscape::GC::release(text_in_span);
1346                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1348                     } else if (child == end_item) {
1349                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1350                         //  (includes case where start_text_iter == begin())
1351                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1352                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1354                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1355                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1356                         child_span->appendChild(text_in_span);
1357                         Inkscape::GC::release(text_in_span);
1358                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1360                     } else if (start_text_iter != string_item->string.begin()) {
1361                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1362                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1364                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1365                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1366                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1367                         Inkscape::GC::release(text_before);
1368                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1369                         child_span->appendChild(text_in_span);
1370                         Inkscape::GC::release(text_in_span);
1371                         child->deleteObject();
1372                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1374                     } else
1375                         surround_entire_string = true;
1376                 }
1377                 if (surround_entire_string) {
1378                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1379                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1380                     Inkscape::GC::anchor(child_repr);
1381                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1382                     child_span->appendChild(child_repr);
1383                     Inkscape::GC::release(child_repr);
1384                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1385                 }
1386                 Inkscape::GC::release(child_span);
1388             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1389                 apply_css_recursive(child, css);
1390             }
1392         } else {  // !passed_start
1393             if (child->isAncestorOf(start_item)) {
1394                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1395                 if (end_item && child->isAncestorOf(end_item))
1396                     break;   // only happens when start_item == end_item (I think)
1397                 passed_start = true;
1398             }
1399         }
1401         if (end_item == child)
1402             break;
1403     }
1406 /* if item is at the beginning of a tree it doesn't matter which element
1407 it points to so for neatness we would like it to point to the highest
1408 possible child of \a common_ancestor. There is no iterator return because
1409 a string can never be an ancestor.
1411 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1412 like * to point to the inner span because we can apply style to that whole
1413 span. */
1414 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1416     if (item == common_ancestor)
1417         return item;
1418     if (SP_IS_STRING(item))
1419         if (text_iter != SP_STRING(item)->string.begin())
1420             return item;
1421     for ( ; ; ) {
1422         SPObject *parent = SP_OBJECT_PARENT(item);
1423         if (parent == common_ancestor)
1424             break;
1425         if (item != parent->firstChild())
1426             break;
1427         item = parent;
1428     }
1429     return item;
1433 /**     empty spans: abc<span></span>def
1434                       -> abcdef                  */
1435 static bool tidy_operator_empty_spans(SPObject **item)
1437     if ((*item)->hasChildren()) return false;
1438     if (is_line_break_object(*item)) return false;
1439     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1440     SPObject *next = SP_OBJECT_NEXT(*item);
1441     (*item)->deleteObject();
1442     *item = next;
1443     return true;
1446 /**    inexplicable spans: abc<span style="">def</span>ghi
1447                             -> "abc""def""ghi"
1448 the repeated strings will be merged by another operator. */
1449 static bool tidy_operator_inexplicable_spans(SPObject **item)
1451     if (*item && sp_repr_is_meta_element((*item)->repr)) return false;
1452     if (SP_IS_STRING(*item)) return false;
1453     if (is_line_break_object(*item)) return false;
1454     TextTagAttributes *attrs = attributes_for_object(*item);
1455     if (attrs && attrs->anyAttributesSet()) return false;
1456     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1457     SPObject *next = *item;
1458     while ((*item)->hasChildren()) {
1459         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1460         Inkscape::GC::anchor(repr);
1461         SP_OBJECT_REPR(*item)->removeChild(repr);
1462         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1463         Inkscape::GC::release(repr);
1464         next = SP_OBJECT_NEXT(next);
1465     }
1466     (*item)->deleteObject();
1467     *item = next;
1468     return true;
1471 /**    repeated spans: <font a>abc</font><font a>def</font>
1472                         -> <font a>abcdef</font>            */
1473 static bool tidy_operator_repeated_spans(SPObject **item)
1475     SPObject *first = *item;
1476     SPObject *second = SP_OBJECT_NEXT(first);
1477     if (second == NULL) return false;
1479     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1480     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1482     if (first_repr->type() != second_repr->type()) return false;
1484     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1485         // also amalgamate consecutive SPStrings into one
1486         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1487         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1488         second_repr->parent()->removeChild(second_repr);
1489         return true;
1490     }
1492     // merge consecutive spans with identical styles into one
1493     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1494     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1495     if (is_line_break_object(second)) return false;
1496     gchar const *first_style = first_repr->attribute("style");
1497     gchar const *second_style = second_repr->attribute("style");
1498     if (!((first_style == NULL && second_style == NULL)
1499           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1500         return false;
1502     // all our tests passed: do the merge
1503     TextTagAttributes *attributes_first = attributes_for_object(first);
1504     TextTagAttributes *attributes_second = attributes_for_object(second);
1505     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1506         TextTagAttributes attributes_first_copy = *attributes_first;
1507         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1508     }
1509     move_child_nodes(second_repr, first_repr);
1510     second_repr->parent()->removeChild(second_repr);
1511     return true;
1512     // *item is still the next object to process
1515 /**    redundant nesting: <font a><font b>abc</font></font>
1516                            -> <font b>abc</font>
1517        excessive nesting: <font a><size 1>abc</size></font>
1518                            -> <font a,size 1>abc</font>      */
1519 static bool tidy_operator_excessive_nesting(SPObject **item)
1521     if (!(*item)->hasChildren()) return false;
1522     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1523     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1524         return false;
1525     if (SP_IS_STRING((*item)->firstChild())) return false;
1526     if (is_line_break_object((*item)->firstChild())) return false;
1527     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1528     if (attrs && attrs->anyAttributesSet()) return false;
1529     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1530     if (child_style && *child_style)
1531         overwrite_style_with_string(*item, child_style);
1532     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1533     (*item)->firstChild()->deleteObject();
1534     return true;
1537 /** helper for tidy_operator_redundant_double_nesting() */
1538 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1540     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1541         return false;
1542     if (SP_IS_STRING(child)) return false;
1543     if (is_line_break_object(child)) return false;
1544     if (is_line_break_object(*item)) return false;
1545     TextTagAttributes *attrs = attributes_for_object(child);
1546     if (attrs && attrs->anyAttributesSet()) return false;
1547     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1549     Inkscape::XML::Node *insert_after_repr;
1550     if (!prepend) insert_after_repr = SP_OBJECT_REPR(*item);
1551     else if (SP_OBJECT_PREV(*item)) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1552     else insert_after_repr = NULL;
1553     while (SP_OBJECT_REPR(child)->childCount()) {
1554         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1555         Inkscape::GC::anchor(move_repr);
1556         SP_OBJECT_REPR(child)->removeChild(move_repr);
1557         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1558         Inkscape::GC::release(move_repr);
1559         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1560     }
1561     child->deleteObject();
1562     return true;
1565 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1566                                 -> <font b>abc<font a>def</font>ghi</font>
1567 this function does its work when the parameter is the <font a> tag in the
1568 example. You may note that this only does its work when the doubly-nested
1569 child is the first or last. The other cases are called 'style inversion'
1570 below, and I'm not yet convinced that the result of that operation will be
1571 tidier in all cases. */
1572 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1574     if (!(*item)->hasChildren()) return false;
1575     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1576     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1577         return true;
1578     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1579         return true;
1580     return false;
1583 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1584 then compares the styles for item+child versus just child. If they're equal,
1585 tidying is possible. */
1586 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1588     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1589         return false;
1590     if (SP_IS_STRING(child)) return false;
1591     if (is_line_break_object(child)) return false;
1592     if (is_line_break_object(*item)) return false;
1593     TextTagAttributes *attrs = attributes_for_object(child);
1594     if (attrs && attrs->anyAttributesSet()) return false;
1595     attrs = attributes_for_object(*item);
1596     if (attrs && attrs->anyAttributesSet()) return false;
1598     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1599     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1600     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1601     if (child_style && *child_style) {
1602         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1603         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1604     }
1605     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1606     if (item_style && *item_style) {
1607         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1608     }
1609     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1610     sp_repr_css_attr_unref(css_child_and_item);
1611     sp_repr_css_attr_unref(css_child_only);
1612     if (!equal) return false;
1614     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1615     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1616     if (prepend) {
1617         SPObject *prev = SP_OBJECT_PREV(*item);
1618         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1619     } else
1620         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1621     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1622     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1623     Inkscape::GC::release(new_span);
1624     child->deleteObject();
1625     return true;
1628 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1629                                 -> <font b>abc</font><font>def</font>
1630 test this by applying a colour to a region, then a different colour to
1631 a partially-overlapping region. */
1632 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1634     if (!(*item)->hasChildren()) return false;
1635     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1636     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1637         return true;
1638     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1639         return true;
1640     return false;
1643 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1644 in a paragraph which is not \a not_obj. */
1645 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1647     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1648     {
1649         if (child == not_obj) continue;
1650         if (child->hasChildren()) {
1651             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1652             if (ret) return ret;
1653         } else if (SP_IS_STRING(child))
1654             return SP_STRING(child);
1655     }
1656     return NULL;
1659 /** whitespace-only spans: abc<font> </font>def
1660                             -> abc<font></font> def
1661                            abc<b><i>def</i> </b>ghi
1662                             -> abc<b><i>def</i></b> ghi   */
1663 static bool tidy_operator_styled_whitespace(SPObject **item)
1665     if (!SP_IS_STRING(*item)) return false;
1666     Glib::ustring const &str = SP_STRING(*item)->string;
1667     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1668         if (!g_unichar_isspace(*it)) return false;
1670     SPObject *test_item = *item;
1671     SPString *next_string;
1672     for ( ; ; ) {  // find the next string
1673         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1674         if (next_string) {
1675             next_string->string.insert(0, str);
1676             break;
1677         }
1678         for ( ; ; ) {   // go up one item in the xml
1679             test_item = SP_OBJECT_PARENT(test_item);
1680             if (is_line_break_object(test_item)) break;
1681             if (SP_IS_FLOWTEXT(test_item)) return false;
1682             SPObject *next = SP_OBJECT_NEXT(test_item);
1683             if (next) {
1684                 test_item = next;
1685                 break;
1686             }
1687         }
1688         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1689             next_string = find_last_string_child_not_equal_to(test_item, *item);
1690             if (next_string == NULL) return false;   // an empty paragraph
1691             next_string->string += str;
1692             break;
1693         }
1694     }
1695     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1696     SPObject *delete_obj = *item;
1697     *item = SP_OBJECT_NEXT(*item);
1698     delete_obj->deleteObject();
1699     return true;
1702 /* possible tidy operators that are not yet implemented, either because
1703 they are difficult, occur infrequently, or because I'm not sure that the
1704 output is tidier in all cases:
1705     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1706                                               -> <div italic><para>abc</para></div>
1707     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1708                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1709     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1710                           -> <size 1><font a>abc</font>def</size>
1711 */
1713 /** Recursively walks the xml tree calling a set of cleanup operations on
1714 every child. Returns true if any changes were made to the tree.
1716 All the tidy operators return true if they made changes, and alter their
1717 parameter to point to the next object that should be processed, or NULL.
1718 They must not significantly alter (ie delete) any ancestor elements of the
1719 one they are passed.
1721 It may be that some of the later tidy operators that I wrote are actually
1722 general cases of the earlier operators, and hence the special-case-only
1723 versions can be removed. I haven't analysed my work in detail to figure
1724 out if this is so. */
1725 static bool tidy_xml_tree_recursively(SPObject *root)
1727     static bool (* const tidy_operators[])(SPObject**) = {
1728         tidy_operator_empty_spans,
1729         tidy_operator_inexplicable_spans,
1730         tidy_operator_repeated_spans,
1731         tidy_operator_excessive_nesting,
1732         tidy_operator_redundant_double_nesting,
1733         tidy_operator_redundant_semi_nesting,
1734         tidy_operator_styled_whitespace
1735     };
1736     bool changes = false;
1738     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1739         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1740             child = SP_OBJECT_NEXT(child);
1741             continue;
1742         }
1743         if (child->hasChildren())
1744             changes |= tidy_xml_tree_recursively(child);
1746         unsigned i;
1747         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1748             if (tidy_operators[i](&child)) {
1749                 changes = true;
1750                 break;
1751             }
1752         }
1753         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1754             child = SP_OBJECT_NEXT(child);
1755     }
1756     return changes;
1759 /** Applies the given CSS fragment to the characters of the given text or
1760 flowtext object between \a start and \a end, creating or removing span
1761 elements as necessary and optimal. */
1762 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1764     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1765     if (start == end) return;
1766     Inkscape::Text::Layout::iterator first, last;
1767     if (start < end) {
1768         first = start;
1769         last = end;
1770     } else {
1771         first = end;
1772         last = start;
1773     }
1774     Inkscape::Text::Layout const *layout = te_get_layout(text);
1775     SPObject *start_item = 0, *end_item = 0;
1776     void *rawptr = 0;
1777     Glib::ustring::iterator start_text_iter, end_text_iter;
1778     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1779     start_item = SP_OBJECT(rawptr);
1780     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1781     end_item = SP_OBJECT(rawptr);
1782     if (start_item == 0)
1783         return;   // start is at end of text
1784     if (is_line_break_object(start_item))
1785         start_item = SP_OBJECT_NEXT(start_item);
1786     if (is_line_break_object(end_item))
1787         end_item = SP_OBJECT_NEXT(end_item);
1788     if (end_item == 0) end_item = text;
1789     
1790     
1791     /* Special case: With a tref, we only want to change its style when the whole
1792      * string is selected, in which case the style can be applied directly to the
1793      * tref node.  If only part of the tref's string child is selected, just return. */
1794      
1795     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1796         
1797         return;
1798     } 
1800     /* stage 1: applying the style. Go up to the closest common ancestor of
1801     start and end and then semi-recursively apply the style to all the
1802     objects in between. The semi-recursion is because it's only necessary
1803     at the beginning and end; the style can just be applied to the root
1804     child in the middle.
1805     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1806     The recursion may involve creating new spans.
1807     */
1808     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1810     // bug #168370 (consider parent transform and viewBox)
1811     // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
1812     SPCSSAttr *css_set = sp_repr_css_attr_new();
1813     sp_repr_css_merge(css_set, (SPCSSAttr*) css);
1814     {
1815         Geom::Matrix const local(sp_item_i2doc_affine(SP_ITEM(common_ancestor)));
1816         double const ex(local.descrim());
1817         if ( ( ex != 0. )
1818              && ( ex != 1. ) ) {
1819             sp_css_attr_scale(css_set, 1/ex);
1820         }
1821     }
1823     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1824     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1825     recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1826     sp_repr_css_attr_unref(css_set);
1828     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1829     /* discussion: this stage requires a certain level of inventiveness because
1830     it's not clear what the best representation is in many cases. An ideal
1831     implementation would provide some sort of scoring function to rate the
1832     ugliness of a given xml tree and try to reduce said function, but providing
1833     the various possibilities to be rated is non-trivial. Instead, I have opted
1834     for a multi-pass technique which simply recognises known-ugly patterns and
1835     has matching routines for optimising the patterns it finds. It's reasonably
1836     easy to add new pattern matching processors. If everything gets disastrous
1837     and neither option can be made to work, a fallback could be to reduce
1838     everything to a single level of nesting and drop all pretence of
1839     roundtrippability. */
1840     while (tidy_xml_tree_recursively(common_ancestor)){};
1842     // if we only modified subobjects this won't have been automatically sent
1843     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1846 bool is_part_of_text_subtree (SPObject *obj)
1848     return (SP_IS_TSPAN(obj) 
1849             || SP_IS_TEXT(obj) 
1850             || SP_IS_FLOWTEXT(obj)
1851             || SP_IS_FLOWTSPAN(obj)
1852             || SP_IS_FLOWDIV(obj)
1853             || SP_IS_FLOWPARA(obj)
1854             || SP_IS_FLOWLINE(obj)
1855             || SP_IS_FLOWREGIONBREAK(obj));
1858 bool is_top_level_text_object (SPObject *obj)
1860     return (SP_IS_TEXT(obj) 
1861             || SP_IS_FLOWTEXT(obj));
1864 bool has_visible_text (SPObject *obj)
1866     if (SP_IS_STRING(obj) && !SP_STRING(obj)->string.empty()) 
1867         return true; // maybe we should also check that it's not all whitespace?
1869     for (SPObject const *child = obj->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
1870         if (has_visible_text((SPObject *) child))
1871             return true;
1872     }
1874     return false;
1877 /*
1878   Local Variables:
1879   mode:c++
1880   c-file-style:"stroustrup"
1881   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1882   indent-tabs-mode:nil
1883   fill-column:99
1884   End:
1885 */
1886 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :