Code

Translations. French translation minor update.
[inkscape.git] / src / text-editing.cpp
1 /*
2  * Parent class for text and flowtext
3  *
4  * Authors:
5  *   bulia byak
6  *   Richard Hughes
7  *   Jon A. Cruz <jon@joncruz.org>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 2004-5 authors
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
19 #include <cstring>
20 #include <string>
21 #include <glibmm/i18n.h>
23 #include "desktop.h"
24 #include "inkscape.h"
25 #include "message-stack.h"
26 #include "style.h"
27 #include "unit-constants.h"
29 #include "document.h"
30 #include "xml/repr.h"
31 #include "xml/attribute-record.h"
33 #include "sp-textpath.h"
34 #include "sp-flowtext.h"
35 #include "sp-flowdiv.h"
36 #include "sp-flowregion.h"
37 #include "sp-tref.h"
38 #include "sp-tspan.h"
40 #include "text-editing.h"
42 static const gchar *tref_edit_message = _("You cannot edit <b>cloned character data</b>.");
44 static bool tidy_xml_tree_recursively(SPObject *root);
46 Inkscape::Text::Layout const * te_get_layout (SPItem const *item)
47 {
48     if (SP_IS_TEXT(item)) {
49         return &(SP_TEXT(item)->layout);
50     } else if (SP_IS_FLOWTEXT (item)) {
51         return &(SP_FLOWTEXT(item)->layout);
52     }
53     return NULL;
54 }
56 static void te_update_layout_now (SPItem *item)
57 {
58     if (SP_IS_TEXT(item))
59         SP_TEXT(item)->rebuildLayout();
60     else if (SP_IS_FLOWTEXT (item))
61         SP_FLOWTEXT(item)->rebuildLayout();
62     item->updateRepr();
63 }
65 bool sp_te_output_is_empty(SPItem const *item)
66 {
67     Inkscape::Text::Layout const *layout = te_get_layout(item);
68     return layout->begin() == layout->end();
69 }
71 bool sp_te_input_is_empty(SPObject const *item)
72 {
73     bool empty = true;
74     if (SP_IS_STRING(item)) {
75         empty = SP_STRING(item)->string.empty();
76     } else {
77         for (SPObject const *child = item->firstChild() ; child ; child = child->getNext()) {
78             if (!sp_te_input_is_empty(child)) {
79                 empty = false;
80                 break;
81             }
82         }
83     }
84     return empty;
85 }
87 Inkscape::Text::Layout::iterator
88 sp_te_get_position_by_coords (SPItem const *item, Geom::Point const &i_p)
89 {
90     Geom::Matrix im (item->i2d_affine ());
91     im = im.inverse();
93     Geom::Point p = i_p * im;
94     Inkscape::Text::Layout const *layout = te_get_layout(item);
95     return layout->getNearestCursorPositionTo(p);
96 }
98 std::vector<Geom::Point> sp_te_create_selection_quads(SPItem const *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, Geom::Matrix const &transform)
99 {
100     if (start == end)
101         return std::vector<Geom::Point>();
102     Inkscape::Text::Layout const *layout = te_get_layout(item);
103     if (layout == NULL)
104         return std::vector<Geom::Point>();
106     return layout->createSelectionShape(start, end, transform);
109 void
110 sp_te_get_cursor_coords (SPItem const *item, Inkscape::Text::Layout::iterator const &position, Geom::Point &p0, Geom::Point &p1)
112     Inkscape::Text::Layout const *layout = te_get_layout(item);
113     double height, rotation;
114     layout->queryCursorShape(position, p0, height, rotation);
115     p1 = Geom::Point(p0[Geom::X] + height * sin(rotation), p0[Geom::Y] - height * cos(rotation));
118 SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
120     SPObject const *pos_obj = sp_te_object_at_position(text, position);
121     if (pos_obj)
122         return SP_OBJECT_STYLE(pos_obj);
123     return NULL;
126 SPObject const * sp_te_object_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
128     Inkscape::Text::Layout const *layout = te_get_layout(text);
129     if (layout == NULL)
130         return NULL;
131     SPObject const *pos_obj = 0;
132     void *rawptr = 0;
133     layout->getSourceOfCharacter(position, &rawptr);
134     pos_obj = SP_OBJECT(rawptr);
135     if (pos_obj == 0) pos_obj = text;
136     while (SP_OBJECT_STYLE(pos_obj) == NULL)
137         pos_obj = SP_OBJECT_PARENT(pos_obj);   // not interested in SPStrings 
138     return pos_obj;
141 /*
142  * for debugging input
143  *
144 char * dump_hexy(const gchar * utf8)
146     static char buffer[1024];
148     buffer[0]='\0';
149     for (const char *ptr=utf8; *ptr; ptr++) {
150         sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
151     }
152     return buffer;
154 */
156 Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
158     iterator_pair pair;
159     sp_te_delete(item, start, end, pair);
160     return sp_te_insert(item, pair.first, utf8);
164 /* ***************************************************************************************************/
165 //                             I N S E R T I N G   T E X T
167 static bool is_line_break_object(SPObject const *object)
169     bool is_line_break = false;
170     
171     if (object) {
172         if (SP_IS_TEXT(object)
173                 || (SP_IS_TSPAN(object) && SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
174                 || SP_IS_TEXTPATH(object)
175                 || SP_IS_FLOWDIV(object)
176                 || SP_IS_FLOWPARA(object)
177                 || SP_IS_FLOWLINE(object)
178                 || SP_IS_FLOWREGIONBREAK(object)) {
179                     
180             is_line_break = true;
181         }
182     }
183     
184     return is_line_break;
187 /** returns the attributes for an object, or NULL if it isn't a text,
188 tspan, tref, or textpath. */
189 static TextTagAttributes* attributes_for_object(SPObject *object)
191     if (SP_IS_TSPAN(object))
192         return &SP_TSPAN(object)->attributes;
193     if (SP_IS_TEXT(object))
194         return &SP_TEXT(object)->attributes;
195     if (SP_IS_TREF(object))
196         return &SP_TREF(object)->attributes;
197     if (SP_IS_TEXTPATH(object))
198         return &SP_TEXTPATH(object)->attributes;
199     return NULL;
202 static const char * span_name_for_text_object(SPObject const *object)
204     if (SP_IS_TEXT(object)) return "svg:tspan";
205     else if (SP_IS_FLOWTEXT(object)) return "svg:flowSpan";
206     return NULL;
209 unsigned sp_text_get_length(SPObject const *item)
211     unsigned length = 0;
213     if (SP_IS_STRING(item)) {
214         length = SP_STRING(item)->string.length();
215     } else {
216         if (is_line_break_object(item)) {
217             length++;
218         }
219     
220         for (SPObject const *child = item->firstChild() ; child ; child = child->getNext()) {
221             if (SP_IS_STRING(child)) {
222                 length += SP_STRING(child)->string.length();
223             } else {
224                 length += sp_text_get_length(child);
225             }
226         }
227     }
228     
229     return length;
232 unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
234     unsigned length = 0;
236     // The string is the lowest level and the length can be counted directly. 
237     if (SP_IS_STRING(item)) {
238         return SP_STRING(item)->string.length();
239     }
240     
241     // Take care of new lines...
242     if (is_line_break_object(item) && !SP_IS_TEXT(item)) {
243         if (item != SP_OBJECT_PARENT(item)->firstChild()) {
244             // add 1 for each newline
245             length++;
246         }
247     }
248     
249     // Count the length of the children
250     for (SPObject const *child = item->firstChild() ; child ; child = child->getNext()) {
251         if (upto && child == upto) {
252             // hit upto, return immediately
253             return length;
254         }
255         if (SP_IS_STRING(child)) {
256             length += SP_STRING(child)->string.length();
257         }
258         else {
259             if (upto && child->isAncestorOf(upto)) {
260                 // upto is below us, recurse and break loop
261                 length += sp_text_get_length_upto(child, upto);
262                 return length;
263             } else {
264                 // recurse and go to the next sibling
265                 length += sp_text_get_length_upto(child, upto);
266             }
267         }
268     }
269     return length;
272 static Inkscape::XML::Node* duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
274     switch (old_node->type()) {
275         case Inkscape::XML::ELEMENT_NODE: {
276             Inkscape::XML::Node *new_node = xml_doc->createElement(old_node->name());
277             Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attributes = old_node->attributeList();
278             GQuark const id_key = g_quark_from_string("id");
279             for ( ; attributes ; attributes++) {
280                 if (attributes->key == id_key) continue;
281                 new_node->setAttribute(g_quark_to_string(attributes->key), attributes->value);
282             }
283             return new_node;
284         }
286         case Inkscape::XML::TEXT_NODE:
287             return xml_doc->createTextNode(old_node->content());
289         case Inkscape::XML::COMMENT_NODE:
290             return xml_doc->createComment(old_node->content());
292         case Inkscape::XML::PI_NODE:
293             return xml_doc->createPI(old_node->name(), old_node->content());
295         case Inkscape::XML::DOCUMENT_NODE:
296             return NULL;   // this had better never happen
297     }
298     return NULL;
301 /** returns the sum of the (recursive) lengths of all the SPStrings prior
302 to \a item at the same level. */
303 static unsigned sum_sibling_text_lengths_before(SPObject const *item)
305     unsigned char_index = 0;
306     for (SPObject *sibling = SP_OBJECT_PARENT(item)->firstChild() ; sibling && sibling != item ; sibling = sibling->getNext()) {
307         char_index += sp_text_get_length(sibling);
308     }
309     return char_index;
312 /** splits the attributes for the first object at the given \a char_index
313 and moves the ones after that point into \a second_item. */
314 static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
316     TextTagAttributes *first_attrs = attributes_for_object(first_item);
317     TextTagAttributes *second_attrs = attributes_for_object(second_item);
318     if (first_attrs && second_attrs)
319         first_attrs->split(char_index, second_attrs);
322 /** recursively divides the XML node tree into two objects: the original will
323 contain all objects up to and including \a split_obj and the returned value
324 will be the new leaf which represents the copy of \a split_obj and extends
325 down the tree with new elements all the way to the common root which is the
326 parent of the first line break node encountered.
327 */
328 static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
330     Inkscape::XML::Document *xml_doc = SP_OBJECT_DOCUMENT(split_obj)->getReprDoc();
331     if (is_line_break_object(split_obj)) {
332         Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
333         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
334         Inkscape::GC::release(new_node);
335         split_attributes(split_obj, split_obj->getNext(), char_index);
336         return split_obj->getNext();
337     }
339     unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
340     SPObject *duplicate_obj = split_text_object_tree_at(SP_OBJECT_PARENT(split_obj), char_index + char_count_before);
341     // copy the split node
342     Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
343     SP_OBJECT_REPR(duplicate_obj)->appendChild(new_node);
344     Inkscape::GC::release(new_node);
346     // sort out the copied attributes (x/y/dx/dy/rotate)
347     split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
349     // then move all the subsequent nodes
350     split_obj = split_obj->getNext();
351     while (split_obj) {
352         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(split_obj);
353         SPObject *next_obj = split_obj->getNext();  // this is about to become invalidated by removeChild()
354         Inkscape::GC::anchor(move_repr);
355         SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->removeChild(move_repr);
356         SP_OBJECT_REPR(duplicate_obj)->appendChild(move_repr);
357         Inkscape::GC::release(move_repr);
359         split_obj = next_obj;
360     }
361     return duplicate_obj->firstChild();
364 /** inserts a new line break at the given position in a text or flowtext
365 object. If the position is in the middle of a span, the XML tree must be
366 chopped in two such that the line can be created at the root of the text
367 element. Returns an iterator pointing just after the inserted break. */
368 Inkscape::Text::Layout::iterator sp_te_insert_line (SPItem *item, Inkscape::Text::Layout::iterator const &position)
370     // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
371     // texpaths attached to the same path, with a vertical shift
372     if (SP_IS_TEXT_TEXTPATH (item) || SP_IS_TREF(item))
373         return position;
374         
375     SPDesktop *desktop = SP_ACTIVE_DESKTOP; 
377     Inkscape::Text::Layout const *layout = te_get_layout(item);
378     SPObject *split_obj = 0;
379     Glib::ustring::iterator split_text_iter;
380     if (position != layout->end()) {
381         void *rawptr = 0;
382         layout->getSourceOfCharacter(position, &rawptr, &split_text_iter);
383         split_obj = SP_OBJECT(rawptr);
384     }
386     if (split_obj == 0 || is_line_break_object(split_obj)) {
387         if (split_obj == 0) split_obj = item->lastChild();
388         
389         if (SP_IS_TREF(split_obj)) {
390             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
391             return position;
392         }
393         
394         if (split_obj) {
395             Inkscape::XML::Document *xml_doc = SP_OBJECT_DOCUMENT(split_obj)->getReprDoc();
396             Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, SP_OBJECT_REPR(split_obj));
397             SP_OBJECT_REPR(SP_OBJECT_PARENT(split_obj))->addChild(new_node, SP_OBJECT_REPR(split_obj));
398             Inkscape::GC::release(new_node);
399         }
400     } else if (SP_IS_STRING(split_obj)) {
401         // If the parent is a tref, editing on this particular string is disallowed.
402         if (SP_IS_TREF(SP_OBJECT_PARENT(split_obj))) {
403             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
404             return position;
405         }
406         
407         Glib::ustring *string = &SP_STRING(split_obj)->string;
408         unsigned char_index = 0;
409         for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; it++)
410             char_index++;
411         // we need to split the entire text tree into two
412         SPString *new_string = SP_STRING(split_text_object_tree_at(split_obj, char_index));
413         SP_OBJECT_REPR(new_string)->setContent(&*split_text_iter.base());   // a little ugly
414         string->erase(split_text_iter, string->end());
415         SP_OBJECT_REPR(split_obj)->setContent(string->c_str());
416         // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
417     } else {
418         // TODO
419         // I think the only case to put here is arbitrary gaps, which nobody uses yet
420     }
421     item->updateRepr();
422     unsigned char_index = layout->iteratorToCharIndex(position);
423     te_update_layout_now(item);
424     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
425     return layout->charIndexToIterator(char_index + 1);
428 /** finds the first SPString after the given position, including children, excluding parents */
429 static SPString* sp_te_seek_next_string_recursive(SPObject *start_obj)
431     while (start_obj) {
432         if (start_obj->hasChildren()) {
433             SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
434             if (found_string) {
435                 return found_string;
436             }
437         }
438         if (SP_IS_STRING(start_obj)) {
439             return SP_STRING(start_obj);
440         }
441         start_obj = start_obj->getNext();
442         if (is_line_break_object(start_obj)) {
443             break;   // don't cross line breaks
444         }
445     }
446     return NULL;
449 /** inserts the given characters into the given string and inserts
450 corresponding new x/y/dx/dy/rotate attributes into all its parents. */
451 static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
453     unsigned char_index = 0;
454     unsigned char_count = g_utf8_strlen(utf8, -1);
455     Glib::ustring *string = &SP_STRING(string_item)->string;
457     for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; it++)
458         char_index++;
459     string->replace(iter_at, iter_at, utf8);
461     SPObject *parent_item = string_item;
462     for ( ; ; ) {
463         char_index += sum_sibling_text_lengths_before(parent_item);
464         parent_item = SP_OBJECT_PARENT(parent_item);
465         TextTagAttributes *attributes = attributes_for_object(parent_item);
466         if (!attributes) break;
467         attributes->insert(char_index, char_count);
468     }
471 /** Inserts the given text into a text or flowroot object. Line breaks
472 cannot be inserted using this function, see sp_te_insert_line(). Returns
473 an iterator pointing just after the inserted text. */
474 Inkscape::Text::Layout::iterator
475 sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
477     if (!g_utf8_validate(utf8,-1,NULL)) {
478         g_warning("Trying to insert invalid utf8");
479         return position;
480     }
481     
482     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
484     Inkscape::Text::Layout const *layout = te_get_layout(item);
485     SPObject *source_obj = 0;
486     void *rawptr = 0;
487     Glib::ustring::iterator iter_text;
488     // we want to insert after the previous char, not before the current char.
489     // it makes a difference at span boundaries
490     Inkscape::Text::Layout::iterator it_prev_char = position;
491     bool cursor_at_start = !it_prev_char.prevCharacter();
492     bool cursor_at_end = position == layout->end();
493     layout->getSourceOfCharacter(it_prev_char, &rawptr, &iter_text);
494     source_obj = SP_OBJECT(rawptr);
495     if (SP_IS_STRING(source_obj)) {
496         // If the parent is a tref, editing on this particular string is disallowed.
497         if (SP_IS_TREF(SP_OBJECT_PARENT(source_obj))) {
498             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
499             return position;
500         }
501         
502         // Now the simple case can begin...
503         if (!cursor_at_start) iter_text++;
504         SPString *string_item = SP_STRING(source_obj);
505         insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
506     } else {
507         // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
508         Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
509         if (cursor_at_start) {
510             source_obj = item;
511             if (source_obj->hasChildren()) {
512                 source_obj = source_obj->firstChild();
513                 if (SP_IS_FLOWTEXT(item)) {
514                     while (SP_IS_FLOWREGION(source_obj) || SP_IS_FLOWREGIONEXCLUDE(source_obj)) {
515                         source_obj = source_obj->getNext();
516                     }
517                     if (source_obj == NULL) {
518                         source_obj = item;
519                     }
520                 }
521             }
522             if (source_obj == item && SP_IS_FLOWTEXT(item)) {
523                 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
524                 SP_OBJECT_REPR(item)->appendChild(para);
525                 source_obj = item->lastChild();
526             }
527         } else
528             source_obj = source_obj->getNext();
530         if (source_obj) {  // never fails
531             SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
532             if (string_item == NULL) {
533                 // need to add an SPString in this (pathological) case
534                 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
535                 SP_OBJECT_REPR(source_obj)->addChild(rstring, NULL);
536                 Inkscape::GC::release(rstring);
537                 g_assert(SP_IS_STRING(source_obj->firstChild()));
538                 string_item = SP_STRING(source_obj->firstChild());
539             }
540             // If the parent is a tref, editing on this particular string is disallowed.
541             if (SP_IS_TREF(SP_OBJECT_PARENT(string_item))) {
542                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
543                 return position;
544             }
545             
546             insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
547         }
548     }
550     item->updateRepr();
551     unsigned char_index = layout->iteratorToCharIndex(position);
552     te_update_layout_now(item);
553     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
554     return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
558 /* ***************************************************************************************************/
559 //                            D E L E T I N G   T E X T
561 /** moves all the children of \a from_repr to \a to_repr, either before
562 the existing children or after them. Order is maintained. The empty
563 \a from_repr is not deleted. */
564 static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false)
566     while (from_repr->childCount()) {
567         Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
568         Inkscape::GC::anchor(child);
569         from_repr->removeChild(child);
570         if (prepend) to_repr->addChild(child, NULL);
571         else to_repr->appendChild(child);
572         Inkscape::GC::release(child);
573     }
576 /** returns the object in the tree which is the closest ancestor of both
577 \a one and \a two. It will never return anything higher than \a text. */
578 static SPObject* get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
580     if (one == NULL || two == NULL)
581         return text;
582     SPObject *common_ancestor = one;
583     if (SP_IS_STRING(common_ancestor))
584         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
585     while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
586         g_assert(common_ancestor != text);
587         common_ancestor = SP_OBJECT_PARENT(common_ancestor);
588     }
589     return common_ancestor;
592 /** positions \a para_obj and \a text_iter to be pointing at the end
593 of the last string in the last leaf object of \a para_obj. If the last
594 leaf is not an SPString then \a text_iter will be unchanged. */
595 static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
597     while ((*para_obj)->hasChildren())
598         *para_obj = (*para_obj)->lastChild();
599     if (SP_IS_STRING(*para_obj))
600         *text_iter = SP_STRING(*para_obj)->string.end();
603 /** delete the line break pointed to by \a item by merging its children into
604 the next suitable object and deleting \a item. Returns the object after the
605 ones that have just been moved and sets \a next_is_sibling accordingly. */
606 static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
608     Inkscape::XML::Node *this_repr = SP_OBJECT_REPR(item);
609     SPObject *next_item = NULL;
610     unsigned moved_char_count = sp_text_get_length(item) - 1;   // the -1 is because it's going to count the line break
612     /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
613       <div></div><p>*text</p>
614       <p><div></div>*text</p>
615       <p><div></div></p><p>*text</p>
616     */
617     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
618     Inkscape::XML::Node *new_span_repr = xml_doc->createElement(span_name_for_text_object(root));
620     if (gchar const *a = this_repr->attribute("dx"))
621         new_span_repr->setAttribute("dx", a);
622     if (gchar const *a = this_repr->attribute("dy"))
623         new_span_repr->setAttribute("dy", a);
624     if (gchar const *a = this_repr->attribute("rotate"))
625         new_span_repr->setAttribute("rotate", a);
627     SPObject *following_item = item;
628     while (following_item->getNext() == NULL) {
629         following_item = SP_OBJECT_PARENT(following_item);
630         g_assert(following_item != root);
631     }
632     following_item = following_item->getNext();
634     SPObject *new_parent_item;
635     if (SP_IS_STRING(following_item)) {
636         new_parent_item = SP_OBJECT_PARENT(following_item);
637         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, following_item->getPrev() ? SP_OBJECT_REPR(following_item->getPrev()) : NULL);
638         next_item = following_item;
639         *next_is_sibling = true;
640     } else {
641         new_parent_item = following_item;
642         next_item = new_parent_item->firstChild();
643         *next_is_sibling = true;
644         if (next_item == NULL) {
645             next_item = new_parent_item;
646             *next_is_sibling = false;
647         }
648         SP_OBJECT_REPR(new_parent_item)->addChild(new_span_repr, NULL);
649     }
651     // work around a bug in sp_style_write_difference() which causes the difference
652     // not to be written if the second param has a style set which the first does not
653     // by causing the first param to have everything set
654     SPCSSAttr *dest_node_attrs = sp_repr_css_attr(SP_OBJECT_REPR(new_parent_item), "style");
655     SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
656     SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
657     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = dest_node_attrs->attributeList();
658     for ( ; attrs ; attrs++) {
659         gchar const *key = g_quark_to_string(attrs->key);
660         gchar const *this_attr = this_node_attrs_inherited->attribute(key);
661         if ((this_attr == NULL || strcmp(attrs->value, this_attr)) && this_node_attrs->attribute(key) == NULL)
662             this_node_attrs->setAttribute(key, this_attr);
663     }
664     sp_repr_css_attr_unref(this_node_attrs_inherited);
665     sp_repr_css_attr_unref(this_node_attrs);
666     sp_repr_css_attr_unref(dest_node_attrs);
667     sp_repr_css_change(new_span_repr, this_node_attrs, "style");
669     TextTagAttributes *attributes = attributes_for_object(new_parent_item);
670     if (attributes)
671         attributes->insert(0, moved_char_count);
672     move_child_nodes(this_repr, new_span_repr);
673     this_repr->parent()->removeChild(this_repr);
674     return next_item;
677 /** erases the given characters from the given string and deletes the
678 corresponding x/y/dx/dy/rotate attributes from all its parents. */
679 static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
681     unsigned char_index = 0;
682     unsigned char_count = 0;
683     Glib::ustring *string = &SP_STRING(string_item)->string;
685     for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; it++)
686         char_index++;
687     for (Glib::ustring::iterator it = iter_from ; it != iter_to ; it++)
688         char_count++;
689     string->erase(iter_from, iter_to);
690     SP_OBJECT_REPR(string_item)->setContent(string->c_str());
692     SPObject *parent_item = string_item;
693     for ( ; ; ) {
694         char_index += sum_sibling_text_lengths_before(parent_item);
695         parent_item = SP_OBJECT_PARENT(parent_item);
696         TextTagAttributes *attributes = attributes_for_object(parent_item);
697         if (attributes == NULL) break;
699         attributes->erase(char_index, char_count);
700         attributes->writeTo(SP_OBJECT_REPR(parent_item));
701     }
704 /* Deletes the given characters from a text or flowroot object. This is
705 quite a complicated operation, partly due to the cleanup that is done if all
706 the text in a subobject has been deleted, and partly due to the difficulty
707 of figuring out what is a line break and how to delete one. Returns the
708 real start and ending iterators based on the situation. */
709 bool
710 sp_te_delete (SPItem *item, Inkscape::Text::Layout::iterator const &start,
711               Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
713     bool success = false;
715     iter_pair.first = start;
716     iter_pair.second = end;
717     
718     if (start == end) return success;
719     
720     if (start > end) {
721         iter_pair.first = end;
722         iter_pair.second = start;
723     }
724     
725     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
726     
727     Inkscape::Text::Layout const *layout = te_get_layout(item);
728     SPObject *start_item = 0, *end_item = 0;
729     void *rawptr = 0;
730     Glib::ustring::iterator start_text_iter, end_text_iter;
731     layout->getSourceOfCharacter(iter_pair.first, &rawptr, &start_text_iter);
732     start_item = SP_OBJECT(rawptr);
733     layout->getSourceOfCharacter(iter_pair.second, &rawptr, &end_text_iter);
734     end_item = SP_OBJECT(rawptr);
735     if (start_item == 0)
736         return success;   // start is at end of text
737     if (is_line_break_object(start_item))
738         move_to_end_of_paragraph(&start_item, &start_text_iter);
739     if (end_item == 0) {
740         end_item = item->lastChild();
741         move_to_end_of_paragraph(&end_item, &end_text_iter);
742     }
743     else if (is_line_break_object(end_item))
744         move_to_end_of_paragraph(&end_item, &end_text_iter);
746     SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
748     if (start_item == end_item) {
749         // the quick case where we're deleting stuff all from the same string
750         if (SP_IS_STRING(start_item)) {     // always true (if it_start != it_end anyway)
751             // If the parent is a tref, editing on this particular string is disallowed.
752             if (SP_IS_TREF(SP_OBJECT_PARENT(start_item))) {
753                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
754             } else {
755                 erase_from_spstring(SP_STRING(start_item), start_text_iter, end_text_iter);
756                 success = true;
757             }
758         }
759     } else {
760         SPObject *sub_item = start_item;
761         // walk the tree from start_item to end_item, deleting as we go
762         while (sub_item != item) {
763             if (sub_item == end_item) {
764                 if (SP_IS_STRING(sub_item)) {
765                     // If the parent is a tref, editing on this particular string is disallowed.
766                     if (SP_IS_TREF(SP_OBJECT_PARENT(sub_item))) {
767                         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, tref_edit_message);
768                         break;
769                     }
770             
771                     Glib::ustring *string = &SP_STRING(sub_item)->string;
772                     erase_from_spstring(SP_STRING(sub_item), string->begin(), end_text_iter);
773                     success = true;
774                 }
775                 break;
776             }
777             if (SP_IS_STRING(sub_item)) {
778                 SPString *string = SP_STRING(sub_item);
779                 if (sub_item == start_item)
780                     erase_from_spstring(string, start_text_iter, string->string.end());
781                 else
782                     erase_from_spstring(string, string->string.begin(), string->string.end());
783                 success = true;
784             }
785             // walk to the next item in the tree
786             if (sub_item->hasChildren())
787                 sub_item = sub_item->firstChild();
788             else {
789                 SPObject *next_item;
790                 do {
791                     bool is_sibling = true;
792                     next_item = sub_item->getNext();
793                     if (next_item == NULL) {
794                         next_item = SP_OBJECT_PARENT(sub_item);
795                         is_sibling = false;
796                     }
798                     if (is_line_break_object(sub_item))
799                         next_item = delete_line_break(item, sub_item, &is_sibling);
801                     sub_item = next_item;
802                     if (is_sibling) break;
803                     // no more siblings, go up a parent
804                 } while (sub_item != item && sub_item != end_item);
805             }
806         }
807     }
809     while (tidy_xml_tree_recursively(common_ancestor)){};
810     te_update_layout_now(item);
811     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
812     layout->validateIterator(&iter_pair.first);
813     layout->validateIterator(&iter_pair.second);
814     return success;
818 /* ***************************************************************************************************/
819 //                            P L A I N   T E X T   F U N C T I O N S
821 /** Gets a text-only representation of the given text or flowroot object,
822 replacing line break elements with '\n'. */
823 static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
825     if (*pending_line_break) {
826         *string += '\n';
827     }
828     for (SPObject const *child = root->firstChild() ; child ; child = child->getNext()) {
829         if (SP_IS_STRING(child)) {
830             *string += SP_STRING(child)->string;
831         } else {
832             sp_te_get_ustring_multiline(child, string, pending_line_break);
833         }
834     }
835     if (!SP_IS_TEXT(root) && !SP_IS_TEXTPATH(root) && is_line_break_object(root)) {
836         *pending_line_break = true;
837     }
840 /** Gets a text-only representation of the given text or flowroot object,
841 replacing line break elements with '\n'. The return value must be free()d. */
842 gchar *
843 sp_te_get_string_multiline (SPItem const *text)
845     Glib::ustring string;
846     bool pending_line_break = false;
848     if (!SP_IS_TEXT(text) && !SP_IS_FLOWTEXT(text)) return NULL;
849     sp_te_get_ustring_multiline(text, &string, &pending_line_break);
850     if (string.empty()) return NULL;
851     return strdup(string.data());
854 /** Gets a text-only representation of the characters in a text or flowroot
855 object from \a start to \a end only. Line break elements are replaced with
856 '\n'. */
857 Glib::ustring
858 sp_te_get_string_multiline (SPItem const *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end)
860     if (start == end) return "";
861     Inkscape::Text::Layout::iterator first, last;
862     if (start < end) {
863         first = start;
864         last = end;
865     } else {
866         first = end;
867         last = start;
868     }
869     Inkscape::Text::Layout const *layout = te_get_layout(text);
870     Glib::ustring result;
871     // not a particularly fast piece of code. I'll optimise it if people start to notice.
872     for ( ; first < last ; first.nextCharacter()) {
873         SPObject *char_item = 0;
874         void *rawptr = 0;
875         Glib::ustring::iterator text_iter;
876         layout->getSourceOfCharacter(first, &rawptr, &text_iter);
877         char_item = SP_OBJECT(rawptr);
878         if (SP_IS_STRING(char_item))
879             result += *text_iter;
880         else
881             result += '\n';
882     }
883     return result;
886 void
887 sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
889     g_return_if_fail (text != NULL);
890     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
892     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(text)->document();
893     Inkscape::XML::Node *repr;
894     SPObject *object;
895     bool is_textpath = false;
896     if (SP_IS_TEXT_TEXTPATH (text)) {
897         repr = SP_OBJECT_REPR(text->firstChild());
898         object = text->firstChild();
899         is_textpath = true;
900     } else {
901         repr = SP_OBJECT_REPR (text);
902         object = SP_OBJECT (text);
903     }
905     if (!str) str = "";
906     gchar *content = g_strdup (str);
908     repr->setContent("");
909     SPObject *child = object->firstChild();
910     while (child) {
911         SPObject *next = child->getNext();
912         if (!SP_IS_FLOWREGION(child) && !SP_IS_FLOWREGIONEXCLUDE(child)) {
913             repr->removeChild(SP_OBJECT_REPR(child));
914         }
915         child = next;
916     }
918     gchar *p = content;
919     while (p) {
920         gchar *e = strchr (p, '\n');
921         if (is_textpath) {
922             if (e) *e = ' '; // no lines for textpath, replace newlines with spaces
923         } else {
924             if (e) *e = '\0';
925             Inkscape::XML::Node *rtspan;
926             if (SP_IS_TEXT(text)) { // create a tspan for each line
927                 rtspan = xml_doc->createElement("svg:tspan");
928                 rtspan->setAttribute("sodipodi:role", "line");
929             } else { // create a flowPara for each line
930                 rtspan = xml_doc->createElement("svg:flowPara");
931             }
932             Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
933             rtspan->addChild(rstr, NULL);
934             Inkscape::GC::release(rstr);
935             repr->appendChild(rtspan);
936             Inkscape::GC::release(rtspan);
937         }
938         p = (e) ? e + 1 : NULL;
939     }
940     if (is_textpath) {
941         Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
942         repr->addChild(rstr, NULL);
943         Inkscape::GC::release(rstr);
944     }
946     g_free (content);
949 /* ***************************************************************************************************/
950 //                           K E R N I N G   A N D   S P A C I N G
952 /** Returns the attributes block and the character index within that block
953 which represents the iterator \a position. */
954 TextTagAttributes*
955 text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
957     if (item == NULL || char_index == NULL || !SP_IS_TEXT(item))
958         return NULL;   // flowtext doesn't support kerning yet
959     SPText *text = SP_TEXT(item);
961     SPObject *source_item = 0;
962     void *rawptr = 0;
963     Glib::ustring::iterator source_text_iter;
964     text->layout.getSourceOfCharacter(position, &rawptr, &source_text_iter);
965     source_item = SP_OBJECT(rawptr);
967     if (!SP_IS_STRING(source_item)) return NULL;
968     Glib::ustring *string = &SP_STRING(source_item)->string;
969     *char_index = sum_sibling_text_lengths_before(source_item);
970     for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; it++)
971         ++*char_index;
973     return attributes_for_object(SP_OBJECT_PARENT(source_item));
976 void
977 sp_te_adjust_kerning_screen (SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, Geom::Point by)
979     // divide increment by zoom
980     // divide increment by matrix expansion
981     gdouble factor = 1 / desktop->current_zoom();
982     Geom::Matrix t (item->i2doc_affine());
983     factor = factor / t.descrim();
984     by = factor * by;
986     unsigned char_index;
987     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
988     if (attributes) attributes->addToDxDy(char_index, by);
989     if (start != end) {
990         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
991         if (attributes) attributes->addToDxDy(char_index, -by);
992     }
994     item->updateRepr();
995     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
998 void sp_te_adjust_dx(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop * /*desktop*/, double delta)
1000     unsigned char_index = 0;
1001     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
1002     if (attributes) {
1003         attributes->addToDx(char_index, delta);
1004     }
1005     if (start != end) {
1006         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1007         if (attributes) {
1008             attributes->addToDx(char_index, -delta);
1009         }
1010     }
1012     item->updateRepr();
1013     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1016 void sp_te_adjust_dy(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop * /*desktop*/, double delta)
1018     unsigned char_index = 0;
1019     TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
1020     if (attributes) {
1021         attributes->addToDy(char_index, delta);
1022     }
1023     if (start != end) {
1024         attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1025         if (attributes) {
1026             attributes->addToDy(char_index, -delta);
1027         }
1028     }
1030     item->updateRepr();
1031     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1034 void
1035 sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
1037     // divide increment by zoom
1038     // divide increment by matrix expansion
1039     gdouble factor = 1 / desktop->current_zoom();
1040     Geom::Matrix t (text->i2doc_affine());
1041     factor = factor / t.descrim();
1042     Inkscape::Text::Layout const *layout = te_get_layout(text);
1043     if (layout == NULL) return;
1044     SPObject *source_item = 0;
1045     void *rawptr = 0;
1046     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1047     source_item = SP_OBJECT(rawptr);
1048     if (source_item == 0) return;
1049     gdouble degrees = (180/M_PI) * atan2(pixels, SP_OBJECT_PARENT(source_item)->style->font_size.computed / factor);
1051     sp_te_adjust_rotation(text, start, end, desktop, degrees);
1054 void
1055 sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1057     unsigned char_index;
1058     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1059     if (attributes == NULL) return;
1061     if (start != end) {
1062         for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1063             attributes = text_tag_attributes_at_position(text, it, &char_index);
1064             if (attributes) attributes->addToRotate(char_index, degrees);
1065         }
1066     } else
1067         attributes->addToRotate(char_index, degrees);
1069     text->updateRepr();
1070     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1073 void sp_te_set_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop */*desktop*/, gdouble degrees)
1075     unsigned char_index = 0;
1076     TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1077     if (attributes != NULL) {
1078         if (start != end) {
1079             for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1080                 attributes = text_tag_attributes_at_position(text, it, &char_index);
1081                 if (attributes) {
1082                     attributes->setRotate(char_index, degrees);
1083                 }
1084             }
1085         } else {
1086             attributes->setRotate(char_index, degrees);
1087         }
1089         text->updateRepr();
1090         text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1091     }
1094 void
1095 sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
1097     g_return_if_fail (text != NULL);
1098     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1100     Inkscape::Text::Layout const *layout = te_get_layout(text);
1102     gdouble val;
1103     SPObject *source_obj = 0;
1104     void *rawptr = 0;
1105     unsigned nb_let;
1106     layout->getSourceOfCharacter(std::min(start, end), &rawptr);
1107     source_obj = SP_OBJECT(rawptr);
1109     if (source_obj == 0) {   // end of text
1110         source_obj = text->lastChild();
1111     }
1112     if (SP_IS_STRING(source_obj)) {
1113         source_obj = source_obj->parent;
1114     }
1116     SPStyle *style = SP_OBJECT_STYLE (source_obj);
1118     // calculate real value
1119     /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1120        get rid of the `else val = 0.0'.  Similarly below and in sp-string.cpp. */
1121     if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1122         if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1123             val = style->font_size.computed * style->letter_spacing.value;
1124         } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1125             val = style->font_size.computed * style->letter_spacing.value * 0.5;
1126         } else { // unknown unit - should not happen
1127             val = 0.0;
1128         }
1129     } else { // there's a real value in .computed, or it's zero
1130         val = style->letter_spacing.computed;
1131     }
1133     if (start == end) {
1134         while (!is_line_break_object(source_obj))     // move up the tree so we apply to the closest paragraph
1135             source_obj = SP_OBJECT_PARENT(source_obj);
1136         nb_let = sp_text_get_length(source_obj);
1137     } else {
1138         nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1139     }
1141     // divide increment by zoom and by the number of characters in the line,
1142     // so that the entire line is expanded by by pixels, no matter what its length
1143     gdouble const zoom = desktop->current_zoom();
1144     gdouble const zby = (by
1145                          / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1146                          / to_2geom(SP_ITEM(source_obj)->i2doc_affine()).descrim());
1147     val += zby;
1149     if (start == end) {
1150         // set back value to entire paragraph
1151         style->letter_spacing.normal = FALSE;
1152         if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1153             if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1154                 style->letter_spacing.value = val / style->font_size.computed;
1155             } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1156                 style->letter_spacing.value = val / style->font_size.computed * 2;
1157             }
1158         } else {
1159             style->letter_spacing.computed = val;
1160         }
1162         style->letter_spacing.set = TRUE;
1163     } else {
1164         // apply to selection only
1165         SPCSSAttr *css = sp_repr_css_attr_new();
1166         char string_val[40];
1167         g_snprintf(string_val, sizeof(string_val), "%f", val);
1168         sp_repr_css_set_property(css, "letter-spacing", string_val);
1169         sp_te_apply_style(text, start, end, css);
1170         sp_repr_css_attr_unref(css);
1171     }
1173     text->updateRepr();
1174     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1177 double
1178 sp_te_get_average_linespacing (SPItem *text)
1180     Inkscape::Text::Layout const *layout = te_get_layout(text);
1181     if (!layout)
1182         return 0;
1184     unsigned line_count = layout->lineIndex(layout->end());
1185     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1186     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1187     return average_line_height;
1190 void
1191 sp_te_adjust_linespacing_screen (SPItem *text, Inkscape::Text::Layout::iterator const &/*start*/, Inkscape::Text::Layout::iterator const &/*end*/, SPDesktop *desktop, gdouble by)
1193     // TODO: use start and end iterators to delineate the area to be affected
1194     g_return_if_fail (text != NULL);
1195     g_return_if_fail (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text));
1197     Inkscape::Text::Layout const *layout = te_get_layout(text);
1198     SPStyle *style = SP_OBJECT_STYLE (text);
1200     if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1201         style->line_height.set = TRUE;
1202         style->line_height.inherit = FALSE;
1203         style->line_height.normal = FALSE;
1204         style->line_height.unit = SP_CSS_UNIT_PERCENT;
1205         style->line_height.value = style->line_height.computed = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
1206     }
1208     unsigned line_count = layout->lineIndex(layout->end());
1209     double all_lines_height = layout->characterAnchorPoint(layout->end())[Geom::Y] - layout->characterAnchorPoint(layout->begin())[Geom::Y];
1210     double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1211     if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1213     // divide increment by zoom and by the number of lines,
1214     // so that the entire object is expanded by by pixels
1215     gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1217     // divide increment by matrix expansion
1218     Geom::Matrix t (SP_ITEM(text)->i2doc_affine ());
1219     zby = zby / t.descrim();
1221     switch (style->line_height.unit) {
1222         case SP_CSS_UNIT_NONE:
1223         default:
1224             // multiplier-type units, stored in computed
1225             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
1226             else style->line_height.computed *= (average_line_height + zby) / average_line_height;
1227             style->line_height.value = style->line_height.computed;
1228             break;
1229         case SP_CSS_UNIT_EM:
1230         case SP_CSS_UNIT_EX:
1231         case SP_CSS_UNIT_PERCENT:
1232             // multiplier-type units, stored in value
1233             if (fabs(style->line_height.value) < 0.001) style->line_height.value = by < 0.0 ? -0.001 : 0.001;
1234             else style->line_height.value *= (average_line_height + zby) / average_line_height;
1235             break;
1236             // absolute-type units
1237         case SP_CSS_UNIT_PX:
1238             style->line_height.computed += zby;
1239             style->line_height.value = style->line_height.computed;
1240             break;
1241         case SP_CSS_UNIT_PT:
1242             style->line_height.computed += zby * PT_PER_PX;
1243             style->line_height.value = style->line_height.computed;
1244             break;
1245         case SP_CSS_UNIT_PC:
1246             style->line_height.computed += zby * (PT_PER_PX / 12);
1247             style->line_height.value = style->line_height.computed;
1248             break;
1249         case SP_CSS_UNIT_MM:
1250             style->line_height.computed += zby * MM_PER_PX;
1251             style->line_height.value = style->line_height.computed;
1252             break;
1253         case SP_CSS_UNIT_CM:
1254             style->line_height.computed += zby * CM_PER_PX;
1255             style->line_height.value = style->line_height.computed;
1256             break;
1257         case SP_CSS_UNIT_IN:
1258             style->line_height.computed += zby * IN_PER_PX;
1259             style->line_height.value = style->line_height.computed;
1260             break;
1261     }
1262     text->updateRepr();
1263     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1267 /* ***************************************************************************************************/
1268 //                           S T Y L E   A P P L I C A T I O N
1271 /** converts an iterator to a character index, mainly because ustring::substr()
1272 doesn't have a version that takes iterators as parameters. */
1273 static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1275     unsigned n = 0;
1276     for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; it++)
1277         n++;
1278     return n;
1281 /** applies the given style string on top of the existing styles for \a item,
1282 as opposed to sp_style_merge_from_style_string which merges its parameter
1283 underneath the existing styles (ie ignoring already set properties). */
1284 static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1286     SPStyle *new_style = sp_style_new(SP_OBJECT_DOCUMENT(item));
1287     sp_style_merge_from_style_string(new_style, style_string);
1288     gchar const *item_style_string = SP_OBJECT_REPR(item)->attribute("style");
1289     if (item_style_string && *item_style_string)
1290         sp_style_merge_from_style_string(new_style, item_style_string);
1291     gchar *new_style_string = sp_style_write_string(new_style);
1292     sp_style_unref(new_style);
1293     SP_OBJECT_REPR(item)->setAttribute("style", new_style_string && *new_style_string ? new_style_string : NULL);
1294     g_free(new_style_string);
1297 /** Returns true if the style of \a parent and the style of \a child are
1298 equivalent (and hence the children of both will appear the same). It is a
1299 limitation of the current implementation that \a parent must be a (not
1300 necessarily immediate) ancestor of \a child. */
1301 static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
1303     // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1304     // will not work. Firstly because of an inheritance bug in style.cpp that has
1305     // implications too large for me to feel safe fixing, but mainly because the css spec
1306     // requires that the computed value is inherited, not the specified value.
1307     g_assert(parent->isAncestorOf(child));
1308     gchar *parent_style = sp_style_write_string(parent->style, SP_STYLE_FLAG_ALWAYS);
1309     // we have to write parent_style then read it again, because some properties format their values
1310     // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1311     SPStyle *parent_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1312     sp_style_merge_from_style_string(parent_spstyle, parent_style);
1313     g_free(parent_style);
1314     parent_style = sp_style_write_string(parent_spstyle, SP_STYLE_FLAG_ALWAYS);
1315     sp_style_unref(parent_spstyle);
1317     Glib::ustring child_style_construction;
1318     while (child != parent) {
1319         // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1320         char const *style_text = SP_OBJECT_REPR(child)->attribute("style");
1321         if (style_text && *style_text) {
1322             child_style_construction.insert(0, style_text);
1323             child_style_construction.insert(0, 1, ';');
1324         }
1325         child = SP_OBJECT_PARENT(child);
1326     }
1327     child_style_construction.insert(0, parent_style);
1328     SPStyle *child_spstyle = sp_style_new(SP_OBJECT_DOCUMENT(parent));
1329     sp_style_merge_from_style_string(child_spstyle, child_style_construction.c_str());
1330     gchar *child_style = sp_style_write_string(child_spstyle, SP_STYLE_FLAG_ALWAYS);
1331     sp_style_unref(child_spstyle);
1332     bool equal = !strcmp(child_style, parent_style);
1333     g_free(child_style);
1334     g_free(parent_style);
1335     return equal;
1338 /** returns true if \a first and \a second contain all the same attributes
1339 with the same values as each other. Note that we have to compare both
1340 forwards and backwards to make sure we don't miss any attributes that are
1341 in one but not the other. */
1342 static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1344     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList();
1345     for ( ; attrs ; attrs++) {
1346         gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key));
1347         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1348             return false;
1349     }
1350     attrs = second->attributeList();
1351     for ( ; attrs ; attrs++) {
1352         gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key));
1353         if (other_attr == NULL || strcmp(attrs->value, other_attr))
1354             return false;
1355     }
1356     return true;
1359 /** sets the given css attribute on this object and all its descendants.
1360 Annoyingly similar to sp_desktop_apply_css_recursive(), except without the
1361 transform stuff. */
1362 static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
1364     sp_repr_css_change(SP_OBJECT_REPR(o), const_cast<SPCSSAttr*>(css), "style");
1366     for (SPObject *child = o->firstChild() ; child ; child = child->getNext() ) {
1367         if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", NULL) != NULL) {
1368             // Unset properties which are accumulating and thus should not be set recursively.
1369             // 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.
1370             SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1371             sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1372             sp_repr_css_set_property(css_recurse, "opacity", NULL);
1373             apply_css_recursive(child, css_recurse);
1374             sp_repr_css_attr_unref(css_recurse);
1375         } else {
1376             apply_css_recursive(child, const_cast<SPCSSAttr*>(css));
1377         }
1378     }
1381 /** applies the given style to all the objects at the given level and below
1382 which are between \a start_item and \a end_item, creating spans as necessary.
1383 If \a start_item or \a end_item are NULL then the style is applied to all
1384 objects to the beginning or end respectively. \a span_object_name is the
1385 name of the xml for a text span (ie tspan or flowspan). */
1386 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)
1388     bool passed_start = start_item == NULL ? true : false;
1389     Inkscape::XML::Document *xml_doc = SP_OBJECT_DOCUMENT(common_ancestor)->getReprDoc();
1390     
1391     for (SPObject *child = common_ancestor->firstChild() ; child ; child = child->getNext()) {
1392         if (start_item == child) {
1393             passed_start = true;
1394         }
1396         if (passed_start) {
1397             if (end_item && child->isAncestorOf(end_item)) {
1398                 recursively_apply_style(child, css, NULL, start_text_iter, end_item, end_text_iter, span_object_name);
1399                 break;
1400             }
1401             // apply style
1403             // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1404             // often this means that new spans are created before child and child is modified only
1405             if (SP_IS_STRING(child)) {
1406                 SPString *string_item = SP_STRING(child);
1407                 bool surround_entire_string = true;
1409                 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1410                 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style");   // better hope that prototype wasn't nonconst for a good reason
1411                 SPObject *prev_item = child->getPrev();
1412                 Inkscape::XML::Node *prev_repr = prev_item ? SP_OBJECT_REPR(prev_item) : NULL;
1414                 if (child == start_item || child == end_item) {
1415                     surround_entire_string = false;
1416                     if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1417                         // eg "abcDEFghi"  -> "abc"<span>"DEF"</span>"ghi"
1418                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1419                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1421                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1422                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1423                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1424                         Inkscape::GC::release(text_before);
1425                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1426                         child_span->appendChild(text_in_span);
1427                         Inkscape::GC::release(text_in_span);
1428                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1430                     } else if (child == end_item) {
1431                         // eg "ABCdef" -> <span>"ABC"</span>"def"
1432                         //  (includes case where start_text_iter == begin())
1433                         // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1434                         unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1436                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, prev_repr);
1437                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1438                         child_span->appendChild(text_in_span);
1439                         Inkscape::GC::release(text_in_span);
1440                         SP_OBJECT_REPR(child)->setContent(string_item->string.substr(end_char_index).c_str());
1442                     } else if (start_text_iter != string_item->string.begin()) {
1443                         // eg "abcDEF" -> "abc"<span>"DEF"</span>
1444                         unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1446                         Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1447                         SP_OBJECT_REPR(common_ancestor)->addChild(text_before, prev_repr);
1448                         SP_OBJECT_REPR(common_ancestor)->addChild(child_span, text_before);
1449                         Inkscape::GC::release(text_before);
1450                         Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1451                         child_span->appendChild(text_in_span);
1452                         Inkscape::GC::release(text_in_span);
1453                         child->deleteObject();
1454                         child = common_ancestor->get_child_by_repr(child_span);
1456                     } else
1457                         surround_entire_string = true;
1458                 }
1459                 if (surround_entire_string) {
1460                     Inkscape::XML::Node *child_repr = SP_OBJECT_REPR(child);
1461                     SP_OBJECT_REPR(common_ancestor)->addChild(child_span, child_repr);
1462                     Inkscape::GC::anchor(child_repr);
1463                     SP_OBJECT_REPR(common_ancestor)->removeChild(child_repr);
1464                     child_span->appendChild(child_repr);
1465                     Inkscape::GC::release(child_repr);
1466                     child = common_ancestor->get_child_by_repr(child_span);
1467                 }
1468                 Inkscape::GC::release(child_span);
1470             } else if (child != end_item) {   // not a string and we're applying to the entire object. This is easy
1471                 apply_css_recursive(child, css);
1472             }
1474         } else {  // !passed_start
1475             if (child->isAncestorOf(start_item)) {
1476                 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1477                 if (end_item && child->isAncestorOf(end_item))
1478                     break;   // only happens when start_item == end_item (I think)
1479                 passed_start = true;
1480             }
1481         }
1483         if (end_item == child)
1484             break;
1485     }
1488 /* if item is at the beginning of a tree it doesn't matter which element
1489 it points to so for neatness we would like it to point to the highest
1490 possible child of \a common_ancestor. There is no iterator return because
1491 a string can never be an ancestor.
1493 eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1494 like * to point to the inner span because we can apply style to that whole
1495 span. */
1496 static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1498     if (item == common_ancestor)
1499         return item;
1500     if (SP_IS_STRING(item))
1501         if (text_iter != SP_STRING(item)->string.begin())
1502             return item;
1503     for ( ; ; ) {
1504         SPObject *parent = SP_OBJECT_PARENT(item);
1505         if (parent == common_ancestor)
1506             break;
1507         if (item != parent->firstChild())
1508             break;
1509         item = parent;
1510     }
1511     return item;
1515 /**     empty spans: abc<span></span>def
1516                       -> abcdef                  */
1517 static bool tidy_operator_empty_spans(SPObject **item)
1519     bool result = false;
1520     if ( !(*item)->hasChildren()
1521          && !is_line_break_object(*item)
1522          && !(SP_IS_STRING(*item) && !SP_STRING(*item)->string.empty())
1523         ) {
1524         SPObject *next = (*item)->getNext();
1525         (*item)->deleteObject();
1526         *item = next;
1527         result = true;
1528     }
1529     return result;
1532 /**    inexplicable spans: abc<span style="">def</span>ghi
1533                             -> "abc""def""ghi"
1534 the repeated strings will be merged by another operator. */
1535 static bool tidy_operator_inexplicable_spans(SPObject **item)
1537     //XML Tree being directly used here while it shouldn't be.
1538     if (*item && sp_repr_is_meta_element((*item)->getRepr())) return false;
1539     if (SP_IS_STRING(*item)) return false;
1540     if (is_line_break_object(*item)) return false;
1541     TextTagAttributes *attrs = attributes_for_object(*item);
1542     if (attrs && attrs->anyAttributesSet()) return false;
1543     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), *item)) return false;
1544     SPObject *next = *item;
1545     while ((*item)->hasChildren()) {
1546         Inkscape::XML::Node *repr = SP_OBJECT_REPR((*item)->firstChild());
1547         Inkscape::GC::anchor(repr);
1548         SP_OBJECT_REPR(*item)->removeChild(repr);
1549         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(repr, SP_OBJECT_REPR(next));
1550         Inkscape::GC::release(repr);
1551         next = next->getNext();
1552     }
1553     (*item)->deleteObject();
1554     *item = next;
1555     return true;
1558 /**    repeated spans: <font a>abc</font><font a>def</font>
1559                         -> <font a>abcdef</font>            */
1560 static bool tidy_operator_repeated_spans(SPObject **item)
1562     SPObject *first = *item;
1563     SPObject *second = first->getNext();
1564     if (second == NULL) return false;
1566     Inkscape::XML::Node *first_repr = SP_OBJECT_REPR(first);
1567     Inkscape::XML::Node *second_repr = SP_OBJECT_REPR(second);
1569     if (first_repr->type() != second_repr->type()) return false;
1571     if (SP_IS_STRING(first) && SP_IS_STRING(second)) {
1572         // also amalgamate consecutive SPStrings into one
1573         Glib::ustring merged_string = SP_STRING(first)->string + SP_STRING(second)->string;
1574         SP_OBJECT_REPR(first)->setContent(merged_string.c_str());
1575         second_repr->parent()->removeChild(second_repr);
1576         return true;
1577     }
1579     // merge consecutive spans with identical styles into one
1580     if (first_repr->type() != Inkscape::XML::ELEMENT_NODE) return false;
1581     if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1582     if (is_line_break_object(second)) return false;
1583     gchar const *first_style = first_repr->attribute("style");
1584     gchar const *second_style = second_repr->attribute("style");
1585     if (!((first_style == NULL && second_style == NULL)
1586           || (first_style != NULL && second_style != NULL && !strcmp(first_style, second_style))))
1587         return false;
1589     // all our tests passed: do the merge
1590     TextTagAttributes *attributes_first = attributes_for_object(first);
1591     TextTagAttributes *attributes_second = attributes_for_object(second);
1592     if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1593         TextTagAttributes attributes_first_copy = *attributes_first;
1594         attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1595     }
1596     move_child_nodes(second_repr, first_repr);
1597     second_repr->parent()->removeChild(second_repr);
1598     return true;
1599     // *item is still the next object to process
1602 /**    redundant nesting: <font a><font b>abc</font></font>
1603                            -> <font b>abc</font>
1604        excessive nesting: <font a><size 1>abc</size></font>
1605                            -> <font a,size 1>abc</font>      */
1606 static bool tidy_operator_excessive_nesting(SPObject **item)
1608     if (!(*item)->hasChildren()) return false;
1609     if ((*item)->firstChild() != (*item)->lastChild()) return false;
1610     if (SP_IS_FLOWREGION((*item)->firstChild()) || SP_IS_FLOWREGIONEXCLUDE((*item)->firstChild()))
1611         return false;
1612     if (SP_IS_STRING((*item)->firstChild())) return false;
1613     if (is_line_break_object((*item)->firstChild())) return false;
1614     TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1615     if (attrs && attrs->anyAttributesSet()) return false;
1616     gchar const *child_style = SP_OBJECT_REPR((*item)->firstChild())->attribute("style");
1617     if (child_style && *child_style)
1618         overwrite_style_with_string(*item, child_style);
1619     move_child_nodes(SP_OBJECT_REPR((*item)->firstChild()), SP_OBJECT_REPR(*item));
1620     (*item)->firstChild()->deleteObject();
1621     return true;
1624 /** helper for tidy_operator_redundant_double_nesting() */
1625 static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1627     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1628         return false;
1629     if (SP_IS_STRING(child)) return false;
1630     if (is_line_break_object(child)) return false;
1631     if (is_line_break_object(*item)) return false;
1632     TextTagAttributes *attrs = attributes_for_object(child);
1633     if (attrs && attrs->anyAttributesSet()) return false;
1634     if (!objects_have_equal_style(SP_OBJECT_PARENT(*item), child)) return false;
1636     Inkscape::XML::Node *insert_after_repr = 0;
1637     if (!prepend) {
1638         insert_after_repr = SP_OBJECT_REPR(*item);
1639     } else if ((*item)->getPrev()) {
1640         insert_after_repr = SP_OBJECT_REPR((*item)->getPrev());
1641     }
1642     while (SP_OBJECT_REPR(child)->childCount()) {
1643         Inkscape::XML::Node *move_repr = SP_OBJECT_REPR(child)->firstChild();
1644         Inkscape::GC::anchor(move_repr);
1645         SP_OBJECT_REPR(child)->removeChild(move_repr);
1646         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(move_repr, insert_after_repr);
1647         Inkscape::GC::release(move_repr);
1648         insert_after_repr = move_repr;      // I think this will stay valid long enough. It's garbage collected these days.
1649     }
1650     child->deleteObject();
1651     return true;
1654 /**    redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font>
1655                                 -> <font b>abc<font a>def</font>ghi</font>
1656 this function does its work when the parameter is the <font a> tag in the
1657 example. You may note that this only does its work when the doubly-nested
1658 child is the first or last. The other cases are called 'style inversion'
1659 below, and I'm not yet convinced that the result of that operation will be
1660 tidier in all cases. */
1661 static bool tidy_operator_redundant_double_nesting(SPObject **item)
1663     if (!(*item)->hasChildren()) return false;
1664     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is excessive nesting, done above
1665     if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1666         return true;
1667     if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1668         return true;
1669     return false;
1672 /** helper for tidy_operator_redundant_semi_nesting(). Checks a few things,
1673 then compares the styles for item+child versus just child. If they're equal,
1674 tidying is possible. */
1675 static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
1677     if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child))
1678         return false;
1679     if (SP_IS_STRING(child)) return false;
1680     if (is_line_break_object(child)) return false;
1681     if (is_line_break_object(*item)) return false;
1682     TextTagAttributes *attrs = attributes_for_object(child);
1683     if (attrs && attrs->anyAttributesSet()) return false;
1684     attrs = attributes_for_object(*item);
1685     if (attrs && attrs->anyAttributesSet()) return false;
1687     SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1688     SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1689     gchar const *item_style = SP_OBJECT_REPR(*item)->attribute("style");
1690     if (item_style && *item_style) {
1691         sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1692     }
1693     gchar const *child_style = SP_OBJECT_REPR(child)->attribute("style");
1694     if (child_style && *child_style) {
1695         sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1696         sp_repr_css_attr_add_from_string(css_child_only, child_style);
1697     }
1698     bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1699     sp_repr_css_attr_unref(css_child_and_item);
1700     sp_repr_css_attr_unref(css_child_only);
1701     if (!equal) return false;
1703     Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(*item)->document();
1704     Inkscape::XML::Node *new_span = xml_doc->createElement(SP_OBJECT_REPR(*item)->name());
1705     if (prepend) {
1706         SPObject *prev = (*item)->getPrev();
1707         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, prev ? SP_OBJECT_REPR(prev) : NULL);
1708     } else
1709         SP_OBJECT_REPR(SP_OBJECT_PARENT(*item))->addChild(new_span, SP_OBJECT_REPR(*item));
1710     new_span->setAttribute("style", SP_OBJECT_REPR(child)->attribute("style"));
1711     move_child_nodes(SP_OBJECT_REPR(child), new_span);
1712     Inkscape::GC::release(new_span);
1713     child->deleteObject();
1714     return true;
1717 /**    redundant semi-nesting: <font a><font b>abc</font>def</font>
1718                                 -> <font b>abc</font><font>def</font>
1719 test this by applying a colour to a region, then a different colour to
1720 a partially-overlapping region. */
1721 static bool tidy_operator_redundant_semi_nesting(SPObject **item)
1723     if (!(*item)->hasChildren()) return false;
1724     if ((*item)->firstChild() == (*item)->lastChild()) return false;     // this is redundant nesting, done above
1725     if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1726         return true;
1727     if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1728         return true;
1729     return false;
1732 /** helper for tidy_operator_styled_whitespace(), finds the last string object
1733 in a paragraph which is not \a not_obj. */
1734 static SPString* find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
1736     for (SPObject *child = root->lastChild() ; child ; child = child->getPrev())
1737     {
1738         if (child == not_obj) continue;
1739         if (child->hasChildren()) {
1740             SPString *ret = find_last_string_child_not_equal_to(child, not_obj);
1741             if (ret) return ret;
1742         } else if (SP_IS_STRING(child))
1743             return SP_STRING(child);
1744     }
1745     return NULL;
1748 /** whitespace-only spans: abc<font> </font>def
1749                             -> abc<font></font> def
1750                            abc<b><i>def</i> </b>ghi
1751                             -> abc<b><i>def</i></b> ghi   */
1752 static bool tidy_operator_styled_whitespace(SPObject **item)
1754     if (!SP_IS_STRING(*item)) return false;
1755     Glib::ustring const &str = SP_STRING(*item)->string;
1756     for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it)
1757         if (!g_unichar_isspace(*it)) return false;
1759     SPObject *test_item = *item;
1760     SPString *next_string;
1761     for ( ; ; ) {  // find the next string
1762         next_string = sp_te_seek_next_string_recursive(test_item->getNext());
1763         if (next_string) {
1764             next_string->string.insert(0, str);
1765             break;
1766         }
1767         for ( ; ; ) {   // go up one item in the xml
1768             test_item = SP_OBJECT_PARENT(test_item);
1769             if (is_line_break_object(test_item)) break;
1770             if (SP_IS_FLOWTEXT(test_item)) return false;
1771             SPObject *next = test_item->getNext();
1772             if (next) {
1773                 test_item = next;
1774                 break;
1775             }
1776         }
1777         if (is_line_break_object(test_item)) {  // no next string, see if there's a prev string
1778             next_string = find_last_string_child_not_equal_to(test_item, *item);
1779             if (next_string == NULL) return false;   // an empty paragraph
1780             next_string->string += str;
1781             break;
1782         }
1783     }
1784     SP_OBJECT_REPR(next_string)->setContent(next_string->string.c_str());
1785     SPObject *delete_obj = *item;
1786     *item = (*item)->getNext();
1787     delete_obj->deleteObject();
1788     return true;
1791 /* possible tidy operators that are not yet implemented, either because
1792 they are difficult, occur infrequently, or because I'm not sure that the
1793 output is tidier in all cases:
1794     duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1795                                               -> <div italic><para>abc</para></div>
1796     style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1797                       -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1798     mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1799                           -> <size 1><font a>abc</font>def</size>
1800 */
1802 /** Recursively walks the xml tree calling a set of cleanup operations on
1803 every child. Returns true if any changes were made to the tree.
1805 All the tidy operators return true if they made changes, and alter their
1806 parameter to point to the next object that should be processed, or NULL.
1807 They must not significantly alter (ie delete) any ancestor elements of the
1808 one they are passed.
1810 It may be that some of the later tidy operators that I wrote are actually
1811 general cases of the earlier operators, and hence the special-case-only
1812 versions can be removed. I haven't analysed my work in detail to figure
1813 out if this is so. */
1814 static bool tidy_xml_tree_recursively(SPObject *root)
1816     static bool (* const tidy_operators[])(SPObject**) = {
1817         tidy_operator_empty_spans,
1818         tidy_operator_inexplicable_spans,
1819         tidy_operator_repeated_spans,
1820         tidy_operator_excessive_nesting,
1821         tidy_operator_redundant_double_nesting,
1822         tidy_operator_redundant_semi_nesting,
1823         tidy_operator_styled_whitespace
1824     };
1825     bool changes = false;
1827     for (SPObject *child = root->firstChild() ; child != NULL ; ) {
1828         if (SP_IS_FLOWREGION(child) || SP_IS_FLOWREGIONEXCLUDE(child) || SP_IS_TREF(child)) {
1829             child = child->getNext();
1830             continue;
1831         }
1832         if (child->hasChildren()) {
1833             changes |= tidy_xml_tree_recursively(child);
1834         }
1836         unsigned i;
1837         for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
1838             if (tidy_operators[i](&child)) {
1839                 changes = true;
1840                 break;
1841             }
1842         }
1843         if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0])) {
1844             child = child->getNext();
1845         }
1846     }
1847     return changes;
1850 /** Applies the given CSS fragment to the characters of the given text or
1851 flowtext object between \a start and \a end, creating or removing span
1852 elements as necessary and optimal. */
1853 void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
1855     // in the comments in the code below, capital letters are inside the application region, lowercase are outside
1856     if (start == end) return;
1857     Inkscape::Text::Layout::iterator first, last;
1858     if (start < end) {
1859         first = start;
1860         last = end;
1861     } else {
1862         first = end;
1863         last = start;
1864     }
1865     Inkscape::Text::Layout const *layout = te_get_layout(text);
1866     SPObject *start_item = 0, *end_item = 0;
1867     void *rawptr = 0;
1868     Glib::ustring::iterator start_text_iter, end_text_iter;
1869     layout->getSourceOfCharacter(first, &rawptr, &start_text_iter);
1870     start_item = SP_OBJECT(rawptr);
1871     layout->getSourceOfCharacter(last, &rawptr, &end_text_iter);
1872     end_item = SP_OBJECT(rawptr);
1873     if (start_item == 0) {
1874         return;   // start is at end of text
1875     }
1876     if (is_line_break_object(start_item)) {
1877         start_item = start_item->getNext();
1878     }
1879     if (is_line_break_object(end_item)) {
1880         end_item = end_item->getNext();
1881     }
1882     if (end_item == 0) {
1883         end_item = text;
1884     }
1885     
1886     
1887     /* Special case: With a tref, we only want to change its style when the whole
1888      * string is selected, in which case the style can be applied directly to the
1889      * tref node.  If only part of the tref's string child is selected, just return. */
1890      
1891     if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
1892         
1893         return;
1894     } 
1896     /* stage 1: applying the style. Go up to the closest common ancestor of
1897     start and end and then semi-recursively apply the style to all the
1898     objects in between. The semi-recursion is because it's only necessary
1899     at the beginning and end; the style can just be applied to the root
1900     child in the middle.
1901     eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
1902     The recursion may involve creating new spans.
1903     */
1904     SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
1906     // bug #168370 (consider parent transform and viewBox)
1907     // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
1908     SPCSSAttr *css_set = sp_repr_css_attr_new();
1909     sp_repr_css_merge(css_set, (SPCSSAttr*) css);
1910     {
1911         Geom::Matrix const local(SP_ITEM(common_ancestor)->i2doc_affine());
1912         double const ex(local.descrim());
1913         if ( ( ex != 0. )
1914              && ( ex != 1. ) ) {
1915             sp_css_attr_scale(css_set, 1/ex);
1916         }
1917     }
1919     start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
1920     end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
1921     recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
1922     sp_repr_css_attr_unref(css_set);
1924     /* stage 2: cleanup the xml tree (of which there are multiple passes) */
1925     /* discussion: this stage requires a certain level of inventiveness because
1926     it's not clear what the best representation is in many cases. An ideal
1927     implementation would provide some sort of scoring function to rate the
1928     ugliness of a given xml tree and try to reduce said function, but providing
1929     the various possibilities to be rated is non-trivial. Instead, I have opted
1930     for a multi-pass technique which simply recognises known-ugly patterns and
1931     has matching routines for optimising the patterns it finds. It's reasonably
1932     easy to add new pattern matching processors. If everything gets disastrous
1933     and neither option can be made to work, a fallback could be to reduce
1934     everything to a single level of nesting and drop all pretence of
1935     roundtrippability. */
1936     while (tidy_xml_tree_recursively(common_ancestor)){};
1938     // if we only modified subobjects this won't have been automatically sent
1939     text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1942 bool is_part_of_text_subtree (SPObject *obj)
1944     return (SP_IS_TSPAN(obj) 
1945             || SP_IS_TEXT(obj) 
1946             || SP_IS_FLOWTEXT(obj)
1947             || SP_IS_FLOWTSPAN(obj)
1948             || SP_IS_FLOWDIV(obj)
1949             || SP_IS_FLOWPARA(obj)
1950             || SP_IS_FLOWLINE(obj)
1951             || SP_IS_FLOWREGIONBREAK(obj));
1954 bool is_top_level_text_object (SPObject *obj)
1956     return (SP_IS_TEXT(obj) 
1957             || SP_IS_FLOWTEXT(obj));
1960 bool has_visible_text(SPObject *obj)
1962     bool hasVisible = false;
1964     if (SP_IS_STRING(obj) && !SP_STRING(obj)->string.empty()) {
1965         hasVisible = true; // maybe we should also check that it's not all whitespace?
1966     } else {
1967         for (SPObject const *child = obj->firstChild() ; child ; child = child->getNext()) {
1968             if (has_visible_text((SPObject *) child)) {
1969                 hasVisible = true;
1970                 break;
1971             }
1972         }
1973     }
1975     return hasVisible;
1978 /*
1979   Local Variables:
1980   mode:c++
1981   c-file-style:"stroustrup"
1982   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1983   indent-tabs-mode:nil
1984   fill-column:99
1985   End:
1986 */
1987 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :