Code

start switching sp_repr_new* over to XML::Document::create*, and rename create method...
[inkscape.git] / src / text-chemistry.cpp
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);
191 void
192 text_remove_from_path()
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     }
229 void
230 text_remove_all_kerns_recursively(SPObject *o)
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     }
241 //FIXME: must work with text selection
242 void
243 text_remove_all_kerns()
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     }
277 void
278 text_flow_into_shape()
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);
369 void
370 text_unflow ()
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         /* Create <text> */
401         Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
402         rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
404         /* Set style */
405         rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
407         NRRect bbox;
408         sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
409         NR::Point xy(bbox.x0, bbox.y0);
410         if (xy[NR::X] != 1e18 && xy[NR::Y] != 1e18) {
411             sp_repr_set_svg_double(rtext, "x", xy[NR::X]);
412             sp_repr_set_svg_double(rtext, "y", xy[NR::Y]);
413         }
415         /* Create <tspan> */
416         Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
417         rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
418         rtext->addChild(rtspan, NULL);
420         gchar *text_string = sp_te_get_string_multiline(flowtext);
421         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_string); // FIXME: transfer all formatting!!!
422         free(text_string);
423         rtspan->appendChild(text_repr);
425         SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
426         SPObject *text_object = doc->getObjectByRepr(rtext);
428         new_objs = g_slist_prepend (new_objs, text_object);
429         old_objs = g_slist_prepend (old_objs, flowtext);
431         Inkscape::GC::release(rtext);
432         Inkscape::GC::release(rtspan);
433         Inkscape::GC::release(text_repr);
434     }
436     selection->clear();
437     selection->setList(new_objs);
438     for (GSList *i = old_objs; i; i = i->next) {
439         SP_OBJECT(i->data)->deleteObject (true);
440     }
442     g_slist_free (old_objs);
443     g_slist_free (new_objs);
445     sp_document_done(doc, SP_VERB_CONTEXT_TEXT, 
446                      _("Unflow flowed text"));
449 void
450 flowtext_to_text()
452     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
454     Inkscape::Selection *selection = sp_desktop_selection(desktop);
456     if (selection->isEmpty()) {
457         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, 
458                                                  _("Select <b>flowed text(s)</b> to convert."));
459         return;
460     }
462     bool did = false;
464     GSList *reprs = NULL;
465     GSList *items = g_slist_copy((GSList *) selection->itemList());
466     for (; items != NULL; items = items->next) {
467         
468         SPItem *item = (SPItem *) items->data;
470         if (!SP_IS_FLOWTEXT(item))
471             continue;
473         if (!SP_FLOWTEXT(item)->layout.outputExists()) {
474             sp_desktop_message_stack(desktop)->
475                 flash(Inkscape::WARNING_MESSAGE, 
476                       _("The flowed text(s) must be <b>visible</b> in order to be converted."));
477             return;
478         }
480         Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
482         if (!repr) break;
484         did = true;
486         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
487         parent->appendChild(repr);
489         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
490         sp_item_write_transform(new_item, repr, item->transform);
491         SP_OBJECT(new_item)->updateRepr();
492     
493         Inkscape::GC::release(repr);
494         item->deleteObject();
496         reprs = g_slist_prepend(reprs, repr);
497     }
499     g_slist_free(items);
501     if (did) {
502         sp_document_done(sp_desktop_document(desktop), 
503                          SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
504                          _("Convert flowed text to text"));
505         selection->setReprList(reprs);        
506     } else {
507         sp_desktop_message_stack(desktop)->
508             flash(Inkscape::ERROR_MESSAGE,
509                   _("<b>No flowed text(s)</b> to convert in the selection."));
510     }
512     g_slist_free(reprs);
516 /*
517   Local Variables:
518   mode:c++
519   c-file-style:"stroustrup"
520   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
521   indent-tabs-mode:nil
522   fill-column:99
523   End:
524 */
525 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :