Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
text-editing.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Parent class for text and flowtext
4 *
5 * Authors:
6 * bulia byak
7 * Richard Hughes
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2004-5 authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#ifdef HAVE_CONFIG_H
17#endif
18
19#include <cstring>
20#include <string>
21#include <glibmm/i18n.h>
22
23#include "desktop.h"
24#include "document.h"
25#include "inkscape.h"
26#include "message-stack.h"
27#include "text-editing.h"
28
29#include "object/sp-textpath.h"
30#include "object/sp-flowtext.h"
31#include "object/sp-flowdiv.h"
34#include "object/sp-tref.h"
35#include "object/sp-tspan.h"
36#include "style.h"
37
38#include "util/units.h"
39
41#include "xml/sp-css-attr.h"
42
43static const gchar *tref_edit_message = _("You cannot edit <b>cloned character data</b>.");
44static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend = false);
45
46static bool tidy_xml_tree_recursively(SPObject *root, bool has_text_decoration);
47
49{
50 if (is<SPText>(item)) {
51 return &(cast<SPText>(item)->layout);
52 } else if (is<SPFlowtext>(item)) {
53 return &(cast<SPFlowtext>(item)->layout);
54 }
55 return nullptr;
56}
57
59{
60 if (is<SPText>(item))
61 cast<SPText>(item)->rebuildLayout();
62 else if (is<SPFlowtext>(item))
63 cast<SPFlowtext>(item)->rebuildLayout();
65}
66
68{
69 if (is<SPGroup>(item)) {
70 std::vector<SPItem*> item_list = cast<SPGroup>(item)->item_list();
71 for(auto list_item : item_list){
73 }
74 } else if (is<SPText>(item))
75 cast<SPText>(item)->rebuildLayout();
76 else if (is<SPFlowtext>(item))
77 cast<SPFlowtext>(item)->rebuildLayout();
79}
80
82{
84 return layout->begin() == layout->end();
85}
86
88{
89 bool empty = true;
90 if (is<SPString>(item)) {
91 empty = cast<SPString>(item)->string.empty();
92 } else {
93 for (auto& child: item->children) {
95 empty = false;
96 break;
97 }
98 }
99 }
100 return empty;
101}
102
105{
107 im = im.inverse();
108
109 Geom::Point p = i_p * im;
111 return layout->getNearestCursorPositionTo(p);
112}
113
115{
116 if (start == end)
117 return std::vector<Geom::Point>();
119 if (layout == nullptr)
120 return std::vector<Geom::Point>();
121
122 return layout->createSelectionShape(start, end, transform);
123}
124
125void
127{
129 double height, rotation;
130 layout->queryCursorShape(position, p0, height, rotation);
131 p1 = Geom::Point(p0[Geom::X] + height * sin(rotation), p0[Geom::Y] - height * cos(rotation)); // valgrind warns that rotation is not initialized here. Why is to be seen in queryCursorShape
132}
133
135{
136 SPObject const *pos_obj = sp_te_object_at_position(text, position);
137 SPStyle *result = (pos_obj) ? pos_obj->style : nullptr;
138 return result;
139}
140
142{
143 Inkscape::Text::Layout const *layout = te_get_layout(text);
144 if (layout == nullptr) {
145 return nullptr;
146 }
147 SPObject *rawptr = nullptr;
148 layout->getSourceOfCharacter(position, &rawptr);
149 SPObject const *pos_obj = rawptr;
150 if (pos_obj == nullptr) {
151 pos_obj = text;
152 }
153 while (pos_obj->style == nullptr) {
154 pos_obj = pos_obj->parent; // not interested in SPStrings
155 }
156 return pos_obj;
157}
158
159/*
160 * for debugging input
161 *
162char * dump_hexy(const gchar * utf8)
163{
164 static char buffer[1024];
165
166 buffer[0]='\0';
167 for (const char *ptr=utf8; *ptr; ptr++) {
168 sprintf(buffer+strlen(buffer),"x%02X",(unsigned char)*ptr);
169 }
170 return buffer;
171}
172*/
173
180
181
182/* ***************************************************************************************************/
183// I N S E R T I N G T E X T
184
185static bool is_line_break_object(SPObject const *object)
186{
187 bool is_line_break = false;
188
189 if (object) {
190 if (is<SPText>(object)
191 || (is<SPTSpan>(object) && cast<SPTSpan>(object)->role != SP_TSPAN_ROLE_UNSPECIFIED)
192 || is<SPTextPath>(object)
193 || is<SPFlowdiv>(object)
194 || is<SPFlowpara>(object)
195 || is<SPFlowline>(object)
196 || is<SPFlowregionbreak>(object)) {
197
198 is_line_break = true;
199 }
200 }
201
202 return is_line_break;
203}
204
208{
209 if (is<SPTSpan>(object))
210 return &cast<SPTSpan>(object)->attributes;
211 if (is<SPText>(object))
212 return &cast<SPText>(object)->attributes;
213 if (is<SPTRef>(object))
214 return &cast<SPTRef>(object)->attributes;
215 if (is<SPTextPath>(object))
216 return &cast<SPTextPath>(object)->attributes;
217 return nullptr;
218}
219
220static const char * span_name_for_text_object(SPObject const *object)
221{
222 if (is<SPText>(object)) return "svg:tspan";
223 else if (is<SPFlowtext>(object)) return "svg:flowSpan";
224 return nullptr;
225}
226
228{
229 unsigned length = 0;
230
231 if (is<SPString>(item)) {
232 length = cast<SPString>(item)->string.length();
233 } else {
235 length++;
236 }
237
238 for (auto& child: item->children) {
239 if (is<SPString>(&child)) {
240 length += cast<SPString>(&child)->string.length();
241 } else {
242 length += sp_text_get_length(&child);
243 }
244 }
245 }
246
247 return length;
248}
249
250unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
251{
252 unsigned length = 0;
253
254 // The string is the lowest level and the length can be counted directly.
255 if (is<SPString>(item)) {
256 return cast<SPString>(item)->string.length();
257 }
258
259 // Take care of new lines...
260 if (is_line_break_object(item) && !is<SPText>(item)) {
261 if (item != item->parent->firstChild()) {
262 // add 1 for each newline
263 length++;
264 }
265 }
266
267 // Count the length of the children
268 for (auto& child: item->children) {
269 if (upto && &child == upto) {
270 // hit upto, return immediately
271 return length;
272 }
273 if (is<SPString>(&child)) {
274 length += cast<SPString>(&child)->string.length();
275 }
276 else {
277 if (upto && child.isAncestorOf(upto)) {
278 // upto is below us, recurse and break loop
279 length += sp_text_get_length_upto(&child, upto);
280 return length;
281 } else {
282 // recurse and go to the next sibling
283 length += sp_text_get_length_upto(&child, upto);
284 }
285 }
286 }
287 return length;
288}
289
291{
292 switch (old_node->type()) {
294 Inkscape::XML::Node *new_node = xml_doc->createElement(old_node->name());
295 GQuark const id_key = g_quark_from_string("id");
296 for ( const auto & attr: old_node->attributeList() ) {
297 if (attr.key == id_key) continue;
298 new_node->setAttribute(g_quark_to_string(attr.key), attr.value);
299 }
300 return new_node;
301 }
302
304 return xml_doc->createTextNode(old_node->content());
305
307 return xml_doc->createComment(old_node->content());
308
310 return xml_doc->createPI(old_node->name(), old_node->content());
311
313 return nullptr; // this had better never happen
314 }
315 return nullptr;
316}
317
321{
322 unsigned char_index = 0;
323 for (auto& sibling: item->parent->children) {
324 if (&sibling == item) {
325 break;
326 }
327 char_index += sp_text_get_length(&sibling);
328 }
329 return char_index;
330}
331
334static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
335{
336 TextTagAttributes *first_attrs = attributes_for_object(first_item);
337 TextTagAttributes *second_attrs = attributes_for_object(second_item);
338 if (first_attrs && second_attrs)
339 first_attrs->split(char_index, second_attrs);
340}
341
348static SPObject* split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
349{
350 Inkscape::XML::Document *xml_doc = split_obj->document->getReprDoc();
351 if (is_line_break_object(split_obj)) {
352 Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, split_obj->getRepr());
353 split_obj->parent->getRepr()->addChild(new_node, split_obj->getRepr());
354 Inkscape::GC::release(new_node);
355 split_attributes(split_obj, split_obj->getNext(), char_index);
356 return split_obj->getNext();
357 } else if (!is<SPTSpan>(split_obj) &&
358 !is<SPFlowtspan>(split_obj) &&
359 !is<SPString>(split_obj)) {
360 std::cerr << "split_text_object_tree_at: Illegal split object type! (Illegal document structure.)" << std::endl;
361 return nullptr;
362 }
363
364 unsigned char_count_before = sum_sibling_text_lengths_before(split_obj);
365 SPObject *duplicate_obj = split_text_object_tree_at(split_obj->parent, char_index + char_count_before);
366
367 if (duplicate_obj == nullptr) {
368 // Illegal document structure (no line break object).
369 return nullptr;
370 }
371
372 // copy the split node
373 Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, split_obj->getRepr());
374 duplicate_obj->getRepr()->appendChild(new_node);
375 Inkscape::GC::release(new_node);
376
377 // sort out the copied attributes (x/y/dx/dy/rotate)
378 split_attributes(split_obj, duplicate_obj->firstChild(), char_index);
379
380 // then move all the subsequent nodes
381 split_obj = split_obj->getNext();
382 while (split_obj) {
383 Inkscape::XML::Node *move_repr = split_obj->getRepr();
384 SPObject *next_obj = split_obj->getNext(); // this is about to become invalidated by removeChild()
385 Inkscape::GC::anchor(move_repr);
386 split_obj->parent->getRepr()->removeChild(move_repr);
387 duplicate_obj->getRepr()->appendChild(move_repr);
388 Inkscape::GC::release(move_repr);
389
390 split_obj = next_obj;
391 }
392 return duplicate_obj->firstChild();
393}
394
400{
401 // Disable newlines in a textpath; TODO: maybe on Enter in a textpath, separate it into two
402 // texpaths attached to the same path, with a vertical shift
403 if (SP_IS_TEXT_TEXTPATH (item) || is<SPTRef>(item))
404 return position;
405
407
408 // If this is plain SVG 1.1 text object without a tspan with sodipodi:role="line", we need
409 // to wrap it or our custom line breaking code won't work!
410 bool need_to_wrap = false;
411 auto text_object = cast<SPText>(item);
412 if (text_object && !text_object->has_shape_inside() && !text_object->has_inline_size()) {
413
414 need_to_wrap = true;
415 for (auto child : item->childList(false)) {
416 auto tspan = cast<SPTSpan>(child);
417 if (tspan && tspan->role == SP_TSPAN_ROLE_LINE) {
418 // Already wrapped
419 need_to_wrap = false;
420 break;
421 }
422 }
423
424 if (need_to_wrap) {
425
426 // We'll need to rebuild layout, so store character position:
427 int char_index = layout->iteratorToCharIndex(position);
428
429 // Create wrapping tspan.
430 Inkscape::XML::Node *text_repr = text_object->getRepr();
431 Inkscape::XML::Document *xml_doc = text_repr->document();
432 Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan");
433 new_tspan_repr->setAttribute("sodipodi:role", "line");
434
435 // Move text content to tspan and add tspan to text object.
436 // To do: This moves <desc> and <title> too.
437 move_child_nodes(text_repr, new_tspan_repr);
438 text_repr->appendChild(new_tspan_repr);
439
440 // Need to find new iterator.
441 text_object->rebuildLayout();
442 position = layout->charIndexToIterator(char_index);
443 }
444 }
445
446 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
447
448 SPObject *split_obj = nullptr;
449 Glib::ustring::iterator split_text_iter;
450 if (position != layout->end()) {
451 layout->getSourceOfCharacter(position, &split_obj, &split_text_iter);
452 }
453
454 if (split_obj == nullptr || is_line_break_object(split_obj)) {
455
456 if (split_obj == nullptr) split_obj = item->lastChild();
457
458 if (is<SPTRef>(split_obj)) {
460 return position;
461 }
462
463 if (split_obj) {
464 Inkscape::XML::Document *xml_doc = split_obj->document->getReprDoc();
465 Inkscape::XML::Node *new_node = duplicate_node_without_children(xml_doc, split_obj->getRepr());
466 // if we finally go to a text element without TSpan we mist set content to none
467 // new_node->setContent("");
468 split_obj->parent->getRepr()->addChild(new_node, split_obj->getRepr());
469 Inkscape::GC::release(new_node);
470 }
471
472 } else if (is<SPString>(split_obj)) {
473
474 // If the parent is a tref, editing on this particular string is disallowed.
475 if (is<SPTRef>(split_obj->parent)) {
477 return position;
478 }
479
480 Glib::ustring *string = &cast<SPString>(split_obj)->string;
481 unsigned char_index = 0;
482 for (Glib::ustring::iterator it = string->begin() ; it != split_text_iter ; ++it)
483 char_index++;
484 // we need to split the entire text tree into two
485
486 SPObject *object = split_text_object_tree_at(split_obj, char_index);
487 if (object == nullptr) {
488 // Illegal document structure
489 return position;
490 }
491
492 auto new_string = cast<SPString>(object);
493 new_string->getRepr()->setContent(&*split_text_iter.base()); // a little ugly
494 string->erase(split_text_iter, string->end());
495 split_obj->getRepr()->setContent(string->c_str());
496 // TODO: if the split point was at the beginning of a span we have a whole load of empty elements to clean up
497 } else {
498 // TODO
499 // I think the only case to put here is arbitrary gaps, which nobody uses yet
500 }
501
502 unsigned char_index = layout->iteratorToCharIndex(position);
504 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
505 return layout->charIndexToIterator(char_index + 1);
506}
507
510{
511 while (start_obj) {
512 if (start_obj->hasChildren()) {
513 SPString *found_string = sp_te_seek_next_string_recursive(start_obj->firstChild());
514 if (found_string) {
515 return found_string;
516 }
517 }
518 if (is<SPString>(start_obj)) {
519 return cast<SPString>(start_obj);
520 }
521 start_obj = start_obj->getNext();
522 if (is_line_break_object(start_obj)) {
523 break; // don't cross line breaks
524 }
525 }
526 return nullptr;
527}
528
531static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
532{
533 unsigned char_index = 0;
534 unsigned char_count = g_utf8_strlen(utf8, -1);
535 Glib::ustring *string = &string_item->string;
536
537 for (Glib::ustring::iterator it = string->begin() ; it != iter_at ; ++it)
538 char_index++;
539 string->replace(iter_at, iter_at, utf8);
540
541 SPObject *parent_item = string_item;
542 for ( ; ; ) {
543 char_index += sum_sibling_text_lengths_before(parent_item);
544 parent_item = parent_item->parent;
545 TextTagAttributes *attributes = attributes_for_object(parent_item);
546 if (!attributes) break;
547 attributes->insert(char_index, char_count);
548 }
549}
550
555sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
556{
557 if (!g_utf8_validate(utf8,-1,nullptr)) {
558 g_warning("Trying to insert invalid utf8");
559 return position;
560 }
561
562 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
563
565 Glib::ustring::iterator iter_text;
566 // we want to insert after the previous char, not before the current char.
567 // it makes a difference at span boundaries
568 Inkscape::Text::Layout::iterator it_prev_char = position;
569 bool cursor_at_start = !it_prev_char.prevCharacter();
570 bool cursor_at_end = position == layout->end();
571 SPObject *source_obj = nullptr;
572 layout->getSourceOfCharacter(it_prev_char, &source_obj, &iter_text);
573 if (is<SPString>(source_obj)) {
574 // If the parent is a tref, editing on this particular string is disallowed.
575 if (is<SPTRef>(source_obj->parent)) {
577 return position;
578 }
579
580 // Now the simple case can begin...
581 if (!cursor_at_start){
582 ++iter_text;
583 }
584 auto string_item = cast<SPString>(source_obj);
585 insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : iter_text, utf8);
586 } else {
587 // the not-so-simple case where we're at a line break or other control char; add to the next child/sibling SPString
589 if (cursor_at_start) {
590 source_obj = item;
591 if (source_obj->hasChildren()) {
592 source_obj = source_obj->firstChild();
593 if (is<SPFlowtext>(item)) {
594 while (is<SPFlowregion>(source_obj) || is<SPFlowregionExclude>(source_obj)) {
595 source_obj = source_obj->getNext();
596 }
597 if (source_obj == nullptr) {
598 source_obj = item;
599 }
600 }
601 }
602 if (source_obj == item && is<SPFlowtext>(item)) {
603 Inkscape::XML::Node *para = xml_doc->createElement("svg:flowPara");
604 item->getRepr()->appendChild(para);
605 source_obj = item->lastChild();
606 }
607 } else
608 source_obj = source_obj->getNext();
609
610 if (source_obj) { // never fails
611 SPString *string_item = sp_te_seek_next_string_recursive(source_obj);
612 if (string_item == nullptr) {
613 // need to add an SPString in this (pathological) case
614 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
615 source_obj->getRepr()->addChild(rstring, nullptr);
616 Inkscape::GC::release(rstring);
617 g_assert(is<SPString>(source_obj->firstChild()));
618 string_item = cast<SPString>(source_obj->firstChild());
619 }
620 // If the parent is a tref, editing on this particular string is disallowed.
621 if (is<SPTRef>(string_item->parent)) {
623 return position;
624 }
625
626 insert_into_spstring(string_item, cursor_at_end ? string_item->string.end() : string_item->string.begin(), utf8);
627 }
628 }
629
630 unsigned char_index = layout->iteratorToCharIndex(position);
632 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
633 return layout->charIndexToIterator(char_index + g_utf8_strlen(utf8, -1));
634}
635
636
637/* ***************************************************************************************************/
638// D E L E T I N G T E X T
639
643static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend)
644{
645 while (from_repr->childCount()) {
646 Inkscape::XML::Node *child = prepend ? from_repr->lastChild() : from_repr->firstChild();
648 from_repr->removeChild(child);
649 if (prepend) to_repr->addChild(child, nullptr);
650 else to_repr->appendChild(child);
652 }
653}
654
658{
659 if (one == nullptr || two == nullptr)
660 return text;
661 SPObject *common_ancestor = one;
662 if (is<SPString>(common_ancestor))
663 common_ancestor = common_ancestor->parent;
664 while (!(common_ancestor == two || common_ancestor->isAncestorOf(two))) {
665 g_assert(common_ancestor != text);
666 common_ancestor = common_ancestor->parent;
667 }
668 return common_ancestor;
669}
670
674static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
675{
676 while ((*para_obj)->hasChildren())
677 *para_obj = (*para_obj)->lastChild();
678 if (is<SPString>(*para_obj))
679 *text_iter = cast<SPString>(*para_obj)->string.end();
680}
681
685static SPObject* delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
686{
687 Inkscape::XML::Node *this_repr = item->getRepr();
688 SPObject *next_item = nullptr;
689 unsigned moved_char_count = sp_text_get_length(item) - 1; // the -1 is because it's going to count the line break
690
691 /* some sample cases (the div is the item to be deleted, the * represents where to put the new span):
692 <div></div><p>*text</p>
693 <p><div></div>*text</p>
694 <p><div></div></p><p>*text</p>
695 */
698
699 new_span_repr->setAttributeOrRemoveIfEmpty("dx", this_repr->attribute("dx"));
700 new_span_repr->setAttributeOrRemoveIfEmpty("dy", this_repr->attribute("dy"));
701 new_span_repr->setAttributeOrRemoveIfEmpty("rotate", this_repr->attribute("rotate"));
702
703 SPObject *following_item = item;
704 while (following_item->getNext() == nullptr) {
705 following_item = following_item->parent;
706 g_assert(following_item != root);
707 }
708 following_item = following_item->getNext();
709
710 SPObject *new_parent_item;
711 if (is<SPString>(following_item)) {
712 new_parent_item = following_item->parent;
713 new_parent_item->getRepr()->addChild(new_span_repr, following_item->getPrev() ? following_item->getPrev()->getRepr() : nullptr);
714 next_item = following_item;
715 *next_is_sibling = true;
716 } else {
717 new_parent_item = following_item;
718 next_item = new_parent_item->firstChild();
719 *next_is_sibling = true;
720 if (next_item == nullptr) {
721 next_item = new_parent_item;
722 *next_is_sibling = false;
723 }
724 new_parent_item->getRepr()->addChild(new_span_repr, nullptr);
725 }
726
727 // work around a bug in sp_style_write_difference() which causes the difference
728 // not to be written if the second param has a style set which the first does not
729 // by causing the first param to have everything set
730 SPCSSAttr *dest_node_attrs = sp_repr_css_attr(new_parent_item->getRepr(), "style");
731 SPCSSAttr *this_node_attrs = sp_repr_css_attr(this_repr, "style");
732 SPCSSAttr *this_node_attrs_inherited = sp_repr_css_attr_inherited(this_repr, "style");
733 for ( const auto & attr :dest_node_attrs->attributeList()) {
734 gchar const *key = g_quark_to_string(attr.key);
735 gchar const *this_attr = this_node_attrs_inherited->attribute(key);
736 if ((this_attr == nullptr || strcmp(attr.value, this_attr)) && this_node_attrs->attribute(key) == nullptr)
737 this_node_attrs->setAttribute(key, this_attr);
738 }
739 sp_repr_css_attr_unref(this_node_attrs_inherited);
740 sp_repr_css_attr_unref(this_node_attrs);
741 sp_repr_css_attr_unref(dest_node_attrs);
742 sp_repr_css_change(new_span_repr, this_node_attrs, "style");
743
744 TextTagAttributes *attributes = attributes_for_object(new_parent_item);
745 if (attributes)
746 attributes->insert(0, moved_char_count);
747 move_child_nodes(this_repr, new_span_repr);
748 this_repr->parent()->removeChild(this_repr);
749 return next_item;
750}
751
754static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
755{
756 unsigned char_index = 0;
757 unsigned char_count = 0;
758 Glib::ustring *string = &string_item->string;
759
760 for (Glib::ustring::iterator it = string->begin() ; it != iter_from ; ++it){
761 char_index++;
762 }
763 for (Glib::ustring::iterator it = iter_from ; it != iter_to ; ++it){
764 char_count++;
765 }
766 string->erase(iter_from, iter_to);
767 string_item->getRepr()->setContent(string->c_str());
768
769 SPObject *parent_item = string_item;
770 for ( ; ; ) {
771 char_index += sum_sibling_text_lengths_before(parent_item);
772 parent_item = parent_item->parent;
773 TextTagAttributes *attributes = attributes_for_object(parent_item);
774 if (attributes == nullptr) {
775 break;
776 }
777
778 attributes->erase(char_index, char_count);
779 attributes->writeTo(parent_item->getRepr());
780 }
781}
782
783/* Deletes the given characters from a text or flowroot object. This is
784quite a complicated operation, partly due to the cleanup that is done if all
785the text in a subobject has been deleted, and partly due to the difficulty
786of figuring out what is a line break and how to delete one. Returns the
787real start and ending iterators based on the situation. */
788bool
791{
792 bool success = false;
793
794 iter_pair.first = start;
795 iter_pair.second = end;
796
797 if (start == end) return success;
798
799 if (start > end) {
800 iter_pair.first = end;
801 iter_pair.second = start;
802 }
803
804 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
805
807 SPObject *start_item = nullptr, *end_item = nullptr;
808 Glib::ustring::iterator start_text_iter, end_text_iter;
809 layout->getSourceOfCharacter(iter_pair.first, &start_item, &start_text_iter);
810 layout->getSourceOfCharacter(iter_pair.second, &end_item, &end_text_iter);
811 if (start_item == nullptr) {
812 return success; // start is at end of text
813 }
814 if (is_line_break_object(start_item)) {
815 move_to_end_of_paragraph(&start_item, &start_text_iter);
816 }
817 if (end_item == nullptr) {
818 end_item = item->lastChild();
819 move_to_end_of_paragraph(&end_item, &end_text_iter);
820 } else if (is_line_break_object(end_item)) {
821 move_to_end_of_paragraph(&end_item, &end_text_iter);
822 }
823
824 SPObject *common_ancestor = get_common_ancestor(item, start_item, end_item);
825
826 bool has_text_decoration = false;
827 gchar const *root_style = (item)->getRepr()->attribute("style");
828 if(root_style && strstr(root_style,"text-decoration"))has_text_decoration = true;
829
830 if (start_item == end_item) {
831 // the quick case where we're deleting stuff all from the same string
832 if (is<SPString>(start_item)) { // always true (if it_start != it_end anyway)
833 // If the parent is a tref, editing on this particular string is disallowed.
834 if (is<SPTRef>(start_item->parent)) {
836 } else {
837 erase_from_spstring(cast<SPString>(start_item), start_text_iter, end_text_iter);
838 success = true;
839 }
840 }
841 } else {
842 SPObject *sub_item = start_item;
843 // walk the tree from start_item to end_item, deleting as we go
844 while (sub_item != item) {
845 if (sub_item == end_item) {
846 if (is<SPString>(sub_item)) {
847 // If the parent is a tref, editing on this particular string is disallowed.
848 if (is<SPTRef>(sub_item->parent)) {
850 break;
851 }
852
853 Glib::ustring *string = &cast<SPString>(sub_item)->string;
854 erase_from_spstring(cast<SPString>(sub_item), string->begin(), end_text_iter);
855 success = true;
856 }
857 break;
858 }
859 if (is<SPString>(sub_item)) {
860 auto string = cast<SPString>(sub_item);
861 if (sub_item == start_item)
862 erase_from_spstring(string, start_text_iter, string->string.end());
863 else
864 erase_from_spstring(string, string->string.begin(), string->string.end());
865 success = true;
866 }
867 // walk to the next item in the tree
868 if (sub_item->hasChildren())
869 sub_item = sub_item->firstChild();
870 else {
872 do {
873 bool is_sibling = true;
874 next_item = sub_item->getNext();
875 if (next_item == nullptr) {
876 next_item = sub_item->parent;
877 is_sibling = false;
878 }
879
880 if (is_line_break_object(sub_item))
881 next_item = delete_line_break(item, sub_item, &is_sibling);
882
883 sub_item = next_item;
884 if (is_sibling) break;
885 // no more siblings, go up a parent
886 } while (sub_item != item && sub_item != end_item);
887 }
888 }
889 }
890
891 while (tidy_xml_tree_recursively(common_ancestor, has_text_decoration)){};
893 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
894 layout->validateIterator(&iter_pair.first);
895 layout->validateIterator(&iter_pair.second);
896 return success;
897}
898
899
900/* ***************************************************************************************************/
901// P L A I N T E X T F U N C T I O N S
902
905static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
906{
907 if (*pending_line_break) {
908 *string += '\n';
909 *pending_line_break = false;
910 }
911 for (auto& child: root->children) {
912 if (is<SPString>(&child)) {
913 *string += cast<SPString>(&child)->string;
914 } else if (is_part_of_text_subtree(&child) || is<SPTextPath>(&child)) {
915 sp_te_get_ustring_multiline(&child, string, pending_line_break);
916 }
917 }
918 if (!is<SPText>(root) && !is<SPTextPath>(root) && is_line_break_object(root)) {
919 *pending_line_break = true;
920 }
921}
922
925Glib::ustring
927{
928 Glib::ustring string;
929 bool pending_line_break = false;
930
931 if (is<SPText>(text) || is<SPFlowtext>(text)) {
932 sp_te_get_ustring_multiline(text, &string, &pending_line_break);
933 }
934 return string;
935}
936
940Glib::ustring
942{
943 if (start == end) return "";
945 if (start < end) {
946 first = start;
947 last = end;
948 } else {
949 first = end;
950 last = start;
951 }
952 Inkscape::Text::Layout const *layout = te_get_layout(text);
953 Glib::ustring result;
954 // not a particularly fast piece of code. I'll optimise it if people start to notice.
955 for ( ; first < last ; first.nextCharacter()) {
956 SPObject *char_item = nullptr;
957 Glib::ustring::iterator text_iter;
958 layout->getSourceOfCharacter(first, &char_item, &text_iter);
959 if (is<SPString>(char_item)) {
960 result += *text_iter;
961 } else {
962 result += '\n';
963 }
964 }
965 return result;
966}
967
968void
969sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
970{
971 g_return_if_fail (text != nullptr);
972 g_return_if_fail (is<SPText>(text) || is<SPFlowtext>(text));
973
974 Inkscape::XML::Document *xml_doc = text->getRepr()->document();
976 SPObject *object;
977 bool is_textpath = false;
978 if (SP_IS_TEXT_TEXTPATH (text)) {
979 repr = text->firstChild()->getRepr();
980 object = text->firstChild();
981 is_textpath = true;
982 } else {
983 repr = text->getRepr();
984 object = text;
985 }
986
987 if (!str) {
988 str = "";
989 }
990 gchar *content = g_strdup (str);
991
992 repr->setContent("");
993 for (auto& child: object->childList(false)) {
994 if (!is<SPFlowregion>(child) && !is<SPFlowregionExclude>(child)) {
995 repr->removeChild(child->getRepr());
996 }
997 }
998
999 if (is_textpath) {
1000 gchar *p = content;
1001 while (*p != '\0') {
1002 if (*p == '\n') {
1003 *p = ' '; // No lines for textpath, replace newlines with spaces.
1004 }
1005 ++p;
1006 }
1007 Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
1008 repr->addChild(rstr, nullptr);
1010 } else {
1011 auto sptext = cast<SPText>(text);
1012 if (sptext && (sptext->has_inline_size() || sptext->has_shape_inside())) {
1013 // Do nothing... we respect newlines (and assume CSS already set to do so).
1014 Inkscape::XML::Node *rstr = xml_doc->createTextNode(content);
1015 repr->addChild(rstr, nullptr);
1017 } else {
1018 // Break into tspans with sodipodi:role="line".
1019 gchar *p = content;
1020 while (p) {
1021 gchar *e = strchr (p, '\n');
1022 if (e) *e = '\0';
1023 Inkscape::XML::Node *rtspan;
1024 if (is<SPText>(text)) { // create a tspan for each line
1025 rtspan = xml_doc->createElement("svg:tspan");
1026 rtspan->setAttribute("sodipodi:role", "line");
1027 } else { // create a flowPara for each line
1028 rtspan = xml_doc->createElement("svg:flowPara");
1029 }
1030 Inkscape::XML::Node *rstr = xml_doc->createTextNode(p);
1031 rtspan->addChild(rstr, nullptr);
1033 repr->appendChild(rtspan);
1034 Inkscape::GC::release(rtspan);
1035
1036 p = (e) ? e + 1 : nullptr;
1037 }
1038 }
1039 }
1040 g_free (content);
1041}
1042
1043/* ***************************************************************************************************/
1044// K E R N I N G A N D S P A C I N G
1045
1050{
1051 if (item == nullptr || char_index == nullptr || !is<SPText>(item)) {
1052 return nullptr; // flowtext doesn't support kerning yet
1053 }
1054 auto text = cast<SPText>(item);
1055
1056 SPObject *source_item = nullptr;
1057 Glib::ustring::iterator source_text_iter;
1058 text->layout.getSourceOfCharacter(position, &source_item, &source_text_iter);
1059
1060 if (!is<SPString>(source_item)) {
1061 return nullptr;
1062 }
1063 Glib::ustring *string = &cast<SPString>(source_item)->string;
1064 *char_index = sum_sibling_text_lengths_before(source_item);
1065 for (Glib::ustring::iterator it = string->begin() ; it != source_text_iter ; ++it) {
1066 ++*char_index;
1067 }
1068
1069 return attributes_for_object(source_item->parent);
1070}
1071
1072void
1074{
1075 // divide increment by zoom
1076 // divide increment by matrix expansion
1077 gdouble factor = 1 / desktop->current_zoom();
1079 factor = factor / t.descrim();
1080 by = factor * by;
1081
1082 unsigned char_index;
1083 TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
1084 if (attributes) attributes->addToDxDy(char_index, by);
1085 if (start != end) {
1086 attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1087 if (attributes) attributes->addToDxDy(char_index, -by);
1088 }
1089
1090 item->updateRepr();
1091 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1092}
1093
1095{
1096 unsigned char_index = 0;
1097 TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
1098 if (attributes) {
1099 attributes->addToDx(char_index, delta);
1100 }
1101 if (start != end) {
1102 attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1103 if (attributes) {
1104 attributes->addToDx(char_index, -delta);
1105 }
1106 }
1107
1108 item->updateRepr();
1109 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1110}
1111
1113{
1114 unsigned char_index = 0;
1115 TextTagAttributes *attributes = text_tag_attributes_at_position(item, std::min(start, end), &char_index);
1116 if (attributes) {
1117 attributes->addToDy(char_index, delta);
1118 }
1119 if (start != end) {
1120 attributes = text_tag_attributes_at_position(item, std::max(start, end), &char_index);
1121 if (attributes) {
1122 attributes->addToDy(char_index, -delta);
1123 }
1124 }
1125
1126 item->updateRepr();
1127 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1128}
1129
1130void
1132{
1133 // divide increment by zoom
1134 // divide increment by matrix expansion
1135 gdouble factor = 1 / desktop->current_zoom();
1136 Geom::Affine t (text->i2doc_affine());
1137 factor = factor / t.descrim();
1138 Inkscape::Text::Layout const *layout = te_get_layout(text);
1139 if (layout == nullptr) return;
1140 SPObject *source_item = nullptr;
1141 layout->getSourceOfCharacter(std::min(start, end), &source_item);
1142 if (source_item == nullptr) {
1143 return;
1144 }
1145 gdouble degrees = (180/M_PI) * atan2(pixels, source_item->parent->style->font_size.computed / factor);
1146
1147 sp_te_adjust_rotation(text, start, end, desktop, degrees);
1148}
1149
1150void
1152{
1153 unsigned char_index;
1154 TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1155 if (attributes == nullptr) return;
1156
1157 if (start != end) {
1158 for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1159 attributes = text_tag_attributes_at_position(text, it, &char_index);
1160 if (attributes) attributes->addToRotate(char_index, degrees);
1161 }
1162 } else
1163 attributes->addToRotate(char_index, degrees);
1164
1165 text->updateRepr();
1166 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1167}
1168
1170{
1171 unsigned char_index = 0;
1172 TextTagAttributes *attributes = text_tag_attributes_at_position(text, std::min(start, end), &char_index);
1173 if (attributes != nullptr) {
1174 if (start != end) {
1175 for (Inkscape::Text::Layout::iterator it = std::min(start, end) ; it != std::max(start, end) ; it.nextCharacter()) {
1176 attributes = text_tag_attributes_at_position(text, it, &char_index);
1177 if (attributes) {
1178 attributes->setRotate(char_index, degrees);
1179 }
1180 }
1181 } else {
1182 attributes->setRotate(char_index, degrees);
1183 }
1184
1185 text->updateRepr();
1186 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1187 }
1188}
1189
1190void
1192{
1193 g_return_if_fail (text != nullptr);
1194 g_return_if_fail (is<SPText>(text) || is<SPFlowtext>(text));
1195
1196 Inkscape::Text::Layout const *layout = te_get_layout(text);
1197
1198 gdouble val;
1199 SPObject *source_obj = nullptr;
1200 unsigned nb_let;
1201 layout->getSourceOfCharacter(std::min(start, end), &source_obj);
1202
1203 if (source_obj == nullptr) { // end of text
1204 source_obj = text->lastChild();
1205 }
1206 if (is<SPString>(source_obj)) {
1207 source_obj = source_obj->parent;
1208 }
1209
1210 SPStyle *style = source_obj->style;
1211
1212 // calculate real value
1213 /* TODO: Consider calculating val unconditionally, i.e. drop the first `if' line, and
1214 get rid of the `else val = 0.0'. Similarly below and in sp-string.cpp. */
1215 if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1216 if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1217 val = style->font_size.computed * style->letter_spacing.value;
1218 } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1219 val = style->font_size.computed * style->letter_spacing.value * 0.5;
1220 } else { // unknown unit - should not happen
1221 val = 0.0;
1222 }
1223 } else { // there's a real value in .computed, or it's zero
1224 val = style->letter_spacing.computed;
1225 }
1226
1227 if (start == end) {
1228 while (!is_line_break_object(source_obj)) { // move up the tree so we apply to the closest paragraph
1229 source_obj = source_obj->parent;
1230 }
1231 nb_let = sp_text_get_length(source_obj);
1232 } else {
1233 nb_let = abs(layout->iteratorToCharIndex(end) - layout->iteratorToCharIndex(start));
1234 }
1235
1236 // divide increment by zoom and by the number of characters in the line,
1237 // so that the entire line is expanded by by pixels, no matter what its length
1238 gdouble const zoom = desktop->current_zoom();
1239 gdouble const zby = (by
1240 / (zoom * (nb_let > 1 ? nb_let - 1 : 1))
1241 / cast<SPItem>(source_obj)->i2doc_affine().descrim());
1242 val += zby;
1243
1244 if (start == end) {
1245 // set back value to entire paragraph
1246 style->letter_spacing.normal = FALSE;
1247 if (style->letter_spacing.value != 0 && style->letter_spacing.computed == 0) { // set in em or ex
1248 if (style->letter_spacing.unit == SP_CSS_UNIT_EM) {
1249 style->letter_spacing.value = val / style->font_size.computed;
1250 } else if (style->letter_spacing.unit == SP_CSS_UNIT_EX) {
1251 style->letter_spacing.value = val / style->font_size.computed * 2;
1252 }
1253 } else {
1254 style->letter_spacing.computed = val;
1255 }
1256
1257 style->letter_spacing.set = TRUE;
1258 } else {
1259 // apply to selection only
1261 char string_val[40];
1262 g_snprintf(string_val, sizeof(string_val), "%f", val);
1263 sp_repr_css_set_property(css, "letter-spacing", string_val);
1264 sp_te_apply_style(text, start, end, css);
1266 }
1267
1268 text->updateRepr();
1269 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1270}
1271
1272// Only used for page-up and page-down and sp_te_adjust_linespacing_screen
1273double
1275{
1276 Inkscape::Text::Layout const *layout = te_get_layout(text);
1277 if (!layout)
1278 return 0;
1279
1280 unsigned line_count = layout->lineIndex(layout->end());
1281 auto mode = text->style->writing_mode.computed;
1282 bool horizontal = (mode == SP_CSS_WRITING_MODE_LR_TB || mode == SP_CSS_WRITING_MODE_RL_TB);
1283 auto index = horizontal ? Geom::Y : Geom::X;
1284 double all_lines_height = layout->characterAnchorPoint(layout->end())[index] - layout->characterAnchorPoint(layout->begin())[index];
1285 double average_line_height = all_lines_height / (line_count == 0 ? 1 : line_count);
1287 average_line_height = -average_line_height;
1288 }
1289 return average_line_height;
1290}
1291
1296void
1297sp_te_adjust_line_height (SPObject *object, double amount, double average, bool top_level = true) {
1298
1299 SPStyle *style = object->style;
1300
1301 // Always set if top level true.
1302 // Also set if line_height is set to a non-zero value.
1303 if (top_level ||
1304 (style->line_height.set && !style->line_height.inherit && style->line_height.computed != 0)){
1305
1306 // Scale default values
1307 if (!style->line_height.set || style->line_height.inherit || style->line_height.normal) {
1308 style->line_height.set = TRUE;
1309 style->line_height.inherit = FALSE;
1310 style->line_height.normal = FALSE;
1311 style->line_height.unit = SP_CSS_UNIT_NONE;
1313 }
1314
1315 switch (style->line_height.unit) {
1316
1317 case SP_CSS_UNIT_NONE:
1318 default:
1319 // Multiplier-type units, stored in computed
1320 if (fabs(style->line_height.computed) < 0.001) {
1321 style->line_height.computed = amount < 0.0 ? -0.001 : 0.001;
1322 // the formula below could get stuck at zero
1323 } else {
1324 style->line_height.computed *= (average + amount) / average;
1325 }
1326 style->line_height.value = style->line_height.computed;
1327 break;
1328
1329
1330 // Relative units, stored in value
1331 case SP_CSS_UNIT_EM:
1332 case SP_CSS_UNIT_EX:
1334 if (fabs(style->line_height.value) < 0.001) {
1335 style->line_height.value = amount < 0.0 ? -0.001 : 0.001;
1336 } else {
1337 style->line_height.value *= (average + amount) / average;
1338 }
1339 break;
1340
1341
1342 // Absolute units
1343 case SP_CSS_UNIT_PX:
1344 style->line_height.computed += amount;
1345 style->line_height.value = style->line_height.computed;
1346 break;
1347 case SP_CSS_UNIT_PT:
1348 style->line_height.computed += Inkscape::Util::Quantity::convert(amount, "px", "pt");
1349 style->line_height.value = style->line_height.computed;
1350 break;
1351 case SP_CSS_UNIT_PC:
1352 style->line_height.computed += Inkscape::Util::Quantity::convert(amount, "px", "pc");
1353 style->line_height.value = style->line_height.computed;
1354 break;
1355 case SP_CSS_UNIT_MM:
1356 style->line_height.computed += Inkscape::Util::Quantity::convert(amount, "px", "mm");
1357 style->line_height.value = style->line_height.computed;
1358 break;
1359 case SP_CSS_UNIT_CM:
1360 style->line_height.computed += Inkscape::Util::Quantity::convert(amount, "px", "cm");
1361 style->line_height.value = style->line_height.computed;
1362 break;
1363 case SP_CSS_UNIT_IN:
1364 style->line_height.computed += Inkscape::Util::Quantity::convert(amount, "px", "in");
1365 style->line_height.value = style->line_height.computed;
1366 break;
1367 }
1368 object->updateRepr();
1369 }
1370
1371 std::vector<SPObject*> children = object->childList(false);
1372 for (auto child: children) {
1373 sp_te_adjust_line_height (child, amount, average, false);
1374 }
1375}
1376
1377void
1379{
1380 // TODO: use start and end iterators to delineate the area to be affected
1381 g_return_if_fail (text != nullptr);
1382 g_return_if_fail (is<SPText>(text) || is<SPFlowtext>(text));
1383
1384 Inkscape::Text::Layout const *layout = te_get_layout(text);
1385
1386 double average_line_height = sp_te_get_average_linespacing (text);
1387 if (fabs(average_line_height) < 0.001) average_line_height = 0.001;
1388
1389 // divide increment by zoom and by the number of lines,
1390 // so that the entire object is expanded by by pixels
1391 unsigned line_count = layout->lineIndex(layout->end());
1392 gdouble zby = by / (desktop->current_zoom() * (line_count == 0 ? 1 : line_count));
1393
1394 // divide increment by matrix expansion
1395 Geom::Affine t(text->i2doc_affine());
1396 zby = zby / t.descrim();
1397
1398 sp_te_adjust_line_height (text, zby, average_line_height, false);
1399
1400 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
1401}
1402
1403
1404/* ***************************************************************************************************/
1405// S T Y L E A P P L I C A T I O N
1406
1407
1410static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
1411{
1412 unsigned n = 0;
1413 for (Glib::ustring::const_iterator it = string.begin() ; it != string.end() && it != text_iter ; ++it)
1414 n++;
1415 return n;
1416}
1417
1418// Move to style.h?
1422static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
1423{
1424 SPStyle style(item->document);
1425 style.mergeString(style_string);
1426 gchar const *item_style_string = item->getRepr()->attribute("style");
1427 if (item_style_string && *item_style_string) {
1428 style.mergeString(item_style_string);
1429 }
1430 Glib::ustring new_style_string = style.write();
1431 item->setAttributeOrRemoveIfEmpty("style", new_style_string);
1432}
1433
1434// Move to style.h?
1440{
1441 // the obvious implementation of strcmp(style_write_all(parent), style_write_all(child))
1442 // will not work. Firstly because of an inheritance bug in style.cpp that has
1443 // implications too large for me to feel safe fixing, but mainly because the css spec
1444 // requires that the computed value is inherited, not the specified value.
1445 g_assert(parent->isAncestorOf(child));
1446
1447 Glib::ustring parent_style = parent->style->write( SP_STYLE_FLAG_ALWAYS );
1448
1449 // we have to write parent_style then read it again, because some properties format their values
1450 // differently depending on whether they're set or not (*cough*dash-offset*cough*)
1451 SPStyle parent_spstyle(parent->document);
1452 parent_spstyle.mergeString(parent_style.c_str());
1453 parent_style = parent_spstyle.write(SP_STYLE_FLAG_ALWAYS);
1454
1455 Glib::ustring child_style_construction;
1456 while (child != parent) {
1457 // FIXME: this assumes that child's style is only in style= whereas it can also be in css attributes!
1458 char const *style_text = child->getRepr()->attribute("style");
1459 if (style_text && *style_text) {
1460 child_style_construction.insert(0, style_text);
1461 child_style_construction.insert(0, 1, ';');
1462 }
1463 child = child->parent;
1464 }
1465 child_style_construction.insert(0, parent_style);
1466
1467 SPStyle child_spstyle(parent->document);
1468 child_spstyle.mergeString(child_style_construction.c_str());
1469 Glib::ustring child_style = child_spstyle.write(SP_STYLE_FLAG_ALWAYS);
1470
1471 bool equal = (child_style == parent_style); // Glib::ustring overloads == operator
1472 return equal;
1473}
1474
1479static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
1480{
1481 for ( const auto & attr : first->attributeList()) {
1482 gchar const *other_attr = second->attribute(g_quark_to_string(attr.key));
1483 if (other_attr == nullptr || strcmp(attr.value, other_attr))
1484 return false;
1485 }
1486 for (const auto & attr : second->attributeList()) {
1487 gchar const *other_attr = first->attribute(g_quark_to_string(attr.key));
1488 if (other_attr == nullptr || strcmp(attr.value, other_attr))
1489 return false;
1490 }
1491 return true;
1492}
1493
1498{
1499 sp_repr_css_change(o->getRepr(), const_cast<SPCSSAttr*>(css), "style");
1500
1501 for (auto& child: o->children) {
1502 if (sp_repr_css_property(const_cast<SPCSSAttr*>(css), "opacity", nullptr) != nullptr) {
1503 // Unset properties which are accumulating and thus should not be set recursively.
1504 // 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.
1505 SPCSSAttr *css_recurse = sp_repr_css_attr_new();
1506 sp_repr_css_merge(css_recurse, const_cast<SPCSSAttr*>(css));
1507 sp_repr_css_set_property(css_recurse, "opacity", nullptr);
1508 apply_css_recursive(&child, css_recurse);
1509 sp_repr_css_attr_unref(css_recurse);
1510 } else {
1511 apply_css_recursive(&child, const_cast<SPCSSAttr*>(css));
1512 }
1513 }
1514}
1515
1521static 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)
1522{
1523 bool passed_start = start_item == nullptr ? true : false;
1524 Inkscape::XML::Document *xml_doc = common_ancestor->document->getReprDoc();
1525
1526 for (SPObject *child = common_ancestor->firstChild() ; child ; child = child->getNext()) {
1527 if (start_item == child) {
1528 passed_start = true;
1529 }
1530
1531 if (passed_start) {
1532 if (end_item && child->isAncestorOf(end_item)) {
1533 recursively_apply_style(child, css, nullptr, start_text_iter, end_item, end_text_iter, span_object_name);
1534 break;
1535 }
1536 // apply style
1537
1538 // note that when adding stuff we must make sure that 'child' stays valid so the for loop keeps working.
1539 // often this means that new spans are created before child and child is modified only
1540 if (is<SPString>(child)) {
1541 auto string_item = cast<SPString>(child);
1542 bool surround_entire_string = true;
1543
1544 Inkscape::XML::Node *child_span = xml_doc->createElement(span_object_name);
1545 sp_repr_css_set(child_span, const_cast<SPCSSAttr*>(css), "style"); // better hope that prototype wasn't nonconst for a good reason
1546 SPObject *prev_item = child->getPrev();
1547 Inkscape::XML::Node *prev_repr = prev_item ? prev_item->getRepr() : nullptr;
1548
1549 if (child == start_item || child == end_item) {
1550 surround_entire_string = false;
1551 if (start_item == end_item && start_text_iter != string_item->string.begin()) {
1552 // eg "abcDEFghi" -> "abc"<span>"DEF"</span>"ghi"
1553 unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1554 unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1555
1556 Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1557 common_ancestor->getRepr()->addChild(text_before, prev_repr);
1558 common_ancestor->getRepr()->addChild(child_span, text_before);
1559 Inkscape::GC::release(text_before);
1560 Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index, end_char_index - start_char_index).c_str());
1561 child_span->appendChild(text_in_span);
1562 Inkscape::GC::release(text_in_span);
1563 child->getRepr()->setContent(string_item->string.substr(end_char_index).c_str());
1564 } else if (child == end_item) {
1565 // eg "ABCdef" -> <span>"ABC"</span>"def"
1566 // (includes case where start_text_iter == begin())
1567 // NB: we might create an empty string here. Doesn't matter, it'll get cleaned up later
1568 unsigned end_char_index = char_index_of_iterator(string_item->string, end_text_iter);
1569
1570 common_ancestor->getRepr()->addChild(child_span, prev_repr);
1571 Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(0, end_char_index).c_str());
1572 child_span->appendChild(text_in_span);
1573 Inkscape::GC::release(text_in_span);
1574 child->getRepr()->setContent(string_item->string.substr(end_char_index).c_str());
1575 } else if (start_text_iter != string_item->string.begin()) {
1576 // eg "abcDEF" -> "abc"<span>"DEF"</span>
1577 unsigned start_char_index = char_index_of_iterator(string_item->string, start_text_iter);
1578
1579 Inkscape::XML::Node *text_before = xml_doc->createTextNode(string_item->string.substr(0, start_char_index).c_str());
1580 common_ancestor->getRepr()->addChild(text_before, prev_repr);
1581 common_ancestor->getRepr()->addChild(child_span, text_before);
1582 Inkscape::GC::release(text_before);
1583 Inkscape::XML::Node *text_in_span = xml_doc->createTextNode(string_item->string.substr(start_char_index).c_str());
1584 child_span->appendChild(text_in_span);
1585 Inkscape::GC::release(text_in_span);
1586 child->deleteObject();
1587 child = common_ancestor->get_child_by_repr(child_span);
1588
1589 } else
1590 surround_entire_string = true;
1591 }
1592 if (surround_entire_string) {
1593 Inkscape::XML::Node *child_repr = child->getRepr();
1594 common_ancestor->getRepr()->addChild(child_span, child_repr);
1595 Inkscape::GC::anchor(child_repr);
1596 common_ancestor->getRepr()->removeChild(child_repr);
1597 child_span->appendChild(child_repr);
1598 Inkscape::GC::release(child_repr);
1599 child = common_ancestor->get_child_by_repr(child_span);
1600 }
1601 Inkscape::GC::release(child_span);
1602
1603 } else if (child != end_item) { // not a string and we're applying to the entire object. This is easy
1605 }
1606
1607 } else { // !passed_start
1608 if (child->isAncestorOf(start_item)) {
1609 recursively_apply_style(child, css, start_item, start_text_iter, end_item, end_text_iter, span_object_name);
1610 if (end_item && child->isAncestorOf(end_item))
1611 break; // only happens when start_item == end_item (I think)
1612 passed_start = true;
1613 }
1614 }
1615
1616 if (end_item == child)
1617 break;
1618 }
1619}
1620
1621/* if item is at the beginning of a tree it doesn't matter which element
1622it points to so for neatness we would like it to point to the highest
1623possible child of \a common_ancestor. There is no iterator return because
1624a string can never be an ancestor.
1625
1626eg: <span><span>*ABC</span>DEFghi</span> where * is the \a item. We would
1627like * to point to the inner span because we can apply style to that whole
1628span. */
1629static SPObject* ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
1630{
1631 if (item == common_ancestor)
1632 return item;
1633 if (is<SPString>(item))
1634 if (text_iter != cast<SPString>(item)->string.begin())
1635 return item;
1636 for ( ; ; ) {
1638 if (parent == common_ancestor) {
1639 break;
1640 }
1641 if (item != parent->firstChild()) {
1642 break;
1643 }
1644 item = parent;
1645 }
1646 return item;
1647}
1648
1649
1652static bool tidy_operator_empty_spans(SPObject **item, bool /*has_text_decoration*/)
1653{
1654 bool result = false;
1655 if ( !(*item)->hasChildren()
1657 && !(is<SPString>(*item) && !cast_unsafe<SPString>(*item)->string.empty())
1658 ) {
1659 SPObject *next = (*item)->getNext();
1660 (*item)->deleteObject();
1661 *item = next;
1662 result = true;
1663 }
1664 return result;
1665}
1666
1670static bool tidy_operator_inexplicable_spans(SPObject **item, bool /*has_text_decoration*/)
1671{
1672 //XML Tree being directly used here while it shouldn't be.
1673 if (*item && sp_repr_is_meta_element((*item)->getRepr())) {
1674 return false;
1675 }
1676 if (is<SPString>(*item)) {
1677 return false;
1678 }
1679 if (is_line_break_object(*item)) {
1680 return false;
1681 }
1683 if (attrs && attrs->anyAttributesSet()) {
1684 return false;
1685 }
1686 if (!objects_have_equal_style((*item)->parent, *item)) {
1687 return false;
1688 }
1689 SPObject *next = *item;
1690 while ((*item)->hasChildren()) {
1691 Inkscape::XML::Node *repr = (*item)->firstChild()->getRepr();
1693 (*item)->getRepr()->removeChild(repr);
1694 (*item)->parent->getRepr()->addChild(repr, next->getRepr());
1696 next = next->getNext();
1697 }
1698 (*item)->deleteObject();
1699 *item = next;
1700 return true;
1701}
1702
1705static bool tidy_operator_repeated_spans(SPObject **item, bool /*has_text_decoration*/)
1706{
1707 SPObject *first = *item;
1708 SPObject *second = first->getNext();
1709 if (second == nullptr) return false;
1710
1711 Inkscape::XML::Node *first_repr = first->getRepr();
1712 Inkscape::XML::Node *second_repr = second->getRepr();
1713
1714 if (first_repr->type() != second_repr->type()) return false;
1715
1716 if (is<SPString>(first) && is<SPString>(second)) {
1717 // also amalgamate consecutive SPStrings into one
1718 Glib::ustring merged_string = cast<SPString>(first)->string + cast<SPString>(second)->string;
1719 first->getRepr()->setContent(merged_string.c_str());
1720 second_repr->parent()->removeChild(second_repr);
1721 return true;
1722 }
1723
1724 // merge consecutive spans with identical styles into one
1725 if (first_repr->type() != Inkscape::XML::NodeType::ELEMENT_NODE) return false;
1726 if (strcmp(first_repr->name(), second_repr->name()) != 0) return false;
1727 if (is_line_break_object(second)) return false;
1728 gchar const *first_style = first_repr->attribute("style");
1729 gchar const *second_style = second_repr->attribute("style");
1730 if (!((first_style == nullptr && second_style == nullptr)
1731 || (first_style != nullptr && second_style != nullptr && !strcmp(first_style, second_style))))
1732 return false;
1733
1734 // all our tests passed: do the merge
1735 TextTagAttributes *attributes_first = attributes_for_object(first);
1736 TextTagAttributes *attributes_second = attributes_for_object(second);
1737 if (attributes_first && attributes_second && attributes_second->anyAttributesSet()) {
1738 TextTagAttributes attributes_first_copy = *attributes_first;
1739 attributes_first->join(attributes_first_copy, *attributes_second, sp_text_get_length(first));
1740 }
1741 move_child_nodes(second_repr, first_repr);
1742 second_repr->parent()->removeChild(second_repr);
1743 return true;
1744 // *item is still the next object to process
1745}
1746
1751static bool tidy_operator_excessive_nesting(SPObject **item, bool /*has_text_decoration*/)
1752{
1753 if (!(*item)->hasChildren()) {
1754 return false;
1755 }
1756 if ((*item)->firstChild() != (*item)->lastChild()) {
1757 return false;
1758 }
1759 if (is<SPFlowregion>((*item)->firstChild()) || is<SPFlowregionExclude>((*item)->firstChild())) {
1760 return false;
1761 }
1762 if (is<SPString>((*item)->firstChild())) {
1763 return false;
1764 }
1765 if (is_line_break_object((*item)->firstChild())) {
1766 return false;
1767 }
1768 TextTagAttributes *attrs = attributes_for_object((*item)->firstChild());
1769 if (attrs && attrs->anyAttributesSet()) {
1770 return false;
1771 }
1772 gchar const *child_style = (*item)->firstChild()->getRepr()->attribute("style");
1773 if (child_style && *child_style) {
1774 overwrite_style_with_string(*item, child_style);
1775 }
1776 move_child_nodes((*item)->firstChild()->getRepr(), (*item)->getRepr());
1777 (*item)->firstChild()->deleteObject();
1778 return true;
1779}
1780
1783{
1784 if (is<SPFlowregion>(child) || is<SPFlowregionExclude>(child)) {
1785 return false;
1786 }
1787 if (is<SPString>(child)) {
1788 return false;
1789 }
1791 return false;
1792 }
1793 if (is_line_break_object(*item)) {
1794 return false;
1795 }
1797 if (attrs && attrs->anyAttributesSet()) {
1798 return false;
1799 }
1800 if (!objects_have_equal_style((*item)->parent, child)) {
1801 return false;
1802 }
1803
1804 Inkscape::XML::Node *insert_after_repr = nullptr;
1805 if (!prepend) {
1806 insert_after_repr = (*item)->getRepr();
1807 } else if ((*item)->getPrev()) {
1808 insert_after_repr = (*item)->getPrev()->getRepr();
1809 }
1810 while (child->getRepr()->childCount()) {
1811 Inkscape::XML::Node *move_repr = child->getRepr()->firstChild();
1812 Inkscape::GC::anchor(move_repr);
1813 child->getRepr()->removeChild(move_repr);
1814 (*item)->parent->getRepr()->addChild(move_repr, insert_after_repr);
1815 Inkscape::GC::release(move_repr);
1816 insert_after_repr = move_repr; // I think this will stay valid long enough. It's garbage collected these days.
1817 }
1818 child->deleteObject();
1819 return true;
1820}
1821
1829static bool tidy_operator_redundant_double_nesting(SPObject **item, bool /*has_text_decoration*/)
1830{
1831 if (!(*item)->hasChildren()) return false;
1832 if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is excessive nesting, done above
1833 if (redundant_double_nesting_processor(item, (*item)->firstChild(), true))
1834 return true;
1835 if (redundant_double_nesting_processor(item, (*item)->lastChild(), false))
1836 return true;
1837 return false;
1838}
1839
1844{
1845 if (is<SPFlowregion>(child) || is<SPFlowregionExclude>(child))
1846 return false;
1847 if (is<SPString>(child)) return false;
1848 if (is_line_break_object(child)) return false;
1849 if (is_line_break_object(*item)) return false;
1851 if (attrs && attrs->anyAttributesSet()) return false;
1852 attrs = attributes_for_object(*item);
1853 if (attrs && attrs->anyAttributesSet()) return false;
1854
1855 SPCSSAttr *css_child_and_item = sp_repr_css_attr_new();
1856 SPCSSAttr *css_child_only = sp_repr_css_attr_new();
1857 gchar const *item_style = (*item)->getRepr()->attribute("style");
1858 if (item_style && *item_style) {
1859 sp_repr_css_attr_add_from_string(css_child_and_item, item_style);
1860 }
1861 gchar const *child_style = child->getRepr()->attribute("style");
1862 if (child_style && *child_style) {
1863 sp_repr_css_attr_add_from_string(css_child_and_item, child_style);
1864 sp_repr_css_attr_add_from_string(css_child_only, child_style);
1865 }
1866 bool equal = css_attrs_are_equal(css_child_only, css_child_and_item);
1867 sp_repr_css_attr_unref(css_child_and_item);
1868 sp_repr_css_attr_unref(css_child_only);
1869 if (!equal) return false;
1870
1871 Inkscape::XML::Document *xml_doc = (*item)->getRepr()->document();
1872 Inkscape::XML::Node *new_span = xml_doc->createElement((*item)->getRepr()->name());
1873 if (prepend) {
1874 SPObject *prev = (*item)->getPrev();
1875 (*item)->parent->getRepr()->addChild(new_span, prev ? prev->getRepr() : nullptr);
1876 } else {
1877 (*item)->parent->getRepr()->addChild(new_span, (*item)->getRepr());
1878 }
1879 new_span->setAttribute("style", child->getRepr()->attribute("style"));
1880 move_child_nodes(child->getRepr(), new_span);
1881 Inkscape::GC::release(new_span);
1882 child->deleteObject();
1883 return true;
1884}
1885
1890static bool tidy_operator_redundant_semi_nesting(SPObject **item, bool /*has_text_decoration*/)
1891{
1892 if (!(*item)->hasChildren()) return false;
1893 if ((*item)->firstChild() == (*item)->lastChild()) return false; // this is redundant nesting, done above
1894 if (redundant_semi_nesting_processor(item, (*item)->firstChild(), true))
1895 return true;
1896 if (redundant_semi_nesting_processor(item, (*item)->lastChild(), false))
1897 return true;
1898 return false;
1899}
1900
1901
1902/* tidy_operator_styled_whitespace commented out: not only did it have bugs,
1903 * but it did *not* preserve the rendering: spaces in different font sizes,
1904 * for instance, have different width, so moving them out of tspans changes
1905 * the document. cf https://bugs.launchpad.net/inkscape/+bug/1477723
1906*/
1907
1908#if 0
1912{
1913 for (SPObject *child = root->lastChild() ; child ; child = child->getPrev())
1914 {
1915 if (child == not_obj) continue;
1916 if (child->hasChildren()) {
1918 if (ret) return ret;
1919 } else if (is<SPString>(child))
1920 return cast<SPString>(child);
1921 }
1922 return NULL;
1923}
1924
1936static bool tidy_operator_styled_whitespace(SPObject **item, bool has_text_decoration)
1937{
1938 // any type of visible text decoration is OK as pure spaces, so do nothing here in that case.
1939 if (!is<SPString>(*item) || has_text_decoration ) {
1940 return false;
1941 }
1942 Glib::ustring const &str = cast<SPString>(*item)->string;
1943 for (Glib::ustring::const_iterator it = str.begin() ; it != str.end() ; ++it) {
1944 if (!g_unichar_isspace(*it)) {
1945 return false;
1946 }
1947 }
1948
1949
1950 SPObject *test_item = *item;
1951 SPString *next_string;
1952 for ( ; ; ) { // find the next string
1953 next_string = sp_te_seek_next_string_recursive(test_item->getNext());
1954 if (next_string) {
1955 next_string->string.insert(0, str);
1956 break;
1957 }
1958 for ( ; ; ) { // go up one item in the xml
1959 test_item = test_item->parent;
1960 if (is_line_break_object(test_item)) {
1961 break;
1962 }
1963 if (is<SPFlowtext>(test_item)) {
1964 return false;
1965 }
1966 SPObject *next = test_item->getNext();
1967 if (next) {
1968 test_item = next;
1969 break;
1970 }
1971 }
1972 if (is_line_break_object(test_item)) { // no next string, see if there's a prev string
1973 next_string = find_last_string_child_not_equal_to(test_item, *item);
1974 if (next_string == NULL) {
1975 return false; // an empty paragraph
1976 }
1977 next_string->string = str + next_string->string;
1978 break;
1979 }
1980 }
1981 next_string->getRepr()->setContent(next_string->string.c_str());
1982 SPObject *delete_obj = *item;
1983 *item = (*item)->getNext();
1984 delete_obj->deleteObject();
1985 return true;
1986}
1987#endif
1988
1989/* possible tidy operators that are not yet implemented, either because
1990they are difficult, occur infrequently, or because I'm not sure that the
1991output is tidier in all cases:
1992 duplicate styles in line break elements: <div italic><para italic>abc</para></div>
1993 -> <div italic><para>abc</para></div>
1994 style inversion: <font a>abc<font b>def<font a>ghi</font>jkl</font>mno</font>
1995 -> <font a>abc<font b>def</font>ghi<font b>jkl</font>mno</font>
1996 mistaken precedence: <font a,size 1>abc</font><size 1>def</size>
1997 -> <size 1><font a>abc</font>def</size>
1998*/
1999
2012static bool tidy_xml_tree_recursively(SPObject *root, bool has_text_decoration)
2013{
2014 gchar const *root_style = (root)->getRepr()->attribute("style");
2015 if(root_style && strstr(root_style,"text-decoration"))has_text_decoration = true;
2016 static bool (* const tidy_operators[])(SPObject**, bool) = {
2023 };
2024 bool changes = false;
2025
2026 for (SPObject *child = root->firstChild() ; child != nullptr ; ) {
2027 if (is<SPFlowregion>(child) || is<SPFlowregionExclude>(child) || is<SPTRef>(child)) {
2028 child = child->getNext();
2029 continue;
2030 }
2031 if (child->hasChildren()) {
2032 changes |= tidy_xml_tree_recursively(child, has_text_decoration);
2033 }
2034
2035 unsigned i;
2036 for (i = 0 ; i < sizeof(tidy_operators) / sizeof(tidy_operators[0]) ; i++) {
2037 if (tidy_operators[i](&child, has_text_decoration)) {
2038 changes = true;
2039 break;
2040 }
2041 }
2042 if (i == sizeof(tidy_operators) / sizeof(tidy_operators[0])) {
2043 child = child->getNext();
2044 }
2045 }
2046 return changes;
2047}
2048
2053{
2054 // in the comments in the code below, capital letters are inside the application region, lowercase are outside
2055 if (start == end) return;
2057 if (start < end) {
2058 first = start;
2059 last = end;
2060 } else {
2061 first = end;
2062 last = start;
2063 }
2064 Inkscape::Text::Layout const *layout = te_get_layout(text);
2065 SPObject *start_item = nullptr, *end_item = nullptr;
2066 Glib::ustring::iterator start_text_iter, end_text_iter;
2067 layout->getSourceOfCharacter(first, &start_item, &start_text_iter);
2068 layout->getSourceOfCharacter(last, &end_item, &end_text_iter);
2069 if (start_item == nullptr) {
2070 return; // start is at end of text
2071 }
2072 if (is_line_break_object(start_item)) {
2073 start_item = start_item->getNext();
2074 }
2075 if (is_line_break_object(end_item)) {
2076 end_item = end_item->getNext();
2077 }
2078 if (end_item == nullptr) {
2079 end_item = text;
2080 }
2081
2082
2083 /* Special case: With a tref, we only want to change its style when the whole
2084 * string is selected, in which case the style can be applied directly to the
2085 * tref node. If only part of the tref's string child is selected, just return. */
2086
2087 if (!sp_tref_fully_contained(start_item, start_text_iter, end_item, end_text_iter)) {
2088
2089 return;
2090 }
2091
2092 /* stage 1: applying the style. Go up to the closest common ancestor of
2093 start and end and then semi-recursively apply the style to all the
2094 objects in between. The semi-recursion is because it's only necessary
2095 at the beginning and end; the style can just be applied to the root
2096 child in the middle.
2097 eg: <span>abcDEF</span><span>GHI</span><span>JKLmno</span>
2098 The recursion may involve creating new spans.
2099 */
2100 SPObject *common_ancestor = get_common_ancestor(text, start_item, end_item);
2101
2102 // bug #168370 (consider parent transform and viewBox)
2103 // snipplet copied from desktop-style.cpp sp_desktop_apply_css_recursive(...)
2104 SPCSSAttr *css_set = sp_repr_css_attr_new();
2105 sp_repr_css_merge(css_set, const_cast<SPCSSAttr*>(css));
2106 {
2107 Geom::Affine const local(cast<SPItem>(common_ancestor)->i2doc_affine());
2108 double const ex(local.descrim());
2109 if ( ( ex != 0. )
2110 && ( ex != 1. ) ) {
2111 sp_css_attr_scale(css_set, 1/ex);
2112 }
2113 }
2114
2115 start_item = ascend_while_first(start_item, start_text_iter, common_ancestor);
2116 end_item = ascend_while_first(end_item, end_text_iter, common_ancestor);
2117 recursively_apply_style(common_ancestor, css_set, start_item, start_text_iter, end_item, end_text_iter, span_name_for_text_object(text));
2118 sp_repr_css_attr_unref(css_set);
2119
2120 /* stage 2: cleanup the xml tree (of which there are multiple passes) */
2121 /* discussion: this stage requires a certain level of inventiveness because
2122 it's not clear what the best representation is in many cases. An ideal
2123 implementation would provide some sort of scoring function to rate the
2124 ugliness of a given xml tree and try to reduce said function, but providing
2125 the various possibilities to be rated is non-trivial. Instead, I have opted
2126 for a multi-pass technique which simply recognises known-ugly patterns and
2127 has matching routines for optimising the patterns it finds. It's reasonably
2128 easy to add new pattern matching processors. If everything gets disastrous
2129 and neither option can be made to work, a fallback could be to reduce
2130 everything to a single level of nesting and drop all pretense of
2131 roundtrippability. */
2132 bool has_text_decoration = false;
2133 gchar const *root_style = (text)->getRepr()->attribute("style");
2134 if(root_style && strstr(root_style,"text-decoration")) has_text_decoration = true;
2135 while (tidy_xml_tree_recursively(common_ancestor, has_text_decoration)){};
2136
2137 // update layout right away, so any pending selection change will use valid data;
2138 // resolves https://gitlab.com/inkscape/inkscape/-/issues/3954 (use after free),
2139 // where recursively_apply_style deletes a child and later text_tag_attributes_at_position
2140 // tries to use deleted SPString pointed to by stale text layout;
2141 // note: requestDisplayUpdate will update layout too, but only on idle (so too late)
2143
2144 // if we only modified subobjects this won't have been automatically sent
2145 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
2146}
2147
2149{
2150 return is<SPTSpan>(obj)
2151 || is<SPText>(obj)
2152 || is<SPFlowtext>(obj)
2153 || is<SPFlowtspan>(obj)
2154 || is<SPFlowdiv>(obj)
2155 || is<SPFlowpara>(obj)
2156 || is<SPFlowline>(obj)
2157 || is<SPFlowregionbreak>(obj);
2158}
2159
2161{
2162 return is<SPText>(obj)
2163 || is<SPFlowtext>(obj);
2164}
2165
2167{
2168 bool hasVisible = false;
2169
2170 if (is<SPString>(obj) && !cast_unsafe<SPString>(obj)->string.empty()) {
2171 hasVisible = true; // maybe we should also check that it's not all whitespace?
2172 } else if (is_part_of_text_subtree(obj)) {
2173 for (auto& child: obj->children) {
2174 if (has_visible_text(&child)) {
2175 hasVisible = true;
2176 break;
2177 }
2178 }
2179 }
2180
2181 return hasVisible;
2182}
2183
2184/*
2185 Local Variables:
2186 mode:c++
2187 c-file-style:"stroustrup"
2188 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2189 indent-tabs-mode:nil
2190 fill-column:99
2191 End:
2192*/
2193// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
TODO: insert short description here.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Two-dimensional point that doubles as a vector.
Definition point.h:66
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
Holds a position within the glyph output of Layout.
Definition Layout-TNG.h:973
Generates the layout for either wrapped or non-wrapped text and stores the result.
Definition Layout-TNG.h:144
std::vector< Geom::Point > createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const
Basically uses characterBoundingBox() on all the characters from start to end and returns the union o...
iterator charIndexToIterator(int char_index) const
Returns an iterator pointing at the given character index.
void queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
Gets the ideal cursor shape for a given iterator.
unsigned lineIndex(iterator const &it) const
Returns the zero-based line number of the character pointed to by it.
void validateIterator(iterator *it) const
Checks the validity of the given iterator over the current layout.
static const double LINE_HEIGHT_NORMAL
The CSS spec allows line-height:normal to be whatever the user agent thinks will look good.
Definition Layout-TNG.h:209
Geom::Point characterAnchorPoint(iterator const &it) const
For latin text, the left side of the character, on the baseline.
int iteratorToCharIndex(iterator const &it) const
Returns the character index from the start of the flow represented by the given iterator.
iterator end() const
Returns an iterator pointing just past the end of the last glyph, which is also just past the end of ...
void getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator=nullptr) const
Discovers where the character pointed to by it came from, by retrieving the object that was passed to...
iterator getNearestCursorPositionTo(double x, double y) const
Returns an iterator pointing to the cursor position for a mouse click at the given coordinates.
iterator begin() const
Returns an iterator pointing at the first glyph of the flowed output.
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
virtual const AttributeVector & attributeList() const =0
Get a list of the node's attributes.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * firstChild()=0
Get the first child of this node.
virtual void setContent(char const *value)=0
Set the content of a text or comment node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual char const * content() const =0
Get the content of a text or comment node.
virtual Document * document()=0
Get the node's associated document.
virtual void removeChild(Node *child)=0
Remove a child of this node.
virtual unsigned childCount() const =0
Get the number of children of this node.
virtual NodeType type() const =0
Get the type of the node.
virtual Node * lastChild()=0
Get the last child of this node.
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPObject * getNext()
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Definition sp-object.h:785
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
SPObject * lastChild()
Definition sp-object.h:318
SPObject * get_child_by_repr(Inkscape::XML::Node *repr)
Return object's child whose node pointer equals repr.
SPDocument * document
Definition sp-object.h:188
SPObject * firstChild()
Definition sp-object.h:315
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * getPrev()
Returns previous object in sibling list or NULL.
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
bool hasChildren() const
Definition sp-object.h:313
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
bool isAncestorOf(SPObject const *object) const
True if object is non-NULL and this is some in/direct parent of object.
ChildrenList children
Definition sp-object.h:907
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
Glib::ustring string
Definition sp-string.h:28
An SVG style object.
Definition style.h:45
T< SPAttr::LINE_HEIGHT, SPILengthOrNormal > line_height
Line height (css2 10.8.1)
Definition style.h:118
T< SPAttr::LETTER_SPACING, SPILengthOrNormal > letter_spacing
letter spacing (css2 16.4)
Definition style.h:153
Glib::ustring write(unsigned flags, SPStyleSrc style_src_req, SPStyle const *base=nullptr) const
void mergeString(char const *p)
Parses a style="..." string and merges it with an existing SPStyle.
Definition style.cpp:854
T< SPAttr::WRITING_MODE, SPIEnum< SPCSSWritingMode > > writing_mode
Writing mode (svg1.1 10.7.2, CSS Writing Modes 3)
Definition style.h:163
T< SPAttr::FONT_SIZE, SPIFontSize > font_size
Size of the font.
Definition style.h:116
void split(unsigned index, TextTagAttributes *second)
Divides the stored attributes into two, at the given index.
Definition sp-text.cpp:1606
void setRotate(unsigned index, double angle)
Sets rotate vector at the given index.
Definition sp-text.cpp:1785
void addToDy(unsigned index, double delta)
Adds the given value to the dy vector at the given index.
Definition sp-text.cpp:1733
void addToDxDy(unsigned index, Geom::Point const &adjust)
Adds the given values to the dx and dy vectors at the given index.
Definition sp-text.cpp:1742
void addToRotate(unsigned index, double delta)
Adds the given value to the rotate vector at the given index.
Definition sp-text.cpp:1770
void erase(unsigned start_index, unsigned n)
Deletes all the values from all the vectors beginning at start_index and extending for n fields.
Definition sp-text.cpp:1559
bool anyAttributesSet() const
Returns false if all of the vectors are zero length.
Definition sp-text.cpp:1464
void writeTo(Inkscape::XML::Node *node) const
Write out all the contents of attributes to the given node.
Definition sp-text.cpp:1400
Inkscape::Text::Layout::OptionalTextTagAttrs attributes
This holds the actual values.
void insert(unsigned start_index, unsigned n)
Inserts n new values in all the stored vectors at start_index.
Definition sp-text.cpp:1580
void addToDx(unsigned index, double delta)
Adds the given value to the dx vector at the given index.
Definition sp-text.cpp:1724
void join(TextTagAttributes const &first, TextTagAttributes const &second, unsigned second_index)
Overwrites all the attributes contained in this object with the given parameters by putting first at ...
Definition sp-text.cpp:1629
RootCluster root
std::shared_ptr< Css const > css
Css & result
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
SPItem * item
Geom::Point start
Geom::Point end
Raw stack of active status messages.
static R & anchor(R &r)
Increments the reference count of a anchored object.
Definition gc-anchored.h:92
static R & release(R &r)
Decrements the reference count of a anchored object.
@ DOCUMENT_NODE
Top-level document node. Do not confuse with the root node.
@ PI_NODE
Processing instruction node, e.g. <?xml version="1.0" encoding="utf-8" standalone="no"?...
@ COMMENT_NODE
Comment node, e.g. <!– some comment –>.
@ ELEMENT_NODE
Regular element node, e.g. <group />.
@ TEXT_NODE
Text node, e.g. "Some text" in <group>Some text</group> is represented by a text node.
@ ERROR_MESSAGE
Definition message.h:29
static cairo_user_data_key_t key
int mode
Ocnode * child[8]
Definition quantize.cpp:33
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
Merges two SPCSSAttr's.
Definition repr-css.cpp:299
SPCSSAttr * sp_repr_css_attr_inherited(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute whose value is determined by cascading.
Definition repr-css.cpp:116
void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
Sets an attribute (e.g.
Definition repr-css.cpp:265
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
SPCSSAttr * sp_repr_css_attr(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute (i.e.
Definition repr-css.cpp:88
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
void sp_repr_css_attr_add_from_string(SPCSSAttr *css, gchar const *p)
Use libcroco to parse a string for CSS properties and then merge them into an existing SPCSSAttr.
Definition repr-css.cpp:341
bool sp_repr_is_meta_element(const Inkscape::XML::Node *node)
Determine if the node is a 'title', 'desc' or 'metadata' element.
SPItem * next_item(SPDesktop *desktop, std::vector< SPObject * > &path, SPObject *root, bool only_in_viewport, PrefsSelectionContext inlayer, bool onlyvisible, bool onlysensitive)
SPCSSAttr - interface for CSS Attributes.
TODO: insert short description here.
TODO: insert short description here.
TODO: insert short description here.
guint32 GQuark
TODO: insert short description here.
bool SP_IS_TEXT_TEXTPATH(SPObject const *obj)
Definition sp-textpath.h:47
bool sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start, SPObject *end_item, Glib::ustring::iterator &end)
Returns true if a tref is fully contained in the confines of the given iterators and layout (or if th...
Definition sp-tref.cpp:324
SVG <tref> implementation, see sp-tref.cpp.
TODO: insert short description here.
@ SP_TSPAN_ROLE_UNSPECIFIED
Definition sp-tspan.h:21
@ SP_TSPAN_ROLE_LINE
Definition sp-tspan.h:23
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
virtual Node * createComment(char const *content)=0
virtual Node * createPI(char const *target, char const *content)=0
@ SP_CSS_WRITING_MODE_TB_RL
@ SP_CSS_WRITING_MODE_LR_TB
@ SP_CSS_WRITING_MODE_RL_TB
static const unsigned SP_STYLE_FLAG_ALWAYS(1<< 2)
@ SP_CSS_UNIT_IN
@ SP_CSS_UNIT_PT
@ SP_CSS_UNIT_PX
@ SP_CSS_UNIT_PC
@ SP_CSS_UNIT_MM
@ SP_CSS_UNIT_PERCENT
@ SP_CSS_UNIT_NONE
@ SP_CSS_UNIT_EM
@ SP_CSS_UNIT_CM
@ SP_CSS_UNIT_EX
SPCSSAttr * sp_css_attr_scale(SPCSSAttr *css, double ex)
Scale any properties that may hold <length> by ex.
Definition style.cpp:1625
SPStyle - a style object for SPItem objects.
int delta
int index
SPDesktop * desktop
double height
Inkscape::Text::Layout::iterator sp_te_insert_line(SPItem *item, Inkscape::Text::Layout::iterator &position)
inserts a new line break at the given position in a text or flowtext object.
static bool redundant_double_nesting_processor(SPObject **item, SPObject *child, bool prepend)
helper for tidy_operator_redundant_double_nesting()
static void overwrite_style_with_string(SPObject *item, gchar const *style_string)
applies the given style string on top of the existing styles for item, as opposed to sp_style_merge_f...
static bool is_line_break_object(SPObject const *object)
SPObject const * sp_te_object_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
static unsigned sum_sibling_text_lengths_before(SPObject const *item)
returns the sum of the (recursive) lengths of all the SPStrings prior to item at the same level.
Inkscape::Text::Layout::iterator sp_te_get_position_by_coords(SPItem const *item, Geom::Point const &i_p)
static SPObject * split_text_object_tree_at(SPObject *split_obj, unsigned char_index)
recursively divides the XML node tree into two objects: the original will contain all objects up to a...
void sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, gdouble degrees)
static SPObject * ascend_while_first(SPObject *item, Glib::ustring::iterator text_iter, SPObject *common_ancestor)
static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second)
returns true if first and second contain all the same attributes with the same values as each other.
void sp_te_adjust_rotation_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble pixels)
void sp_te_adjust_dx(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, double delta)
void sp_te_adjust_tspan_letterspacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, gdouble by)
SPStyle const * sp_te_style_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
unsigned sp_text_get_length_upto(SPObject const *item, SPObject const *upto)
Recursively gets the length of all the SPStrings at or below the given item, before and not including...
static bool tidy_operator_redundant_semi_nesting(SPObject **item, bool)
redundant semi-nesting: <font a><font b>abc</font>def</font> -> <font b>abc</font><font>def</font> te...
static void apply_css_recursive(SPObject *o, SPCSSAttr const *css)
sets the given css attribute on this object and all its descendants.
static Inkscape::XML::Node * duplicate_node_without_children(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node const *old_node)
void te_update_layout_now_recursive(SPItem *item)
double sp_te_get_average_linespacing(SPItem *text)
bool has_visible_text(SPObject const *obj)
void sp_te_get_cursor_coords(SPItem const *item, Inkscape::Text::Layout::iterator const &position, Geom::Point &p0, Geom::Point &p1)
unsigned sp_text_get_length(SPObject const *item)
Recursively gets the length of all the SPStrings at or below the given item.
bool sp_te_output_is_empty(SPItem const *item)
Returns true if there are no visible characters on the canvas.
static const char * span_name_for_text_object(SPObject const *object)
static unsigned char_index_of_iterator(Glib::ustring const &string, Glib::ustring::const_iterator text_iter)
converts an iterator to a character index, mainly because ustring::substr() doesn't have a version th...
static void sp_te_get_ustring_multiline(SPObject const *root, Glib::ustring *string, bool *pending_line_break)
Gets a text-only representation of the given text or flowroot object, replacing line break elements w...
static void split_attributes(SPObject *first_item, SPObject *second_item, unsigned char_index)
splits the attributes for the first object at the given char_index and moves the ones after that poin...
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::Affine const &transform)
static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node *to_repr, bool prepend=false)
moves all the children of from_repr to to_repr, either before the existing children or after them.
static bool tidy_operator_excessive_nesting(SPObject **item, bool)
redundant nesting: <font a><font b>abc</font></font> -> <font b>abc</font> excessive nesting: <font a...
void sp_te_adjust_linespacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &, Inkscape::Text::Layout::iterator const &, SPDesktop *desktop, gdouble by)
static SPObject * get_common_ancestor(SPObject *text, SPObject *one, SPObject *two)
returns the object in the tree which is the closest ancestor of both one and two.
Glib::ustring sp_te_get_string_multiline(SPItem const *text)
Gets a text-only representation of the given text or flowroot object, replacing line break elements w...
bool is_part_of_text_subtree(SPObject const *obj)
void sp_te_adjust_line_height(SPObject *object, double amount, double average, bool top_level=true)
Adjust the line height by 'amount'.
static SPString * find_last_string_child_not_equal_to(SPObject *root, SPObject *not_obj)
helper for tidy_operator_styled_whitespace(), finds the last string object in a paragraph which is no...
static void move_to_end_of_paragraph(SPObject **para_obj, Glib::ustring::iterator *text_iter)
positions para_obj and text_iter to be pointing at the end of the last string in the last leaf object...
TextTagAttributes * text_tag_attributes_at_position(SPItem *item, Inkscape::Text::Layout::iterator const &position, unsigned *char_index)
Returns the attributes block and the character index within that block which represents the iterator ...
static void te_update_layout_now(SPItem *item)
static bool tidy_operator_styled_whitespace(SPObject **item, bool has_text_decoration)
whitespace-only spans: abc<font> </font>def -> abc<font></font> def abcdef ghi -> abcdef ghi
static SPObject * delete_line_break(SPObject *root, SPObject *item, bool *next_is_sibling)
delete the line break pointed to by item by merging its children into the next suitable object and de...
void sp_te_adjust_dy(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, double delta)
Inkscape::Text::Layout const * te_get_layout(SPItem const *item)
bool sp_te_delete(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, iterator_pair &iter_pair)
static const gchar * tref_edit_message
void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css)
Applies the given CSS fragment to the characters of the given text or flowtext object between start a...
static void erase_from_spstring(SPString *string_item, Glib::ustring::iterator iter_from, Glib::ustring::iterator iter_to)
erases the given characters from the given string and deletes the corresponding x/y/dx/dy/rotate attr...
static bool redundant_semi_nesting_processor(SPObject **item, SPObject *child, bool prepend)
helper for tidy_operator_redundant_semi_nesting().
static bool objects_have_equal_style(SPObject const *parent, SPObject const *child)
Returns true if the style of parent and the style of child are equivalent (and hence the children of ...
static bool tidy_operator_inexplicable_spans(SPObject **item, bool)
inexplicable spans: abcdefghi -> "abc""def""ghi" the repeated strings will be merged by another opera...
void sp_te_adjust_kerning_screen(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *desktop, Geom::Point by)
Inkscape::Text::Layout::iterator sp_te_replace(SPItem *item, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, gchar const *utf8)
static void insert_into_spstring(SPString *string_item, Glib::ustring::iterator iter_at, gchar const *utf8)
inserts the given characters into the given string and inserts corresponding new x/y/dx/dy/rotate att...
static bool tidy_operator_repeated_spans(SPObject **item, bool)
repeated spans: <font a>abc</font><font a>def</font> -> <font a>abcdef</font>
void sp_te_set_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, gdouble degrees)
bool is_top_level_text_object(SPObject const *obj)
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)
applies the given style to all the objects at the given level and below which are between start_item ...
static bool tidy_operator_empty_spans(SPObject **item, bool)
empty spans: abc<span>def -> abcdef
static TextTagAttributes * attributes_for_object(SPObject *object)
returns the attributes for an object, or NULL if it isn't a text, tspan, tref, or textpath.
static SPString * sp_te_seek_next_string_recursive(SPObject *start_obj)
finds the first SPString after the given position, including children, excluding parents
static bool tidy_xml_tree_recursively(SPObject *root, bool has_text_decoration)
Recursively walks the xml tree calling a set of cleanup operations on every child.
static bool tidy_operator_redundant_double_nesting(SPObject **item, bool)
redundant double nesting: <font b><font a><font b>abc</font>def</font>ghi</font> -> <font b>abc<font ...
Inkscape::Text::Layout::iterator sp_te_insert(SPItem *item, Inkscape::Text::Layout::iterator const &position, gchar const *utf8)
Inserts the given text into a text or flowroot object.
void sp_te_set_repr_text_multiline(SPItem *text, gchar const *str)
bool sp_te_input_is_empty(SPObject const *item)
Returns true if the user has typed nothing in the text box.
std::pair< Inkscape::Text::Layout::iterator, Inkscape::Text::Layout::iterator > iterator_pair