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