style.cpp revision 3b8bed62d9bf9d9fc0445985ebc53f80f53b8257
/**
* @file
* SVG stylesheets implementation.
*/
/* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* Peter Moulder <pmoulder@mail.csse.monash.edu.au>
* bulia byak <buliabyak@users.sf.net>
* Abhishek Sharma
* Tavmjong Bah <tavmjong@free.fr>
* Kris De Gussem <Kris.DeGussem@gmail.com>
*
* Copyright (C) 2001-2002 Lauris Kaplinski
* Copyright (C) 2001 Ximian, Inc.
* Copyright (C) 2005 Monash University
* Copyright (C) 2012 Kris De Gussem
* Copyright (C) 2014 Tavmjong Bah
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstring>
#include <string>
#include <algorithm>
#include "libcroco/cr-sel-eng.h"
#include "xml/croco-node-iface.h"
#include "svg/svg.h"
#include "svg/svg-color.h"
#include "svg/svg-icc-color.h"
#include "display/canvas-bpath.h"
#include "attributes.h"
#include "document.h"
#include "extract-uri.h"
#include "uri-references.h"
#include "uri.h"
#include "sp-paint-server.h"
#include "streq.h"
#include "strneq.h"
#include "style.h"
#include "svg/css-ostringstream.h"
#include "xml/repr.h"
#include "xml/simple-document.h"
#include "util/units.h"
#include "macros.h"
#include "preferences.h"
#include "sp-filter-reference.h"
#include <sigc++/functors/ptr_fun.h>
#include <sigc++/adaptors/bind.h>
#include <2geom/math-utils.h>
#include <glibmm/regex.h>
using Inkscape::CSSOStringStream;
using std::vector;
#define BMAX 8192
#define SP_CSS_FONT_SIZE_DEFAULT 12.0;
struct SPStyleEnum;
int SPStyle::_count = 0;
/*#########################
## FORWARD DECLARATIONS
#########################*/
void sp_style_filter_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
void sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
void sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style);
static void sp_style_object_release(SPObject *object, SPStyle *style);
static CRSelEng *sp_repr_sel_eng();
//SPPropMap SPStyle::_propmap;
// C++11 allows one constructor to call another... might be useful. The original C code
// had separate calls to create SPStyle, one with only SPDocument and the other with only
// SPObject as parameters.
SPStyle::SPStyle(SPDocument *document_in, SPObject *object_in) :
// Unimplemented SVG 1.1: alignment-baseline, clip, clip-path, color-profile, cursor,
// dominant-baseline, flood-color, flood-opacity, font-size-adjust,
// glyph-orientation-horizontal, glyph-orientation-vertical, kerning, lighting-color,
// pointer-events, stop-color, stop-opacity, unicode-bidi
// For enums: property( name, enumeration, default value , inherits = true );
// For scale24: property( name, default value = 0, inherits = true );
// 'font', 'font-size', and 'font-family' must come first as other properties depend on them
// for calculated values (through 'em' and 'ex'). ('ex' is currently not read.)
// The following properties can depend on 'em' and 'ex':
// baseline-shift, kerning, letter-spacing, stroke-dash-offset, stroke-width, word-spacing,
// Non-SVG 1.1: text-indent, line-spacing
// Hidden in SPIFontStyle: (to be refactored)
// font-family
// font-specification
// Font related properties and 'font' shorthand
font_style( "font-style", enum_font_style, SP_CSS_FONT_STYLE_NORMAL ),
font_variant( "font-variant", enum_font_variant, SP_CSS_FONT_VARIANT_NORMAL ),
font_weight( "font-weight", enum_font_weight, SP_CSS_FONT_WEIGHT_NORMAL, SP_CSS_FONT_WEIGHT_400 ),
font_stretch( "font-stretch", enum_font_stretch, SP_CSS_FONT_STRETCH_NORMAL ),
font_size(),
line_height( "line-height", 1.0 ), // SPILengthOrNormal
font_family( "font-family", "sans-serif" ), // SPIString w/default
font(), // SPIFont
font_specification( "-inkscape-font-specification" ), // SPIString
// Text related properties
text_indent( "text-indent", 0.0 ), // SPILength
text_align( "text-align", enum_text_align, SP_CSS_TEXT_ALIGN_START ),
text_decoration(),
text_decoration_line(),
text_decoration_style(),
text_decoration_color( "text-decoration-color" ), // SPIColor
letter_spacing( "letter-spacing", 0.0 ), // SPILengthOrNormal
word_spacing( "word-spacing", 0.0 ), // SPILengthOrNormal
text_transform( "text-transform", enum_text_transform, SP_CSS_TEXT_TRANSFORM_NONE ),
direction( "direction", enum_direction, SP_CSS_DIRECTION_LTR ),
block_progression("block-progression", enum_block_progression, SP_CSS_BLOCK_PROGRESSION_TB),
writing_mode( "writing-mode", enum_writing_mode, SP_CSS_WRITING_MODE_LR_TB ),
baseline_shift(),
text_anchor( "text-anchor", enum_text_anchor, SP_CSS_TEXT_ANCHOR_START ),
// General visual properties
clip_rule( "clip-rule", enum_clip_rule, SP_WIND_RULE_NONZERO ),
display( "display", enum_display, SP_CSS_DISPLAY_INLINE, false ),
overflow( "overflow", enum_overflow, SP_CSS_OVERFLOW_VISIBLE, false ),
visibility( "visibility", enum_visibility, SP_CSS_VISIBILITY_VISIBLE ),
opacity( "opacity", SP_SCALE24_MAX, false ),
isolation( "isolation", enum_isolation, SP_CSS_ISOLATION_AUTO ),
mix_blend_mode( "mix-blend-mode", enum_blend_mode, SP_CSS_BLEND_NORMAL ),
paint_order(), // SPIPaintOrder
// Color properties
color( "color" ), // SPIColor
color_interpolation( "color-interpolation", enum_color_interpolation, SP_CSS_COLOR_INTERPOLATION_SRGB),
color_interpolation_filters("color-interpolation-filters", enum_color_interpolation, SP_CSS_COLOR_INTERPOLATION_LINEARRGB),
// Solid color properties
solid_color( "solid-color" ), // SPIColor
solid_opacity( "solid-opacity", SP_SCALE24_MAX ),
// Fill properties
fill( "fill" ), // SPIPaint
fill_opacity( "fill-opacity", SP_SCALE24_MAX ),
fill_rule( "fill-rule", enum_fill_rule, SP_WIND_RULE_NONZERO ),
// Stroke properites
stroke( "stroke" ), // SPIPaint
stroke_width( "stroke-width", 1.0 ), // SPILength
stroke_linecap( "stroke-linecap", enum_stroke_linecap, SP_STROKE_LINECAP_BUTT ),
stroke_linejoin( "stroke-linejoin", enum_stroke_linejoin, SP_STROKE_LINEJOIN_MITER ),
stroke_miterlimit("stroke-miterlimit", 4 ), // SPIFloat (only use of float!)
stroke_dasharray(), // SPIDashArray
stroke_dashoffset("stroke-dashoffset", 0.0 ), // SPILength for now
stroke_opacity( "stroke-opacity", SP_SCALE24_MAX ),
marker( "marker" ), // SPIString
marker_start( "marker-start" ), // SPIString
marker_mid( "marker-mid" ), // SPIString
marker_end( "marker-end" ), // SPIString
// Filter properties
filter(),
filter_blend_mode("filter-blend-mode", enum_blend_mode, SP_CSS_BLEND_NORMAL),
filter_gaussianBlur_deviation( "filter-gaussianBlur-deviation", 0.0 ), // SPILength
enable_background("enable-background", enum_enable_background, SP_CSS_BACKGROUND_ACCUMULATE, false),
// Rendering hint properties
color_rendering( "color-rendering", enum_color_rendering, SP_CSS_COLOR_RENDERING_AUTO),
image_rendering( "image-rendering", enum_image_rendering, SP_CSS_IMAGE_RENDERING_AUTO),
shape_rendering( "shape-rendering", enum_shape_rendering, SP_CSS_SHAPE_RENDERING_AUTO),
text_rendering( "text-rendering", enum_text_rendering, SP_CSS_TEXT_RENDERING_AUTO )
{
// std::cout << "SPStyle::SPStyle( SPDocument ): Entrance: (" << _count << ")" << std::endl;
// std::cout << " Document: " << (document_in?"present":"null") << std::endl;
// std::cout << " Object: "
// << (object_in?(object_in->getId()?object_in->getId():"id null"):"object null") << std::endl;
// static bool first = true;
// if( first ) {
// std::cout << "Size of SPStyle: " << sizeof(SPStyle) << std::endl;
// std::cout << " SPIBase: " << sizeof(SPIBase) << std::endl;
// std::cout << " SPIFloat: " << sizeof(SPIFloat) << std::endl;
// std::cout << " SPIScale24: " << sizeof(SPIScale24) << std::endl;
// std::cout << " SPILength: " << sizeof(SPILength) << std::endl;
// std::cout << " SPILengthOrNormal: " << sizeof(SPILengthOrNormal) << std::endl;
// std::cout << " SPIColor: " << sizeof(SPIColor) << std::endl;
// std::cout << " SPIPaint: " << sizeof(SPIPaint) << std::endl;
// std::cout << " SPITextDecorationLine" << sizeof(SPITextDecorationLine) << std::endl;
// std::cout << " Glib::ustring:" << sizeof(Glib::ustring) << std::endl;
// std::cout << " SPColor: " << sizeof(SPColor) << std::endl;
// first = false;
// }
++_count; // Poor man's memory leak detector
_refcount = 1;
cloned = false;
object = object_in;
if( object ) {
g_assert( SP_IS_OBJECT(object) );
document = object->document;
release_connection =
object->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_style_object_release), this));
cloned = object->cloned;
} else {
document = document_in;
}
// 'font' shorthand requires access to included properties.
font.setStylePointer( this );
// Properties that depend on 'font-size' for calculating lengths.
baseline_shift.setStylePointer( this );
text_indent.setStylePointer( this );
line_height.setStylePointer( this );
letter_spacing.setStylePointer( this );
word_spacing.setStylePointer( this );
stroke_width.setStylePointer( this );
stroke_dashoffset.setStylePointer( this );
// Properties that depend on 'color'
text_decoration_color.setStylePointer( this );
fill.setStylePointer( this );
stroke.setStylePointer( this );
// color.setStylePointer( this ); // Doen't need reference to self
// 'text_decoration' shorthand requires access to included properties.
text_decoration.setStylePointer( this );
// SPIPaint, SPIFilter needs access to 'this' (SPStyle)
// for setting up signals... 'fill', 'stroke' already done
filter.setStylePointer( this );
// Used to iterate over markers
marker_ptrs[SP_MARKER_LOC] = &marker;
marker_ptrs[SP_MARKER_LOC_START] = &marker_start;
marker_ptrs[SP_MARKER_LOC_MID] = &marker_mid;
marker_ptrs[SP_MARKER_LOC_END] = &marker_end;
// This might be too resource hungary... but for now it possible to loop over properties
// 'color' must be before 'fill', 'stroke', 'text-decoration-color', ...
_properties.push_back( &color );
// 'font-size'/'font' must be before properties that need to know em, ex size (SPILength,
// SPILenghtOrNormal)
_properties.push_back( &font_style );
_properties.push_back( &font_variant );
_properties.push_back( &font_weight );
_properties.push_back( &font_stretch );
_properties.push_back( &font_size );
_properties.push_back( &line_height );
_properties.push_back( &font_family );
_properties.push_back( &font );
_properties.push_back( &font_specification );
_properties.push_back( &text_indent );
_properties.push_back( &text_align );
_properties.push_back( &text_decoration );
_properties.push_back( &text_decoration_line );
_properties.push_back( &text_decoration_style );
_properties.push_back( &text_decoration_color );
_properties.push_back( &letter_spacing );
_properties.push_back( &word_spacing );
_properties.push_back( &text_transform );
_properties.push_back( &direction );
_properties.push_back( &block_progression );
_properties.push_back( &writing_mode );
_properties.push_back( &baseline_shift );
_properties.push_back( &text_anchor );
_properties.push_back( &clip_rule );
_properties.push_back( &display );
_properties.push_back( &overflow );
_properties.push_back( &visibility );
_properties.push_back( &opacity );
_properties.push_back( &isolation );
_properties.push_back( &mix_blend_mode );
_properties.push_back( &color_interpolation );
_properties.push_back( &color_interpolation_filters );
_properties.push_back( &solid_color );
_properties.push_back( &solid_opacity );
_properties.push_back( &fill );
_properties.push_back( &fill_opacity );
_properties.push_back( &fill_rule );
_properties.push_back( &stroke );
_properties.push_back( &stroke_width );
_properties.push_back( &stroke_linecap );
_properties.push_back( &stroke_linejoin );
_properties.push_back( &stroke_miterlimit );
_properties.push_back( &stroke_dasharray );
_properties.push_back( &stroke_dashoffset );
_properties.push_back( &stroke_opacity );
_properties.push_back( &marker );
_properties.push_back( &marker_start );
_properties.push_back( &marker_mid );
_properties.push_back( &marker_end );
_properties.push_back( &paint_order );
_properties.push_back( &filter );
_properties.push_back( &filter_blend_mode );
_properties.push_back( &filter_gaussianBlur_deviation );
_properties.push_back( &color_rendering );
_properties.push_back( &image_rendering );
_properties.push_back( &shape_rendering );
_properties.push_back( &text_rendering );
_properties.push_back( &enable_background );
// MAP -------------------------------------------
// if( _propmap.size() == 0 ) {
// // 'color' must be before 'fill', 'stroke', 'text-decoration-color', ...
// _propmap.insert( std::make_pair( color.name, reinterpret_cast<SPIBasePtr>(&SPStyle::color ) ) );
// // 'font-size' must be before properties that need to know em, ex size (SPILength, SPILenghtOrNormal)
// _propmap.insert( std::make_pair( font_style.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_style ) ) );
// _propmap.insert( std::make_pair( font_variant.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_variant ) ) );
// _propmap.insert( std::make_pair( font_weight.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_weight ) ) );
// _propmap.insert( std::make_pair( font_stretch.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_stretch ) ) );
// _propmap.insert( std::make_pair( font_size.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_size ) ) );
// _propmap.insert( std::make_pair( line_height.name, reinterpret_cast<SPIBasePtr>(&SPStyle::line_height ) ) );
// _propmap.insert( std::make_pair( font_family.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_family ) ) );
// _propmap.insert( std::make_pair( font.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font ) ) );
// _propmap.insert( std::make_pair( font_specification.name, reinterpret_cast<SPIBasePtr>(&SPStyle::font_specification ) ) );
// _propmap.insert( std::make_pair( text_indent.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_indent ) ) );
// _propmap.insert( std::make_pair( text_align.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_align ) ) );
// _propmap.insert( std::make_pair( text_decoration.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_decoration ) ) );
// _propmap.insert( std::make_pair( text_decoration_line.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_decoration_line ) ) );
// _propmap.insert( std::make_pair( text_decoration_style.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_decoration_style ) ) );
// _propmap.insert( std::make_pair( text_decoration_color.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_decoration_color ) ) );
// _propmap.insert( std::make_pair( letter_spacing.name, reinterpret_cast<SPIBasePtr>(&SPStyle::letter_spacing ) ) );
// _propmap.insert( std::make_pair( word_spacing.name, reinterpret_cast<SPIBasePtr>(&SPStyle::word_spacing ) ) );
// _propmap.insert( std::make_pair( text_transform.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_transform ) ) );
// _propmap.insert( std::make_pair( direction.name, reinterpret_cast<SPIBasePtr>(&SPStyle::direction ) ) );
// _propmap.insert( std::make_pair( block_progression.name, reinterpret_cast<SPIBasePtr>(&SPStyle::block_progression ) ) );
// _propmap.insert( std::make_pair( writing_mode.name, reinterpret_cast<SPIBasePtr>(&SPStyle::writing_mode ) ) );
// _propmap.insert( std::make_pair( baseline_shift.name, reinterpret_cast<SPIBasePtr>(&SPStyle::baseline_shift ) ) );
// _propmap.insert( std::make_pair( text_anchor.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_anchor ) ) );
// _propmap.insert( std::make_pair( clip_rule.name, reinterpret_cast<SPIBasePtr>(&SPStyle::clip_rule ) ) );
// _propmap.insert( std::make_pair( display.name, reinterpret_cast<SPIBasePtr>(&SPStyle::display ) ) );
// _propmap.insert( std::make_pair( overflow.name, reinterpret_cast<SPIBasePtr>(&SPStyle::overflow ) ) );
// _propmap.insert( std::make_pair( visibility.name, reinterpret_cast<SPIBasePtr>(&SPStyle::visibility ) ) );
// _propmap.insert( std::make_pair( opacity.name, reinterpret_cast<SPIBasePtr>(&SPStyle::opacity ) ) );
// _propmap.insert( std::make_pair( isolation.name, reinterpret_cast<SPIBasePtr>(&SPStyle::isolation ) ) );
// _propmap.insert( std::make_pair( mix_blend_mode.name, reinterpret_cast<SPIBasePtr>(&SPStyle::mix_blend_mode ) ) );
// _propmap.insert( std::make_pair( color_interpolation.name, reinterpret_cast<SPIBasePtr>(&SPStyle::color_interpolation ) ) );
// _propmap.insert( std::make_pair( color_interpolation_filters.name, reinterpret_cast<SPIBasePtr>(&SPStyle::color_interpolation_filters ) ) );
// _propmap.insert( std::make_pair( solid_color.name, reinterpret_cast<SPIBasePtr>(&SPStyle::solid_color ) ) );
// _propmap.insert( std::make_pair( solid_opacity.name, reinterpret_cast<SPIBasePtr>(&SPStyle::solid_opacity ) ) );
// _propmap.insert( std::make_pair( fill.name, reinterpret_cast<SPIBasePtr>(&SPStyle::fill ) ) );
// _propmap.insert( std::make_pair( fill_opacity.name, reinterpret_cast<SPIBasePtr>(&SPStyle::fill_opacity ) ) );
// _propmap.insert( std::make_pair( fill_rule.name, reinterpret_cast<SPIBasePtr>(&SPStyle::fill_rule ) ) );
// _propmap.insert( std::make_pair( stroke.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke ) ) );
// _propmap.insert( std::make_pair( stroke_width.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_width ) ) );
// _propmap.insert( std::make_pair( stroke_linecap.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_linecap ) ) );
// _propmap.insert( std::make_pair( stroke_linejoin.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_linejoin ) ) );
// _propmap.insert( std::make_pair( stroke_miterlimit.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_miterlimit ) ) );
// _propmap.insert( std::make_pair( stroke_dasharray.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_dasharray ) ) );
// _propmap.insert( std::make_pair( stroke_dashoffset.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_dashoffset ) ) );
// _propmap.insert( std::make_pair( stroke_opacity.name, reinterpret_cast<SPIBasePtr>(&SPStyle::stroke_opacity ) ) );
// _propmap.insert( std::make_pair( marker.name, reinterpret_cast<SPIBasePtr>(&SPStyle::marker ) ) );
// _propmap.insert( std::make_pair( marker_start.name, reinterpret_cast<SPIBasePtr>(&SPStyle::marker_start ) ) );
// _propmap.insert( std::make_pair( marker_mid.name, reinterpret_cast<SPIBasePtr>(&SPStyle::marker_mid ) ) );
// _propmap.insert( std::make_pair( marker_end.name, reinterpret_cast<SPIBasePtr>(&SPStyle::marker_end ) ) );
// _propmap.insert( std::make_pair( paint_order.name, reinterpret_cast<SPIBasePtr>(&SPStyle::paint_order ) ) );
// _propmap.insert( std::make_pair( filter.name, reinterpret_cast<SPIBasePtr>(&SPStyle::filter ) ) );
// _propmap.insert( std::make_pair( filter_blend_mode.name, reinterpret_cast<SPIBasePtr>(&SPStyle::filter_blend_mode ) ) );
// _propmap.insert( std::make_pair( filter_gaussianBlur_deviation.name, reinterpret_cast<SPIBasePtr>(&SPStyle::filter_gaussianBlur_deviation ) ) );
// _propmap.insert( std::make_pair( color_rendering.name, reinterpret_cast<SPIBasePtr>(&SPStyle::color_rendering ) ) );
// _propmap.insert( std::make_pair( image_rendering.name, reinterpret_cast<SPIBasePtr>(&SPStyle::image_rendering ) ) );
// _propmap.insert( std::make_pair( shape_rendering.name, reinterpret_cast<SPIBasePtr>(&SPStyle::shape_rendering ) ) );
// _propmap.insert( std::make_pair( text_rendering.name, reinterpret_cast<SPIBasePtr>(&SPStyle::text_rendering ) ) );
// _propmap.insert( std::make_pair( enable_background.name, reinterpret_cast<SPIBasePtr>(&SPStyle::enable_background ) ) );
// }
}
SPStyle::~SPStyle() {
// std::cout << "SPStyle::~SPStyle" << std::endl;
--_count; // Poor man's memory leak detector.
// Remove connections
release_connection.disconnect();
// The following shoud be moved into SPIPaint and SPIFilter
if (fill.value.href) {
fill_ps_modified_connection.disconnect();
}
if (stroke.value.href) {
stroke_ps_modified_connection.disconnect();
}
if (filter.href) {
filter_modified_connection.disconnect();
}
_properties.clear();
// std::cout << "SPStyle::~SPstyle(): Exit\n" << std::endl;
}
// Used in SPStyle::clear()
void clear_property( SPIBase* p ) {
p->clear();
}
// Matches void sp_style_clear();
void
SPStyle::clear() {
for_each( _properties.begin(), _properties.end(), clear_property );
// for(SPPropMap::iterator i = _propmap.begin(); i != _propmap.end(); ++i ) {
// (this->*(i->second)).clear();
// }
// Release connection to object, created in sp_style_new_from_object()
release_connection.disconnect();
// href->detach() called in fill->clear()...
fill_ps_modified_connection.disconnect();
if (fill.value.href) {
delete fill.value.href;
fill.value.href = NULL;
}
stroke_ps_modified_connection.disconnect();
if (stroke.value.href) {
delete stroke.value.href;
stroke.value.href = NULL;
}
filter_modified_connection.disconnect();
if (filter.href) {
delete filter.href;
filter.href = NULL;
}
if (document) {
filter.href = new SPFilterReference(document);
filter.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), this));
fill.value.href = new SPPaintServerReference(document);
fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this));
stroke.value.href = new SPPaintServerReference(document);
stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this));
}
cloned = false;
}
// Matches void sp_style_read(SPStyle *style, SPObject *object, Inkscape::XML::Node *repr)
void
SPStyle::read( SPObject *object, Inkscape::XML::Node *repr ) {
// std::cout << "SPstyle::read( SPObject, Inkscape::XML::Node ): Entrance: "
// << (object?(object->getId()?object->getId():"id null"):"object null") << " "
// << (repr?(repr->name()?repr->name():"no name"):"repr null")
// << std::endl;
g_assert(repr != NULL);
g_assert(!object || (object->getRepr() == repr));
// // Uncomment to verify that we don't need to call clear.
// std::cout << " Creating temp style for testing" << std::endl;
// SPStyle *temp = new SPStyle();
// if( !(*temp == *this ) ) std::cout << "SPStyle::read: Need to clear" << std::endl;
// delete temp;
clear(); // FIXME, If this isn't here, gradient editing stops working. Why?
if (object && object->cloned) {
cloned = true;
}
/* 1. Style attribute */
// std::cout << " MERGING STYLE ATTRIBUTE" << std::endl;
gchar const *val = repr->attribute("style");
if( val != NULL && *val ) {
_mergeString( val );
}
/* 2 Style sheet */
// std::cout << " MERGING OBJECT STYLESHEET" << std::endl;
if (object) {
_mergeObjectStylesheet( object );
} else {
// std::cerr << "SPStyle::read: No object! Can not read style sheet" << std::endl;
}
/* 3 Presentation attributes */
// std::cout << " MERGING PRESENTATION ATTRIBUTES" << std::endl;
for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
_properties[i]->readAttribute( repr );
}
// for(SPPropMap::iterator i = _propmap.begin(); i != _propmap.end(); ++i ) {
// (this->*(i->second)).readAttribute( repr );
// }
/* 4 Cascade from parent */
// std::cout << " CASCADING FROM PARENT" << std::endl;
if( object ) {
if( object->parent ) {
cascade( object->parent->style );
}
} else {
// When does this happen?
// std::cout << "SPStyle::read(): reading via repr->parent()" << std::endl;
if( repr->parent() ) {
SPStyle *parent = new SPStyle();
parent->read( NULL, repr->parent() );
cascade( parent );
delete parent;
}
}
}
// Matches void sp_style_read_from_object(SPStyle *style, SPObject *object);
void
SPStyle::readFromObject( SPObject *object ) {
// std::cout << "SPStyle::readFromObject: "<< (object->getId()?object->getId():"null")<< std::endl;
g_return_if_fail(object != NULL);
g_return_if_fail(SP_IS_OBJECT(object));
Inkscape::XML::Node *repr = object->getRepr();
g_return_if_fail(repr != NULL);
read( object, repr );
}
// Matches sp_style_merge_property(SPStyle *style, gint id, gchar const *val)
void
SPStyle::readIfUnset( gint id, gchar const *val ) {
// std::cout << "SPStyle::readIfUnset: Entrance: " << (val?val:"null") << std::endl;
// To Do: If it is not too slow, use std::map instead of std::vector inorder to remove switch()
// (looking up SP_PROP_xxxx already uses a hash).
g_return_if_fail(val != NULL);
switch (id) {
case SP_PROP_INKSCAPE_FONT_SPEC:
font_specification.readIfUnset( val );
break;
case SP_PROP_FONT_FAMILY:
font_family.readIfUnset( val );
break;
case SP_PROP_FONT_SIZE:
font_size.readIfUnset( val );
break;
case SP_PROP_FONT_SIZE_ADJUST:
if (strcmp(val, "none") != 0) {
g_warning("Unimplemented style property id SP_PROP_FONT_SIZE_ADJUST: value: %s", val);
}
break;
case SP_PROP_FONT_STYLE:
font_style.readIfUnset( val );
break;
case SP_PROP_FONT_VARIANT:
font_variant.readIfUnset( val );
break;
case SP_PROP_FONT_WEIGHT:
font_weight.readIfUnset( val );
break;
case SP_PROP_FONT_STRETCH:
font_stretch.readIfUnset( val );
break;
case SP_PROP_FONT:
font.readIfUnset( val );
break;
/* Text */
case SP_PROP_TEXT_INDENT:
text_indent.readIfUnset( val );
break;
case SP_PROP_TEXT_ALIGN:
text_align.readIfUnset( val );
break;
case SP_PROP_TEXT_DECORATION:
text_decoration.readIfUnset( val );
break;
case SP_PROP_TEXT_DECORATION_LINE:
text_decoration_line.readIfUnset( val );
break;
case SP_PROP_TEXT_DECORATION_STYLE:
text_decoration_style.readIfUnset( val );
break;
case SP_PROP_TEXT_DECORATION_COLOR:
text_decoration_color.readIfUnset( val );
break;
case SP_PROP_LINE_HEIGHT:
line_height.readIfUnset( val );
break;
case SP_PROP_LETTER_SPACING:
letter_spacing.readIfUnset( val );
break;
case SP_PROP_WORD_SPACING:
word_spacing.readIfUnset( val );
break;
case SP_PROP_TEXT_TRANSFORM:
text_transform.readIfUnset( val );
break;
/* Text (css3) */
case SP_PROP_DIRECTION:
direction.readIfUnset( val );
break;
case SP_PROP_BLOCK_PROGRESSION:
block_progression.readIfUnset( val );
break;
case SP_PROP_WRITING_MODE:
writing_mode.readIfUnset( val );
break;
case SP_PROP_TEXT_ANCHOR:
text_anchor.readIfUnset( val );
break;
case SP_PROP_BASELINE_SHIFT:
baseline_shift.readIfUnset( val );
break;
case SP_PROP_TEXT_RENDERING:
text_rendering.readIfUnset( val );
break;
case SP_PROP_ALIGNMENT_BASELINE:
g_warning("Unimplemented style property SP_PROP_ALIGNMENT_BASELINE: value: %s", val);
break;
case SP_PROP_DOMINANT_BASELINE:
g_warning("Unimplemented style property SP_PROP_DOMINANT_BASELINE: value: %s", val);
break;
case SP_PROP_GLYPH_ORIENTATION_HORIZONTAL:
g_warning("Unimplemented style property SP_PROP_ORIENTATION_HORIZONTAL: value: %s", val);
break;
case SP_PROP_GLYPH_ORIENTATION_VERTICAL:
g_warning("Unimplemented style property SP_PROP_ORIENTATION_VERTICAL: value: %s", val);
break;
case SP_PROP_KERNING:
g_warning("Unimplemented style property SP_PROP_KERNING: value: %s", val);
break;
/* Misc */
case SP_PROP_CLIP:
g_warning("Unimplemented style property SP_PROP_CLIP: value: %s", val);
break;
case SP_PROP_COLOR:
color.readIfUnset( val );
break;
case SP_PROP_CURSOR:
g_warning("Unimplemented style property SP_PROP_CURSOR: value: %s", val);
break;
case SP_PROP_DISPLAY:
display.readIfUnset( val );
break;
case SP_PROP_OVERFLOW:
overflow.readIfUnset( val );
break;
case SP_PROP_VISIBILITY:
visibility.readIfUnset( val );
break;
case SP_PROP_ISOLATION:
isolation.readIfUnset( val );
break;
case SP_PROP_MIX_BLEND_MODE:
mix_blend_mode.readIfUnset( val );
break;
/* SVG */
/* Clip/Mask */
case SP_PROP_CLIP_PATH:
/** \todo
* This is a workaround. Inkscape only supports 'clip-path' as SVG attribute, not as
* style property. By having both CSS and SVG attribute set, editing of clip-path
* will fail, since CSS always overwrites SVG attributes.
* Fixes Bug #324849
*/
g_warning("attribute 'clip-path' given as CSS");
//XML Tree being directly used here.
this->object->getRepr()->setAttribute("clip-path", val);
break;
case SP_PROP_CLIP_RULE:
clip_rule.readIfUnset( val );
break;
case SP_PROP_MASK:
/** \todo
* See comment for SP_PROP_CLIP_PATH
*/
g_warning("attribute 'mask' given as CSS");
//XML Tree being directly used here.
this->object->getRepr()->setAttribute("mask", val);
break;
case SP_PROP_OPACITY:
opacity.readIfUnset( val );
break;
case SP_PROP_ENABLE_BACKGROUND:
enable_background.readIfUnset( val );
break;
/* Filter */
case SP_PROP_FILTER:
if( !filter.inherit ) filter.readIfUnset( val );
break;
case SP_PROP_FLOOD_COLOR:
g_warning("Unimplemented style property SP_PROP_FLOOD_COLOR: value: %s", val);
break;
case SP_PROP_FLOOD_OPACITY:
g_warning("Unimplemented style property SP_PROP_FLOOD_OPACITY: value: %s", val);
break;
case SP_PROP_LIGHTING_COLOR:
g_warning("Unimplemented style property SP_PROP_LIGHTING_COLOR: value: %s", val);
break;
/* Gradient */
case SP_PROP_STOP_COLOR:
g_warning("Unimplemented style property SP_PROP_STOP_COLOR: value: %s", val);
break;
case SP_PROP_STOP_OPACITY:
g_warning("Unimplemented style property SP_PROP_STOP_OPACITY: value: %s", val);
break;
/* Interactivity */
case SP_PROP_POINTER_EVENTS:
g_warning("Unimplemented style property SP_PROP_POINTER_EVENTS: value: %s", val);
break;
/* Paint */
case SP_PROP_COLOR_INTERPOLATION:
// We read it but issue warning
color_interpolation.readIfUnset( val );
if( color_interpolation.value != SP_CSS_COLOR_INTERPOLATION_SRGB ) {
g_warning("Inkscape currently only supports color-interpolation = sRGB");
}
break;
case SP_PROP_COLOR_INTERPOLATION_FILTERS:
color_interpolation_filters.readIfUnset( val );
break;
case SP_PROP_COLOR_PROFILE:
g_warning("Unimplemented style property SP_PROP_COLOR_PROFILE: value: %s", val);
break;
case SP_PROP_COLOR_RENDERING:
color_rendering.readIfUnset( val );
break;
case SP_PROP_SOLID_COLOR:
solid_color.readIfUnset( val );
break;
case SP_PROP_SOLID_OPACITY:
solid_opacity.readIfUnset( val );
break;
case SP_PROP_FILL:
fill.readIfUnset( val );
break;
case SP_PROP_FILL_OPACITY:
fill_opacity.readIfUnset( val );
break;
case SP_PROP_FILL_RULE:
fill_rule.readIfUnset( val );
break;
case SP_PROP_IMAGE_RENDERING:
image_rendering.readIfUnset( val );
break;
case SP_PROP_MARKER:
/* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
marker.readIfUnset( val );
break;
case SP_PROP_MARKER_START:
/* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
marker_start.readIfUnset( val );
break;
case SP_PROP_MARKER_MID:
/* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
marker_mid.readIfUnset( val );
break;
case SP_PROP_MARKER_END:
/* TODO: Call sp_uri_reference_resolve(SPDocument *document, guchar const *uri) */
marker_end.readIfUnset( val );
break;
case SP_PROP_SHAPE_RENDERING:
shape_rendering.readIfUnset( val );
break;
case SP_PROP_STROKE:
stroke.readIfUnset( val );
break;
case SP_PROP_STROKE_WIDTH:
stroke_width.readIfUnset( val );
break;
case SP_PROP_STROKE_DASHARRAY:
stroke_dasharray.readIfUnset( val );
break;
case SP_PROP_STROKE_DASHOFFSET:
stroke_dashoffset.readIfUnset( val );
break;
case SP_PROP_STROKE_LINECAP:
stroke_linecap.readIfUnset( val );
break;
case SP_PROP_STROKE_LINEJOIN:
stroke_linejoin.readIfUnset( val );
break;
case SP_PROP_STROKE_MITERLIMIT:
stroke_miterlimit.readIfUnset( val );
break;
case SP_PROP_STROKE_OPACITY:
stroke_opacity.readIfUnset( val );
break;
case SP_PROP_PAINT_ORDER:
paint_order.readIfUnset( val );
break;
default:
g_warning("SPIStyle::readIfUnset(): Invalid style property id: %d value: %s", id, val);
break;
}
}
Glib::ustring
SPStyle::write( guint const flags, SPStyle const *const base ) const {
// std::cout << "SPStyle::write" << std::endl;
Glib::ustring style_string;
for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
if( base != NULL ) {
style_string += _properties[i]->write( flags, base->_properties[i] );
} else {
style_string += _properties[i]->write( flags, NULL );
}
}
// for(SPPropMap::iterator i = _propmap.begin(); i != _propmap.end(); ++i ) {
// if( base != NULL ) {
// style_string += (this->*(i->second)).write( flags, &(base->*(i->second)) );
// } else {
// style_string += (this->*(i->second)).write( flags, NULL );
// }
// }
// Remove trailing ';'
if( style_string.size() > 0 ) {
style_string.erase( style_string.size() - 1 );
}
return style_string;
}
// Corresponds to sp_style_merge_from_parent()
void
SPStyle::cascade( SPStyle const *const parent ) {
// std::cout << "SPStyle::cascade" << std::endl;
for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
_properties[i]->cascade( parent->_properties[i] );
}
// for(SPPropMap::iterator i = _propmap.begin(); i != _propmap.end(); ++i ) {
// (this->*(i->second)).cascade( &(parent->*(i->second)) );
// }
}
// Corresponds to sp_style_merge_from_dying_parent()
void
SPStyle::merge( SPStyle const *const parent ) {
// std::cout << "SPStyle::merge" << std::endl;
for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
_properties[i]->merge( parent->_properties[i] );
}
// for(SPPropMap::iterator i = _propmap.begin(); i != _propmap.end(); ++i ) {
// (this->*(i->second)).cascade( &(parent->*(i->second)) );
// }
}
// Mostly for unit testing
bool
SPStyle::operator==(const SPStyle& rhs) {
// Uncomment for testing
// for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
// if( *_properties[i] != *rhs._properties[i])
// std::cout << _properties[i]->name << ": "
// << _properties[i]->write(SP_STYLE_FLAG_ALWAYS,NULL) << " "
// << rhs._properties[i]->write(SP_STYLE_FLAG_ALWAYS,NULL)
// << (*_properties[i] == *rhs._properties[i]) << std::endl;
// }
for(std::vector<SPIBase*>::size_type i = 0; i != _properties.size(); ++i) {
if( *_properties[i] != *rhs._properties[i]) return false;
}
return true;
}
void
SPStyle::_mergeString( gchar const *const p ) {
// std::cout << "SPStyle::_mergeString: " << (p?p:"null") << std::endl;
CRDeclaration *const decl_list
= cr_declaration_parse_list_from_buf(reinterpret_cast<guchar const *>(p), CR_UTF_8);
if (decl_list) {
_mergeDeclList( decl_list );
cr_declaration_destroy(decl_list);
}
}
void
SPStyle::_mergeDeclList( CRDeclaration const *const decl_list ) {
// std::cout << "SPStyle::_mergeDeclList" << std::endl;
// In reverse order, as later declarations to take precedence over earlier ones.
// (Properties are only set if not previously set. See:
// Ref: http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order point 4.)
if (decl_list->next) {
_mergeDeclList( decl_list->next );
}
_mergeDecl( decl_list );
}
void
SPStyle::_mergeDecl( CRDeclaration const *const decl ) {
// std::cout << "SPStyle::_mergeDecl" << std::endl;
unsigned const prop_idx = sp_attribute_lookup(decl->property->stryng->str);
if (prop_idx != SP_ATTR_INVALID) {
/** \todo
* effic: Test whether the property is already set before trying to
* convert to string. Alternatively, set from CRTerm directly rather
* than converting to string.
*/
guchar *const str_value_unsigned = cr_term_to_string(decl->value);
gchar *const str_value = reinterpret_cast<gchar *>(str_value_unsigned);
readIfUnset( prop_idx, str_value );
g_free(str_value);
}
}
void
SPStyle::_mergeProps( CRPropList *const props ) {
// std::cout << "SPStyle::_mergeProps" << std::endl;
// In reverse order, as later declarations to take precedence over earlier ones.
if (props) {
_mergeProps( cr_prop_list_get_next( props ) );
CRDeclaration *decl = NULL;
cr_prop_list_get_decl(props, &decl);
_mergeDecl( decl );
}
}
void
SPStyle::_mergeObjectStylesheet( SPObject const *const object ) {
// std::cout << "SPStyle::_mergeObjectStylesheet: " << (object->getId()?object->getId():"null") << std::endl;
static CRSelEng *sel_eng = NULL;
if (!sel_eng) {
sel_eng = sp_repr_sel_eng();
}
CRPropList *props = NULL;
//XML Tree being directly used here while it shouldn't be.
CRStatus status = cr_sel_eng_get_matched_properties_from_cascade(sel_eng,
object->document->style_cascade,
object->getRepr(),
&props);
g_return_if_fail(status == CR_OK);
/// \todo Check what errors can occur, and handle them properly.
if (props) {
_mergeProps(props);
cr_prop_list_destroy(props);
}
}
// Internal
/**
* Release callback.
*/
static void
sp_style_object_release(SPObject *object, SPStyle *style)
{
(void)object; // TODO
style->object = NULL;
}
// Internal
/**
* Emit style modified signal on style's object if the filter changed.
*/
static void
sp_style_filter_ref_modified(SPObject *obj, guint flags, SPStyle *style)
{
(void)flags; // TODO
SPFilter *filter=static_cast<SPFilter *>(obj);
if (style->getFilter() == filter)
{
if (style->object) {
style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
}
}
}
// Internal
/**
* Gets called when the filter is (re)attached to the style
*/
void
sp_style_filter_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
{
if (old_ref) {
style->filter_modified_connection.disconnect();
}
if ( SP_IS_FILTER(ref))
{
style->filter_modified_connection =
ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_filter_ref_modified), style));
}
sp_style_filter_ref_modified(ref, 0, style);
}
/**
* Emit style modified signal on style's object if server is style's fill
* or stroke paint server.
*/
static void
sp_style_paint_server_ref_modified(SPObject *obj, guint flags, SPStyle *style)
{
(void)flags; // TODO
SPPaintServer *server = static_cast<SPPaintServer *>(obj);
if ((style->fill.isPaintserver())
&& style->getFillPaintServer() == server)
{
if (style->object) {
/** \todo
* fixme: I do not know, whether it is optimal - we are
* forcing reread of everything (Lauris)
*/
/** \todo
* fixme: We have to use object_modified flag, because parent
* flag is only available downstreams.
*/
style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
}
} else if ((style->stroke.isPaintserver())
&& style->getStrokePaintServer() == server)
{
if (style->object) {
/// \todo fixme:
style->object->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
}
} else if (server) {
g_assert_not_reached();
}
}
/**
* Gets called when the paintserver is (re)attached to the style
*/
void
sp_style_fill_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
{
if (old_ref) {
style->fill_ps_modified_connection.disconnect();
}
if (SP_IS_PAINT_SERVER(ref)) {
style->fill_ps_modified_connection =
ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
}
sp_style_paint_server_ref_modified(ref, 0, style);
}
/**
* Gets called when the paintserver is (re)attached to the style
*/
void
sp_style_stroke_paint_server_ref_changed(SPObject *old_ref, SPObject *ref, SPStyle *style)
{
if (old_ref) {
style->stroke_ps_modified_connection.disconnect();
}
if (SP_IS_PAINT_SERVER(ref)) {
style->stroke_ps_modified_connection =
ref->connectModified(sigc::bind(sigc::ptr_fun(&sp_style_paint_server_ref_modified), style));
}
sp_style_paint_server_ref_modified(ref, 0, style);
}
// Called in: desktop-style.cpp, gradient-chemistry.cpp, sp-object.cpp, sp-stop.cpp, style.cpp
// text-editing.cpp, libnrtype/font-lister.cpp, widgets/dash-selector.cpp, widgets/fill-style.cpp,
// widgets/stroke-style.cpp, widgets/text-toolbar.cpp, ui/dialog/glyphs.cpp, ui/dialog/swatches.cpp,
// ui/dialog/swatches.cpp, ui/dialog/text-edit.cpp. ui/tools/freehand-base.cpp,
// ui/widget/object-composite-settings.cpp, ui/widget/selected-style.cpp, ui/widget/style-swatch.cpp
/**
* Returns a new SPStyle object with default settings.
*/
SPStyle *
sp_style_new(SPDocument *document)
{
SPStyle *const style = new SPStyle( document );
return style;
}
// Called in: sp-object.cpp
/**
* Creates a new SPStyle object, and attaches it to the specified SPObject.
*/
SPStyle *
sp_style_new_from_object(SPObject *object)
{
g_return_val_if_fail(object != NULL, NULL);
g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
SPStyle *const style = new SPStyle( NULL, object );
return style;
}
// Called in display/drawing-item.cpp, display/nr-filter-primitive.cpp, libnrtype/Layout-TNG-Input.cpp
/**
* Increase refcount of style.
*/
SPStyle *
sp_style_ref(SPStyle *style)
{
g_return_val_if_fail(style != NULL, NULL);
style->ref(); // Increase ref count
return style;
}
// Called in style.cpp, desktop-style.cpp, sp-object.cpp, sp-stop.cpp, text-editing.cpp
// display/drawing-group.cpp, ...
/**
* Decrease refcount of style with possible destruction.
*/
SPStyle *
sp_style_unref(SPStyle *style)
{
g_return_val_if_fail(style != NULL, NULL);
if (style->unref() < 1) {
delete style;
return NULL;
}
return style;
}
// Called in: sp-clippath.cpp, sp-item.cpp (suspicious), sp-object.cpp, sp-style-elem.cpp
/**
* Read style properties from object's repr.
*
* 1. Reset existing object style
* 2. Load current effective object style
* 3. Load i attributes from immediate parent (which has to be up-to-date)
*/
void
sp_style_read_from_object(SPStyle *style, SPObject *object)
{
// std::cout << "sp_style_read_from_object: " << (object->getId()?object->getId():"null") << std::endl;
g_return_if_fail(style != NULL);
g_return_if_fail(object != NULL);
g_return_if_fail(SP_IS_OBJECT(object));
Inkscape::XML::Node *repr = object->getRepr();
g_return_if_fail(repr != NULL);
style->read( object, repr );
}
// Called in: libnrtype/font-lister.cpp, widgets/dash-selector.cpp, widgets/text-toolbar.cpp,
// ui/dialog/text-edit.cpp
// Why is this called when draging a gradient handle?
/**
* Read style properties from preferences.
* @param style The style to write to
* @param path Preferences directory from which the style should be read
*/
void
sp_style_read_from_prefs(SPStyle *style, Glib::ustring const &path)
{
g_return_if_fail(style != NULL);
g_return_if_fail(path != "");
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
// not optimal: we reconstruct the node based on the prefs, then pass it to
// sp_style_read for actual processing.
Inkscape::XML::SimpleDocument *tempdoc = new Inkscape::XML::SimpleDocument;
Inkscape::XML::Node *tempnode = tempdoc->createElement("prefs");
std::vector<Inkscape::Preferences::Entry> attrs = prefs->getAllEntries(path);
for (std::vector<Inkscape::Preferences::Entry>::iterator i = attrs.begin(); i != attrs.end(); ++i) {
tempnode->setAttribute(i->getEntryName().data(), i->getString().data());
}
style->read( NULL, tempnode );
Inkscape::GC::release(tempnode);
Inkscape::GC::release(tempdoc);
delete tempdoc;
}
static CRSelEng *
sp_repr_sel_eng()
{
CRSelEng *const ret = cr_sel_eng_new();
cr_sel_eng_set_node_iface(ret, &Inkscape::XML::croco_node_iface);
/** \todo
* Check whether we need to register any pseudo-class handlers.
* libcroco has its own default handlers for first-child and lang.
*
* We probably want handlers for link and arguably visited (though
* inkscape can't visit links at the time of writing). hover etc.
* more useful in inkview than the editor inkscape.
*
* http://www.w3.org/TR/SVG11/styling.html#StylingWithCSS says that
* the following should be honoured, at least by inkview:
* :hover, :active, :focus, :visited, :link.
*/
g_assert(ret);
return ret;
}
// Called in text-editting.cpp, ui/tools/frehand-base.cpp, ui/widget/style-swatch.cpp
/**
* Parses a style="..." string and merges it with an existing SPStyle.
*/
void
sp_style_merge_from_style_string(SPStyle *const style, gchar const *const p)
{
// std::cout << "sp_style_merge_from_style_string: " << (p?p:"null") <<std::endl;
/*
* Reference: http://www.w3.org/TR/SVG11/styling.html#StyleAttribute:
* ``When CSS styling is used, CSS inline style is specified by including
* semicolon-separated property declarations of the form "name : value"
* within the style attribute''.
*
* That's fairly ambiguous. Is a `value' allowed to contain semicolons?
* Why does it say "including", what else is allowed in the style
* attribute value?
*/
style->_mergeString( p );
}
/** Indexed by SP_CSS_FONT_SIZE_blah. These seem a bit small */
static float const font_size_table[] = {6.0, 8.0, 10.0, 12.0, 14.0, 18.0, 24.0};
// Called in sp-object.cpp, sp-tref.cpp, sp-use.cpp
/**
* Sets computed values in \a style, which may involve inheriting from (or in some other way
* calculating from) corresponding computed values of \a parent.
*
* References: http://www.w3.org/TR/SVG11/propidx.html shows what properties inherit by default.
* http://www.w3.org/TR/SVG11/styling.html#Inheritance gives general rules as to what it means to
* inherit a value. http://www.w3.org/TR/REC-CSS2/cascade.html#computed-value is more precise
* about what the computed value is (not obvious for lengths).
*
* \pre \a parent's computed values are already up-to-date.
*/
void
sp_style_merge_from_parent(SPStyle *const style, SPStyle const *const parent)
{
// std::cout << "sp_style_merge_from_parent" << std::endl;
g_return_if_fail(style != NULL);
if (!parent)
return;
style->cascade( parent );
return;
}
// Called in: sp-use.cpp, sp-tref.cpp, sp-item.cpp
/**
* Combine \a style and \a parent style specifications into a single style specification that
* preserves (as much as possible) the effect of the existing \a style being a child of \a parent.
*
* Called when the parent repr is to be removed (e.g. the parent is a \<use\> element that is being
* unlinked), in which case we copy/adapt property values that are explicitly set in \a parent,
* trying to retain the same visual appearance once the parent is removed. Interesting cases are
* when there is unusual interaction with the parent's value (opacity, display) or when the value
* can be specified as relative to the parent computed value (font-size, font-weight etc.).
*
* Doesn't update computed values of \a style. For correctness, you should subsequently call
* sp_style_merge_from_parent against the new parent (presumably \a parent's parent) even if \a
* style was previously up-to-date wrt \a parent.
*
* \pre \a parent's computed values are already up-to-date.
* (\a style's computed values needn't be up-to-date.)
*/
void
sp_style_merge_from_dying_parent(SPStyle *const style, SPStyle const *const parent)
{
// std::cout << "sp_style_merge_from_dying_parent" << std::endl;
style->merge( parent );
}
// The following functions should be incorporated into SPIPaint. FIXME
// Called in: style.cpp, style-internal.cpp
void
sp_style_set_ipaint_to_uri(SPStyle *style, SPIPaint *paint, const Inkscape::URI *uri, SPDocument *document)
{
// std::cout << "sp_style_set_ipaint_to_uri: Entrance: " << uri << " " << (void*)document << std::endl;
// it may be that this style's SPIPaint has not yet created its URIReference;
// now that we have a document, we can create it here
if (!paint->value.href && document) {
paint->value.href = new SPPaintServerReference(document);
paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun((paint == &style->fill)? sp_style_fill_paint_server_ref_changed : sp_style_stroke_paint_server_ref_changed), style));
}
if (paint->value.href){
if (paint->value.href->getObject()){
paint->value.href->detach();
}
try {
paint->value.href->attach(*uri);
} catch (Inkscape::BadURIException &e) {
g_warning("%s", e.what());
paint->value.href->detach();
}
}
}
// Called in: style.cpp, style-internal.cpp
void
sp_style_set_ipaint_to_uri_string (SPStyle *style, SPIPaint *paint, const gchar *uri)
{
try {
const Inkscape::URI IURI(uri);
sp_style_set_ipaint_to_uri(style, paint, &IURI, style->document);
} catch (...) {
g_warning("URI failed to parse: %s", uri);
}
}
// Called in: desktop-style.cpp
void
sp_style_set_to_uri_string (SPStyle *style, bool isfill, const gchar *uri)
{
sp_style_set_ipaint_to_uri_string (style, isfill? &style->fill : &style->stroke, uri);
}
// Called in: widgets/font-selector.cpp, widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
gchar const *
sp_style_get_css_unit_string(int unit)
{
// specify px by default, see inkscape bug 1221626, mozilla bug 234789
switch (unit) {
case SP_CSS_UNIT_NONE: return "px";
case SP_CSS_UNIT_PX: return "px";
case SP_CSS_UNIT_PT: return "pt";
case SP_CSS_UNIT_PC: return "pc";
case SP_CSS_UNIT_MM: return "mm";
case SP_CSS_UNIT_CM: return "cm";
case SP_CSS_UNIT_IN: return "in";
case SP_CSS_UNIT_EM: return "em";
case SP_CSS_UNIT_EX: return "ex";
case SP_CSS_UNIT_PERCENT: return "%";
default: return "px";
}
return "px";
}
// Called in: style-internal.cpp, widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
/*
* Convert a size in pixels into another CSS unit size
*/
double
sp_style_css_size_px_to_units(double size, int unit)
{
double unit_size = size;
switch (unit) {
case SP_CSS_UNIT_NONE: unit_size = size; break;
case SP_CSS_UNIT_PX: unit_size = size; break;
case SP_CSS_UNIT_PT: unit_size = Inkscape::Util::Quantity::convert(size, "px", "pt"); break;
case SP_CSS_UNIT_PC: unit_size = Inkscape::Util::Quantity::convert(size, "px", "pc"); break;
case SP_CSS_UNIT_MM: unit_size = Inkscape::Util::Quantity::convert(size, "px", "mm"); break;
case SP_CSS_UNIT_CM: unit_size = Inkscape::Util::Quantity::convert(size, "px", "cm"); break;
case SP_CSS_UNIT_IN: unit_size = Inkscape::Util::Quantity::convert(size, "px", "in"); break;
case SP_CSS_UNIT_EM: unit_size = size / SP_CSS_FONT_SIZE_DEFAULT; break;
case SP_CSS_UNIT_EX: unit_size = size * 2.0 / SP_CSS_FONT_SIZE_DEFAULT ; break;
case SP_CSS_UNIT_PERCENT: unit_size = size * 100.0 / SP_CSS_FONT_SIZE_DEFAULT; break;
default:
g_warning("sp_style_get_css_font_size_units conversion to %d not implemented.", unit);
break;
}
return unit_size;
}
// Called in: widgets/text-toolbar.cpp, ui/dialog/text-edit.cpp
/*
* Convert a size in a CSS unit size to pixels
*/
double
sp_style_css_size_units_to_px(double size, int unit)
{
if (unit == SP_CSS_UNIT_PX) {
return size;
}
//g_message("sp_style_css_size_units_to_px %f %d = %f px", size, unit, out);
return size * (size / sp_style_css_size_px_to_units(size, unit));;
}
// Called in style.cpp, text-editing.cpp
/**
* Dumps the style to a CSS string, with either SP_STYLE_FLAG_IFSET or
* SP_STYLE_FLAG_ALWAYS flags. Used with Always for copying an object's
* complete cascaded style to style_clipboard. When you need a CSS string
* for an object in the document tree, you normally call
* sp_style_write_difference instead to take into account the object's parent.
*
* \pre style != NULL.
* \pre flags in {IFSET, ALWAYS}.
* \post ret != NULL.
*/
gchar *
sp_style_write_string(SPStyle const *const style, guint const flags)
{
/** \todo
* Merge with write_difference, much duplicate code!
*/
g_return_val_if_fail(style != NULL, NULL);
g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
(flags == SP_STYLE_FLAG_ALWAYS) ),
NULL);
return g_strdup( style->write( flags ).c_str() );
}
// Called in style.cpp, path-chemistry, NOT in text-editting.cpp (because of bug)
/**
* Dumps style to CSS string, see sp_style_write_string()
*
* \pre from != NULL.
* \pre to != NULL.
* \post ret != NULL.
*/
gchar *
sp_style_write_difference(SPStyle const *const from, SPStyle const *const to)
{
g_return_val_if_fail(from != NULL, NULL);
g_return_val_if_fail(to != NULL, NULL);
return g_strdup( from->write( SP_STYLE_FLAG_IFDIFF, to ).c_str() );
}
// FIXME: Everything below this line belongs in a different file - css-chemistry?
void
sp_style_set_property_url (SPObject *item, gchar const *property, SPObject *linked, bool recursive)
{
Inkscape::XML::Node *repr = item->getRepr();
if (repr == NULL) return;
SPCSSAttr *css = sp_repr_css_attr_new();
if (linked) {
gchar *val = g_strdup_printf("url(#%s)", linked->getId());
sp_repr_css_set_property(css, property, val);
g_free(val);
} else {
sp_repr_css_unset_property(css, "filter");
}
if (recursive) {
sp_repr_css_change_recursive(repr, css, "style");
} else {
sp_repr_css_change(repr, css, "style");
}
sp_repr_css_attr_unref(css);
}
// Called in sp-object.cpp
/**
* Clear all style property attributes in object.
*/
void
sp_style_unset_property_attrs(SPObject *o)
{
if (!o) {
return;
}
SPStyle *style = o->style;
if (!style) {
return;
}
Inkscape::XML::Node *repr = o->getRepr();
if (!repr) {
return;
}
if (style->opacity.set) {
repr->setAttribute("opacity", NULL);
}
if (style->color.set) {
repr->setAttribute("color", NULL);
}
if (style->color_interpolation.set) {
repr->setAttribute("color-interpolation", NULL);
}
if (style->color_interpolation_filters.set) {
repr->setAttribute("color-interpolation-filters", NULL);
}
if (style->solid_color.set) {
repr->setAttribute("solid-color", NULL);
}
if (style->solid_opacity.set) {
repr->setAttribute("solid-opacity", NULL);
}
if (style->fill.set) {
repr->setAttribute("fill", NULL);
}
if (style->fill_opacity.set) {
repr->setAttribute("fill-opacity", NULL);
}
if (style->fill_rule.set) {
repr->setAttribute("fill-rule", NULL);
}
if (style->stroke.set) {
repr->setAttribute("stroke", NULL);
}
if (style->stroke_width.set) {
repr->setAttribute("stroke-width", NULL);
}
if (style->stroke_linecap.set) {
repr->setAttribute("stroke-linecap", NULL);
}
if (style->stroke_linejoin.set) {
repr->setAttribute("stroke-linejoin", NULL);
}
if (style->marker.set) {
repr->setAttribute("marker", NULL);
}
if (style->marker_start.set) {
repr->setAttribute("marker-start", NULL);
}
if (style->marker_mid.set) {
repr->setAttribute("marker-mid", NULL);
}
if (style->marker_end.set) {
repr->setAttribute("marker-end", NULL);
}
if (style->stroke_opacity.set) {
repr->setAttribute("stroke-opacity", NULL);
}
if (style->stroke_dasharray.set) {
repr->setAttribute("stroke-dasharray", NULL);
}
if (style->stroke_dashoffset.set) {
repr->setAttribute("stroke-dashoffset", NULL);
}
if (style->paint_order.set) {
repr->setAttribute("paint-order", NULL);
}
if (style->font_specification.set) {
repr->setAttribute("-inkscape-font-specification", NULL);
}
if (style->font_family.set) {
repr->setAttribute("font-family", NULL);
}
if (style->text_anchor.set) {
repr->setAttribute("text-anchor", NULL);
}
if (style->writing_mode.set) {
repr->setAttribute("writing_mode", NULL);
}
if (style->filter.set) {
repr->setAttribute("filter", NULL);
}
if (style->enable_background.set) {
repr->setAttribute("enable-background", NULL);
}
if (style->clip_rule.set) {
repr->setAttribute("clip-rule", NULL);
}
if (style->color_rendering.set) {
repr->setAttribute("color-rendering", NULL);
}
if (style->image_rendering.set) {
repr->setAttribute("image-rendering", NULL);
}
if (style->shape_rendering.set) {
repr->setAttribute("shape-rendering", NULL);
}
if (style->text_rendering.set) {
repr->setAttribute("text-rendering", NULL);
}
}
/**
* \pre style != NULL.
* \pre flags in {IFSET, ALWAYS}.
*/
SPCSSAttr *
sp_css_attr_from_style(SPStyle const *const style, guint const flags)
{
g_return_val_if_fail(style != NULL, NULL);
g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
(flags == SP_STYLE_FLAG_ALWAYS) ),
NULL);
gchar *style_str = sp_style_write_string(style, flags);
SPCSSAttr *css = sp_repr_css_attr_new();
sp_repr_css_attr_add_from_string(css, style_str);
g_free(style_str);
return css;
}
// Called in: selection-chemistry.cpp, widgets/stroke-marker-selector.cpp, widgets/stroke-style.cpp,
// ui/tools/freehand-base.cpp
/**
* \pre object != NULL
* \pre flags in {IFSET, ALWAYS}.
*/
SPCSSAttr *sp_css_attr_from_object(SPObject *object, guint const flags)
{
g_return_val_if_fail(((flags == SP_STYLE_FLAG_IFSET) ||
(flags == SP_STYLE_FLAG_ALWAYS) ),
NULL);
SPCSSAttr * result = 0;
if (object->style) {
result = sp_css_attr_from_style(object->style, flags);
}
return result;
}
// Called in: selection-chemistry.cpp, ui/dialog/inkscape-preferences.cpp
/**
* Unset any text-related properties
*/
SPCSSAttr *
sp_css_attr_unset_text(SPCSSAttr *css)
{
sp_repr_css_set_property(css, "font", NULL);
sp_repr_css_set_property(css, "-inkscape-font-specification", NULL);
sp_repr_css_set_property(css, "font-size", NULL);
sp_repr_css_set_property(css, "font-size-adjust", NULL); // not implemented yet
sp_repr_css_set_property(css, "font-style", NULL);
sp_repr_css_set_property(css, "font-variant", NULL);
sp_repr_css_set_property(css, "font-weight", NULL);
sp_repr_css_set_property(css, "font-stretch", NULL);
sp_repr_css_set_property(css, "font-family", NULL);
sp_repr_css_set_property(css, "text-indent", NULL);
sp_repr_css_set_property(css, "text-align", NULL);
sp_repr_css_set_property(css, "text-decoration", NULL);
sp_repr_css_set_property(css, "line-height", NULL);
sp_repr_css_set_property(css, "letter-spacing", NULL);
sp_repr_css_set_property(css, "word-spacing", NULL);
sp_repr_css_set_property(css, "text-transform", NULL);
sp_repr_css_set_property(css, "direction", NULL);
sp_repr_css_set_property(css, "block-progression", NULL);
sp_repr_css_set_property(css, "writing-mode", NULL);
sp_repr_css_set_property(css, "text-anchor", NULL);
sp_repr_css_set_property(css, "kerning", NULL); // not implemented yet
sp_repr_css_set_property(css, "dominant-baseline", NULL); // not implemented yet
sp_repr_css_set_property(css, "alignment-baseline", NULL); // not implemented yet
sp_repr_css_set_property(css, "baseline-shift", NULL);
return css;
}
// Called in style.cpp
static bool
is_url(char const *p)
{
if (p == NULL)
return false;
/** \todo
* FIXME: I'm not sure if this applies to SVG as well, but CSS2 says any URIs
* in property values must start with 'url('.
*/
return (g_ascii_strncasecmp(p, "url(", 4) == 0);
}
// Called in: ui/dialog/inkscape-preferences.cpp, ui/tools/tweek-tool.cpp
/**
* Unset any properties that contain URI values.
*
* Used for storing style that will be reused across documents when carrying
* the referenced defs is impractical.
*/
SPCSSAttr *
sp_css_attr_unset_uris(SPCSSAttr *css)
{
// All properties that may hold <uri> or <paint> according to SVG 1.1
if (is_url(sp_repr_css_property(css, "clip-path", NULL))) sp_repr_css_set_property(css, "clip-path", NULL);
if (is_url(sp_repr_css_property(css, "color-profile", NULL))) sp_repr_css_set_property(css, "color-profile", NULL);
if (is_url(sp_repr_css_property(css, "cursor", NULL))) sp_repr_css_set_property(css, "cursor", NULL);
if (is_url(sp_repr_css_property(css, "filter", NULL))) sp_repr_css_set_property(css, "filter", NULL);
if (is_url(sp_repr_css_property(css, "marker", NULL))) sp_repr_css_set_property(css, "marker", NULL);
if (is_url(sp_repr_css_property(css, "marker-start", NULL))) sp_repr_css_set_property(css, "marker-start", NULL);
if (is_url(sp_repr_css_property(css, "marker-mid", NULL))) sp_repr_css_set_property(css, "marker-mid", NULL);
if (is_url(sp_repr_css_property(css, "marker-end", NULL))) sp_repr_css_set_property(css, "marker-end", NULL);
if (is_url(sp_repr_css_property(css, "mask", NULL))) sp_repr_css_set_property(css, "mask", NULL);
if (is_url(sp_repr_css_property(css, "fill", NULL))) sp_repr_css_set_property(css, "fill", NULL);
if (is_url(sp_repr_css_property(css, "stroke", NULL))) sp_repr_css_set_property(css, "stroke", NULL);
return css;
}
// Called in style.cpp
/**
* Scale a single-value property.
*/
static void
sp_css_attr_scale_property_single(SPCSSAttr *css, gchar const *property,
double ex, bool only_with_units = false)
{
gchar const *w = sp_repr_css_property(css, property, NULL);
if (w) {
gchar *units = NULL;
double wd = g_ascii_strtod(w, &units) * ex;
if (w == units) {// nothing converted, non-numeric value
return;
}
if (only_with_units && (units == NULL || *units == '\0' || *units == '%')) {
// only_with_units, but no units found, so do nothing.
return;
}
Inkscape::CSSOStringStream os;
os << wd << units; // reattach units
sp_repr_css_set_property(css, property, os.str().c_str());
}
}
// Called in style.cpp for stroke-dasharray
/**
* Scale a list-of-values property.
*/
static void
sp_css_attr_scale_property_list(SPCSSAttr *css, gchar const *property, double ex)
{
gchar const *string = sp_repr_css_property(css, property, NULL);
if (string) {
Inkscape::CSSOStringStream os;
gchar **a = g_strsplit(string, ",", 10000);
bool first = true;
for (gchar **i = a; i != NULL; i++) {
gchar *w = *i;
if (w == NULL)
break;
gchar *units = NULL;
double wd = g_ascii_strtod(w, &units) * ex;
if (w == units) {// nothing converted, non-numeric value ("none" or "inherit"); do nothing
g_strfreev(a);
return;
}
if (!first) {
os << ",";
}
os << wd << units; // reattach units
first = false;
}
sp_repr_css_set_property(css, property, os.str().c_str());
g_strfreev(a);
}
}
// Called in: text-editing.cpp,
/**
* Scale any properties that may hold <length> by ex.
*/
SPCSSAttr *
sp_css_attr_scale(SPCSSAttr *css, double ex)
{
sp_css_attr_scale_property_single(css, "baseline-shift", ex);
sp_css_attr_scale_property_single(css, "stroke-width", ex);
sp_css_attr_scale_property_list (css, "stroke-dasharray", ex);
sp_css_attr_scale_property_single(css, "stroke-dashoffset", ex);
sp_css_attr_scale_property_single(css, "font-size", ex);
sp_css_attr_scale_property_single(css, "kerning", ex);
sp_css_attr_scale_property_single(css, "letter-spacing", ex);
sp_css_attr_scale_property_single(css, "word-spacing", ex);
sp_css_attr_scale_property_single(css, "line-height", ex, true);
return css;
}
/**
* Quote and/or escape string for writing to CSS, changing strings in place.
* See: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
*/
void
css_quote(Glib::ustring &val)
{
Glib::ustring out;
bool quote = false;
// Can't wait for C++11!
for( Glib::ustring::iterator it = val.begin(); it != val.end(); ++it) {
if(g_ascii_isalnum(*it) || *it=='-' || *it=='_' || *it > 0xA0) {
out += *it;
} else if (*it == '\'') {
// Single quotes require escaping and quotes.
out += '\\';
out += *it;
quote = true;
} else {
// Quote everything else including spaces.
// (CSS Fonts Level 3 recommends quoting with spaces.)
out += *it;
quote = true;
}
if( it == val.begin() && !g_ascii_isalpha(*it) ) {
// A non-ASCII/non-alpha initial value on any indentifier needs quotes.
// (Actually it's a bit more complicated but as it never hurts to quote...)
quote = true;
}
}
if( quote ) {
out.insert( out.begin(), '\'' );
out += '\'';
}
val = out;
}
/**
* Quote font names in font-family lists, changing string in place.
* We use unquoted names internally but some need to be quoted in CSS.
*/
void
css_font_family_quote(Glib::ustring &val)
{
std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", val );
val.erase();
for( unsigned i=0; i < tokens.size(); ++i ) {
css_quote( tokens[i] );
val += tokens[i] + ", ";
}
if( val.size() > 1 )
val.erase( val.size() - 2 ); // Remove trailing ", "
}
// Called in style-internal.cpp, xml/repr-css.cpp
/**
* Remove paired single and double quotes from a string, changing string in place.
*/
void
css_unquote(Glib::ustring &val)
{
if( val.size() > 1 &&
( (val[0] == '"' && val[val.size()-1] == '"' ) ||
(val[0] == '\'' && val[val.size()-1] == '\'' ) ) ) {
val.erase( 0, 1 );
val.erase( val.size()-1 );
}
}
// Called in style-internal.cpp, text-toolbar.cpp
/**
* Remove paired single and double quotes from font names in font-family lists,
* changing string in place.
* We use unquoted family names internally but CSS sometimes uses quoted names.
*/
void
css_font_family_unquote(Glib::ustring &val)
{
std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", val );
val.erase();
for( unsigned i=0; i < tokens.size(); ++i ) {
css_unquote( tokens[i] );
val += tokens[i] + ", ";
}
if( val.size() > 1 )
val.erase( val.size() - 2 ); // Remove trailing ", "
}
/*
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 :