diff options
Diffstat (limited to 'src/style-internal.cpp')
-rw-r--r-- | src/style-internal.cpp | 3321 |
1 files changed, 3321 insertions, 0 deletions
diff --git a/src/style-internal.cpp b/src/style-internal.cpp new file mode 100644 index 0000000..56c93e4 --- /dev/null +++ b/src/style-internal.cpp @@ -0,0 +1,3321 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * SVG stylesheets implementation - Classes used by SPStyle class. + */ + +/* Authors: + * C++ conversion: + * Tavmjong Bah <tavmjong@free.fr> + * Legacy C implementation: + * Lauris Kaplinski <lauris@kaplinski.com> + * Peter Moulder <pmoulder@mail.csse.monash.edu.au> + * bulia byak <buliabyak@users.sf.net> + * Abhishek Sharma + * 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, 2018 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/regex.h> + +#include "style-internal.h" +#include "style.h" + +#include "bad-uri-exception.h" +#include "extract-uri.h" +#include "inkscape.h" +#include "preferences.h" +#include "streq.h" +#include "strneq.h" + +#include "object/sp-text.h" +#include "object/object-set.h" + +#include "svg/svg.h" +#include "svg/svg-color.h" +#include "svg/css-ostringstream.h" + +#include "util/units.h" + +// TODO REMOVE OR MAKE MEMBER FUNCTIONS +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); +void sp_style_filter_ref_changed( SPObject *old_ref, SPObject *ref, SPStyle *style); +void sp_style_set_ipaint_to_uri(SPStyle *style, SPIPaint *paint, const Inkscape::URI *uri, SPDocument *document); +void sp_style_set_ipaint_to_uri_string (SPStyle *style, SPIPaint *paint, const gchar *uri); + +using Inkscape::CSSOStringStream; + +// SPIBase -------------------------------------------------------------- + +Glib::ustring const &SPIBase::name() const +{ + static Glib::ustring names[(int)SPAttr::SPAttr_SIZE]; + auto &name = names[(int)id()]; + if (name.empty()) { + auto const *namecstr = sp_attribute_name(id()); + name = namecstr ? namecstr : "anonymous"; + } + return name; +} + +/** + * Check if this property should be written. This function won't do any writing so can + * be used as a quick way to check if specific kinds of style attrs have been changed. + * + * @param flags Conditions bitmask of `SP_STYLE_FLAG_*` bits + * @param style_src_req For `SP_STYLE_FLAG_IFSRC` + * @param base Parent node style for `SP_STYLE_FLAG_IFDIFF`, can be NULL + * + * @return True if property should be written, false otherwise + */ +bool SPIBase::shall_write(guint const flags, SPStyleSrc const &style_src_req, SPIBase const *const base) const +{ + // flags SP_STYLE_FLAG_IFSET and SP_STYLE_FLAG_IFDIFF are ignored, their + // information is redundant FIXME remove those flags + + // pointer equality handled in SPStyle::write, not expected here + assert(base != this); + + if ((flags & SP_STYLE_FLAG_ALWAYS)) { + assert(!(flags & SP_STYLE_FLAG_IFSRC)); + assert(!base); + return true; + } + + if (!set) { + return false; + } + + if ((flags & SP_STYLE_FLAG_IFSRC) && style_src_req != style_src) { + return false; + } + + if (base && inherits && *base == *this) { + return false; + } + + return true; +} + +/** + * Compile this style conditionally into a 'name: value' string suitable for css attrs. + * see shall_write for details on conditional flags. + * + * @param flags Conditions bitmask of `SP_STYLE_FLAG_*` bits + * @param style_src_req For `SP_STYLE_FLAG_IFSRC` + * @param base Parent node style for `SP_STYLE_FLAG_IFDIFF`, can be NULL + * + * @return completed css string or empty string if conditions not met. + */ +const Glib::ustring SPIBase::write(guint const flags, SPStyleSrc const &style_src_req, SPIBase const *const base) const +{ + if (shall_write(flags, style_src_req, base)) { + auto value = this->get_value(); + if ( !value.empty() ) { + return (name() + ":" + value + important_str() + ";"); + } + } + return Glib::ustring(""); +} + + +/** + * If str.endswith("!important") then assign stripped = str[:-10].rstrip() and return true. + * Otherwise, leave stripped unmodified and return false. + */ +static bool strip_important(gchar const *str, std::string &stripped) +{ + assert(str != nullptr); + + constexpr size_t N = 10; // strlen("!important") + auto pos = strlen(str); + + if (pos >= N && strncmp(str + pos - N, "!important", N) == 0) { + pos -= N; + + // strip whitespace from the right + while (pos > 0 && g_ascii_isspace(str[pos - 1])) { + --pos; + } + + stripped.assign(str, pos); + return true; + } + + return false; +} + +void SPIBase::readIfUnset(gchar const *str, SPStyleSrc source) +{ + if (!str) + return; + + if (source == SPStyleSrc::ATTRIBUTE && id() == SPAttr::D) { + return; + } + + bool has_important = false; + std::string stripped; + + // '!important' is invalid on attributes + if (source != SPStyleSrc::ATTRIBUTE) { + has_important = strip_important(str, stripped); + if (has_important) { + str = stripped.c_str(); + } + } + + if (!set || (has_important && !important)) { + read(str); // clears style_src + style_src = source; + if (set) { + if (has_important) { + important = true; + } + } + } +} + + +// SPIFloat ------------------------------------------------------------- + +void +SPIFloat::read( gchar const *str ) { + + if( !str ) return; + + if ( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else { + gfloat value_tmp; + if (sp_svg_number_read_f(str, &value_tmp)) { + set = true; + inherit = false; + value = value_tmp; + } + } +} + +const Glib::ustring SPIFloat::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + return Glib::ustring::format(this->value); +} + +void +SPIFloat::cascade( const SPIBase* const parent ) { + if( const SPIFloat* p = dynamic_cast<const SPIFloat*>(parent) ) { + if( (inherits && !set) || inherit ) value = p->value; + } else { + std::cerr << "SPIFloat::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIFloat::merge( const SPIBase* const parent ) { + if( const SPIFloat* p = dynamic_cast<const SPIFloat*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + value = p->value; + } + } + } else { + std::cerr << "SPIFloat::merge(): Incorrect parent type" << std::endl; + } +} + +bool +SPIFloat::operator==(const SPIBase& rhs) const { + if( const SPIFloat* r = dynamic_cast<const SPIFloat*>(&rhs) ) { + return (value == r->value && SPIBase::operator==(rhs)); + } else { + return false; + } +} + + + +// SPIScale24 ----------------------------------------------------------- + +void +SPIScale24::read( gchar const *str ) { + + if( !str ) return; + + if ( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else { + gfloat value_in; + if (sp_svg_number_read_f(str, &value_in)) { + set = true; + inherit = false; + value_in = CLAMP(value_in, 0.0, 1.0); + value = SP_SCALE24_FROM_FLOAT( value_in ); + } + } +} + +const Glib::ustring SPIScale24::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + return Glib::ustring::format(SP_SCALE24_TO_FLOAT(this->value)); +} + +void +SPIScale24::cascade( const SPIBase* const parent ) { + if( const SPIScale24* p = dynamic_cast<const SPIScale24*>(parent) ) { + if( (inherits && !set) || inherit ) value = p->value; + } else { + std::cerr << "SPIScale24::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIScale24::merge( const SPIBase* const parent ) { + if( const SPIScale24* p = dynamic_cast<const SPIScale24*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + value = p->value; + } + } else { + // Needed only for 'opacity' and 'stop-opacity' which do not inherit. See comment at bottom of file. + if (id() != SPAttr::OPACITY && id() != SPAttr::STOP_OPACITY) + std::cerr << "SPIScale24::merge: unhandled property: " << name() << std::endl; + if( !set || (!inherit && value == SP_SCALE24_MAX) ) { + value = p->value; + set = (value != SP_SCALE24_MAX); + } else { + if( inherit ) value = p->value; // Insures child is up-to-date + value = SP_SCALE24_MUL( value, p->value ); + inherit = (inherit && p->inherit && (p->value == 0 || p->value == SP_SCALE24_MAX) ); + set = (inherit || value < SP_SCALE24_MAX); + } + } + } else { + std::cerr << "SPIScale24::merge(): Incorrect parent type" << std::endl; + } +} + +bool +SPIScale24::operator==(const SPIBase& rhs) const { + if( const SPIScale24* r = dynamic_cast<const SPIScale24*>(&rhs) ) { + return (value == r->value && SPIBase::operator==(rhs)); + } else { + return false; + } +} + + + +// SPILength ------------------------------------------------------------ + +void +SPILength::read( gchar const *str ) { + + if( !str ) return; + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + unit = SP_CSS_UNIT_NONE; + value = computed = 0.0; + } else { + gdouble value_tmp; + gchar *e; + /** \todo fixme: Move this to standard place (Lauris) */ + value_tmp = g_ascii_strtod(str, &e); + if ( !std::isfinite(value_tmp) ) { // fix for bug lp:935157 + return; + } + if ((gchar const *) e != str) { + + value = value_tmp; + if (!*e) { + /* Userspace */ + unit = SP_CSS_UNIT_NONE; + computed = value; + } else if (!strcmp(e, "px")) { + /* Userspace */ + unit = SP_CSS_UNIT_PX; + computed = value; + } else if (!strcmp(e, "pt")) { + /* Userspace / DEVICESCALE */ + unit = SP_CSS_UNIT_PT; + computed = Inkscape::Util::Quantity::convert(value, "pt", "px"); + } else if (!strcmp(e, "pc")) { + unit = SP_CSS_UNIT_PC; + computed = Inkscape::Util::Quantity::convert(value, "pc", "px"); + } else if (!strcmp(e, "mm")) { + unit = SP_CSS_UNIT_MM; + computed = Inkscape::Util::Quantity::convert(value, "mm", "px"); + } else if (!strcmp(e, "cm")) { + unit = SP_CSS_UNIT_CM; + computed = Inkscape::Util::Quantity::convert(value, "cm", "px"); + } else if (!strcmp(e, "in")) { + unit = SP_CSS_UNIT_IN; + computed = Inkscape::Util::Quantity::convert(value, "in", "px"); + } else if (!strcmp(e, "em")) { + /* EM square */ + unit = SP_CSS_UNIT_EM; + if( style ) { + computed = value * style->font_size.computed; + } else { + computed = value * SPIFontSize::font_size_default; + } + } else if (!strcmp(e, "ex")) { + /* ex square */ + unit = SP_CSS_UNIT_EX; + if( style ) { + computed = value * style->font_size.computed * 0.5; // FIXME + } else { + computed = value * SPIFontSize::font_size_default * 0.5; + } + } else if (!strcmp(e, "%")) { + /* Percentage */ + unit = SP_CSS_UNIT_PERCENT; + value = value * 0.01; + if (id() == SPAttr::LINE_HEIGHT) { + // See: http://www.w3.org/TR/CSS2/visudet.html#propdef-line-height + if( style ) { + computed = value * style->font_size.computed; + } else { + computed = value * SPIFontSize::font_size_default; + } + } + } else { + /* Invalid */ + return; + } + set = true; + inherit = false; + } + } +} + +const Glib::ustring SPILength::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + auto value = this->computed; + auto unit_out = Glib::ustring(""); + switch (this->unit) { + case SP_CSS_UNIT_NONE: + break; + case SP_CSS_UNIT_PX: + unit_out = "px"; + break; + case SP_CSS_UNIT_PT: + case SP_CSS_UNIT_PC: + case SP_CSS_UNIT_MM: + case SP_CSS_UNIT_CM: + case SP_CSS_UNIT_IN: + unit_out = sp_style_get_css_unit_string(this->unit); + value = Inkscape::Util::Quantity::convert(this->computed, "px", unit_out); + break; + case SP_CSS_UNIT_EM: + case SP_CSS_UNIT_EX: + unit_out = sp_style_get_css_unit_string(this->unit); + value = this->value; + break; + case SP_CSS_UNIT_PERCENT: + unit_out = "%"; + value = this->value * 100.0; + break; + default: + /* Invalid */ + break; + } + return Glib::ustring::format(value) + unit_out; +} + +void +SPILength::cascade( const SPIBase* const parent ) { + if( const SPILength* p = dynamic_cast<const SPILength*>(parent) ) { + if( (inherits && !set) || inherit ) { + unit = p->unit; + value = p->value; + computed = p->computed; + } else { + // Recalculate based on new font-size, font-family inherited from parent + double const em = style->font_size.computed; + if (unit == SP_CSS_UNIT_EM) { + computed = value * em; + } else if (unit == SP_CSS_UNIT_EX) { + // FIXME: Get x height from libnrtype or pango. + computed = value * em * 0.5; + } else if (unit == SP_CSS_UNIT_PERCENT && id() == SPAttr::LINE_HEIGHT) { + // Special case + computed = value * em; + } + } + } else { + std::cerr << "SPILength::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPILength::merge( const SPIBase* const parent ) { + if( const SPILength* p = dynamic_cast<const SPILength*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + unit = p->unit; + value = p->value; + computed = p->computed; + + // Fix up so values are correct + switch (p->unit) { + case SP_CSS_UNIT_EM: + case SP_CSS_UNIT_EX: + value *= p->style->font_size.computed / style->font_size.computed; + /** \todo + * FIXME: Have separate ex ratio parameter. + * Get x height from libnrtype or pango. + */ + if (!std::isfinite(value)) { + value = computed; + unit = SP_CSS_UNIT_NONE; + } + break; + + default: + break; + } + } + } + } else { + std::cerr << "SPIFloat::merge(): Incorrect parent type" << std::endl; + } +} + +void SPILength::setDouble(double v) +{ + unit = SP_CSS_UNIT_NONE; + value = v; + computed = v; + value_default = v; +} + +// Generate a string and allow emove name for parsing dasharray, etc. +const Glib::ustring SPILength::toString(bool wname) const +{ + CSSOStringStream os; + if (wname) { + os << name() << ":"; + } + os << this->get_value(); + if (wname) { + os << important_str(); + os << ";"; + } + return os.str(); +} + +bool +SPILength::operator==(const SPIBase& rhs) const { + if( const SPILength* r = dynamic_cast<const SPILength*>(&rhs) ) { + + if( unit != r->unit ) return false; + + // If length depends on external parameter, lengths cannot be equal. + if (unit == SP_CSS_UNIT_EM) return false; + if (unit == SP_CSS_UNIT_EX) return false; + if (unit == SP_CSS_UNIT_PERCENT) return false; + if (r->unit == SP_CSS_UNIT_EM) return false; + if (r->unit == SP_CSS_UNIT_EX) return false; + if (r->unit == SP_CSS_UNIT_PERCENT) return false; + + return (computed == r->computed ); + } else { + return false; + } +} + +// SPILengthOrNormal ---------------------------------------------------- + +void +SPILengthOrNormal::read( gchar const *str ) { + + if( !str ) return; + + if ( !strcmp(str, "normal") ) { + set = true; + inherit = false; + unit = SP_CSS_UNIT_NONE; + value = computed = 0.0; + normal = true; + } else { + SPILength::read( str ); + normal = false; + } +}; + +const Glib::ustring SPILengthOrNormal::get_value() const +{ + if (this->normal) return Glib::ustring("normal"); + return SPILength::get_value(); +} + +void +SPILengthOrNormal::cascade( const SPIBase* const parent ) { + if( const SPILengthOrNormal* p = dynamic_cast<const SPILengthOrNormal*>(parent) ) { + if( (inherits && !set) || inherit ) { + normal = p->normal; + } + SPILength::cascade( parent ); + } else { + std::cerr << "SPILengthOrNormal::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPILengthOrNormal::merge( const SPIBase* const parent ) { + if( const SPILengthOrNormal* p = dynamic_cast<const SPILengthOrNormal*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + normal = p->normal; + SPILength::merge( parent ); + } + } + } +} + +bool +SPILengthOrNormal::operator==(const SPIBase& rhs) const { + if( const SPILengthOrNormal* r = dynamic_cast<const SPILengthOrNormal*>(&rhs) ) { + if( normal && r->normal ) { return true; } + if( normal != r->normal ) { return false; } + return SPILength::operator==(rhs); + } else { + return false; + } +} + + +// SPIFontVariationSettings ---------------------------------------------------- + +void +SPIFontVariationSettings::read( gchar const *str ) { + + if( !str ) return; + + if ( !strcmp(str, "normal") ) { + set = true; + inherit = false; + normal = true; + axes.clear(); + return; + } + + + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", str); + + // Match a pattern of a CSS <string> of length 4, whitespace, CSS <number>. + // (CSS string is quoted with double quotes). + + // Matching must use a Glib::ustring or matching may produce + // subtle errors which may be shown by an "Invalid byte sequence + // in conversion input" error. + Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create( + "[\"'](\\w{4})[\"']" + "\\s+([-+]?\\d*\\.?\\d+([eE][-+]?\\d+)?)"); + Glib::MatchInfo matchInfo; + + for (auto token: tokens) { + regex->match(token, matchInfo); + if (matchInfo.matches()) { + float value = std::stod(matchInfo.fetch(2)); + axes.insert(std::pair<Glib::ustring,float>(matchInfo.fetch(1), value)); + } + } + + if (!axes.empty()) { + set = true; + inherit = false; + normal = false; + } +}; + +const Glib::ustring SPIFontVariationSettings::get_value() const +{ + if (this->normal) return Glib::ustring("normal"); + auto ret = Glib::ustring(""); + for(auto it: axes) { + ret += "'" + it.first + "' " + Glib::ustring::format(it.second) + ", "; + } + if (!ret.empty()) ret.erase(ret.size() - 2); + return ret; +} + +void +SPIFontVariationSettings::cascade( const SPIBase* const parent ) { + if( const SPIFontVariationSettings* p = dynamic_cast<const SPIFontVariationSettings*>(parent) ) { + if( !set || inherit ) { // Always inherits + normal = p->normal; + axes.clear(); + axes = p->axes; + } + } else { + std::cerr << "SPIFontVariationSettings::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIFontVariationSettings::merge( const SPIBase* const parent ) { + if( const SPIFontVariationSettings* p = dynamic_cast<const SPIFontVariationSettings*>(parent) ) { + // if( inherits ) { 'font-variation-settings' always inherits. + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + normal = p->normal; + axes = p->axes; + } + } +} + +bool +SPIFontVariationSettings::operator==(const SPIBase& rhs) const { + if( const SPIFontVariationSettings* r = dynamic_cast<const SPIFontVariationSettings*>(&rhs) ) { + if( normal && r->normal ) { return true; } + if( normal != r->normal ) { return false; } + return axes == r->axes; + } else { + return false; + } +} + +// Generate a string useful for passing to Pango, etc. +const Glib::ustring +SPIFontVariationSettings::toString() const { + + Inkscape::CSSOStringStream os; + for (const auto & axe : axes){ + os << axe.first << "=" << axe.second << ","; + } + + std::string string = os.str(); // Glib::ustring doesn't have pop_back() + if (!string.empty()) { + string.pop_back(); // Delete extra comma at end + } + + return string; +} + +// Helpers for SPIEnum ----------------------------------------------------- + +// The default exists to satisfy linking of derived classes but must never be called +template <typename T> static SPStyleEnum const *get_enums() { g_assert_not_reached(); return nullptr; } + +template <> SPStyleEnum const *get_enums<SPBlendMode>() { return enum_blend_mode; } +template <> SPStyleEnum const *get_enums<SPColorInterpolation>() { return enum_color_interpolation; } +template <> SPStyleEnum const *get_enums<SPColorRendering>() { return enum_color_rendering; } +template <> SPStyleEnum const *get_enums<SPCSSBaseline>() { return enum_baseline; } +template <> SPStyleEnum const *get_enums<SPCSSDirection>() { return enum_direction; } +template <> SPStyleEnum const *get_enums<SPCSSDisplay>() { return enum_display; } +template <> SPStyleEnum const *get_enums<SPCSSFontVariantAlternates>() { return enum_font_variant_alternates; } +template <> SPStyleEnum const *get_enums<SPCSSTextAlign>() { return enum_text_align; } +template <> SPStyleEnum const *get_enums<SPCSSTextOrientation>() { return enum_text_orientation; } +template <> SPStyleEnum const *get_enums<SPCSSTextTransform>() { return enum_text_transform; } +template <> SPStyleEnum const *get_enums<SPCSSWritingMode>() { return enum_writing_mode; } +template <> SPStyleEnum const *get_enums<SPEnableBackground>() { return enum_enable_background; } +template <> SPStyleEnum const *get_enums<SPImageRendering>() { return enum_image_rendering; } +template <> SPStyleEnum const *get_enums<SPIsolation>() { return enum_isolation; } +template <> SPStyleEnum const *get_enums<SPOverflow>() { return enum_overflow; } +template <> SPStyleEnum const *get_enums<SPShapeRendering>() { return enum_shape_rendering; } +template <> SPStyleEnum const *get_enums<SPStrokeCapType>() { return enum_stroke_linecap; } +template <> SPStyleEnum const *get_enums<SPStrokeJoinType>() { return enum_stroke_linejoin; } +template <> SPStyleEnum const *get_enums<SPTextAnchor>() { return enum_text_anchor; } +template <> SPStyleEnum const *get_enums<SPTextRendering>() { return enum_text_rendering; } +template <> SPStyleEnum const *get_enums<SPVisibility>() { return enum_visibility; } +template <> SPStyleEnum const *get_enums<SPWhiteSpace>() { return enum_white_space; } +template <> SPStyleEnum const *get_enums<SPWindRule>() { return enum_clip_rule; } +template <> SPStyleEnum const *get_enums<SPCSSFontStyle>() { return enum_font_style; } +template <> SPStyleEnum const *get_enums<SPCSSFontVariant>() { return enum_font_variant; } +template <> SPStyleEnum const *get_enums<SPCSSFontWeight>() { return enum_font_weight; } +template <> SPStyleEnum const *get_enums<SPCSSFontStretch>() { return enum_font_stretch; } +template <> SPStyleEnum const *get_enums<SPCSSFontVariantPosition>() { return enum_font_variant_position; } +template <> SPStyleEnum const *get_enums<SPCSSFontVariantCaps>() { return enum_font_variant_caps; } + +// SPIEnum -------------------------------------------------------------- + +template <typename T> +void SPIEnum<T>::update_computed() +{ + computed = value; +} + +template <> +void SPIEnum<SPCSSFontWeight>::update_computed() +{ + // The following is defined in CSS 2.1 + if (value == SP_CSS_FONT_WEIGHT_NORMAL) { + computed = SP_CSS_FONT_WEIGHT_400; + } else if (value == SP_CSS_FONT_WEIGHT_BOLD) { + computed = SP_CSS_FONT_WEIGHT_700; + } else { + computed = value; + } +} + +template <typename T> +void SPIEnum<T>::read(gchar const *str) +{ + + if( !str ) return; + + if( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else { + auto const *enums = get_enums<T>(); + for (unsigned i = 0; enums[i].key; i++) { + if (!strcmp(str, enums[i].key)) { + set = true; + inherit = false; + value = static_cast<T>(enums[i].value); + /* Save copying for values not needing it */ + break; + } + } + + // type-specialized subroutine + update_computed(); + } +} + +template <typename T> +const Glib::ustring SPIEnum<T>::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + auto const *enums = get_enums<T>(); + for (unsigned i = 0; enums[i].key; ++i) { + if (enums[i].value == static_cast< gint > (this->value) ) { + return Glib::ustring(enums[i].key); + } + } + return Glib::ustring(""); +} + +template <> +void SPIEnum<SPCSSFontWeight>::update_computed_cascade(SPCSSFontWeight const &p_computed) +{ + // strictly, 'bolder' and 'lighter' should go to the next weight + // expressible in the current font family, but that's difficult to + // find out, so jumping by 3 seems an appropriate approximation + if (value == SP_CSS_FONT_WEIGHT_LIGHTER) { + computed = static_cast<SPCSSFontWeight>(std::max<int>(SP_CSS_FONT_WEIGHT_100, int(p_computed) - 3)); + } else if (value == SP_CSS_FONT_WEIGHT_BOLDER) { + computed = static_cast<SPCSSFontWeight>(std::min<int>(SP_CSS_FONT_WEIGHT_900, p_computed + 3)); + } +} + +template <> +void SPIEnum<SPCSSFontStretch>::update_computed_cascade(SPCSSFontStretch const &p_computed) +{ + if (value == SP_CSS_FONT_STRETCH_NARROWER) { + computed = + static_cast<SPCSSFontStretch>(std::max<int>(SP_CSS_FONT_STRETCH_ULTRA_CONDENSED, int(p_computed) - 1)); + } else if (value == SP_CSS_FONT_STRETCH_WIDER) { + computed = static_cast<SPCSSFontStretch>(std::min<int>(SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, p_computed + 1)); + } +} + +template <typename T> +void SPIEnum<T>::cascade(const SPIBase *const parent) +{ + if (const auto *p = dynamic_cast<const SPIEnum<T> *>(parent)) { + if( inherits && (!set || inherit) ) { + computed = p->computed; + } else { + // type-specialized subroutine + update_computed_cascade(p->computed); + } + } else { + std::cerr << "SPIEnum<T>::cascade(): Incorrect parent type" << std::endl; + } +} + +// FIXME Handle font_stretch and font_weight (relative values) New derived class? +template <typename T> +void SPIEnum<T>::update_value_merge(SPIEnum<T> const &p, T smaller, T larger) +{ + g_assert(set); + + if (value == p.value) { + // Leave as is, what does applying "wider" twice do? + } else if ((value == smaller && p.value == larger) || // + (value == larger && p.value == smaller)) { + // Values cancel, unset + set = false; + } else if (value == smaller || value == larger) { + value = computed; + inherit = false; + } +} + +template <> +void SPIEnum<SPCSSFontWeight>::update_value_merge(SPIEnum<SPCSSFontWeight> const &p) +{ + update_value_merge(p, SP_CSS_FONT_WEIGHT_LIGHTER, SP_CSS_FONT_WEIGHT_BOLDER); +} + +template <> +void SPIEnum<SPCSSFontStretch>::update_value_merge(SPIEnum<SPCSSFontStretch> const &p) +{ + update_value_merge(p, SP_CSS_FONT_STRETCH_NARROWER, SP_CSS_FONT_STRETCH_WIDER); +} + +template <typename T> +void SPIEnum<T>::merge(const SPIBase *const parent) +{ + if (const auto *p = dynamic_cast<const SPIEnum<T> *>(parent)) { + if( inherits ) { + if( p->set && !p->inherit ) { + if( !set || inherit ) { + set = p->set; + inherit = p->inherit; + value = p->value; + computed = p->computed; // Different from value for font-weight and font-stretch + } else { + // type-specialized subroutine + update_value_merge(*p); + } + } + } + } +} + +template <typename T> +bool SPIEnum<T>::operator==(const SPIBase &rhs) const +{ + if (auto *r = dynamic_cast<const SPIEnum<T> *>(&rhs)) { + return (computed == r->computed && SPIBase::operator==(rhs)); + } else { + return false; + } +} + + +#if 0 +// SPIEnumBits ---------------------------------------------------------- +// Used for 'font-variant-xxx' +void +SPIEnumBits::read( gchar const *str ) { + + if( !str ) return; + if( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else { + for (unsigned i = 0; enums[i].key; i++) { + if (!strcmp(str, enums[i].key)) { + set = true; + inherit = false; + value |= enums[i].value; + } + } + /* Save copying for values not needing it */ + computed = value; + } +} + +const Glib::ustring SPIEnumBits::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->value == 0) return Glib::ustring("normal"); + auto ret = Glib::ustring(""); + for (unsigned i = 0; enums[i].key; ++i) { + if (this->value & enums[i].value) { + if (!ret.empty()) ret += " "; + ret += enums[i].key; + } + } + return ret; +} +#endif + +// SPILigatures ----------------------------------------------------- +// Used for 'font-variant-ligatures' +void +SPILigatures::read( gchar const *str ) { + + if( !str ) return; + + value = SP_CSS_FONT_VARIANT_LIGATURES_NORMAL; + if( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else if (!strcmp(str, "normal" )) { + // Defaults for TrueType + inherit = false; + set = true; + } else if (!strcmp(str, "none" )) { + value = SP_CSS_FONT_VARIANT_LIGATURES_NONE; + inherit = false; + set = true; + } else { + // We need to parse in order + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", str ); + auto const *enums = enum_font_variant_ligatures; + for(auto & token : tokens) { + for (unsigned j = 0; enums[j].key; ++j ) { + if (token.compare( enums[j].key ) == 0 ) { + set = true; + inherit = false; + if( enums[j].value < SP_CSS_FONT_VARIANT_LIGATURES_NOCOMMON ) { + // Turn on + value |= enums[j].value; + } else { + // Turn off + value &= ~(enums[j].value >> 4); + } + } + } + } + } + computed = value; +} + +/* FIXME:: This whole class is bogus and should be an SPIBitEnum TODO */ + +const Glib::ustring SPILigatures::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->value == SP_CSS_FONT_VARIANT_LIGATURES_NONE) return Glib::ustring("none"); + if (this->value == SP_CSS_FONT_VARIANT_LIGATURES_NORMAL) return Glib::ustring("normal"); + auto ret = Glib::ustring(""); + if (!(value & SP_CSS_FONT_VARIANT_LIGATURES_COMMON)) + ret += "no-common-ligatures "; + if (value & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY) + ret += "discretionary-ligatures "; + if (value & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL ) + ret += "historical-ligatures "; + if ( !(value & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL) ) + ret += "no-contextual "; + ret.erase(ret.size() - 1); + return ret; +} + +// SPINumeric ----------------------------------------------------- +// Used for 'font-variant-numeric' +void +SPINumeric::read( gchar const *str ) { + + if( !str ) return; + + value = SP_CSS_FONT_VARIANT_NUMERIC_NORMAL; + if( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else if (!strcmp(str, "normal" )) { + // Defaults for TrueType + inherit = false; + set = true; + } else { + // We need to parse in order + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", str ); + auto const *enums = enum_font_variant_numeric; + for(auto & token : tokens) { + for (unsigned j = 0; enums[j].key; ++j ) { + if (token.compare( enums[j].key ) == 0 ) { + set = true; + inherit = false; + value |= enums[j].value; + + // Must switch off incompatible value + switch (enums[j].value ) { + case SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS: + value &= ~SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS; + break; + case SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS: + value &= ~SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS; + break; + + case SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS: + value &= ~SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS; + break; + case SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS: + value &= ~SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS; + break; + + case SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS: + value &= ~SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS; + break; + case SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS: + value &= ~SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS; + break; + + case SP_CSS_FONT_VARIANT_NUMERIC_NORMAL: + case SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL: + case SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO: + // Do nothing + break; + + default: + std::cerr << "SPINumeric::read(): Invalid value." << std::endl; + break; + } + } + } + } + } + computed = value; +} + +/* FIXME:: This whole class is bogus and should be an SPIBitEnum TODO */ +const Glib::ustring SPINumeric::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->value == 0) return Glib::ustring("normal"); + auto ret = Glib::ustring(""); + auto enums = enum_font_variant_numeric; + for (unsigned i = 1; enums[i].key; ++i) { + // Bitmap is shifted by 1 because normal is zero + if (this->value & (1 << (i - 1))) { + if (!ret.empty()) ret += " "; + ret += enums[i].key; + } + } + return ret; +} + +// SPIEastAsian --------------------------------------------------- +// Used for 'font-variant-east-asian' +void +SPIEastAsian::read( gchar const *str ) { + + if( !str ) return; + + value = SP_CSS_FONT_VARIANT_EAST_ASIAN_NORMAL; + if( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else if (!strcmp(str, "normal" )) { + // Defaults for TrueType + inherit = false; + set = true; + } else { + // We need to parse in order + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", str ); + auto const *enums = enum_font_variant_east_asian; + for(auto & token : tokens) { + for (unsigned j = 0; enums[j].key; ++j ) { + if (token.compare( enums[j].key ) == 0 ) { + set = true; + inherit = false; + + // Must switch off incompatible value (turn on correct one below) + switch (enums[j].value ) { + case SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78: + case SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83: + case SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90: + case SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04: + case SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED: + case SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL: + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78; + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83; + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90; + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04; + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED; + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL; + break; + + case SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH: + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH; + break; + case SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH: + value &= ~SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH; + break; + + case SP_CSS_FONT_VARIANT_EAST_ASIAN_NORMAL: + case SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY: + // Do nothing + break; + + default: + std::cerr << "SPIEastasian::read(): Invalid value." << std::endl; + break; + } + + value |= enums[j].value; + } + } + } + } + computed = value; +} + +const Glib::ustring SPIEastAsian::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->value == 0) return Glib::ustring("normal"); + auto ret = Glib::ustring(""); + auto enums = enum_font_variant_east_asian; + for (unsigned i = 0; enums[i].key; ++i) { + if (this->value & (1 << i)) { + if (!ret.empty()) ret += " "; + ret += enums[i].key; + } + } + return ret; +} + +// SPIString ------------------------------------------------------------ + +void +SPIString::read( gchar const *str ) { + + if( !str ) return; + + clear(); + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + } else if (!g_strcmp0(str, get_default_value())) { + // no need to copy string + set = true; + } else { + Glib::ustring str_temp; + + if (id() == SPAttr::FONT_FAMILY) { + // Family names may be quoted in CSS, internally we use unquoted names. + str_temp = str; + css_font_family_unquote( str_temp ); + str = str_temp.c_str(); + } else if (id() == SPAttr::INKSCAPE_FONT_SPEC) { + str_temp = str; + css_unquote( str_temp ); + str = str_temp.c_str(); + } + + set = true; + _value = g_strdup(str); + } +} + + +/** + * Value as it should be written to CSS representation, including quotes if needed. + */ +const Glib::ustring SPIString::get_value() const +{ + Glib::ustring val; + + if (set && inherit) { + val = "inherit"; + } else if (auto *v = value()) { + val = v; + + if (id() == SPAttr::FONT_FAMILY) { + css_font_family_quote(val); + } else if (id() == SPAttr::INKSCAPE_FONT_SPEC) { + css_quote(val); + } + } + + return val; +} + +char const *SPIString::value() const +{ + return _value ? _value : get_default_value(); +} + +char const *SPIString::get_default_value() const +{ + switch (id()) { + case SPAttr::FONT_FAMILY: + return "sans-serif"; + case SPAttr::FONT_FEATURE_SETTINGS: + return "normal"; + default: + return nullptr; + } +} + + +void +SPIString::clear() { + SPIBase::clear(); + g_free(_value); + _value = nullptr; +} + +void +SPIString::cascade( const SPIBase* const parent ) { + if( const SPIString* p = dynamic_cast<const SPIString*>(parent) ) { + if( inherits && (!set || inherit) ) { + g_free(_value); + _value = g_strdup(p->_value); + } + } else { + std::cerr << "SPIString::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIString::merge( const SPIBase* const parent ) { + if( const SPIString* p = dynamic_cast<const SPIString*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + g_free(_value); + _value = g_strdup(p->_value); + } + } + } +} + +bool +SPIString::operator==(const SPIBase& rhs) const { + if( const SPIString* r = dynamic_cast<const SPIString*>(&rhs) ) { + return g_strcmp0(_value, r->_value) == 0 && SPIBase::operator==(rhs); + } else { + return false; + } +} + + +// SPIShapes ------------------------------------------------------------ + +SPIShapes::~SPIShapes() { + hrefs_clear(); +} + +SPIShapes::SPIShapes() + : SPIString(false) +{ +} + +/** + * Check if a Shape object exists in the selection + * Needed for when user selects multiple objects and + * * transforms them all simultaneously + * ie. Flowed text and a Rectangle (See issue 1029) + * + * \param the ObjectSet to search + * \return true if found, else false + */ +bool SPIShapes::containsAnyShape(Inkscape::ObjectSet *set) { + for (auto ref : hrefs) { + if (set->includes(ref->getObject())) { + return true; + } + } + + return false; +} + +// Used to add/remove listeners for text wrapped in shapes. +// Note: this is done differently than for patterns, etc. where presentation attributes can be used. +// 'shape-inside' and 'shape-subtract' are only properties. +void +SPIShapes::read( gchar const *str) { + + if (!style) { + std::cerr << "SPIShapes::read: no style!" << std::endl; + return; + } + + if( !str ) return; + + SPIString::read(str); + assert(set); + + // The object/repr this property is connected to.. + SPObject* object = style->object; + if (!object) { + std::cout << " No object" << std::endl; + return; + } + + // clear(); // Already cleared! (In SPStyle::read.) Calling again causes segfault. + + // Add new listeners + std::vector<Glib::ustring> shapes_url = Glib::Regex::split_simple(" ", str); + for (auto shape_url : shapes_url) { + if ( shape_url.compare(0,5,"url(#") != 0 || shape_url.compare(shape_url.size()-1,1,")") != 0 ){ + std::cerr << "SPIShapes::read: Invalid shape value: " << shape_url << std::endl; + } else { + auto uri = extract_uri(shape_url.c_str()); // Do before we erase "url(#" + + // This ups the href count of the shape. This is required so that vacuuming a + // document does not delete shapes stored in <defs>. + SPShapeReference *href = new SPShapeReference(object); + + if (href->try_attach(uri.c_str())) { + hrefs.emplace_back(href); + } else { + delete href; + } + } + } +} + +void +SPIShapes::clear() { + + SPIBase::clear(); + + hrefs_clear(); +} + +void SPIShapes::hrefs_clear() +{ + for (auto href : hrefs) { + delete href; + } + hrefs.clear(); +} + +// SPIColor ------------------------------------------------------------- + +// Used for 'color', 'text-decoration-color', 'flood-color', 'lighting-color', and 'stop-color'. +// (The last three have yet to be implemented.) +// CSS3: 'currentcolor' is allowed value and is equal to inherit for the 'color' property. +// FIXME: We should preserve named colors, hsl colors, etc. +void SPIColor::read( gchar const *str ) { + + if( !str ) return; + + set = false; + inherit = false; + currentcolor = false; + if ( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else if ( !strcmp(str, "currentColor") ) { + set = true; + currentcolor = true; + if (id() == SPAttr::COLOR) { + inherit = true; // CSS3 + } else if (style) { + setColor( style->color.value.color ); + } else { + std::cerr << "SPIColor::read(): value is 'currentColor' but 'color' not available." << std::endl; + } + } else { + guint32 const rgb0 = sp_svg_read_color(str, 0xff); + if (rgb0 != 0xff) { + setColor(rgb0); + set = true; + } + } +} + +const Glib::ustring SPIColor::get_value() const +{ + // currentcolor goes first to handle special case for 'color' property + if (this->currentcolor) return Glib::ustring("currentColor"); + if (this->inherit) return Glib::ustring("inherit"); + return this->value.color.toString(); +} + +void +SPIColor::cascade( const SPIBase* const parent ) { + if( const SPIColor* p = dynamic_cast<const SPIColor*>(parent) ) { + if( (inherits && !set) || inherit) { // FIXME verify for 'color' + if( !(inherit && currentcolor) ) currentcolor = p->currentcolor; + setColor( p->value.color ); + } else { + // Add CSS4 Color: Lighter, Darker + } + } else { + std::cerr << "SPIColor::cascade(): Incorrect parent type" << std::endl; + } + +} + +void +SPIColor::merge( const SPIBase* const parent ) { + if( const SPIColor* p = dynamic_cast<const SPIColor*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + currentcolor = p->currentcolor; + value.color = p->value.color; + } + } + } +} + +bool +SPIColor::operator==(const SPIBase& rhs) const { + if( const SPIColor* r = dynamic_cast<const SPIColor*>(&rhs) ) { + + if ( (this->currentcolor != r->currentcolor ) || + (this->value.color != r->value.color ) || + (this->value.color.icc != r->value.color.icc ) || + (this->value.color.icc && r->value.color.icc && + this->value.color.icc->colorProfile != r->value.color.icc->colorProfile && + this->value.color.icc->colors != r->value.color.icc->colors ) ) { + return false; + } + + return SPIBase::operator==(rhs); + + } else { + return false; + } +} + + + +// SPIPaint ------------------------------------------------------------- + +// Paint is used for 'fill' and 'stroke'. SPIPaint perhaps should be derived from SPIColor. +// 'style' is set in SPStyle::SPStyle or in the legacy SPIPaint::read( gchar, style, document ) +// It is needed for computed value when value is 'currentColor'. It is also needed to +// find the object for creating an href (this is done through document but should be done +// directly so document not needed.. FIXME). + +SPIPaint::~SPIPaint() { + if( value.href ) { + clear(); + delete value.href; + value.href = nullptr; + } +} + +/** + * Set SPIPaint object from string. + * + * \pre paint == \&style.fill || paint == \&style.stroke. + */ +void +SPIPaint::read( gchar const *str ) { + + // std::cout << "SPIPaint::read: Entrance: " << " |" << (str?str:"null") << "|" << std::endl; + // if( style ) { + // std::cout << " document: " << (void*)style->document << std::endl; + // std::cout << " object: " << (style->object?"present":"null") << std::endl; + // if( style->object ) + // std::cout << " : " << (style->object->getId()?style->object->getId():"no ID") + // << " document: " << (style->object->document?"yes":"no") << std::endl; + // } + + if(!str ) return; + + reset( false ); // Do not init + + // Is this necessary? + while (g_ascii_isspace(*str)) { + ++str; + } + + if (streq(str, "inherit")) { + set = true; + inherit = true; + } else { + // Read any URL first. The other values can be stand-alone or backup to the URL. + + if ( strneq(str, "url", 3) ) { + + // FIXME: THE FOLLOWING CODE SHOULD BE PUT IN A PRIVATE FUNCTION FOR REUSE + auto uri = extract_uri(str, &str); // std::string + if(uri.empty()) { + std::cerr << "SPIPaint::read: url is empty or invalid" << std::endl; + } else if (!style ) { + std::cerr << "SPIPaint::read: url with empty SPStyle pointer" << std::endl; + } else { + set = true; + SPDocument *document = (style->object) ? style->object->document : nullptr; + + // Create href if not done already + if (!value.href) { + + if (style->object) { + value.href = new SPPaintServerReference(style->object); + } else if (document) { + value.href = new SPPaintServerReference(document); + } else { + std::cerr << "SPIPaint::read: No valid object or document!" << std::endl; + return; + } + + if (this == &style->fill) { + style->fill_ps_changed_connection = value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style)); + } else { + style->stroke_ps_changed_connection = value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style)); + } + } + + // TODO check what this does in light of move away from union + sp_style_set_ipaint_to_uri_string(style, this, uri.c_str()); + } + } + + while ( g_ascii_isspace(*str) ) { + ++str; + } + + if (streq(str, "currentColor")) { + set = true; + paintOrigin = SP_CSS_PAINT_ORIGIN_CURRENT_COLOR; + if (style) { + setColor( style->color.value.color ); + } else { + // Normally an SPIPaint is part of an SPStyle and the value of 'color' is + // available. SPIPaint can be used 'stand-alone' (e.g. to parse color values) in + // which case a value of 'currentColor' is meaningless, thus we shouldn't reach + // here. + std::cerr << "SPIPaint::read(): value is 'currentColor' but 'color' not available." << std::endl; + setColor( 0 ); + } + } else if (streq(str, "context-fill")) { + set = true; + paintOrigin = SP_CSS_PAINT_ORIGIN_CONTEXT_FILL; + } else if (streq(str, "context-stroke")) { + set = true; + paintOrigin = SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE; + } else if (streq(str, "none")) { + set = true; + noneSet = true; + } else { + guint32 const rgb0 = sp_svg_read_color(str, &str, 0xff); + if (rgb0 != 0xff) { + setColor( rgb0 ); + set = true; + + while (g_ascii_isspace(*str)) { + ++str; + } + if (strneq(str, "icc-color(", 10)) { + SVGICCColor* tmp = new SVGICCColor(); + if ( ! sp_svg_read_icc_color( str, &str, tmp ) ) { + delete tmp; + tmp = nullptr; + } + value.color.icc = tmp; + } + } + } + } +} + +// Stand-alone read (Legacy read()), used multiple places, e.g. sp-stop.cpp +// This function should not be necessary. FIXME +void +SPIPaint::read( gchar const *str, SPStyle &style_in, SPDocument *document_in ) { + style = &style_in; + style->document = document_in; + read( str ); +} + +const Glib::ustring SPIPaint::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->noneSet) return Glib::ustring("none"); + // url must go first as other values can serve as fallbacks + auto ret = Glib::ustring(""); + if (this->value.href && this->value.href->getURI()) { + ret += this->value.href->getURI()->cssStr(); + } + switch(this->paintOrigin) { + case SP_CSS_PAINT_ORIGIN_CURRENT_COLOR: + if (!ret.empty()) ret += " "; + ret += "currentColor"; + break; + case SP_CSS_PAINT_ORIGIN_CONTEXT_FILL: + if (!ret.empty()) ret += " "; + ret += "context-fill"; + break; + case SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE: + if (!ret.empty()) ret += " "; + ret += "context-stroke"; + break; + case SP_CSS_PAINT_ORIGIN_NORMAL: + if (this->colorSet) { + char color_buf[8]; + sp_svg_write_color(color_buf, sizeof(color_buf), this->value.color.toRGBA32(0)); + if (!ret.empty()) ret += " "; + ret += color_buf; + } + if (this->value.color.icc) { + ret += " icc-color("; + ret += this->value.color.icc->colorProfile; + for(auto i: this->value.color.icc->colors) { + ret += ", " + Glib::ustring::format(i); + } + ret += ")"; + } + break; + } + return ret; +} + +void +SPIPaint::clear() { + // std::cout << "SPIPaint::clear(): " << name << std::endl; + reset( true ); // Reset and Init +} + +void +SPIPaint::reset( bool init ) { + + // std::cout << "SPIPaint::reset(): " << name << " " << init << std::endl; + SPIBase::clear(); + paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL; + colorSet = false; + noneSet = false; + value.color.set( false ); + tag = nullptr; + if (value.href){ + if (value.href->getObject()) { + value.href->detach(); + } + } + if( init ) { + if (id() == SPAttr::FILL) { + // 'black' is default for 'fill' + setColor(0.0, 0.0, 0.0); + } else if (id() == SPAttr::TEXT_DECORATION_COLOR) { + // currentcolor = true; + } + } +} + +void +SPIPaint::cascade( const SPIBase* const parent ) { + + // std::cout << "SPIPaint::cascade" << std::endl; + if( const SPIPaint* p = dynamic_cast<const SPIPaint*>(parent) ) { + if (!set || inherit) { // Always inherits + + reset( false ); // Do not init + + if( p->isPaintserver() ) { + if( p->value.href) { + // Why can we use p->document ? + sp_style_set_ipaint_to_uri( style, this, p->value.href->getURI(), p->value.href->getOwnerDocument()); + } else { + std::cerr << "SPIPaint::cascade: Expected paint server not found." << std::endl; + } + } else if( p->isColor() ) { + setColor( p->value.color ); + } else if( p->isNoneSet() ) { + noneSet = true; + } else if( p->paintOrigin == SP_CSS_PAINT_ORIGIN_CURRENT_COLOR ) { + paintOrigin = SP_CSS_PAINT_ORIGIN_CURRENT_COLOR; + setColor( style->color.value.color ); + } else if( isNone() ) { + // + } else { + g_assert_not_reached(); + } + } else { + if( paintOrigin == SP_CSS_PAINT_ORIGIN_CURRENT_COLOR ) { + // Update in case color value changed. + setColor( style->color.value.color ); + } + } + + } else { + std::cerr << "SPIPaint::cascade(): Incorrect parent type" << std::endl; + } + +} + +void +SPIPaint::merge( const SPIBase* const parent ) { + if( const SPIPaint* p = dynamic_cast<const SPIPaint*>(parent) ) { + // if( inherits ) { Paint always inherits + if( (!set || inherit) && p->set && !(p->inherit) ) { + this->cascade( parent ); // Must call before setting 'set' + set = p->set; + inherit = p->inherit; + } + } +} + +bool +SPIPaint::operator==(const SPIBase& rhs) const { + + if( const SPIPaint* r = dynamic_cast<const SPIPaint*>(&rhs) ) { + + if ( (this->isColor() != r->isColor() ) || + (this->isPaintserver() != r->isPaintserver() ) || + (this->paintOrigin != r->paintOrigin ) ) { + return false; + } + + if ( this->isPaintserver() ) { + if( this->value.href == nullptr || r->value.href == nullptr || + this->value.href->getObject() != r->value.href->getObject() ) { + return false; + } + } + + if ( this->isColor() ) { + if ( (this->value.color != r->value.color ) || + (this->value.color.icc != r->value.color.icc ) || + (this->value.color.icc && r->value.color.icc && + this->value.color.icc->colorProfile != r->value.color.icc->colorProfile && + this->value.color.icc->colors != r->value.color.icc->colors ) ) { + return false; + } + } + + return SPIBase::operator==(rhs); + + } else { + return false; + } +} + + + +// SPIPaintOrder -------------------------------------------------------- + +void +SPIPaintOrder::read( gchar const *str ) { + + if( !str ) return; + + g_free(value); + set = false; + inherit = false; + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + } else { + set = true; + value = g_strdup(str); + + if (!strcmp(value, "normal")) { + layer[0] = SP_CSS_PAINT_ORDER_NORMAL; + layer_set[0] = true; + } else { + // This certainly can be done more efficiently + gchar** c = g_strsplit(value, " ", PAINT_ORDER_LAYERS + 1); + bool used[3] = {false, false, false}; + unsigned int i = 0; + for( ; i < PAINT_ORDER_LAYERS; ++i ) { + if( c[i] ) { + layer_set[i] = false; + if( !strcmp( c[i], "fill")) { + layer[i] = SP_CSS_PAINT_ORDER_FILL; + layer_set[i] = true; + used[0] = true; + } else if( !strcmp( c[i], "stroke")) { + layer[i] = SP_CSS_PAINT_ORDER_STROKE; + layer_set[i] = true; + used[1] = true; + } else if( !strcmp( c[i], "markers")) { + layer[i] = SP_CSS_PAINT_ORDER_MARKER; + layer_set[i] = true; + used[2] = true; + } else { + std::cerr << "sp_style_read_ipaintorder: illegal value: " << c[i] << std::endl; + break; + } + } else { + break; + } + } + g_strfreev(c); + + // Fill out rest of the layers using the default order + if( !used[0] && i < PAINT_ORDER_LAYERS ) { + layer[i] = SP_CSS_PAINT_ORDER_FILL; + layer_set[i] = false; + ++i; + } + if( !used[1] && i < PAINT_ORDER_LAYERS ) { + layer[i] = SP_CSS_PAINT_ORDER_STROKE; + layer_set[i] = false; + ++i; + } + if( !used[2] && i < PAINT_ORDER_LAYERS ) { + layer[i] = SP_CSS_PAINT_ORDER_MARKER; + layer_set[i] = false; + } + } + } +} + +const Glib::ustring SPIPaintOrder::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + auto ret = Glib::ustring(""); + for( unsigned i = 0; i < PAINT_ORDER_LAYERS; ++i ) { + if (layer_set[i]) { + if (!ret.empty()) ret += " "; + switch (this->layer[i]) { + case SP_CSS_PAINT_ORDER_NORMAL: + ret += "normal"; + assert( i == 0 ); + break; + case SP_CSS_PAINT_ORDER_FILL: + ret += "fill"; + break; + case SP_CSS_PAINT_ORDER_STROKE: + ret += "stroke"; + break; + case SP_CSS_PAINT_ORDER_MARKER: + ret += "markers"; + break; + } + } else { + break; + } + } + return ret; +} + +void +SPIPaintOrder::cascade( const SPIBase* const parent ) { + if( const SPIPaintOrder* p = dynamic_cast<const SPIPaintOrder*>(parent) ) { + if (!set || inherit) { // Always inherits + for( unsigned i = 0; i < PAINT_ORDER_LAYERS; ++i ) { + layer[i] = p->layer[i]; + layer_set[i] = p->layer_set[i]; + } + g_free( value ); + value = g_strdup(p->value); + } + } else { + std::cerr << "SPIPaintOrder::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIPaintOrder::merge( const SPIBase* const parent ) { + if( const SPIPaintOrder* p = dynamic_cast<const SPIPaintOrder*>(parent) ) { + // if( inherits ) { PaintOrder always inherits + if( (!set || inherit) && p->set && !(p->inherit) ) { + this->cascade( parent ); // Must call be setting 'set' + set = p->set; + inherit = p->inherit; + } + } +} + +bool +SPIPaintOrder::operator==(const SPIBase& rhs) const { + if( const SPIPaintOrder* r = dynamic_cast<const SPIPaintOrder*>(&rhs) ) { + if( layer[0] == SP_CSS_PAINT_ORDER_NORMAL && + r->layer[0] == SP_CSS_PAINT_ORDER_NORMAL ) return SPIBase::operator==(rhs); + for (unsigned i = 0; i < PAINT_ORDER_LAYERS; ++i ) { + if( layer[i] != r->layer[i] ) return false; + } + return SPIBase::operator==(rhs); + } else { + return false; + } +} + + + +// SPIFilter ------------------------------------------------------------ + +SPIFilter::~SPIFilter() { + if( href ) { + clear(); + delete href; + href = nullptr; + } +} + +void +SPIFilter::read( gchar const *str ) { + + if( !str ) return; + + clear(); + + if ( streq(str, "inherit") ) { + set = true; + inherit = true; + } else if (streq(str, "none")) { + set = true; + } else if (strneq(str, "url", 3)) { + auto uri = extract_uri(str); + if (uri.empty()) { + std::cerr << "SPIFilter::read: url is empty or invalid" << std::endl; + return; + } else if (!style) { + std::cerr << "SPIFilter::read: url with empty SPStyle pointer" << std::endl; + return; + } + set = true; + + // Create href if not already done. + if (!href) { + if (style->object) { + href = new SPFilterReference(style->object); + } + // Do we have href now? + if ( href ) { + style->filter_changed_connection = href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), style)); + } else { + std::cerr << "SPIFilter::read(): Could not allocate 'href'" << std::endl; + return; + } + } + + // We have href + try { + href->attach(Inkscape::URI(uri.c_str())); + } catch (Inkscape::BadURIException &e) { + std::cerr << "SPIFilter::read() " << e.what() << std::endl; + delete href; + href = nullptr; + } + + } else { + std::cerr << "SPIFilter::read(): malformed value: " << str << std::endl; + } +} + +const Glib::ustring SPIFilter::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->href) return this->href->getURI()->cssStr(); + return Glib::ustring(""); +} + +void +SPIFilter::clear() { + + SPIBase::clear(); + if( href ) { + if( href->getObject() ) { + href->detach(); + } + } +} + +void +SPIFilter::cascade( const SPIBase* const parent ) { + if( const SPIFilter* p = dynamic_cast<const SPIFilter*>(parent) ) { + if( inherit ) { // Only inherits if 'inherit' true/ + // FIXME: This is rather unlikely so ignore for now. + (void)p; + std::cerr << "SPIFilter::cascade: value 'inherit' not supported." << std::endl; + } else { + // Do nothing + } + } else { + std::cerr << "SPIFilter::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIFilter::merge( const SPIBase* const parent ) { + if( const SPIFilter* p = dynamic_cast<const SPIFilter*>(parent) ) { + // The "correct" thing to do is to combine the filter primitives. + // The next best thing is to keep any filter on this object. If there + // is no filter on this object, then use any filter on the parent. + if( (!set || inherit) && p->href && p->href->getObject() ) { // is the getObject() needed? + set = p->set; + inherit = p->inherit; + if( href ) { + // If we already have an href, use it (unlikely but heck...) + if( href->getObject() ) { + href->detach(); + } + } else { + // If we don't have an href, create it + if( style->document ) { // FIXME + href = new SPFilterReference(style->document); + //href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), style)); + } else if (style->object) { + href = new SPFilterReference(style->object); + } + } + if( href ) { + // If we now have an href, try to attach parent filter + try { + href->attach(*p->href->getURI()); + } catch (Inkscape::BadURIException &e) { + std::cerr << "SPIFilter::merge: " << e.what() << std::endl; + href->detach(); + } + } + } + } +} + +// FIXME +bool +SPIFilter::operator==(const SPIBase& rhs) const { + if( const SPIFilter* r = dynamic_cast<const SPIFilter*>(&rhs) ) { + (void)r; + return true; + } else { + return false; + } +} + + + +// SPIDashArray --------------------------------------------------------- + +void +SPIDashArray::read( gchar const *str ) { + + if( !str ) return; + + set = true; + + if( strcmp( str, "inherit") == 0 ) { + inherit = true; + return; + } + + values.clear(); + + if( strcmp(str, "none") == 0) { + return; + } + + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[(,\\s|\\s)]+", str); + + bool LineSolid = true; + + for (auto token : tokens) { + SPILength spilength; + spilength.read(token.c_str()); + if (spilength.value > 0.00000001) + LineSolid = false; + values.push_back(spilength); + } + + if (LineSolid) { + values.clear(); + } + return; +} + +const Glib::ustring SPIDashArray::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->values.empty()) return Glib::ustring("none"); + auto ret = Glib::ustring(""); + for(auto value: this->values) { + if (!ret.empty()) ret += ", "; + ret += value.toString(); + } + return ret; +} + +void +SPIDashArray::cascade( const SPIBase* const parent ) { + if( const SPIDashArray* p = dynamic_cast<const SPIDashArray*>(parent) ) { + if( !set || inherit ) values = p->values; // Always inherits + } else { + std::cerr << "SPIDashArray::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPIDashArray::merge( const SPIBase* const parent ) { + if( const SPIDashArray* p = dynamic_cast<const SPIDashArray*>(parent) ) { + if( inherits ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + values = p->values; + } + } + } else { + std::cerr << "SPIDashArray::merge(): Incorrect parent type" << std::endl; + } +} + +bool +SPIDashArray::operator==(const SPIBase& rhs) const { + if (const SPIDashArray *r = dynamic_cast<const SPIDashArray *>(&rhs)) { + if (values.size() != r->values.size()) { + return false; + } + for (int i = 0; i < values.size(); i++) { + if (values[i] != r->values[i]) { + return false; + } + } + } + return SPIBase::operator==(rhs); +} + + +// SPIFontSize ---------------------------------------------------------- + +/** Indexed by SP_CSS_FONT_SIZE_blah. These seem a bit small */ +float const SPIFontSize::font_size_table[] = {6.0, 8.0, 10.0, 12.0, 14.0, 18.0, 24.0}; +float const SPIFontSize::font_size_default = 12.0; + +void +SPIFontSize::read( gchar const *str ) { + + if( !str ) return; + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + } else if ((*str == 'x') || (*str == 's') || (*str == 'm') || (*str == 'l')) { + // xx-small, x-small, etc. + for (unsigned i = 0; enum_font_size[i].key; i++) { + if (!strcmp(str, enum_font_size[i].key)) { + set = true; + inherit = false; + type = SP_FONT_SIZE_LITERAL; + literal = enum_font_size[i].value; + return; + } + } + /* Invalid */ + return; + } else { + SPILength length; + length.set = false; + length.read( str ); + if( length.set ) { + set = true; + inherit = length.inherit; + unit = length.unit; + value = length.value; + computed = length.computed; + /* Set a minimum font size to something much smaller than should ever (ever!) be encountered in a real file. + If a bad SVG file is encountered and this is zero odd things + might happen because the inverse is used in some scaling actions. + */ + if ( computed <= 1.0e-32 ) { computed = 1.0e-32; } + if( unit == SP_CSS_UNIT_PERCENT ) { + type = SP_FONT_SIZE_PERCENTAGE; + } else { + type = SP_FONT_SIZE_LENGTH; + } + } + return; + } +} + +const Glib::ustring SPIFontSize::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + auto ret = Glib::ustring(""); + switch (this->type) { + case SP_FONT_SIZE_LITERAL: + for (unsigned i = 0; enum_font_size[i].key; i++) { + if (enum_font_size[i].value == static_cast< gint > (this->literal) ) { + if (!ret.empty()) ret += " "; + ret += enum_font_size[i].key; + } + } + break; + case SP_FONT_SIZE_LENGTH: + if (prefs->getBool("/options/font/textOutputPx", true)) { + unit = SP_CSS_UNIT_PX; + } + ret += Glib::ustring::format(sp_style_css_size_px_to_units(this->computed, unit)); + ret += sp_style_get_css_unit_string(unit); + break; + case SP_FONT_SIZE_PERCENTAGE: + return Glib::ustring::format(this->value * 100.0) + "%"; + default: + g_error("Invalid FontSize value, not writing it."); + } + return ret; +} + +void +SPIFontSize::cascade( const SPIBase* const parent ) { + if( const SPIFontSize* p = dynamic_cast<const SPIFontSize*>(parent) ) { + if( !set || inherit ) { // Always inherits + computed = p->computed;value = p->value; + + + // Calculate computed based on parent as needed + } else if( type == SP_FONT_SIZE_LITERAL ) { + if( literal < SP_CSS_FONT_SIZE_SMALLER ) { + computed = font_size_table[ literal ]; + } else if( literal == SP_CSS_FONT_SIZE_SMALLER ) { + computed = p->computed / 1.2; + } else if( literal == SP_CSS_FONT_SIZE_LARGER ) { + computed = p->computed * 1.2; + } else { + std::cerr << "SPIFontSize::cascade: Illegal literal value" << std::endl; + } + } else if( type == SP_FONT_SIZE_PERCENTAGE ) { + // Percentage for font size is relative to parent computed (rather than viewport) + computed = p->computed * value; + } else if( type == SP_FONT_SIZE_LENGTH ) { + switch ( unit ) { + case SP_CSS_UNIT_EM: + /* Relative to parent font size */ + computed = p->computed * value; + break; + case SP_CSS_UNIT_EX: + /* Relative to parent font size */ + computed = p->computed * value * 0.5; /* Hack FIXME */ + break; + default: + /* No change */ + break; + } + } + /* Set a minimum font size to something much smaller than should ever (ever!) be encountered in a real file. + If a bad SVG file is encountered and this is zero odd things + might happen because the inverse is used in some scaling actions. + */ + if ( computed <= 1.0e-32 ) { computed = 1.0e-32; } + } else { + std::cerr << "SPIFontSize::cascade(): Incorrect parent type" << std::endl; + } +} + +double +SPIFontSize::relative_fraction() const { + + switch (type) { + case SP_FONT_SIZE_LITERAL: { + switch (literal) { + case SP_CSS_FONT_SIZE_SMALLER: + return 5.0 / 6.0; + + case SP_CSS_FONT_SIZE_LARGER: + return 6.0 / 5.0; + + default: + g_assert_not_reached(); + } + } + + case SP_FONT_SIZE_PERCENTAGE: + return value; + + case SP_FONT_SIZE_LENGTH: { + switch (unit ) { + case SP_CSS_UNIT_EM: + return value; + + case SP_CSS_UNIT_EX: + return value * 0.5; + + default: + g_assert_not_reached(); + } + } + } + g_assert_not_reached(); + return 1; // Make -Wreturn-type happy +} + +void +SPIFontSize::merge( const SPIBase* const parent ) { + if( const SPIFontSize* p = dynamic_cast<const SPIFontSize*>(parent) ) { + if( p->set && !(p->inherit) ) { + // Parent has definined font-size + if( (!set || inherit) ) { + // Computed value same as parent + set = p->set; + inherit = p->inherit; + type = p->type; + unit = p->unit; + literal = p->literal; + value = p->value; + computed = p->computed; // Just to be sure + } else if ( type == SP_FONT_SIZE_LENGTH && + unit != SP_CSS_UNIT_EM && + unit != SP_CSS_UNIT_EX ) { + // Absolute size, computed value already set + } else if ( type == SP_FONT_SIZE_LITERAL && + literal < SP_CSS_FONT_SIZE_SMALLER ) { + // Absolute size, computed value already set + //g_assert( literal < G_N_ELEMENTS(font_size_table) ); + g_assert( computed == font_size_table[literal] ); + } else { + // Relative size + double const child_frac( relative_fraction() ); + set = true; + inherit = false; + computed = p->computed * child_frac; + + if ( ( p->type == SP_FONT_SIZE_LITERAL && + p->literal < SP_CSS_FONT_SIZE_SMALLER ) || + ( p->type == SP_FONT_SIZE_LENGTH && + p->unit != SP_CSS_UNIT_EM && + p->unit != SP_CSS_UNIT_EX ) ) { + // Parent absolute size + type = SP_FONT_SIZE_LENGTH; + + } else { + // Parent relative size + double const parent_frac( p->relative_fraction() ); + if( type == SP_FONT_SIZE_LENGTH ) { + // ex/em + value *= parent_frac; + } else { + value = parent_frac * child_frac; + type = SP_FONT_SIZE_PERCENTAGE; + } + } + } // Relative size + /* Set a minimum font size to something much smaller than should ever (ever!) be encountered in a real file. + If a bad SVG file is encountered and this is zero odd things + might happen because the inverse is used in some scaling actions. + */ + if ( computed <= 1.0e-32 ) { computed = 1.0e-32; } + } // Parent set and not inherit + } else { + std::cerr << "SPIFontSize::merge(): Incorrect parent type" << std::endl; + } +} + +// What about different SVG units? +bool +SPIFontSize::operator==(const SPIBase& rhs) const { + if( const SPIFontSize* r = dynamic_cast<const SPIFontSize*>(&rhs) ) { + if( type != r->type ) { return false;} + if( type == SP_FONT_SIZE_LENGTH ) { + if( computed != r->computed ) { return false;} + } else if (type == SP_FONT_SIZE_LITERAL ) { + if( literal != r->literal ) { return false;} + } else { + if( value != r->value ) { return false;} + } + return SPIBase::operator==(rhs); + } else { + return false; + } +} + + + +// SPIFont ---------------------------------------------------------- + +void +SPIFont::read( gchar const *str ) { + + if( !str ) return; + + if( !style ) { + std::cerr << "SPIFont::read(): style is void" << std::endl; + return; + } + + if ( !strcmp(str, "inherit") ) { + set = true; + inherit = true; + } else { + + // Break string into white space separated tokens + std::stringstream os( str ); + Glib::ustring param; + + while (os >> param) { + + // CSS is case insensitive but we're comparing against lowercase strings + Glib::ustring lparam = param.lowercase(); + + if (lparam == "/" ) { + // line_height follows... note: font-size already read + + os >> param; + lparam = param.lowercase(); + style->line_height.readIfUnset( lparam.c_str() ); + + } else { + // Try to parse each property in turn + + decltype(style->font_style) test_style; + test_style.read( lparam.c_str() ); + if( test_style.set ) { + style->font_style = test_style; + continue; + } + + // font-variant (Note: only CSS2.1 value small-caps is valid in shortcut.) + decltype(style->font_variant) test_variant; + test_variant.read( lparam.c_str() ); + if( test_variant.set ) { + style->font_variant = test_variant; + continue; + } + + // font-weight + decltype(style->font_weight) test_weight; + test_weight.read( lparam.c_str() ); + if( test_weight.set ) { + style->font_weight = test_weight; + continue; + } + + // font-stretch (added in CSS 3 Fonts) + decltype(style->font_stretch) test_stretch; + test_stretch.read( lparam.c_str() ); + if( test_stretch.set ) { + style->font_stretch = test_stretch; + continue; + } + + // font-size + decltype(style->font_size) test_size; + test_size.read( lparam.c_str() ); + if( test_size.set ) { + style->font_size = test_size; + continue; + } + + // No valid property value found. + break; + } + } // params + + // The rest must be font-family... + std::string str_s = str; // Why this extra step? + std::string family = str_s.substr( str_s.find( param ) ); + + style->font_family.readIfUnset( family.c_str() ); + + // Everything in shorthand is set per CSS rules, this works since + // properties are read backwards from end to start. + style->font_style.set = true; + style->font_variant.set = true; + style->font_weight.set = true; + style->font_stretch.set = true; + style->font_size.set = true; + style->line_height.set = true; + style->font_family.set = true; + // style->font_size_adjust.set = true; + // style->font_kerning.set = true; + // style->font_language_override.set = true;; + } +} + +const Glib::ustring SPIFont::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + // At the moment, do nothing. We could add a preference to write out + // 'font' shorthand rather than longhand properties. + /* SPIFontSize const *const my_base = dynamic_cast<const SPIFontSize*>(base); + if ( (flags & SP_STYLE_FLAG_ALWAYS) || + ((flags & SP_STYLE_FLAG_IFSET) && this->set) || + ((flags & SP_STYLE_FLAG_IFDIFF) && this->set + && (!my_base->set || this != my_base ))) + { + }*/ + + return Glib::ustring(""); +} + +// void +// SPIFont::cascade( const SPIBase* const parent ) { +// } + +// void +// SPIFont::merge( const SPIBase* const parent ) { +// } + +// Does nothing... +bool +SPIFont::operator==(const SPIBase& rhs) const { + if( /* const SPIFont* r = */ dynamic_cast<const SPIFont*>(&rhs) ) { + return SPIBase::operator==(rhs); + } else { + return false; + } +} + + + +// SPIBaselineShift ----------------------------------------------------- + +void +SPIBaselineShift::read( gchar const *str ) { + + if( !str ) return; + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + } else if ((*str == 'b') || (*str == 's')) { + // baseline or sub or super + for (unsigned i = 0; enum_baseline_shift[i].key; i++) { + if (!strcmp(str, enum_baseline_shift[i].key)) { + set = true; + inherit = false; + type = SP_BASELINE_SHIFT_LITERAL; + literal = enum_baseline_shift[i].value; + return; + } + } + /* Invalid */ + return; + } else { + SPILength length; + length.read( str ); + set = length.set; + inherit = length.inherit; + unit = length.unit; + value = length.value; + computed = length.computed; + if( unit == SP_CSS_UNIT_PERCENT ) { + type = SP_BASELINE_SHIFT_PERCENTAGE; + } else { + type = SP_BASELINE_SHIFT_LENGTH; + } + return; + } +} + +const Glib::ustring SPIBaselineShift::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + auto ret = Glib::ustring(""); + switch (this->type) { + case SP_BASELINE_SHIFT_LITERAL: + for (unsigned i = 0; enum_baseline_shift[i].key; i++) { + if (enum_baseline_shift[i].value == static_cast< gint > (this->literal) ) { + if (!ret.empty()) ret += " "; + ret += enum_baseline_shift[i].key; + } + } + break; + case SP_BASELINE_SHIFT_LENGTH: + if( this->unit == SP_CSS_UNIT_EM || this->unit == SP_CSS_UNIT_EX ) { + ret += Glib::ustring::format(this->value); + ret += (this->unit == SP_CSS_UNIT_EM ? "em" : "ex"); + } else { + // must specify px, see inkscape bug 1221626, mozilla bug 234789 + ret += Glib::ustring::format(this->computed) + "px"; + } + break; + case SP_BASELINE_SHIFT_PERCENTAGE: + return Glib::ustring::format(this->value * 100.0) + "%"; + } + return ret; +} + +void +SPIBaselineShift::cascade( const SPIBase* const parent ) { + if( const SPIBaselineShift* p = dynamic_cast<const SPIBaselineShift*>(parent) ) { + SPIFontSize *pfont_size = &(p->style->font_size); + g_assert( pfont_size != nullptr ); + + if( !set || inherit ) { + computed = p->computed; // Shift relative to parent shift, corrected below + } else if (type == SP_BASELINE_SHIFT_LITERAL) { + if( literal == SP_CSS_BASELINE_SHIFT_BASELINE ) { + computed = 0; // No change + } else if (literal == SP_CSS_BASELINE_SHIFT_SUB ) { + // Should use subscript position from font relative to alphabetic baseline + // OpenOffice, Adobe: -0.33, Word -0.14, LaTex about -0.2. + computed = -0.2 * pfont_size->computed; + } else if (literal == SP_CSS_BASELINE_SHIFT_SUPER ) { + // Should use superscript position from font relative to alphabetic baseline + // OpenOffice, Adobe: 0.33, Word 0.35, LaTex about 0.45. + computed = 0.4 * pfont_size->computed; + } else { + /* Illegal value */ + } + } else if (type == SP_BASELINE_SHIFT_PERCENTAGE) { + // Percentage for baseline shift is relative to computed "line-height" + // which is just font-size (see SVG1.1 'font'). + computed = pfont_size->computed * value; + } else if (type == SP_BASELINE_SHIFT_LENGTH) { + switch (unit) { + case SP_CSS_UNIT_EM: + computed = value * pfont_size->computed; + break; + case SP_CSS_UNIT_EX: + computed = value * 0.5 * pfont_size->computed; + break; + default: + /* No change */ + break; + } + } + // baseline-shifts are relative to parent baseline + computed += p->computed; + + } else { + std::cerr << "SPIBaselineShift::cascade(): Incorrect parent type" << std::endl; + } +} + +// This was not defined in the legacy C code, it needs some serious thinking (but is low priority). +// FIX ME +void +SPIBaselineShift::merge( const SPIBase* const parent ) { + if( const SPIBaselineShift* p = dynamic_cast<const SPIBaselineShift*>(parent) ) { + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + value = p->value; + } + } else { + std::cerr << "SPIBaselineShift::merge(): Incorrect parent type" << std::endl; + } +} + +// This is not used but we have it for completeness, it has not been tested. +bool +SPIBaselineShift::operator==(const SPIBase& rhs) const { + if( const SPIBaselineShift* r = dynamic_cast<const SPIBaselineShift*>(&rhs) ) { + if( type != r->type ) return false; + if( type == SP_BASELINE_SHIFT_LENGTH ) { + if( computed != r->computed ) return false; + } else if ( type == SP_BASELINE_SHIFT_LITERAL ) { + if( literal != r->literal ) return false; + } else { + if( value != r->value ) return false; + } + return SPIBase::operator==(rhs); + } else { + return false; + } +} + +bool +SPIBaselineShift::isZero() const { + if( type == SP_BASELINE_SHIFT_LITERAL ) { + if( literal == SP_CSS_BASELINE_SHIFT_BASELINE ) return true; + } else { + if( value == 0.0 ) return true; + } + return false; +} + + + +// SPITextDecorationLine ------------------------------------------------ + +void +SPITextDecorationLine::read( gchar const *str ) { + + if( !str ) return; + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + } else if (!strcmp(str, "none")) { + set = true; + inherit = false; + underline = false; + overline = false; + line_through = false; + blink = false; + } else { + bool found_one = false; + bool hit_one = false; + + // CSS 2 keywords + bool found_underline = false; + bool found_overline = false; + bool found_line_through = false; + bool found_blink = false; + + // This method ignores inlineid keys and extra delimiters, so " ,,, blink hello" will set + // blink and ignore hello + const gchar *hstr = str; + while (true) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + // CSS 2 keywords + while(true){ // not really a loop, used to avoid a goto + hit_one = true; // most likely we will + if ((slen == 9) && strneq(hstr, "underline", slen)){ found_underline = true; break; } + if ((slen == 8) && strneq(hstr, "overline", slen)){ found_overline = true; break; } + if ((slen == 12) && strneq(hstr, "line-through", slen)){ found_line_through = true; break; } + if ((slen == 5) && strneq(hstr, "blink", slen)){ found_blink = true; break; } + if ((slen == 4) && strneq(hstr, "none", slen)){ break; } + + hit_one = false; // whatever this thing is, we do not recognize it + break; + } + found_one |= hit_one; + if(*str == '\0')break; + hstr = str + 1; + } + str++; + } + if (found_one) { + set = true; + inherit = false; + underline = found_underline; + overline = found_overline; + line_through = found_line_through; + blink = found_blink; + } + else { + set = false; + inherit = false; + } + } +} + +const Glib::ustring SPITextDecorationLine::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + auto ret = Glib::ustring(""); + if (underline) ret += "underline "; + if (overline) ret += "overline "; + if (line_through) ret += "line-through "; + if (blink) ret += "blink "; // Deprecated + if (ret.empty()) { + ret = "none"; + } else { + assert(ret.raw().back() == ' '); + ret.resize(ret.size() - 1); + } + return ret; +} + +void +SPITextDecorationLine::cascade( const SPIBase* const parent ) { + if( const SPITextDecorationLine* p = dynamic_cast<const SPITextDecorationLine*>(parent) ) { + if( inherits && (!set || inherit) ) { + underline = p->underline; + overline = p->overline; + line_through = p->line_through; + blink = p->blink; + } + } else { + std::cerr << "SPITextDecorationLine::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPITextDecorationLine::merge( const SPIBase* const parent ) { + if( const SPITextDecorationLine* p = dynamic_cast<const SPITextDecorationLine*>(parent) ) { + if( inherits ) { // Always inherits... but special rules? + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + underline = p->underline; + overline = p->overline; + line_through = p->line_through; + blink = p->blink; + } + } + } +} + +bool +SPITextDecorationLine::operator==(const SPIBase& rhs) const { + if( const SPITextDecorationLine* r = dynamic_cast<const SPITextDecorationLine*>(&rhs) ) { + return + (underline == r->underline ) && + (overline == r->overline ) && + (line_through == r->line_through ) && + (blink == r->blink ) && + SPIBase::operator==(rhs); + } else { + return false; + } +} + + + +// SPITextDecorationStyle ----------------------------------------------- + +void +SPITextDecorationStyle::read( gchar const *str ) { + + if( !str ) return; + + set = false; + inherit = false; + + solid = true; // Default + isdouble = false; + dotted = false; + dashed = false; + wavy = false; + + if (!strcmp(str, "inherit")) { + set = true; + inherit = true; + solid = false; + } else { + // note, these are CSS 3 keywords + bool found_solid = false; + bool found_double = false; + bool found_dotted = false; + bool found_dashed = false; + bool found_wavy = false; + bool found_one = false; + + // this method ignores inlineid keys and extra delimiters, so " ,,, style hello" will set style and ignore hello + // if more than one style is present, the first is used + const gchar *hstr = str; + while (true) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + if ( (slen == 5) && strneq(hstr, "solid", slen)){ found_solid = true; found_one = true; break; } + else if ((slen == 6) && strneq(hstr, "double", slen)){ found_double = true; found_one = true; break; } + else if ((slen == 6) && strneq(hstr, "dotted", slen)){ found_dotted = true; found_one = true; break; } + else if ((slen == 6) && strneq(hstr, "dashed", slen)){ found_dashed = true; found_one = true; break; } + else if ((slen == 4) && strneq(hstr, "wavy", slen)){ found_wavy = true; found_one = true; break; } + if(*str == '\0')break; // nothing more to test + hstr = str + 1; + } + str++; + } + if(found_one){ + set = true; + solid = found_solid; + isdouble = found_double; + dotted = found_dotted; + dashed = found_dashed; + wavy = found_wavy; + } + else { + set = false; + inherit = false; + } + } +} + +const Glib::ustring SPITextDecorationStyle::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->solid) return Glib::ustring("solid"); + if (this->isdouble) return Glib::ustring("double"); + if (this->dotted) return Glib::ustring("dotted"); + if (this->dashed) return Glib::ustring("dashed"); + if (this->wavy) return Glib::ustring("wavy"); + g_error("SPITextDecorationStyle::write(): No valid value for property"); + return Glib::ustring(""); +} + +void +SPITextDecorationStyle::cascade( const SPIBase* const parent ) { + if( const SPITextDecorationStyle* p = dynamic_cast<const SPITextDecorationStyle*>(parent) ) { + if( inherits && (!set || inherit) ) { + solid = p->solid; + isdouble = p->isdouble; + dotted = p->dotted; + dashed = p->dashed; + wavy = p->wavy; + } + } else { + std::cerr << "SPITextDecorationStyle::cascade(): Incorrect parent type" << std::endl; + } +} + +void +SPITextDecorationStyle::merge( const SPIBase* const parent ) { + if( const SPITextDecorationStyle* p = dynamic_cast<const SPITextDecorationStyle*>(parent) ) { + if( inherits ) { // Always inherits... but special rules? + if( (!set || inherit) && p->set && !(p->inherit) ) { + set = p->set; + inherit = p->inherit; + solid = p->solid; + isdouble = p->isdouble; + dotted = p->dotted; + dashed = p->dashed; + wavy = p->wavy; + } + } + } +} + +bool +SPITextDecorationStyle::operator==(const SPIBase& rhs) const { + if( const SPITextDecorationStyle* r = dynamic_cast<const SPITextDecorationStyle*>(&rhs) ) { + return + (solid == r->solid ) && + (isdouble == r->isdouble ) && + (dotted == r->dotted ) && + (dashed == r->dashed ) && + (wavy == r->wavy ) && + SPIBase::operator==(rhs); + } else { + return false; + } +} + + + +// TextDecorationColor is handled by SPIPaint (should be SPIColor), default value is "currentColor" +// FIXME + + + +// SPITextDecoration ---------------------------------------------------- + +void +SPITextDecoration::read( gchar const *str ) { + + if( !str ) return; + + bool is_css3 = false; + + decltype(style->text_decoration_line) test_line; + test_line.read( str ); + if( test_line.set ) { + if (!style->text_decoration_line.set) { + style->text_decoration_line = test_line; + } + set = true; + } + + decltype(style->text_decoration_style) test_style; + test_style.read( str ); + if( test_style.set ) { + style->text_decoration_style = test_style; + is_css3 = true; + } + + // the color routine must be fed one token at a time - if multiple colors are found the LAST + // one is used ???? then why break on set? + + // This could certainly be designed better + decltype(style->text_decoration_color) test_color; + test_color.setStylePointer( style ); + test_color.read( "currentColor" ); // Default value + test_color.set = false; + const gchar *hstr = str; + while (true) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + gchar *frag = g_strndup(hstr,slen+1); // only send one piece at a time, since keywords may be intermixed + + if( strcmp( frag, "none" ) != 0 ) { // 'none' not allowed + test_color.read( frag ); + } + + free(frag); + if( test_color.set ) { + style->text_decoration_color = test_color; + is_css3 = true; + break; + } + test_color.read( "currentColor" ); // Default value + test_color.set = false; + if( *str == '\0' )break; + hstr = str + 1; + } + str++; + } + + // If we read a style or color then we have CSS3 which require any non-set values to be + // set to their default values. + if( is_css3 ) { + style->text_decoration_line.set = true; + style->text_decoration_style.set = true; + style->text_decoration_color.set = true; + set = true; + } + + // If we set text_decoration_line, then update style_td (for CSS2 text-decoration) + if( style->text_decoration_line.set ) { + style_td = style; + } +} + +// Returns CSS2 'text-decoration' (using settings in SPTextDecorationLine) +// This is required until all SVG renderers support CSS3 'text-decoration' +const Glib::ustring SPITextDecoration::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + return style->text_decoration_line.get_value(); +} +const Glib::ustring +SPITextDecoration::write( guint const flags, SPStyleSrc const &style_src_req, SPIBase const *const base) const { + SPITextDecoration const *const my_base = dynamic_cast<const SPITextDecoration*>(base); + assert(!base || my_base); + // proxy for text-decoration-line, but only if set + if (set && style && + style->text_decoration_line.shall_write(flags, style_src_req, + my_base ? &my_base->style->text_decoration_line : nullptr)) { + return (name() + ":" + this->get_value() + important_str() + ";"); + } + return Glib::ustring(""); +} + +void +SPITextDecoration::cascade( const SPIBase* const parent ) { + if( const SPITextDecoration* p = dynamic_cast<const SPITextDecoration*>(parent) ) { + if( style_td == nullptr ) { + style_td = p->style_td; + } + } else { + std::cerr << "SPITextDecoration::cascade(): Incorrect parent type" << std::endl; + } + +} + +void +SPITextDecoration::merge( const SPIBase* const parent ) { + if( const SPITextDecoration* p = dynamic_cast<const SPITextDecoration*>(parent) ) { + if( style_td == nullptr ) { + style_td = p->style_td; + } + } else { + std::cerr << "SPITextDecoration::merge(): Incorrect parent type" << std::endl; + } + +} + +// Use CSS2 value +bool +SPITextDecoration::operator==(const SPIBase& rhs) const { + if( const SPITextDecoration* r = dynamic_cast<const SPITextDecoration*>(&rhs) ) { + return (style->text_decoration_line == r->style->text_decoration_line && + SPIBase::operator==(rhs)); + } else { + return false; + } +} + +// SPIVectorEffect ------------------------------------------------ + +void +SPIVectorEffect::read( gchar const *str ) { + + if( !str ) return; + + if (!strcmp(str, "none")) { + set = true; + stroke = false; + size = false; + rotate = false; + fixed = false; + } else { + bool found_one = false; + bool hit_one = false; + + bool found_stroke = false; + bool found_size = false; + bool found_rotate = false; + bool found_fixed = false; + + const gchar *hstr = str; + while (true) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + + while(true){ // not really a loop, used to avoid a goto + hit_one = true; // most likely we will + if ((slen == 18) && strneq(hstr, "non-scaling-stroke", slen)){ found_stroke = true; break; } + if ((slen == 16) && strneq(hstr, "non-scaling-size", slen)){ found_size = true; break; } + if ((slen == 12) && strneq(hstr, "non-rotation", slen)){ found_rotate = true; break; } + if ((slen == 14) && strneq(hstr, "fixed-position", slen)){ found_fixed = true; break; } + if ((slen == 4) && strneq(hstr, "none", slen)){ break; } + + hit_one = false; // whatever this thing is, we do not recognize it + break; + } + found_one |= hit_one; + if(*str == '\0')break; + hstr = str + 1; + } + str++; + } + if (found_one) { + set = true; + stroke = found_stroke; + size = found_size; + rotate = found_rotate; + fixed = found_fixed; + } + else { + set = false; + } + } + + // std::cout << " stroke: " << stroke + // << " size: " << size + // << " rotate: " << rotate + // << " fixed: " << fixed + // << std::endl; +} + +const Glib::ustring SPIVectorEffect::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + auto ret = Glib::ustring(""); + if (this->stroke) ret += " non-scaling-stroke"; + if (this->size) ret += " non-scaling-size"; + if (this->rotate) ret += " non-rotation"; + if (this->fixed) ret += " fixed-position"; + if (ret.empty()) { + ret += "none"; + } else { + ret.erase(0, 1); + } + return ret; +} + +// Does not inherit! +// void +// SPIVectorEffect::cascade( const SPIBase* const parent ) { +// } + +// void +// SPIVectorEffect::merge( const SPIBase* const parent ) { +// } + +bool +SPIVectorEffect::operator==(const SPIBase& rhs) const { + if( const SPIVectorEffect* r = dynamic_cast<const SPIVectorEffect*>(&rhs) ) { + return + (stroke == r->stroke) && + (size == r->size ) && + (rotate == r->rotate) && + (fixed == r->fixed ) && + SPIBase::operator==(rhs); + } else { + return false; + } +} + +// SPIStrokeExtensions ------------------------------------------------ + +void +SPIStrokeExtensions::read( gchar const *str ) { + + if( !str ) return; + + set = false; + hairline = false; + if (!strcmp(str, "none")) { + set = true; + } else if (!strcmp(str, "hairline")) { + set = true; + hairline = true; + } +} + +const Glib::ustring SPIStrokeExtensions::get_value() const +{ + if (this->inherit) return Glib::ustring("inherit"); + if (this->hairline) return Glib::ustring("hairline"); + return Glib::ustring("none"); +} + +// Does not inherit! +// void +// SPIStrokeExtensions::cascade( const SPIBase* const parent ) { +// } + +// void +// SPIStrokeExtensions::merge( const SPIBase* const parent ) { +// } + +bool +SPIStrokeExtensions::operator==(const SPIBase& rhs) const { + if( const SPIStrokeExtensions* r = dynamic_cast<const SPIStrokeExtensions*>(&rhs) ) { + return + (hairline == r->hairline ) && + SPIBase::operator==(rhs); + } else { + return false; + } +} + +// template instantiation +template class SPIEnum<SPBlendMode>; +template class SPIEnum<SPColorInterpolation>; +template class SPIEnum<SPColorRendering>; +template class SPIEnum<SPCSSBaseline>; +template class SPIEnum<SPCSSDirection>; +template class SPIEnum<SPCSSDisplay>; +template class SPIEnum<SPCSSFontVariantAlternates>; +template class SPIEnum<SPCSSTextAlign>; +template class SPIEnum<SPCSSTextOrientation>; +template class SPIEnum<SPCSSTextTransform>; +template class SPIEnum<SPCSSWritingMode>; +template class SPIEnum<SPEnableBackground>; +template class SPIEnum<SPImageRendering>; +template class SPIEnum<SPIsolation>; +template class SPIEnum<SPOverflow>; +template class SPIEnum<SPShapeRendering>; +template class SPIEnum<SPStrokeCapType>; +template class SPIEnum<SPStrokeJoinType>; +template class SPIEnum<SPTextAnchor>; +template class SPIEnum<SPTextRendering>; +template class SPIEnum<SPVisibility>; +template class SPIEnum<SPWhiteSpace>; +template class SPIEnum<SPWindRule>; +template class SPIEnum<SPCSSFontStretch>; +template class SPIEnum<SPCSSFontStyle>; +template class SPIEnum<SPCSSFontVariant>; +template class SPIEnum<SPCSSFontVariantPosition>; +template class SPIEnum<SPCSSFontVariantCaps>; +template class SPIEnum<SPCSSFontWeight>; +template class SPIEnum<uint_least16_t>; +template class SPIEnum<uint_least8_t>; + + + +/* ---------------------------- NOTES ----------------------------- */ + +/* + * opacity's effect is cumulative; we set the new value to the combined effect. The default value + * for opacity is 1.0, not inherit. stop-opacity also does not inherit. (Note that stroke-opacity + * and fill-opacity are quite different from opacity, and don't need any special handling.) + * + * Cases: + * - parent & child were each previously unset, in which case the effective + * opacity value is 1.0, and style should remain unset. + * - parent was previously unset (so computed opacity value of 1.0) + * and child was set to inherit. The merged child should + * get a value of 1.0, and shouldn't inherit (lest the new parent + * has a different opacity value). Given that opacity's default + * value is 1.0 (rather than inherit), we might as well have the + * merged child's opacity be unset. + * - parent was previously unset (so opacity 1.0), and child was set to a number. + * The merged child should retain its existing settings (though it doesn't matter + * if we make it unset if that number was 1.0). + * - parent was inherit and child was unset. Merged child should be set to inherit. + * - parent was inherit and child was inherit. (We can't in general reproduce this + * effect (short of introducing a new group), but setting opacity to inherit is rare.) + * If the inherited value was strictly between 0.0 and 1.0 (exclusive) then the merged + * child's value should be set to the product of the two, i.e. the square of the + * inherited value, and should not be marked as inherit. (This decision assumes that it + * is more important to retain the effective opacity than to retain the inheriting + * effect, and assumes that the inheriting effect either isn't important enough to create + * a group or isn't common enough to bother maintaining the code to create a group.) If + * the inherited value was 0.0 or 1.0, then marking the merged child as inherit comes + * closer to maintaining the effect. + * - parent was inherit and child was set to a numerical value. If the child's value + * was 1.0, then the merged child should have the same settings as the parent. + * If the child's value was 0, then the merged child should also be set to 0. + * If the child's value was anything else, then we do the same as for the inherit/inherit + * case above: have the merged child set to the product of the two opacities and not + * marked as inherit, for the same reasons as for that case. + * - parent was set to a value, and child was unset. The merged child should have + * parent's settings. + * - parent was set to a value, and child was inherit. The merged child should + * be set to the product, i.e. the square of the parent's value. + * - parent & child are each set to a value. The merged child should be set to the + * product. + */ + + +/* + 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 : |