diff options
Diffstat (limited to 'src/sp-style-elem.cpp')
| -rw-r--r-- | src/sp-style-elem.cpp | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/src/sp-style-elem.cpp b/src/sp-style-elem.cpp new file mode 100644 index 000000000..ff27f3de0 --- /dev/null +++ b/src/sp-style-elem.cpp @@ -0,0 +1,407 @@ +#include <libcroco/cr-parser.h> +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include "document.h" +#include "sp-style-elem.h" +#include "attributes.h" +using Inkscape::XML::TEXT_NODE; + +static void sp_style_elem_init(SPStyleElem *style_elem); +static void sp_style_elem_class_init(SPStyleElemClass *klass); +static void sp_style_elem_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr); +static void sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value); +static void sp_style_elem_read_content(SPObject *); +static Inkscape::XML::Node *sp_style_elem_write(SPObject *, Inkscape::XML::Node *, guint flags); + +static SPObjectClass *parent_class; + +GType +sp_style_elem_get_type() +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPStyleElemClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_style_elem_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(SPStyleElem), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_style_elem_init, + NULL, /* value_table */ + }; + type = g_type_register_static(SP_TYPE_OBJECT, "SPStyleElem", &info, (GTypeFlags) 0); + } + + return type; +} + +static void +sp_style_elem_class_init(SPStyleElemClass *klass) +{ + parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT); + /* FIXME */ + + klass->build = sp_style_elem_build; + klass->set = sp_style_elem_set; + klass->read_content = sp_style_elem_read_content; + klass->write = sp_style_elem_write; +} + +static void +sp_style_elem_init(SPStyleElem *style_elem) +{ + media_set_all(style_elem->media); + style_elem->is_css = false; +} + +static void +sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value) +{ + g_return_if_fail(object); + SPStyleElem &style_elem = *SP_STYLE_ELEM(object); + + 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. */ + style_elem.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. */ + style_elem.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: { + if (parent_class->set) { + parent_class->set(object, key, value); + } + break; + } + } +} + +static void +child_add_rm_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, Inkscape::XML::Node *, + void *const data) +{ + sp_style_elem_read_content(static_cast<SPObject *>(data)); +} + +static void +content_changed_cb(Inkscape::XML::Node *, gchar const *, gchar const *, + void *const data) +{ + sp_style_elem_read_content(static_cast<SPObject *>(data)); +} + +static void +child_order_changed_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, + Inkscape::XML::Node *, Inkscape::XML::Node *, + void *const data) +{ + sp_style_elem_read_content(static_cast<SPObject *>(data)); +} + +static Inkscape::XML::Node * +sp_style_elem_write(SPObject *const object, Inkscape::XML::Node *repr, guint const flags) +{ + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new("svg:style"); + } + + g_return_val_if_fail(object, repr); + SPStyleElem &style_elem = *SP_STYLE_ELEM(object); + 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(sp_repr_new_text(s), NULL). */ + } + if (style_elem.is_css) { + repr->setAttribute("type", "text/css"); + } + /* todo: media */ + + if (((SPObjectClass *) parent_class)->write) + ((SPObjectClass *) parent_class)->write(object, repr, flags); + + return repr; +} + + +/** Returns the concatenation of the content of the text children of the specified object. */ +static GString * +concat_children(Inkscape::XML::Node const &repr) +{ + GString *ret = g_string_sized_new(0); + // effic: 0 is just to catch bugs. Increase to something reasonable. + for (Inkscape::XML::Node const *rch = repr.firstChild(); rch != NULL; rch = rch->next()) { + if ( rch->type() == TEXT_NODE ) { + ret = g_string_append(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; + unsigned magic; + static unsigned const ParseTmp_magic = 0x23474397; // from /dev/urandom + + ParseTmp(CRStyleSheet *const stylesheet) : + stylesheet(stylesheet), + stmtType(NO_STMT), + currStmt(NULL), + magic(ParseTmp_magic) + { } + + bool hasMagic() const { + return magic == ParseTmp_magic; + } + + ~ParseTmp() + { + g_return_if_fail(hasMagic()); + magic = 0; + } +}; + +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 != NULL); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + if ( (parse_tmp.currStmt != NULL) + || (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, NULL, NULL); + 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 != NULL); + 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 = NULL; + parse_tmp.stmtType = NO_STMT; +} + +static void +start_font_face_cb(CRDocHandler *a_handler, + CRParsingLocation *) +{ + g_return_if_fail(a_handler->app_data != NULL); + 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 != NULL) { + 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. + } + parse_tmp.stmtType = FONT_FACE_STMT; + parse_tmp.currStmt = NULL; +} + +static void +end_font_face_cb(CRDocHandler *a_handler) +{ + g_return_if_fail(a_handler->app_data != NULL); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + if (parse_tmp.stmtType != FONT_FACE_STMT || parse_tmp.currStmt != NULL) { + g_warning("Expecting currStmt==NULL and stmtType==1 (FONT_FACE_STMT) at end 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. + parse_tmp.currStmt = NULL; + } + 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) +{ + g_return_if_fail(a_handler && a_name); + g_return_if_fail(a_handler->app_data != NULL); + ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data); + g_return_if_fail(parse_tmp.hasMagic()); + if (parse_tmp.stmtType == FONT_FACE_STMT) { + if (parse_tmp.currStmt != NULL) { + g_warning("Found non-NULL currStmt %p though stmtType==FONT_FACE_STMT.", parse_tmp.currStmt); + } + /* We currently ignore @font-face descriptors. */ + return; + } + CRStatement *const ruleset = parse_tmp.currStmt; + g_return_if_fail(ruleset + && ruleset->type == RULESET_STMT + && parse_tmp.stmtType == NORMAL_RULESET_STMT); + CRDeclaration *const decl = cr_declaration_new(ruleset, cr_string_dup(a_name), a_value); + g_return_if_fail(decl); + decl->important = a_important; + CRStatus const append_status = cr_statement_ruleset_append_decl(ruleset, decl); + g_return_if_fail(append_status == CR_OK); +} + +static void +sp_style_elem_read_content(SPObject *const object) +{ + SPStyleElem &style_elem = *SP_STYLE_ELEM(object); + + /* fixme: If there's more than one <style> element in a document, then the document stylesheet + * will be set to a random one of them, even switching between them. + * + * However, I don't see in the spec what's supposed to happen when there are multiple <style> + * elements. The wording suggests that <style>'s content should be a full stylesheet. + * http://www.w3.org/TR/REC-CSS2/cascade.html#cascade says that "The author specifies style + * sheets for a source document according to the conventions of the document language. For + * instance, in HTML, style sheets may be included in the document or linked externally." + * (Note the plural in both sentences.) Whereas libcroco's CRCascade allows only one author + * stylesheet. CRStyleSheet has no next/prev members that I can see, nor can I see any append + * stuff. + * + * Dodji replies "right, that's *bug*"; just an unexpected oversight. + */ + + GString *const text = concat_children(*style_elem.repr); + CRParser *parser = cr_parser_new_from_buf(reinterpret_cast<guchar *>(text->str), text->len, + CR_UTF_8, FALSE); + + /* I see a cr_statement_parse_from_buf for returning a CRStatement*, but no corresponding + cr_stylesheet_parse_from_buf. And cr_statement_parse_from_buf takes a char*, not a + CRInputBuf, and doesn't provide a way for calling it in a loop over the one buffer. + (I.e. it doesn't tell us where it got up to in the buffer.) + + There's also the generic cr_parser_parse_stylesheet (or just cr_parser_parse), but that + just calls user-supplied callbacks rather than constructing a CRStylesheet. + */ + CRDocHandler *sac_handler = cr_doc_handler_new(); + // impl: ref_count inited to 0, so cr_parser_destroy suffices to delete sac_handler. + g_return_if_fail(sac_handler); // out of memory + CRStyleSheet *const stylesheet = cr_stylesheet_new(NULL); + ParseTmp parse_tmp(stylesheet); + sac_handler->app_data = &parse_tmp; + 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; + /* todo: start_media, end_media. */ + /* todo: Test error condition. */ + cr_parser_set_sac_handler(parser, sac_handler); + CRStatus const parse_status = cr_parser_parse(parser); + g_assert(sac_handler->app_data == &parse_tmp); + if (parse_status == CR_OK) { + cr_cascade_set_sheet(style_elem.document->style_cascade, stylesheet, ORIGIN_AUTHOR); + } else { + if (parse_status != CR_PARSING_ERROR) { + g_printerr("parsing error code=%u\n", unsigned(parse_status)); + /* Better than nothing. TODO: Improve libcroco's error handling. At a minimum, add a + strerror-like function so that we can give a string rather than an integer. */ + /* TODO: Improve error diagnosis stuff in inkscape. We'd like a panel showing the + errors/warnings/unsupported features of the current document. */ + } + } + cr_parser_destroy(parser); + //object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * 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 != NULL; child = child->next()) { + rec_add_listener(*child, fns, data); + } +} + +static void +sp_style_elem_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + sp_style_elem_read_content(object); + + sp_object_read_attr(object, "type"); + sp_object_read_attr(object, "media"); + + static Inkscape::XML::NodeEventVector const nodeEventVector = { + child_add_rm_cb, // child_added + child_add_rm_cb, // child_removed + NULL, // attr_changed + content_changed_cb, // content_changed + child_order_changed_cb, // order_changed + }; + rec_add_listener(*repr, &nodeEventVector, object); + + if (((SPObjectClass *) parent_class)->build) { + ((SPObjectClass *) parent_class)->build(object, document, repr); + } +} + + +/* + 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:encoding=utf-8:textwidth=99 : |
