Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
text-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Jon A. Cruz <jon@joncruz.org>
10 * Abhishek Sharma
11 *
12 * Copyright (C) 1999-2005 authors
13 * Copyright (C) 2001 Ximian, Inc.
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include "text-tool.h"
19
20#include <algorithm>
21#include <cmath>
22#include <iomanip>
23#include <memory>
24#include <sstream>
25#include <glibmm/i18n.h>
26#include <glibmm/main.h>
27#include <glibmm/regex.h>
28#include <gdk/gdkkeysyms.h>
29#include <gdkmm/clipboard.h>
30#include <gtkmm/settings.h>
31#include <gtkmm/window.h>
32
33#include "context-fns.h"
34#include "desktop-style.h"
35#include "desktop.h"
36#include "document-undo.h"
37#include "document.h"
38#include "inkscape-window.h"
39#include "message-context.h"
40#include "message-stack.h"
41#include "rubberband.h"
42#include "selection-chemistry.h"
43#include "selection.h"
44#include "style.h"
45#include "text-editing.h"
50#include "display/curve.h"
51#include "livarot/Path.h"
52#include "livarot/Shape.h"
53#include "object/sp-flowtext.h"
54#include "object/sp-namedview.h"
55#include "object/sp-text.h"
56#include "object/sp-textpath.h"
57#include "object/sp-shape.h"
58#include "ui/knot/knot-holder.h"
59#include "ui/icon-names.h"
60#include "ui/shape-editor.h"
61#include "ui/widget/canvas.h"
65#include "util/units.h"
66#include "xml/sp-css-attr.h"
67
69
70namespace Inkscape::UI::Tools {
71
73 : ToolBase(desktop, "/tools/text", "text.svg")
74{
75 Gtk::Settings::get_default()->get_property("gtk-cursor-blink-time", blink_time);
76 if (blink_time < 0) {
77 blink_time = 200;
78 } else {
79 blink_time /= 2;
80 }
81
82 cursor = make_canvasitem<CanvasItemCurve>(_desktop->getCanvasControls());
83 cursor->set_stroke(0x000000ff);
84 cursor->set_visible(false);
85
86 // The rectangle box tightly wrapping text object when selected or under cursor.
87 indicator = make_canvasitem<CanvasItemRect>(_desktop->getCanvasControls());
88 indicator->set_stroke(0x0000ff7f);
89 indicator->set_shadow(0xffffff7f, 1);
90 indicator->set_visible(false);
91
92 // The shape that the text is flowing into
93 frame = make_canvasitem<CanvasItemBpath>(_desktop->getCanvasControls());
94 frame->set_fill(0x00 /* zero alpha */, SP_WIND_RULE_NONZERO);
95 frame->set_stroke(0x0000ff7f);
96 frame->set_visible(false);
97
98 // A second frame for showing the padding of the above frame
99 padding_frame = make_canvasitem<CanvasItemBpath>(_desktop->getCanvasControls());
100 padding_frame->set_fill(0x00 /* zero alpha */, SP_WIND_RULE_NONZERO);
101 padding_frame->set_stroke(0xccccccdf);
102 padding_frame->set_visible(false);
103
105
106 imc = gtk_im_multicontext_new();
107 if (imc) {
108 auto canvas = _desktop->getCanvas();
109
110 /* im preedit handling is very broken in inkscape for
111 * multi-byte characters. See bug 1086769.
112 * We need to let the IM handle the preediting, and
113 * just take in the characters when they're finished being
114 * entered.
115 */
116 gtk_im_context_set_use_preedit(imc, FALSE);
117 gtk_im_context_set_client_widget(imc, canvas->Gtk::Widget::gobj());
118
119 // Note: Connecting to property_is_focus().signal_changed() would result in slight regression due to signal emisssion ordering.
120 focus_in_conn = canvas->connectFocusIn([this] { gtk_im_context_focus_in(imc); });
121 focus_out_conn = canvas->connectFocusOut([this] { gtk_im_context_focus_out(imc); });
122 g_signal_connect(G_OBJECT(imc), "commit", Util::make_g_callback<&TextTool::_commit>, this);
123
124 if (canvas->has_focus()) {
125 gtk_im_context_focus_in(imc);
126 }
127 }
128
130
132 if (is<SPFlowtext>(item) || is<SPText>(item)) {
134 }
135
137 sigc::mem_fun(*this, &TextTool::_selectionChanged)
138 );
140 sigc::mem_fun(*this, &TextTool::_selectionModified)
141 );
143 sigc::hide(sigc::mem_fun(*this, &TextTool::_styleSet))
144 );
146 sigc::mem_fun(*this, &TextTool::_styleQueried)
147 );
148
150
151 auto prefs = Preferences::get();
152 if (prefs->getBool("/tools/text/selcue")) {
154 }
155 if (prefs->getBool("/tools/text/gradientdrag")) {
156 enableGrDrag();
157 }
158}
159
161{
162 enableGrDrag(false);
163
164 _forgetText();
165
166 if (imc) {
167 // Note: We rely on this being the last reference, so we don't need to disconnect from signals.
168 g_object_unref(G_OBJECT(imc));
169 }
170
171 delete shape_editor;
172
174
176}
177
179{
181 DocumentUndo::done(_desktop->getDocument(), _("Delete text"), INKSCAPE_ICON("draw-text"));
182}
183
185{
187 auto const old_start = text_sel_start;
188
189 bool ret = false;
190
191 inspect_event(event,
192 [&] (ButtonPressEvent const &event) {
193 if (event.button != 1) {
194 return;
195 }
196 auto const n_press = event.num_press % 3; // cycle through selection modes on repeated clicking
197 if (n_press == 1) {
198 // this var allow too much lees subbselection queries
199 // reducing it to cursor iteracion, mouseup and down
200 // find out clicked item, disregarding groups
201 auto const item_ungrouped = _desktop->getItemAtPoint(event.pos, true);
202 if (is<SPText>(item_ungrouped) || is<SPFlowtext>(item_ungrouped)) {
203 _desktop->getSelection()->set(item_ungrouped);
204 if (text) {
205 // find out click point in document coordinates
206 auto const p = _desktop->w2d(event.pos);
207 // set the cursor closest to that point
208 if (event.modifiers & GDK_SHIFT_MASK) {
209 text_sel_start = old_start;
211 } else {
213 }
214 // update display
217 dragging_state = 1;
218 }
219 ret = true;
220 }
221 } else if (n_press == 2 && text && dragging_state) {
222 if (auto const layout = te_get_layout(text)) {
223 if (!layout->isStartOfWord(text_sel_start)) {
225 }
226 if (!layout->isEndOfWord(text_sel_end)) {
228 }
231 dragging_state = 2;
232 ret = true;
233 }
234 } else if (n_press == 0 && text && dragging_state) {
239 dragging_state = 3;
240 ret = true;
241 }
242 },
243 [&] (ButtonReleaseEvent const &event) {
244 if (event.button == 1 && dragging_state) {
245 dragging_state = 0;
248 ret = true;
249 }
250 },
251 [&] (CanvasEvent const &event) {}
252 );
253
254 return ret || ToolBase::item_handler(item, event);
255}
256
258{
259 /* Create <text> */
261 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
262 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
263
264 /* Set style */
265 sp_desktop_apply_style_tool(_desktop, rtext, "/tools/text", true);
266
267 rtext->setAttributeSvgDouble("x", pdoc.x());
268 rtext->setAttributeSvgDouble("y", pdoc.y());
269
270 /* Create <tspan> */
271 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
272 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
273 rtext->addChild(rtspan, nullptr);
274 Inkscape::GC::release(rtspan);
275
276 /* Create TEXT */
277 Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
278 rtspan->addChild(rstring, nullptr);
279 Inkscape::GC::release(rstring);
280 auto text_item = cast<SPItem>(currentLayer()->appendChildRepr(rtext));
281 /* fixme: Is selection::changed really immediate? */
282 /* yes, it's immediate .. why does it matter? */
283 _desktop->getSelection()->set(text_item);
285 text_item->transform = currentLayer()->i2doc_affine().inverse();
286
287 text_item->updateRepr();
288 text_item->doWriteTransform(text_item->transform, nullptr, true);
289 DocumentUndo::done(_desktop->getDocument(), _("Create text"), INKSCAPE_ICON("draw-text"));
290}
291
299{
300 assert(!uni.empty());
301
302 unsigned uv;
303 std::stringstream ss;
304 ss << std::hex << uni;
305 ss >> uv;
306 uni.clear();
307
308 if (!g_unichar_isprint(static_cast<gunichar>(uv))
309 && !(g_unichar_validate(static_cast<gunichar>(uv)) && g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE))
310 {
311 // This may be due to bad input, so it goes to statusbar.
312 _desktop->messageStack()->flash(ERROR_MESSAGE, _("Non-printable character"));
313 } else {
314 if (!text) { // printable key; create text if none (i.e. if nascent_object)
315 _setupText();
316 nascent_object = false; // we don't need it anymore, having created a real <text>
317 }
318
319 char u[10];
320 auto const len = g_unichar_to_utf8(uv, u);
321 u[len] = '\0';
322
326 DocumentUndo::done(_desktop->getDocument(), _("Insert Unicode character"), INKSCAPE_ICON("draw-text"));
327 }
328}
329
330static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8)
331{
332 unsigned uv;
333 std::stringstream ss;
334 ss << std::hex << ehex;
335 ss >> uv;
336 if (!g_unichar_isprint(static_cast<gunichar>(uv))) {
337 uv = 0xfffd;
338 }
339 auto const len = g_unichar_to_utf8(uv, utf8);
340 utf8[len] = '\0';
341}
342
344{
345 if (!uni.empty()) {
346 char utf8[10];
347 hex_to_printable_utf8_buf(uni.c_str(), utf8);
348
349 // Status bar messages are in pango markup, so we need xml escaping.
350 if (utf8[1] == '\0') {
351 switch (utf8[0]) {
352 case '<': strcpy(utf8, "&lt;"); break;
353 case '>': strcpy(utf8, "&gt;"); break;
354 case '&': strcpy(utf8, "&amp;"); break;
355 default: break;
356 }
357 }
359 _("Unicode (<b>Enter</b> to finish): %s: %s"), uni.c_str(), utf8);
360 } else {
361 defaultMessageContext()->set(NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
362 }
363}
364
366{
367 if constexpr (DEBUG_EVENTS) {
368 dump_event(event, "TextTool::root_handler");
369 }
370
371 indicator->set_visible(false);
372
374
375 auto prefs = Preferences::get();
376 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
377
378 bool ret = false;
379
380 inspect_event(event,
381 [&] (ButtonPressEvent const &event) {
382 if (event.button != 1 || event.num_press != 1) {
383 return;
384 }
385
387 ret = true;
388 return;
389 }
390
391 saveDragOrigin(event.pos);
392
393 auto button_dt = _desktop->w2d(event.pos);
394
395 auto &m = _desktop->getNamedView()->snap_manager;
396 m.setup(_desktop);
397 m.freeSnapReturnByRef(button_dt, SNAPSOURCE_NODE_HANDLE);
398 m.unSetup();
399
400 p0 = button_dt;
401 auto rubberband = Rubberband::get(_desktop);
402 rubberband->start(_desktop, p0);
403
405
406 creating = true;
407
408 ret = true;
409 },
410 [&] (MotionEvent const &event) {
411 if (creating && event.modifiers & GDK_BUTTON1_MASK) {
412 if (!checkDragMoved(event.pos)) {
413 return;
414 }
415
416 auto p = _desktop->w2d(event.pos);
417
418 auto &m = _desktop->getNamedView()->snap_manager;
419 m.setup(_desktop);
420 m.freeSnapReturnByRef(p, SNAPSOURCE_NODE_HANDLE);
421 m.unSetup();
422
424 gobble_motion_events(GDK_BUTTON1_MASK);
425
426 // status text
427 auto const diff = p - p0;
428 auto const x_q = Util::Quantity(std::abs(diff.x()), "px");
429 auto const y_q = Util::Quantity(std::abs(diff.y()), "px");
430 auto const xs = x_q.string(_desktop->getNamedView()->display_units);
431 auto const ys = y_q.string(_desktop->getNamedView()->display_units);
432 message_context->setF(IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs.c_str(), ys.c_str());
433 } else if (!sp_event_context_knot_mouseover()) {
434 auto &m = _desktop->getNamedView()->snap_manager;
435 m.setup(_desktop);
436
437 auto const motion_dt = _desktop->w2d(event.pos);
438 m.preSnap(SnapCandidatePoint(motion_dt, SNAPSOURCE_OTHER_HANDLE));
439 m.unSetup();
440 }
441
442 if (event.modifiers & GDK_BUTTON1_MASK && dragging_state) {
443 auto const layout = te_get_layout(text);
444 if (!layout) {
445 return;
446 }
447 // find out click point in document coordinates
448 auto const p = _desktop->w2d(event.pos);
449 // set the cursor closest to that point
450 auto new_end = sp_te_get_position_by_coords(text, p);
451 if (dragging_state == 2) {
452 // double-click dragging_state: go by word
453 if (new_end < text_sel_start) {
454 if (!layout->isStartOfWord(new_end)) {
455 new_end.prevStartOfWord();
456 }
457 } else if (!layout->isEndOfWord(new_end)) {
458 new_end.nextEndOfWord();
459 }
460 } else if (dragging_state == 3) {
461 // triple-click dragging_state: go by line
462 if (new_end < text_sel_start) {
463 new_end.thisStartOfLine();
464 } else {
465 new_end.thisEndOfLine();
466 }
467 }
468 // update display
469 if (text_sel_end != new_end) {
470 text_sel_end = new_end;
473 }
474 gobble_motion_events(GDK_BUTTON1_MASK);
475 return;
476 }
477
478 // find out item under mouse, disregarding groups
479 auto const item_ungrouped = _desktop->getItemAtPoint(event.pos, true, nullptr);
480 if (is<SPText>(item_ungrouped) || is<SPFlowtext>(item_ungrouped)) {
481 auto const layout = te_get_layout(item_ungrouped);
482 if (layout->inputTruncated()) {
483 indicator->set_stroke(0xff0000ff);
484 } else {
485 indicator->set_stroke(0x0000ff7f);
486 }
487 auto const ibbox = item_ungrouped->desktopVisualBounds();
488 if (ibbox) {
489 indicator->set_rect(*ibbox);
490 }
491 indicator->set_visible(true);
492
493 set_cursor("text-insert.svg");
495 if (is<SPText>(item_ungrouped)) {
498 _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
499 } else {
502 _("<b>Click</b> to edit the flowed text, <b>drag</b> to select part of the text."));
503 }
504 over_text = true;
505 } else {
506 // update cursor and statusbar: we are not over a text object now
507 set_cursor("text.svg");
509 over_text = false;
510 }
511 },
512
513 [&] (ButtonReleaseEvent const &event) {
514 if (event.button != 1) {
515 return;
516 }
517
519
520 auto p1 = _desktop->w2d(event.pos);
521
522 auto &m = _desktop->getNamedView()->snap_manager;
523 m.setup(_desktop);
524 m.freeSnapReturnByRef(p1, SNAPSOURCE_NODE_HANDLE);
525 m.unSetup();
526
528
530
531 if (creating && within_tolerance) {
532 // Button 1, set X & Y & new item.
534 pdoc = _desktop->dt2doc(p1);
535 nascent_object = true; // new object was just created
536
537 // Cursor height is defined by the new text object's font size; it needs to be set
538 // artificially here, for the text object does not exist yet:
539 double cursor_height = sp_desktop_get_font_size_tool(_desktop);
540 auto const y_dir = _desktop->yaxisdir();
541 auto const cursor_size = Geom::Point(0, y_dir * cursor_height);
542 cursor->set_coords(p1, p1 - cursor_size);
543 _showCursor();
544
545 if (imc) {
546 GdkRectangle im_cursor;
547 Geom::Point const top_left = _desktop->get_display_area().corner(0);
548 Geom::Point const im_d0 = _desktop->d2w(p1 - top_left);
549 Geom::Point const im_d1 = _desktop->d2w(p1 - cursor_size - top_left);
550 Geom::Rect const im_rect(im_d0, im_d1);
551 im_cursor.x = std::floor(im_rect.left());
552 im_cursor.y = std::floor(im_rect.top());
553 im_cursor.width = std::floor(im_rect.width());
554 im_cursor.height = std::floor(im_rect.height());
555 gtk_im_context_set_cursor_location(imc, &im_cursor);
556 }
557 message_context->set(NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync
558
559 within_tolerance = false;
560 } else if (creating) {
561 double cursor_height = sp_desktop_get_font_size_tool(_desktop);
562 if (std::abs(p1.y() - p0.y()) > cursor_height) {
563 // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
564
565 if (prefs->getBool("/tools/text/use_svg2", true)) {
566 // SVG 2 text
569 } else {
570 // SVG 1.2 text
572 _desktop->getSelection()->set(ft);
573 }
574
575 _desktop->messageStack()->flash(NORMAL_MESSAGE, _("Flowed text is created."));
576 DocumentUndo::done(_desktop->getDocument(), _("Create flowed text"), INKSCAPE_ICON("draw-text"));
577
578 } else {
579 _desktop->messageStack()->flash(ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
580 }
581 }
582 creating = false;
584
585 ret = true;
586 },
587 [&] (KeyPressEvent const &event) {
588 auto const group0_keyval = get_latin_keyval(event);
589
590 if (group0_keyval == GDK_KEY_KP_Add || group0_keyval == GDK_KEY_KP_Subtract) {
591 if (!(event.modifiers & GDK_CONTROL_MASK)) { // mod2 is NumLock; if on, type +/- keys
592 return; // otherwise pass on keypad +/- so they can zoom
593 }
594 }
595
596 if (text || nascent_object) {
597 // there is an active text object in this context, or a new object was just created
598
599 // Input methods often use Ctrl+Shift+U for preediting (unimode).
600 // Override it so we can use our unimode.
601 bool preedit_activation = mod_ctrl(event) && mod_shift(event) && !mod_alt(event)
602 && (group0_keyval == GDK_KEY_U || group0_keyval == GDK_KEY_u);
603
604 auto surface = _desktop->getInkscapeWindow()->get_surface()->gobj();
605 if (unimode || !imc || preedit_activation || !gtk_im_context_filter_key(imc, true, surface, const_cast<GdkDevice*>(event.device->gobj()), event.time, event.keycode, (GdkModifierType)event.modifiers, event.group)) {
606 // IM did not consume the key, or we're in unimode
607
608 if (!mod_ctrl_only(event) && unimode) {
609 /* TODO: ISO 14755 (section 3 Definitions) says that we should also
610 accept the first 6 characters of alphabets other than the latin
611 alphabet "if the Latin alphabet is not used". The below is also
612 reasonable (viz. hope that the user's keyboard includes latin
613 characters and force latin interpretation -- just as we do for our
614 keyboard shortcuts), but differs from the ISO 14755
615 recommendation. */
616 switch (group0_keyval) {
617 case GDK_KEY_space:
618 case GDK_KEY_KP_Space: {
619 if (!uni.empty()) {
621 }
622 // Stay in unimode.
624 ret = true;
625 return;
626 }
627
628 case GDK_KEY_BackSpace: {
629 if (!uni.empty()) {
630 uni.pop_back();
631 }
633 ret = true;
634 return;
635 }
636
637 case GDK_KEY_Return:
638 case GDK_KEY_KP_Enter: {
639 if (!uni.empty()) {
641 }
642 // Exit unimode.
643 unimode = false;
645 ret = true;
646 return;
647 }
648
649 case GDK_KEY_Escape: {
650 // Cancel unimode.
651 unimode = false;
652 gtk_im_context_reset(imc);
654 ret = true;
655 return;
656 }
657
658 case GDK_KEY_Shift_L:
659 case GDK_KEY_Shift_R:
660 break;
661
662 default: {
663 auto const xdigit = gdk_keyval_to_unicode(group0_keyval);
664 if (xdigit <= 255 && g_ascii_isxdigit(xdigit)) {
665 uni.push_back(xdigit);
666 if (uni.length() == 8) {
667 /* This behaviour is partly to due to the previous use
668 of a fixed-length buffer for uni. The reason for
669 choosing the number 8 is that it's the length of
670 ``canonical form'' mentioned in the ISO 14755 spec.
671 An advantage over choosing 6 is that it allows using
672 backspace for typos & misremembering when entering a
673 6-digit number. */
675 }
677 } else {
678 /* The intent is to ignore but consume characters that could be
679 typos for hex digits. Gtk seems to ignore & consume all
680 non-hex-digits, and we do similar here. Though note that some
681 shortcuts (like keypad +/- for zoom) get processed before
682 reaching this code. */
683 }
684 ret = true;
685 return;
686 }
687 }
688 }
689
690 auto const old_start = text_sel_start;
691 auto const old_end = text_sel_end;
692 bool cursor_moved = false;
693 int screenlines = 1;
694 if (text) {
695 double spacing = sp_te_get_average_linespacing(this->text);
697 screenlines = static_cast<int>(std::floor(d.height() / spacing)) - 1;
698 screenlines = std::max(screenlines, 1);
699 }
700
701 // Neither unimode nor IM consumed key; process text tool shortcuts.
702 switch (group0_keyval) {
703 case GDK_KEY_x:
704 case GDK_KEY_X:
705 if (mod_alt_only(event)) {
706 _desktop->setToolboxFocusTo("TextFontFamilyAction_entry");
707 ret = true;
708 return;
709 }
710 break;
711 case GDK_KEY_space:
712 if (mod_ctrl_only(event)) {
713 // No-break space
714 if (!text) { // printable key; create text if none (i.e. if nascent_object)
715 _setupText();
716 nascent_object = false; // we don't need it anymore, having created a real <text>
717 }
721 _desktop->messageStack()->flash(NORMAL_MESSAGE, _("No-break space"));
722 DocumentUndo::done(_desktop->getDocument(), _("Insert no-break space"), INKSCAPE_ICON("draw-text"));
723 ret = true;
724 return;
725 }
726 break;
727 case GDK_KEY_U:
728 case GDK_KEY_u:
729 if (mod_ctrl_only(event) || (mod_ctrl(event) && mod_shift(event))) {
730 if (unimode) {
731 unimode = false;
733 } else {
734 unimode = true;
735 uni.clear();
736 defaultMessageContext()->set(NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
737 }
738 if (imc) {
739 gtk_im_context_reset(imc);
740 }
741 ret = true;
742 return;
743 }
744 break;
745 case GDK_KEY_B:
746 case GDK_KEY_b:
747 if (mod_ctrl_only(event) && text) {
748 auto const style = sp_te_style_at_position(text, std::min(text_sel_start, text_sel_end));
749 auto const css = sp_repr_css_attr_new();
750 const auto weight = static_cast<int>(style->font_weight.computed);
753 {
754 sp_repr_css_set_property(css, "font-weight", "bold");
755 } else {
756 sp_repr_css_set_property(css, "font-weight", "normal");
757 }
760 DocumentUndo::done(_desktop->getDocument(), _("Make bold"), INKSCAPE_ICON("draw-text"));
763 ret = true;
764 return;
765 }
766 break;
767 case GDK_KEY_I:
768 case GDK_KEY_i:
769 if (mod_ctrl_only(event) && text) {
770 auto const style = sp_te_style_at_position(text, std::min(text_sel_start, text_sel_end));
771 auto const css = sp_repr_css_attr_new();
772 if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL) {
773 sp_repr_css_set_property(css, "font-style", "normal");
774 } else {
775 sp_repr_css_set_property(css, "font-style", "italic");
776 }
779 DocumentUndo::done(_desktop->getDocument(), _("Make italic"), INKSCAPE_ICON("draw-text"));
782 ret = true;
783 return;
784 }
785 break;
786
787 case GDK_KEY_A:
788 case GDK_KEY_a:
789 if (mod_ctrl_only(event) && text) {
790 if (auto const layout = te_get_layout(text)) {
791 text_sel_start = layout->begin();
792 text_sel_end = layout->end();
795 ret = true;
796 return;
797 }
798 }
799 break;
800
801 case GDK_KEY_Return:
802 case GDK_KEY_KP_Enter: {
803 if (!text) { // printable key; create text if none (i.e. if nascent_object)
804 _setupText();
805 nascent_object = false; // we don't need it anymore, having created a real <text>
806 }
807
808 auto text_element = cast<SPText>(text);
809 if (text_element && (text_element->has_shape_inside() || text_element->has_inline_size())) {
810 // Handle new line like any other character.
812 } else {
813 // Replace new line by either <tspan sodipodi:role="line" or <flowPara>.
814 iterator_pair enter_pair;
816 text_sel_start = text_sel_end = enter_pair.first;
818 }
819
822 DocumentUndo::done(_desktop->getDocument(), _("New line"), INKSCAPE_ICON("draw-text"));
823 ret = true;
824 return;
825 }
826 case GDK_KEY_BackSpace:
827 if (text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
828
829 bool noSelection = false;
830
831 if (mod_ctrl(event)) {
833 }
834
836 if (mod_ctrl(event)) {
838 } else {
840 }
841 noSelection = true;
842 }
843
844 iterator_pair bspace_pair;
845 bool success = sp_te_delete(text, text_sel_start, text_sel_end, bspace_pair);
846
847 if (noSelection) {
848 if (success) {
849 text_sel_start = text_sel_end = bspace_pair.first;
850 } else { // nothing deleted
851 text_sel_start = text_sel_end = bspace_pair.second;
852 }
853 } else {
854 if (success) {
855 text_sel_start = text_sel_end = bspace_pair.first;
856 } else { // nothing deleted
857 text_sel_start = bspace_pair.first;
858 text_sel_end = bspace_pair.second;
859 }
860 }
861
864 DocumentUndo::done(_desktop->getDocument(), _("Backspace"), INKSCAPE_ICON("draw-text"));
865 }
866 ret = true;
867 return;
868 case GDK_KEY_Delete:
869 case GDK_KEY_KP_Delete:
870 if (text) {
871 bool noSelection = false;
872
873 if (mod_ctrl(event)) {
875 }
876
878 if (mod_ctrl(event)) {
880 } else {
882 }
883 noSelection = true;
884 }
885
886 iterator_pair del_pair;
887 bool success = sp_te_delete(text, text_sel_start, text_sel_end, del_pair);
888
889 if (noSelection) {
890 text_sel_start = text_sel_end = del_pair.first;
891 } else {
892 if (success) {
893 text_sel_start = text_sel_end = del_pair.first;
894 } else { // nothing deleted
895 text_sel_start = del_pair.first;
896 text_sel_end = del_pair.second;
897 }
898 }
899
902 DocumentUndo::done(_desktop->getDocument(), _("Delete"), INKSCAPE_ICON("draw-text"));
903 }
904 ret = true;
905 return;
906 case GDK_KEY_Left:
907 case GDK_KEY_KP_Left:
908 case GDK_KEY_KP_4:
909 if (this->text) {
910 if (mod_alt(event)) {
911 int mul = 1 + gobble_key_events(get_latin_keyval(event), 0); // with any mask
912 if (mod_shift(event)) {
914 } else {
916 }
919 DocumentUndo::maybeDone(_desktop->getDocument(), "kern:left", _("Kern to the left"), INKSCAPE_ICON("draw-text"));
920 } else {
921 if (mod_ctrl(event)) {
923 } else {
925 }
926 cursor_moved = true;
927 break;
928 }
929 }
930 ret = true;
931 return;
932 case GDK_KEY_Right:
933 case GDK_KEY_KP_Right:
934 case GDK_KEY_KP_6:
935 if (text) {
936 if (mod_alt(event)) {
937 int mul = 1 + gobble_key_events(get_latin_keyval(event), 0); // with any mask
938 if (mod_shift(event)) {
940 } else {
942 }
945 DocumentUndo::maybeDone(_desktop->getDocument(), "kern:right", _("Kern to the right"), INKSCAPE_ICON("draw-text"));
946 } else {
947 if (mod_ctrl(event)) {
949 } else {
951 }
952 cursor_moved = true;
953 break;
954 }
955 }
956 ret = true;
957 return;
958 case GDK_KEY_Up:
959 case GDK_KEY_KP_Up:
960 case GDK_KEY_KP_8:
961 if (text) {
962 if (mod_alt(event)) {
963 int mul = 1 + gobble_key_events(get_latin_keyval(event), 0); // with any mask
964 if (mod_shift(event)) {
966 } else {
968 }
971 DocumentUndo::maybeDone(_desktop->getDocument(), "kern:up", _("Kern up"), INKSCAPE_ICON("draw-text"));
972 } else {
973 if (mod_ctrl(event)) {
975 } else {
977 }
978 cursor_moved = true;
979 break;
980 }
981 }
982 ret = true;
983 return;
984 case GDK_KEY_Down:
985 case GDK_KEY_KP_Down:
986 case GDK_KEY_KP_2:
987 if (text) {
988 if (mod_alt(event)) {
989 int mul = 1 + gobble_key_events(get_latin_keyval(event), 0); // with any mask
990 if (mod_shift(event)) {
992 } else {
994 }
997 DocumentUndo::maybeDone(_desktop->getDocument(), "kern:down", _("Kern down"), INKSCAPE_ICON("draw-text"));
998 } else {
999 if (mod_ctrl(event)) {
1001 } else {
1003 }
1004 cursor_moved = true;
1005 break;
1006 }
1007 }
1008 ret = true;
1009 return;
1010 case GDK_KEY_Home:
1011 case GDK_KEY_KP_Home:
1012 if (text) {
1013 if (mod_ctrl(event)) {
1015 } else {
1017 }
1018 cursor_moved = true;
1019 break;
1020 }
1021 ret = true;
1022 return;
1023 case GDK_KEY_End:
1024 case GDK_KEY_KP_End:
1025 if (text) {
1026 if (mod_ctrl(event)) {
1028 } else {
1030 }
1031 cursor_moved = true;
1032 break;
1033 }
1034 ret = true;
1035 return;
1036 case GDK_KEY_Page_Down:
1037 case GDK_KEY_KP_Page_Down:
1038 if (text) {
1039 text_sel_end.cursorDown(screenlines);
1040 cursor_moved = true;
1041 break;
1042 }
1043 ret = true;
1044 return;
1045 case GDK_KEY_Page_Up:
1046 case GDK_KEY_KP_Page_Up:
1047 if (text) {
1048 text_sel_end.cursorUp(screenlines);
1049 cursor_moved = true;
1050 break;
1051 }
1052 ret = true;
1053 return;
1054 case GDK_KEY_Escape:
1055 if (creating) {
1056 creating = false;
1059 } else {
1061 }
1062 nascent_object = false;
1063 ret = true;
1064 return;
1065 case GDK_KEY_bracketleft:
1066 if (text) {
1067 if (mod_alt(event) || mod_ctrl(event)) {
1068 if (mod_alt(event)) {
1069 if (mod_shift(event)) {
1070 // FIXME: alt+shift+[] does not work, don't know why
1072 } else {
1074 }
1075 } else {
1077 }
1078 DocumentUndo::maybeDone(_desktop->getDocument(), "textrot:ccw", _("Rotate counterclockwise"), INKSCAPE_ICON("draw-text"));
1079 _updateCursor();
1081 ret = true;
1082 return;
1083 }
1084 }
1085 break;
1086 case GDK_KEY_bracketright:
1087 if (text) {
1088 if (mod_alt(event) || mod_ctrl(event)) {
1089 if (mod_alt(event)) {
1090 if (mod_shift(event)) {
1091 // FIXME: alt+shift+[] does not work, don't know why
1093 } else {
1095 }
1096 } else {
1098 }
1099 DocumentUndo::maybeDone(_desktop->getDocument(), "textrot:cw", _("Rotate clockwise"), INKSCAPE_ICON("draw-text"));
1100 _updateCursor();
1102 ret = true;
1103 return;
1104 }
1105 }
1106 break;
1107 case GDK_KEY_less:
1108 case GDK_KEY_comma:
1109 if (text) {
1110 if (mod_alt(event)) {
1111 if (mod_ctrl(event)) {
1112 if (mod_shift(event)) {
1114 } else {
1116 }
1117 DocumentUndo::maybeDone(_desktop->getDocument(), "linespacing:dec", _("Contract line spacing"), INKSCAPE_ICON("draw-text"));
1118 } else {
1119 if (mod_shift(event)) {
1121 } else {
1123 }
1124 DocumentUndo::maybeDone(_desktop->getDocument(), "letterspacing:dec", _("Contract letter spacing"), INKSCAPE_ICON("draw-text"));
1125 }
1126 _updateCursor();
1128 ret = true;
1129 return;
1130 }
1131 }
1132 break;
1133 case GDK_KEY_greater:
1134 case GDK_KEY_period:
1135 if (text) {
1136 if (mod_alt(event)) {
1137 if (mod_ctrl(event)) {
1138 if (mod_shift(event)) {
1140 } else {
1142 }
1143 DocumentUndo::maybeDone(_desktop->getDocument(), "linespacing:inc", _("Expand line spacing"), INKSCAPE_ICON("draw-text"));
1144 } else {
1145 if (mod_shift(event)) {
1147 } else {
1149 }
1150 DocumentUndo::maybeDone(_desktop->getDocument(), "letterspacing:inc", _("Expand letter spacing"), INKSCAPE_ICON("draw-text"));
1151 }
1152 _updateCursor();
1154 ret = true;
1155 return;
1156 }
1157 }
1158 break;
1159 default:
1160 break;
1161 }
1162
1163 if (cursor_moved) {
1164 if (!mod_shift(event)) {
1166 }
1167 if (old_start != text_sel_start || old_end != text_sel_end) {
1168 _updateCursor();
1170 }
1171 ret = true;
1172 }
1173 } else {
1174 ret = true; // consumed by the IM
1175 }
1176 } else { // do nothing if there's no object to type in - the key will be sent to parent context,
1177 // except up/down that are swallowed to prevent the zoom field from activation
1178 if ((group0_keyval == GDK_KEY_Up ||
1179 group0_keyval == GDK_KEY_Down ||
1180 group0_keyval == GDK_KEY_KP_Up ||
1181 group0_keyval == GDK_KEY_KP_Down )
1182 && !mod_ctrl_only(event))
1183 {
1184 ret = true;
1185 } else if (group0_keyval == GDK_KEY_Escape) { // cancel rubberband
1186 if (creating) {
1187 creating = false;
1190 }
1191 } else if ((group0_keyval == GDK_KEY_x || group0_keyval == GDK_KEY_X) && mod_alt_only(event)) {
1192 _desktop->setToolboxFocusTo("TextFontFamilyAction_entry");
1193 ret = true;
1194 }
1195 }
1196 },
1197 [&] (KeyReleaseEvent const &event) {
1198 auto surface = _desktop->getInkscapeWindow()->get_surface()->gobj();
1199 if (!unimode && imc && gtk_im_context_filter_key(imc, false, surface, const_cast<GdkDevice*>(event.device->gobj()), event.time, event.keycode, (GdkModifierType)event.modifiers, event.group)) {
1200 ret = true;
1201 }
1202 },
1203 [&] (CanvasEvent const &event) {}
1204 );
1205
1206 return ret || ToolBase::root_handler(event);
1207}
1208
1212bool TextTool::pasteInline(Glib::ustring const clip_text)
1213{
1214 if (text || nascent_object) {
1215 // There is an active text object, or a new object was just created.
1216
1217 if (!clip_text.empty()) {
1218
1219 bool is_svg2 = false;
1220 auto const textitem = cast<SPText>(text);
1221 if (textitem) {
1222 is_svg2 = textitem->has_shape_inside() /*|| textitem->has_inline_size()*/; // Do now since hiding messes this up.
1223 textitem->hide_shape_inside();
1224 }
1225
1226 auto const flowtext = cast<SPFlowtext>(text);
1227 if (flowtext) {
1228 flowtext->fix_overflow_flowregion(false);
1229 }
1230
1231 // Fix for 244940
1232 // The XML standard defines the following as valid characters
1233 // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
1234 // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1235 // Since what comes in off the paste buffer will go right into XML, clean
1236 // the text here.
1237 auto txt = clip_text;
1238
1239 for (auto itr = txt.begin(); itr != txt.end(); ) {
1240 auto const paste_string_uchar = *itr;
1241
1242 // Make sure we don't have a control character. We should really check
1243 // for the whole range above... Add the rest of the invalid cases from
1244 // above if we find additional issues
1245 if (paste_string_uchar >= 0x00000020 ||
1246 paste_string_uchar == 0x00000009 ||
1247 paste_string_uchar == 0x0000000A ||
1248 paste_string_uchar == 0x0000000D)
1249 {
1250 ++itr;
1251 } else {
1252 itr = txt.erase(itr);
1253 }
1254 }
1255
1256 if (!text) { // create text if none (i.e. if nascent_object)
1257 _setupText();
1258 nascent_object = false; // we don't need it anymore, having created a real <text>
1259 }
1260
1261 // using indices is slow in ustrings. Whatever.
1262 Glib::ustring::size_type begin = 0;
1263 while (true) {
1264 auto const end = txt.find('\n', begin);
1265
1266 if (end == Glib::ustring::npos || is_svg2) {
1267 // Paste everything
1268 if (begin != txt.length()) {
1269 text_sel_start = text_sel_end = sp_te_replace(text, text_sel_start, text_sel_end, txt.substr(begin).c_str());
1270 }
1271 break;
1272 }
1273
1274 // Paste up to new line, add line, repeat.
1275 text_sel_start = text_sel_end = sp_te_replace(text, text_sel_start, text_sel_end, txt.substr(begin, end - begin).c_str());
1277 begin = end + 1;
1278 }
1279 if (textitem) {
1280 textitem->show_shape_inside();
1281 }
1282 if (flowtext) {
1283 flowtext->fix_overflow_flowregion(true);
1284 }
1285 DocumentUndo::done(_desktop->getDocument(), _("Paste text"), INKSCAPE_ICON("draw-text"));
1286
1287 return true;
1288 }
1289
1290 } // FIXME: else create and select a new object under cursor!
1291
1292 return false;
1293}
1294
1299Glib::ustring get_selected_text(TextTool const &tool)
1300{
1301 if (!tool.textItem()) {
1302 return {};
1303 }
1304
1306}
1307
1309{
1310 if (!tool.textItem()) {
1311 return nullptr;
1312 }
1313
1314 if (auto obj = sp_te_object_at_position(tool.textItem(), tool.text_sel_end)) {
1315 return take_style_from_item(const_cast<SPObject*>(obj));
1316 }
1317
1318 return nullptr;
1319}
1320
1326{
1327 if (!text) {
1328 return false;
1329 }
1330
1332 return false;
1333 }
1334
1335 iterator_pair pair;
1336 bool success = sp_te_delete(text, text_sel_start, text_sel_end, pair);
1337
1338 if (success) {
1339 text_sel_start = text_sel_end = pair.first;
1340 } else { // nothing deleted
1341 text_sel_start = pair.first;
1342 text_sel_end = pair.second;
1343 }
1344
1345 _updateCursor();
1347
1348 return true;
1349}
1350
1355{
1356 g_assert(selection);
1357 auto item = selection->singleItem();
1358
1359 if (text && item != text) {
1360 _forgetText();
1361 }
1362 text = nullptr;
1363
1365 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1367
1368 text = item;
1369 if (auto layout = te_get_layout(text)) {
1370 text_sel_start = text_sel_end = layout->end();
1371 }
1372 } else {
1373 text = nullptr;
1374 }
1375
1376 // we update cursor without scrolling, because this position may not be final;
1377 // item_handler moves cursor to the point of click immediately
1378 _updateCursor(false);
1380}
1381
1382void TextTool::_selectionModified(Selection */*selection*/, unsigned /*flags*/)
1383{
1384 bool scroll = !shape_editor->has_knotholder() ||
1385 !shape_editor->knotholder->is_dragging();
1386 _updateCursor(scroll);
1388}
1389
1391{
1392 if (!text) {
1393 return false;
1394 }
1396 return false; // will get picked up by the parent and applied to the whole text object
1397 }
1398
1400
1401 // This is a bandaid fix... whenever a style is changed it might cause the text layout to
1402 // change which requires rewriting the 'x' and 'y' attributes of the tpsans for Inkscape
1403 // multi-line text (with sodipodi:role="line"). We need to rewrite the repr after this is
1404 // done. rebuldLayout() will be called a second time unnecessarily.
1405 if (auto sptext = cast<SPText>(text)) {
1406 sptext->rebuildLayout();
1407 sptext->updateRepr();
1408 }
1409
1410 DocumentUndo::done(_desktop->getDocument(), _("Set text style"), INKSCAPE_ICON("draw-text"));
1411 _updateCursor();
1413 return true;
1414}
1415
1416int TextTool::_styleQueried(SPStyle *style, int property)
1417{
1418 if (!text) {
1419 return QUERY_STYLE_NOTHING;
1420 }
1421
1422 auto layout = te_get_layout(this->text);
1423 if (!layout) {
1424 return QUERY_STYLE_NOTHING;
1425 }
1426
1428
1429 Inkscape::Text::Layout::iterator begin_it, end_it;
1431 begin_it = text_sel_start;
1432 end_it = text_sel_end;
1433 } else {
1434 begin_it = text_sel_end;
1435 end_it = text_sel_start;
1436 }
1437 if (begin_it == end_it) {
1438 if (!begin_it.prevCharacter()) {
1439 end_it.nextCharacter();
1440 }
1441 }
1442
1443 std::vector<SPItem*> styles_list;
1444 for (auto it = begin_it; it < end_it; it.nextStartOfSpan()) {
1445 SPObject *pos_obj = nullptr;
1446 layout->getSourceOfCharacter(it, &pos_obj);
1447 if (!pos_obj) {
1448 continue;
1449 }
1450 if (!pos_obj->parent) { // the string is not in the document anymore (deleted)
1451 return 0;
1452 }
1453
1454 if (is<SPString>(pos_obj)) {
1455 pos_obj = pos_obj->parent; // SPStrings don't have style
1456 }
1457 styles_list.emplace_back(cast_unsafe<SPItem>(pos_obj));
1458 }
1459 std::reverse(styles_list.begin(), styles_list.end());
1460
1461 return sp_desktop_query_style_from_list(styles_list, style, property);
1462}
1463
1465{
1466 if (!text) {
1467 return;
1468 }
1469 if (auto layout = te_get_layout(text)) { // undo can change the text length without us knowing it
1470 layout->validateIterator(&text_sel_start);
1471 layout->validateIterator(&text_sel_end);
1472 }
1473}
1474
1476{
1477 blink_conn = Glib::signal_timeout().connect([this] { _blinkCursor(); return true; }, blink_time);
1478}
1479
1481{
1482 show = true;
1483 phase = false;
1484 cursor->set_stroke(0x000000ff);
1485 cursor->set_visible(true);
1487}
1488
1489void TextTool::_updateCursor(bool scroll_to_see)
1490{
1491 if (text) {
1492 Geom::Point p0, p1;
1494 Geom::Point const d0 = p0 * text->i2dt_affine();
1495 Geom::Point const d1 = p1 * text->i2dt_affine();
1496
1497 // scroll to show cursor
1498 if (scroll_to_see) {
1499
1500 // We don't want to scroll outside the text box area (i.e. when there is hidden text)
1501 // or we could end up in Timbuktu.
1502 bool scroll = true;
1503 if (auto sptext = cast<SPText>(text)) {
1504 Geom::OptRect opt_frame = sptext->get_frame();
1505 if (opt_frame && !opt_frame->contains(p0)) {
1506 scroll = false;
1507 }
1508 } else if (auto spflowtext = cast<SPFlowtext>(text)) {
1509 SPItem *frame = spflowtext->get_frame(nullptr); // first frame only
1510 Geom::OptRect opt_frame = frame->geometricBounds();
1511 if (opt_frame && !opt_frame->contains(p0)) {
1512 scroll = false;
1513 }
1514 }
1515
1516 if (scroll) {
1517 Geom::Point const center = _desktop->current_center();
1518 if (Geom::L2(d0 - center) > Geom::L2(d1 - center)) {
1519 // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
1521 } else {
1523 }
1524 }
1525 }
1526
1527 cursor->set_coords(d0, d1);
1528 _showCursor();
1529
1530 /* fixme: ... need another transformation to get canvas widget coordinate space? */
1531 if (imc) {
1532 GdkRectangle im_cursor = { 0, 0, 1, 1 };
1533 Geom::Point const top_left = _desktop->get_display_area().corner(0);
1534 Geom::Point const im_d0 = _desktop->d2w(d0 - top_left);
1535 Geom::Point const im_d1 = _desktop->d2w(d1 - top_left);
1536 Geom::Rect const im_rect(im_d0, im_d1);
1537 im_cursor.x = std::floor(im_rect.left());
1538 im_cursor.y = std::floor(im_rect.top());
1539 im_cursor.width = std::floor(im_rect.width());
1540 im_cursor.height = std::floor(im_rect.height());
1541 gtk_im_context_set_cursor_location(imc, &im_cursor);
1542 }
1543
1544 auto layout = te_get_layout(text);
1545 int const nChars = layout->iteratorToCharIndex(layout->end());
1546 char const *edit_message = ngettext("Type or edit text (%d character%s); <b>Enter</b> to start new line.", "Type or edit text (%d characters%s); <b>Enter</b> to start new line.", nChars);
1547 char const *edit_message_flowed = ngettext("Type or edit flowed text (%d character%s); <b>Enter</b> to start new paragraph.", "Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph.", nChars);
1548 bool truncated = layout->inputTruncated();
1549 char const *trunc = truncated ? _(" [truncated]") : "";
1550
1551 if (truncated) {
1552 frame->set_stroke(0xff0000ff);
1553 } else {
1554 frame->set_stroke(0x0000ff7f);
1555 }
1556
1557 std::vector<SPItem const *> shapes;
1558 std::unique_ptr<Shape> exclusion_shape;
1559 double padding = 0.0;
1560
1561 // Frame around text
1562 if (auto spflowtext = cast<SPFlowtext>(text)) {
1563 auto frame = spflowtext->get_frame(nullptr); // first frame only
1564 shapes.emplace_back(frame);
1565
1566 message_context->setF(NORMAL_MESSAGE, edit_message_flowed, nChars, trunc);
1567
1568 } else if (auto sptext = cast<SPText>(text)) {
1569 if (text->style->shape_inside.set) {
1570 for (auto const *href : text->style->shape_inside.hrefs) {
1571 shapes.push_back(href->getObject());
1572 }
1573 if (text->style->shape_padding.set) {
1574 // Calculate it here so we never show padding on FlowText or non-flowed Text (even if set)
1575 padding = text->style->shape_padding.computed;
1576 }
1577 if (text->style->shape_subtract.set) {
1578 // Find union of all exclusion shapes for later use
1579 exclusion_shape = sptext->getExclusionShape();
1580 }
1581 message_context->setF(NORMAL_MESSAGE, edit_message_flowed, nChars, trunc);
1582 } else {
1583 for (auto &child : text->children) {
1584 if (auto textpath = cast<SPTextPath>(&child)) {
1585 shapes.emplace_back(sp_textpath_get_path_item(textpath));
1586 }
1587 }
1588 message_context->setF(NORMAL_MESSAGE, edit_message, nChars, trunc);
1589 }
1590 }
1591
1592 SPCurve curve;
1593 for (auto shape_item : shapes) {
1594 if (auto shape = cast<SPShape>(shape_item)) {
1595 if (shape->curve()) {
1596 curve.append(shape->curve()->transformed(shape->transform));
1597 }
1598 }
1599 }
1600
1601 if (!curve.is_empty()) {
1602 bool has_padding = std::abs(padding) > 1e-12;
1603
1604 if (has_padding || exclusion_shape) {
1605 // Should only occur for SVG2 autoflowed text
1606 // See sp-text.cpp function _buildLayoutInit()
1607 Path temp;
1608 temp.LoadPathVector(curve.get_pathvector());
1609
1610 // Get initial shape-inside curve
1611 auto uncross = std::make_unique<Shape>();
1612 {
1613 Shape sh;
1614 temp.ConvertWithBackData(0.25); // Convert to polyline
1615 temp.Fill(&sh, 0);
1616 uncross->ConvertToShape(&sh);
1617 }
1618
1619 // Get padded shape exclusion
1620 if (has_padding) {
1621 Shape pad_shape;
1622 {
1623 Path padded;
1624 Path padt;
1625 Shape sh;
1626 padt.LoadPathVector(curve.get_pathvector());
1627 padt.Outline(&padded, padding, join_round, butt_straight, 20.0);
1628 padded.ConvertWithBackData(1.0); // Convert to polyline
1629 padded.Fill(&sh, 0);
1630 pad_shape.ConvertToShape(&sh);
1631 }
1632
1633 auto copy = std::make_unique<Shape>();
1634 copy->Booleen(uncross.get(), &pad_shape, padding > 0.0 ? bool_op_diff : bool_op_union);
1635 uncross = std::move(copy);
1636 }
1637
1638 // Remove exclusions plus margins from padding frame
1639 if (exclusion_shape && exclusion_shape->hasEdges()) {
1640 auto copy = std::make_unique<Shape>();
1641 copy->Booleen(uncross.get(), exclusion_shape.get(), bool_op_diff);
1642 uncross = std::move(copy);
1643 }
1644
1645 uncross->ConvertToForme(&temp);
1646 padding_frame->set_bpath(temp.MakePathVector() * text->i2dt_affine());
1647 padding_frame->set_visible(true);
1648 } else {
1649 padding_frame->set_visible(false);
1650 }
1651
1652 // Transform curve after doing padding.
1653 curve.transform(text->i2dt_affine());
1654 frame->set_bpath(&curve);
1655 frame->set_visible(true);
1656 } else {
1657 frame->set_visible(false);
1658 padding_frame->set_visible(false);
1659 }
1660
1661 } else {
1662 cursor->set_visible(false);
1663 frame->set_visible(false);
1664 show = false;
1665 if (!nascent_object) {
1666 message_context->set(NORMAL_MESSAGE, _("<b>Click</b> to select or create text, <b>drag</b> to create flowed text; then type.")); // FIXME: this is a copy of string from tools-switch, do not desync
1667 }
1668 }
1669
1671}
1672
1674{
1675 text_selection_quads.clear();
1676
1677 if (text) {
1679 for (int i = 0; i + 3 < quads.size(); i += 4) {
1680 auto quad = make_canvasitem<CanvasItemQuad>(_desktop->getCanvasControls(), quads[i], quads[i+1], quads[i+2], quads[i+3]);
1681 quad->set_fill(0x00777777); // Semi-transparent blue as Cairo cannot do inversion.
1682 quad->set_visible(true);
1683 text_selection_quads.emplace_back(std::move(quad));
1684 }
1685 }
1686
1688 shape_editor->knotholder->update_knots();
1689 }
1690}
1691
1693{
1694 if (!show) {
1695 return;
1696 }
1697
1698 if (phase) {
1699 phase = false;
1700 cursor->set_stroke(0x000000ff);
1701 } else {
1702 phase = true;
1703 cursor->set_stroke(0xffffffff);
1704 }
1705
1706 cursor->set_visible(true);
1707}
1708
1710{
1711 if (!text) {
1712 return;
1713 }
1714 auto ti = text;
1715 (void)ti;
1716 /* We have to set it to zero,
1717 * or selection changed signal messes everything up */
1718 text = nullptr;
1719
1720/* FIXME: this automatic deletion when nothing is inputted crashes the XML editor and also crashes when duplicating an empty flowtext.
1721 So don't create an empty flowtext in the first place? Create it when first character is typed.
1722 */
1723/*
1724 if ((is<SPText>(ti) || is<SPFlowtext>(ti)) && sp_te_input_is_empty(ti)) {
1725 auto text_repr = ti->getRepr();
1726 // the repr may already have been unparented
1727 // if we were called e.g. as the result of
1728 // an undo or the element being removed from
1729 // the XML editor
1730 if (text_repr && text_repr->parent()) {
1731 sp_repr_unparent(text_repr);
1732 DocumentUndo::done(_desktop->getDocument(), _("Remove empty text"), INKSCAPE_ICON("draw-text"));
1733 }
1734 }
1735*/
1736}
1737
1738void TextTool::_commit(GtkIMContext *, char *string)
1739{
1740 if (!text) {
1741 _setupText();
1742 nascent_object = false; // we don't need it anymore, having created a real <text>
1743 }
1744
1746 _updateCursor();
1748
1749 DocumentUndo::done(text->document, _("Type text"), INKSCAPE_ICON("draw-text"));
1750}
1751
1753{
1754 _desktop->getSelection()->set(other_text);
1755 text_sel_start = text_sel_end = where;
1756 _updateCursor();
1758}
1759
1761{
1762 _desktop->getSelection()->set(other_text);
1764}
1765
1766Text::Layout::iterator const *get_cursor_position(TextTool const &tool, SPObject const *other_text)
1767{
1768 if (other_text != tool.textItem()) {
1769 return nullptr;
1770 }
1771 return &tool.text_sel_end;
1772}
1773
1774} // namespace Inkscape::UI::Tools
1775
1776/*
1777 Local Variables:
1778 mode:c++
1779 c-file-style:"stroustrup"
1780 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1781 indent-tabs-mode:nil
1782 fill-column:99
1783 End:
1784*/
1785// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
@ butt_straight
Definition LivarotDefs.h:52
@ bool_op_diff
Definition LivarotDefs.h:79
@ bool_op_union
Definition LivarotDefs.h:77
@ join_round
Definition LivarotDefs.h:62
TODO: insert short description here.
TODO: insert short description here.
Callback converter for interfacing with C APIs.
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
Inkscape canvas widget.
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
bool contains(CRect const &r) const
Check whether the rectangle includes all points in the given rectangle.
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Rect bounds() const
Axis-aligned bounding box.
Point corner(unsigned i) const
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Axis aligned, non-empty rectangle.
Definition rect.h:92
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void set(MessageType type, char const *message)
pushes a message on the stack, replacing our old message
void setF(MessageType type, char const *format,...) G_GNUC_PRINTF(3
pushes a message on the stack using prinf-style formatting, and replacing our old message
void clear()
removes our current message from the stack
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void clear()
Unselects all selected objects.
SPItem * singleItem()
Returns a single selected item.
static Preferences * get()
Access the singleton Preferences object.
static Rubberband * get(SPDesktop *desktop)
void move(Geom::Point const &p)
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
sigc::connection connectModifiedFirst(sigc::slot< void(Selection *, unsigned)> slot)
Similar to connectModified, but will be run first.
Definition selection.h:239
sigc::connection connectChangedFirst(sigc::slot< void(Selection *)> slot)
Similar to connectChanged, but will be run first.
Definition selection.h:188
Class to store data for points which are snap candidates, either as a source or as a target.
Holds a position within the glyph output of Layout.
Definition Layout-TNG.h:973
void set_item(SPItem *item)
void unset_item(bool keep_knotholder=false)
std::unique_ptr< KnotHolder > knotholder
int _styleQueried(SPStyle *style, int property)
Text::Layout::iterator text_sel_end
Definition text-tool.h:59
sigc::scoped_connection focus_in_conn
Definition text-tool.h:101
CanvasItemPtr< CanvasItemRect > indicator
Definition text-tool.h:81
TextTool(SPDesktop *desktop)
Definition text-tool.cpp:72
SPItem * textItem() const
Definition text-tool.h:54
sigc::scoped_connection sel_modified_connection
Definition text-tool.h:98
void placeCursor(SPObject *text, Text::Layout::iterator where)
CanvasItemPtr< CanvasItemBpath > frame
Definition text-tool.h:82
sigc::scoped_connection focus_out_conn
Definition text-tool.h:102
void _selectionChanged(Selection *selection)
sigc::scoped_connection sel_changed_connection
Definition text-tool.h:97
sigc::scoped_connection blink_conn
Definition text-tool.h:103
std::vector< CanvasItemPtr< CanvasItemQuad > > text_selection_quads
Definition text-tool.h:84
CanvasItemPtr< CanvasItemCurve > cursor
Definition text-tool.h:80
void _updateCursor(bool scroll_to_see=true)
bool deleteSelection()
Deletes the currently selected characters.
bool root_handler(CanvasEvent const &event) override
CanvasItemPtr< CanvasItemBpath > padding_frame
Definition text-tool.h:83
bool _styleSet(SPCSSAttr const *css)
sigc::scoped_connection style_query_connection
Definition text-tool.h:100
void _insertUnichar()
Insert the character indicated by uni to replace the current selection, and reset uni to empty string...
bool pasteInline(Glib::ustring const clip_text)
Attempts to paste system clipboard into the currently edited text, returns true on success.
Text::Layout::iterator text_sel_start
Definition text-tool.h:58
void _selectionModified(Selection *selection, unsigned flags)
void _commit(GtkIMContext *imc, char *string)
sigc::scoped_connection style_set_connection
Definition text-tool.h:99
bool item_handler(SPItem *item, CanvasEvent const &event) override
Handles item specific events.
void placeCursorAt(SPObject *text, Geom::Point const &p)
Base class for Event processors.
Definition tool-base.h:107
bool sp_event_context_knot_mouseover() const
Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case).
void ungrabCanvasEvents()
Ungrab events from the Canvas Catchall.
void set_cursor(std::string filename)
Sets the current cursor to the given filename.
void grabCanvasEvents(EventMask mask=EventType::KEY_PRESS|EventType::BUTTON_RELEASE|EventType::MOTION|EventType::BUTTON_PRESS)
Grab events from the Canvas Catchall.
SPGroup * currentLayer() const
bool within_tolerance
are we still within tolerance of origin
Definition tool-base.h:148
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
virtual bool root_handler(CanvasEvent const &event)
void saveDragOrigin(Geom::Point const &pos)
bool checkDragMoved(Geom::Point const &pos)
Analyse the current position and return true once it has moved farther than tolerance from the drag o...
virtual bool item_handler(SPItem *item, CanvasEvent const &event)
Handles item specific events.
void enableGrDrag(bool enable=true)
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
Interface for refcounted XML nodes.
Definition node.h:80
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Path and its polyline approximation.
Definition Path.h:93
void ConvertWithBackData(double threshhold, bool relative=false)
Creates a polyline approximation of the path.
void Fill(Shape *dest, int pathID=-1, bool justAdd=false, bool closeIfNeeded=true, bool invert=false)
Fills the shape with the polyline approximation stored in this object.
void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation)
Load a lib2geom Geom::PathVector in this path object.
Geom::PathVector MakePathVector() const
Create a lib2geom Geom::PathVector from this Path object.
void Outline(Path *dest, double width, JoinType join, ButtType butt, double miter)
Wrapper around a Geom::PathVector object.
Definition curve.h:26
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
Geom::Point current_center() const
Definition desktop.cpp:660
Geom::Parallelogram get_display_area() const
Return canvas viewbox in desktop coordinates.
Definition desktop.cpp:521
Inkscape::CanvasItemGroup * getCanvasControls() const
Definition desktop.h:196
SPDocument * getDocument() const
Definition desktop.h:189
void emit_text_cursor_moved(Inkscape::UI::Tools::TextTool *tool)
Definition desktop.cpp:1369
void setToolboxFocusTo(char const *label)
Definition desktop.cpp:1128
InkscapeWindow const * getInkscapeWindow() const
Definition desktop.cpp:975
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
sigc::connection connectQueryStyle(F &&slot)
Definition desktop.h:269
Geom::Affine const & dt2doc() const
Definition desktop.cpp:1343
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
SPItem * getItemAtPoint(Geom::Point const &p, bool into_groups, SPItem *upto=nullptr) const
Definition desktop.cpp:352
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
double yaxisdir() const
Definition desktop.h:426
sigc::connection connectSetStyle(F &&slot)
Definition desktop.h:265
SPDocument * doc() const
Definition desktop.h:159
bool scroll_to_point(Geom::Point const &s_dt, double autoscrollspeed=0)
Scroll screen so as to keep point 'p' visible in window.
Definition desktop.cpp:894
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
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:1828
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1823
SnapManager snap_manager
Inkscape::Util::Unit const * display_units
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
ChildrenList children
Definition sp-object.h:907
An SVG style object.
Definition style.h:45
T< SPAttr::SHAPE_PADDING, SPILength > shape_padding
Definition style.h:181
T< SPAttr::SHAPE_INSIDE, SPIShapes > shape_inside
SVG2 Text Wrapping.
Definition style.h:179
T< SPAttr::SHAPE_SUBTRACT, SPIShapes > shape_subtract
Definition style.h:180
A class to store/manipulate directed graphs.
Definition Shape.h:65
int ConvertToShape(Shape *a, FillRule directed=fill_nonZero, bool invert=false)
Using a given fill rule, find all intersections in the shape given, create a new intersection free sh...
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
std::shared_ptr< Css const > css
double sp_desktop_get_font_size_tool(SPDesktop *desktop)
Returns the font size (in SVG pixels) of the text tool style (if text tool uses its own style) or des...
int sp_desktop_query_style_from_list(const std::vector< SPItem * > &list, SPStyle *style, int property)
Query the given list of objects for the given property, write the result to style,...
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
@ QUERY_STYLE_NOTHING
Editable view implementation.
TODO: insert short description here.
Macro for icon names used in Inkscape.
SPItem * item
Inkscape - An SVG editor.
Interface for locally managing a current status message.
Raw stack of active status messages.
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
static R & release(R &r)
Decrements the reference count of a anchored object.
Glib::ustring get_selected_text(TextTool const &tool)
Gets the raw characters that comprise the currently selected text, converting line breaks into lf cha...
SPCSSAttr * get_style_at_cursor(TextTool const &tool)
static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8)
Text::Layout::iterator const * get_cursor_position(TextTool const &tool, SPObject const *other_text)
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
int gobble_key_events(unsigned keyval, unsigned mask)
Definition tool-base.h:243
bool mod_alt(unsigned modifiers)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool mod_ctrl_only(unsigned modifiers)
bool mod_ctrl(unsigned modifiers)
bool mod_shift(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
void dump_event(CanvasEvent const &event, char const *prefix, bool merge=true)
Print an event to stdout.
Definition debug.h:29
@ ERROR_MESSAGE
Definition message.h:29
@ NORMAL_MESSAGE
Definition message.h:26
@ IMMEDIATE_MESSAGE
Definition message.h:27
constexpr bool DEBUG_EVENTS
Whether event debug printing is enabled.
Definition debug.h:20
bool have_viable_layer(SPDesktop *desktop, MessageContext *message)
Check to see if the current layer is both unhidden and unlocked.
unsigned long weight
Definition quantize.cpp:37
Ocnode * child[8]
Definition quantize.cpp:33
void uncross(std::list< Point > &loop)
Definition rdm-area.cpp:102
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_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
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
auto len
Definition safe-printf.h:21
SPCSSAttr * take_style_from_item(SPObject *object)
Inkscape::ShapeEditor This is a container class which contains a knotholder for shapes.
SPCSSAttr - interface for CSS Attributes.
SPItem * create_flowtext_with_internal_frame(SPDesktop *desktop, Geom::Point p0, Geom::Point p1)
TODO: insert short description here.
SPItem * create_text_with_rectangle(SPDesktop *desktop, Geom::Point p0, Geom::Point p1)
Definition sp-text.cpp:1276
TODO: insert short description here.
SPItem * sp_textpath_get_path_item(SPTextPath const *tp)
Definition sp-tspan.cpp:463
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
std::shared_ptr< Gdk::Device const > device
The device that sourced the event. May be null.
A key has been pressed.
A key has been released.
Movement of the mouse pointer.
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
Definition curve.h:24
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
@ SP_CSS_FONT_STYLE_NORMAL
Definition style-enums.h:61
@ SP_CSS_FONT_WEIGHT_NORMAL
Definition style-enums.h:81
@ SP_CSS_FONT_WEIGHT_400
Definition style-enums.h:75
@ SP_CSS_FONT_WEIGHT_100
Definition style-enums.h:72
SPStyle - a style object for SPItem objects.
SPDesktop * desktop
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.
SPObject const * sp_te_object_at_position(SPItem const *text, Inkscape::Text::Layout::iterator const &position)
Inkscape::Text::Layout::iterator sp_te_get_position_by_coords(SPItem const *item, Geom::Point const &i_p)
void sp_te_adjust_rotation(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPDesktop *, gdouble degrees)
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_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)
double sp_te_get_average_linespacing(SPItem *text)
void sp_te_get_cursor_coords(SPItem const *item, Inkscape::Text::Layout::iterator const &position, Geom::Point &p0, Geom::Point &p1)
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)
void sp_te_adjust_linespacing_screen(SPItem *text, Inkscape::Text::Layout::iterator const &, Inkscape::Text::Layout::iterator const &, SPDesktop *desktop, gdouble by)
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...
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)
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...
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)
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.
std::pair< Inkscape::Text::Layout::iterator, Inkscape::Text::Layout::iterator > iterator_pair
TextTool.
_GtkIMContext GtkIMContext
Definition text-tool.h:30
Debug printing of event data.
unsigned int gunichar