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