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