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 "desktop-handles.h"
27 #include "text-editing.h"
28 #include "sp-flowtext.h"
29 #include "sp-flowregion.h"
30 #include "sp-flowdiv.h"
33 SPItem *
34 text_in_selection(Inkscape::Selection *selection)
35 {
36 for (GSList *items = (GSList *) selection->itemList();
37 items != NULL;
38 items = items->next) {
39 if (SP_IS_TEXT(items->data))
40 return ((SPItem *) items->data);
41 }
42 return NULL;
43 }
45 SPItem *
46 flowtext_in_selection(Inkscape::Selection *selection)
47 {
48 for (GSList *items = (GSList *) selection->itemList();
49 items != NULL;
50 items = items->next) {
51 if (SP_IS_FLOWTEXT(items->data))
52 return ((SPItem *) items->data);
53 }
54 return NULL;
55 }
57 SPItem *
58 shape_in_selection(Inkscape::Selection *selection)
59 {
60 for (GSList *items = (GSList *) selection->itemList();
61 items != NULL;
62 items = items->next) {
63 if (SP_IS_SHAPE(items->data))
64 return ((SPItem *) items->data);
65 }
66 return NULL;
67 }
69 void
70 text_put_on_path()
71 {
72 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
73 if (!desktop)
74 return;
76 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
78 SPItem *text = text_in_selection(selection);
79 SPItem *shape = shape_in_selection(selection);
81 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
82 SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
83 return;
84 }
86 if (SP_IS_TEXT_TEXTPATH(text)) {
87 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("This text object is <b>already put to a path</b>. Remove it from the path first. Use <b>Shift+D</b> to look up its path."));
88 return;
89 }
91 if (SP_IS_RECT(shape)) {
92 // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
93 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
94 return;
95 }
97 Inkscape::Text::Layout const *layout = te_get_layout(text);
98 Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
100 // remove transform from text, but recursively scale text's fontsize by the expansion
101 SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
102 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
104 // make a list of text children
105 GSList *text_reprs = NULL;
106 for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
107 text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
108 }
110 // create textPath and put it into the text
111 Inkscape::XML::Node *textpath = sp_repr_new("svg:textPath");
112 // reference the shape
113 textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
114 if (text_alignment == Inkscape::Text::Layout::RIGHT)
115 textpath->setAttribute("startOffset", "100%");
116 else if (text_alignment == Inkscape::Text::Layout::CENTER)
117 textpath->setAttribute("startOffset", "50%");
118 SP_OBJECT_REPR(text)->addChild(textpath, NULL);
120 for ( GSList *i = text_reprs ; i ; i = i->next ) {
121 // make a copy of each text child
122 Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate();
123 // We cannot have multiline in textpath, so remove line attrs from tspans
124 if (!strcmp(copy->name(), "svg:tspan")) {
125 copy->setAttribute("sodipodi:role", NULL);
126 copy->setAttribute("x", NULL);
127 copy->setAttribute("y", NULL);
128 }
129 // remove the old repr from under text
130 SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
131 // put its copy into under textPath
132 textpath->addChild(copy, NULL); // fixme: copy id
133 }
135 // x/y are useless with textpath, and confuse Batik 1.5
136 SP_OBJECT_REPR(text)->setAttribute("x", NULL);
137 SP_OBJECT_REPR(text)->setAttribute("y", NULL);
139 sp_document_done(SP_DT_DOCUMENT(desktop));
140 g_slist_free(text_reprs);
141 }
143 void
144 text_remove_from_path()
145 {
146 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
148 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
150 if (selection->isEmpty()) {
151 SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
152 return;
153 }
155 bool did = false;
157 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
158 items != NULL;
159 items = items->next) {
161 if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
162 continue;
163 }
165 SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
167 did = true;
169 sp_textpath_to_text(tp);
170 }
172 if (!did) {
173 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
174 } else {
175 selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
176 sp_document_done(SP_DT_DOCUMENT(desktop));
177 }
178 }
180 void
181 text_remove_all_kerns_recursively(SPObject *o)
182 {
183 SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
184 SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
185 SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
187 for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
188 text_remove_all_kerns_recursively(i);
189 }
190 }
192 //FIXME: must work with text selection
193 void
194 text_remove_all_kerns()
195 {
196 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
198 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
200 if (selection->isEmpty()) {
201 SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
202 return;
203 }
205 bool did = false;
207 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
208 items != NULL;
209 items = items->next) {
211 if (!SP_IS_TEXT(SP_OBJECT(items->data))) {
212 continue;
213 }
215 text_remove_all_kerns_recursively(SP_OBJECT(items->data));
216 SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
217 did = true;
218 }
220 if (!did) {
221 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
222 } else {
223 sp_document_done(SP_DT_DOCUMENT(desktop));
224 }
225 }
227 void
228 text_flow_into_shape()
229 {
230 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
231 if (!desktop)
232 return;
234 SPDocument *doc = SP_DT_DOCUMENT (desktop);
236 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
238 SPItem *text = text_in_selection(selection);
239 SPItem *shape = shape_in_selection(selection);
241 if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
242 SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text</b> and one or more <b>paths or shapes</b> to flow text into frame."));
243 return;
244 }
246 // remove transform from text, but recursively scale text's fontsize by the expansion
247 SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
248 SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
250 Inkscape::XML::Node *root_repr = sp_repr_new("svg:flowRoot");
251 root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
252 root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
253 SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
254 SPObject *root_object = doc->getObjectByRepr(root_repr);
255 g_return_if_fail(SP_IS_FLOWTEXT(root_object));
257 Inkscape::XML::Node *region_repr = sp_repr_new("svg:flowRegion");
258 root_repr->appendChild(region_repr);
259 SPObject *object = doc->getObjectByRepr(region_repr);
260 g_return_if_fail(SP_IS_FLOWREGION(object));
262 /* Add clones */
263 for (GSList *items = (GSList *) selection->itemList();
264 items != NULL;
265 items = items->next) {
266 SPItem *item = SP_ITEM(items->data);
267 if (SP_IS_SHAPE(item)){
268 Inkscape::XML::Node *clone = sp_repr_new("svg:use");
269 clone->setAttribute("x", "0");
270 clone->setAttribute("y", "0");
271 clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
273 // add the new clone to the region
274 region_repr->appendChild(clone);
275 }
276 }
278 Inkscape::XML::Node *para_repr = sp_repr_new("svg:flowPara");
279 root_repr->appendChild(para_repr);
280 object = doc->getObjectByRepr(para_repr);
281 g_return_if_fail(SP_IS_FLOWPARA(object));
283 Inkscape::Text::Layout const *layout = te_get_layout(text);
284 Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
286 Inkscape::XML::Node *text_repr = sp_repr_new_text(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
287 para_repr->appendChild(text_repr);
289 SP_OBJECT(text)->deleteObject (true);
291 sp_document_done(doc);
293 SP_DT_SELECTION(desktop)->set(SP_ITEM(root_object));
295 Inkscape::GC::release(root_repr);
296 Inkscape::GC::release(region_repr);
297 Inkscape::GC::release(para_repr);
298 Inkscape::GC::release(text_repr);
299 }
301 void
302 text_unflow ()
303 {
304 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
305 if (!desktop)
306 return;
308 SPDocument *doc = SP_DT_DOCUMENT (desktop);
310 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
313 if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
314 SP_DT_MSGSTACK(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
315 return;
316 }
318 GSList *new_objs = NULL;
319 GSList *old_objs = NULL;
321 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
322 items != NULL;
323 items = items->next) {
325 if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
326 continue;
327 }
329 SPItem *flowtext = SP_ITEM(items->data);
331 /* Create <text> */
332 Inkscape::XML::Node *rtext = sp_repr_new("svg:text");
333 rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
335 /* Set style */
336 rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
338 NRRect bbox;
339 sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
340 NR::Point xy(bbox.x0, bbox.y0);
341 if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
342 sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
343 sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
344 }
346 /* Create <tspan> */
347 Inkscape::XML::Node *rtspan = sp_repr_new("svg:tspan");
348 rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
349 rtext->addChild(rtspan, NULL);
351 gchar *text_string = sp_te_get_string_multiline(flowtext);
352 Inkscape::XML::Node *text_repr = sp_repr_new_text(text_string); // FIXME: transfer all formatting!!!
353 free(text_string);
354 rtspan->appendChild(text_repr);
356 SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
357 SPObject *text_object = doc->getObjectByRepr(rtext);
359 new_objs = g_slist_prepend (new_objs, text_object);
360 old_objs = g_slist_prepend (old_objs, flowtext);
362 Inkscape::GC::release(rtext);
363 Inkscape::GC::release(rtspan);
364 Inkscape::GC::release(text_repr);
365 }
367 selection->clear();
368 selection->setList(new_objs);
369 for (GSList *i = old_objs; i; i = i->next) {
370 SP_OBJECT(i->data)->deleteObject (true);
371 }
373 g_slist_free (old_objs);
374 g_slist_free (new_objs);
376 sp_document_done(doc);
377 }
381 /*
382 Local Variables:
383 mode:c++
384 c-file-style:"stroustrup"
385 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
386 indent-tabs-mode:nil
387 fill-column:99
388 End:
389 */
390 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :