diff options
Diffstat (limited to 'src/object/sp-style-elem.cpp')
-rw-r--r-- | src/object/sp-style-elem.cpp | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/src/object/sp-style-elem.cpp b/src/object/sp-style-elem.cpp new file mode 100644 index 0000000..f543289 --- /dev/null +++ b/src/object/sp-style-elem.cpp @@ -0,0 +1,567 @@ +// 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. + */ +#include <3rdparty/libcroco/cr-parser.h> +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include "document.h" +#include "sp-style-elem.h" +#include "sp-root.h" +#include "attributes.h" +#include "style.h" + +// For external style sheets +#include "io/resource.h" +#include <iostream> +#include <fstream> + +// For font-rule +#include "libnrtype/FontFactory.h" + +using Inkscape::XML::TEXT_NODE; + +SPStyleElem::SPStyleElem() : SPObject() { + media_set_all(this->media); + this->is_css = false; + this->style_sheet = nullptr; +} + +SPStyleElem::~SPStyleElem() = default; + +void SPStyleElem::set(SPAttributeEnum key, const gchar* value) { + switch (key) { + case SP_ATTR_TYPE: { + if (!value) { + /* TODO: `type' attribute is required. Give error message as per + http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */ + is_css = false; + } else { + /* fixme: determine what whitespace is allowed. Will probably need to ask on SVG + list; though the relevant RFC may give info on its lexer. */ + is_css = ( g_ascii_strncasecmp(value, "text/css", 8) == 0 + && ( value[8] == '\0' || + value[8] == ';' ) ); + } + break; + } + +#if 0 /* unfinished */ + case SP_ATTR_MEDIA: { + parse_media(style_elem, value); + break; + } +#endif + + /* title is ignored. */ + default: { + SPObject::set(key, value); + break; + } + } +} + + +static void +child_add_rm_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, Inkscape::XML::Node *, + void *const data) +{ + SPObject *obj = reinterpret_cast<SPObject *>(data); + g_assert(data != nullptr); + obj->read_content(); +} + +static void +content_changed_cb(Inkscape::XML::Node *, gchar const *, gchar const *, + void *const data) +{ + SPObject *obj = reinterpret_cast<SPObject *>(data); + g_assert(data != nullptr); + obj->read_content(); + obj->document->getRoot()->emitModified( SP_OBJECT_MODIFIED_CASCADE ); +} + +static void +child_order_changed_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, + Inkscape::XML::Node *, Inkscape::XML::Node *, + void *const data) +{ + SPObject *obj = reinterpret_cast<SPObject *>(data); + g_assert(data != nullptr); + obj->read_content(); +} + +Inkscape::XML::Node* SPStyleElem::write(Inkscape::XML::Document* xml_doc, Inkscape::XML::Node* repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:style"); + } + + if (flags & SP_OBJECT_WRITE_BUILD) { + g_warning("nyi: Forming <style> content for SP_OBJECT_WRITE_BUILD."); + /* fixme: Consider having the CRStyleSheet be a member of SPStyleElem, and then + pretty-print to a string s, then repr->addChild(xml_doc->createTextNode(s), NULL). */ + } + if (is_css) { + repr->setAttribute("type", "text/css"); + } + /* todo: media */ + + SPObject::write(xml_doc, repr, flags); + + return repr; +} + + +/** Returns the concatenation of the content of the text children of the specified object. */ +static Glib::ustring +concat_children(Inkscape::XML::Node const &repr) +{ + Glib::ustring ret; + // effic: Initialising ret to a reasonable starting size could speed things up. + for (Inkscape::XML::Node const *rch = repr.firstChild(); rch != nullptr; rch = rch->next()) { + if ( rch->type() == TEXT_NODE ) { + ret += rch->content(); + } + } + return ret; +} + + + +/* Callbacks for SAC-style libcroco parser. */ + +enum StmtType { NO_STMT, FONT_FACE_STMT, NORMAL_RULESET_STMT }; + +struct ParseTmp +{ + CRStyleSheet *const stylesheet; + StmtType stmtType; + CRStatement *currStmt; + SPDocument *const document; // Need file location for '@import' + unsigned magic; + static unsigned const ParseTmp_magic = 0x23474397; // from /dev/urandom + + ParseTmp(CRStyleSheet *const stylesheet, SPDocument *const document) : + stylesheet(stylesheet), + stmtType(NO_STMT), + currStmt(nullptr), + document(document), + magic(ParseTmp_magic) + { } + + bool hasMagic() const { + return magic == ParseTmp_magic; + } + + ~ParseTmp() + { + g_return_if_fail(hasMagic()); + magic = 0; + } +}; + +CRParser* +parser_init(CRStyleSheet *const stylesheet, SPDocument *const document); + +static void +import_style_cb (CRDocHandler *a_handler, + GList *a_media_list, + CRString *a_uri, + CRString *a_uri_default_ns, + CRParsingLocation *a_location) +{ + /* a_uri_default_ns is set to NULL and is unused by libcroco */ + + // Get document + g_return_if_fail(a_handler && a_uri); + g_return_if_fail(a_handler->app_data != nullptr); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + + SPDocument* document = parse_tmp.document; + if (!document) { + std::cerr << "import_style_cb: No document!" << std::endl; + return; + } + if (!document->getDocumentURI()) { + std::cerr << "import_style_cb: Document URI is NULL" << std::endl; + return; + } + + // Get file + Glib::ustring import_file = + Inkscape::IO::Resource::get_filename (document->getDocumentURI(), a_uri->stryng->str); + + // Parse file + CRStyleSheet *stylesheet = cr_stylesheet_new (nullptr); + CRParser *parser = parser_init(stylesheet, document); + CRStatus const parse_status = + cr_parser_parse_file (parser, reinterpret_cast<const guchar *>(import_file.c_str()), CR_UTF_8); + if (parse_status == CR_OK) { + if (!document->getStyleSheet()) { + // if the style is the first style sheet that we've seen, set the document's + // first style sheet to this style and create a cascade object with it. + document->setStyleSheet(stylesheet); + cr_cascade_set_sheet(document->getStyleCascade(), document->getStyleSheet(), ORIGIN_AUTHOR); + } else { + // If not the first, then chain up this style_sheet + cr_stylesheet_append_import(document->getStyleSheet(), stylesheet); + } + } else { + std::cerr << "import_style_cb: Could not parse: " << import_file << std::endl; + cr_stylesheet_destroy (stylesheet); + } + + // Need to delete ParseTmp created by parser_init() + CRDocHandler *sac_handler = nullptr; + cr_parser_get_sac_handler (parser, &sac_handler); + ParseTmp *parse_new = reinterpret_cast<ParseTmp *>(sac_handler->app_data); + cr_parser_destroy(parser); + delete parse_new; +}; + +#if 0 +/* FIXME: NOT USED, incomplete libcroco implementation */ +static void +import_style_result_cb (CRDocHandler *a_this, + GList *a_media_list, + CRString *a_uri, + CRString *a_uri_default_ns, + CRStyleSheet *a_sheet) +{ + /* a_uri_default_ns and a_sheet are set to NULL and are unused by libcroco */ + std::cerr << "import_style_result_cb: unimplemented" << std::endl; +}; +#endif + +static void +start_selector_cb(CRDocHandler *a_handler, + CRSelector *a_sel_list) +{ + g_return_if_fail(a_handler && a_sel_list); + g_return_if_fail(a_handler->app_data != nullptr); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + if ( (parse_tmp.currStmt != nullptr) + || (parse_tmp.stmtType != NO_STMT) ) { + g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of ruleset, but found currStmt=%p, stmtType=%u", + static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType)); + // fixme: Check whether we need to unref currStmt if non-NULL. + } + CRStatement *ruleset = cr_statement_new_ruleset(parse_tmp.stylesheet, a_sel_list, nullptr, nullptr); + g_return_if_fail(ruleset && ruleset->type == RULESET_STMT); + parse_tmp.stmtType = NORMAL_RULESET_STMT; + parse_tmp.currStmt = ruleset; +} + +static void +end_selector_cb(CRDocHandler *a_handler, + CRSelector *a_sel_list) +{ + g_return_if_fail(a_handler && a_sel_list); + g_return_if_fail(a_handler->app_data != nullptr); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + CRStatement *const ruleset = parse_tmp.currStmt; + if (parse_tmp.stmtType == NORMAL_RULESET_STMT + && ruleset + && ruleset->type == RULESET_STMT + && ruleset->kind.ruleset->sel_list == a_sel_list) + { + parse_tmp.stylesheet->statements = cr_statement_append(parse_tmp.stylesheet->statements, + ruleset); + } else { + g_warning("Found stmtType=%u, stmt=%p, stmt.type=%u, ruleset.sel_list=%p, a_sel_list=%p.", + unsigned(parse_tmp.stmtType), + ruleset, + unsigned(ruleset->type), + ruleset->kind.ruleset->sel_list, + a_sel_list); + } + parse_tmp.currStmt = nullptr; + parse_tmp.stmtType = NO_STMT; +} + +static void +start_font_face_cb(CRDocHandler *a_handler, + CRParsingLocation *) +{ + g_return_if_fail(a_handler->app_data != nullptr); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + if (parse_tmp.stmtType != NO_STMT || parse_tmp.currStmt != nullptr) { + g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of @font-face, but found currStmt=%p, stmtType=%u", + static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType)); + // fixme: Check whether we need to unref currStmt if non-NULL. + } + CRStatement *font_face_rule = cr_statement_new_at_font_face_rule (parse_tmp.stylesheet, nullptr); + g_return_if_fail(font_face_rule && font_face_rule->type == AT_FONT_FACE_RULE_STMT); + parse_tmp.stmtType = FONT_FACE_STMT; + parse_tmp.currStmt = font_face_rule; +} + +static void +end_font_face_cb(CRDocHandler *a_handler) +{ + g_return_if_fail(a_handler->app_data != nullptr); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + + CRStatement *const font_face_rule = parse_tmp.currStmt; + if (parse_tmp.stmtType == FONT_FACE_STMT + && font_face_rule + && font_face_rule->type == AT_FONT_FACE_RULE_STMT) + { + parse_tmp.stylesheet->statements = cr_statement_append(parse_tmp.stylesheet->statements, + font_face_rule); + } else { + g_warning("Found stmtType=%u, stmt=%p, stmt.type=%u.", + unsigned(parse_tmp.stmtType), + font_face_rule, + unsigned(font_face_rule->type)); + } + + std::cout << "end_font_face_cb: font face rule limited support." << std::endl; + cr_declaration_dump (font_face_rule->kind.font_face_rule->decl_list, stdout, 2, TRUE); + printf ("\n"); + + // Get document + SPDocument* document = parse_tmp.document; + if (!document) { + std::cerr << "end_font_face_cb: No document!" << std::endl; + return; + } + if (!document->getDocumentURI()) { + std::cerr << "end_font_face_cb: Document URI is NULL" << std::endl; + return; + } + + // Add ttf or otf fonts. + CRDeclaration const *cur = nullptr; + for (cur = font_face_rule->kind.font_face_rule->decl_list; cur; cur = cur->next) { + if (cur->property && + cur->property->stryng && + cur->property->stryng->str && + strcmp(cur->property->stryng->str, "src") == 0 ) { + + if (cur->value && + cur->value->content.str && + cur->value->content.str->stryng && + cur->value->content.str->stryng->str) { + + Glib::ustring value = cur->value->content.str->stryng->str; + + if (value.rfind("ttf") == (value.length() - 3) || + value.rfind("otf") == (value.length() - 3)) { + + // Get file + Glib::ustring ttf_file = + Inkscape::IO::Resource::get_filename (document->getDocumentURI(), value); + + if (!ttf_file.empty()) { + font_factory *factory = font_factory::Default(); + factory->AddFontFile( ttf_file.c_str() ); + std::cout << "end_font_face_cb: Added font: " << ttf_file << std::endl; + + // FIX ME: Need to refresh font list. + } else { + std::cout << "end_font_face_cb: Failed to add: " << value << std::endl; + } + } + } + } + } + + parse_tmp.currStmt = nullptr; + parse_tmp.stmtType = NO_STMT; + +} + +static void +property_cb(CRDocHandler *const a_handler, + CRString *const a_name, + CRTerm *const a_value, gboolean const a_important) +{ + // std::cout << "property_cb: Entrance: " << a_name->stryng->str << ": " << cr_term_to_string(a_value) << std::endl; + g_return_if_fail(a_handler && a_name); + g_return_if_fail(a_handler->app_data != nullptr); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + + CRStatement *const ruleset = parse_tmp.currStmt; + g_return_if_fail(ruleset); + + CRDeclaration *const decl = cr_declaration_new (ruleset, cr_string_dup(a_name), a_value); + g_return_if_fail(decl); + decl->important = a_important; + + switch (parse_tmp.stmtType) { + + case NORMAL_RULESET_STMT: { + g_return_if_fail (ruleset->type == RULESET_STMT); + CRStatus const append_status = cr_statement_ruleset_append_decl (ruleset, decl); + g_return_if_fail (append_status == CR_OK); + break; + } + case FONT_FACE_STMT: { + g_return_if_fail (ruleset->type == AT_FONT_FACE_RULE_STMT); + CRDeclaration *new_decls = cr_declaration_append (ruleset->kind.font_face_rule->decl_list, decl); + g_return_if_fail (new_decls); + ruleset->kind.font_face_rule->decl_list = new_decls; + break; + } + default: + g_warning ("property_cb: Unhandled stmtType: %u", parse_tmp.stmtType); + return; + } +} + +CRParser* +parser_init(CRStyleSheet *const stylesheet, SPDocument *const document) { + + ParseTmp *parse_tmp = new ParseTmp(stylesheet, document); + + CRDocHandler *sac_handler = cr_doc_handler_new(); + sac_handler->app_data = parse_tmp; + sac_handler->import_style = import_style_cb; + sac_handler->start_selector = start_selector_cb; + sac_handler->end_selector = end_selector_cb; + sac_handler->start_font_face = start_font_face_cb; + sac_handler->end_font_face = end_font_face_cb; + sac_handler->property = property_cb; + + CRParser *parser = cr_parser_new (nullptr); + cr_parser_set_sac_handler(parser, sac_handler); + + return parser; +} + +void update_style_recursively( SPObject *object ) { + if (object) { + // std::cout << "update_style_recursively: " + // << (object->getId()?object->getId():"null") << std::endl; + if (object->style) { + object->style->readFromObject( object ); + } + for (auto& child : object->children) { + update_style_recursively( &child ); + } + } +} + +void SPStyleElem::read_content() { + + // First, create the style-sheet object and track it in this + // element so that it can be edited. It'll be combined with + // the document's style sheet later. + style_sheet = cr_stylesheet_new (nullptr); + CRParser *parser = parser_init(style_sheet, document); + + CRDocHandler *sac_handler = nullptr; + cr_parser_get_sac_handler (parser, &sac_handler); + ParseTmp *parse_tmp = reinterpret_cast<ParseTmp *>(sac_handler->app_data); + + //XML Tree being used directly here while it shouldn't be. + Glib::ustring const text = concat_children(*getRepr()); + if (!(text.find_first_not_of(" \t\r\n") != std::string::npos)) { + return; + } + CRStatus const parse_status = + cr_parser_parse_buf (parser, reinterpret_cast<const guchar *>(text.c_str()), text.bytes(), CR_UTF_8); + + if (parse_status == CR_OK) { + if(!document->getStyleSheet()) { + // if the style is the first style sheet that we've seen, set the document's + // first style sheet to this style and create a cascade object with it. + document->setStyleSheet(style_sheet); + cr_cascade_set_sheet (document->getStyleCascade(), document->getStyleSheet(), ORIGIN_AUTHOR); + } else { + // If not the first, then chain up this style_sheet + cr_stylesheet_append_stylesheet (document->getStyleSheet(), style_sheet); + } + } else { + cr_stylesheet_destroy (style_sheet); + style_sheet = nullptr; + if (parse_status != CR_PARSING_ERROR) { + g_printerr("parsing error code=%u\n", unsigned(parse_status)); + } + } + + cr_parser_destroy(parser); + delete parse_tmp; + gint count = 0; + if (style_sheet) { + // Record each css statement as an SPStyle + count = cr_stylesheet_nr_rules(style_sheet); + } + // Clean out any previous styles + for (auto& style:styles) + sp_style_unref(style); + styles.clear(); + + for (gint x = 0; x < count; x++) { + SPStyle *item = new SPStyle(nullptr, nullptr); + CRStatement *statement = cr_stylesheet_statement_get_from_list(style_sheet, x); + item->mergeStatement(statement); + styles.push_back(item); + } + // If style sheet has changed, we need to cascade the entire object tree, top down + // Get root, read style, loop through children + update_style_recursively( (SPObject *)document->getRoot() ); + // cr_stylesheet_dump (document->getStyleSheet(), stdout); +} + +/** + * Does addListener(fns, data) on \a repr and all of its descendents. + */ +static void +rec_add_listener(Inkscape::XML::Node &repr, + Inkscape::XML::NodeEventVector const *const fns, void *const data) +{ + repr.addListener(fns, data); + for (Inkscape::XML::Node *child = repr.firstChild(); child != nullptr; child = child->next()) { + rec_add_listener(*child, fns, data); + } +} + +void SPStyleElem::build(SPDocument *document, Inkscape::XML::Node *repr) { + read_content(); + + readAttr( "type" ); + readAttr( "media" ); + + static Inkscape::XML::NodeEventVector const nodeEventVector = { + child_add_rm_cb, // child_added + child_add_rm_cb, // child_removed + nullptr, // attr_changed + content_changed_cb, // content_changed + child_order_changed_cb, // order_changed + }; + rec_add_listener(*repr, &nodeEventVector, this); + + SPObject::build(document, repr); +} + +void SPStyleElem::release() { + for (auto& style:styles) + sp_style_unref(style); + styles.clear(); + SPObject::release(); +} + + +/* + 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 : |