Code

Fix self-snapping when dragging the transformation center of a selection containing...
[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 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 sp_te_adjust_dx(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop * /*desktop*/, double delta)
977     unsigned char_index = 0;
978     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
979     if (attributes) {
980         attributes->addToDx(char_index, delta);
981     }
982     if (start != end) {
983         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
984         if (attributes) {
985             attributes->addToDx(char_index, -delta);
986         }
987     }
989     item->updateRepr();
990     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
993 void sp_te_adjust_dy(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop * /*desktop*/, double delta)
995     unsigned char_index = 0;
996     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
997     if (attributes) {
998         attributes->addToDy(char_index, delta);
999     }
1000     if (start != end) {
1001         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1002         if (attributes) {
1003             attributes->addToDy(char_index, -delta);
1004         }
1005     }
1007     item->updateRepr();
1008     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1011 void
1012 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
1014     // divide increment by zoom
1015     // divide increment by matrix expansion
1016     gdouble factor = 1 / desktop->current_zoom();
1017     Geom::Matrix t (sp_item_i2doc_affine(text));
1018     factor = factor / t.descrim();
1019     Inkscape::Text::Layout const *layout = te_get_layout(text);
1020     if (layout == NULL) return;
1021     SPObject *source_item = 0;
1022     void *rawptr = 0;
1023     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1024     source_item = SP_OBJECT(rawptr);
1025     if (source_item == 0) return;
1026     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
1028     sp_te_adjust_rotation(text, start, end, desktop, degrees);
1031 void
1032 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1034     unsigned char_index;
1035     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1036     if (attributes == NULL) return;
1038     if (start != end) {
1039         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1040             attributes = text_tag_attributes_at_position(text, it, &char_index);
1041             if (attributes) attributes->addToRotate(char_index, degrees);
1042         }
1043     } else
1044         attributes->addToRotate(char_index, degrees);
1046     text->updateRepr();
1047     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1050 void sp_te_set_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1052     unsigned char_index = 0;
1053     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1054     if (attributes != NULL) {
1055         if (start != end) {
1056             for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1057                 attributes = text_tag_attributes_at_position(text, it, &char_index);
1058                 if (attributes) {
1059                     attributes->setRotate(char_index, degrees);
1060                 }
1061             }
1062         } else {
1063             attributes->setRotate(char_index, degrees);
1064         }
1066         text->updateRepr();
1067         text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1068     }
1071 void
1072 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1074     g_return_if_fail (text != NULL);
1075     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1077     Inkscape::Text::Layout const *layout = te_get_layout(text);
1079     gdouble val;
1080     SPObject *source_obj = 0;
1081     void *rawptr = 0;
1082     unsigned nb_let;
1083     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1084     source_obj = SP_OBJECT(rawptr);
1086     if (source_obj == 0) {   // end of text
1087         source_obj = text->lastChild();
1088     }
1089     if (SP_IS_STRING(source_obj)) {
1090         source_obj = source_obj->parent;
1091     }
1093     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1095     // calculate real value
1096     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1097        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1098     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1099         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1100             val = style->font_size.computed * style->letter_spacing.value;
1101         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1102             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1103         } else { // unknown unit - should not happen
1104             val = 0.0;
1105         }
1106     } else { // there's a real value in .computed, or it's zero
1107         val = style->letter_spacing.computed;
1108     }
1110     if (start == end) {
1111         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1112             source_obj = SP_OBJECT_PARENT(source_obj);
1113         nb_let = sp_text_get_length(source_obj);
1114     } else {
1115         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1116     }
1118     // divide increment by zoom and by the number of characters in the line,
1119     // so that the entire line is expanded by by pixels, no matter what its length
1120     gdouble const zoom = desktop->current_zoom();
1121     gdouble const zby = (by
1122                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1123                          / to_2geom(sp_item_i2doc_affine(SP_ITEM(source_obj))).descrim());
1124     val += zby;
1126     if (start == end) {
1127         // set back value to entire paragraph
1128         style->letter_spacing.normal = FALSE;
1129         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1130             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1131                 style->letter_spacing.value = val / style->font_size.computed;
1132             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1133                 style->letter_spacing.value = val / style->font_size.computed * 2;
1134             }
1135         } else {
1136             style->letter_spacing.computed = val;
1137         }
1139         style->letter_spacing.set = TRUE;
1140     } else {
1141         // apply to selection only
1142         SPCSSAttr *css = sp_repr_css_attr_new();
1143         char string_val[40];
1144         g_snprintf(string_val, sizeof(string_val), "%f", val);
1145         sp_repr_css_set_property(css, "letter-spacing", string_val);
1146         sp_te_apply_style(text, start, end, css);
1147         sp_repr_css_attr_unref(css);
1148     }
1150     text->updateRepr();
1151     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1154 double
1155 sp_te_get_average_linespacing (SPItem *text)
1157     Inkscape::Text::Layout const *layout = te_get_layout(text);
1158     if (!layout)
1159         return 0;
1161     unsigned line_count = layout->lineIndex(layout->end());
1162     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1163     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1164     return average_line_height;
1167 void
1168 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1170     // TODO: use start and end iterators to delineate the area to be affected
1171     g_return_if_fail (text != NULL);
1172     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1174     Inkscape::Text::Layout const *layout = te_get_layout(text);
1175     SPStyle *style = SP_OBJECT_STYLE (text);
1177     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1178         style->line_height.set = TRUE;
1179         style->line_height.inherit = FALSE;
1180         style->line_height.normal = FALSE;
1181         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1182         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1183     }
1185     unsigned line_count = layout->lineIndex(layout->end());
1186     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1187     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1188     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1190     // divide increment by zoom and by the number of lines,
1191     // so that the entire object is expanded by by pixels
1192     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1194     // divide increment by matrix expansion
1195     Geom::Matrix t (sp_item_i2doc_affine (SP_ITEM(text)));
1196     zby = zby / t.descrim();
1198     switch (style->line_height.unit) {
1199         case SP_CSS_UNIT_NONE:
1200         default:
1201             // multiplier-type units, stored in computed
1202             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
1203             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1204             style->line_height.value = style->line_height.computed;
1205             break;
1206         case SP_CSS_UNIT_EM:
1207         case SP_CSS_UNIT_EX:
1208         case SP_CSS_UNIT_PERCENT:
1209             // multiplier-type units, stored in value
1210             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1211             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1212             break;
1213             // absolute-type units
1214         case SP_CSS_UNIT_PX:
1215             style->line_height.computed += zby;
1216             style->line_height.value = style->line_height.computed;
1217             break;
1218         case SP_CSS_UNIT_PT:
1219             style->line_height.computed += zby * PT_PER_PX;
1220             style->line_height.value = style->line_height.computed;
1221             break;
1222         case SP_CSS_UNIT_PC:
1223             style->line_height.computed += zby * (PT_PER_PX / 12);
1224             style->line_height.value = style->line_height.computed;
1225             break;
1226         case SP_CSS_UNIT_MM:
1227             style->line_height.computed += zby * MM_PER_PX;
1228             style->line_height.value = style->line_height.computed;
1229             break;
1230         case SP_CSS_UNIT_CM:
1231             style->line_height.computed += zby * CM_PER_PX;
1232             style->line_height.value = style->line_height.computed;
1233             break;
1234         case SP_CSS_UNIT_IN:
1235             style->line_height.computed += zby * IN_PER_PX;
1236             style->line_height.value = style->line_height.computed;
1237             break;
1238     }
1239     text->updateRepr();
1240     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1244 /* ***************************************************************************************************/
1245 //                           S T Y L E   A P P L I C A T I O N
1248 /** converts an iterator to a character index, mainly because ustring::substr()
1249 doesn't have a version that takes iterators as parameters. */
1250 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1252     unsigned n = 0;
1253     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1254         n++;
1255     return n;
1258 /** applies the given style string on top of the existing styles for \a item,
1259 as opposed to sp_style_merge_from_style_string which merges its parameter
1260 underneath the existing styles (ie ignoring already set properties). */
1261 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1263     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1264     sp_style_merge_from_style_string(new_style, style_string);
1265     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1266     if (item_style_string && *item_style_string)
1267         sp_style_merge_from_style_string(new_style, item_style_string);
1268     gchar *new_style_string = sp_style_write_string(new_style);
1269     sp_style_unref(new_style);
1270     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1271     g_free(new_style_string);
1274 /** Returns true if the style of \a parent and the style of \a child are
1275 equivalent (and hence the children of both will appear the same). It is a
1276 limitation of the current implementation that \a parent must be a (not
1277 necessarily immediate) ancestor of \a child. */
1278 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1280     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1281     // will not work. Firstly because of an inheritance bug in style.cpp that has
1282     // implications too large for me to feel safe fixing, but mainly because the css spec
1283     // requires that the computed value is inherited, not the specified value.
1284     g_assert(parent->isAncestorOf(child));
1285     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1286     // we have to write parent_style then read it again, because some properties format their values
1287     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1288     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1289     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1290     g_free(parent_style);
1291     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1292     sp_style_unref(parent_spstyle);
1294     Glib::ustring child_style_construction(parent_style);
1295     while (child != parent) {
1296         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1297         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1298         if (style_text && *style_text) {
1299             child_style_construction += ';';
1300             child_style_construction += style_text;
1301         }
1302         child = SP_OBJECT_PARENT(child);
1303     }
1304     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1305     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1306     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1307     sp_style_unref(child_spstyle);
1308     bool equal = !strcmp(child_style, parent_style);
1309     g_free(child_style);
1310     g_free(parent_style);
1311     return equal;
1314 /** returns true if \a first and \a second contain all the same attributes
1315 with the same values as each other. Note that we have to compare both
1316 forwards and backwards to make sure we don't miss any attributes that are
1317 in one but not the other. */
1318 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1320     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1321     for ( ; attrs ; attrs++) {
1322         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1323         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1324             return false;
1325     }
1326     attrs = second->attributeList();
1327     for ( ; attrs ; attrs++) {
1328         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1329         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1330             return false;
1331     }
1332     return true;
1335 /** sets the given css attribute on this object and all its descendants.
1336 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1337 transform stuff. */
1338 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1340     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1342     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1343         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1344             // Unset properties which are accumulating and thus should not be set recursively.
1345             // 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.
1346             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1347             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1348             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1349             apply_css_recursive(child, css_recurse);
1350             sp_repr_css_attr_unref(css_recurse);
1351         } else {
1352             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1353         }
1354     }
1357 /** applies the given style to all the objects at the given level and below
1358 which are between \a start_item and \a end_item, creating spans as necessary.
1359 If \a start_item or \a end_item are NULL then the style is applied to all
1360 objects to the beginning or end respectively. \a span_object_name is the
1361 name of the xml for a text span (ie tspan or flowspan). */
1362 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)
1364     bool passed_start = start_item == NULL ? true : false;
1365     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1366     
1367     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1368         if (start_item == child)
1369             passed_start = true;
1371         if (passed_start) {
1372             if (end_item && child->isAncestorOf(end_item)) {
1373                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1374                 break;
1375             }
1376             // apply style
1378             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1379             // often this means that new spans are created before child and child is modified only
1380             if (SP_IS_STRING(child)) {
1381                 SPString *string_item = SP_STRING(child);
1382                 bool surround_entire_string = true;
1384                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1385                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1386                 SPObject *prev_item = SP_OBJECT_PREV(child);
1387                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1389                 if (child == start_item || child == end_item) {
1390                     surround_entire_string = false;
1391                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1392                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1393                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1394                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1396                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1397                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1398                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1399                         Inkscape::GC::release(text_before);
1400                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1401                         child_span->appendChild(text_in_span);
1402                         Inkscape::GC::release(text_in_span);
1403                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1405                     } else if (child == end_item) {
1406                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1407                         //  (includes case where start_text_iter == begin())
1408                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1409                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1411                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1412                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1413                         child_span->appendChild(text_in_span);
1414                         Inkscape::GC::release(text_in_span);
1415                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1417                     } else if (start_text_iter != string_item->string.begin()) {
1418                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1419                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1421                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1422                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1423                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1424                         Inkscape::GC::release(text_before);
1425                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1426                         child_span->appendChild(text_in_span);
1427                         Inkscape::GC::release(text_in_span);
1428                         child->deleteObject();
1429                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1431                     } else
1432                         surround_entire_string = true;
1433                 }
1434                 if (surround_entire_string) {
1435                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1436                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1437                     Inkscape::GC::anchor(child_repr);
1438                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1439                     child_span->appendChild(child_repr);
1440                     Inkscape::GC::release(child_repr);
1441                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1442                 }
1443                 Inkscape::GC::release(child_span);
1445             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1446                 apply_css_recursive(child, css);
1447             }
1449         } else {  // !passed_start
1450             if (child->isAncestorOf(start_item)) {
1451                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1452                 if (end_item && child->isAncestorOf(end_item))
1453                     break;   // only happens when start_item == end_item (I think)
1454                 passed_start = true;
1455             }
1456         }
1458         if (end_item == child)
1459             break;
1460     }
1463 /* if item is at the beginning of a tree it doesn't matter which element
1464 it points to so for neatness we would like it to point to the highest
1465 possible child of \a common_ancestor. There is no iterator return because
1466 a string can never be an ancestor.
1468 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1469 like * to point to the inner span because we can apply style to that whole
1470 span. */
1471 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1473     if (item == common_ancestor)
1474         return item;
1475     if (SP_IS_STRING(item))
1476         if (text_iter != SP_STRING(item)->string.begin())
1477             return item;
1478     for ( ; ; ) {
1479         SPObject *parent = SP_OBJECT_PARENT(item);
1480         if (parent == common_ancestor)
1481             break;
1482         if (item != parent->firstChild())
1483             break;
1484         item = parent;
1485     }
1486     return item;
1490 /**     empty spans: abc<span></span>def
1491                       -> abcdef                  */
1492 static bool tidy_operator_empty_spans(SPObject **item)
1494     if ((*item)->hasChildren()) return false;
1495     if (is_line_break_object(*item)) return false;
1496     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1497     SPObject *next = SP_OBJECT_NEXT(*item);
1498     (*item)->deleteObject();
1499     *item = next;
1500     return true;
1503 /**    inexplicable spans: abc<span style="">def</span>ghi
1504                             -> "abc""def""ghi"
1505 the repeated strings will be merged by another operator. */
1506 static bool tidy_operator_inexplicable_spans(SPObject **item)
1508     if (*item && sp_repr_is_meta_element((*item)->repr)) return false;
1509     if (SP_IS_STRING(*item)) return false;
1510     if (is_line_break_object(*item)) return false;
1511     TextTagAttributes *attrs = attributes_for_object(*item);
1512     if (attrs && attrs->anyAttributesSet()) return false;
1513     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1514     SPObject *next = *item;
1515     while ((*item)->hasChildren()) {
1516         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1517         Inkscape::GC::anchor(repr);
1518         SP_OBJECT_REPR(*item)->removeChild(repr);
1519         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1520         Inkscape::GC::release(repr);
1521         next = SP_OBJECT_NEXT(next);
1522     }
1523     (*item)->deleteObject();
1524     *item = next;
1525     return true;
1528 /**    repeated spans: <font a>abc</font><font a>def</font>
1529                         -> <font a>abcdef</font>            */
1530 static bool tidy_operator_repeated_spans(SPObject **item)
1532     SPObject *first = *item;
1533     SPObject *second = SP_OBJECT_NEXT(first);
1534     if (second == NULL) return false;
1536     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1537     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1539     if (first_repr->type() != second_repr->type()) return false;
1541     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1542         // also amalgamate consecutive SPStrings into one
1543         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1544         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1545         second_repr->parent()->removeChild(second_repr);
1546         return true;
1547     }
1549     // merge consecutive spans with identical styles into one
1550     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1551     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1552     if (is_line_break_object(second)) return false;
1553     gchar const *first_style = first_repr->attribute("style");
1554     gchar const *second_style = second_repr->attribute("style");
1555     if (!((first_style == NULL && second_style == NULL)
1556           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1557         return false;
1559     // all our tests passed: do the merge
1560     TextTagAttributes *attributes_first = attributes_for_object(first);
1561     TextTagAttributes *attributes_second = attributes_for_object(second);
1562     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1563         TextTagAttributes attributes_first_copy = *attributes_first;
1564         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1565     }
1566     move_child_nodes(second_repr, first_repr);
1567     second_repr->parent()->removeChild(second_repr);
1568     return true;
1569     // *item is still the next object to process
1572 /**    redundant nesting: <font a><font b>abc</font></font>
1573                            -> <font b>abc</font>
1574        excessive nesting: <font a><size 1>abc</size></font>
1575                            -> <font a,size 1>abc</font>      */
1576 static bool tidy_operator_excessive_nesting(SPObject **item)
1578     if (!(*item)->hasChildren()) return false;
1579     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1580     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1581         return false;
1582     if (SP_IS_STRING((*item)->firstChild())) return false;
1583     if (is_line_break_object((*item)->firstChild())) return false;
1584     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1585     if (attrs && attrs->anyAttributesSet()) return false;
1586     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1587     if (child_style && *child_style)
1588         overwrite_style_with_string(*item, child_style);
1589     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1590     (*item)->firstChild()->deleteObject();
1591     return true;
1594 /** helper for tidy_operator_redundant_double_nesting() */
1595 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1597     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1598         return false;
1599     if (SP_IS_STRING(child)) return false;
1600     if (is_line_break_object(child)) return false;
1601     if (is_line_break_object(*item)) return false;
1602     TextTagAttributes *attrs = attributes_for_object(child);
1603     if (attrs && attrs->anyAttributesSet()) return false;
1604     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1606     Inkscape::XML::Node *insert_after_repr;
1607     if (!prepend) insert_after_repr = SP_OBJECT_REPR(*item);
1608     else if (SP_OBJECT_PREV(*item)) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1609     else insert_after_repr = NULL;
1610     while (SP_OBJECT_REPR(child)->childCount()) {
1611         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1612         Inkscape::GC::anchor(move_repr);
1613         SP_OBJECT_REPR(child)->removeChild(move_repr);
1614         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1615         Inkscape::GC::release(move_repr);
1616         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1617     }
1618     child->deleteObject();
1619     return true;
1622 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1623                                 -> <font b>abc<font a>def</font>ghi</font>
1624 this function does its work when the parameter is the <font a> tag in the
1625 example. You may note that this only does its work when the doubly-nested
1626 child is the first or last. The other cases are called 'style inversion'
1627 below, and I'm not yet convinced that the result of that operation will be
1628 tidier in all cases. */
1629 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1631     if (!(*item)->hasChildren()) return false;
1632     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1633     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1634         return true;
1635     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1636         return true;
1637     return false;
1640 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1641 then compares the styles for item+child versus just child. If they're equal,
1642 tidying is possible. */
1643 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1645     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1646         return false;
1647     if (SP_IS_STRING(child)) return false;
1648     if (is_line_break_object(child)) return false;
1649     if (is_line_break_object(*item)) return false;
1650     TextTagAttributes *attrs = attributes_for_object(child);
1651     if (attrs && attrs->anyAttributesSet()) return false;
1652     attrs = attributes_for_object(*item);
1653     if (attrs && attrs->anyAttributesSet()) return false;
1655     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1656     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1657     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1658     if (child_style && *child_style) {
1659         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1660         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1661     }
1662     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1663     if (item_style && *item_style) {
1664         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1665     }
1666     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1667     sp_repr_css_attr_unref(css_child_and_item);
1668     sp_repr_css_attr_unref(css_child_only);
1669     if (!equal) return false;
1671     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1672     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1673     if (prepend) {
1674         SPObject *prev = SP_OBJECT_PREV(*item);
1675         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1676     } else
1677         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1678     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1679     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1680     Inkscape::GC::release(new_span);
1681     child->deleteObject();
1682     return true;
1685 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1686                                 -> <font b>abc</font><font>def</font>
1687 test this by applying a colour to a region, then a different colour to
1688 a partially-overlapping region. */
1689 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1691     if (!(*item)->hasChildren()) return false;
1692     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1693     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1694         return true;
1695     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1696         return true;
1697     return false;
1700 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1701 in a paragraph which is not \a not_obj. */
1702 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1704     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1705     {
1706         if (child == not_obj) continue;
1707         if (child->hasChildren()) {
1708             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1709             if (ret) return ret;
1710         } else if (SP_IS_STRING(child))
1711             return SP_STRING(child);
1712     }
1713     return NULL;
1716 /** whitespace-only spans: abc<font> </font>def
1717                             -> abc<font></font> def
1718                            abc<b><i>def</i> </b>ghi
1719                             -> abc<b><i>def</i></b> ghi   */
1720 static bool tidy_operator_styled_whitespace(SPObject **item)
1722     if (!SP_IS_STRING(*item)) return false;
1723     Glib::ustring const &str = SP_STRING(*item)->string;
1724     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1725         if (!g_unichar_isspace(*it)) return false;
1727     SPObject *test_item = *item;
1728     SPString *next_string;
1729     for ( ; ; ) {  // find the next string
1730         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1731         if (next_string) {
1732             next_string->string.insert(0, str);
1733             break;
1734         }
1735         for ( ; ; ) {   // go up one item in the xml
1736             test_item = SP_OBJECT_PARENT(test_item);
1737             if (is_line_break_object(test_item)) break;
1738             if (SP_IS_FLOWTEXT(test_item)) return false;
1739             SPObject *next = SP_OBJECT_NEXT(test_item);
1740             if (next) {
1741                 test_item = next;
1742                 break;
1743             }
1744         }
1745         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1746             next_string = find_last_string_child_not_equal_to(test_item, *item);
1747             if (next_string == NULL) return false;   // an empty paragraph
1748             next_string->string += str;
1749             break;
1750         }
1751     }
1752     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1753     SPObject *delete_obj = *item;
1754     *item = SP_OBJECT_NEXT(*item);
1755     delete_obj->deleteObject();
1756     return true;
1759 /* possible tidy operators that are not yet implemented, either because
1760 they are difficult, occur infrequently, or because I'm not sure that the
1761 output is tidier in all cases:
1762     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1763                                               -> <div italic><para>abc</para></div>
1764     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1765                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1766     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1767                           -> <size 1><font a>abc</font>def</size>
1768 */
1770 /** Recursively walks the xml tree calling a set of cleanup operations on
1771 every child. Returns true if any changes were made to the tree.
1773 All the tidy operators return true if they made changes, and alter their
1774 parameter to point to the next object that should be processed, or NULL.
1775 They must not significantly alter (ie delete) any ancestor elements of the
1776 one they are passed.
1778 It may be that some of the later tidy operators that I wrote are actually
1779 general cases of the earlier operators, and hence the special-case-only
1780 versions can be removed. I haven't analysed my work in detail to figure
1781 out if this is so. */
1782 static bool tidy_xml_tree_recursively(SPObject *root)
1784     static bool (* const tidy_operators[])(SPObject**) = {
1785         tidy_operator_empty_spans,
1786         tidy_operator_inexplicable_spans,
1787         tidy_operator_repeated_spans,
1788         tidy_operator_excessive_nesting,
1789         tidy_operator_redundant_double_nesting,
1790         tidy_operator_redundant_semi_nesting,
1791         tidy_operator_styled_whitespace
1792     };
1793     bool changes = false;
1795     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1796         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1797             child = SP_OBJECT_NEXT(child);
1798             continue;
1799         }
1800         if (child->hasChildren())
1801             changes |= tidy_xml_tree_recursively(child);
1803         unsigned i;
1804         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1805             if (tidy_operators[i](&child)) {
1806                 changes = true;
1807                 break;
1808             }
1809         }
1810         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1811             child = SP_OBJECT_NEXT(child);
1812     }
1813     return changes;
1816 /** Applies the given CSS fragment to the characters of the given text or
1817 flowtext object between \a start and \a end, creating or removing span
1818 elements as necessary and optimal. */
1819 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1821     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1822     if (start == end) return;
1823     Inkscape::Text::Layout::iterator first, last;
1824     if (start < end) {
1825         first = start;
1826         last = end;
1827     } else {
1828         first = end;
1829         last = start;
1830     }
1831     Inkscape::Text::Layout const *layout = te_get_layout(text);
1832     SPObject *start_item = 0, *end_item = 0;
1833     void *rawptr = 0;
1834     Glib::ustring::iterator start_text_iter, end_text_iter;
1835     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1836     start_item = SP_OBJECT(rawptr);
1837     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1838     end_item = SP_OBJECT(rawptr);
1839     if (start_item == 0)
1840         return;   // start is at end of text
1841     if (is_line_break_object(start_item))
1842         start_item = SP_OBJECT_NEXT(start_item);
1843     if (is_line_break_object(end_item))
1844         end_item = SP_OBJECT_NEXT(end_item);
1845     if (end_item == 0) end_item = text;
1846     
1847     
1848     /* Special case: With a tref, we only want to change its style when the whole
1849      * string is selected, in which case the style can be applied directly to the
1850      * tref node.  If only part of the tref's string child is selected, just return. */
1851      
1852     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1853         
1854         return;
1855     } 
1857     /* stage 1: applying the style. Go up to the closest common ancestor of
1858     start and end and then semi-recursively apply the style to all the
1859     objects in between. The semi-recursion is because it's only necessary
1860     at the beginning and end; the style can just be applied to the root
1861     child in the middle.
1862     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1863     The recursion may involve creating new spans.
1864     */
1865     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1867     // bug #168370 (consider parent transform and viewBox)
1868     // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
1869     SPCSSAttr *css_set = sp_repr_css_attr_new();
1870     sp_repr_css_merge(css_set, (SPCSSAttr*) css);
1871     {
1872         Geom::Matrix const local(sp_item_i2doc_affine(SP_ITEM(common_ancestor)));
1873         double const ex(local.descrim());
1874         if ( ( ex != 0. )
1875              && ( ex != 1. ) ) {
1876             sp_css_attr_scale(css_set, 1/ex);
1877         }
1878     }
1880     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1881     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1882     recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1883     sp_repr_css_attr_unref(css_set);
1885     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1886     /* discussion: this stage requires a certain level of inventiveness because
1887     it's not clear what the best representation is in many cases. An ideal
1888     implementation would provide some sort of scoring function to rate the
1889     ugliness of a given xml tree and try to reduce said function, but providing
1890     the various possibilities to be rated is non-trivial. Instead, I have opted
1891     for a multi-pass technique which simply recognises known-ugly patterns and
1892     has matching routines for optimising the patterns it finds. It's reasonably
1893     easy to add new pattern matching processors. If everything gets disastrous
1894     and neither option can be made to work, a fallback could be to reduce
1895     everything to a single level of nesting and drop all pretence of
1896     roundtrippability. */
1897     while (tidy_xml_tree_recursively(common_ancestor)){};
1899     // if we only modified subobjects this won't have been automatically sent
1900     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1903 bool is_part_of_text_subtree (SPObject *obj)
1905     return (SP_IS_TSPAN(obj) 
1906             || SP_IS_TEXT(obj) 
1907             || SP_IS_FLOWTEXT(obj)
1908             || SP_IS_FLOWTSPAN(obj)
1909             || SP_IS_FLOWDIV(obj)
1910             || SP_IS_FLOWPARA(obj)
1911             || SP_IS_FLOWLINE(obj)
1912             || SP_IS_FLOWREGIONBREAK(obj));
1915 bool is_top_level_text_object (SPObject *obj)
1917     return (SP_IS_TEXT(obj) 
1918             || SP_IS_FLOWTEXT(obj));
1921 bool has_visible_text (SPObject *obj)
1923     if (SP_IS_STRING(obj) && !SP_STRING(obj)->string.empty()) 
1924         return true; // maybe we should also check that it's not all whitespace?
1926     for (SPObject const *child = obj->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
1927         if (has_visible_text((SPObject *) child))
1928             return true;
1929     }
1931     return false;
1934 /*
1935   Local Variables:
1936   mode:c++
1937   c-file-style:"stroustrup"
1938   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1939   indent-tabs-mode:nil
1940   fill-column:99
1941   End:
1942 */
1943 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :