Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / text-chemistry.cpp
1 /*
2  * Text commands
3  *
4  * Authors:
5  *   bulia byak
6  *   Jon A. Cruz <jon@joncruz.org>
7  *   Abhishek Sharma
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
18 #include <cstring>
19 #include <string>
20 #include <glibmm/i18n.h>
22 #include "libnr/nr-matrix-fns.h"
23 #include "xml/repr.h"
24 #include "sp-rect.h"
25 #include "sp-textpath.h"
26 #include "inkscape.h"
27 #include "desktop.h"
28 #include "document.h"
29 #include "message-stack.h"
30 #include "selection.h"
31 #include "style.h"
32 #include "desktop-handles.h"
33 #include "text-editing.h"
34 #include "text-chemistry.h"
35 #include "sp-flowtext.h"
36 #include "sp-flowregion.h"
37 #include "sp-flowdiv.h"
38 #include "sp-tspan.h"
40 using Inkscape::DocumentUndo;
42 SPItem *
43 text_in_selection(Inkscape::Selection *selection)
44 {
45     for (GSList *items = (GSList *) selection->itemList();
46          items != NULL;
47          items = items->next) {
48         if (SP_IS_TEXT(items->data))
49             return ((SPItem *) items->data);
50     }
51     return NULL;
52 }
54 SPItem *
55 flowtext_in_selection(Inkscape::Selection *selection)
56 {
57     for (GSList *items = (GSList *) selection->itemList();
58          items != NULL;
59          items = items->next) {
60         if (SP_IS_FLOWTEXT(items->data))
61             return ((SPItem *) items->data);
62     }
63     return NULL;
64 }
66 SPItem *
67 text_or_flowtext_in_selection(Inkscape::Selection *selection)
68 {
69     for (GSList *items = (GSList *) selection->itemList();
70          items != NULL;
71          items = items->next) {
72         if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
73             return ((SPItem *) items->data);
74     }
75     return NULL;
76 }
78 SPItem *
79 shape_in_selection(Inkscape::Selection *selection)
80 {
81     for (GSList *items = (GSList *) selection->itemList();
82          items != NULL;
83          items = items->next) {
84         if (SP_IS_SHAPE(items->data))
85             return ((SPItem *) items->data);
86     }
87     return NULL;
88 }
90 void
91 text_put_on_path()
92 {
93     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
94     if (!desktop)
95         return;
97     Inkscape::Selection *selection = sp_desktop_selection(desktop);
99     SPItem *text = text_or_flowtext_in_selection(selection);
100     SPItem *shape = shape_in_selection(selection);
102     Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
104     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
105         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
106         return;
107     }
109     if (SP_IS_TEXT_TEXTPATH(text)) {
110         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."));
111         return;
112     }
114     if (SP_IS_RECT(shape)) {
115         // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
116         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
117         return;
118     }
120     // if a flowed text is selected, convert it to a regular text object
121     if (SP_IS_FLOWTEXT(text)) {
123         if (!SP_FLOWTEXT(text)->layout.outputExists()) {
124             sp_desktop_message_stack(desktop)->
125                 flash(Inkscape::WARNING_MESSAGE, 
126                       _("The flowed text(s) must be <b>visible</b> in order to be put on a path."));
127         }
129         Inkscape::XML::Node *repr = SP_FLOWTEXT(text)->getAsText();
131         if (!repr) return;
133         Inkscape::XML::Node *parent = SP_OBJECT_REPR(text)->parent();
134         parent->appendChild(repr);
136         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
137         new_item->doWriteTransform(repr, text->transform);
138         SP_OBJECT(new_item)->updateRepr();
140         Inkscape::GC::release(repr);
141         text->deleteObject(); // delete the orignal flowtext
143         sp_desktop_document(desktop)->ensureUpToDate();
145         selection->clear();
147         text = new_item; // point to the new text
148     }
150     Inkscape::Text::Layout const *layout = te_get_layout(text);
151     Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
153     // remove transform from text, but recursively scale text's fontsize by the expansion
154     SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
155     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
157     // make a list of text children
158     GSList *text_reprs = NULL;
159     for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
160         text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
161     }
163     // create textPath and put it into the text
164     Inkscape::XML::Node *textpath = xml_doc->createElement("svg:textPath");
165     // reference the shape
166     textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
167     if (text_alignment == Inkscape::Text::Layout::RIGHT)
168         textpath->setAttribute("startOffset", "100%");
169     else if (text_alignment == Inkscape::Text::Layout::CENTER)
170         textpath->setAttribute("startOffset", "50%");
171     SP_OBJECT_REPR(text)->addChild(textpath, NULL);
173     for ( GSList *i = text_reprs ; i ; i = i->next ) {
174         // Make a copy of each text child
175         Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate(xml_doc);
176         // We cannot have multiline in textpath, so remove line attrs from tspans
177         if (!strcmp(copy->name(), "svg:tspan")) {
178             copy->setAttribute("sodipodi:role", NULL);
179             copy->setAttribute("x", NULL);
180             copy->setAttribute("y", NULL);
181         }
182         // remove the old repr from under text
183         SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
184         // put its copy into under textPath
185         textpath->addChild(copy, NULL); // fixme: copy id
186     }
188     // x/y are useless with textpath, and confuse Batik 1.5
189     SP_OBJECT_REPR(text)->setAttribute("x", NULL);
190     SP_OBJECT_REPR(text)->setAttribute("y", NULL);
192     DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
193                        _("Put text on path"));
194     g_slist_free(text_reprs);
197 void
198 text_remove_from_path()
200     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
202     Inkscape::Selection *selection = sp_desktop_selection(desktop);
204     if (selection->isEmpty()) {
205         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
206         return;
207     }
209     bool did = false;
211     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
212          items != NULL;
213          items = items->next) {
215         if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
216             continue;
217         }
219         SPObject *tp = SP_OBJECT(items->data)->firstChild();
221         did = true;
223         sp_textpath_to_text(tp);
224     }
226     if (!did) {
227         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
228     } else {
229         DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
230                            _("Remove text from path"));
231         selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
232     }
235 void
236 text_remove_all_kerns_recursively(SPObject *o)
238     SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
239     SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
240     SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
242     // if x contains a list, leave only the first value
243     gchar *x = (gchar *) SP_OBJECT_REPR(o)->attribute("x");
244     if (x) {
245         gchar **xa_space = g_strsplit(x, " ", 0);
246         gchar **xa_comma = g_strsplit(x, ",", 0);
247         if (xa_space && *xa_space && *(xa_space + 1)) {
248             SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_space));
249         } else if (xa_comma && *xa_comma && *(xa_comma + 1)) {
250             SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_comma));
251         }
252         g_strfreev(xa_space);
253         g_strfreev(xa_comma);
254     }
256     for (SPObject *i = o->firstChild(); i != NULL; i = i->getNext()) {
257         text_remove_all_kerns_recursively(i);
258     }
261 //FIXME: must work with text selection
262 void
263 text_remove_all_kerns()
265     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
267     Inkscape::Selection *selection = sp_desktop_selection(desktop);
269     if (selection->isEmpty()) {
270         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
271         return;
272     }
274     bool did = false;
276     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
277          items != NULL;
278          items = items->next) {
279         SPObject *obj = SP_OBJECT(items->data);
281         if (!SP_IS_TEXT(obj) && !SP_IS_TSPAN(obj) && !SP_IS_FLOWTEXT(obj)) {
282             continue;
283         }
285         text_remove_all_kerns_recursively(obj);
286         obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
287         did = true;
288     }
290     if (!did) {
291         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
292     } else {
293         DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
294                            _("Remove manual kerns"));
295     }
298 void
299 text_flow_into_shape()
301     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
302     if (!desktop)
303         return;
305     SPDocument *doc = sp_desktop_document (desktop);
306     Inkscape::XML::Document *xml_doc = doc->getReprDoc();
308     Inkscape::Selection *selection = sp_desktop_selection(desktop);
310     SPItem *text = text_or_flowtext_in_selection(selection);
311     SPItem *shape = shape_in_selection(selection);
313     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
314         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."));
315         return;
316     }
318     if (SP_IS_TEXT(text)) {
319       // remove transform from text, but recursively scale text's fontsize by the expansion
320       SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
321       SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
322     }
324     Inkscape::XML::Node *root_repr = xml_doc->createElement("svg:flowRoot");
325     root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
326     root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
327     SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
328     SPObject *root_object = doc->getObjectByRepr(root_repr);
329     g_return_if_fail(SP_IS_FLOWTEXT(root_object));
331     Inkscape::XML::Node *region_repr = xml_doc->createElement("svg:flowRegion");
332     root_repr->appendChild(region_repr);
333     SPObject *object = doc->getObjectByRepr(region_repr);
334     g_return_if_fail(SP_IS_FLOWREGION(object));
336     /* Add clones */
337     for (GSList *items = (GSList *) selection->itemList();
338          items != NULL;
339          items = items->next) {
340         SPItem *item = SP_ITEM(items->data);
341         if (SP_IS_SHAPE(item)){
342             Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
343             clone->setAttribute("x", "0");
344             clone->setAttribute("y", "0");
345             clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
347             // add the new clone to the region
348             region_repr->appendChild(clone);
349         }
350     }
352     if (SP_IS_TEXT(text)) { // flow from text, as string
353         Inkscape::XML::Node *para_repr = xml_doc->createElement("svg:flowPara");
354         root_repr->appendChild(para_repr);
355         object = doc->getObjectByRepr(para_repr);
356         g_return_if_fail(SP_IS_FLOWPARA(object));
358         Inkscape::Text::Layout const *layout = te_get_layout(text);
359         Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
361         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
362         para_repr->appendChild(text_repr);
364         Inkscape::GC::release(para_repr);
365         Inkscape::GC::release(text_repr);
367     } else { // reflow an already flowed text, preserving paras
368         for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
369             if (SP_IS_FLOWPARA(o)) {
370                 Inkscape::XML::Node *para_repr = SP_OBJECT_REPR(o)->duplicate(xml_doc);
371                 root_repr->appendChild(para_repr);
372                 object = doc->getObjectByRepr(para_repr);
373                 g_return_if_fail(SP_IS_FLOWPARA(object));
374                 Inkscape::GC::release(para_repr);
375             }
376         }
377     }
379     SP_OBJECT(text)->deleteObject (true);
381     DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT,
382                        _("Flow text into shape"));
384     sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
386     Inkscape::GC::release(root_repr);
387     Inkscape::GC::release(region_repr);
390 void
391 text_unflow ()
393     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
394     if (!desktop)
395         return;
397     SPDocument *doc = sp_desktop_document (desktop);
398     Inkscape::XML::Document *xml_doc = doc->getReprDoc();
400     Inkscape::Selection *selection = sp_desktop_selection(desktop);
403     if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
404         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
405         return;
406     }
408     GSList *new_objs = NULL;
409     GSList *old_objs = NULL;
411     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
412          items != NULL;
413          items = items->next) {
415         if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
416             continue;
417         }
419         SPItem *flowtext = SP_ITEM(items->data);
421         // we discard transform when unflowing, but we must preserve expansion which is visible as
422         // font size multiplier
423         double ex = (flowtext->transform).descrim();
425         if (sp_te_get_string_multiline(flowtext) == NULL) { // flowtext is empty
426             continue;
427         }
429         /* Create <text> */
430         Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
431         rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
433         /* Set style */
434         rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
436         NRRect bbox;
437         SP_ITEM(flowtext)->invoke_bbox( &bbox, SP_ITEM(flowtext)->i2doc_affine(), TRUE);
438         Geom::Point xy(bbox.x0, bbox.y0);
439         if (xy[Geom::X] != 1e18 && xy[Geom::Y] != 1e18) {
440             sp_repr_set_svg_double(rtext, "x", xy[Geom::X]);
441             sp_repr_set_svg_double(rtext, "y", xy[Geom::Y]);
442         }
444         /* Create <tspan> */
445         Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
446         rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
447         rtext->addChild(rtspan, NULL);
449         gchar *text_string = sp_te_get_string_multiline(flowtext);
450         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_string); // FIXME: transfer all formatting!!!
451         free(text_string);
452         rtspan->appendChild(text_repr);
454         SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
455         SPObject *text_object = doc->getObjectByRepr(rtext);
457         // restore the font size multiplier from the flowtext's transform
458         SP_TEXT(text_object)->_adjustFontsizeRecursive(SP_ITEM(text_object), ex);
460         new_objs = g_slist_prepend (new_objs, text_object);
461         old_objs = g_slist_prepend (old_objs, flowtext);
463         Inkscape::GC::release(rtext);
464         Inkscape::GC::release(rtspan);
465         Inkscape::GC::release(text_repr);
466     }
468     selection->clear();
469     selection->setList(new_objs);
470     for (GSList *i = old_objs; i; i = i->next) {
471         SP_OBJECT(i->data)->deleteObject (true);
472     }
474     g_slist_free (old_objs);
475     g_slist_free (new_objs);
477     DocumentUndo::done(doc, SP_VERB_CONTEXT_TEXT, 
478                        _("Unflow flowed text"));
481 void
482 flowtext_to_text()
484     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
486     Inkscape::Selection *selection = sp_desktop_selection(desktop);
488     if (selection->isEmpty()) {
489         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, 
490                                                  _("Select <b>flowed text(s)</b> to convert."));
491         return;
492     }
494     bool did = false;
496     GSList *reprs = NULL;
497     GSList *items = g_slist_copy((GSList *) selection->itemList());
498     for (; items != NULL; items = items->next) {
499         
500         SPItem *item = (SPItem *) items->data;
502         if (!SP_IS_FLOWTEXT(item))
503             continue;
505         if (!SP_FLOWTEXT(item)->layout.outputExists()) {
506             sp_desktop_message_stack(desktop)->
507                 flash(Inkscape::WARNING_MESSAGE, 
508                       _("The flowed text(s) must be <b>visible</b> in order to be converted."));
509             return;
510         }
512         Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
514         if (!repr) break;
516         did = true;
518         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
519         parent->addChild(repr, SP_OBJECT_REPR(item));
521         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
522         new_item->doWriteTransform(repr, item->transform);
523         SP_OBJECT(new_item)->updateRepr();
524     
525         Inkscape::GC::release(repr);
526         item->deleteObject();
528         reprs = g_slist_prepend(reprs, repr);
529     }
531     g_slist_free(items);
533     if (did) {
534         DocumentUndo::done(sp_desktop_document(desktop), 
535                            SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
536                            _("Convert flowed text to text"));
537         selection->setReprList(reprs);        
538     } else {
539         sp_desktop_message_stack(desktop)->
540             flash(Inkscape::ERROR_MESSAGE,
541                   _("<b>No flowed text(s)</b> to convert in the selection."));
542     }
544     g_slist_free(reprs);
548 /*
549   Local Variables:
550   mode:c++
551   c-file-style:"stroustrup"
552   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
553   indent-tabs-mode:nil
554   fill-column:99
555   End:
556 */
557 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :