Code

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