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