Code

Warning cleanup
[inkscape.git] / src / text-editing.cpp
1 /*
2  * Parent class for text and flowtext
3  *
4  * Authors:
5  *   bulia byak
6  *   Richard Hughes
7  *
8  * Copyright (C) 2004-5 authors
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <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_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
157                 || SP_IS_TEXTPATH(object)
158                 || SP_IS_FLOWDIV(object)
159                 || SP_IS_FLOWPARA(object)
160                 || SP_IS_FLOWLINE(object)
161                 || SP_IS_FLOWREGIONBREAK(object)) {
162                     
163             is_line_break = true;
164         }
165     }
166     
167     return is_line_break;
170 /** returns the attributes for an object, or NULL if it isn't a text,
171 tspan, tref, or textpath. */
172 static TextTagAttributes* attributes_for_object(SPObject *object)
174     if (SP_IS_TSPAN(object))
175         return &SP_TSPAN(object)->attributes;
176     if (SP_IS_TEXT(object))
177         return &SP_TEXT(object)->attributes;
178     if (SP_IS_TREF(object))
179         return &SP_TREF(object)->attributes;
180     if (SP_IS_TEXTPATH(object))
181         return &SP_TEXTPATH(object)->attributes;
182     return NULL;
185 static const char * span_name_for_text_object(SPObject const *object)
187     if (SP_IS_TEXT(object)) return "svg:tspan";
188     else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
189     return NULL;
192 /** Recursively gets the length of all the SPStrings at or below the given
193 \a item. Also adds 1 for each line break encountered. */
194 unsigned sp_text_get_length(SPObject const *item)
196     unsigned length = 0;
198     if (SP_IS_STRING(item)) return SP_STRING(item)->string.length();
199     
200     if (is_line_break_object(item)) length++;
201     
202     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
203         if (SP_IS_STRING(child)) length += SP_STRING(child)->string.length();
204         else length += sp_text_get_length(child);
205     }
206     return length;
209 /** Recursively gets the length of all the SPStrings at or below the given
210 \a item, before and not including \a upto. Also adds 1 for each line break encountered. */
211 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
213     unsigned length = 0;
215     // The string is the lowest level and the length can be counted directly. 
216     if (SP_IS_STRING(item)) {
217         return SP_STRING(item)->string.length();
218     }
219     
220     // Take care of new lines...
221     if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
222         if (item != SP_OBJECT_PARENT(item)->firstChild()) {
223             // add 1 for each newline
224             length++;
225         }
226     }
227     
228     // Count the length of the children
229     for (SPObject const *child = item->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
230         if (upto && child == upto) {
231             // hit upto, return immediately
232             return length;
233         }
234         if (SP_IS_STRING(child)) {
235             length += SP_STRING(child)->string.length();
236         }
237         else {
238             if (upto && child->isAncestorOf(upto)) {
239                 // upto is below us, recurse and break loop
240                 length += sp_text_get_length_upto(child, upto);
241                 return length;
242             } else {
243                 // recurse and go to the next sibling
244                 length += sp_text_get_length_upto(child, upto);
245             }
246         }
247     }
248     return length;
251 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
253     switch (old_node->type()) {
254         case Inkscape::XML::ELEMENT_NODE: {
255             Inkscape::XML::Node *new_node = xml_doc->createElement(old_node->name());
256             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
257             GQuark const id_key = g_quark_from_string("id");
258             for ( ; attributes ; attributes++) {
259                 if (attributes->key == id_key) continue;
260                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
261             }
262             return new_node;
263         }
265         case Inkscape::XML::TEXT_NODE:
266             return xml_doc->createTextNode(old_node->content());
268         case Inkscape::XML::COMMENT_NODE:
269             return xml_doc->createComment(old_node->content());
271         case Inkscape::XML::DOCUMENT_NODE:
272             return NULL;   // this had better never happen
273     }
274     return NULL;
277 /** returns the sum of the (recursive) lengths of all the SPStrings prior
278 to \a item at the same level. */
279 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
281     unsigned char_index = 0;
282     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = SP_OBJECT_NEXT(sibling))
283         char_index += sp_text_get_length(sibling);
284     return char_index;
287 /** splits the attributes for the first object at the given \a char_index
288 and moves the ones after that point into \a second_item. */
289 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
291     TextTagAttributes *first_attrs = attributes_for_object(first_item);
292     TextTagAttributes *second_attrs = attributes_for_object(second_item);
293     if (first_attrs && second_attrs)
294         first_attrs->split(char_index, second_attrs);
297 /** recursively divides the XML node tree into two objects: the original will
298 contain all objects up to and including \a split_obj and the returned value
299 will be the new leaf which represents the copy of \a split_obj and extends
300 down the tree with new elements all the way to the common root which is the
301 parent of the first line break node encountered.
302 */
303 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
305     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
306     if (is_line_break_object(split_obj)) {
307         Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
308         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
309         Inkscape::GC::release(new_node);
310         split_attributes(split_obj, SP_OBJECT_NEXT(split_obj), char_index);
311         return SP_OBJECT_NEXT(split_obj);
312     }
314     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
315     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
316     // copy the split node
317     Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
318     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
319     Inkscape::GC::release(new_node);
321     // sort out the copied attributes (x/y/dx/dy/rotate)
322     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
324     // then move all the subsequent nodes
325     split_obj = SP_OBJECT_NEXT(split_obj);
326     while (split_obj) {
327         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
328         SPObject *next_obj = SP_OBJECT_NEXT(split_obj);  // this is about to become invalidated by removeChild()
329         Inkscape::GC::anchor(move_repr);
330         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
331         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
332         Inkscape::GC::release(move_repr);
334         split_obj = next_obj;
335     }
336     return duplicate_obj->firstChild();
339 /** inserts a new line break at the given position in a text or flowtext
340 object. If the position is in the middle of a span, the XML tree must be
341 chopped in two such that the line can be created at the root of the text
342 element. Returns an iterator pointing just after the inserted break. */
343 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
345     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
346     // texpaths attached to the same path, with a vertical shift
347     if (SP_IS_TEXT_TEXTPATH (item) || SP_IS_TREF(item))
348         return position;
349         
350     SPDesktop *desktop = SP_ACTIVE_DESKTOP; 
352     Inkscape::Text::Layout const *layout = te_get_layout(item);
353     SPObject *split_obj = 0;
354     Glib::ustring::iterator split_text_iter;
355     if (position != layout->end()) {
356         void *rawptr = 0;
357         layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
358         split_obj = SP_OBJECT(rawptr);
359     }
361     if (split_obj == 0 || is_line_break_object(split_obj)) {
362         if (split_obj == 0) split_obj = item->lastChild();
363         
364         if (SP_IS_TREF(split_obj)) {
365                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
366             return position;
367         }
368         
369         if (split_obj) {
370             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(split_obj));
371             Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
372             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
373             Inkscape::GC::release(new_node);
374         }
375     } else if (SP_IS_STRING(split_obj)) {
376         // If the parent is a tref, editing on this particular string is disallowed.
377         if (SP_IS_TREF(SP_OBJECT_PARENT(split_obj))) {
378             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
379             return position;
380         }
381         
382         Glib::ustring *string = &SP_STRING(split_obj)->string;
383         unsigned char_index = 0;
384         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
385             char_index++;
386         // we need to split the entire text tree into two
387         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
388         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
389         string->erase(split_text_iter, string->end());
390         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
391         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
392     } else {
393         // TODO
394         // I think the only case to put here is arbitrary gaps, which nobody uses yet
395     }
396     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
397     unsigned char_index = layout->iteratorToCharIndex(position);
398     te_update_layout_now(item);
399     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
400     return layout->charIndexToIterator(char_index + 1);
403 /** finds the first SPString after the given position, including children, excluding parents */
404 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
406     while (start_obj) {
407         if (start_obj->hasChildren()) {
408             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
409             if (found_string) return found_string;
410         }
411         if (SP_IS_STRING(start_obj)) return SP_STRING(start_obj);
412         start_obj = SP_OBJECT_NEXT(start_obj);
413         if (is_line_break_object(start_obj))
414             break;   // don't cross line breaks
415     }
416     return NULL;
419 /** inserts the given characters into the given string and inserts
420 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
421 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
423     unsigned char_index = 0;
424     unsigned char_count = g_utf8_strlen(utf8, -1);
425     Glib::ustring *string = &SP_STRING(string_item)->string;
427     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
428         char_index++;
429     string->replace(iter_at, iter_at, utf8);
431     SPObject *parent_item = string_item;
432     for ( ; ; ) {
433         char_index += sum_sibling_text_lengths_before(parent_item);
434         parent_item = SP_OBJECT_PARENT(parent_item);
435         TextTagAttributes *attributes = attributes_for_object(parent_item);
436         if (!attributes) break;
437         attributes->insert(char_index, char_count);
438     }
441 /** Inserts the given text into a text or flowroot object. Line breaks
442 cannot be inserted using this function, see sp_te_insert_line(). Returns
443 an iterator pointing just after the inserted text. */
444 Inkscape::Text::Layout::iterator
445 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
447     if (!g_utf8_validate(utf8,-1,NULL)) {
448         g_warning("Trying to insert invalid utf8");
449         return position;
450     }
451     
452     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
454     Inkscape::Text::Layout const *layout = te_get_layout(item);
455     SPObject *source_obj = 0;
456     void *rawptr = 0;
457     Glib::ustring::iterator iter_text;
458     // we want to insert after the previous char, not before the current char.
459     // it makes a difference at span boundaries
460     Inkscape::Text::Layout::iterator it_prev_char = position;
461     bool cursor_at_start = !it_prev_char.prevCharacter();
462     bool cursor_at_end = position == layout->end();
463     layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
464     source_obj = SP_OBJECT(rawptr);
465     if (SP_IS_STRING(source_obj)) {
466         // If the parent is a tref, editing on this particular string is disallowed.
467         if (SP_IS_TREF(SP_OBJECT_PARENT(source_obj))) {
468             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
469             return position;
470         }
471         
472         // Now the simple case can begin...
473         if (!cursor_at_start) iter_text++;
474         SPString *string_item = SP_STRING(source_obj);
475         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
476     } else {
477         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
478         Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
479         if (cursor_at_start) {
480             source_obj = item;
481             if (source_obj->hasChildren()) {
482                 source_obj = source_obj->firstChild();
483                 if (SP_IS_FLOWTEXT(item)) {
484                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj))
485                         source_obj = SP_OBJECT_NEXT(source_obj);
486                     if (source_obj == NULL)
487                         source_obj = item;
488                 }
489             }
490             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
491                 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
492                 SP_OBJECT_REPR(item)->appendChild(para);
493                 source_obj = item->lastChild();
494             }
495         } else
496             source_obj = SP_OBJECT_NEXT(source_obj);
498         if (source_obj) {  // never fails
499             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
500             if (string_item == NULL) {
501                 // need to add an SPString in this (pathological) case
502                 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
503                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
504                 Inkscape::GC::release(rstring);
505                 g_assert(SP_IS_STRING(source_obj->firstChild()));
506                 string_item = SP_STRING(source_obj->firstChild());
507             }
508             // If the parent is a tref, editing on this particular string is disallowed.
509             if (SP_IS_TREF(SP_OBJECT_PARENT(string_item))) {
510                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
511                 return position;
512             }
513             
514             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
515         }
516     }
518     item->updateRepr(SP_OBJECT_REPR(item),SP_OBJECT_WRITE_EXT);
519     unsigned char_index = layout->iteratorToCharIndex(position);
520     te_update_layout_now(item);
521     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
522     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
526 /* ***************************************************************************************************/
527 //                            D E L E T I N G   T E X T
529 /** moves all the children of \a from_repr to \a to_repr, either before
530 the existing children or after them. Order is maintained. The empty
531 \a from_repr is not deleted. */
532 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
534     while (from_repr->childCount()) {
535         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
536         Inkscape::GC::anchor(child);
537         from_repr->removeChild(child);
538         if (prepend) to_repr->addChild(child, NULL);
539         else to_repr->appendChild(child);
540         Inkscape::GC::release(child);
541     }
544 /** returns the object in the tree which is the closest ancestor of both
545 \a one and \a two. It will never return anything higher than \a text. */
546 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
548     if (one == NULL || two == NULL)
549         return text;
550     SPObject *common_ancestor = one;
551     if (SP_IS_STRING(common_ancestor))
552         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
553     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
554         g_assert(common_ancestor != text);
555         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
556     }
557     return common_ancestor;
560 /** positions \a para_obj and \a text_iter to be pointing at the end
561 of the last string in the last leaf object of \a para_obj. If the last
562 leaf is not an SPString then \a text_iter will be unchanged. */
563 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
565     while ((*para_obj)->hasChildren())
566         *para_obj = (*para_obj)->lastChild();
567     if (SP_IS_STRING(*para_obj))
568         *text_iter = SP_STRING(*para_obj)->string.end();
571 /** delete the line break pointed to by \a item by merging its children into
572 the next suitable object and deleting \a item. Returns the object after the
573 ones that have just been moved and sets \a next_is_sibling accordingly. */
574 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
576     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
577     SPObject *next_item = NULL;
578     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
580     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
581       <div></div><p>*text</p>
582       <p><div></div>*text</p>
583       <p><div></div></p><p>*text</p>
584     */
585     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
586     Inkscape::XML::Node *new_span_repr = xml_doc->createElement(span_name_for_text_object(root));
588     if (gchar const *a = this_repr->attribute("dx"))
589         new_span_repr->setAttribute("dx", a);
590     if (gchar const *a = this_repr->attribute("dy"))
591         new_span_repr->setAttribute("dy", a);
592     if (gchar const *a = this_repr->attribute("rotate"))
593         new_span_repr->setAttribute("rotate", a);
595     SPObject *following_item = item;
596     while (SP_OBJECT_NEXT(following_item) == NULL) {
597         following_item = SP_OBJECT_PARENT(following_item);
598         g_assert(following_item != root);
599     }
600     following_item = SP_OBJECT_NEXT(following_item);
602     SPObject *new_parent_item;
603     if (SP_IS_STRING(following_item)) {
604         new_parent_item = SP_OBJECT_PARENT(following_item);
605         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, SP_OBJECT_PREV(following_item) ? SP_OBJECT_REPR(SP_OBJECT_PREV(following_item)) : NULL);
606         next_item = following_item;
607         *next_is_sibling = true;
608     } else {
609         new_parent_item = following_item;
610         next_item = new_parent_item->firstChild();
611         *next_is_sibling = true;
612         if (next_item == NULL) {
613             next_item = new_parent_item;
614             *next_is_sibling = false;
615         }
616         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
617     }
619     // work around a bug in sp_style_write_difference() which causes the difference
620     // not to be written if the second param has a style set which the first does not
621     // by causing the first param to have everything set
622     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
623     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
624     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
625     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
626     for ( ; attrs ; attrs++) {
627         gchar const *key = g_quark_to_string(attrs->key);
628         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
629         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
630             this_node_attrs->setAttribute(key, this_attr);
631     }
632     sp_repr_css_attr_unref(this_node_attrs_inherited);
633     sp_repr_css_attr_unref(this_node_attrs);
634     sp_repr_css_attr_unref(dest_node_attrs);
635     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
637     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
638     if (attributes)
639         attributes->insert(0, moved_char_count);
640     move_child_nodes(this_repr, new_span_repr);
641     this_repr->parent()->removeChild(this_repr);
642     return next_item;
645 /** erases the given characters from the given string and deletes the
646 corresponding x/y/dx/dy/rotate attributes from all its parents. */
647 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
649     unsigned char_index = 0;
650     unsigned char_count = 0;
651     Glib::ustring *string = &SP_STRING(string_item)->string;
653     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
654         char_index++;
655     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
656         char_count++;
657     string->erase(iter_from, iter_to);
658     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
660     SPObject *parent_item = string_item;
661     for ( ; ; ) {
662         char_index += sum_sibling_text_lengths_before(parent_item);
663         parent_item = SP_OBJECT_PARENT(parent_item);
664         TextTagAttributes *attributes = attributes_for_object(parent_item);
665         if (attributes == NULL) break;
667         attributes->erase(char_index, char_count);
668         attributes->writeTo(SP_OBJECT_REPR(parent_item));
669     }
672 /* Deletes the given characters from a text or flowroot object. This is
673 quite a complicated operation, partly due to the cleanup that is done if all
674 the text in a subobject has been deleted, and partly due to the difficulty
675 of figuring out what is a line break and how to delete one. Returns the
676 real start and ending iterators based on the situation. */
677 bool
678 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start,
679               Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
681     bool success = false;
683     iter_pair.first = start;
684     iter_pair.second = end;
685     
686     if (start == end) return success;
687     
688     if (start > end) {
689         iter_pair.first = end;
690         iter_pair.second = start;
691     }
692     
693     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
694     
695     Inkscape::Text::Layout const *layout = te_get_layout(item);
696     SPObject *start_item = 0, *end_item = 0;
697     void *rawptr = 0;
698     Glib::ustring::iterator start_text_iter, end_text_iter;
699     layout->getSourceOfCharacter(iter_pair.first, &rawptr, &start_text_iter);
700     start_item = SP_OBJECT(rawptr);
701     layout->getSourceOfCharacter(iter_pair.second, &rawptr, &end_text_iter);
702     end_item = SP_OBJECT(rawptr);
703     if (start_item == 0)
704         return success;   // start is at end of text
705     if (is_line_break_object(start_item))
706         move_to_end_of_paragraph(&start_item, &start_text_iter);
707     if (end_item == 0) {
708         end_item = item->lastChild();
709         move_to_end_of_paragraph(&end_item, &end_text_iter);
710     }
711     else if (is_line_break_object(end_item))
712         move_to_end_of_paragraph(&end_item, &end_text_iter);
714     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
716     if (start_item == end_item) {
717         // the quick case where we're deleting stuff all from the same string
718         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
719             // If the parent is a tref, editing on this particular string is disallowed.
720             if (SP_IS_TREF(SP_OBJECT_PARENT(start_item))) {
721                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
722             } else {
723                 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
724                 success = true;
725             }
726         }
727     } else {
728         SPObject *sub_item = start_item;
729         // walk the tree from start_item to end_item, deleting as we go
730         while (sub_item != item) {
731             if (sub_item == end_item) {
732                 if (SP_IS_STRING(sub_item)) {
733                     // If the parent is a tref, editing on this particular string is disallowed.
734                     if (SP_IS_TREF(SP_OBJECT_PARENT(sub_item))) {
735                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
736                         break;
737                     }
738             
739                     Glib::ustring *string = &SP_STRING(sub_item)->string;
740                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
741                     success = true;
742                 }
743                 break;
744             }
745             if (SP_IS_STRING(sub_item)) {
746                 SPString *string = SP_STRING(sub_item);
747                 if (sub_item == start_item)
748                     erase_from_spstring(string, start_text_iter, string->string.end());
749                 else
750                     erase_from_spstring(string, string->string.begin(), string->string.end());
751                 success = true;
752             }
753             // walk to the next item in the tree
754             if (sub_item->hasChildren())
755                 sub_item = sub_item->firstChild();
756             else {
757                 SPObject *next_item;
758                 do {
759                     bool is_sibling = true;
760                     next_item = SP_OBJECT_NEXT(sub_item);
761                     if (next_item == NULL) {
762                         next_item = SP_OBJECT_PARENT(sub_item);
763                         is_sibling = false;
764                     }
766                     if (is_line_break_object(sub_item))
767                         next_item = delete_line_break(item, sub_item, &is_sibling);
769                     sub_item = next_item;
770                     if (is_sibling) break;
771                     // no more siblings, go up a parent
772                 } while (sub_item != item && sub_item != end_item);
773             }
774         }
775     }
777     while (tidy_xml_tree_recursively(common_ancestor));
778     te_update_layout_now(item);
779     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
780     layout->validateIterator(&iter_pair.first);
781     layout->validateIterator(&iter_pair.second);
782     return success;
786 /* ***************************************************************************************************/
787 //                            P L A I N   T E X T   F U N C T I O N S
789 /** Gets a text-only representation of the given text or flowroot object,
790 replacing line break elements with '\n'. */
791 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
793     if (*pending_line_break)
794         *string += '\n';
795     for (SPObject const *child = root->firstChild() ; child ; child = SP_OBJECT_NEXT(child)) {
796         if (SP_IS_STRING(child))
797             *string += SP_STRING(child)->string;
798         else
799             sp_te_get_ustring_multiline(child, string, pending_line_break);
800     }
801     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root))
802         *pending_line_break = true;
805 /** Gets a text-only representation of the given text or flowroot object,
806 replacing line break elements with '\n'. The return value must be free()d. */
807 gchar *
808 sp_te_get_string_multiline (SPItem const *text)
810     Glib::ustring string;
811     bool pending_line_break = false;
813     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
814     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
815     if (string.empty()) return NULL;
816     return strdup(string.data());
819 /** Gets a text-only representation of the characters in a text or flowroot
820 object from \a start to \a end only. Line break elements are replaced with
821 '\n'. */
822 Glib::ustring
823 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
825     if (start == end) return "";
826     Inkscape::Text::Layout::iterator first, last;
827     if (start < end) {
828         first = start;
829         last = end;
830     } else {
831         first = end;
832         last = start;
833     }
834     Inkscape::Text::Layout const *layout = te_get_layout(text);
835     Glib::ustring result;
836     // not a particularly fast piece of code. I'll optimise it if people start to notice.
837     for ( ; first < last ; first.nextCharacter()) {
838         SPObject *char_item = 0;
839         void *rawptr = 0;
840         Glib::ustring::iterator text_iter;
841         layout->getSourceOfCharacter(first, &rawptr, &text_iter);
842         char_item = SP_OBJECT(rawptr);
843         if (SP_IS_STRING(char_item))
844             result += *text_iter;
845         else
846             result += '\n';
847     }
848     return result;
851 void
852 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
854     g_return_if_fail (text != NULL);
855     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
857     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(text)->document();
858     Inkscape::XML::Node *repr;
859     SPObject *object;
860     bool is_textpath = false;
861     if (SP_IS_TEXT_TEXTPATH (text)) {
862         repr = SP_OBJECT_REPR (sp_object_first_child(SP_OBJECT (text)));
863         object = sp_object_first_child(SP_OBJECT (text));
864         is_textpath = true;
865     } else {
866         repr = SP_OBJECT_REPR (text);
867         object = SP_OBJECT (text);
868     }
870     if (!str) str = "";
871     gchar *content = g_strdup (str);
873     repr->setContent("");
874     SPObject *child = object->firstChild();
875     while (child) {
876         SPObject *next = SP_OBJECT_NEXT(child);
877         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child))
878             repr->removeChild(SP_OBJECT_REPR(child));
879         child = next;
880     }
882     gchar *p = content;
883     while (p) {
884         gchar *e = strchr (p, '\n');
885         if (is_textpath) {
886             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
887         } else {
888             if (e) *e = '\0';
889             Inkscape::XML::Node *rtspan;
890             if (SP_IS_TEXT(text)) { // create a tspan for each line
891                 rtspan = xml_doc->createElement("svg:tspan");
892                 rtspan->setAttribute("sodipodi:role", "line");
893             } else { // create a flowPara for each line
894                 rtspan = xml_doc->createElement("svg:flowPara");
895             }
896             Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
897             rtspan->addChild(rstr, NULL);
898             Inkscape::GC::release(rstr);
899             repr->appendChild(rtspan);
900             Inkscape::GC::release(rtspan);
901         }
902         p = (e) ? e + 1 : NULL;
903     }
904     if (is_textpath) {
905         Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
906         repr->addChild(rstr, NULL);
907         Inkscape::GC::release(rstr);
908     }
910     g_free (content);
913 /* ***************************************************************************************************/
914 //                           K E R N I N G   A N D   S P A C I N G
916 /** Returns the attributes block and the character index within that block
917 which represents the iterator \a position. */
918 static TextTagAttributes*
919 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
921     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
922         return NULL;   // flowtext doesn't support kerning yet
923     SPText *text = SP_TEXT(item);
925     SPObject *source_item = 0;
926     void *rawptr = 0;
927     Glib::ustring::iterator source_text_iter;
928     text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
929     source_item = SP_OBJECT(rawptr);
931     if (!SP_IS_STRING(source_item)) return NULL;
932     Glib::ustring *string = &SP_STRING(source_item)->string;
933     *char_index = sum_sibling_text_lengths_before(source_item);
934     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
935         ++*char_index;
937     return attributes_for_object(SP_OBJECT_PARENT(source_item));
940 void
941 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, NR::Point by)
943     // divide increment by zoom
944     // divide increment by matrix expansion
945     gdouble factor = 1 / desktop->current_zoom();
946     NR::Matrix t = sp_item_i2doc_affine(item);
947     factor = factor / NR::expansion(t);
948     by = factor * by;
950     unsigned char_index;
951     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
952     if (attributes) attributes->addToDxDy(char_index, by);
953     if (start != end) {
954         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
955         if (attributes) attributes->addToDxDy(char_index, -by);
956     }
958     item->updateRepr();
959     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
962 void
963 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
965     // divide increment by zoom
966     // divide increment by matrix expansion
967     gdouble factor = 1 / desktop->current_zoom();
968     NR::Matrix t = sp_item_i2doc_affine(text);
969     factor = factor / NR::expansion(t);
970     Inkscape::Text::Layout const *layout = te_get_layout(text);
971     if (layout == NULL) return;
972     SPObject *source_item = 0;
973     void *rawptr = 0;
974     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
975     source_item = SP_OBJECT(rawptr);
976     if (source_item == 0) return;
977     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
979     sp_te_adjust_rotation(text, start, end, desktop, degrees);
982 void
983 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
985     unsigned char_index;
986     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
987     if (attributes == NULL) return;
989     if (start != end) {
990         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
991             attributes = text_tag_attributes_at_position(text, it, &char_index);
992             if (attributes) attributes->addToRotate(char_index, degrees);
993         }
994     } else
995         attributes->addToRotate(char_index, degrees);
997     text->updateRepr();
998     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1001 void
1002 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1004     g_return_if_fail (text != NULL);
1005     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1007     Inkscape::Text::Layout const *layout = te_get_layout(text);
1009     gdouble val;
1010     SPObject *source_obj = 0;
1011     void *rawptr = 0;
1012     unsigned nb_let;
1013     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1014     source_obj = SP_OBJECT(rawptr);
1016     if (source_obj == 0) {   // end of text
1017         source_obj = text->lastChild();
1018     }
1019     if (SP_IS_STRING(source_obj)) {
1020         source_obj = source_obj->parent;
1021     }
1023     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1025     // calculate real value
1026     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1027        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1028     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1029         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1030             val = style->font_size.computed * style->letter_spacing.value;
1031         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1032             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1033         } else { // unknown unit - should not happen
1034             val = 0.0;
1035         }
1036     } else { // there's a real value in .computed, or it's zero
1037         val = style->letter_spacing.computed;
1038     }
1040     if (start == end) {
1041         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1042             source_obj = SP_OBJECT_PARENT(source_obj);
1043         nb_let = sp_text_get_length(source_obj);
1044     } else {
1045         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1046     }
1048     // divide increment by zoom and by the number of characters in the line,
1049     // so that the entire line is expanded by by pixels, no matter what its length
1050     gdouble const zoom = desktop->current_zoom();
1051     gdouble const zby = (by
1052                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1053                          / NR::expansion(sp_item_i2doc_affine(SP_ITEM(source_obj))));
1054     val += zby;
1056     if (start == end) {
1057         // set back value to entire paragraph
1058         style->letter_spacing.normal = FALSE;
1059         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1060             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1061                 style->letter_spacing.value = val / style->font_size.computed;
1062             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1063                 style->letter_spacing.value = val / style->font_size.computed * 2;
1064             }
1065         } else {
1066             style->letter_spacing.computed = val;
1067         }
1069         style->letter_spacing.set = TRUE;
1070     } else {
1071         // apply to selection only
1072         SPCSSAttr *css = sp_repr_css_attr_new();
1073         char string_val[40];
1074         g_snprintf(string_val, sizeof(string_val), "%f", val);
1075         sp_repr_css_set_property(css, "letter-spacing", string_val);
1076         sp_te_apply_style(text, start, end, css);
1077         sp_repr_css_attr_unref(css);
1078     }
1080     text->updateRepr();
1081     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1084 void
1085 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1087     // TODO: use start and end iterators to delineate the area to be affected
1088     g_return_if_fail (text != NULL);
1089     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1091     Inkscape::Text::Layout const *layout = te_get_layout(text);
1092     SPStyle *style = SP_OBJECT_STYLE (text);
1094     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1095         style->line_height.set = TRUE;
1096         style->line_height.inherit = FALSE;
1097         style->line_height.normal = FALSE;
1098         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1099         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1100     }
1102     unsigned line_count = layout->lineIndex(layout->end());
1103     double all_lines_height = layout->characterAnchorPoint(layout->end())[NR::Y] - layout->characterAnchorPoint(layout->begin())[NR::Y];
1104     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1105     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1107     // divide increment by zoom and by the number of lines,
1108     // so that the entire object is expanded by by pixels
1109     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1111     // divide increment by matrix expansion
1112     NR::Matrix t = sp_item_i2doc_affine (SP_ITEM(text));
1113     zby = zby / NR::expansion(t);
1115     switch (style->line_height.unit) {
1116         case SP_CSS_UNIT_NONE:
1117         default:
1118             // multiplier-type units, stored in computed
1119             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
1120             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1121             style->line_height.value = style->line_height.computed;
1122             break;
1123         case SP_CSS_UNIT_EM:
1124         case SP_CSS_UNIT_EX:
1125         case SP_CSS_UNIT_PERCENT:
1126             // multiplier-type units, stored in value
1127             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1128             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1129             break;
1130             // absolute-type units
1131         case SP_CSS_UNIT_PX:
1132             style->line_height.computed += zby;
1133             style->line_height.value = style->line_height.computed;
1134             break;
1135         case SP_CSS_UNIT_PT:
1136             style->line_height.computed += zby * PT_PER_PX;
1137             style->line_height.value = style->line_height.computed;
1138             break;
1139         case SP_CSS_UNIT_PC:
1140             style->line_height.computed += zby * (PT_PER_PX / 12);
1141             style->line_height.value = style->line_height.computed;
1142             break;
1143         case SP_CSS_UNIT_MM:
1144             style->line_height.computed += zby * MM_PER_PX;
1145             style->line_height.value = style->line_height.computed;
1146             break;
1147         case SP_CSS_UNIT_CM:
1148             style->line_height.computed += zby * CM_PER_PX;
1149             style->line_height.value = style->line_height.computed;
1150             break;
1151         case SP_CSS_UNIT_IN:
1152             style->line_height.computed += zby * IN_PER_PX;
1153             style->line_height.value = style->line_height.computed;
1154             break;
1155     }
1156     text->updateRepr();
1157     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1161 /* ***************************************************************************************************/
1162 //                           S T Y L E   A P P L I C A T I O N
1165 /** converts an iterator to a character index, mainly because ustring::substr()
1166 doesn't have a version that takes iterators as parameters. */
1167 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1169     unsigned n = 0;
1170     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1171         n++;
1172     return n;
1175 /** applies the given style string on top of the existing styles for \a item,
1176 as opposed to sp_style_merge_from_style_string which merges its parameter
1177 underneath the existing styles (ie ignoring already set properties). */
1178 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1180     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1181     sp_style_merge_from_style_string(new_style, style_string);
1182     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1183     if (item_style_string && *item_style_string)
1184         sp_style_merge_from_style_string(new_style, item_style_string);
1185     gchar *new_style_string = sp_style_write_string(new_style);
1186     sp_style_unref(new_style);
1187     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1188     g_free(new_style_string);
1191 /** Returns true if the style of \a parent and the style of \a child are
1192 equivalent (and hence the children of both will appear the same). It is a
1193 limitation of the current implementation that \a parent must be a (not
1194 necessarily immediate) ancestor of \a child. */
1195 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1197     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1198     // will not work. Firstly because of an inheritance bug in style.cpp that has
1199     // implications too large for me to feel safe fixing, but mainly because the css spec
1200     // requires that the computed value is inherited, not the specified value.
1201     g_assert(parent->isAncestorOf(child));
1202     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1203     // we have to write parent_style then read it again, because some properties format their values
1204     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1205     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1206     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1207     g_free(parent_style);
1208     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1209     sp_style_unref(parent_spstyle);
1211     Glib::ustring child_style_construction(parent_style);
1212     while (child != parent) {
1213         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1214         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1215         if (style_text && *style_text) {
1216             child_style_construction += ';';
1217             child_style_construction += style_text;
1218         }
1219         child = SP_OBJECT_PARENT(child);
1220     }
1221     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1222     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1223     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1224     sp_style_unref(child_spstyle);
1225     bool equal = !strcmp(child_style, parent_style);
1226     g_free(child_style);
1227     g_free(parent_style);
1228     return equal;
1231 /** returns true if \a first and \a second contain all the same attributes
1232 with the same values as each other. Note that we have to compare both
1233 forwards and backwards to make sure we don't miss any attributes that are
1234 in one but not the other. */
1235 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1237     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1238     for ( ; attrs ; attrs++) {
1239         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1240         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1241             return false;
1242     }
1243     attrs = second->attributeList();
1244     for ( ; attrs ; attrs++) {
1245         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1246         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1247             return false;
1248     }
1249     return true;
1252 /** sets the given css attribute on this object and all its descendants.
1253 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1254 transform stuff. */
1255 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1257     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1259     for (SPObject *child = sp_object_first_child(SP_OBJECT(o)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
1260         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1261             // Unset properties which are accumulating and thus should not be set recursively.
1262             // 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.
1263             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1264             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1265             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1266             apply_css_recursive(child, css_recurse);
1267             sp_repr_css_attr_unref(css_recurse);
1268         } else {
1269             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1270         }
1271     }
1274 /** applies the given style to all the objects at the given level and below
1275 which are between \a start_item and \a end_item, creating spans as necessary.
1276 If \a start_item or \a end_item are NULL then the style is applied to all
1277 objects to the beginning or end respectively. \a span_object_name is the
1278 name of the xml for a text span (ie tspan or flowspan). */
1279 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)
1281     bool passed_start = start_item == NULL ? true : false;
1282     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(common_ancestor));
1283     
1284     for (SPObject *child = common_ancestor->firstChild() ; child != NULL ; child = SP_OBJECT_NEXT(child)) {
1285         if (start_item == child)
1286             passed_start = true;
1288         if (passed_start) {
1289             if (end_item && child->isAncestorOf(end_item)) {
1290                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1291                 break;
1292             }
1293             // apply style
1295             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1296             // often this means that new spans are created before child and child is modified only
1297             if (SP_IS_STRING(child)) {
1298                 SPString *string_item = SP_STRING(child);
1299                 bool surround_entire_string = true;
1301                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1302                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1303                 SPObject *prev_item = SP_OBJECT_PREV(child);
1304                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1306                 if (child == start_item || child == end_item) {
1307                     surround_entire_string = false;
1308                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1309                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1310                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1311                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1313                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1314                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1315                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1316                         Inkscape::GC::release(text_before);
1317                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1318                         child_span->appendChild(text_in_span);
1319                         Inkscape::GC::release(text_in_span);
1320                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1322                     } else if (child == end_item) {
1323                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1324                         //  (includes case where start_text_iter == begin())
1325                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1326                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1328                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1329                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1330                         child_span->appendChild(text_in_span);
1331                         Inkscape::GC::release(text_in_span);
1332                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1334                     } else if (start_text_iter != string_item->string.begin()) {
1335                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1336                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1338                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1339                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1340                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1341                         Inkscape::GC::release(text_before);
1342                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1343                         child_span->appendChild(text_in_span);
1344                         Inkscape::GC::release(text_in_span);
1345                         child->deleteObject();
1346                         child = sp_object_get_child_by_repr(common_ancestor, child_span);
1348                     } else
1349                         surround_entire_string = true;
1350                 }
1351                 if (surround_entire_string) {
1352                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1353                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1354                     Inkscape::GC::anchor(child_repr);
1355                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1356                     child_span->appendChild(child_repr);
1357                     Inkscape::GC::release(child_repr);
1358                     child = sp_object_get_child_by_repr(common_ancestor, child_span);
1359                 }
1360                 Inkscape::GC::release(child_span);
1362             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1363                 apply_css_recursive(child, css);
1364             }
1366         } else {  // !passed_start
1367             if (child->isAncestorOf(start_item)) {
1368                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1369                 if (end_item && child->isAncestorOf(end_item))
1370                     break;   // only happens when start_item == end_item (I think)
1371                 passed_start = true;
1372             }
1373         }
1375         if (end_item == child)
1376             break;
1377     }
1380 /* if item is at the beginning of a tree it doesn't matter which element
1381 it points to so for neatness we would like it to point to the highest
1382 possible child of \a common_ancestor. There is no iterator return because
1383 a string can never be an ancestor.
1385 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1386 like * to point to the inner span because we can apply style to that whole
1387 span. */
1388 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1390     if (item == common_ancestor)
1391         return item;
1392     if (SP_IS_STRING(item))
1393         if (text_iter != SP_STRING(item)->string.begin())
1394             return item;
1395     for ( ; ; ) {
1396         SPObject *parent = SP_OBJECT_PARENT(item);
1397         if (parent == common_ancestor)
1398             break;
1399         if (item != parent->firstChild())
1400             break;
1401         item = parent;
1402     }
1403     return item;
1407 /**     empty spans: abc<span></span>def
1408                       -> abcdef                  */
1409 static bool tidy_operator_empty_spans(SPObject **item)
1411     if ((*item)->hasChildren()) return false;
1412     if (is_line_break_object(*item)) return false;
1413     if (SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty()) return false;
1414     SPObject *next = SP_OBJECT_NEXT(*item);
1415     (*item)->deleteObject();
1416     *item = next;
1417     return true;
1420 /**    inexplicable spans: abc<span style="">def</span>ghi
1421                             -> "abc""def""ghi"
1422 the repeated strings will be merged by another operator. */
1423 static bool tidy_operator_inexplicable_spans(SPObject **item)
1425     if (SP_IS_STRING(*item)) return false;
1426     if (is_line_break_object(*item)) return false;
1427     TextTagAttributes *attrs = attributes_for_object(*item);
1428     if (attrs && attrs->anyAttributesSet()) return false;
1429     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1430     SPObject *next = *item;
1431     while ((*item)->hasChildren()) {
1432         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1433         Inkscape::GC::anchor(repr);
1434         SP_OBJECT_REPR(*item)->removeChild(repr);
1435         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1436         Inkscape::GC::release(repr);
1437         next = SP_OBJECT_NEXT(next);
1438     }
1439     (*item)->deleteObject();
1440     *item = next;
1441     return true;
1444 /**    repeated spans: <font a>abc</font><font a>def</font>
1445                         -> <font a>abcdef</font>            */
1446 static bool tidy_operator_repeated_spans(SPObject **item)
1448     SPObject *first = *item;
1449     SPObject *second = SP_OBJECT_NEXT(first);
1450     if (second == NULL) return false;
1452     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1453     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1455     if (first_repr->type() != second_repr->type()) return false;
1457     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1458         // also amalgamate consecutive SPStrings into one
1459         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1460         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1461         second_repr->parent()->removeChild(second_repr);
1462         return true;
1463     }
1465     // merge consecutive spans with identical styles into one
1466     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1467     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1468     if (is_line_break_object(second)) return false;
1469     gchar const *first_style = first_repr->attribute("style");
1470     gchar const *second_style = second_repr->attribute("style");
1471     if (!((first_style == NULL && second_style == NULL)
1472           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1473         return false;
1475     // all our tests passed: do the merge
1476     TextTagAttributes *attributes_first = attributes_for_object(first);
1477     TextTagAttributes *attributes_second = attributes_for_object(second);
1478     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1479         TextTagAttributes attributes_first_copy = *attributes_first;
1480         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1481     }
1482     move_child_nodes(second_repr, first_repr);
1483     second_repr->parent()->removeChild(second_repr);
1484     return true;
1485     // *item is still the next object to process
1488 /**    redundant nesting: <font a><font b>abc</font></font>
1489                            -> <font b>abc</font>
1490        excessive nesting: <font a><size 1>abc</size></font>
1491                            -> <font a,size 1>abc</font>      */
1492 static bool tidy_operator_excessive_nesting(SPObject **item)
1494     if (!(*item)->hasChildren()) return false;
1495     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1496     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1497         return false;
1498     if (SP_IS_STRING((*item)->firstChild())) return false;
1499     if (is_line_break_object((*item)->firstChild())) return false;
1500     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1501     if (attrs && attrs->anyAttributesSet()) return false;
1502     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1503     if (child_style && *child_style)
1504         overwrite_style_with_string(*item, child_style);
1505     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1506     (*item)->firstChild()->deleteObject();
1507     return true;
1510 /** helper for tidy_operator_redundant_double_nesting() */
1511 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1513     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1514         return false;
1515     if (SP_IS_STRING(child)) return false;
1516     if (is_line_break_object(child)) return false;
1517     if (is_line_break_object(*item)) return false;
1518     TextTagAttributes *attrs = attributes_for_object(child);
1519     if (attrs && attrs->anyAttributesSet()) return false;
1520     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1522     Inkscape::XML::Node *insert_after_repr;
1523     if (prepend) insert_after_repr = SP_OBJECT_REPR(SP_OBJECT_PREV(*item));
1524     else insert_after_repr = SP_OBJECT_REPR(*item);
1525     while (SP_OBJECT_REPR(child)->childCount()) {
1526         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1527         Inkscape::GC::anchor(move_repr);
1528         SP_OBJECT_REPR(child)->removeChild(move_repr);
1529         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1530         Inkscape::GC::release(move_repr);
1531         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1532     }
1533     child->deleteObject();
1534     return true;
1537 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1538                                 -> <font b>abc<font a>def</font>ghi</font>
1539 this function does its work when the parameter is the <font a> tag in the
1540 example. You may note that this only does its work when the doubly-nested
1541 child is the first or last. The other cases are called 'style inversion'
1542 below, and I'm not yet convinced that the result of that operation will be
1543 tidier in all cases. */
1544 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1546     if (!(*item)->hasChildren()) return false;
1547     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1548     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1549         return true;
1550     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1551         return true;
1552     return false;
1555 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1556 then compares the styles for item+child versus just child. If they're equal,
1557 tidying is possible. */
1558 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1560     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1561         return false;
1562     if (SP_IS_STRING(child)) return false;
1563     if (is_line_break_object(child)) return false;
1564     if (is_line_break_object(*item)) return false;
1565     TextTagAttributes *attrs = attributes_for_object(child);
1566     if (attrs && attrs->anyAttributesSet()) return false;
1567     attrs = attributes_for_object(*item);
1568     if (attrs && attrs->anyAttributesSet()) return false;
1570     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1571     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1572     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1573     if (child_style && *child_style) {
1574         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1575         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1576     }
1577     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1578     if (item_style && *item_style) {
1579         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1580     }
1581     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1582     sp_repr_css_attr_unref(css_child_and_item);
1583     sp_repr_css_attr_unref(css_child_only);
1584     if (!equal) return false;
1586     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1587     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1588     if (prepend) {
1589         SPObject *prev = SP_OBJECT_PREV(*item);
1590         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1591     } else
1592         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1593     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1594     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1595     Inkscape::GC::release(new_span);
1596     child->deleteObject();
1597     return true;
1600 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1601                                 -> <font b>abc</font><font>def</font>
1602 test this by applying a colour to a region, then a different colour to
1603 a partially-overlapping region. */
1604 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1606     if (!(*item)->hasChildren()) return false;
1607     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1608     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1609         return true;
1610     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1611         return true;
1612     return false;
1615 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1616 in a paragraph which is not \a not_obj. */
1617 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1619     for (SPObject *child = root->lastChild() ; child ; child = SP_OBJECT_PREV(child))
1620     {
1621         if (child == not_obj) continue;
1622         if (child->hasChildren()) {
1623             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1624             if (ret) return ret;
1625         } else if (SP_IS_STRING(child))
1626             return SP_STRING(child);
1627     }
1628     return NULL;
1631 /** whitespace-only spans: abc<font> </font>def
1632                             -> abc<font></font> def
1633                            abc<b><i>def</i> </b>ghi
1634                             -> abc<b><i>def</i></b> ghi   */
1635 static bool tidy_operator_styled_whitespace(SPObject **item)
1637     if (!SP_IS_STRING(*item)) return false;
1638     Glib::ustring const &str = SP_STRING(*item)->string;
1639     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1640         if (!g_unichar_isspace(*it)) return false;
1642     SPObject *test_item = *item;
1643     SPString *next_string;
1644     for ( ; ; ) {  // find the next string
1645         next_string = sp_te_seek_next_string_recursive(SP_OBJECT_NEXT(test_item));
1646         if (next_string) {
1647             next_string->string.insert(0, str);
1648             break;
1649         }
1650         for ( ; ; ) {   // go up one item in the xml
1651             test_item = SP_OBJECT_PARENT(test_item);
1652             if (is_line_break_object(test_item)) break;
1653             SPObject *next = SP_OBJECT_NEXT(test_item);
1654             if (next) {
1655                 test_item = next;
1656                 break;
1657             }
1658         }
1659         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1660             next_string = find_last_string_child_not_equal_to(test_item, *item);
1661             if (next_string == NULL) return false;   // an empty paragraph
1662             next_string->string += str;
1663             break;
1664         }
1665     }
1666     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1667     SPObject *delete_obj = *item;
1668     *item = SP_OBJECT_NEXT(*item);
1669     delete_obj->deleteObject();
1670     return true;
1673 /* possible tidy operators that are not yet implemented, either because
1674 they are difficult, occur infrequently, or because I'm not sure that the
1675 output is tidier in all cases:
1676     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1677                                               -> <div italic><para>abc</para></div>
1678     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1679                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1680     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1681                           -> <size 1><font a>abc</font>def</size>
1682 */
1684 /** Recursively walks the xml tree calling a set of cleanup operations on
1685 every child. Returns true if any changes were made to the tree.
1687 All the tidy operators return true if they made changes, and alter their
1688 parameter to point to the next object that should be processed, or NULL.
1689 They must not significantly alter (ie delete) any ancestor elements of the
1690 one they are passed.
1692 It may be that some of the later tidy operators that I wrote are actually
1693 general cases of the earlier operators, and hence the special-case-only
1694 versions can be removed. I haven't analysed my work in detail to figure
1695 out if this is so. */
1696 static bool tidy_xml_tree_recursively(SPObject *root)
1698     static bool (* const tidy_operators[])(SPObject**) = {
1699         tidy_operator_empty_spans,
1700         tidy_operator_inexplicable_spans,
1701         tidy_operator_repeated_spans,
1702         tidy_operator_excessive_nesting,
1703         tidy_operator_redundant_double_nesting,
1704         tidy_operator_redundant_semi_nesting,
1705         tidy_operator_styled_whitespace
1706     };
1707     bool changes = false;
1709     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1710         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1711             child = SP_OBJECT_NEXT(child);
1712             continue;
1713         }
1714         if (child->hasChildren())
1715             changes |= tidy_xml_tree_recursively(child);
1717         unsigned i;
1718         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1719             if (tidy_operators[i](&child)) {
1720                 changes = true;
1721                 break;
1722             }
1723         }
1724         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0]))
1725             child = SP_OBJECT_NEXT(child);
1726     }
1727     return changes;
1730 /** Applies the given CSS fragment to the characters of the given text or
1731 flowtext object between \a start and \a end, creating or removing span
1732 elements as necessary and optimal. */
1733 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1735     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1736     if (start == end) return;
1737     Inkscape::Text::Layout::iterator first, last;
1738     if (start < end) {
1739         first = start;
1740         last = end;
1741     } else {
1742         first = end;
1743         last = start;
1744     }
1745     Inkscape::Text::Layout const *layout = te_get_layout(text);
1746     SPObject *start_item = 0, *end_item = 0;
1747     void *rawptr = 0;
1748     Glib::ustring::iterator start_text_iter, end_text_iter;
1749     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1750     start_item = SP_OBJECT(rawptr);
1751     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1752     end_item = SP_OBJECT(rawptr);
1753     if (start_item == 0)
1754         return;   // start is at end of text
1755     if (is_line_break_object(start_item))
1756         start_item = SP_OBJECT_NEXT(start_item);
1757     if (is_line_break_object(end_item))
1758         end_item = SP_OBJECT_NEXT(end_item);
1759     if (end_item == 0) end_item = text;
1760     
1761     
1762     /* Special case: With a tref, we only want to change its style when the whole
1763      * string is selected, in which case the style can be applied directly to the
1764      * tref node.  If only part of the tref's string child is selected, just return. */
1765      
1766     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1767         
1768         return;
1769     } 
1771     /* stage 1: applying the style. Go up to the closest common ancestor of
1772     start and end and then semi-recursively apply the style to all the
1773     objects in between. The semi-recursion is because it's only necessary
1774     at the beginning and end; the style can just be applied to the root
1775     child in the middle.
1776     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1777     The recursion may involve creating new spans.
1778     */
1779     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1780     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1781     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1782     recursively_apply_style(common_ancestor, css, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1784     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1785     /* discussion: this stage requires a certain level of inventiveness because
1786     it's not clear what the best representation is in many cases. An ideal
1787     implementation would provide some sort of scoring function to rate the
1788     ugliness of a given xml tree and try to reduce said function, but providing
1789     the various possibilities to be rated is non-trivial. Instead, I have opted
1790     for a multi-pass technique which simply recognises known-ugly patterns and
1791     has matching routines for optimising the patterns it finds. It's reasonably
1792     easy to add new pattern matching processors. If everything gets disastrous
1793     and neither option can be made to work, a fallback could be to reduce
1794     everything to a single level of nesting and drop all pretence of
1795     roundtrippability. */
1796     while (tidy_xml_tree_recursively(common_ancestor));
1798     // if we only modified subobjects this won't have been automatically sent
1799     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1802 /*
1803   Local Variables:
1804   mode:c++
1805   c-file-style:"stroustrup"
1806   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1807   indent-tabs-mode:nil
1808   fill-column:99
1809   End:
1810 */
1811 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :