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