sp-text.cpp revision 54abbf38ce4380f790aa38411ea3a6496dc5c5b3
/*
* 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)
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <libnrtype/FontFactory.h>
#include <libnrtype/font-instance.h>
#include <libnrtype/font-style-to-pos.h>
#include "svg/stringstream.h"
#include "display/drawing-text.h"
#include "attributes.h"
#include "document.h"
#include "preferences.h"
#include "desktop-handles.h"
#include "sp-namedview.h"
#include "style.h"
#include "inkscape.h"
#include "mod360.h"
#include "sp-title.h"
#include "sp-desc.h"
#include "sp-text.h"
#include "sp-textpath.h"
#include "sp-tref.h"
#include "sp-tspan.h"
#include "text-editing.h"
#include "sp-factory.h"
namespace {
SPObject* createText() {
return new SPText();
}
}
/*#####################################################
# SPTEXT
#####################################################*/
//new (&this->layout) Inkscape::Text::Layout;
//new (&this->attributes) TextTagAttributes;
}
}
this->readAttr( "x" );
this->readAttr( "y" );
this->readAttr( "dx" );
this->readAttr( "dy" );
this->readAttr( "rotate" );
}
//this->attributes.~TextTagAttributes();
//this->layout.~Layout();
}
} else {
switch (key) {
// convert deprecated tag to css
if (value) {
this->style->line_height.value = this->style->line_height.computed = sp_svg_read_percentage (value, 1.0);
}
break;
default:
break;
}
}
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
}
if (flags & SP_OBJECT_MODIFIED_FLAG) {
}
// Create temporary list of children
sp_object_ref(child, this);
l = g_slist_prepend (l, child);
}
l = g_slist_reverse (l);
while (l) {
SPObject *child = reinterpret_cast<SPObject*>(l->data); // We just built this list, so cast is safe.
l = g_slist_remove (l, child);
/* fixme: Do we need transform? */
}
sp_object_unref(child, this);
}
// update ourselves after updating children
if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG |
{
/* 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();
this->_clearFlow(g);
// pass the bbox of the this this as paintbox (used for paintserver fills)
}
}
}
// SPItem::onModified(flags);
if (flags & SP_OBJECT_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 )) {
this->_clearFlow(g);
}
}
// Create temporary list of children
sp_object_ref(child, this);
l = g_slist_prepend (l, child);
}
l = g_slist_reverse (l);
while (l) {
SPObject *child = reinterpret_cast<SPObject*>(l->data); // We just built this list, so cast is safe.
l = g_slist_remove (l, child);
}
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) {
}
continue;
}
if (SP_IS_STRING(child)) {
} else {
}
if (crepr) {
l = g_slist_prepend (l, crepr);
}
}
while (l) {
l = g_slist_remove (l, l->data);
}
} else {
continue;
}
if (SP_IS_STRING(child)) {
} else {
}
}
}
this->rebuildLayout(); // copied from update(), see LP Bug 1339305
// deprecated attribute, but keep it around for backwards compatibility
if (this->style->line_height.set && !this->style->line_height.inherit && !this->style->line_height.normal && this->style->line_height.unit == SP_CSS_UNIT_PERCENT) {
} else {
}
return repr;
}
// FIXME this code is incorrect
}
return bbox;
}
Inkscape::DrawingItem* SPText::show(Inkscape::Drawing &drawing, unsigned /*key*/, unsigned /*flags*/) {
flowed->setPickChildren(false);
// pass the bbox of the text object as paintbox (used for paintserver fills)
return flowed;
}
this->_clearFlow(g);
}
}
}
const char* SPText::displayName() const {
return _("Text");
}
char *n;
if (tf) {
char name_buf[256];
n = xml_quote_strdup(name_buf);
} else {
/* TRANSLATORS: For description of font with no name. */
n = g_strdup(_("<no name found>"));
}
char const *trunc = "";
trunc = _(" [truncated]");
}
char *ret = ( SP_IS_TEXT_TEXTPATH(this)
g_free(n);
return ret;
}
void SPText::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
// 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)
if (pt) {
p.push_back(Inkscape::SnapCandidatePoint((*pt) * this->i2dt_affine(), Inkscape::SNAPSOURCE_TEXT_ANCHOR, Inkscape::SNAPTARGET_TEXT_ANCHOR));
}
}
}
}
// 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;
}
}
/* 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
// stretched shape). Using fontsize_expansion only here broke setting the style via font
// dialog. This needs to be investigated further.
if (ex == 0) {
return xform;
}
// Adjust font size
this->_adjustFontsizeRecursive (this, ex);
// Adjust stroke width
this->adjust_stroke_width_recursive (ex);
// Adjust pattern fill
// Adjust gradient fill
return ret;
}
pbox = this->geometricBounds();
bbox = this->desktopVisualBounds();
}
/*
* Member functions
*/
unsigned SPText::_buildLayoutInput(SPObject *root, Inkscape::Text::Layout::OptionalTextTagAttrs const &parent_optional_attrs, unsigned parent_attrs_offset, bool in_textpath)
{
unsigned length = 0;
int child_attrs_offset = 0;
if (SP_IS_TEXT(root)) {
SP_TEXT(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
}
else if (SP_IS_TSPAN(root)) {
// 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);
}
else if (SP_IS_TREF(root)) {
SP_TREF(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
}
else if (SP_IS_TEXTPATH(root)) {
in_textpath = true;
SP_TEXTPATH(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, false, true);
optional_attrs.x.clear();
optional_attrs.y.clear();
}
else {
}
if (SP_IS_TSPAN(root))
// we need to allow the first line not to have role=line, but still set the source_cookie to the right value
if (!layout.inputExists()) {
}
}
if (!root->hasChildren()) {
}
// 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.
}
if (SP_IS_STRING(child)) {
} /*XML Tree being directly used here while it shouldn't be.*/ else if (!sp_repr_is_meta_element(child->getRepr())) {
}
}
return length;
}
void SPText::rebuildLayout()
{
_buildLayoutInput(this, optional_attrs, 0, false);
if (SP_IS_TEXTPATH(child)) {
//g_print("%s", layout.dumpAsText().c_str());
}
}
}
//g_print("%s", layout.dumpAsText().c_str());
// set the x,y attributes on role:line spans
if (SP_IS_TSPAN(child)) {
}
}
}
}
{
}
item->updateRepr();
}
if (SP_IS_ITEM(o))
}
}
{
if (SP_IS_TSPAN(item))
// 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))
else if (SP_IS_TEXTPATH(item))
else if (SP_IS_TREF(item)) {
}
if (SP_IS_ITEM(o))
}
}
{
}
/*
* TextTagAttributes implementation
*/
{
}
{
switch (key) {
default: return false;
}
// FIXME: sp_svg_length_list_read() amalgamates repeated separators. This prevents unset values.
return true;
}
{
}
void TextTagAttributes::writeSingleAttribute(Inkscape::XML::Node *node, gchar const *key, std::vector<SVGLength> const &attr_vector)
{
if (attr_vector.empty())
else {
// 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) {
}
}
}
bool TextTagAttributes::singleXYCoordinates() const
{
}
bool TextTagAttributes::anyAttributesSet() const
{
return !attributes.x.empty() || !attributes.y.empty() || !attributes.dx.empty() || !attributes.dy.empty() || !attributes.rotate.empty();
}
{
return point;
}
{
zero_length = 0.0;
if (attributes.x.empty())
if (attributes.y.empty())
}
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 : NULL);
mergeSingleAttribute(&output->y, parent_attrs.y, parent_attrs_offset, copy_xy ? &attributes.y : NULL);
mergeSingleAttribute(&output->dx, parent_attrs.dx, parent_attrs_offset, copy_dxdyrotate ? &attributes.dx : NULL);
mergeSingleAttribute(&output->dy, parent_attrs.dy, parent_attrs_offset, copy_dxdyrotate ? &attributes.dy : NULL);
mergeSingleAttribute(&output->rotate, parent_attrs.rotate, parent_attrs_offset, copy_dxdyrotate ? &attributes.rotate : NULL);
}
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 == NULL) {
{
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;
} else {
}
}
}
}
{
if (n == 0) return;
if (!singleXYCoordinates()) {
}
}
void TextTagAttributes::eraseSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n)
{
else
}
{
if (n == 0) return;
if (!singleXYCoordinates()) {
}
}
void TextTagAttributes::insertSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n, bool is_xy)
{
zero_length = 0.0;
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++)
}
}
{
if (!singleXYCoordinates()) {
}
}
void TextTagAttributes::splitSingleAttribute(std::vector<SVGLength> *first_vector, unsigned index, std::vector<SVGLength> *second_vector, bool trimZeros)
{
second_vector->clear();
if (trimZeros)
}
void TextTagAttributes::join(TextTagAttributes const &first, TextTagAttributes const &second, unsigned second_index)
{
if (second.singleXYCoordinates()) {
} else {
}
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())
else {
zero_length = 0.0;
std::fill(dest_vector->begin() + first_vector.size(), dest_vector->begin() + second_index, zero_length);
} else
}
}
void TextTagAttributes::transform(Geom::Affine const &matrix, double scale_x, double scale_y, bool extend_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)
*/
points_count = 1;
for (unsigned i = 0 ; i < points_count ; i++) {
if (i < attributes.x.size())
}
if (i < attributes.y.size())
}
}
for (std::vector<SVGLength>::iterator it = attributes.dx.begin() ; it != attributes.dx.end() ; ++it)
for (std::vector<SVGLength>::iterator it = attributes.dy.begin() ; it != attributes.dy.end() ; ++it)
}
{
return 0.0;
}
} else {
return 0.0; // attributes.dx.back().computed;
}
}
{
return 0.0;
}
} else {
return 0.0; // attributes.dy.back().computed;
}
}
{
zero_length = 0.0;
}
{
zero_length = 0.0;
}
{
zero_length = 0.0;
}
}
}
{
return 0.0;
}
} else {
}
}
{
zero_length = 0.0;
else
}
}
{
zero_length = 0.0;
else
}
}
/*
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 :