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