sp-text.cpp revision 280aacbbe34a383b9a53217d5f66efdd67a9fbee
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * SVG <text> and <tspan> implementation
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Lauris Kaplinski <lauris@kaplinski.com>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * bulia byak <buliabyak@users.sf.net>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Jon A. Cruz <jon@joncruz.org>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Abhishek Sharma
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Copyright (C) 1999-2002 Lauris Kaplinski
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Copyright (C) 2000-2001 Ximian, Inc.
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Released under GNU GPL, read the file 'COPYING' for more information
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * These subcomponents should not be items, or alternately
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * we have to invent set of flags to mark, whether standard
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * attributes are applicable to given item (I even like this
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * idea somewhat - Lauris)
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return new SPText();
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm bool textRegistered = SPFactory::instance().registerObject("svg:text", createText);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak/*#####################################################
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm#####################################################*/
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakvoid SPText::build(SPDocument *doc, Inkscape::XML::Node *repr) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak // SVG 2 Auto wrapped text
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->readAttr( "sodipodi:linespacing" ); // has to happen after the styles are read
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakvoid SPText::set(unsigned int key, const gchar* value) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak //std::cout << "SPText::set: " << sp_attribute_name( key ) << ": " << (value?value:"Null") << std::endl;
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm if (this->attributes.readSingleAttribute(key, value)) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak // convert deprecated tag to css
60bbcba041e80a4b29118269c0897df5c068563eacspike this->style->line_height.value = this->style->line_height.computed = sp_svg_read_percentage (value, 1.0);
a223910930e4cf964962a08cf1d30928395652c5pjrm this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
77d65c763568495a6ffc7c15a81964448139f47apjrm if (!this->width.read(value) || this->width.value < 0.0) {
case SP_ATTR_HEIGHT:
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);
l = g_slist_reverse (l);
SPObject *child = reinterpret_cast<SPObject*>(l->data); // We just built this list, so cast is safe.
this->rebuildLayout();
this->_clearFlow(g);
this->_clearFlow(g);
l = g_slist_reverse (l);
SPObject *child = reinterpret_cast<SPObject*>(l->data); // We just built this list, so cast is safe.
Inkscape::XML::Node *SPText::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
if (!repr) {
if (crepr) {
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) {
return repr;
return bbox;
Inkscape::DrawingItem* SPText::show(Inkscape::Drawing &drawing, unsigned /*key*/, unsigned /*flags*/) {
return flowed;
this->_clearFlow(g);
GString *xs = g_string_new(q.string(sp_desktop_namedview(SP_ACTIVE_DESKTOP)->display_units).c_str());
return ret;
void SPText::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
if (pt) {
p.push_back(Inkscape::SnapCandidatePoint((*pt) * this->i2dt_affine(), Inkscape::SNAPSOURCE_TEXT_ANCHOR, Inkscape::SNAPTARGET_TEXT_ANCHOR));
if (SP_IS_TEXT_TEXTPATH (this)) {
if (!this->_optimizeTextpathText) {
return xform;
this->_optimizeTextpathText = false;
if (ex == 0) {
return xform;
return ret;
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;
SP_TEXT(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
// x, y attributes are stripped from some tspans marked with role="line" as we do our own line layout.
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);
SP_TREF(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, true, true);
in_textpath = true;
SP_TEXTPATH(root)->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, false, true);
// we need to allow the first line not to have role=line, but still set the source_cookie to the right value
} /*XML Tree being directly used here while it shouldn't be.*/ else if (!sp_repr_is_meta_element(child->getRepr())) {
return length;
if (SP_IS_ITEM(o))
// it doesn't matter if we change the x,y for role=line spans because we'll just overwrite them anyway
if (SP_IS_ITEM(o))
switch (key) {
void TextTagAttributes::writeSingleAttribute(Inkscape::XML::Node *node, gchar const *key, std::vector<SVGLength> const &attr_vector)
// 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) {
return !attributes.x.empty() || !attributes.y.empty() || !attributes.dx.empty() || !attributes.dy.empty() || !attributes.rotate.empty();
return point;
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)
std::copy(parent_list.begin() + parent_offset, parent_list.end(), std::back_inserter(*output_list));
output_list->reserve(std::max((int)parent_list.size() - (int)parent_offset, (int)overlay_list->size()));
unsigned overlay_offset = 0;
if (!singleXYCoordinates()) {
void TextTagAttributes::eraseSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n)
if (!singleXYCoordinates()) {
void TextTagAttributes::insertSingleAttribute(std::vector<SVGLength> *attr_vector, unsigned start_index, unsigned n, bool is_xy)
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()
if (!singleXYCoordinates()) {
void TextTagAttributes::splitSingleAttribute(std::vector<SVGLength> *first_vector, unsigned index, std::vector<SVGLength> *second_vector, bool trimZeros)
if (trimZeros)
void TextTagAttributes::join(TextTagAttributes const &first, TextTagAttributes const &second, unsigned 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)
std::fill(dest_vector->begin() + first_vector.size(), dest_vector->begin() + second_index, zero_length);
void TextTagAttributes::transform(Geom::Affine const &matrix, double scale_x, double scale_y, bool extend_zero_length)
for (unsigned i = 0 ; i < points_count ; i++) {
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)