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 "document.h"
24 #include "message-stack.h"
25 #include "selection.h"
26 #include "style.h"
27 #include "desktop-handles.h"
28 #include "text-editing.h"
29 #include "text-chemistry.h"
30 #include "sp-flowtext.h"
31 #include "sp-flowregion.h"
32 #include "sp-flowdiv.h"
35 SPItem *
36 text_in_selection(Inkscape::Selection *selection)
37 {
38 for (GSList *items = (GSList *) selection->itemList();
39 items != NULL;
40 items = items->next) {
41 if (SP_IS_TEXT(items->data))
42 return ((SPItem *) items->data);
43 }
44 return NULL;
45 }
47 SPItem *
48 flowtext_in_selection(Inkscape::Selection *selection)
49 {
50 for (GSList *items = (GSList *) selection->itemList();
51 items != NULL;
52 items = items->next) {
53 if (SP_IS_FLOWTEXT(items->data))
54 return ((SPItem *) items->data);
55 }
56 return NULL;
57 }
59 SPItem *
60 text_or_flowtext_in_selection(Inkscape::Selection *selection)
61 {
62 for (GSList *items = (GSList *) selection->itemList();
63 items != NULL;
64 items = items->next) {
65 if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
66 return ((SPItem *) items->data);
67 }
68 return NULL;
69 }
71 SPItem *
72 shape_in_selection(Inkscape::Selection *selection)
73 {
74 for (GSList *items = (GSList *) selection->itemList();
75 items != NULL;
76 items = items->next) {
77 if (SP_IS_SHAPE(items->data))
78 return ((SPItem *) items->data);
79 }
80 return NULL;
81 }
83 void
84 text_put_on_path()
85 {
86 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
87 if (!desktop)
88 return;
90 Inkscape::Selection *selection = sp_desktop_selection(desktop);
92 SPItem *text = text_or_flowtext_in_selection(selection);
93 SPItem *shape = shape_in_selection(selection);
95 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
96 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
97 return;
98 }
100 if (SP_IS_TEXT_TEXTPATH(text)) {
101 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."));
102 return;
103 }
105 if (SP_IS_RECT(shape)) {
106 // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
107 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
108 return;
109 }
111 // if a flowed text is selected, convert it to a regular text object
112 if (SP_IS_FLOWTEXT(text)) {
113 Inkscape::XML::Node *repr = SP_FLOWTEXT(text)->getAsText();
114 Inkscape::XML::Node *parent = SP_OBJECT_REPR(text)->parent();
115 parent->appendChild(repr);
117 SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
118 sp_item_write_transform(new_item, repr, text->transform);
119 SP_OBJECT(new_item)->updateRepr();
121 Inkscape::GC::release(repr);
122 text->deleteObject(); // delete the orignal flowtext
124 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
126 selection->clear();
128 text = new_item; // point to the new text
129 }
131 Inkscape::Text::Layout const *layout = te_get_layout(text);
132 Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
134 // remove transform from text, but recursively scale text's fontsize by the expansion
135 SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
136 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
138 // make a list of text children
139 GSList *text_reprs = NULL;
140 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
141 text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
142 }
144 // create textPath and put it into the text
145 Inkscape::XML::Node *textpath = sp_repr_new("svg:textPath");
146 // reference the shape
147 textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
148 if (text_alignment == Inkscape::Text::Layout::RIGHT)
149 textpath->setAttribute("startOffset", "100%");
150 else if (text_alignment == Inkscape::Text::Layout::CENTER)
151 textpath->setAttribute("startOffset", "50%");
152 SP_OBJECT_REPR(text)->addChild(textpath, NULL);
154 for ( GSList *i = text_reprs ; i ; i = i->next ) {
155 // make a copy of each text child
156 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate();
157 // We cannot have multiline in textpath, so remove line attrs from tspans
158 if (!strcmp(copy->name(), "svg:tspan")) {
159 copy->setAttribute("sodipodi:role", NULL);
160 copy->setAttribute("x", NULL);
161 copy->setAttribute("y", NULL);
162 }
163 // remove the old repr from under text
164 SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
165 // put its copy into under textPath
166 textpath->addChild(copy, NULL); // fixme: copy id
167 }
169 // x/y are useless with textpath, and confuse Batik 1.5
170 SP_OBJECT_REPR(text)->setAttribute("x", NULL);
171 SP_OBJECT_REPR(text)->setAttribute("y", NULL);
173 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
174 _("Put text on path"));
175 g_slist_free(text_reprs);
176 }
178 void
179 text_remove_from_path()
180 {
181 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
183 Inkscape::Selection *selection = sp_desktop_selection(desktop);
185 if (selection->isEmpty()) {
186 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
187 return;
188 }
190 bool did = false;
192 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
193 items != NULL;
194 items = items->next) {
196 if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
197 continue;
198 }
200 SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
202 did = true;
204 sp_textpath_to_text(tp);
205 }
207 if (!did) {
208 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
209 } else {
210 selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
211 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
212 _("Remove text from path"));
213 }
214 }
216 void
217 text_remove_all_kerns_recursively(SPObject *o)
218 {
219 SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
220 SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
221 SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
223 for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
224 text_remove_all_kerns_recursively(i);
225 }
226 }
228 //FIXME: must work with text selection
229 void
230 text_remove_all_kerns()
231 {
232 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
234 Inkscape::Selection *selection = sp_desktop_selection(desktop);
236 if (selection->isEmpty()) {
237 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
238 return;
239 }
241 bool did = false;
243 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
244 items != NULL;
245 items = items->next) {
247 if (!SP_IS_TEXT(SP_OBJECT(items->data))) {
248 continue;
249 }
251 text_remove_all_kerns_recursively(SP_OBJECT(items->data));
252 SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
253 did = true;
254 }
256 if (!did) {
257 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
258 } else {
259 sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
260 _("Remove manual kerns"));
261 }
262 }
264 void
265 text_flow_into_shape()
266 {
267 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
268 if (!desktop)
269 return;
271 SPDocument *doc = sp_desktop_document (desktop);
273 Inkscape::Selection *selection = sp_desktop_selection(desktop);
275 SPItem *text = text_or_flowtext_in_selection(selection);
276 SPItem *shape = shape_in_selection(selection);
278 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
279 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."));
280 return;
281 }
283 if (SP_IS_TEXT(text)) {
284 // remove transform from text, but recursively scale text's fontsize by the expansion
285 SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
286 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
287 }
289 Inkscape::XML::Node *root_repr = sp_repr_new("svg:flowRoot");
290 root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
291 root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
292 SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
293 SPObject *root_object = doc->getObjectByRepr(root_repr);
294 g_return_if_fail(SP_IS_FLOWTEXT(root_object));
296 Inkscape::XML::Node *region_repr = sp_repr_new("svg:flowRegion");
297 root_repr->appendChild(region_repr);
298 SPObject *object = doc->getObjectByRepr(region_repr);
299 g_return_if_fail(SP_IS_FLOWREGION(object));
301 /* Add clones */
302 for (GSList *items = (GSList *) selection->itemList();
303 items != NULL;
304 items = items->next) {
305 SPItem *item = SP_ITEM(items->data);
306 if (SP_IS_SHAPE(item)){
307 Inkscape::XML::Node *clone = sp_repr_new("svg:use");
308 clone->setAttribute("x", "0");
309 clone->setAttribute("y", "0");
310 clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
312 // add the new clone to the region
313 region_repr->appendChild(clone);
314 }
315 }
317 if (SP_IS_TEXT(text)) { // flow from text, as string
318 Inkscape::XML::Node *para_repr = sp_repr_new("svg:flowPara");
319 root_repr->appendChild(para_repr);
320 object = doc->getObjectByRepr(para_repr);
321 g_return_if_fail(SP_IS_FLOWPARA(object));
323 Inkscape::Text::Layout const *layout = te_get_layout(text);
324 Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
326 Inkscape::XML::Node *text_repr = sp_repr_new_text(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
327 para_repr->appendChild(text_repr);
329 Inkscape::GC::release(para_repr);
330 Inkscape::GC::release(text_repr);
332 } else { // reflow an already flowed text, preserving paras
333 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
334 if (SP_IS_FLOWPARA(o)) {
335 Inkscape::XML::Node *para_repr = SP_OBJECT_REPR(o)->duplicate();
336 root_repr->appendChild(para_repr);
337 object = doc->getObjectByRepr(para_repr);
338 g_return_if_fail(SP_IS_FLOWPARA(object));
339 Inkscape::GC::release(para_repr);
340 }
341 }
342 }
344 SP_OBJECT(text)->deleteObject (true);
346 sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
347 _("Flow text into shape"));
349 sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
351 Inkscape::GC::release(root_repr);
352 Inkscape::GC::release(region_repr);
353 }
355 void
356 text_unflow ()
357 {
358 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
359 if (!desktop)
360 return;
362 SPDocument *doc = sp_desktop_document (desktop);
364 Inkscape::Selection *selection = sp_desktop_selection(desktop);
367 if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
368 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
369 return;
370 }
372 GSList *new_objs = NULL;
373 GSList *old_objs = NULL;
375 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
376 items != NULL;
377 items = items->next) {
379 if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
380 continue;
381 }
383 SPItem *flowtext = SP_ITEM(items->data);
385 /* Create <text> */
386 Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
387 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
389 /* Set style */
390 rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
392 NRRect bbox;
393 sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
394 NR::Point xy(bbox.x0, bbox.y0);
395 if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
396 sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
397 sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
398 }
400 /* Create <tspan> */
401 Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
402 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
403 rtext->addChild(rtspan, NULL);
405 gchar *text_string = sp_te_get_string_multiline(flowtext);
406 Inkscape::XML::Node *text_repr = sp_repr_new_text(text_string); // FIXME: transfer all formatting!!!
407 free(text_string);
408 rtspan->appendChild(text_repr);
410 SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
411 SPObject *text_object = doc->getObjectByRepr(rtext);
413 new_objs = g_slist_prepend (new_objs, text_object);
414 old_objs = g_slist_prepend (old_objs, flowtext);
416 Inkscape::GC::release(rtext);
417 Inkscape::GC::release(rtspan);
418 Inkscape::GC::release(text_repr);
419 }
421 selection->clear();
422 selection->setList(new_objs);
423 for (GSList *i = old_objs; i; i = i->next) {
424 SP_OBJECT(i->data)->deleteObject (true);
425 }
427 g_slist_free (old_objs);
428 g_slist_free (new_objs);
430 sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
431 _("Unflow flowed text"));
432 }
434 void
435 flowtext_to_text()
436 {
437 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
439 Inkscape::Selection *selection = sp_desktop_selection(desktop);
441 if (selection->isEmpty()) {
442 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE,
443 _("Select <b>flowed text(s)</b> to convert."));
444 return;
445 }
447 bool did = false;
449 GSList *reprs = NULL;
450 GSList *items = g_slist_copy((GSList *) selection->itemList());
451 for (; items != NULL; items = items->next) {
453 SPItem *item = (SPItem *) items->data;
455 if (!SP_IS_FLOWTEXT(item))
456 continue;
458 did = true;
460 Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
461 Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
462 parent->appendChild(repr);
464 SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
465 sp_item_write_transform(new_item, repr, item->transform);
466 SP_OBJECT(new_item)->updateRepr();
468 Inkscape::GC::release(repr);
469 item->deleteObject();
471 reprs = g_slist_prepend(reprs, repr);
472 }
474 g_slist_free(items);
476 if (did) {
477 sp_document_done(sp_desktop_document(desktop),
478 SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
479 _("Convert flowed text to text"));
480 selection->setReprList(reprs);
481 } else {
482 sp_desktop_message_stack(desktop)->
483 flash(Inkscape::ERROR_MESSAGE,
484 _("<b>No flowed text(s)</b> to convert in the selection."));
485 }
487 g_slist_free(reprs);
488 }
491 /*
492 Local Variables:
493 mode:c++
494 c-file-style:"stroustrup"
495 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
496 indent-tabs-mode:nil
497 fill-column:99
498 End:
499 */
500 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :