Code

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