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