sp-text.cpp revision 8d358698ecbf192ba7c6dc05d4f7de7592753d9f
/*
* SVG <text> and <tspan> implementation
*
* Author:
* Lauris Kaplinski <lauris@kaplinski.com>
* bulia byak <buliabyak@users.sf.net>
*
* 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 <libnr/nr-matrix-fns.h>
#include <libnrtype/FontFactory.h>
#include <libnrtype/font-instance.h>
#include <libnrtype/font-style-to-pos.h>
#include "svg/stringstream.h"
#include "display/nr-arena-glyphs.h"
#include "attributes.h"
#include "document.h"
#include "desktop-handles.h"
#include "sp-namedview.h"
#include "style.h"
#include "inkscape.h"
#include "sp-metrics.h"
#include "mod360.h"
#include "sp-textpath.h"
#include "sp-tref.h"
#include "sp-tspan.h"
#include "text-editing.h"
/*#####################################################
# SPTEXT
#####################################################*/
static void sp_text_child_added (SPObject *object, Inkscape::XML::Node *rch, Inkscape::XML::Node *ref);
static Inkscape::XML::Node *sp_text_write (SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
static void sp_text_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags);
static SPItemClass *text_parent_class;
{
if (!type) {
sizeof (SPTextClass),
NULL, /* base_init */
NULL, /* base_finalize */
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (SPText),
16, /* n_preallocs */
NULL, /* value_table */
};
}
return type;
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
} else {
switch (key) {
// convert deprecated tag to css
if (value) {
text->style->line_height.value = text->style->line_height.computed = sp_svg_read_percentage (value, 1.0);
}
break;
default:
break;
}
}
}
static void
{
text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
}
static void
{
text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_CONTENT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
}
static void
{
/* Create temporary list of children */
for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
l = g_slist_prepend (l, child);
}
l = g_slist_reverse (l);
while (l) {
l = g_slist_remove (l, child);
/* fixme: Do we need transform? */
}
}
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 */
text->rebuildLayout();
// pass the bbox of the text object as paintbox (used for paintserver fills)
}
}
}
static void
{
// FIXME: all that we need to do here is nr_arena_glyphs_[group_]set_style, to set the changed
// style, but there's no easy way to access the arena glyphs or glyph groups corresponding to a
// text object. Therefore we do here the same as in _update, that is, destroy all arena items
// and create new ones. This is probably quite wasteful.
if (flags & ( SP_OBJECT_STYLE_MODIFIED_FLAG )) {
}
}
/* Create temporary list of children */
l = g_slist_prepend (l, child);
}
l = g_slist_reverse (l);
while (l) {
l = g_slist_remove (l, child);
}
}
}
sp_text_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
{
if (flags & SP_OBJECT_WRITE_BUILD) {
if (!repr)
for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
if (SP_IS_STRING(child)) {
} else {
}
}
while (l) {
l = g_slist_remove (l, l->data);
}
} else {
for (SPObject *child = sp_object_first_child(object) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
if (SP_IS_STRING(child)) {
} else {
}
}
}
// deprecated attribute, but keep it around for backwards compatibility
if (text->style->line_height.set && !text->style->line_height.inherit && !text->style->line_height.normal && text->style->line_height.unit == SP_CSS_UNIT_PERCENT) {
}
else
return repr;
}
static void
sp_text_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const /*flags*/)
{
// Add stroke width
}
}
}
}
static NRArenaItem *
{
// pass the bbox of the text object as paintbox (used for paintserver fills)
return flowed;
}
static void
{
}
static char *
{
char name_buf[256];
char *n;
if (tf) {
n = xml_quote_strdup(name_buf);
} else {
/* TRANSLATORS: For description of font with no name. */
n = g_strdup(_("<no name found>"));
}
GString *xs = SP_PX_TO_METRIC_STRING(style->font_size.computed, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->getDefaultMetric());
g_free(n);
return ret;
}
{
// the baseline anchor of the first char
}
}
{
// we cannot optimize textpath because changing its fontsize will break its match to the path
if (SP_IS_TEXT_TEXTPATH (text))
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
// 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
// Adjust stroke width
// Adjust pattern fill
// Adjust gradient fill
return ret;
}
static void
{
}
/*
* 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)) {
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.
}
for (SPObject *child = sp_object_first_child(root) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
if (SP_IS_STRING(child)) {
}
}
return length;
}
void SPText::rebuildLayout()
{
_buildLayoutInput(this, optional_attrs, 0, false);
if (SP_IS_TEXTPATH(child)) {
//g_print(layout.dumpAsText().c_str());
}
}
}
//g_print(layout.dumpAsText().c_str());
// set the x,y attributes on role:line spans
if (!SP_IS_TSPAN(child)) continue;
}
}
{
}
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(NR::Matrix 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++)
}
{
zero_length = 0.0;
}
}
}
{
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:encoding=utf-8:textwidth=99 :