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"
36 SPItem *
37 text_in_selection(Inkscape::Selection *selection)
38 {
39 for (GSList *items = (GSList *) selection->itemList();
40 items != NULL;
41 items = items->next) {
42 if (SP_IS_TEXT(items->data))
43 return ((SPItem *) items->data);
44 }
45 return NULL;
46 }
48 SPItem *
49 flowtext_in_selection(Inkscape::Selection *selection)
50 {
51 for (GSList *items = (GSList *) selection->itemList();
52 items != NULL;
53 items = items->next) {
54 if (SP_IS_FLOWTEXT(items->data))
55 return ((SPItem *) items->data);
56 }
57 return NULL;
58 }
60 SPItem *
61 text_or_flowtext_in_selection(Inkscape::Selection *selection)
62 {
63 for (GSList *items = (GSList *) selection->itemList();
64 items != NULL;
65 items = items->next) {
66 if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
67 return ((SPItem *) items->data);
68 }
69 return NULL;
70 }
72 SPItem *
73 shape_in_selection(Inkscape::Selection *selection)
74 {
75 for (GSList *items = (GSList *) selection->itemList();
76 items != NULL;
77 items = items->next) {
78 if (SP_IS_SHAPE(items->data))
79 return ((SPItem *) items->data);
80 }
81 return NULL;
82 }
84 void
85 text_put_on_path()
86 {
87 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
88 if (!desktop)
89 return;
91 Inkscape::Selection *selection = sp_desktop_selection(desktop);
93 SPItem *text = text_or_flowtext_in_selection(selection);
94 SPItem *shape = shape_in_selection(selection);
96 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
98 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
99 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
100 return;
101 }
103 if (SP_IS_TEXT_TEXTPATH(text)) {
104 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."));
105 return;
106 }
108 if (SP_IS_RECT(shape)) {
109 // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
110 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
111 return;
112 }
114 // if a flowed text is selected, convert it to a regular text object
115 if (SP_IS_FLOWTEXT(text)) {
117 if (!SP_FLOWTEXT(text)->layout.outputExists()) {
118 sp_desktop_message_stack(desktop)->
119 flash(Inkscape::WARNING_MESSAGE,
120 _("The flowed text(s) must be <b>visible</b> in order to be put on a path."));
121 }
123 Inkscape::XML::Node *repr = SP_FLOWTEXT(text)->getAsText();
125 if (!repr) return;
127 Inkscape::XML::Node *parent = SP_OBJECT_REPR(text)->parent();
128 parent->appendChild(repr);
130 SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
131 sp_item_write_transform(new_item, repr, text->transform);
132 SP_OBJECT(new_item)->updateRepr();
134 Inkscape::GC::release(repr);
135 text->deleteObject(); // delete the orignal flowtext
137 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
139 selection->clear();
141 text = new_item; // point to the new text
142 }
144 Inkscape::Text::Layout const *layout = te_get_layout(text);
145 Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
147 // remove transform from text, but recursively scale text's fontsize by the expansion
148 SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
149 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
151 // make a list of text children
152 GSList *text_reprs = NULL;
153 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
154 text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
155 }
157 // create textPath and put it into the text
158 Inkscape::XML::Node *textpath = xml_doc->createElement("svg:textPath");
159 // reference the shape
160 textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
161 if (text_alignment == Inkscape::Text::Layout::RIGHT)
162 textpath->setAttribute("startOffset", "100%");
163 else if (text_alignment == Inkscape::Text::Layout::CENTER)
164 textpath->setAttribute("startOffset", "50%");
165 SP_OBJECT_REPR(text)->addChild(textpath, NULL);
167 for ( GSList *i = text_reprs ; i ; i = i->next ) {
168 // make a copy of each text child
169 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate();
170 // We cannot have multiline in textpath, so remove line attrs from tspans
171 if (!strcmp(copy->name(), "svg:tspan")) {
172 copy->setAttribute("sodipodi:role", NULL);
173 copy->setAttribute("x", NULL);
174 copy->setAttribute("y", NULL);
175 }
176 // remove the old repr from under text
177 SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
178 // put its copy into under textPath
179 textpath->addChild(copy, NULL); // fixme: copy id
180 }
182 // x/y are useless with textpath, and confuse Batik 1.5
183 SP_OBJECT_REPR(text)->setAttribute("x", NULL);
184 SP_OBJECT_REPR(text)->setAttribute("y", NULL);
186 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
187 _("Put text on path"));
188 g_slist_free(text_reprs);
189 }
191 void
192 text_remove_from_path()
193 {
194 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
196 Inkscape::Selection *selection = sp_desktop_selection(desktop);
198 if (selection->isEmpty()) {
199 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
200 return;
201 }
203 bool did = false;
205 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
206 items != NULL;
207 items = items->next) {
209 if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
210 continue;
211 }
213 SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
215 did = true;
217 sp_textpath_to_text(tp);
218 }
220 if (!did) {
221 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
222 } else {
223 selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
224 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
225 _("Remove text from path"));
226 }
227 }
229 void
230 text_remove_all_kerns_recursively(SPObject *o)
231 {
232 SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
233 SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
234 SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
236 for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
237 text_remove_all_kerns_recursively(i);
238 }
239 }
241 //FIXME: must work with text selection
242 void
243 text_remove_all_kerns()
244 {
245 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
247 Inkscape::Selection *selection = sp_desktop_selection(desktop);
249 if (selection->isEmpty()) {
250 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
251 return;
252 }
254 bool did = false;
256 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
257 items != NULL;
258 items = items->next) {
260 if (!SP_IS_TEXT(SP_OBJECT(items->data))) {
261 continue;
262 }
264 text_remove_all_kerns_recursively(SP_OBJECT(items->data));
265 SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
266 did = true;
267 }
269 if (!did) {
270 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
271 } else {
272 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
273 _("Remove manual kerns"));
274 }
275 }
277 void
278 text_flow_into_shape()
279 {
280 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
281 if (!desktop)
282 return;
284 SPDocument *doc = sp_desktop_document (desktop);
285 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
287 Inkscape::Selection *selection = sp_desktop_selection(desktop);
289 SPItem *text = text_or_flowtext_in_selection(selection);
290 SPItem *shape = shape_in_selection(selection);
292 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
293 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."));
294 return;
295 }
297 if (SP_IS_TEXT(text)) {
298 // remove transform from text, but recursively scale text's fontsize by the expansion
299 SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
300 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
301 }
303 Inkscape::XML::Node *root_repr = xml_doc->createElement("svg:flowRoot");
304 root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
305 root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
306 SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
307 SPObject *root_object = doc->getObjectByRepr(root_repr);
308 g_return_if_fail(SP_IS_FLOWTEXT(root_object));
310 Inkscape::XML::Node *region_repr = xml_doc->createElement("svg:flowRegion");
311 root_repr->appendChild(region_repr);
312 SPObject *object = doc->getObjectByRepr(region_repr);
313 g_return_if_fail(SP_IS_FLOWREGION(object));
315 /* Add clones */
316 for (GSList *items = (GSList *) selection->itemList();
317 items != NULL;
318 items = items->next) {
319 SPItem *item = SP_ITEM(items->data);
320 if (SP_IS_SHAPE(item)){
321 Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
322 clone->setAttribute("x", "0");
323 clone->setAttribute("y", "0");
324 clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
326 // add the new clone to the region
327 region_repr->appendChild(clone);
328 }
329 }
331 if (SP_IS_TEXT(text)) { // flow from text, as string
332 Inkscape::XML::Node *para_repr = xml_doc->createElement("svg:flowPara");
333 root_repr->appendChild(para_repr);
334 object = doc->getObjectByRepr(para_repr);
335 g_return_if_fail(SP_IS_FLOWPARA(object));
337 Inkscape::Text::Layout const *layout = te_get_layout(text);
338 Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
340 Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
341 para_repr->appendChild(text_repr);
343 Inkscape::GC::release(para_repr);
344 Inkscape::GC::release(text_repr);
346 } else { // reflow an already flowed text, preserving paras
347 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
348 if (SP_IS_FLOWPARA(o)) {
349 Inkscape::XML::Node *para_repr = SP_OBJECT_REPR(o)->duplicate();
350 root_repr->appendChild(para_repr);
351 object = doc->getObjectByRepr(para_repr);
352 g_return_if_fail(SP_IS_FLOWPARA(object));
353 Inkscape::GC::release(para_repr);
354 }
355 }
356 }
358 SP_OBJECT(text)->deleteObject (true);
360 sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
361 _("Flow text into shape"));
363 sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
365 Inkscape::GC::release(root_repr);
366 Inkscape::GC::release(region_repr);
367 }
369 void
370 text_unflow ()
371 {
372 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
373 if (!desktop)
374 return;
376 SPDocument *doc = sp_desktop_document (desktop);
377 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
379 Inkscape::Selection *selection = sp_desktop_selection(desktop);
382 if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
383 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
384 return;
385 }
387 GSList *new_objs = NULL;
388 GSList *old_objs = NULL;
390 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
391 items != NULL;
392 items = items->next) {
394 if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
395 continue;
396 }
398 SPItem *flowtext = SP_ITEM(items->data);
400 if (sp_te_get_string_multiline(flowtext) == NULL) { // flowtext is empty
401 continue;
402 }
404 /* Create <text> */
405 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
406 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
408 /* Set style */
409 rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
411 NRRect bbox;
412 sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
413 NR::Point xy(bbox.x0, bbox.y0);
414 if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
415 sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
416 sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
417 }
419 /* Create <tspan> */
420 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
421 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
422 rtext->addChild(rtspan, NULL);
424 gchar *text_string = sp_te_get_string_multiline(flowtext);
425 Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_string); // FIXME: transfer all formatting!!!
426 free(text_string);
427 rtspan->appendChild(text_repr);
429 SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
430 SPObject *text_object = doc->getObjectByRepr(rtext);
432 new_objs = g_slist_prepend (new_objs, text_object);
433 old_objs = g_slist_prepend (old_objs, flowtext);
435 Inkscape::GC::release(rtext);
436 Inkscape::GC::release(rtspan);
437 Inkscape::GC::release(text_repr);
438 }
440 selection->clear();
441 selection->setList(new_objs);
442 for (GSList *i = old_objs; i; i = i->next) {
443 SP_OBJECT(i->data)->deleteObject (true);
444 }
446 g_slist_free (old_objs);
447 g_slist_free (new_objs);
449 sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
450 _("Unflow flowed text"));
451 }
453 void
454 flowtext_to_text()
455 {
456 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
458 Inkscape::Selection *selection = sp_desktop_selection(desktop);
460 if (selection->isEmpty()) {
461 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE,
462 _("Select <b>flowed text(s)</b> to convert."));
463 return;
464 }
466 bool did = false;
468 GSList *reprs = NULL;
469 GSList *items = g_slist_copy((GSList *) selection->itemList());
470 for (; items != NULL; items = items->next) {
472 SPItem *item = (SPItem *) items->data;
474 if (!SP_IS_FLOWTEXT(item))
475 continue;
477 if (!SP_FLOWTEXT(item)->layout.outputExists()) {
478 sp_desktop_message_stack(desktop)->
479 flash(Inkscape::WARNING_MESSAGE,
480 _("The flowed text(s) must be <b>visible</b> in order to be converted."));
481 return;
482 }
484 Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
486 if (!repr) break;
488 did = true;
490 Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
491 parent->appendChild(repr);
493 SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
494 sp_item_write_transform(new_item, repr, item->transform);
495 SP_OBJECT(new_item)->updateRepr();
497 Inkscape::GC::release(repr);
498 item->deleteObject();
500 reprs = g_slist_prepend(reprs, repr);
501 }
503 g_slist_free(items);
505 if (did) {
506 sp_document_done(sp_desktop_document(desktop),
507 SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
508 _("Convert flowed text to text"));
509 selection->setReprList(reprs);
510 } else {
511 sp_desktop_message_stack(desktop)->
512 flash(Inkscape::ERROR_MESSAGE,
513 _("<b>No flowed text(s)</b> to convert in the selection."));
514 }
516 g_slist_free(reprs);
517 }
520 /*
521 Local Variables:
522 mode:c++
523 c-file-style:"stroustrup"
524 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
525 indent-tabs-mode:nil
526 fill-column:99
527 End:
528 */
529 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :