diff options
| author | Tavmjong Bah <tavmjong@free.fr> | 2018-10-13 07:37:26 +0000 |
|---|---|---|
| committer | Tavmjong Bah <tavmjong@free.fr> | 2018-10-13 07:37:26 +0000 |
| commit | faf43f2ffa88561acb95909861bb28fcdfaeb858 (patch) | |
| tree | 77f397b880ec970981a754c9549b24842161855b /src | |
| parent | CI/AppVeyor: increase clone depth (diff) | |
| download | inkscape-faf43f2ffa88561acb95909861bb28fcdfaeb858.tar.gz inkscape-faf43f2ffa88561acb95909861bb28fcdfaeb858.zip | |
Move some files to 'io' directory.
Diffstat (limited to 'src')
24 files changed, 1593 insertions, 42 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 26551e99e..2912d40e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,7 +19,6 @@ set(inkscape_SRC desktop-style.cpp desktop.cpp device-manager.cpp - dir-util.cpp document-subset.cpp document-undo.cpp document.cpp @@ -67,7 +66,6 @@ set(inkscape_SRC pure-transform.cpp rdf.cpp removeoverlap.cpp - resource-manager.cpp rubberband.cpp satisfied-guide-cns.cpp selcue.cpp @@ -124,7 +122,6 @@ set(inkscape_SRC desktop-style.h desktop.h device-manager.h - dir-util.h document-private.h document-subset.h document-undo.h @@ -183,7 +180,6 @@ set(inkscape_SRC rdf.h remove-last.h removeoverlap.h - resource-manager.h rubberband.h satisfied-guide-cns.h selcue.h diff --git a/src/desktop.cpp b/src/desktop.cpp index 79fec2927..46589ca04 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -38,7 +38,6 @@ #include "layer-manager.h" #include "message-context.h" #include "message-stack.h" -#include "resource-manager.h" #include "display/canvas-arena.h" #include "display/canvas-debug.h" @@ -55,6 +54,8 @@ #include "helper/action-context.h" #include "helper/action.h" //sp_action_perform +#include "io/resource-manager.h" + #include "object/sp-namedview.h" #include "object/sp-root.h" diff --git a/src/document.cpp b/src/document.cpp index 54b7071c0..5b8fe305c 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -42,7 +42,7 @@ #include <2geom/transforms.h> #include "desktop.h" -#include "dir-util.h" +#include "io/dir-util.h" #include "document-private.h" #include "document-undo.h" #include "file.h" diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp index 9e0dd31c8..fe3b14093 100644 --- a/src/extension/implementation/script.cpp +++ b/src/extension/implementation/script.cpp @@ -54,7 +54,7 @@ #ifdef _WIN32 #include <windows.h> #include <sys/stat.h> -#include "registrytool.h" +#include "io/registrytool.h" #endif /** This is the command buffer that gets allocated from the stack */ diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp index 02ca5af1a..41683d3b4 100644 --- a/src/extension/internal/gdkpixbuf-input.cpp +++ b/src/extension/internal/gdkpixbuf-input.cpp @@ -4,7 +4,7 @@ #include <glib/gprintf.h> #include <glibmm/i18n.h> -#include "dir-util.h" +#include "io/dir-util.h" #include "display/cairo-utils.h" #include "document-private.h" #include "document-undo.h" diff --git a/src/file-update.cpp b/src/file-update.cpp index 68337b806..de09047bd 100644 --- a/src/file-update.cpp +++ b/src/file-update.cpp @@ -14,7 +14,6 @@ #include <gtkmm.h> #include "desktop.h" -#include "dir-util.h" #include "document-undo.h" #include "document.h" #include "file.h" @@ -36,6 +35,7 @@ #include "extension/output.h" #include "extension/system.h" +#include "io/dir-util.h" #include "io/sys.h" #include "object/persp3d.h" diff --git a/src/file.cpp b/src/file.cpp index ac4d0ab62..5e93fb9ce 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -46,7 +46,6 @@ #include "path-prefix.h" #include "print.h" #include "rdf.h" -#include "resource-manager.h" #include "selection-chemistry.h" #include "verbs.h" @@ -58,6 +57,7 @@ #include "helper/png-write.h" #include "io/resource.h" +#include "io/resource-manager.h" #include "io/sys.h" #include "object/sp-defs.h" diff --git a/src/inkscape.cpp b/src/inkscape.cpp index 516c14f4e..6a6310b3d 100644 --- a/src/inkscape.cpp +++ b/src/inkscape.cpp @@ -25,8 +25,6 @@ #include <gtkmm/cssprovider.h> #include <gtkmm/icontheme.h> #include <gtkmm/messagedialog.h> -#include "debug/simple-event.h" -#include "debug/event-tracker.h" #include <glib/gstdio.h> #include <glibmm/i18n.h> @@ -34,22 +32,30 @@ #include <glibmm/convert.h> #include "desktop.h" - #include "device-manager.h" #include "document.h" +#include "inkscape.h" +#include "message-stack.h" +#include "path-prefix.h" + +#include "debug/simple-event.h" +#include "debug/event-tracker.h" + #include "extension/db.h" #include "extension/init.h" #include "extension/output.h" #include "extension/system.h" + #include "helper/action-context.h" -#include "inkscape.h" + #include "io/resource.h" +#include "io/resource-manager.h" #include "io/sys.h" + #include "libnrtype/FontFactory.h" -#include "message-stack.h" -#include "path-prefix.h" -#include "resource-manager.h" + #include "svg/svg-color.h" + #include "ui/dialog/debug.h" #include "ui/tools/tool-base.h" diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 380b983db..c816082b0 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -1,29 +1,41 @@ set(io_SRC - base64stream.cpp - bufferstream.cpp - gzipstream.cpp - inkscapestream.cpp - resource.cpp - stringstream.cpp - sys.cpp - http.cpp - uristream.cpp - xsltstream.cpp + base64stream.cpp + bufferstream.cpp + dir-util.cpp + gzipstream.cpp + inkscapestream.cpp + resource.cpp + resource-manager.cpp + stringstream.cpp + sys.cpp + http.cpp + uristream.cpp + xsltstream.cpp - # ------- - # Headers - base64stream.h - bufferstream.h - gzipstream.h - inkscapestream.h - resource.h - stringstream.h - sys.h - http.h - uristream.h - xsltstream.h + # ------- + # Headers + base64stream.h + bufferstream.h + dir-util.h + gzipstream.h + inkscapestream.h + resource.h + resource-manager.h + stringstream.h + sys.h + http.h + uristream.h + xsltstream.h ) +if(WIN32) + # Sources for the inkscape executable on Windows. + list(APPEND io_SRC + registrytool.h + registrytool.cpp + ) +endif() + # add_inkscape_lib(io_LIB "${io_SRC}") add_inkscape_source("${io_SRC}") diff --git a/src/io/command_line_export.h b/src/io/command_line_export.h new file mode 100644 index 000000000..dad0d573c --- /dev/null +++ b/src/io/command_line_export.h @@ -0,0 +1,59 @@ + +// Command line export... should be using normal export. + + +class InkCommandLineExport { + +public: + enum { + EXPORT_PLAIN_SVG, + EXPORT_INKSCAPE_SVG, + EXPORT_PNG, + EXPORT_PS, + EXPORT_EPS, + EXPORT_PDF, + EXPORT_LATEX, + EXPORT_EMF, + EXPORT_WMF, + EXPORT_XAML, + EXPORT_PRINT + } ExportType; + + InkCommandLineExport(ExportType export_type, + Glib::ustring file_name); + ~InkCommandLineExport() {}; + do_export(); + + double export_dpi; + bool export_area; + bool export_area_drawing; + bool export_area_page; + double export_margin; + bool export_snap; + int export_width; // In pixels + int export_hight; // In pixels + Glib::ustring export_id; + bool export_id_only; + bool export_id_hints; + Glib::ustring export_background; + double export_background_opacity; + int export_ps_level; + double export_pdf_level; + bool export_text_to_path; + bool export_ignore_filters; + +private: + ExportType export_type; + Glib::ustring file_name; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/io/command_line_output.h b/src/io/command_line_output.h new file mode 100644 index 000000000..31ce2cd47 --- /dev/null +++ b/src/io/command_line_output.h @@ -0,0 +1,58 @@ + +// Command line export... should be using normal export. + +class InkCommandLineExport { + +public: + enum { + EXPORT_PLAIN_SVG, + EXPORT_INKSCAPE_SVG, + EXPORT_PNG, + EXPORT_PS, + EXPORT_EPS, + EXPORT_PDF, + EXPORT_LATEX, + EXPORT_EMF, + EXPORT_WMF, + EXPORT_XAML, + EXPORT_PRINT + } ExportType; + + InkCommandLineExport(ExportType export_type, + Glib::ustring file_name); + ~InkCommandLineExport() {}; + do_export(); + + double export_dpi; + bool export_area; + bool export_area_drawing; + bool export_area_page; + double export_margin; + bool export_snap; + int export_width; // In pixels + int export_hight; // In pixels + Glib::ustring export_id; + bool export_id_only; + bool export_id_hints; + Glib::ustring export_background; + double export_background_opacity; + int export_ps_level; + double export_pdf_level; + bool export_text_to_path; + bool export_ignore_filters; + +private: + ExportType export_type; + Glib::ustring file_name; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/io/commandline_io.h b/src/io/commandline_io.h new file mode 100644 index 000000000..31ce2cd47 --- /dev/null +++ b/src/io/commandline_io.h @@ -0,0 +1,58 @@ + +// Command line export... should be using normal export. + +class InkCommandLineExport { + +public: + enum { + EXPORT_PLAIN_SVG, + EXPORT_INKSCAPE_SVG, + EXPORT_PNG, + EXPORT_PS, + EXPORT_EPS, + EXPORT_PDF, + EXPORT_LATEX, + EXPORT_EMF, + EXPORT_WMF, + EXPORT_XAML, + EXPORT_PRINT + } ExportType; + + InkCommandLineExport(ExportType export_type, + Glib::ustring file_name); + ~InkCommandLineExport() {}; + do_export(); + + double export_dpi; + bool export_area; + bool export_area_drawing; + bool export_area_page; + double export_margin; + bool export_snap; + int export_width; // In pixels + int export_hight; // In pixels + Glib::ustring export_id; + bool export_id_only; + bool export_id_hints; + Glib::ustring export_background; + double export_background_opacity; + int export_ps_level; + double export_pdf_level; + bool export_text_to_path; + bool export_ignore_filters; + +private: + ExportType export_type; + Glib::ustring file_name; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/dir-util.cpp b/src/io/dir-util.cpp index 1a215fa29..1a215fa29 100644 --- a/src/dir-util.cpp +++ b/src/io/dir-util.cpp diff --git a/src/dir-util.h b/src/io/dir-util.h index 327e1ad5f..327e1ad5f 100644 --- a/src/dir-util.h +++ b/src/io/dir-util.h diff --git a/src/io/file.h b/src/io/file.h new file mode 100644 index 000000000..c0c44f6ab --- /dev/null +++ b/src/io/file.h @@ -0,0 +1,25 @@ +/* + * File operations (independent of GUI) + * + * Copyright (C) 2018 Tavmjong Bah + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +#ifndef INK_FILE_IO_H +#define INK_FILE_IO_H + +class SPDocument; + +SPDocument* ink_file_new(const std::string &template = nullptr); +SPDocument* ink_file_open(const Glib::RefPtr<Gio::File>& file = Glib::RefPtr<Gio::File>()); + +// To do: +// ink_file_save() +// ink_file_export() +// ink_file_import() + + + +#endif // INK_FILE_IO_H diff --git a/src/io/open_file.h b/src/io/open_file.h new file mode 100644 index 000000000..5da25f88e --- /dev/null +++ b/src/io/open_file.h @@ -0,0 +1,12 @@ +/* + * File operations (independent of GUI) + * + * Copyright (C) 2018 Tavmjong Bah + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +SPDocument* open_file(const Glib::RefPtr<Gio::File>& file = Glib::RefPtr<Gio::File>()); + + diff --git a/src/registrytool.cpp b/src/io/registrytool.cpp index 89442bc36..89442bc36 100644 --- a/src/registrytool.cpp +++ b/src/io/registrytool.cpp diff --git a/src/registrytool.h b/src/io/registrytool.h index 335a8bd52..335a8bd52 100644 --- a/src/registrytool.h +++ b/src/io/registrytool.h diff --git a/src/resource-manager.cpp b/src/io/resource-manager.cpp index 6a455a8cc..6a455a8cc 100644 --- a/src/resource-manager.cpp +++ b/src/io/resource-manager.cpp diff --git a/src/resource-manager.h b/src/io/resource-manager.h index b4d88c7e6..b4d88c7e6 100644 --- a/src/resource-manager.h +++ b/src/io/resource-manager.h diff --git a/src/main-cmdlinexact.cpp b/src/main-cmdlinexact.cpp index a30251275..3d3eb98db 100644 --- a/src/main-cmdlinexact.cpp +++ b/src/main-cmdlinexact.cpp @@ -31,7 +31,6 @@ #include "file.h" #include "inkscape.h" #include "preferences.h" -#include "resource-manager.h" #include "selection.h" #include "verbs.h" #include "yaml.h" @@ -43,6 +42,7 @@ #include "helper/action.h" #include "helper/png-write.h" +#include "io/resource-manager.h" #include "io/sys.h" #include "object/sp-namedview.h" diff --git a/src/main.cpp b/src/main.cpp index 1bbb01efb..5de51a60a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,7 +54,7 @@ #ifdef _WIN32 #include <windows.h> -#include "registrytool.h" +#include "io/registrytool.h" #endif // _WIN32 #include "inkscape.h" diff --git a/src/object/sp-text.cpp.orig b/src/object/sp-text.cpp.orig new file mode 100644 index 000000000..4739f6b12 --- /dev/null +++ b/src/object/sp-text.cpp.orig @@ -0,0 +1,1324 @@ +/* + * SVG <text> and <tspan> implementation + * + * Author: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * fixme: + * + * These subcomponents should not be items, or alternately + * we have to invent set of flags to mark, whether standard + * attributes are applicable to given item (I even like this + * idea somewhat - Lauris) + * + */ + +#include <2geom/affine.h> +#include <libnrtype/FontFactory.h> +#include <libnrtype/font-instance.h> + +#include <glibmm/i18n.h> +#include <glibmm/regex.h> +#include "svg/svg.h" +#include "display/drawing-text.h" +#include "attributes.h" +#include "document.h" +#include "preferences.h" +#include "desktop.h" +#include "sp-namedview.h" +#include "inkscape.h" +#include "xml/quote.h" +#include "mod360.h" + +#include "sp-title.h" +#include "sp-desc.h" +#include "sp-text.h" + +#include "sp-shape.h" +#include "sp-textpath.h" +#include "sp-tref.h" +#include "sp-tspan.h" + +#include "text-editing.h" + +// For SVG 2 text flow +#include "livarot/Shape.h" +#include "display/curve.h" + +/*##################################################### +# SPTEXT +#####################################################*/ +SPText::SPText() : SPItem() { +} + +SPText::~SPText() = default; + +void SPText::build(SPDocument *doc, Inkscape::XML::Node *repr) { + this->readAttr( "x" ); + this->readAttr( "y" ); + this->readAttr( "dx" ); + this->readAttr( "dy" ); + this->readAttr( "rotate" ); + + // textLength and friends + this->readAttr( "textLength" ); + this->readAttr( "lengthAdjust" ); + + SPItem::build(doc, repr); + + this->readAttr( "sodipodi:linespacing" ); // has to happen after the styles are read +} + +void SPText::release() { + SPItem::release(); +} + +void SPText::set(unsigned int key, const gchar* value) { + //std::cout << "SPText::set: " << sp_attribute_name( key ) << ": " << (value?value:"Null") << std::endl; + + if (this->attributes.readSingleAttribute(key, value, style, &viewport)) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } else { + switch (key) { + case SP_ATTR_SODIPODI_LINESPACING: + // convert deprecated tag to css... but only if 'line-height' missing. + if (value && !this->style->line_height.set) { + this->style->line_height.set = TRUE; + this->style->line_height.inherit = FALSE; + this->style->line_height.normal = FALSE; + this->style->line_height.unit = SP_CSS_UNIT_PERCENT; + this->style->line_height.value = this->style->line_height.computed = sp_svg_read_percentage (value, 1.0); + } + // Remove deprecated attribute + this->getRepr()->setAttribute("sodipodi:linespacing", nullptr); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); + break; + + default: + SPItem::set(key, value); + break; + } + } +} + +void SPText::child_added(Inkscape::XML::Node *rch, Inkscape::XML::Node *ref) { + SPItem::child_added(rch, ref); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); +} + +void SPText::remove_child(Inkscape::XML::Node *rch) { + SPItem::remove_child(rch); + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG); +} + + +void SPText::update(SPCtx *ctx, guint flags) { + + unsigned childflags = (flags & SP_OBJECT_MODIFIED_CASCADE); + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + // Create temporary list of children + std::vector<SPObject *> l; + for (auto& child: children) { + sp_object_ref(&child, this); + l.push_back(&child); + } + + for (auto child:l) { + if (childflags || (child->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + /* fixme: Do we need transform? */ + child->updateDisplay(ctx, childflags); + } + sp_object_unref(child, this); + } + + // update ourselves after updating children + SPItem::update(ctx, flags); + + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_CHILD_MODIFIED_FLAG | + SP_TEXT_LAYOUT_MODIFIED_FLAG ) ) + { + + SPItemCtx const *ictx = reinterpret_cast<SPItemCtx const *>(ctx); + + double const w = ictx->viewport.width(); + double const h = ictx->viewport.height(); + double const em = style->font_size.computed; + double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. + + attributes.update( em, ex, w, h ); + + // Set inline_size computed value if necessary (i.e. if unit is %). + if (style->inline_size.set) { + if (style->inline_size.unit == SP_CSS_UNIT_PERCENT) { + if (style->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB || + style->writing_mode.computed == SP_CSS_WRITING_MODE_RL_TB) { + style->inline_size.computed = style->inline_size.value * ictx->viewport.width(); + } else { + style->inline_size.computed = style->inline_size.value * ictx->viewport.height(); + } + } + } + + /* fixme: It is not nice to have it here, but otherwise children content changes does not work */ + /* fixme: Even now it may not work, as we are delayed */ + /* fixme: So check modification flag everywhere immediate state is used */ + this->rebuildLayout(); + + Geom::OptRect paintbox = this->geometricBounds(); + + for (SPItemView* v = this->display; v != nullptr; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); + this->_clearFlow(g); + g->setStyle(this->style, this->parent->style); + // pass the bbox of this as paintbox (used for paintserver fills) + this->layout.show(g, paintbox); + } + } +} + +void SPText::modified(guint flags) { +// SPItem::onModified(flags); + + guint cflags = (flags & SP_OBJECT_MODIFIED_CASCADE); + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + cflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + + // FIXME: all that we need to do here is to call setStyle, to set the changed + // style, but there's no easy way to access the drawing glyphs or texts corresponding to a + // text this. Therefore we do here the same as in _update, that is, destroy all items + // and create new ones. This is probably quite wasteful. + if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG )) { + Geom::OptRect paintbox = this->geometricBounds(); + + for (SPItemView* v = this->display; v != nullptr; v = v->next) { + Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); + this->_clearFlow(g); + g->setStyle(this->style, this->parent->style); + this->layout.show(g, paintbox); + } + } + + // Create temporary list of children + std::vector<SPObject *> l; + for (auto& child: children) { + sp_object_ref(&child, this); + l.push_back(&child); + } + + for (auto child:l) { + if (cflags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + child->emitModified(cflags); + } + sp_object_unref(child, this); + } +} + +Inkscape::XML::Node *SPText::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if (flags & SP_OBJECT_WRITE_BUILD) { + if (!repr) { + repr = xml_doc->createElement("svg:text"); + } + + std::vector<Inkscape::XML::Node *> l; + + for (auto& child: children) { + if (SP_IS_TITLE(&child) || SP_IS_DESC(&child)) { + continue; + } + + Inkscape::XML::Node *crepr = nullptr; + + if (SP_IS_STRING(&child)) { + crepr = xml_doc->createTextNode(SP_STRING(&child)->string.c_str()); + } else { + crepr = child.updateRepr(xml_doc, nullptr, flags); + } + + if (crepr) { + l.push_back(crepr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, nullptr); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + if (SP_IS_TITLE(&child) || SP_IS_DESC(&child)) { + continue; + } + + if (SP_IS_STRING(&child)) { + child.getRepr()->setContent(SP_STRING(&child)->string.c_str()); + } else { + child.updateRepr(flags); + } + } + } + + this->attributes.writeTo(repr); + this->rebuildLayout(); // copied from update(), see LP Bug 1339305 + + SPItem::write(xml_doc, repr, flags); + + return repr; +} + +Geom::OptRect SPText::bbox(Geom::Affine const &transform, SPItem::BBoxType type) const { + Geom::OptRect bbox = SP_TEXT(this)->layout.bounds(transform); + + // FIXME this code is incorrect + if (bbox && type == SPItem::VISUAL_BBOX && !this->style->stroke.isNone()) { + double scale = transform.descrim(); + bbox->expandBy(0.5 * this->style->stroke_width.computed * scale); + } + + return bbox; +} + +Inkscape::DrawingItem* SPText::show(Inkscape::Drawing &drawing, unsigned /*key*/, unsigned /*flags*/) { + Inkscape::DrawingGroup *flowed = new Inkscape::DrawingGroup(drawing); + flowed->setPickChildren(false); + flowed->setStyle(this->style, this->parent->style); + + // pass the bbox of the text object as paintbox (used for paintserver fills) + this->layout.show(flowed, this->geometricBounds()); + + return flowed; +} + + +void SPText::hide(unsigned int key) { + for (SPItemView* v = this->display; v != nullptr; v = v->next) { + if (v->key == key) { + Inkscape::DrawingGroup *g = dynamic_cast<Inkscape::DrawingGroup *>(v->arenaitem); + this->_clearFlow(g); + } + } +} + +const char* SPText::displayName() const { + return _("Text"); +} + +gchar* SPText::description() const { + + SPStyle *style = this->style; + + char *n = xml_quote_strdup( style->font_family.value ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + Inkscape::Util::Quantity q = Inkscape::Util::Quantity(style->font_size.computed, "px"); + q.quantity *= this->i2doc_affine().descrim(); + Glib::ustring xs = q.string(sp_style_get_css_unit_string(unit)); + + char const *trunc = ""; + Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) this); + + if (layout && layout->inputTruncated()) { + trunc = _(" [truncated]"); + } + + char *ret = ( SP_IS_TEXT_TEXTPATH(this) + ? g_strdup_printf(_("on path%s (%s, %s)"), trunc, n, xs.c_str()) + : g_strdup_printf(_("%s (%s, %s)"), trunc, n, xs.c_str()) ); + return ret; +} + +void SPText::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const { + if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_TEXT_BASELINE)) { + // Choose a point on the baseline for snapping from or to, with the horizontal position + // of this point depending on the text alignment (left vs. right) + Inkscape::Text::Layout const *layout = te_get_layout(this); + + if (layout != nullptr && layout->outputExists()) { + boost::optional<Geom::Point> pt = layout->baselineAnchorPoint(); + + if (pt) { + p.emplace_back((*pt) * this->i2dt_affine(), Inkscape::SNAPSOURCE_TEXT_ANCHOR, Inkscape::SNAPTARGET_TEXT_ANCHOR); + } + } + } +} + +Geom::Affine SPText::set_transform(Geom::Affine const &xform) { + // we cannot optimize textpath because changing its fontsize will break its match to the path + if (SP_IS_TEXT_TEXTPATH (this)) { + if (!this->_optimizeTextpathText) { + return xform; + } else { + this->_optimizeTextpathText = false; + } + } + + // we cannot optimize text with textLength because it may show different size than specified + if (this->attributes.getTextLength()->_set) + return xform; + + /* This function takes care of scaling & translation only, we return whatever parts we can't + handle. */ + +// TODO: pjrm tried to use fontsize_expansion(xform) here and it works for text in that font size +// is scaled more intuitively when scaling non-uniformly; however this necessitated using +// fontsize_expansion instead of expansion in other places too, where it was not appropriate +// (e.g. it broke stroke width on copy/pasting of style from horizontally stretched to vertically +// stretched shape). Using fontsize_expansion only here broke setting the style via font +// dialog. This needs to be investigated further. + double const ex = xform.descrim(); + if (ex == 0) { + return xform; + } + + Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); + ret[0] /= ex; + ret[1] /= ex; + ret[2] /= ex; + ret[3] /= ex; + + // Adjust x/y, dx/dy + this->_adjustCoordsRecursive (this, xform * ret.inverse(), ex); + + // Adjust font size + this->_adjustFontsizeRecursive (this, ex); + + // Adjust stroke width + this->adjust_stroke_width_recursive (ex); + + // Adjust pattern fill + this->adjust_pattern(xform * ret.inverse()); + + // Adjust gradient fill + this->adjust_gradient(xform * ret.inverse()); + + return ret; +} + +void SPText::print(SPPrintContext *ctx) { + Geom::OptRect pbox, bbox, dbox; + pbox = this->geometricBounds(); + bbox = this->desktopVisualBounds(); + dbox = Geom::Rect::from_xywh(Geom::Point(0,0), this->document->getDimensions()); + + Geom::Affine const ctm (this->i2dt_affine()); + + this->layout.print(ctx,pbox,dbox,bbox,ctm); +} + +/* + * Member functions + */ + +void SPText::_buildLayoutInit() +{ + + layout.strut.reset(); + layout.wrap_mode = Inkscape::Text::Layout::WRAP_NONE; // Default to SVG 1.1 + + if (style) { + + // Strut + font_instance *font = font_factory::Default()->FaceFromStyle( style ); + if (font) { + font->FontMetrics(layout.strut.ascent, layout.strut.descent, layout.strut.xheight); + font->Unref(); + } + layout.strut *= style->font_size.computed; + if (style->line_height.normal ) { + layout.strut.computeEffective( Inkscape::Text::Layout::LINE_HEIGHT_NORMAL ); + } else if (style->line_height.unit == SP_CSS_UNIT_NONE) { + layout.strut.computeEffective( style->line_height.computed ); + } else { + if( style->font_size.computed > 0.0 ) { + layout.strut.computeEffective( style->line_height.computed/style->font_size.computed ); + } + } + + + // To do: follow SPItem clip_ref/mask_ref code + if (style->shape_inside.set ) { + + layout.wrap_mode = Inkscape::Text::Layout::WRAP_SHAPE_INSIDE; + + // Find union of all exclusion shapes + Shape *exclusion_shape = nullptr; + if(style->shape_subtract.set) { + exclusion_shape = _buildExclusionShape(); + } + + // Extract out shapes (a comma separated list of urls) + Glib::ustring shapeInside_value = style->shape_inside.value; + std::vector<Glib::ustring> shapes_url = Glib::Regex::split_simple(" ", shapeInside_value); + for (unsigned int i=0; i<shapes_url.size(); ++i) { + Glib::ustring shape_url = shapes_url.at(i); + if ( shape_url.compare(0,5,"url(#") != 0 || shape_url.compare(shape_url.size()-1,1,")") != 0 ){ + std::cerr << "SPText::_buildLayoutInit(): Invalid shape-inside value: " << shape_url << std::endl; + } else { + shape_url.erase(0,5); + shape_url.erase(shape_url.size()-1,1); + // std::cout << "SPText::_buildLayoutInit(): shape-inside: " << shape_url << std::endl; + SPShape *shape = dynamic_cast<SPShape *>(document->getObjectById( shape_url )); + if ( shape ) { + + // This code adapted from sp-flowregion.cpp: GetDest() + if (!(shape->_curve)) { + shape->set_shape(); + } + SPCurve *curve = shape->getCurve(); + + if ( curve ) { + Path *temp = new Path; + Path *padded = new Path; + temp->LoadPathVector( curve->get_pathvector(), shape->transform, true ); + if( style->shape_padding.set ) { + // std::cout << " padding: " << style->shape_padding.computed << std::endl; + temp->OutsideOutline ( padded, style->shape_padding.computed, join_round, butt_straight, 20.0 ); + } else { + // std::cout << " no padding" << std::endl; + padded->Copy( temp ); + } + padded->Convert( 0.25 ); // Convert to polyline + Shape* sh = new Shape; + padded->Fill( sh, 0 ); + // for( unsigned i = 0; i < temp->pts.size(); ++i ) { + // std::cout << " ........ " << temp->pts[i].p << std::endl; + // } + // std::cout << " ...... shape: " << sh->numberOfPoints() << std::endl; + Shape *uncross = new Shape; + uncross->ConvertToShape( sh ); + + // Subtract exclusion shape + if(style->shape_subtract.set) { + Shape *copy = new Shape; + if (exclusion_shape && exclusion_shape->hasEdges()) { + copy->Booleen(uncross, const_cast<Shape*>(exclusion_shape), bool_op_diff); + } else { + copy->Copy(uncross); + } + layout.appendWrapShape( copy ); + //delete exclusion_shape; + continue; + } + + layout.appendWrapShape( uncross ); + + delete temp; + delete padded; + delete sh; + // delete uncross; + } else { + std::cerr << "SPText::_buildLayoutInit(): Failed to get curve." << std::endl; + } + } else { + std::cerr << "SPText::_buildLayoutInit(): Failed to find shape." << std::endl; + } + } + } + + } else if (style->inline_size.set) { + + layout.wrap_mode = Inkscape::Text::Layout::WRAP_INLINE_SIZE; + + // If both shape_inside and inline_size are set, shape_inside wins out. + + // We construct a rectangle with one dimension set by the computed value of 'inline-size' + // and the other dimension set to infinity. Text is laid out starting at the 'x' and 'y' + // attribute values. This is handled elsewhere. + + double inline_size = style->inline_size.computed; + unsigned mode = style->writing_mode.computed; + unsigned anchor = style->text_anchor.computed; + unsigned direction = style->direction.computed; + + Geom::Rect frame; + if (mode == SP_CSS_WRITING_MODE_LR_TB || + mode == SP_CSS_WRITING_MODE_RL_TB) { + // horizontal + frame = Geom::Rect::from_xywh(attributes.firstXY()[Geom::X], -100000, inline_size, 200000); + if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + frame *= Geom::Translate (-inline_size/2.0, 0 ); + } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) || + (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) { + frame *= Geom::Translate (-inline_size, 0); + } + } else { + // vertical + frame = Geom::Rect::from_xywh(-100000, attributes.firstXY()[Geom::Y], 200000, inline_size); + if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) { + frame *= Geom::Translate (0, -inline_size/2.0); + } else if (anchor == SP_CSS_TEXT_ANCHOR_END) { + frame *= Geom::Translate (0, -inline_size); + } + } + // std::cout << " inline_size frame: " << frame << std::endl; + + Shape *shape = new Shape; + shape->Reset(); + int v0 = shape->AddPoint(frame.corner(0)); + int v1 = shape->AddPoint(frame.corner(1)); + int v2 = shape->AddPoint(frame.corner(2)); + int v3 = shape->AddPoint(frame.corner(3)); + shape->AddEdge(v0, v1); + shape->AddEdge(v1, v2); + shape->AddEdge(v2, v3); + shape->AddEdge(v3, v0); + Shape *uncross = new Shape; + uncross->ConvertToShape( shape ); + + layout.appendWrapShape( uncross ); + + delete shape; + } + + } // if (style) +} + +unsigned SPText::_buildLayoutInput(SPObject *object, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_optional_attrs, unsigned parent_attrs_offset, bool in_textpath) +{ + unsigned length = 0; + int child_attrs_offset = 0; + Inkscape::Text::Layout::OptionalTextTagAttrs optional_attrs; + + // Per SVG spec, an object with 'display:none' doesn't contribute to text layout. + if (object->style->display.computed == SP_CSS_DISPLAY_NONE) { + return 0; + } + + if (SP_IS_TEXT(object)) { + SP_TEXT(object)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true); + + // SVG 2 Text wrapping + if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_SHAPE_INSIDE) { + + // 'x' and 'y' attributes are always ignored. + optional_attrs.x.clear(); + optional_attrs.y.clear(); + + } else if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_INLINE_SIZE) { + + // For horizontal text: + // 'x' is used to calculate the left/right edges of the rectangle but is not + // needed later. If not deleted here, it will cause an incorrect positioning + // of the first line. + // 'y' is used to determine where the first line box is located and is needed + // during the output stage. + // For vertical text: + // Follow above but exchange 'x' and 'y'. + // The SVG 2 spec currently says use the 'x' and 'y' from the <text> element, + // if not defined in the <text> element, use the 'x' and 'y' from the first child. + // We only look at the <text> element. (Doing otherwise means tracking if + // we've found 'x' and 'y' and then creating the Shape at the end.) + if (style->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB || + style->writing_mode.computed == SP_CSS_WRITING_MODE_RL_TB) { + // Horizontal text + optional_attrs.x.clear(); + if (optional_attrs.y.size() > 0) { + optional_attrs.y.resize(1); // Keep only first + } + } else { + // Vertical text + if (optional_attrs.x.size() > 0) { + optional_attrs.x.resize(1); // Keep only first + } + optional_attrs.y.clear(); + } + } + + // set textLength on the entire layout, see note in TNG-Layout.h + if (SP_TEXT(object)->attributes.getTextLength()->_set) { + layout.textLength._set = true; + layout.textLength.value = SP_TEXT(object)->attributes.getTextLength()->value; + layout.textLength.computed = SP_TEXT(object)->attributes.getTextLength()->computed; + layout.textLength.unit = SP_TEXT(object)->attributes.getTextLength()->unit; + layout.lengthAdjust = (Inkscape::Text::Layout::LengthAdjust) SP_TEXT(object)->attributes.getLengthAdjust(); + } + } + + else if (SP_IS_TSPAN(object)) { + SPTSpan *tspan = SP_TSPAN(object); + // x, y attributes are stripped from some tspans marked with role="line" as we do our own line layout. + // This should be checked carefully, as it can undo line layout in imported SVG files. + bool use_xy = !in_textpath && (tspan->role == SP_TSPAN_ROLE_UNSPECIFIED || !tspan->attributes.singleXYCoordinates()); + tspan->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, true); + + // SVG 2 Text wrapping: see comment above. + if (layout.wrap_mode == Inkscape::Text::Layout::WRAP_SHAPE_INSIDE || + layout.wrap_mode == Inkscape::Text::Layout::WRAP_INLINE_SIZE) { + + // 'x' and 'y' attributes are always ignored. + optional_attrs.x.clear(); + optional_attrs.y.clear(); + } + } + + else if (SP_IS_TREF(object)) { + SP_TREF(object)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true); + } + + else if (SP_IS_TEXTPATH(object)) { + in_textpath = true; + SP_TEXTPATH(object)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, false, true); + optional_attrs.x.clear(); + optional_attrs.y.clear(); + } + + else { + optional_attrs = parent_optional_attrs; + child_attrs_offset = parent_attrs_offset; + } + + + if (SP_IS_TSPAN(object)) { + if (SP_TSPAN(object)->role != SP_TSPAN_ROLE_UNSPECIFIED) { + // we need to allow the first line not to have role=line, but still set the source_cookie to the right value + SPObject *prev_object = object->getPrev(); + if (prev_object && SP_IS_TSPAN(prev_object)) { + if (!layout.inputExists()) { + layout.appendText("", prev_object->style, prev_object, &optional_attrs); + } + layout.appendControlCode(Inkscape::Text::Layout::PARAGRAPH_BREAK, prev_object); + } + if (!object->hasChildren()) { + layout.appendText("", object->style, object, &optional_attrs); + } + length++; // interpreting line breaks as a character for the purposes of x/y/etc attributes + // is a liberal interpretation of the svg spec, but a strict reading would mean + // that if the first line is empty the second line would take its place at the + // start position. Very confusing. + child_attrs_offset--; + } + } + + for (auto& child: object->children) { + SPString *str = dynamic_cast<SPString *>(&child); + if (str) { + Glib::ustring const &string = str->string; + // std::cout << " Appending: >" << string << "<" << std::endl; + layout.appendText(string, object->style, &child, &optional_attrs, child_attrs_offset + length); + length += string.length(); + } else if (!sp_repr_is_meta_element(child.getRepr())) { + /* ^^^^ XML Tree being directly used here while it shouldn't be.*/ + length += _buildLayoutInput(&child, optional_attrs, child_attrs_offset + length, in_textpath); + } + } + + return length; +} + +Shape* SPText::_buildExclusionShape() const +{ + Shape *result = new Shape(); // Union of all exlusion shapes + Shape *shape_temp = new Shape(); + + Glib::ustring shapeSubtract_value = style->shape_subtract.value; + + // Extract out shapes (a comma separated list of urls) + std::vector<Glib::ustring> shapes_url = Glib::Regex::split_simple(" ", shapeSubtract_value); + + for(unsigned int i=0; i<shapes_url.size(); i++) { + Glib::ustring shape_url = shapes_url.at(i); + if ( shape_url.compare(0,5,"url(#") != 0 || shape_url.compare(shape_url.size()-1,1,")") != 0 ){ + std::cerr << "SPText::_buildExclusionShape(): Invalid shape-inside value: " << shape_url << std::endl; + } else { + shape_url.erase(0,5); + shape_url.erase(shape_url.size()-1,1); + // std::cout << "SPText::_buildExlusionShape(): shape-inside: " << shape_url << std::endl; + SPShape *shape = dynamic_cast<SPShape *>(document->getObjectById( shape_url )); + if ( shape ) { + // This code adapted from sp-flowregion.cpp: GetDest() + if (!(shape->_curve)) { + shape->set_shape(); + } + SPCurve *curve = shape->getCurve(); + + if ( curve ) { + Path *temp = new Path; + Path *margin = new Path; + temp->LoadPathVector( curve->get_pathvector(), shape->transform, true ); + + if( shape->style->shape_margin.set ) { + temp->OutsideOutline ( margin, -shape->style->shape_margin.computed, join_round, butt_straight, 20.0 ); + } else { + margin->Copy( temp ); + } + + margin->Convert( 0.25 ); // Convert to polyline + Shape* sh = new Shape; + margin->Fill( sh, 0 ); + + Shape *uncross = new Shape; + uncross->ConvertToShape( sh ); + + if (result->hasEdges()) { + shape_temp->Booleen(result, uncross, bool_op_union); + std::swap(result, shape_temp); + } else { + result->Copy(uncross); + } + } + } + } + } + return result; +} + +void SPText::rebuildLayout() +{ + layout.clear(); + _buildLayoutInit(); + + Inkscape::Text::Layout::OptionalTextTagAttrs optional_attrs; + _buildLayoutInput(this, optional_attrs, 0, false); + + layout.calculateFlow(); + + for (auto& child: children) { + if (SP_IS_TEXTPATH(&child)) { + SPTextPath const *textpath = SP_TEXTPATH(&child); + if (textpath->originalPath != nullptr) { +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif + layout.fitToPathAlign(textpath->startOffset, *textpath->originalPath); + } + } + } +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif + + // set the x,y attributes on role:line spans + for (auto& child: children) { + if (SP_IS_TSPAN(&child)) { + SPTSpan *tspan = SP_TSPAN(&child); + if ( (tspan->role != SP_TSPAN_ROLE_UNSPECIFIED) + && tspan->attributes.singleXYCoordinates() ) { + Inkscape::Text::Layout::iterator iter = layout.sourceToIterator(tspan); + Geom::Point anchor_point = layout.chunkAnchorPoint(iter); + tspan->attributes.setFirstXY(anchor_point); + } + } + } +} + + +void SPText::_adjustFontsizeRecursive(SPItem *item, double ex, bool is_root) +{ + SPStyle *style = item->style; + + if (style && !Geom::are_near(ex, 1.0)) { + if (!style->font_size.set && is_root) { + style->font_size.set = 1; + } + style->font_size.type = SP_FONT_SIZE_LENGTH; + style->font_size.computed *= ex; + style->letter_spacing.computed *= ex; + style->word_spacing.computed *= ex; + if (style->line_height.unit != SP_CSS_UNIT_NONE && + style->line_height.unit != SP_CSS_UNIT_PERCENT && + style->line_height.unit != SP_CSS_UNIT_EM && + style->line_height.unit != SP_CSS_UNIT_EX) { + // No unit on 'line-height' property has special behavior. + style->line_height.computed *= ex; + } + item->updateRepr(); + } + + for(auto& o: item->children) { + if (SP_IS_ITEM(&o)) + _adjustFontsizeRecursive(SP_ITEM(&o), ex, false); + } +} + +void SPText::_adjustCoordsRecursive(SPItem *item, Geom::Affine const &m, double ex, bool is_root) +{ + if (SP_IS_TSPAN(item)) + SP_TSPAN(item)->attributes.transform(m, ex, ex, is_root); + // it doesn't matter if we change the x,y for role=line spans because we'll just overwrite them anyway + else if (SP_IS_TEXT(item)) + SP_TEXT(item)->attributes.transform(m, ex, ex, is_root); + else if (SP_IS_TEXTPATH(item)) + SP_TEXTPATH(item)->attributes.transform(m, ex, ex, is_root); + else if (SP_IS_TREF(item)) { + SP_TREF(item)->attributes.transform(m, ex, ex, is_root); + } + + for(auto& o: item->children) { + if (SP_IS_ITEM(&o)) + _adjustCoordsRecursive(SP_ITEM(&o), m, ex, false); + } +} + + +void SPText::_clearFlow(Inkscape::DrawingGroup *in_arena) +{ + in_arena->clearChildren(); +} + + +/* + * TextTagAttributes implementation + */ + +// Not used. +// void TextTagAttributes::readFrom(Inkscape::XML::Node const *node) +// { +// readSingleAttribute(SP_ATTR_X, node->attribute("x")); +// readSingleAttribute(SP_ATTR_Y, node->attribute("y")); +// readSingleAttribute(SP_ATTR_DX, node->attribute("dx")); +// readSingleAttribute(SP_ATTR_DY, node->attribute("dy")); +// readSingleAttribute(SP_ATTR_ROTATE, node->attribute("rotate")); +// readSingleAttribute(SP_ATTR_TEXTLENGTH, node->attribute("textLength")); +// readSingleAttribute(SP_ATTR_LENGTHADJUST, node->attribute("lengthAdjust")); +// } + +bool TextTagAttributes::readSingleAttribute(unsigned key, gchar const *value, SPStyle const *style, Geom::Rect const *viewport) +{ + // std::cout << "TextTagAttributes::readSingleAttribute: key: " << key + // << " value: " << (value?value:"Null") << std::endl; + std::vector<SVGLength> *attr_vector; + bool update_x = false; + bool update_y = false; + switch (key) { + case SP_ATTR_X: attr_vector = &attributes.x; update_x = true; break; + case SP_ATTR_Y: attr_vector = &attributes.y; update_y = true; break; + case SP_ATTR_DX: attr_vector = &attributes.dx; update_x = true; break; + case SP_ATTR_DY: attr_vector = &attributes.dy; update_y = true; break; + case SP_ATTR_ROTATE: attr_vector = &attributes.rotate; break; + case SP_ATTR_TEXTLENGTH: + attributes.textLength.readOrUnset(value); + return true; + break; + case SP_ATTR_LENGTHADJUST: + attributes.lengthAdjust = (value && !strcmp(value, "spacingAndGlyphs")? + Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS : + Inkscape::Text::Layout::LENGTHADJUST_SPACING); // default is "spacing" + return true; + break; + default: return false; + } + + // FIXME: sp_svg_length_list_read() amalgamates repeated separators. This prevents unset values. + *attr_vector = sp_svg_length_list_read(value); + + if( (update_x || update_y) && style != nullptr && viewport != nullptr ) { + double const w = viewport->width(); + double const h = viewport->height(); + double const em = style->font_size.computed; + double const ex = em * 0.5; + for(std::vector<SVGLength>::iterator it = attr_vector->begin(); it != attr_vector->end(); ++it) { + if( update_x ) + it->update( em, ex, w ); + if( update_y ) + it->update( em, ex, h ); + } + } + return true; +} + +void TextTagAttributes::writeTo(Inkscape::XML::Node *node) const +{ + writeSingleAttributeVector(node, "x", attributes.x); + writeSingleAttributeVector(node, "y", attributes.y); + writeSingleAttributeVector(node, "dx", attributes.dx); + writeSingleAttributeVector(node, "dy", attributes.dy); + writeSingleAttributeVector(node, "rotate", attributes.rotate); + + writeSingleAttributeLength(node, "textLength", attributes.textLength); + + if (attributes.textLength._set) { + if (attributes.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACING) { + node->setAttribute("lengthAdjust", "spacing"); + } else if (attributes.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS) { + node->setAttribute("lengthAdjust", "spacingAndGlyphs"); + } + } +} + +void TextTagAttributes::update( double em, double ex, double w, double h ) +{ + for(std::vector<SVGLength>::iterator it = attributes.x.begin(); it != attributes.x.end(); ++it) { + it->update( em, ex, w ); + } + for(std::vector<SVGLength>::iterator it = attributes.y.begin(); it != attributes.y.end(); ++it) { + it->update( em, ex, h ); + } + for(std::vector<SVGLength>::iterator it = attributes.dx.begin(); it != attributes.dx.end(); ++it) { + it->update( em, ex, w ); + } + for(std::vector<SVGLength>::iterator it = attributes.dy.begin(); it != attributes.dy.end(); ++it) { + it->update( em, ex, h ); + } +} + +void TextTagAttributes::writeSingleAttributeLength(Inkscape::XML::Node *node, gchar const *key, const SVGLength &length) +{ + if (length._set) { + node->setAttribute(key, length.write().c_str()); + } else + node->setAttribute(key, nullptr); +} + +void TextTagAttributes::writeSingleAttributeVector(Inkscape::XML::Node *node, gchar const *key, std::vector<SVGLength> const &attr_vector) +{ + if (attr_vector.empty()) + node->setAttribute(key, nullptr); + else { + Glib::ustring string; + + // FIXME: this has no concept of unset values because sp_svg_length_list_read() can't read them back in + for (std::vector<SVGLength>::const_iterator it = attr_vector.begin() ; it != attr_vector.end() ; ++it) { + if (!string.empty()) string += ' '; + string += it->write(); + } + node->setAttribute(key, string.c_str()); + } +} + +bool TextTagAttributes::singleXYCoordinates() const +{ + return attributes.x.size() <= 1 && attributes.y.size() <= 1; +} + +bool TextTagAttributes::anyAttributesSet() const +{ + return !attributes.x.empty() || !attributes.y.empty() || !attributes.dx.empty() || !attributes.dy.empty() || !attributes.rotate.empty(); +} + +Geom::Point TextTagAttributes::firstXY() const +{ + Geom::Point point; + if (attributes.x.empty()) point[Geom::X] = 0.0; + else point[Geom::X] = attributes.x[0].computed; + if (attributes.y.empty()) point[Geom::Y] = 0.0; + else point[Geom::Y] = attributes.y[0].computed; + return point; +} + +void TextTagAttributes::setFirstXY(Geom::Point &point) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.x.empty()) + attributes.x.resize(1, zero_length); + if (attributes.y.empty()) + attributes.y.resize(1, zero_length); + attributes.x[0] = point[Geom::X]; + attributes.y[0] = point[Geom::Y]; +} + +void TextTagAttributes::mergeInto(Inkscape::Text::Layout::OptionalTextTagAttrs *output, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_attrs, unsigned parent_attrs_offset, bool copy_xy, bool copy_dxdyrotate) const +{ + mergeSingleAttribute(&output->x, parent_attrs.x, parent_attrs_offset, copy_xy ? &attributes.x : nullptr); + mergeSingleAttribute(&output->y, parent_attrs.y, parent_attrs_offset, copy_xy ? &attributes.y : nullptr); + mergeSingleAttribute(&output->dx, parent_attrs.dx, parent_attrs_offset, copy_dxdyrotate ? &attributes.dx : nullptr); + mergeSingleAttribute(&output->dy, parent_attrs.dy, parent_attrs_offset, copy_dxdyrotate ? &attributes.dy : nullptr); + mergeSingleAttribute(&output->rotate, parent_attrs.rotate, parent_attrs_offset, copy_dxdyrotate ? &attributes.rotate : nullptr); + if (attributes.textLength._set) { // only from current node, this is not inherited from parent + output->textLength.value = attributes.textLength.value; + output->textLength.computed = attributes.textLength.computed; + output->textLength.unit = attributes.textLength.unit; + output->textLength._set = attributes.textLength._set; + output->lengthAdjust = attributes.lengthAdjust; + } +} + +void TextTagAttributes::mergeSingleAttribute(std::vector<SVGLength> *output_list, std::vector<SVGLength> const &parent_list, unsigned parent_offset, std::vector<SVGLength> const *overlay_list) +{ + output_list->clear(); + if (overlay_list == nullptr) { + if (parent_list.size() > parent_offset) + { + output_list->reserve(parent_list.size() - parent_offset); + std::copy(parent_list.begin() + parent_offset, parent_list.end(), std::back_inserter(*output_list)); + } + } else { + output_list->reserve(std::max((int)parent_list.size() - (int)parent_offset, (int)overlay_list->size())); + unsigned overlay_offset = 0; + while (parent_offset < parent_list.size() || overlay_offset < overlay_list->size()) { + SVGLength const *this_item; + if (overlay_offset < overlay_list->size()) { + this_item = &(*overlay_list)[overlay_offset]; + overlay_offset++; + parent_offset++; + } else { + this_item = &parent_list[parent_offset]; + parent_offset++; + } + output_list->push_back(*this_item); + } + } +} + +void TextTagAttributes::erase(unsigned start_index, unsigned n) +{ + if (n == 0) return; + if (!singleXYCoordinates()) { + eraseSingleAttribute(&attributes.x, start_index, n); + eraseSingleAttribute(&attributes.y, start_index, n); + } + eraseSingleAttribute(&attributes.dx, start_index, n); + eraseSingleAttribute(&attributes.dy, start_index, n); + eraseSingleAttribute(&attributes.rotate, start_index, n); +} + +void TextTagAttributes::eraseSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n) +{ + if (attr_vector->size() <= start_index) return; + if (attr_vector->size() <= start_index + n) + attr_vector->erase(attr_vector->begin() + start_index, attr_vector->end()); + else + attr_vector->erase(attr_vector->begin() + start_index, attr_vector->begin() + start_index + n); +} + +void TextTagAttributes::insert(unsigned start_index, unsigned n) +{ + if (n == 0) return; + if (!singleXYCoordinates()) { + insertSingleAttribute(&attributes.x, start_index, n, true); + insertSingleAttribute(&attributes.y, start_index, n, true); + } + insertSingleAttribute(&attributes.dx, start_index, n, false); + insertSingleAttribute(&attributes.dy, start_index, n, false); + insertSingleAttribute(&attributes.rotate, start_index, n, false); +} + +void TextTagAttributes::insertSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n, bool is_xy) +{ + if (attr_vector->size() <= start_index) return; + SVGLength zero_length; + zero_length = 0.0; + attr_vector->insert(attr_vector->begin() + start_index, n, zero_length); + if (is_xy) { + double begin = start_index == 0 ? (*attr_vector)[start_index + n].computed : (*attr_vector)[start_index - 1].computed; + double diff = ((*attr_vector)[start_index + n].computed - begin) / n; // n tested for nonzero in insert() + for (unsigned i = 0 ; i < n ; i++) + (*attr_vector)[start_index + i] = begin + diff * i; + } +} + +void TextTagAttributes::split(unsigned index, TextTagAttributes *second) +{ + if (!singleXYCoordinates()) { + splitSingleAttribute(&attributes.x, index, &second->attributes.x, false); + splitSingleAttribute(&attributes.y, index, &second->attributes.y, false); + } + splitSingleAttribute(&attributes.dx, index, &second->attributes.dx, true); + splitSingleAttribute(&attributes.dy, index, &second->attributes.dy, true); + splitSingleAttribute(&attributes.rotate, index, &second->attributes.rotate, true); +} + +void TextTagAttributes::splitSingleAttribute(std::vector<SVGLength> *first_vector, unsigned index, std::vector<SVGLength> *second_vector, bool trimZeros) +{ + second_vector->clear(); + if (first_vector->size() <= index) return; + second_vector->resize(first_vector->size() - index); + std::copy(first_vector->begin() + index, first_vector->end(), second_vector->begin()); + first_vector->resize(index); + if (trimZeros) + while (!first_vector->empty() && (!first_vector->back()._set || first_vector->back().value == 0.0)) + first_vector->resize(first_vector->size() - 1); +} + +void TextTagAttributes::join(TextTagAttributes const &first, TextTagAttributes const &second, unsigned second_index) +{ + if (second.singleXYCoordinates()) { + attributes.x = first.attributes.x; + attributes.y = first.attributes.y; + } else { + joinSingleAttribute(&attributes.x, first.attributes.x, second.attributes.x, second_index); + joinSingleAttribute(&attributes.y, first.attributes.y, second.attributes.y, second_index); + } + joinSingleAttribute(&attributes.dx, first.attributes.dx, second.attributes.dx, second_index); + joinSingleAttribute(&attributes.dy, first.attributes.dy, second.attributes.dy, second_index); + joinSingleAttribute(&attributes.rotate, first.attributes.rotate, second.attributes.rotate, second_index); +} + +void TextTagAttributes::joinSingleAttribute(std::vector<SVGLength> *dest_vector, std::vector<SVGLength> const &first_vector, std::vector<SVGLength> const &second_vector, unsigned second_index) +{ + if (second_vector.empty()) + *dest_vector = first_vector; + else { + dest_vector->resize(second_index + second_vector.size()); + if (first_vector.size() < second_index) { + std::copy(first_vector.begin(), first_vector.end(), dest_vector->begin()); + SVGLength zero_length; + zero_length = 0.0; + std::fill(dest_vector->begin() + first_vector.size(), dest_vector->begin() + second_index, zero_length); + } else + std::copy(first_vector.begin(), first_vector.begin() + second_index, dest_vector->begin()); + std::copy(second_vector.begin(), second_vector.end(), dest_vector->begin() + second_index); + } +} + +void TextTagAttributes::transform(Geom::Affine const &matrix, double scale_x, double scale_y, bool extend_zero_length) +{ + SVGLength zero_length; + zero_length = 0.0; + + /* edge testcases for this code: + 1) moving text elements whose position is done entirely with transform="...", no x,y attributes + 2) unflowing multi-line flowtext then moving it (it has x but not y) + */ + unsigned points_count = std::max(attributes.x.size(), attributes.y.size()); + if (extend_zero_length && points_count < 1) + points_count = 1; + for (unsigned i = 0 ; i < points_count ; i++) { + Geom::Point point; + if (i < attributes.x.size()) point[Geom::X] = attributes.x[i].computed; + else point[Geom::X] = 0.0; + if (i < attributes.y.size()) point[Geom::Y] = attributes.y[i].computed; + else point[Geom::Y] = 0.0; + point *= matrix; + if (i < attributes.x.size()) + attributes.x[i] = point[Geom::X]; + else if (point[Geom::X] != 0.0 && extend_zero_length) { + attributes.x.resize(i + 1, zero_length); + attributes.x[i] = point[Geom::X]; + } + if (i < attributes.y.size()) + attributes.y[i] = point[Geom::Y]; + else if (point[Geom::Y] != 0.0 && extend_zero_length) { + attributes.y.resize(i + 1, zero_length); + attributes.y[i] = point[Geom::Y]; + } + } + for (std::vector<SVGLength>::iterator it = attributes.dx.begin() ; it != attributes.dx.end() ; ++it) + *it = it->computed * scale_x; + for (std::vector<SVGLength>::iterator it = attributes.dy.begin() ; it != attributes.dy.end() ; ++it) + *it = it->computed * scale_y; +} + +double TextTagAttributes::getDx(unsigned index) +{ + if( attributes.dx.empty()) { + return 0.0; + } + if( index < attributes.dx.size() ) { + return attributes.dx[index].computed; + } else { + return 0.0; // attributes.dx.back().computed; + } +} + + +double TextTagAttributes::getDy(unsigned index) +{ + if( attributes.dy.empty() ) { + return 0.0; + } + if( index < attributes.dy.size() ) { + return attributes.dy[index].computed; + } else { + return 0.0; // attributes.dy.back().computed; + } +} + + +void TextTagAttributes::addToDx(unsigned index, double delta) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.dx.size() < index + 1) attributes.dx.resize(index + 1, zero_length); + attributes.dx[index] = attributes.dx[index].computed + delta; +} + +void TextTagAttributes::addToDy(unsigned index, double delta) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.dy.size() < index + 1) attributes.dy.resize(index + 1, zero_length); + attributes.dy[index] = attributes.dy[index].computed + delta; +} + +void TextTagAttributes::addToDxDy(unsigned index, Geom::Point const &adjust) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (adjust[Geom::X] != 0.0) { + if (attributes.dx.size() < index + 1) attributes.dx.resize(index + 1, zero_length); + attributes.dx[index] = attributes.dx[index].computed + adjust[Geom::X]; + } + if (adjust[Geom::Y] != 0.0) { + if (attributes.dy.size() < index + 1) attributes.dy.resize(index + 1, zero_length); + attributes.dy[index] = attributes.dy[index].computed + adjust[Geom::Y]; + } +} + +double TextTagAttributes::getRotate(unsigned index) +{ + if( attributes.rotate.empty() ) { + return 0.0; + } + if( index < attributes.rotate.size() ) { + return attributes.rotate[index].computed; + } else { + return attributes.rotate.back().computed; + } +} + + +void TextTagAttributes::addToRotate(unsigned index, double delta) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.rotate.size() < index + 2) { + if (attributes.rotate.empty()) + attributes.rotate.resize(index + 2, zero_length); + else + attributes.rotate.resize(index + 2, attributes.rotate.back()); + } + attributes.rotate[index] = mod360(attributes.rotate[index].computed + delta); +} + + +void TextTagAttributes::setRotate(unsigned index, double angle) +{ + SVGLength zero_length; + zero_length = 0.0; + + if (attributes.rotate.size() < index + 2) { + if (attributes.rotate.empty()) + attributes.rotate.resize(index + 2, zero_length); + else + attributes.rotate.resize(index + 2, attributes.rotate.back()); + } + attributes.rotate[index] = mod360(angle); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/xml/rebase-hrefs.cpp b/src/xml/rebase-hrefs.cpp index 5d8f270b0..c4b5a4117 100644 --- a/src/xml/rebase-hrefs.cpp +++ b/src/xml/rebase-hrefs.cpp @@ -4,9 +4,9 @@ #include <glibmm/uriutils.h> #include "../document.h" /* Unfortunately there's a separate xml/document.h. */ -#include "dir-util.h" #include "streq.h" +#include "io/dir-util.h" #include "io/sys.h" #include "object/sp-object.h" |
