summaryrefslogtreecommitdiffstats
path: root/src/xml/repr-css.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/xml/repr-css.cpp')
-rw-r--r--src/xml/repr-css.cpp400
1 files changed, 400 insertions, 0 deletions
diff --git a/src/xml/repr-css.cpp b/src/xml/repr-css.cpp
new file mode 100644
index 0000000..0d84ffc
--- /dev/null
+++ b/src/xml/repr-css.cpp
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * bulia byak <buliabyak@users.sf.net>
+ * Tavmjong Bah <tavmjong@free.fr> (Documentation)
+ *
+ * Functions to manipulate SPCSSAttr which is a class derived from Inkscape::XML::Node See
+ * sp-css-attr.h and node.h
+ *
+ * SPCSSAttr is a special node type where the "attributes" are the properties in an element's style
+ * attribute. For example, style="fill:blue;stroke:none" is stored in a List (Inkscape::Util:List)
+ * where the key is the property (e.g. "fill" or "stroke") and the value is the property's value
+ * (e.g. "blue" or "none"). An element's properties are manipulated by adding, removing, or
+ * changing an item in the List. Utility functions are provided to go back and forth between the
+ * two ways of representing properties (by a string or by a list).
+ *
+ * Use sp_repr_css_write_string to go from a property list to a style string.
+ *
+ */
+
+#define SP_REPR_CSS_C
+
+#include <cstring>
+#include <string>
+#include <sstream>
+
+#include <glibmm/ustring.h>
+
+#include "3rdparty/libcroco/cr-declaration.h"
+
+#include "svg/css-ostringstream.h"
+
+#include "xml/repr.h"
+#include "xml/simple-document.h"
+#include "xml/sp-css-attr.h"
+
+using Inkscape::XML::SimpleNode;
+using Inkscape::XML::Node;
+using Inkscape::XML::NodeType;
+using Inkscape::XML::Document;
+
+struct SPCSSAttrImpl : public SimpleNode, public SPCSSAttr {
+public:
+ SPCSSAttrImpl(Document *doc)
+ : SimpleNode(g_quark_from_static_string("css"), doc) {}
+ SPCSSAttrImpl(SPCSSAttrImpl const &other, Document *doc)
+ : SimpleNode(other, doc) {}
+
+ NodeType type() const override { return Inkscape::XML::NodeType::ELEMENT_NODE; }
+
+protected:
+ SimpleNode *_duplicate(Document* doc) const override { return new SPCSSAttrImpl(*this, doc); }
+};
+
+static void sp_repr_css_add_components(SPCSSAttr *css, Node const *repr, gchar const *attr);
+
+/**
+ * Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
+ */
+SPCSSAttr *sp_repr_css_attr_new()
+{
+ static Inkscape::XML::Document *attr_doc=nullptr;
+ if (!attr_doc) {
+ attr_doc = new Inkscape::XML::SimpleDocument();
+ }
+ return new SPCSSAttrImpl(attr_doc);
+}
+
+/**
+ * Unreferences an SPCSSAttr (will be garbage collected if no references remain).
+ */
+void sp_repr_css_attr_unref(SPCSSAttr *css)
+{
+ g_assert(css != nullptr);
+ Inkscape::GC::release((Node *) css);
+}
+
+/**
+ * Creates a new SPCSSAttr with one attribute (i.e. style) copied from an existing repr (node). The
+ * repr attribute data is in the form of a char const * string (e.g. fill:#00ff00;stroke:none). The
+ * string is parsed by libcroco which returns a CRDeclaration list (a typical C linked list) of
+ * properties and values. This list is then used to fill the attributes of the new SPCSSAttr.
+ */
+SPCSSAttr *sp_repr_css_attr(Node const *repr, gchar const *attr)
+{
+ g_assert(repr != nullptr);
+ g_assert(attr != nullptr);
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_add_components(css, repr, attr);
+ return css;
+}
+
+
+/**
+ * Adds an attribute to an existing SPCSAttr with the cascaded value including all parents.
+ */
+static void sp_repr_css_attr_inherited_recursive(SPCSSAttr *css, Node const *repr, gchar const *attr)
+{
+ const Node *parent = repr->parent();
+
+ // read the ancestors from root down, using head recursion, so that children override parents
+ if (parent) {
+ sp_repr_css_attr_inherited_recursive(css, parent, attr);
+ }
+ sp_repr_css_add_components(css, repr, attr);
+}
+
+/**
+ * Creates a new SPCSSAttr with one attribute whose value is determined by cascading.
+ */
+SPCSSAttr *sp_repr_css_attr_inherited(Node const *repr, gchar const *attr)
+{
+ g_assert(repr != nullptr);
+ g_assert(attr != nullptr);
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+
+ sp_repr_css_attr_inherited_recursive(css, repr, attr);
+
+ return css;
+}
+
+/**
+ * Adds components (style properties) to an existing SPCSAttr from the specified attribute's data
+ * (nominally a style attribute).
+ *
+ */
+static void sp_repr_css_add_components(SPCSSAttr *css, Node const *repr, gchar const *attr)
+{
+ g_assert(css != nullptr);
+ g_assert(repr != nullptr);
+ g_assert(attr != nullptr);
+
+ char const *data = repr->attribute(attr);
+ sp_repr_css_attr_add_from_string(css, data);
+}
+
+/**
+ * Returns a character string of the value of a given style property or a default value if the
+ * attribute is not found.
+ */
+char const *sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
+{
+ g_assert(css != nullptr);
+ g_assert(name != nullptr);
+
+ char const *attr = ((Node *)css)->attribute(name);
+ return ( attr == nullptr
+ ? defval
+ : attr );
+}
+
+/**
+ * Returns a character string of the value of a given style property or a default value if the
+ * attribute is not found.
+ */
+Glib::ustring sp_repr_css_property(SPCSSAttr *css, Glib::ustring const &name, Glib::ustring const &defval)
+{
+ g_assert(css != nullptr);
+
+ Glib::ustring retval = defval;
+ char const *attr = ((Node *)css)->attribute(name.c_str());
+ if (attr) {
+ retval = attr;
+ }
+
+ return retval;
+}
+
+/**
+ * Returns true if a style property is present and its value is unset.
+ */
+bool sp_repr_css_property_is_unset(SPCSSAttr *css, gchar const *name)
+{
+ g_assert(css != nullptr);
+ g_assert(name != nullptr);
+
+ char const *attr = ((Node *)css)->attribute(name);
+ return (attr && !strcmp(attr, "inkscape:unset"));
+}
+
+
+/**
+ * Set a style property to a new value (e.g. fill to #ffff00).
+ */
+void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
+{
+ g_assert(css != nullptr);
+ g_assert(name != nullptr);
+
+ ((Node *) css)->setAttribute(name, value);
+}
+
+/**
+ * Set a style property to "inkscape:unset".
+ */
+void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
+{
+ g_assert(css != nullptr);
+ g_assert(name != nullptr);
+
+ ((Node *) css)->setAttribute(name, "inkscape:unset");
+}
+
+/**
+ * Return the value of a style property if property define, or a default value if not.
+ */
+double sp_repr_css_double_property(SPCSSAttr *css, gchar const *name, double defval)
+{
+ g_assert(css != nullptr);
+ g_assert(name != nullptr);
+
+ return css->getAttributeDouble(name, defval);
+}
+
+/**
+ * Set a style property to a new float value (e.g. opacity to 0.5).
+ */
+void sp_repr_css_set_property_double(SPCSSAttr *css, gchar const *name, double value)
+{
+ g_assert(css != nullptr);
+ g_assert(name != nullptr);
+
+ ((Node *) css)->setAttributeCssDouble(name, value);
+}
+
+/**
+ * Write a style attribute string from a list of properties stored in an SPCSAttr object.
+ */
+void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str)
+{
+ str.clear();
+ for (const auto & iter : css->attributeList())
+ {
+ if (iter.value && !strcmp(iter.value, "inkscape:unset")) {
+ continue;
+ }
+
+ if (!str.empty()) {
+ str.push_back(';');
+ }
+
+ str.append(g_quark_to_string(iter.key));
+ str.push_back(':');
+ str.append(iter.value); // Any necessary quoting to be done by calling routine.
+ }
+}
+
+/**
+ * Sets an attribute (e.g. style) to a string created from a list of style properties.
+ */
+void sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
+{
+ g_assert(repr != nullptr);
+ g_assert(css != nullptr);
+ g_assert(attr != nullptr);
+
+ Glib::ustring value;
+ sp_repr_css_write_string(css, value);
+
+ /*
+ * If the new value is different from the old value, this will sometimes send a signal via
+ * CompositeNodeObserver::notiftyAttributeChanged() which results in calling
+ * SPObject::repr_attr_changed and thus updates the object's SPStyle. This update
+ * results in another call to repr->setAttribute().
+ */
+ repr->setAttributeOrRemoveIfEmpty(attr, value);
+}
+
+/**
+ * Loops through a List of style properties, printing key/value pairs.
+ */
+void sp_repr_css_print(SPCSSAttr *css)
+{
+ for ( const auto & attr: css->attributeList() )
+ {
+ gchar const * key = g_quark_to_string(attr.key);
+ gchar const * val = attr.value;
+ g_print("%s:\t%s\n",key,val);
+ }
+}
+
+/**
+ * Merges two SPCSSAttr's. Properties in src overwrite properties in dst if present in both.
+ */
+void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
+{
+ g_assert(dst != nullptr);
+ g_assert(src != nullptr);
+
+ dst->mergeFrom(src, "");
+}
+
+/**
+ * Merges style properties as parsed by libcroco into an existing SPCSSAttr.
+ * libcroco converts all single quotes to double quotes, which needs to be
+ * undone as we always use single quotes inside our 'style' strings since
+ * double quotes are used outside: e.g.:
+ * style="font-family:'DejaVu Sans'"
+ */
+static void sp_repr_css_merge_from_decl(SPCSSAttr *css, CRDeclaration const *const decl)
+{
+ guchar *const str_value_unsigned = cr_term_to_string(decl->value);
+ css->setAttribute(decl->property->stryng->str, reinterpret_cast<char const *>(str_value_unsigned));
+ g_free(str_value_unsigned);
+}
+
+/**
+ * Merges style properties as parsed by libcroco into an existing SPCSSAttr.
+ *
+ * \pre decl_list != NULL
+ */
+static void sp_repr_css_merge_from_decl_list(SPCSSAttr *css, CRDeclaration const *const decl_list)
+{
+ // read the decls from start to end, using tail recursion, so that latter declarations override
+ // (Ref: http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order point 4.)
+ // because sp_repr_css_merge_from_decl sets properties unconditionally
+ sp_repr_css_merge_from_decl(css, decl_list);
+ if (decl_list->next) {
+ sp_repr_css_merge_from_decl_list(css, decl_list->next);
+ }
+}
+
+/**
+ * Use libcroco to parse a string for CSS properties and then merge
+ * them into an existing SPCSSAttr.
+ */
+void sp_repr_css_attr_add_from_string(SPCSSAttr *css, gchar const *p)
+{
+ if (p != nullptr) {
+ CRDeclaration *const decl_list
+ = cr_declaration_parse_list_from_buf(reinterpret_cast<guchar const *>(p), CR_UTF_8);
+ if (decl_list) {
+ sp_repr_css_merge_from_decl_list(css, decl_list);
+ cr_declaration_destroy(decl_list);
+ }
+ }
+}
+
+/**
+ * Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given
+ * SPCSAttr, and then replaces that SPCSAttr with the new one. This is called, for example, for
+ * each object in turn when a selection's style is updated via sp_desktop_set_style().
+ */
+void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
+{
+ g_assert(repr != nullptr);
+ g_assert(css != nullptr);
+ g_assert(attr != nullptr);
+
+ SPCSSAttr *current = sp_repr_css_attr(repr, attr);
+ sp_repr_css_merge(current, css);
+ sp_repr_css_set(repr, current, attr);
+
+ sp_repr_css_attr_unref(current);
+}
+
+void sp_repr_css_change_recursive(Node *repr, SPCSSAttr *css, gchar const *attr)
+{
+ g_assert(repr != nullptr);
+ g_assert(css != nullptr);
+ g_assert(attr != nullptr);
+
+ sp_repr_css_change(repr, css, attr);
+
+ for (Node *child = repr->firstChild(); child != nullptr; child = child->next()) {
+ sp_repr_css_change_recursive(child, css, attr);
+ }
+}
+
+/**
+ * Return a new SPCSSAttr with all the properties found in the input SPCSSAttr unset.
+ */
+SPCSSAttr* sp_repr_css_attr_unset_all(SPCSSAttr *css)
+{
+ SPCSSAttr* css_unset = sp_repr_css_attr_new();
+ for ( const auto & iter : css->attributeList() ) {
+ sp_repr_css_set_property (css_unset, g_quark_to_string(iter.key), "inkscape:unset");
+ }
+ return css_unset;
+}
+
+/*
+ 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 :