e1eaa47fb98e6735167471dd87573714444c1d8a
1 #define __SP_TEXT_CHEMISTRY_C__
3 /*
4 * Text commands
5 *
6 * Authors:
7 * bulia byak
8 *
9 * Copyright (C) 2004 authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
17 #include "libnr/nr-matrix-fns.h"
18 #include "xml/repr.h"
19 #include <glibmm/i18n.h>
20 #include "sp-rect.h"
21 #include "sp-textpath.h"
22 #include "inkscape.h"
23 #include "desktop.h"
24 #include "document.h"
25 #include "message-stack.h"
26 #include "selection.h"
27 #include "style.h"
28 #include "desktop-handles.h"
29 #include "text-editing.h"
30 #include "text-chemistry.h"
31 #include "sp-flowtext.h"
32 #include "sp-flowregion.h"
33 #include "sp-flowdiv.h"
34 #include "sp-tspan.h"
37 SPItem *
38 text_in_selection(Inkscape::Selection *selection)
39 {
40 for (GSList *items = (GSList *) selection->itemList();
41 items != NULL;
42 items = items->next) {
43 if (SP_IS_TEXT(items->data))
44 return ((SPItem *) items->data);
45 }
46 return NULL;
47 }
49 SPItem *
50 flowtext_in_selection(Inkscape::Selection *selection)
51 {
52 for (GSList *items = (GSList *) selection->itemList();
53 items != NULL;
54 items = items->next) {
55 if (SP_IS_FLOWTEXT(items->data))
56 return ((SPItem *) items->data);
57 }
58 return NULL;
59 }
61 SPItem *
62 text_or_flowtext_in_selection(Inkscape::Selection *selection)
63 {
64 for (GSList *items = (GSList *) selection->itemList();
65 items != NULL;
66 items = items->next) {
67 if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
68 return ((SPItem *) items->data);
69 }
70 return NULL;
71 }
73 SPItem *
74 shape_in_selection(Inkscape::Selection *selection)
75 {
76 for (GSList *items = (GSList *) selection->itemList();
77 items != NULL;
78 items = items->next) {
79 if (SP_IS_SHAPE(items->data))
80 return ((SPItem *) items->data);
81 }
82 return NULL;
83 }
85 void
86 text_put_on_path()
87 {
88 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
89 if (!desktop)
90 return;
92 Inkscape::Selection *selection = sp_desktop_selection(desktop);
94 SPItem *text = text_or_flowtext_in_selection(selection);
95 SPItem *shape = shape_in_selection(selection);
97 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
99 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
100 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
101 return;
102 }
104 if (SP_IS_TEXT_TEXTPATH(text)) {
105 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("This text object is <b>already put on a path</b>. Remove it from the path first. Use <b>Shift+D</b> to look up its path."));
106 return;
107 }
109 if (SP_IS_RECT(shape)) {
110 // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
111 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
112 return;
113 }
115 // if a flowed text is selected, convert it to a regular text object
116 if (SP_IS_FLOWTEXT(text)) {
118 if (!SP_FLOWTEXT(text)->layout.outputExists()) {
119 sp_desktop_message_stack(desktop)->
120 flash(Inkscape::WARNING_MESSAGE,
121 _("The flowed text(s) must be <b>visible</b> in order to be put on a path."));
122 }
124 Inkscape::XML::Node *repr = SP_FLOWTEXT(text)->getAsText();
126 if (!repr) return;
128 Inkscape::XML::Node *parent = SP_OBJECT_REPR(text)->parent();
129 parent->appendChild(repr);
131 SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
132 sp_item_write_transform(new_item, repr, text->transform);
133 SP_OBJECT(new_item)->updateRepr();
135 Inkscape::GC::release(repr);
136 text->deleteObject(); // delete the orignal flowtext
138 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
140 selection->clear();
142 text = new_item; // point to the new text
143 }
145 Inkscape::Text::Layout const *layout = te_get_layout(text);
146 Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
148 // remove transform from text, but recursively scale text's fontsize by the expansion
149 SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
150 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
152 // make a list of text children
153 GSList *text_reprs = NULL;
154 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
155 text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
156 }
158 // create textPath and put it into the text
159 Inkscape::XML::Node *textpath = xml_doc->createElement("svg:textPath");
160 // reference the shape
161 textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
162 if (text_alignment == Inkscape::Text::Layout::RIGHT)
163 textpath->setAttribute("startOffset", "100%");
164 else if (text_alignment == Inkscape::Text::Layout::CENTER)
165 textpath->setAttribute("startOffset", "50%");
166 SP_OBJECT_REPR(text)->addChild(textpath, NULL);
168 for ( GSList *i = text_reprs ; i ; i = i->next ) {
169 // Make a copy of each text child
170 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate(xml_doc);
171 // We cannot have multiline in textpath, so remove line attrs from tspans
172 if (!strcmp(copy->name(), "svg:tspan")) {
173 copy->setAttribute("sodipodi:role", NULL);
174 copy->setAttribute("x", NULL);
175 copy->setAttribute("y", NULL);
176 }
177 // remove the old repr from under text
178 SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
179 // put its copy into under textPath
180 textpath->addChild(copy, NULL); // fixme: copy id
181 }
183 // x/y are useless with textpath, and confuse Batik 1.5
184 SP_OBJECT_REPR(text)->setAttribute("x", NULL);
185 SP_OBJECT_REPR(text)->setAttribute("y", NULL);
187 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
188 _("Put text on path"));
189 g_slist_free(text_reprs);
190 }
192 void
193 text_remove_from_path()
194 {
195 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
197 Inkscape::Selection *selection = sp_desktop_selection(desktop);
199 if (selection->isEmpty()) {
200 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
201 return;
202 }
204 bool did = false;
206 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
207 items != NULL;
208 items = items->next) {
210 if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
211 continue;
212 }
214 SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
216 did = true;
218 sp_textpath_to_text(tp);
219 }
221 if (!did) {
222 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
223 } else {
224 selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
225 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
226 _("Remove text from path"));
227 }
228 }
230 void
231 text_remove_all_kerns_recursively(SPObject *o)
232 {
233 SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
234 SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
235 SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
237 // if x contains a list, leave only the first value
238 gchar *x = (gchar *) SP_OBJECT_REPR(o)->attribute("x");
239 if (x) {
240 gchar **xa_space = g_strsplit(x, " ", 0);
241 gchar **xa_comma = g_strsplit(x, ",", 0);
242 if (xa_space && *xa_space && *(xa_space + 1)) {
243 SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_space));
244 } else if (xa_comma && *xa_comma && *(xa_comma + 1)) {
245 SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_comma));
246 }
247 g_strfreev(xa_space);
248 g_strfreev(xa_comma);
249 }
251 for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
252 text_remove_all_kerns_recursively(i);
253 }
254 }
256 //FIXME: must work with text selection
257 void
258 text_remove_all_kerns()
259 {
260 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
262 Inkscape::Selection *selection = sp_desktop_selection(desktop);
264 if (selection->isEmpty()) {
265 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
266 return;
267 }
269 bool did = false;
271 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
272 items != NULL;
273 items = items->next) {
274 SPObject *obj = SP_OBJECT(items->data);
276 if (!SP_IS_TEXT(obj) && !SP_IS_TSPAN(obj) && !SP_IS_FLOWTEXT(obj)) {
277 continue;
278 }
280 text_remove_all_kerns_recursively(obj);
281 obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
282 did = true;
283 }
285 if (!did) {
286 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
287 } else {
288 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
289 _("Remove manual kerns"));
290 }
291 }
293 void
294 text_flow_into_shape()
295 {
296 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
297 if (!desktop)
298 return;
300 SPDocument *doc = sp_desktop_document (desktop);
301 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
303 Inkscape::Selection *selection = sp_desktop_selection(desktop);
305 SPItem *text = text_or_flowtext_in_selection(selection);
306 SPItem *shape = shape_in_selection(selection);
308 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
309 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text</b> and one or more <b>paths or shapes</b> to flow text into frame."));
310 return;
311 }
313 if (SP_IS_TEXT(text)) {
314 // remove transform from text, but recursively scale text's fontsize by the expansion
315 SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
316 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
317 }
319 Inkscape::XML::Node *root_repr = xml_doc->createElement("svg:flowRoot");
320 root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
321 root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
322 SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
323 SPObject *root_object = doc->getObjectByRepr(root_repr);
324 g_return_if_fail(SP_IS_FLOWTEXT(root_object));
326 Inkscape::XML::Node *region_repr = xml_doc->createElement("svg:flowRegion");
327 root_repr->appendChild(region_repr);
328 SPObject *object = doc->getObjectByRepr(region_repr);
329 g_return_if_fail(SP_IS_FLOWREGION(object));
331 /* Add clones */
332 for (GSList *items = (GSList *) selection->itemList();
333 items != NULL;
334 items = items->next) {
335 SPItem *item = SP_ITEM(items->data);
336 if (SP_IS_SHAPE(item)){
337 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
338 clone->setAttribute("x", "0");
339 clone->setAttribute("y", "0");
340 clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
342 // add the new clone to the region
343 region_repr->appendChild(clone);
344 }
345 }
347 if (SP_IS_TEXT(text)) { // flow from text, as string
348 Inkscape::XML::Node *para_repr = xml_doc->createElement("svg:flowPara");
349 root_repr->appendChild(para_repr);
350 object = doc->getObjectByRepr(para_repr);
351 g_return_if_fail(SP_IS_FLOWPARA(object));
353 Inkscape::Text::Layout const *layout = te_get_layout(text);
354 Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
356 Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
357 para_repr->appendChild(text_repr);
359 Inkscape::GC::release(para_repr);
360 Inkscape::GC::release(text_repr);
362 } else { // reflow an already flowed text, preserving paras
363 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
364 if (SP_IS_FLOWPARA(o)) {
365 Inkscape::XML::Node *para_repr = SP_OBJECT_REPR(o)->duplicate(xml_doc);
366 root_repr->appendChild(para_repr);
367 object = doc->getObjectByRepr(para_repr);
368 g_return_if_fail(SP_IS_FLOWPARA(object));
369 Inkscape::GC::release(para_repr);
370 }
371 }
372 }
374 SP_OBJECT(text)->deleteObject (true);
376 sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
377 _("Flow text into shape"));
379 sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
381 Inkscape::GC::release(root_repr);
382 Inkscape::GC::release(region_repr);
383 }
385 void
386 text_unflow ()
387 {
388 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
389 if (!desktop)
390 return;
392 SPDocument *doc = sp_desktop_document (desktop);
393 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
395 Inkscape::Selection *selection = sp_desktop_selection(desktop);
398 if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
399 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
400 return;
401 }
403 GSList *new_objs = NULL;
404 GSList *old_objs = NULL;
406 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
407 items != NULL;
408 items = items->next) {
410 if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
411 continue;
412 }
414 SPItem *flowtext = SP_ITEM(items->data);
416 if (sp_te_get_string_multiline(flowtext) == NULL) { // flowtext is empty
417 continue;
418 }
420 /* Create <text> */
421 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
422 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
424 /* Set style */
425 rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
427 NRRect bbox;
428 sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
429 NR::Point xy(bbox.x0, bbox.y0);
430 if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
431 sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
432 sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
433 }
435 /* Create <tspan> */
436 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
437 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
438 rtext->addChild(rtspan, NULL);
440 gchar *text_string = sp_te_get_string_multiline(flowtext);
441 Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_string); // FIXME: transfer all formatting!!!
442 free(text_string);
443 rtspan->appendChild(text_repr);
445 SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
446 SPObject *text_object = doc->getObjectByRepr(rtext);
448 new_objs = g_slist_prepend (new_objs, text_object);
449 old_objs = g_slist_prepend (old_objs, flowtext);
451 Inkscape::GC::release(rtext);
452 Inkscape::GC::release(rtspan);
453 Inkscape::GC::release(text_repr);
454 }
456 selection->clear();
457 selection->setList(new_objs);
458 for (GSList *i = old_objs; i; i = i->next) {
459 SP_OBJECT(i->data)->deleteObject (true);
460 }
462 g_slist_free (old_objs);
463 g_slist_free (new_objs);
465 sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
466 _("Unflow flowed text"));
467 }
469 void
470 flowtext_to_text()
471 {
472 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
474 Inkscape::Selection *selection = sp_desktop_selection(desktop);
476 if (selection->isEmpty()) {
477 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE,
478 _("Select <b>flowed text(s)</b> to convert."));
479 return;
480 }
482 bool did = false;
484 GSList *reprs = NULL;
485 GSList *items = g_slist_copy((GSList *) selection->itemList());
486 for (; items != NULL; items = items->next) {
488 SPItem *item = (SPItem *) items->data;
490 if (!SP_IS_FLOWTEXT(item))
491 continue;
493 if (!SP_FLOWTEXT(item)->layout.outputExists()) {
494 sp_desktop_message_stack(desktop)->
495 flash(Inkscape::WARNING_MESSAGE,
496 _("The flowed text(s) must be <b>visible</b> in order to be converted."));
497 return;
498 }
500 Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
502 if (!repr) break;
504 did = true;
506 Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
507 parent->appendChild(repr);
509 SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
510 sp_item_write_transform(new_item, repr, item->transform);
511 SP_OBJECT(new_item)->updateRepr();
513 Inkscape::GC::release(repr);
514 item->deleteObject();
516 reprs = g_slist_prepend(reprs, repr);
517 }
519 g_slist_free(items);
521 if (did) {
522 sp_document_done(sp_desktop_document(desktop),
523 SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
524 _("Convert flowed text to text"));
525 selection->setReprList(reprs);
526 } else {
527 sp_desktop_message_stack(desktop)->
528 flash(Inkscape::ERROR_MESSAGE,
529 _("<b>No flowed text(s)</b> to convert in the selection."));
530 }
532 g_slist_free(reprs);
533 }
536 /*
537 Local Variables:
538 mode:c++
539 c-file-style:"stroustrup"
540 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
541 indent-tabs-mode:nil
542 fill-column:99
543 End:
544 */
545 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :