From 6722943b81c0dba84ed187b2d117cc92972117ed Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 26 May 2016 11:18:41 +0100 Subject: move the CSS content handler --- content/handlers/css/Makefile | 4 + content/handlers/css/css.c | 834 ++++++++++++++++++ content/handlers/css/css.h | 62 ++ content/handlers/css/dump.c | 1793 +++++++++++++++++++++++++++++++++++++ content/handlers/css/dump.h | 30 + content/handlers/css/hints.c | 1604 +++++++++++++++++++++++++++++++++ content/handlers/css/hints.h | 55 ++ content/handlers/css/internal.c | 63 ++ content/handlers/css/internal.h | 35 + content/handlers/css/select.c | 1854 +++++++++++++++++++++++++++++++++++++++ content/handlers/css/select.h | 58 ++ content/handlers/css/utils.c | 127 +++ content/handlers/css/utils.h | 49 ++ 13 files changed, 6568 insertions(+) create mode 100644 content/handlers/css/Makefile create mode 100644 content/handlers/css/css.c create mode 100644 content/handlers/css/css.h create mode 100644 content/handlers/css/dump.c create mode 100644 content/handlers/css/dump.h create mode 100644 content/handlers/css/hints.c create mode 100644 content/handlers/css/hints.h create mode 100644 content/handlers/css/internal.c create mode 100644 content/handlers/css/internal.h create mode 100644 content/handlers/css/select.c create mode 100644 content/handlers/css/select.h create mode 100644 content/handlers/css/utils.c create mode 100644 content/handlers/css/utils.h (limited to 'content/handlers/css') diff --git a/content/handlers/css/Makefile b/content/handlers/css/Makefile new file mode 100644 index 000000000..bbfc8d7b4 --- /dev/null +++ b/content/handlers/css/Makefile @@ -0,0 +1,4 @@ +# CSS sources + +S_CSS := css.c dump.c internal.c hints.c select.c utils.c + diff --git a/content/handlers/css/css.c b/content/handlers/css/css.c new file mode 100644 index 000000000..4c0cb7a4c --- /dev/null +++ b/content/handlers/css/css.c @@ -0,0 +1,834 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "content/content_protected.h" +#include "content/fetch.h" +#include "content/hlcache.h" +#include "desktop/system_colour.h" +#include "utils/corestrings.h" +#include "utils/utils.h" +#include "utils/http.h" +#include "utils/log.h" +#include "utils/messages.h" + +#include "css.h" +#include "hints.h" +#include "internal.h" + +/* Define to trace import fetches */ +#undef NSCSS_IMPORT_TRACE + +struct content_css_data; + +/** + * Type of callback called when a CSS object has finished + * + * \param css CSS object that has completed + * \param pw Client-specific data + */ +typedef void (*nscss_done_callback)(struct content_css_data *css, void *pw); + +/** + * CSS content data + */ +struct content_css_data +{ + css_stylesheet *sheet; /**< Stylesheet object */ + char *charset; /**< Character set of stylesheet */ + struct nscss_import *imports; /**< Array of imported sheets */ + uint32_t import_count; /**< Number of sheets imported */ + uint32_t next_to_register; /**< Index of next import to register */ + nscss_done_callback done; /**< Completion callback */ + void *pw; /**< Client data */ +}; + +/** + * CSS content data + */ +typedef struct nscss_content +{ + struct content base; /**< Underlying content object */ + + struct content_css_data data; /**< CSS data */ +} nscss_content; + +/** + * Context for import fetches + */ +typedef struct { + struct content_css_data *css; /**< Object containing import */ + uint32_t index; /**< Index into parent sheet's + * imports array */ +} nscss_import_ctx; + +static bool nscss_process_data(struct content *c, const char *data, + unsigned int size); +static bool nscss_convert(struct content *c); +static void nscss_destroy(struct content *c); +static nserror nscss_clone(const struct content *old, struct content **newc); +static bool nscss_matches_quirks(const struct content *c, bool quirks); +static content_type nscss_content_type(void); + +static nserror nscss_create_css_data(struct content_css_data *c, + const char *url, const char *charset, bool quirks, + nscss_done_callback done, void *pw); +static css_error nscss_process_css_data(struct content_css_data *c, const char *data, + unsigned int size); +static css_error nscss_convert_css_data(struct content_css_data *c); +static void nscss_destroy_css_data(struct content_css_data *c); + +static void nscss_content_done(struct content_css_data *css, void *pw); +static css_error nscss_handle_import(void *pw, css_stylesheet *parent, + lwc_string *url, uint64_t media); +static nserror nscss_import(hlcache_handle *handle, + const hlcache_event *event, void *pw); +static css_error nscss_import_complete(nscss_import_ctx *ctx); + +static css_error nscss_register_imports(struct content_css_data *c); +static css_error nscss_register_import(struct content_css_data *c, + const hlcache_handle *import); + + +static css_stylesheet *blank_import; + + +/** + * Initialise a CSS content + * + * \param handler content handler + * \param imime_type mime-type + * \param params Content-Type parameters + * \param llcache handle to content + * \param fallback_charset The character set to fallback to. + * \param quirks allow quirks + * \param c Content to initialise + * \return NSERROR_OK or error cod eon faliure + */ +static nserror +nscss_create(const content_handler *handler, + lwc_string *imime_type, + const http_parameter *params, + llcache_handle *llcache, + const char *fallback_charset, + bool quirks, + struct content **c) +{ + nscss_content *result; + const char *charset = NULL; + const char *xnsbase = NULL; + lwc_string *charset_value = NULL; + union content_msg_data msg_data; + nserror error; + + result = calloc(1, sizeof(nscss_content)); + if (result == NULL) + return NSERROR_NOMEM; + + error = content__init(&result->base, handler, imime_type, + params, llcache, fallback_charset, quirks); + if (error != NSERROR_OK) { + free(result); + return error; + } + + /* Find charset specified on HTTP layer, if any */ + error = http_parameter_list_find_item(params, corestring_lwc_charset, + &charset_value); + if (error != NSERROR_OK || lwc_string_length(charset_value) == 0) { + /* No charset specified, use fallback, if any */ + /** \todo libcss will take this as gospel, which is wrong */ + charset = fallback_charset; + } else { + charset = lwc_string_data(charset_value); + } + + /* Compute base URL for stylesheet */ + xnsbase = llcache_handle_get_header(llcache, "X-NS-Base"); + if (xnsbase == NULL) { + xnsbase = nsurl_access(content_get_url(&result->base)); + } + + error = nscss_create_css_data(&result->data, + xnsbase, charset, result->base.quirks, + nscss_content_done, result); + if (error != NSERROR_OK) { + msg_data.error = messages_get("NoMemory"); + content_broadcast(&result->base, CONTENT_MSG_ERROR, msg_data); + if (charset_value != NULL) + lwc_string_unref(charset_value); + free(result); + return error; + } + + if (charset_value != NULL) + lwc_string_unref(charset_value); + + *c = (struct content *) result; + + return NSERROR_OK; +} + +/** + * Create a struct content_css_data, creating a stylesheet object + * + * \param c Struct to populate + * \param url URL of stylesheet + * \param charset Stylesheet charset + * \param quirks Stylesheet quirks mode + * \param done Callback to call when content has completed + * \param pw Client data for \a done + * \return NSERROR_OK on success, NSERROR_NOMEM on memory exhaustion + */ +static nserror nscss_create_css_data(struct content_css_data *c, + const char *url, const char *charset, bool quirks, + nscss_done_callback done, void *pw) +{ + css_error error; + css_stylesheet_params params; + + c->pw = pw; + c->done = done; + c->next_to_register = (uint32_t) -1; + c->import_count = 0; + c->imports = NULL; + if (charset != NULL) + c->charset = strdup(charset); + else + c->charset = NULL; + + params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1; + params.level = CSS_LEVEL_DEFAULT; + params.charset = charset; + params.url = url; + params.title = NULL; + params.allow_quirks = quirks; + params.inline_style = false; + params.resolve = nscss_resolve_url; + params.resolve_pw = NULL; + params.import = nscss_handle_import; + params.import_pw = c; + params.color = ns_system_colour; + params.color_pw = NULL; + params.font = NULL; + params.font_pw = NULL; + + error = css_stylesheet_create(¶ms, &c->sheet); + if (error != CSS_OK) { + return NSERROR_NOMEM; + } + + return NSERROR_OK; +} + +/** + * Process CSS source data + * + * \param c Content structure + * \param data Data to process + * \param size Number of bytes to process + * \return true on success, false on failure + */ +bool nscss_process_data(struct content *c, const char *data, unsigned int size) +{ + nscss_content *css = (nscss_content *) c; + union content_msg_data msg_data; + css_error error; + + error = nscss_process_css_data(&css->data, data, size); + if (error != CSS_OK && error != CSS_NEEDDATA) { + msg_data.error = "?"; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + } + + return (error == CSS_OK || error == CSS_NEEDDATA); +} + +/** + * Process CSS data + * + * \param c CSS content object + * \param data Data to process + * \param size Number of bytes to process + * \return CSS_OK on success, appropriate error otherwise + */ +static css_error nscss_process_css_data(struct content_css_data *c, + const char *data, unsigned int size) +{ + return css_stylesheet_append_data(c->sheet, + (const uint8_t *) data, size); +} + +/** + * Convert a CSS content ready for use + * + * \param c Content to convert + * \return true on success, false on failure + */ +bool nscss_convert(struct content *c) +{ + nscss_content *css = (nscss_content *) c; + union content_msg_data msg_data; + css_error error; + + error = nscss_convert_css_data(&css->data); + if (error != CSS_OK) { + msg_data.error = "?"; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + return false; + } + + return true; +} + +/** + * Convert CSS data ready for use + * + * \param c CSS data to convert + * \return CSS error + */ +static css_error nscss_convert_css_data(struct content_css_data *c) +{ + css_error error; + + error = css_stylesheet_data_done(c->sheet); + + /* Process pending imports */ + if (error == CSS_IMPORTS_PENDING) { + /* We must not have registered any imports yet */ + assert(c->next_to_register == (uint32_t) -1); + + /* Start registering, until we find one that + * hasn't finished fetching */ + c->next_to_register = 0; + error = nscss_register_imports(c); + } else if (error == CSS_OK) { + /* No imports, and no errors, so complete conversion */ + c->done(c, c->pw); + } else { + const char *url; + + if (css_stylesheet_get_url(c->sheet, &url) == CSS_OK) { + LOG("Failed converting %p %s (%d)", c, url, error); + } else { + LOG("Failed converting %p (%d)", c, error); + } + } + + return error; +} + +/** + * Clean up a CSS content + * + * \param c Content to clean up + */ +void nscss_destroy(struct content *c) +{ + nscss_content *css = (nscss_content *) c; + + nscss_destroy_css_data(&css->data); +} + +/** + * Clean up CSS data + * + * \param c CSS data to clean up + */ +static void nscss_destroy_css_data(struct content_css_data *c) +{ + uint32_t i; + + for (i = 0; i < c->import_count; i++) { + if (c->imports[i].c != NULL) { + hlcache_handle_release(c->imports[i].c); + } + c->imports[i].c = NULL; + } + + free(c->imports); + + if (c->sheet != NULL) { + css_stylesheet_destroy(c->sheet); + c->sheet = NULL; + } + + free(c->charset); +} + +nserror nscss_clone(const struct content *old, struct content **newc) +{ + const nscss_content *old_css = (const nscss_content *) old; + nscss_content *new_css; + const char *data; + unsigned long size; + nserror error; + + new_css = calloc(1, sizeof(nscss_content)); + if (new_css == NULL) + return NSERROR_NOMEM; + + /* Clone content */ + error = content__clone(old, &new_css->base); + if (error != NSERROR_OK) { + content_destroy(&new_css->base); + return error; + } + + /* Simply replay create/process/convert */ + error = nscss_create_css_data(&new_css->data, + nsurl_access(content_get_url(&new_css->base)), + old_css->data.charset, + new_css->base.quirks, + nscss_content_done, new_css); + if (error != NSERROR_OK) { + content_destroy(&new_css->base); + return error; + } + + data = content__get_source_data(&new_css->base, &size); + if (size > 0) { + if (nscss_process_data(&new_css->base, data, size) == false) { + content_destroy(&new_css->base); + return NSERROR_CLONE_FAILED; + } + } + + if (old->status == CONTENT_STATUS_READY || + old->status == CONTENT_STATUS_DONE) { + if (nscss_convert(&new_css->base) == false) { + content_destroy(&new_css->base); + return NSERROR_CLONE_FAILED; + } + } + + *newc = (struct content *) new_css; + + return NSERROR_OK; +} + +bool nscss_matches_quirks(const struct content *c, bool quirks) +{ + return c->quirks == quirks; +} + +/* exported interface documented in netsurf/css.h */ +css_stylesheet *nscss_get_stylesheet(struct hlcache_handle *h) +{ + nscss_content *c = (nscss_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + + return c->data.sheet; +} + +/* exported interface documented in netsurf/css.h */ +struct nscss_import *nscss_get_imports(hlcache_handle *h, uint32_t *n) +{ + nscss_content *c = (nscss_content *) hlcache_handle_get_content(h); + + assert(c != NULL); + assert(n != NULL); + + *n = c->data.import_count; + + return c->data.imports; +} + +/** + * Compute the type of a content + * + * \return CONTENT_CSS + */ +content_type nscss_content_type(void) +{ + return CONTENT_CSS; +} + +/***************************************************************************** + * Object completion * + *****************************************************************************/ + +/** + * Handle notification that a CSS object is done + * + * \param css CSS object + * \param pw Private data + */ +void nscss_content_done(struct content_css_data *css, void *pw) +{ + union content_msg_data msg_data; + struct content *c = pw; + uint32_t i; + size_t size; + css_error error; + + /* Retrieve the size of this sheet */ + error = css_stylesheet_size(css->sheet, &size); + if (error != CSS_OK) { + msg_data.error = "?"; + content_broadcast(c, CONTENT_MSG_ERROR, msg_data); + content_set_error(c); + return; + } + c->size += size; + + /* Add on the size of the imported sheets */ + for (i = 0; i < css->import_count; i++) { + if (css->imports[i].c != NULL) { + struct content *import = hlcache_handle_get_content( + css->imports[i].c); + + if (import != NULL) { + c->size += import->size; + } + } + } + + /* Finally, catch the content's users up with reality */ + content_set_ready(c); + content_set_done(c); +} + +/***************************************************************************** + * Import handling * + *****************************************************************************/ + +/** + * Handle notification of the need for an imported stylesheet + * + * \param pw CSS object requesting the import + * \param parent Stylesheet requesting the import + * \param url URL of the imported sheet + * \param media Applicable media for the imported sheet + * \return CSS_OK on success, appropriate error otherwise + */ +css_error nscss_handle_import(void *pw, css_stylesheet *parent, + lwc_string *url, uint64_t media) +{ + content_type accept = CONTENT_CSS; + struct content_css_data *c = pw; + nscss_import_ctx *ctx; + hlcache_child_context child; + struct nscss_import *imports; + const char *referer; + css_error error; + nserror nerror; + + nsurl *ns_url; + nsurl *ns_ref; + + assert(parent == c->sheet); + + error = css_stylesheet_get_url(c->sheet, &referer); + if (error != CSS_OK) { + return error; + } + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + return CSS_NOMEM; + + ctx->css = c; + ctx->index = c->import_count; + + /* Increase space in table */ + imports = realloc(c->imports, (c->import_count + 1) * + sizeof(struct nscss_import)); + if (imports == NULL) { + free(ctx); + return CSS_NOMEM; + } + c->imports = imports; + + /** \todo fallback charset */ + child.charset = NULL; + error = css_stylesheet_quirks_allowed(c->sheet, &child.quirks); + if (error != CSS_OK) { + free(ctx); + return error; + } + + /* Create content */ + c->imports[c->import_count].media = media; + + /** \todo Why aren't we getting a relative url part, to join? */ + nerror = nsurl_create(lwc_string_data(url), &ns_url); + if (nerror != NSERROR_OK) { + free(ctx); + return CSS_NOMEM; + } + + /** \todo Constructing nsurl for referer here is silly, avoid */ + nerror = nsurl_create(referer, &ns_ref); + if (nerror != NSERROR_OK) { + nsurl_unref(ns_url); + free(ctx); + return CSS_NOMEM; + } + + /* Avoid importing ourself */ + if (nsurl_compare(ns_url, ns_ref, NSURL_COMPLETE)) { + c->imports[c->import_count].c = NULL; + /* No longer require context as we're not fetching anything */ + free(ctx); + ctx = NULL; + } else { + nerror = hlcache_handle_retrieve(ns_url, + 0, ns_ref, NULL, nscss_import, ctx, + &child, accept, + &c->imports[c->import_count].c); + if (nerror != NSERROR_OK) { + free(ctx); + return CSS_NOMEM; + } + } + + nsurl_unref(ns_url); + nsurl_unref(ns_ref); + +#ifdef NSCSS_IMPORT_TRACE + LOG("Import %d '%s' -> (handle: %p ctx: %p)", c->import_count, lwc_string_data(url), c->imports[c->import_count].c, ctx); +#endif + + c->import_count++; + + return CSS_OK; +} + +/** + * Handler for imported stylesheet events + * + * \param handle Handle for stylesheet + * \param event Event object + * \param pw Callback context + * \return NSERROR_OK on success, appropriate error otherwise + */ +nserror nscss_import(hlcache_handle *handle, + const hlcache_event *event, void *pw) +{ + nscss_import_ctx *ctx = pw; + css_error error = CSS_OK; + +#ifdef NSCSS_IMPORT_TRACE + LOG("Event %d for %p (%p)", event->type, handle, ctx); +#endif + + assert(ctx->css->imports[ctx->index].c == handle); + + switch (event->type) { + case CONTENT_MSG_DONE: + error = nscss_import_complete(ctx); + break; + + case CONTENT_MSG_ERROR: + hlcache_handle_release(handle); + ctx->css->imports[ctx->index].c = NULL; + + error = nscss_import_complete(ctx); + /* Already released handle */ + break; + + default: + break; + } + + /* Preserve out-of-memory. Anything else is OK */ + return error == CSS_NOMEM ? NSERROR_NOMEM : NSERROR_OK; +} + +/** + * Handle an imported stylesheet completing + * + * \param ctx Import context + * \return CSS_OK on success, appropriate error otherwise + */ +css_error nscss_import_complete(nscss_import_ctx *ctx) +{ + css_error error = CSS_OK; + + /* If this import is the next to be registered, do so */ + if (ctx->css->next_to_register == ctx->index) + error = nscss_register_imports(ctx->css); + +#ifdef NSCSS_IMPORT_TRACE + LOG("Destroying import context %p for %d", ctx, ctx->index); +#endif + + /* No longer need import context */ + free(ctx); + + return error; +} + +/***************************************************************************** + * Import registration * + *****************************************************************************/ + +/** + * Register imports with a stylesheet + * + * \param c CSS object containing the imports + * \return CSS_OK on success, appropriate error otherwise + */ +css_error nscss_register_imports(struct content_css_data *c) +{ + uint32_t index; + css_error error; + + assert(c->next_to_register != (uint32_t) -1); + assert(c->next_to_register < c->import_count); + + /* Register imported sheets */ + for (index = c->next_to_register; index < c->import_count; index++) { + /* Stop registering if we encounter one whose fetch hasn't + * completed yet. We'll resume at this point when it has + * completed. + */ + if (c->imports[index].c != NULL && + content_get_status(c->imports[index].c) != + CONTENT_STATUS_DONE) { + break; + } + + error = nscss_register_import(c, c->imports[index].c); + if (error != CSS_OK) + return error; + } + + /* Record identity of the next import to register */ + c->next_to_register = (uint32_t) index; + + if (c->next_to_register == c->import_count) { + /* No more imports: notify parent that we're DONE */ + c->done(c, c->pw); + } + + return CSS_OK; +} + + +/** + * Register an import with a stylesheet + * + * \param c CSS object that requested the import + * \param import Cache handle of import, or NULL for blank + * \return CSS_OK on success, appropriate error otherwise + */ +css_error nscss_register_import(struct content_css_data *c, + const hlcache_handle *import) +{ + css_stylesheet *sheet; + css_error error; + + if (import != NULL) { + nscss_content *s = + (nscss_content *) hlcache_handle_get_content(import); + sheet = s->data.sheet; + } else { + /* Create a blank sheet if needed. */ + if (blank_import == NULL) { + css_stylesheet_params params; + + params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1; + params.level = CSS_LEVEL_DEFAULT; + params.charset = NULL; + params.url = ""; + params.title = NULL; + params.allow_quirks = false; + params.inline_style = false; + params.resolve = nscss_resolve_url; + params.resolve_pw = NULL; + params.import = NULL; + params.import_pw = NULL; + params.color = ns_system_colour; + params.color_pw = NULL; + params.font = NULL; + params.font_pw = NULL; + + error = css_stylesheet_create(¶ms, &blank_import); + if (error != CSS_OK) { + return error; + } + + error = css_stylesheet_data_done(blank_import); + if (error != CSS_OK) { + css_stylesheet_destroy(blank_import); + return error; + } + } + + sheet = blank_import; + } + + error = css_stylesheet_register_import(c->sheet, sheet); + if (error != CSS_OK) { + return error; + } + + return error; +} + +/** + * Clean up after the CSS content handler + */ +static void nscss_fini(void) +{ + if (blank_import != NULL) { + css_stylesheet_destroy(blank_import); + blank_import = NULL; + } + css_hint_fini(); +} + +static const content_handler css_content_handler = { + .fini = nscss_fini, + .create = nscss_create, + .process_data = nscss_process_data, + .data_complete = nscss_convert, + .destroy = nscss_destroy, + .clone = nscss_clone, + .matches_quirks = nscss_matches_quirks, + .type = nscss_content_type, + .no_share = false, +}; + +/* exported interface documented in netsurf/css.h */ +nserror nscss_init(void) +{ + nserror error; + + error = content_factory_register_handler("text/css", + &css_content_handler); + if (error != NSERROR_OK) + goto error; + + error = css_hint_init(); + if (error != NSERROR_OK) + goto error; + + return NSERROR_OK; + +error: + nscss_fini(); + + return error; +} diff --git a/content/handlers/css/css.h b/content/handlers/css/css.h new file mode 100644 index 000000000..4b38829f0 --- /dev/null +++ b/content/handlers/css/css.h @@ -0,0 +1,62 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef netsurf_css_css_h_ +#define netsurf_css_css_h_ + +#include + +#include + +#include "utils/errors.h" + +struct hlcache_handle; + +/** + * Imported stylesheet record + */ +struct nscss_import { + struct hlcache_handle *c; /**< Content containing sheet */ + uint64_t media; /**< Media types that sheet applies to */ +}; + +/** + * Initialise the CSS content handler + * + * \return NSERROR_OK on success or error code on faliure + */ +nserror nscss_init(void); + +/** + * Retrieve the stylesheet object associated with a CSS content + * + * \param h Stylesheet content + * \return Pointer to stylesheet object + */ +css_stylesheet *nscss_get_stylesheet(struct hlcache_handle *h); + +/** + * Retrieve imported stylesheets + * + * \param h Stylesheet containing imports + * \param n Pointer to location to receive number of imports + * \return Pointer to array of imported stylesheets + */ +struct nscss_import *nscss_get_imports(struct hlcache_handle *h, uint32_t *n); + +#endif diff --git a/content/handlers/css/dump.c b/content/handlers/css/dump.c new file mode 100644 index 000000000..125b133cf --- /dev/null +++ b/content/handlers/css/dump.c @@ -0,0 +1,1793 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "dump.h" + +/** + * Dump a fixed point value to the stream in a textual form. + * + * \param stream Stream to write to + * \param f Value to write + */ +static void dump_css_fixed(FILE *stream, css_fixed f) +{ +#define NSCSS_ABS(x) (uint32_t)((x) < 0 ? -(x) : (x)) + uint32_t uintpart = FIXTOINT(NSCSS_ABS(f)); + /* + 500 to ensure round to nearest (division will truncate) */ + uint32_t fracpart = ((NSCSS_ABS(f) & 0x3ff) * 1000 + 500) / (1 << 10); +#undef NSCSS_ABS + + fprintf(stream, "%s%d.%03d", f < 0 ? "-" : "", uintpart, fracpart); +} + +/** + * Dump a numeric value to the stream in a textual form. + * + * \param stream Stream to write to + * \param val Value to write + */ +static void dump_css_number(FILE *stream, css_fixed val) +{ + if (INTTOFIX(FIXTOINT(val)) == val) + fprintf(stream, "%d", FIXTOINT(val)); + else + dump_css_fixed(stream, val); +} + +/** + * Dump a dimension to the stream in a textual form. + * + * \param stream Stream to write to + * \param val Value to write + * \param unit Unit to write + */ +static void dump_css_unit(FILE *stream, css_fixed val, css_unit unit) +{ + dump_css_number(stream, val); + + switch (unit) { + case CSS_UNIT_PX: + fprintf(stream, "px"); + break; + case CSS_UNIT_EX: + fprintf(stream, "ex"); + break; + case CSS_UNIT_EM: + fprintf(stream, "em"); + break; + case CSS_UNIT_IN: + fprintf(stream, "in"); + break; + case CSS_UNIT_CM: + fprintf(stream, "cm"); + break; + case CSS_UNIT_MM: + fprintf(stream, "mm"); + break; + case CSS_UNIT_PT: + fprintf(stream, "pt"); + break; + case CSS_UNIT_PC: + fprintf(stream, "pc"); + break; + case CSS_UNIT_PCT: + fprintf(stream, "%%"); + break; + case CSS_UNIT_DEG: + fprintf(stream, "deg"); + break; + case CSS_UNIT_GRAD: + fprintf(stream, "grad"); + break; + case CSS_UNIT_RAD: + fprintf(stream, "rad"); + break; + case CSS_UNIT_MS: + fprintf(stream, "ms"); + break; + case CSS_UNIT_S: + fprintf(stream, "s"); + break; + case CSS_UNIT_HZ: + fprintf(stream, "Hz"); + break; + case CSS_UNIT_KHZ: + fprintf(stream, "kHz"); + break; + } +} + +/* exported interface documented in content/handlers/css/dump.h */ +void nscss_dump_computed_style(FILE *stream, const css_computed_style *style) +{ + uint8_t val; + css_color color = 0; + lwc_string *url = NULL; + css_fixed len1 = 0, len2 = 0; + css_unit unit1 = CSS_UNIT_PX, unit2 = CSS_UNIT_PX; + css_computed_clip_rect rect = { 0, 0, 0, 0, CSS_UNIT_PX, CSS_UNIT_PX, + CSS_UNIT_PX, CSS_UNIT_PX, true, true, + true, true }; + const css_computed_content_item *content = NULL; + const css_computed_counter *counter = NULL; + lwc_string **string_list = NULL; + int32_t zindex = 0; + + fprintf(stream, "{ "); + + /* background-attachment */ + val = css_computed_background_attachment(style); + switch (val) { + case CSS_BACKGROUND_ATTACHMENT_FIXED: + fprintf(stream, "background-attachment: fixed "); + break; + case CSS_BACKGROUND_ATTACHMENT_SCROLL: + fprintf(stream, "background-attachment: scroll "); + break; + default: + break; + } + + /* background-color */ + val = css_computed_background_color(style, &color); + switch (val) { + case CSS_BACKGROUND_COLOR_COLOR: + fprintf(stream, "background-color: #%08x ", color); + break; + default: + break; + } + + /* background-image */ + val = css_computed_background_image(style, &url); + if (val == CSS_BACKGROUND_IMAGE_IMAGE && url != NULL) { + fprintf(stream, "background-image: url('%.*s') ", + (int) lwc_string_length(url), + lwc_string_data(url)); + } else if (val == CSS_BACKGROUND_IMAGE_NONE) { + fprintf(stream, "background-image: none "); + } + + /* background-position */ + val = css_computed_background_position(style, &len1, &unit1, + &len2, &unit2); + if (val == CSS_BACKGROUND_POSITION_SET) { + fprintf(stream, "background-position: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + dump_css_unit(stream, len2, unit2); + fprintf(stream, " "); + } + + /* background-repeat */ + val = css_computed_background_repeat(style); + switch (val) { + case CSS_BACKGROUND_REPEAT_REPEAT_X: + fprintf(stream, "background-repeat: repeat-x "); + break; + case CSS_BACKGROUND_REPEAT_REPEAT_Y: + fprintf(stream, "background-repeat: repeat-y "); + break; + case CSS_BACKGROUND_REPEAT_REPEAT: + fprintf(stream, "background-repeat: repeat "); + break; + case CSS_BACKGROUND_REPEAT_NO_REPEAT: + fprintf(stream, "background-repeat: no-repeat "); + break; + default: + break; + } + + /* border-collapse */ + val = css_computed_border_collapse(style); + switch (val) { + case CSS_BORDER_COLLAPSE_SEPARATE: + fprintf(stream, "border-collapse: separate "); + break; + case CSS_BORDER_COLLAPSE_COLLAPSE: + fprintf(stream, "border-collapse: collapse "); + break; + default: + + break; + } + + /* border-spacing */ + val = css_computed_border_spacing(style, &len1, &unit1, &len2, &unit2); + if (val == CSS_BORDER_SPACING_SET) { + fprintf(stream, "border-spacing: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + dump_css_unit(stream, len2, unit2); + fprintf(stream, " "); + } + + /* border-top-color */ + val = css_computed_border_top_color(style, &color); + switch (val) { + case CSS_BORDER_COLOR_COLOR: + fprintf(stream, "border-top-color: #%08x ", color); + break; + default: + break; + } + + /* border-right-color */ + val = css_computed_border_right_color(style, &color); + switch (val) { + case CSS_BORDER_COLOR_COLOR: + fprintf(stream, "border-right-color: #%08x ", color); + break; + default: + break; + } + + /* border-bottom-color */ + val = css_computed_border_bottom_color(style, &color); + switch (val) { + case CSS_BORDER_COLOR_COLOR: + fprintf(stream, "border-bottom-color: #%08x ", color); + break; + default: + break; + } + + /* border-left-color */ + val = css_computed_border_left_color(style, &color); + switch (val) { + case CSS_BORDER_COLOR_COLOR: + fprintf(stream, "border-left-color: #%08x ", color); + break; + default: + break; + } + + /* border-top-style */ + val = css_computed_border_top_style(style); + switch (val) { + case CSS_BORDER_STYLE_NONE: + fprintf(stream, "border-top-style: none "); + break; + case CSS_BORDER_STYLE_HIDDEN: + fprintf(stream, "border-top-style: hidden "); + break; + case CSS_BORDER_STYLE_DOTTED: + fprintf(stream, "border-top-style: dotted "); + break; + case CSS_BORDER_STYLE_DASHED: + fprintf(stream, "border-top-style: dashed "); + break; + case CSS_BORDER_STYLE_SOLID: + fprintf(stream, "border-top-style: solid "); + break; + case CSS_BORDER_STYLE_DOUBLE: + fprintf(stream, "border-top-style: double "); + break; + case CSS_BORDER_STYLE_GROOVE: + fprintf(stream, "border-top-style: groove "); + break; + case CSS_BORDER_STYLE_RIDGE: + fprintf(stream, "border-top-style: ridge "); + break; + case CSS_BORDER_STYLE_INSET: + fprintf(stream, "border-top-style: inset "); + break; + case CSS_BORDER_STYLE_OUTSET: + fprintf(stream, "border-top-style: outset "); + break; + default: + break; + } + + /* border-right-style */ + val = css_computed_border_right_style(style); + switch (val) { + case CSS_BORDER_STYLE_NONE: + fprintf(stream, "border-right-style: none "); + break; + case CSS_BORDER_STYLE_HIDDEN: + fprintf(stream, "border-right-style: hidden "); + break; + case CSS_BORDER_STYLE_DOTTED: + fprintf(stream, "border-right-style: dotted "); + break; + case CSS_BORDER_STYLE_DASHED: + fprintf(stream, "border-right-style: dashed "); + break; + case CSS_BORDER_STYLE_SOLID: + fprintf(stream, "border-right-style: solid "); + break; + case CSS_BORDER_STYLE_DOUBLE: + fprintf(stream, "border-right-style: double "); + break; + case CSS_BORDER_STYLE_GROOVE: + fprintf(stream, "border-right-style: groove "); + break; + case CSS_BORDER_STYLE_RIDGE: + fprintf(stream, "border-right-style: ridge "); + break; + case CSS_BORDER_STYLE_INSET: + fprintf(stream, "border-right-style: inset "); + break; + case CSS_BORDER_STYLE_OUTSET: + fprintf(stream, "border-right-style: outset "); + break; + default: + break; + } + + /* border-bottom-style */ + val = css_computed_border_bottom_style(style); + switch (val) { + case CSS_BORDER_STYLE_NONE: + fprintf(stream, "border-bottom-style: none "); + break; + case CSS_BORDER_STYLE_HIDDEN: + fprintf(stream, "border-bottom-style: hidden "); + break; + case CSS_BORDER_STYLE_DOTTED: + fprintf(stream, "border-bottom-style: dotted "); + break; + case CSS_BORDER_STYLE_DASHED: + fprintf(stream, "border-bottom-style: dashed "); + break; + case CSS_BORDER_STYLE_SOLID: + fprintf(stream, "border-bottom-style: solid "); + break; + case CSS_BORDER_STYLE_DOUBLE: + fprintf(stream, "border-bottom-style: double "); + break; + case CSS_BORDER_STYLE_GROOVE: + fprintf(stream, "border-bottom-style: groove "); + break; + case CSS_BORDER_STYLE_RIDGE: + fprintf(stream, "border-bottom-style: ridge "); + break; + case CSS_BORDER_STYLE_INSET: + fprintf(stream, "border-bottom-style: inset "); + break; + case CSS_BORDER_STYLE_OUTSET: + fprintf(stream, "border-bottom-style: outset "); + break; + default: + break; + } + + /* border-left-style */ + val = css_computed_border_left_style(style); + switch (val) { + case CSS_BORDER_STYLE_NONE: + fprintf(stream, "border-left-style: none "); + break; + case CSS_BORDER_STYLE_HIDDEN: + fprintf(stream, "border-left-style: hidden "); + break; + case CSS_BORDER_STYLE_DOTTED: + fprintf(stream, "border-left-style: dotted "); + break; + case CSS_BORDER_STYLE_DASHED: + fprintf(stream, "border-left-style: dashed "); + break; + case CSS_BORDER_STYLE_SOLID: + fprintf(stream, "border-left-style: solid "); + break; + case CSS_BORDER_STYLE_DOUBLE: + fprintf(stream, "border-left-style: double "); + break; + case CSS_BORDER_STYLE_GROOVE: + fprintf(stream, "border-left-style: groove "); + break; + case CSS_BORDER_STYLE_RIDGE: + fprintf(stream, "border-left-style: ridge "); + break; + case CSS_BORDER_STYLE_INSET: + fprintf(stream, "border-left-style: inset "); + break; + case CSS_BORDER_STYLE_OUTSET: + fprintf(stream, "border-left-style: outset "); + break; + default: + break; + } + + /* border-top-width */ + val = css_computed_border_top_width(style, &len1, &unit1); + switch (val) { + case CSS_BORDER_WIDTH_THIN: + fprintf(stream, "border-top-width: thin "); + break; + case CSS_BORDER_WIDTH_MEDIUM: + fprintf(stream, "border-top-width: medium "); + break; + case CSS_BORDER_WIDTH_THICK: + fprintf(stream, "border-top-width: thick "); + break; + case CSS_BORDER_WIDTH_WIDTH: + fprintf(stream, "border-top-width: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + break; + default: + break; + } + + /* border-right-width */ + val = css_computed_border_right_width(style, &len1, &unit1); + switch (val) { + case CSS_BORDER_WIDTH_THIN: + fprintf(stream, "border-right-width: thin "); + break; + case CSS_BORDER_WIDTH_MEDIUM: + fprintf(stream, "border-right-width: medium "); + break; + case CSS_BORDER_WIDTH_THICK: + fprintf(stream, "border-right-width: thick "); + break; + case CSS_BORDER_WIDTH_WIDTH: + fprintf(stream, "border-right-width: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + break; + default: + break; + } + + /* border-bottom-width */ + val = css_computed_border_bottom_width(style, &len1, &unit1); + switch (val) { + case CSS_BORDER_WIDTH_THIN: + fprintf(stream, "border-bottom-width: thin "); + break; + case CSS_BORDER_WIDTH_MEDIUM: + fprintf(stream, "border-bottom-width: medium "); + break; + case CSS_BORDER_WIDTH_THICK: + fprintf(stream, "border-bottom-width: thick "); + break; + case CSS_BORDER_WIDTH_WIDTH: + fprintf(stream, "border-bottom-width: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + break; + default: + break; + } + + /* border-left-width */ + val = css_computed_border_left_width(style, &len1, &unit1); + switch (val) { + case CSS_BORDER_WIDTH_THIN: + fprintf(stream, "border-left-width: thin "); + break; + case CSS_BORDER_WIDTH_MEDIUM: + fprintf(stream, "border-left-width: medium "); + break; + case CSS_BORDER_WIDTH_THICK: + fprintf(stream, "border-left-width: thick "); + break; + case CSS_BORDER_WIDTH_WIDTH: + fprintf(stream, "border-left-width: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + break; + default: + break; + } + + /* bottom */ + val = css_computed_bottom(style, &len1, &unit1); + switch (val) { + case CSS_BOTTOM_AUTO: + fprintf(stream, "bottom: auto "); + break; + case CSS_BOTTOM_SET: + fprintf(stream, "bottom: "); + dump_css_unit(stream, len1, unit1); + fprintf(stream, " "); + break; + default: + break; + } + + /* caption-side */ + val = css_computed_caption_side(style); + switch (val) { + case CSS_CAPTION_SIDE_TOP: + fprintf(stream, "caption_side: top "); + break; + case CSS_CAPTION_SIDE_BOTTOM: + fprintf(stream, "caption_side: bottom "); + break; + default: + break; + } + + /* clear */ + val = css_computed_clear(style); + switch (val) { + case CSS_CLEAR_NONE: + fprintf(stream, "clear: none "); + break; + case CSS_CLEAR_LEFT: + fprintf(stream, "clear: left "); + break; + case CSS_CLEAR_RIGHT: + fprintf(stream, "clear: right "); + break; + case CSS_CLEAR_BOTH: + fprintf(stream, "clear: both "); + break; + default: + break; + } + + /* clip */ + val = css_computed_clip(style, &rect); + switch (val) { + case CSS_CLIP_AUTO: + fprintf(stream, "clip: auto "); + break; + case CSS_CLIP_RECT: + fprintf(stream, "clip: rect( "); + + if (rect.top_auto) + fprintf(stream, "auto"); + else + dump_css_unit(stream, rect.top, rect.tunit); + fprintf(stream, ", "); + + if (rect.right_auto) + fprintf(stream, "auto"); + else + dump_css_unit(stream, rect.right, rect.runit); + fprintf(stream, ", "); + + if (rect.bottom_auto) + fprintf(stream, "auto"); + else + dump_css_unit(stream, rect.bottom, rect.bunit); + fprintf(stream, ", "); + + if (rect.left_auto) + fprintf(stream, "auto"); + else + dump_css_unit(stream, rect.left, rect.lunit); + fprintf(stream, ") "); + break; + default: + break; + } + + /* color */ + val = css_computed_color(style, &color); + if (val == CSS_COLOR_COLOR) { + fprintf(stream, "color: #%08x ", color); + } + + /* content */ + val = css_computed_content(style, &content); + switch (val) { + case CSS_CONTENT_NONE: + fprintf(stream, "content: none "); + break; + case CSS_CONTENT_NORMAL: + fprintf(stream, "content: normal "); + break; + case CSS_CONTENT_SET: + fprintf(stream, "content:"); + + while (content->type != CSS_COMPUTED_CONTENT_NONE) { + fprintf(stream, " "); + + switch (content->type) { + case CSS_COMPUTED_CONTENT_STRING: + fprintf(stream, "\"%.*s\"", + (int) lwc_string_length( + content->data.string), + lwc_string_data( + content->data.string)); + break; + case CSS_COMPUTED_CONTENT_URI: + fprintf(stream, "uri(\"%.*s\")", + (int) lwc_string_length( + content->data.uri), + lwc_string_data( + content->data.uri)); + break; + case CSS_COMPUTED_CONTENT_COUNTER: + fprintf(stream, "counter(%.*s)", + (int) lwc_string_length( + content->data.counter.name), + lwc_string_data( + content->data.counter.name)); + break; + case CSS_COMPUTED_CONTENT_COUNTERS: + fprintf(stream, "counters(%.*s, \"%.*s\")", + (int) lwc_string_length( + content->data.counters.name), + lwc_string_data( + content->data.counters.name), + (int) lwc_string_length( + content->data.counters.sep), + lwc_string_data( + content->data.counters.sep)); + break; + case CSS_COMPUTED_CONTENT_ATTR: + fprintf(stream, "attr(%.*s)", + (int) lwc_string_length( + content->data.attr), + lwc_string_data( + content->data.attr)); + break; + case CSS_COMPUTED_CONTENT_OPEN_QUOTE: + fprintf(stream, "open-quote"); + break; + case CSS_COMPUTED_CONTENT_CLOSE_QUOTE: + fprintf(stream, "close-quote"); + break; + case CSS_COMPUTED_CONTENT_NO_OPEN_QUOTE: + fprintf(stream, "no-open-quote"); + break; + case CSS_COMPUTED_CONTENT_NO_CLOSE_QUOTE: + fprintf(stream, "no-close-quote"); + break; + } + + content++; + } + + fprintf(stream, " "); + break; + default: + break; + } + + /* counter-increment */ + val = css_computed_counter_increment(style, &counter); + if ((val == CSS_COUNTER_INCREMENT_NONE) || (counter == NULL)) { + fprintf(stream, "counter-increment: none "); + } else { + fprintf(stream, "counter-increment:"); + + while (counter->name != NULL) { + fprintf(stream, " %.*s ", + (int) lwc_string_length(counter->name), + lwc_string_data(counter->name)); + + dump_css_fixed(stream, counter->value); + + counter++; + } + + fprintf(stream, " "); + } + + /* counter-reset */ + val = css_computed_counter_reset(style, &counter); + if ((val == CSS_COUNTER_RESET_NONE) || (counter == NULL)) { + fprintf(stream, "counter-reset: none "); + } else { + fprintf(stream, "counter-reset:"); + + while (counter->name != NULL) { + fprintf(stream, " %.*s ", + (int) lwc_string_length(counter->name), + lwc_string_data(counter->name)); + + dump_css_fixed(stream, counter->value); + + counter++; + } + + fprintf(stream, " "); + } + + /* cursor */ + val = css_computed_cursor(style, &string_list); + fprintf(stream, "cursor:"); + + if (string_list != NULL) { + while (*string_list != NULL) { + fprintf(stream, " url\"%.*s\")", + (int) lwc_string_length(*string_list), + lwc_string_data(*string_list)); + + string_list++; + } + } + switch (val) { + case CSS_CURSOR_AUTO: + fprintf(stream, " auto "); + break; + case CSS_CURSOR_CROSSHAIR: + fprintf(stream, " crosshair "); + break; + case CSS_CURSOR_DEFAULT: + fprintf(stream, " default "); + break; + case CSS_CURSOR_POINTER: + fprintf(stream, " pointer "); + break; + case CSS_CURSOR_MOVE: + fprintf(stream, " move "); + break; + case CSS_CURSOR_E_RESIZE: + fprintf(stream, " e-resize "); + break; + case CSS_CURSOR_NE_RESIZE: + fprintf(stream, " ne-resize "); + break; + case CSS_CURSOR_NW_RESIZE: + fprintf(stream, " nw-resize "); + break; + case CSS_CURSOR_N_RESIZE: + fprintf(stream, " n-resize "); + break; + case CSS_CURSOR_SE_RESIZE: + fprintf(stream, " se-resize "); + break; + case CSS_CURSOR_SW_RESIZE: + fprintf(stream, " sw-resize "); + break; + case CSS_CURSOR_S_RESIZE: + fprintf(stream, " s-resize "); + break; + case CSS_CURSOR_W_RESIZE: + fprintf(stream, " w-resize "); + break; + case CSS_CURSOR_TEXT: + fprintf(stream, " text "); + break; + case CSS_CURSOR_WAIT: + fprintf(stream, " wait "); + break; + case CSS_CURSOR_HELP: + fprintf(stream, " help "); + break; + case CSS_CURSOR_PROGRESS: + fprintf(stream, " progress "); + break; + default: + break; + } + + /* direction */ + val = css_computed_direction(style); + switch (val) { + case CSS_DIRECTION_LTR: + fprintf(stream, "direction: ltr "); + break; + case CSS_DIRECTION_RTL: + fprintf(stream, "direction: rtl "); + break; + default: + break; + } + + /* display */ + val = css_computed_display_static(style); + switch (val) { + case CSS_DISPLAY_INLINE: + fprintf(stream, "display: inline "); + break; + case CSS_DISPLAY_BLOCK: + fprintf(stream, "display: block "); + break; + case CSS_DISPLAY_LIST_ITEM: + fprintf(stream, "display: list-item "); + break; + case CSS_DISPLAY_RUN_IN: + fprintf(stream, "display: run-in "); + break; + case CSS_DISPLAY_INLINE_BLOCK: + fprintf(stream, "display: inline-block "); + break; + case CSS_DISPLAY_TABLE: + fprintf(stream, "display: table "); + break; + case CSS_DISPLAY_INLINE_TABLE: + fprintf(stream, "display: inline-table "); + break; + case CSS_DISPLAY_TABLE_ROW_GROUP: + fprintf(stream, "display: table-row-group "); + break; + case CSS_DISPLAY_TABLE_HEADER_GROUP: + fprintf(stream, "display: table-header-group "); + break; + case CSS_DISPLAY_TABLE_FOOTER_GROUP: + fprintf(stream, "display: table-footer-group "); + break; + case CSS_DISPLAY_TABLE_ROW: + fprintf(stream, "display: table-row "); + break; + case CSS_DISPLAY_TABLE_COLUMN_GROUP: + fprintf(stream, "display: table-column-group "); + break; + case CSS_DISPLAY_TABLE_COLUMN: + fprintf(stream, "display: table-column "); + break; + case CSS_DISPLAY_TABLE_CELL: + fprintf(stream, "display: table-cell "); + break; + case CSS_DISPLAY_TABLE_CAPTION: + fprintf(stream, "display: table-caption "); + break; + case CSS_DISPLAY_NONE: + fprintf(stream, "display: none "); + break; + default: + break; + } + + /* empty-cells */ + val = css_computed_empty_cells(style); + switch (val) { + case CSS_EMPTY_CELLS_SHOW: + fprintf(stream, "empty-cells: show "); + break; + case CSS_EMPTY_CELLS_HIDE: + fprintf(stream, "empty-cells: hide "); + break; + default: + break; + } + + /* float */ + val = css_computed_float(style); + switch (val) { + case CSS_FLOAT_LEFT: + fprintf(stream, "float: left "); + break; + case CSS_FLOAT_RIGHT: + fprintf(stream, "float: right "); + break; + case CSS_FLOAT_NONE: + fprintf(stream, "float: none "); + break; + default: + break; + } + + /* font-family */ + val = css_computed_font_family(style, &string_list); + if (val != CSS_FONT_FAMILY_INHERIT) { + fprintf(stream, "font-family:"); + + if (string_list != NULL) { + while (*string_list != NULL) { + fprintf(stream, " \"%.*s\"", + (int) lwc_string_length(*string_list), + lwc_string_data(*string_list)); + + string_list++; + } + } + switch (val) { + case CSS_FONT_FAMILY_SERIF: + fprintf(stream, " serif "); + break; + case CSS_FONT_FAMILY_SANS_SERIF: + fprintf(stream, " sans-serif "); + break; + case CSS_FONT_FAMILY_CURSIVE: + fprintf(stream, " cursive "); + break; + case CSS_FONT_FAMILY_FANTASY: + fprintf(stream, " fantasy "); + break; + case CSS_FONT_FAMILY_MONOSPACE: + fprintf(stream, " monospace "); + break; + } + } + + /* font-size */ + val = css_computed_font_size(style, &len1, &unit1); + switch (val) { + case CSS_FONT_SIZE_XX_SMALL: + fprintf(stream, "font-size: xx-small "); + break; + case CSS_FONT_SIZE_X_SMALL: + fprintf(stream, "font-size: x-small "); + break; + case CSS_FONT_SIZE_SMALL: + fprintf(stream, "font-size: small "); + break; + case CSS_FONT_SIZE_MEDIUM: + fprintf(stream, "font-size: medium "); + break; + case CSS_FONT_SIZE_LARGE: + fprintf(stream, "font-size: large "); + break; + case CSS_FONT_SIZE_X_LARGE: + fprintf(stream, "font-size: x-large "); + break; + case CSS_FONT_SIZE_XX_LARGE: + fprintf(stream, "font-size: xx-large "); + break; + case CSS_FONT_SIZE_LARGER: + fprintf(stream, "font-size: larger "); + break; + case CSS_FONT_SIZE_SMALLER: + fprintf(stream, "font-size: smaller "); + break; + case CSS_FONT_SIZE_DIMENSION: + fprintf(stream, "font-size: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* font-style */ + val = css_computed_font_style(style); + switch (val) { + case CSS_FONT_STYLE_NORMAL: + fprintf(stream, "font-style: normal "); + break; + case CSS_FONT_STYLE_ITALIC: + fprintf(stream, "font-style: italic "); + break; + case CSS_FONT_STYLE_OBLIQUE: + fprintf(stream, "font-style: oblique "); + break; + default: + break; + } + + /* font-variant */ + val = css_computed_font_variant(style); + switch (val) { + case CSS_FONT_VARIANT_NORMAL: + fprintf(stream, "font-variant: normal "); + break; + case CSS_FONT_VARIANT_SMALL_CAPS: + fprintf(stream, "font-variant: small-caps "); + break; + default: + break; + } + + /* font-weight */ + val = css_computed_font_weight(style); + switch (val) { + case CSS_FONT_WEIGHT_NORMAL: + fprintf(stream, "font-weight: normal "); + break; + case CSS_FONT_WEIGHT_BOLD: + fprintf(stream, "font-weight: bold "); + break; + case CSS_FONT_WEIGHT_BOLDER: + fprintf(stream, "font-weight: bolder "); + break; + case CSS_FONT_WEIGHT_LIGHTER: + fprintf(stream, "font-weight: lighter "); + break; + case CSS_FONT_WEIGHT_100: + fprintf(stream, "font-weight: 100 "); + break; + case CSS_FONT_WEIGHT_200: + fprintf(stream, "font-weight: 200 "); + break; + case CSS_FONT_WEIGHT_300: + fprintf(stream, "font-weight: 300 "); + break; + case CSS_FONT_WEIGHT_400: + fprintf(stream, "font-weight: 400 "); + break; + case CSS_FONT_WEIGHT_500: + fprintf(stream, "font-weight: 500 "); + break; + case CSS_FONT_WEIGHT_600: + fprintf(stream, "font-weight: 600 "); + break; + case CSS_FONT_WEIGHT_700: + fprintf(stream, "font-weight: 700 "); + break; + case CSS_FONT_WEIGHT_800: + fprintf(stream, "font-weight: 800 "); + break; + case CSS_FONT_WEIGHT_900: + fprintf(stream, "font-weight: 900 "); + break; + default: + break; + } + + /* height */ + val = css_computed_height(style, &len1, &unit1); + switch (val) { + case CSS_HEIGHT_AUTO: + fprintf(stream, "height: auto "); + break; + case CSS_HEIGHT_SET: + fprintf(stream, "height: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* left */ + val = css_computed_left(style, &len1, &unit1); + switch (val) { + case CSS_LEFT_AUTO: + fprintf(stream, "left: auto "); + break; + case CSS_LEFT_SET: + fprintf(stream, "left: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* letter-spacing */ + val = css_computed_letter_spacing(style, &len1, &unit1); + switch (val) { + case CSS_LETTER_SPACING_NORMAL: + fprintf(stream, "letter-spacing: normal "); + break; + case CSS_LETTER_SPACING_SET: + fprintf(stream, "letter-spacing: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* line-height */ + val = css_computed_line_height(style, &len1, &unit1); + switch (val) { + case CSS_LINE_HEIGHT_NORMAL: + fprintf(stream, "line-height: normal "); + break; + case CSS_LINE_HEIGHT_NUMBER: + fprintf(stream, "line-height: "); + + dump_css_fixed(stream, len1); + + fprintf(stream, " "); + break; + case CSS_LINE_HEIGHT_DIMENSION: + fprintf(stream, "line-height: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* list-style-image */ + val = css_computed_list_style_image(style, &url); + if (url != NULL) { + fprintf(stream, "list-style-image: url('%.*s') ", + (int) lwc_string_length(url), + lwc_string_data(url)); + } else if (val == CSS_LIST_STYLE_IMAGE_NONE) { + fprintf(stream, "list-style-image: none "); + } + + /* list-style-position */ + val = css_computed_list_style_position(style); + switch (val) { + case CSS_LIST_STYLE_POSITION_INSIDE: + fprintf(stream, "list-style-position: inside "); + break; + case CSS_LIST_STYLE_POSITION_OUTSIDE: + fprintf(stream, "list-style-position: outside "); + break; + default: + break; + } + + /* list-style-type */ + val = css_computed_list_style_type(style); + switch (val) { + case CSS_LIST_STYLE_TYPE_DISC: + fprintf(stream, "list-style-type: disc "); + break; + case CSS_LIST_STYLE_TYPE_CIRCLE: + fprintf(stream, "list-style-type: circle "); + break; + case CSS_LIST_STYLE_TYPE_SQUARE: + fprintf(stream, "list-style-type: square "); + break; + case CSS_LIST_STYLE_TYPE_DECIMAL: + fprintf(stream, "list-style-type: decimal "); + break; + case CSS_LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO: + fprintf(stream, "list-style-type: decimal-leading-zero "); + break; + case CSS_LIST_STYLE_TYPE_LOWER_ROMAN: + fprintf(stream, "list-style-type: lower-roman "); + break; + case CSS_LIST_STYLE_TYPE_UPPER_ROMAN: + fprintf(stream, "list-style-type: upper-roman "); + break; + case CSS_LIST_STYLE_TYPE_LOWER_GREEK: + fprintf(stream, "list-style-type: lower-greek "); + break; + case CSS_LIST_STYLE_TYPE_LOWER_LATIN: + fprintf(stream, "list-style-type: lower-latin "); + break; + case CSS_LIST_STYLE_TYPE_UPPER_LATIN: + fprintf(stream, "list-style-type: upper-latin "); + break; + case CSS_LIST_STYLE_TYPE_ARMENIAN: + fprintf(stream, "list-style-type: armenian "); + break; + case CSS_LIST_STYLE_TYPE_GEORGIAN: + fprintf(stream, "list-style-type: georgian "); + break; + case CSS_LIST_STYLE_TYPE_LOWER_ALPHA: + fprintf(stream, "list-style-type: lower-alpha "); + break; + case CSS_LIST_STYLE_TYPE_UPPER_ALPHA: + fprintf(stream, "list-style-type: upper-alpha "); + break; + case CSS_LIST_STYLE_TYPE_NONE: + fprintf(stream, "list-style-type: none "); + break; + default: + break; + } + + /* margin-top */ + val = css_computed_margin_top(style, &len1, &unit1); + switch (val) { + case CSS_MARGIN_AUTO: + fprintf(stream, "margin-top: auto "); + break; + case CSS_MARGIN_SET: + fprintf(stream, "margin-top: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* margin-right */ + val = css_computed_margin_right(style, &len1, &unit1); + switch (val) { + case CSS_MARGIN_AUTO: + fprintf(stream, "margin-right: auto "); + break; + case CSS_MARGIN_SET: + fprintf(stream, "margin-right: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* margin-bottom */ + val = css_computed_margin_bottom(style, &len1, &unit1); + switch (val) { + case CSS_MARGIN_AUTO: + fprintf(stream, "margin-bottom: auto "); + break; + case CSS_MARGIN_SET: + fprintf(stream, "margin-bottom: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* margin-left */ + val = css_computed_margin_left(style, &len1, &unit1); + switch (val) { + case CSS_MARGIN_AUTO: + fprintf(stream, "margin-left: auto "); + break; + case CSS_MARGIN_SET: + fprintf(stream, "margin-left: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* max-height */ + val = css_computed_max_height(style, &len1, &unit1); + switch (val) { + case CSS_MAX_HEIGHT_NONE: + fprintf(stream, "max-height: none "); + break; + case CSS_MAX_HEIGHT_SET: + fprintf(stream, "max-height: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* max-width */ + val = css_computed_max_width(style, &len1, &unit1); + switch (val) { + case CSS_MAX_WIDTH_NONE: + fprintf(stream, "max-width: none "); + break; + case CSS_MAX_WIDTH_SET: + fprintf(stream, "max-width: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* min-height */ + val = css_computed_min_height(style, &len1, &unit1); + switch (val) { + case CSS_MIN_HEIGHT_SET: + fprintf(stream, "min-height: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* min-width */ + val = css_computed_min_width(style, &len1, &unit1); + switch (val) { + case CSS_MIN_WIDTH_SET: + fprintf(stream, "min-width: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* opacity */ + val = css_computed_opacity(style, &len1); + switch (val) { + case CSS_OPACITY_SET: + fprintf(stream, "opacity: "); + + dump_css_fixed(stream, len1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* outline-color */ + val = css_computed_outline_color(style, &color); + switch (val) { + case CSS_OUTLINE_COLOR_INVERT: + fprintf(stream, "outline-color: invert "); + break; + case CSS_OUTLINE_COLOR_COLOR: + fprintf(stream, "outline-color: #%08x ", color); + break; + default: + break; + } + + /* outline-style */ + val = css_computed_outline_style(style); + switch (val) { + case CSS_OUTLINE_STYLE_NONE: + fprintf(stream, "outline-style: none "); + break; + case CSS_OUTLINE_STYLE_DOTTED: + fprintf(stream, "outline-style: dotted "); + break; + case CSS_OUTLINE_STYLE_DASHED: + fprintf(stream, "outline-style: dashed "); + break; + case CSS_OUTLINE_STYLE_SOLID: + fprintf(stream, "outline-style: solid "); + break; + case CSS_OUTLINE_STYLE_DOUBLE: + fprintf(stream, "outline-style: double "); + break; + case CSS_OUTLINE_STYLE_GROOVE: + fprintf(stream, "outline-style: groove "); + break; + case CSS_OUTLINE_STYLE_RIDGE: + fprintf(stream, "outline-style: ridge "); + break; + case CSS_OUTLINE_STYLE_INSET: + fprintf(stream, "outline-style: inset "); + break; + case CSS_OUTLINE_STYLE_OUTSET: + fprintf(stream, "outline-style: outset "); + break; + default: + break; + } + + /* outline-width */ + val = css_computed_outline_width(style, &len1, &unit1); + switch (val) { + case CSS_OUTLINE_WIDTH_THIN: + fprintf(stream, "outline-width: thin "); + break; + case CSS_OUTLINE_WIDTH_MEDIUM: + fprintf(stream, "outline-width: medium "); + break; + case CSS_OUTLINE_WIDTH_THICK: + fprintf(stream, "outline-width: thick "); + break; + case CSS_OUTLINE_WIDTH_WIDTH: + fprintf(stream, "outline-width: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* overflow */ + val = css_computed_overflow_x(style); + switch (val) { + case CSS_OVERFLOW_VISIBLE: + fprintf(stream, "overflow-x: visible "); + break; + case CSS_OVERFLOW_HIDDEN: + fprintf(stream, "overflow-x: hidden "); + break; + case CSS_OVERFLOW_SCROLL: + fprintf(stream, "overflow-x: scroll "); + break; + case CSS_OVERFLOW_AUTO: + fprintf(stream, "overflow-x auto "); + break; + default: + break; + } + + /* overflow */ + val = css_computed_overflow_y(style); + switch (val) { + case CSS_OVERFLOW_VISIBLE: + fprintf(stream, "overflow-y: visible "); + break; + case CSS_OVERFLOW_HIDDEN: + fprintf(stream, "overflow-y: hidden "); + break; + case CSS_OVERFLOW_SCROLL: + fprintf(stream, "overflow-y: scroll "); + break; + case CSS_OVERFLOW_AUTO: + fprintf(stream, "overflow-y: auto "); + break; + default: + break; + } + + /* padding-top */ + val = css_computed_padding_top(style, &len1, &unit1); + switch (val) { + case CSS_PADDING_SET: + fprintf(stream, "padding-top: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* padding-right */ + val = css_computed_padding_right(style, &len1, &unit1); + switch (val) { + case CSS_PADDING_SET: + fprintf(stream, "padding-right: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* padding-bottom */ + val = css_computed_padding_bottom(style, &len1, &unit1); + switch (val) { + case CSS_PADDING_SET: + fprintf(stream, "padding-bottom: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* padding-left */ + val = css_computed_padding_left(style, &len1, &unit1); + switch (val) { + case CSS_PADDING_SET: + fprintf(stream, "padding-left: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* position */ + val = css_computed_position(style); + switch (val) { + case CSS_POSITION_STATIC: + fprintf(stream, "position: static "); + break; + case CSS_POSITION_RELATIVE: + fprintf(stream, "position: relative "); + break; + case CSS_POSITION_ABSOLUTE: + fprintf(stream, "position: absolute "); + break; + case CSS_POSITION_FIXED: + fprintf(stream, "position: fixed "); + break; + default: + break; + } + + /* quotes */ + val = css_computed_quotes(style, &string_list); + if (val == CSS_QUOTES_STRING && string_list != NULL) { + fprintf(stream, "quotes:"); + + while (*string_list != NULL) { + fprintf(stream, " \"%.*s\"", + (int) lwc_string_length(*string_list), + lwc_string_data(*string_list)); + + string_list++; + } + + fprintf(stream, " "); + } else { + switch (val) { + case CSS_QUOTES_NONE: + fprintf(stream, "quotes: none "); + break; + default: + break; + } + } + + /* right */ + val = css_computed_right(style, &len1, &unit1); + switch (val) { + case CSS_RIGHT_AUTO: + fprintf(stream, "right: auto "); + break; + case CSS_RIGHT_SET: + fprintf(stream, "right: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* table-layout */ + val = css_computed_table_layout(style); + switch (val) { + case CSS_TABLE_LAYOUT_AUTO: + fprintf(stream, "table-layout: auto "); + break; + case CSS_TABLE_LAYOUT_FIXED: + fprintf(stream, "table-layout: fixed "); + break; + default: + break; + } + + /* text-align */ + val = css_computed_text_align(style); + switch (val) { + case CSS_TEXT_ALIGN_LEFT: + fprintf(stream, "text-align: left "); + break; + case CSS_TEXT_ALIGN_RIGHT: + fprintf(stream, "text-align: right "); + break; + case CSS_TEXT_ALIGN_CENTER: + fprintf(stream, "text-align: center "); + break; + case CSS_TEXT_ALIGN_JUSTIFY: + fprintf(stream, "text-align: justify "); + break; + case CSS_TEXT_ALIGN_DEFAULT: + fprintf(stream, "text-align: default "); + break; + case CSS_TEXT_ALIGN_LIBCSS_LEFT: + fprintf(stream, "text-align: -libcss-left "); + break; + case CSS_TEXT_ALIGN_LIBCSS_CENTER: + fprintf(stream, "text-align: -libcss-center "); + break; + case CSS_TEXT_ALIGN_LIBCSS_RIGHT: + fprintf(stream, "text-align: -libcss-right "); + break; + default: + break; + } + + /* text-decoration */ + val = css_computed_text_decoration(style); + if (val == CSS_TEXT_DECORATION_NONE) { + fprintf(stream, "text-decoration: none "); + } else { + fprintf(stream, "text-decoration:"); + + if (val & CSS_TEXT_DECORATION_BLINK) { + fprintf(stream, " blink"); + } + if (val & CSS_TEXT_DECORATION_LINE_THROUGH) { + fprintf(stream, " line-through"); + } + if (val & CSS_TEXT_DECORATION_OVERLINE) { + fprintf(stream, " overline"); + } + if (val & CSS_TEXT_DECORATION_UNDERLINE) { + fprintf(stream, " underline"); + } + + fprintf(stream, " "); + } + + /* text-indent */ + val = css_computed_text_indent(style, &len1, &unit1); + switch (val) { + case CSS_TEXT_INDENT_SET: + fprintf(stream, "text-indent: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* text-transform */ + val = css_computed_text_transform(style); + switch (val) { + case CSS_TEXT_TRANSFORM_CAPITALIZE: + fprintf(stream, "text-transform: capitalize "); + break; + case CSS_TEXT_TRANSFORM_UPPERCASE: + fprintf(stream, "text-transform: uppercase "); + break; + case CSS_TEXT_TRANSFORM_LOWERCASE: + fprintf(stream, "text-transform: lowercase "); + break; + case CSS_TEXT_TRANSFORM_NONE: + fprintf(stream, "text-transform: none "); + break; + default: + break; + } + + /* top */ + val = css_computed_top(style, &len1, &unit1); + switch (val) { + case CSS_TOP_AUTO: + fprintf(stream, "top: auto "); + break; + case CSS_TOP_SET: + fprintf(stream, "top: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* unicode-bidi */ + val = css_computed_unicode_bidi(style); + switch (val) { + case CSS_UNICODE_BIDI_NORMAL: + fprintf(stream, "unicode-bidi: normal "); + break; + case CSS_UNICODE_BIDI_EMBED: + fprintf(stream, "unicode-bidi: embed "); + break; + case CSS_UNICODE_BIDI_BIDI_OVERRIDE: + fprintf(stream, "unicode-bidi: bidi-override "); + break; + default: + break; + } + + /* vertical-align */ + val = css_computed_vertical_align(style, &len1, &unit1); + switch (val) { + case CSS_VERTICAL_ALIGN_BASELINE: + fprintf(stream, "vertical-align: baseline "); + break; + case CSS_VERTICAL_ALIGN_SUB: + fprintf(stream, "vertical-align: sub "); + break; + case CSS_VERTICAL_ALIGN_SUPER: + fprintf(stream, "vertical-align: super "); + break; + case CSS_VERTICAL_ALIGN_TOP: + fprintf(stream, "vertical-align: top "); + break; + case CSS_VERTICAL_ALIGN_TEXT_TOP: + fprintf(stream, "vertical-align: text-top "); + break; + case CSS_VERTICAL_ALIGN_MIDDLE: + fprintf(stream, "vertical-align: middle "); + break; + case CSS_VERTICAL_ALIGN_BOTTOM: + fprintf(stream, "vertical-align: bottom "); + break; + case CSS_VERTICAL_ALIGN_TEXT_BOTTOM: + fprintf(stream, "vertical-align: text-bottom "); + break; + case CSS_VERTICAL_ALIGN_SET: + fprintf(stream, "vertical-align: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* visibility */ + val = css_computed_visibility(style); + switch (val) { + case CSS_VISIBILITY_VISIBLE: + fprintf(stream, "visibility: visible "); + break; + case CSS_VISIBILITY_HIDDEN: + fprintf(stream, "visibility: hidden "); + break; + case CSS_VISIBILITY_COLLAPSE: + fprintf(stream, "visibility: collapse "); + break; + default: + break; + } + + /* white-space */ + val = css_computed_white_space(style); + switch (val) { + case CSS_WHITE_SPACE_NORMAL: + fprintf(stream, "white-space: normal "); + break; + case CSS_WHITE_SPACE_PRE: + fprintf(stream, "white-space: pre "); + break; + case CSS_WHITE_SPACE_NOWRAP: + fprintf(stream, "white-space: nowrap "); + break; + case CSS_WHITE_SPACE_PRE_WRAP: + fprintf(stream, "white-space: pre-wrap "); + break; + case CSS_WHITE_SPACE_PRE_LINE: + fprintf(stream, "white-space: pre-line "); + break; + default: + break; + } + + /* width */ + val = css_computed_width(style, &len1, &unit1); + switch (val) { + case CSS_WIDTH_AUTO: + fprintf(stream, "width: auto "); + break; + case CSS_WIDTH_SET: + fprintf(stream, "width: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* word-spacing */ + val = css_computed_word_spacing(style, &len1, &unit1); + switch (val) { + case CSS_WORD_SPACING_NORMAL: + fprintf(stream, "word-spacing: normal "); + break; + case CSS_WORD_SPACING_SET: + fprintf(stream, "word-spacing: "); + + dump_css_unit(stream, len1, unit1); + + fprintf(stream, " "); + break; + default: + break; + } + + /* z-index */ + val = css_computed_z_index(style, &zindex); + switch (val) { + case CSS_Z_INDEX_AUTO: + fprintf(stream, "z-index: auto "); + break; + case CSS_Z_INDEX_SET: + fprintf(stream, "z-index: %d ", zindex); + break; + default: + break; + } + + fprintf(stream, "}"); +} diff --git a/content/handlers/css/dump.h b/content/handlers/css/dump.h new file mode 100644 index 000000000..470e56481 --- /dev/null +++ b/content/handlers/css/dump.h @@ -0,0 +1,30 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NETSURF_CSS_DUMP_H_ +#define NETSURF_CSS_DUMP_H_ + +/** + * Dump a computed style \a style to the give file handle \a stream. + * + * \param stream Stream to write to + * \param style Computed style to dump + */ +void nscss_dump_computed_style(FILE *stream, const css_computed_style *style); + +#endif diff --git a/content/handlers/css/hints.c b/content/handlers/css/hints.c new file mode 100644 index 000000000..f394ea836 --- /dev/null +++ b/content/handlers/css/hints.c @@ -0,0 +1,1604 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "utils/nsoption.h" +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/nsurl.h" +#include "utils/utils.h" + +#include "hints.h" +#include "select.h" + +#define LOG_STATS +#undef LOG_STATS + +/****************************************************************************** + * Utility functions * + ******************************************************************************/ + +/** + * Determine if a given character is whitespace + * + * \param c Character to consider + * \return true if character is whitespace, false otherwise + */ +static bool isWhitespace(char c) +{ + return c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n'; +} + +/** + * Determine if a given character is a valid hex digit + * + * \param c Character to consider + * \return true if character is a valid hex digit, false otherwise + */ +static bool isHex(char c) +{ + return ('0' <= c && c <= '9') || + ('A' <= (c & ~0x20) && (c & ~0x20) <= 'F'); +} + +/** + * Convert a character representing a hex digit to the corresponding hex value + * + * \param c Character to convert + * \return Hex value represented by character + * + * \note This function assumes an ASCII-compatible character set + */ +static uint8_t charToHex(char c) +{ + /* 0-9 */ + c -= '0'; + + /* A-F */ + if (c > 9) + c -= 'A' - '9' - 1; + + /* a-f */ + if (c > 15) + c -= 'a' - 'A'; + + return c; +} + + +/****************************************************************************** + * Common parsing functions * + ******************************************************************************/ + +/** + * Parse a number string + * + * \param data Data to parse (NUL-terminated) + * \param maybe_negative Negative numbers permitted + * \param real Floating point numbers permitted + * \param value Pointer to location to receive numeric value + * \param consumed Pointer to location to receive number of input + * bytes consumed + * \return true on success, false on invalid input + */ +static bool parse_number(const char *data, bool maybe_negative, bool real, + css_fixed *value, size_t *consumed) +{ + size_t len; + const uint8_t *ptr; + int32_t intpart = 0; + int32_t fracpart = 0; + int32_t pwr = 1; + int sign = 1; + + *consumed = 0; + + len = strlen(data); + ptr = (const uint8_t *) data; + + if (len == 0) + return false; + + /* Skip leading whitespace */ + while (len > 0 && isWhitespace(ptr[0])) { + len--; + ptr++; + } + + if (len == 0) + return false; + + /* Extract sign, if any */ + if (ptr[0] == '+') { + len--; + ptr++; + } else if (ptr[0] == '-' && maybe_negative) { + sign = -1; + len--; + ptr++; + } + + if (len == 0) + return false; + + /* Must have a digit [0,9] */ + if ('0' > ptr[0] || ptr[0] > '9') + return false; + + /* Now extract intpart, assuming base 10 */ + while (len > 0) { + /* Stop on first non-digit */ + if (ptr[0] < '0' || '9' < ptr[0]) + break; + + /* Prevent overflow of 'intpart'; proper clamping below */ + if (intpart < (1 << 22)) { + intpart *= 10; + intpart += ptr[0] - '0'; + } + ptr++; + len--; + } + + /* And fracpart, again, assuming base 10 */ + if (real && len > 1 && ptr[0] == '.' && + ('0' <= ptr[1] && ptr[1] <= '9')) { + ptr++; + len--; + + while (len > 0) { + if (ptr[0] < '0' || '9' < ptr[0]) + break; + + if (pwr < 1000000) { + pwr *= 10; + fracpart *= 10; + fracpart += ptr[0] - '0'; + } + ptr++; + len--; + } + + fracpart = ((1 << 10) * fracpart + pwr/2) / pwr; + if (fracpart >= (1 << 10)) { + intpart++; + fracpart &= (1 << 10) - 1; + } + } + + if (sign > 0) { + /* If the result is larger than we can represent, + * then clamp to the maximum value we can store. */ + if (intpart >= (1 << 21)) { + intpart = (1 << 21) - 1; + fracpart = (1 << 10) - 1; + } + } else { + /* If the negated result is smaller than we can represent + * then clamp to the minimum value we can store. */ + if (intpart >= (1 << 21)) { + intpart = -(1 << 21); + fracpart = 0; + } else { + intpart = -intpart; + if (fracpart) { + fracpart = (1 << 10) - fracpart; + intpart--; + } + } + } + + *value = (intpart << 10) | fracpart; + + *consumed = ptr - (const uint8_t *) data; + + return true; +} + +/** + * Parse a dimension string + * + * \param data Data to parse (NUL-terminated) + * \param strict Whether to enforce strict parsing rules + * \param length Pointer to location to receive dimension's length + * \param unit Pointer to location to receive dimension's unit + * \return true on success, false on invalid input + */ +static bool parse_dimension(const char *data, bool strict, css_fixed *length, + css_unit *unit) +{ + size_t len; + size_t read; + css_fixed value; + + len = strlen(data); + + if (parse_number(data, false, true, &value, &read) == false) + return false; + + if (strict && value < INTTOFIX(1)) + return false; + + *length = value; + + if (len > read && data[read] == '%') + *unit = CSS_UNIT_PCT; + else + *unit = CSS_UNIT_PX; + + return true; +} + +/** + * Mapping of colour name to CSS color + */ +struct colour_map { + const char *name; + css_color color; +}; + +/** + * Name comparator for named colour matching + * + * \param a Name to match + * \param b Colour map entry to consider + * \return 0 on match, + * < 0 if a < b, + * > 0 if b > a. + */ +static int cmp_colour_name(const void *a, const void *b) +{ + const char *aa = a; + const struct colour_map *bb = b; + + return strcasecmp(aa, bb->name); +} + +/** + * Parse a named colour + * + * \param name Name to parse + * \param result Pointer to location to receive css_color + * \return true on success, false on invalid input + */ +static bool parse_named_colour(const char *name, css_color *result) +{ + static const struct colour_map named_colours[] = { + { "aliceblue", 0xfff0f8ff }, + { "antiquewhite", 0xfffaebd7 }, + { "aqua", 0xff00ffff }, + { "aquamarine", 0xff7fffd4 }, + { "azure", 0xfff0ffff }, + { "beige", 0xfff5f5dc }, + { "bisque", 0xffffe4c4 }, + { "black", 0xff000000 }, + { "blanchedalmond", 0xffffebcd }, + { "blue", 0xff0000ff }, + { "blueviolet", 0xff8a2be2 }, + { "brown", 0xffa52a2a }, + { "burlywood", 0xffdeb887 }, + { "cadetblue", 0xff5f9ea0 }, + { "chartreuse", 0xff7fff00 }, + { "chocolate", 0xffd2691e }, + { "coral", 0xffff7f50 }, + { "cornflowerblue", 0xff6495ed }, + { "cornsilk", 0xfffff8dc }, + { "crimson", 0xffdc143c }, + { "cyan", 0xff00ffff }, + { "darkblue", 0xff00008b }, + { "darkcyan", 0xff008b8b }, + { "darkgoldenrod", 0xffb8860b }, + { "darkgray", 0xffa9a9a9 }, + { "darkgreen", 0xff006400 }, + { "darkgrey", 0xffa9a9a9 }, + { "darkkhaki", 0xffbdb76b }, + { "darkmagenta", 0xff8b008b }, + { "darkolivegreen", 0xff556b2f }, + { "darkorange", 0xffff8c00 }, + { "darkorchid", 0xff9932cc }, + { "darkred", 0xff8b0000 }, + { "darksalmon", 0xffe9967a }, + { "darkseagreen", 0xff8fbc8f }, + { "darkslateblue", 0xff483d8b }, + { "darkslategray", 0xff2f4f4f }, + { "darkslategrey", 0xff2f4f4f }, + { "darkturquoise", 0xff00ced1 }, + { "darkviolet", 0xff9400d3 }, + { "deeppink", 0xffff1493 }, + { "deepskyblue", 0xff00bfff }, + { "dimgray", 0xff696969 }, + { "dimgrey", 0xff696969 }, + { "dodgerblue", 0xff1e90ff }, + { "feldspar", 0xffd19275 }, + { "firebrick", 0xffb22222 }, + { "floralwhite", 0xfffffaf0 }, + { "forestgreen", 0xff228b22 }, + { "fuchsia", 0xffff00ff }, + { "gainsboro", 0xffdcdcdc }, + { "ghostwhite", 0xfff8f8ff }, + { "gold", 0xffffd700 }, + { "goldenrod", 0xffdaa520 }, + { "gray", 0xff808080 }, + { "green", 0xff008000 }, + { "greenyellow", 0xffadff2f }, + { "grey", 0xff808080 }, + { "honeydew", 0xfff0fff0 }, + { "hotpink", 0xffff69b4 }, + { "indianred", 0xffcd5c5c }, + { "indigo", 0xff4b0082 }, + { "ivory", 0xfffffff0 }, + { "khaki", 0xfff0e68c }, + { "lavender", 0xffe6e6fa }, + { "lavenderblush", 0xfffff0f5 }, + { "lawngreen", 0xff7cfc00 }, + { "lemonchiffon", 0xfffffacd }, + { "lightblue", 0xffadd8e6 }, + { "lightcoral", 0xfff08080 }, + { "lightcyan", 0xffe0ffff }, + { "lightgoldenrodyellow", 0xfffafad2 }, + { "lightgray", 0xffd3d3d3 }, + { "lightgreen", 0xff90ee90 }, + { "lightgrey", 0xffd3d3d3 }, + { "lightpink", 0xffffb6c1 }, + { "lightsalmon", 0xffffa07a }, + { "lightseagreen", 0xff20b2aa }, + { "lightskyblue", 0xff87cefa }, + { "lightslateblue", 0xff8470ff }, + { "lightslategray", 0xff778899 }, + { "lightslategrey", 0xff778899 }, + { "lightsteelblue", 0xffb0c4de }, + { "lightyellow", 0xffffffe0 }, + { "lime", 0xff00ff00 }, + { "limegreen", 0xff32cd32 }, + { "linen", 0xfffaf0e6 }, + { "magenta", 0xffff00ff }, + { "maroon", 0xff800000 }, + { "mediumaquamarine", 0xff66cdaa }, + { "mediumblue", 0xff0000cd }, + { "mediumorchid", 0xffba55d3 }, + { "mediumpurple", 0xff9370db }, + { "mediumseagreen", 0xff3cb371 }, + { "mediumslateblue", 0xff7b68ee }, + { "mediumspringgreen", 0xff00fa9a }, + { "mediumturquoise", 0xff48d1cc }, + { "mediumvioletred", 0xffc71585 }, + { "midnightblue", 0xff191970 }, + { "mintcream", 0xfff5fffa }, + { "mistyrose", 0xffffe4e1 }, + { "moccasin", 0xffffe4b5 }, + { "navajowhite", 0xffffdead }, + { "navy", 0xff000080 }, + { "oldlace", 0xfffdf5e6 }, + { "olive", 0xff808000 }, + { "olivedrab", 0xff6b8e23 }, + { "orange", 0xffffa500 }, + { "orangered", 0xffff4500 }, + { "orchid", 0xffda70d6 }, + { "palegoldenrod", 0xffeee8aa }, + { "palegreen", 0xff98fb98 }, + { "paleturquoise", 0xffafeeee }, + { "palevioletred", 0xffdb7093 }, + { "papayawhip", 0xffffefd5 }, + { "peachpuff", 0xffffdab9 }, + { "peru", 0xffcd853f }, + { "pink", 0xffffc0cb }, + { "plum", 0xffdda0dd }, + { "powderblue", 0xffb0e0e6 }, + { "purple", 0xff800080 }, + { "red", 0xffff0000 }, + { "rosybrown", 0xffbc8f8f }, + { "royalblue", 0xff4169e1 }, + { "saddlebrown", 0xff8b4513 }, + { "salmon", 0xfffa8072 }, + { "sandybrown", 0xfff4a460 }, + { "seagreen", 0xff2e8b57 }, + { "seashell", 0xfffff5ee }, + { "sienna", 0xffa0522d }, + { "silver", 0xffc0c0c0 }, + { "skyblue", 0xff87ceeb }, + { "slateblue", 0xff6a5acd }, + { "slategray", 0xff708090 }, + { "slategrey", 0xff708090 }, + { "snow", 0xfffffafa }, + { "springgreen", 0xff00ff7f }, + { "steelblue", 0xff4682b4 }, + { "tan", 0xffd2b48c }, + { "teal", 0xff008080 }, + { "thistle", 0xffd8bfd8 }, + { "tomato", 0xffff6347 }, + { "turquoise", 0xff40e0d0 }, + { "violet", 0xffee82ee }, + { "violetred", 0xffd02090 }, + { "wheat", 0xfff5deb3 }, + { "white", 0xffffffff }, + { "whitesmoke", 0xfff5f5f5 }, + { "yellow", 0xffffff00 }, + { "yellowgreen", 0xff9acd32 } + }; + const struct colour_map *entry; + + entry = bsearch(name, named_colours, + sizeof(named_colours) / sizeof(named_colours[0]), + sizeof(named_colours[0]), + cmp_colour_name); + + if (entry != NULL) + *result = entry->color; + + return entry != NULL; +} + +/* exported interface documented in content/handlers/css/hints.h */ +bool nscss_parse_colour(const char *data, css_color *result) +{ + size_t len = strlen(data); + uint8_t r, g, b; + + /* 2 */ + if (len == 0) + return false; + + /* 3 */ + if (len == SLEN("transparent") && strcasecmp(data, "transparent") == 0) + return false; + + /* 4 */ + if (parse_named_colour(data, result)) + return true; + + /** \todo Implement HTML5's utterly insane legacy colour parsing */ + + if (data[0] == '#') { + data++; + len--; + } + + if (len == 3 && isHex(data[0]) && isHex(data[1]) && isHex(data[2])) { + r = charToHex(data[0]); + g = charToHex(data[1]); + b = charToHex(data[2]); + + r |= (r << 4); + g |= (g << 4); + b |= (b << 4); + + *result = (0xff << 24) | (r << 16) | (g << 8) | b; + + return true; + } else if (len == 6 && isHex(data[0]) && isHex(data[1]) && + isHex(data[2]) && isHex(data[3]) && isHex(data[4]) && + isHex(data[5])) { + r = (charToHex(data[0]) << 4) | charToHex(data[1]); + g = (charToHex(data[2]) << 4) | charToHex(data[3]); + b = (charToHex(data[4]) << 4) | charToHex(data[5]); + + *result = (0xff << 24) | (r << 16) | (g << 8) | b; + + return true; + } + + return false; +} + +/** + * Parse a font \@size attribute + * + * \param size Data to parse (NUL-terminated) + * \param val Pointer to location to receive enum value + * \param len Pointer to location to receive length + * \param unit Pointer to location to receive unit + * \return True on success, false on failure + */ +static bool parse_font_size(const char *size, uint8_t *val, + css_fixed *len, css_unit *unit) +{ + static const uint8_t size_map[] = { + CSS_FONT_SIZE_XX_SMALL, + CSS_FONT_SIZE_SMALL, + CSS_FONT_SIZE_MEDIUM, + CSS_FONT_SIZE_LARGE, + CSS_FONT_SIZE_X_LARGE, + CSS_FONT_SIZE_XX_LARGE, + CSS_FONT_SIZE_DIMENSION /* xxx-large (see below) */ + }; + + const char *p = size; + char mode; + int value = 0; + + /* Skip whitespace */ + while (*p != '\0' && isWhitespace(*p)) + p++; + + mode = *p; + + /* Skip +/- */ + if (mode == '+' || mode == '-') + p++; + + /* Need at least one digit */ + if (*p < '0' || *p > '9') { + return false; + } + + /* Consume digits, computing value */ + while ('0' <= *p && *p <= '9') { + value = value * 10 + (*p - '0'); + p++; + } + + /* Resolve relative sizes */ + if (mode == '+') + value += 3; + else if (mode == '-') + value = 3 - value; + + /* Clamp to range [1,7] */ + if (value < 1) + value = 1; + else if (value > 7) + value = 7; + + if (value == 7) { + /* Manufacture xxx-large */ + *len = FDIV(FMUL(INTTOFIX(3), INTTOFIX(nsoption_int(font_size))), + F_10); + } else { + /* Len is irrelevant */ + *len = 0; + } + + *unit = CSS_UNIT_PT; + *val = size_map[value - 1]; + + return true; +} + + +/****************************************************************************** + * Hint context management * + ******************************************************************************/ + +#define MAX_HINTS_PER_ELEMENT 32 + +struct css_hint_ctx { + struct css_hint *hints; + uint32_t len; +}; + +struct css_hint_ctx hint_ctx; + +nserror css_hint_init(void) +{ + hint_ctx.hints = malloc(sizeof(struct css_hint) * + MAX_HINTS_PER_ELEMENT); + if (hint_ctx.hints == NULL) { + return NSERROR_NOMEM; + } + + return NSERROR_OK; +} + +void css_hint_fini(void) +{ + hint_ctx.len = 0; + free(hint_ctx.hints); +} + +static void css_hint_clean(void) +{ + hint_ctx.len = 0; +} + +static inline struct css_hint * css_hint_advance(struct css_hint *hint) +{ + hint_ctx.len++; + assert(hint_ctx.len < MAX_HINTS_PER_ELEMENT); + + return ++hint; +} + +static void css_hint_get_hints(struct css_hint **hints, uint32_t *nhints) +{ + *hints = hint_ctx.hints; + *nhints = hint_ctx.len; +} + + +/****************************************************************************** + * Presentational hint handlers * + ******************************************************************************/ + +static void css_hint_table_cell_border_padding( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + css_qname qs; + dom_string *attr = NULL; + dom_node *tablenode = NULL; + dom_exception exc; + + qs.ns = NULL; + qs.name = lwc_string_ref(corestring_lwc_table); + if (named_ancestor_node(ctx, node, &qs, + (void *)&tablenode) != CSS_OK) { + /* Didn't find, or had error */ + lwc_string_unref(qs.name); + return; + } + lwc_string_unref(qs.name); + + if (tablenode == NULL) { + return; + } + /* No need to unref tablenode, named_ancestor_node does not + * return a reffed node to the CSS + */ + + exc = dom_element_get_attribute(tablenode, + corestring_dom_border, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + uint32_t hint_prop; + css_hint_length hint_length; + + if (parse_dimension( + dom_string_data(attr), false, + &hint_length.value, + &hint_length.unit) && + INTTOFIX(0) != hint_length.value) { + + for (hint_prop = CSS_PROP_BORDER_TOP_STYLE; + hint_prop <= CSS_PROP_BORDER_LEFT_STYLE; + hint_prop++) { + hint->prop = hint_prop; + hint->status = CSS_BORDER_STYLE_INSET; + hint = css_hint_advance(hint); + } + + for (hint_prop = CSS_PROP_BORDER_TOP_WIDTH; + hint_prop <= CSS_PROP_BORDER_LEFT_WIDTH; + hint_prop++) { + hint->prop = hint_prop; + hint->data.length.value = INTTOFIX(1); + hint->data.length.unit = CSS_UNIT_PX; + hint->status = CSS_BORDER_WIDTH_WIDTH; + hint = css_hint_advance(hint); + } + } + dom_string_unref(attr); + } + + exc = dom_element_get_attribute(tablenode, + corestring_dom_bordercolor, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + uint32_t hint_prop; + css_color hint_color; + + if (nscss_parse_colour( + (const char *)dom_string_data(attr), + &hint_color)) { + + for (hint_prop = CSS_PROP_BORDER_TOP_COLOR; + hint_prop <= CSS_PROP_BORDER_LEFT_COLOR; + hint_prop++) { + hint->prop = hint_prop; + hint->data.color = hint_color; + hint->status = CSS_BORDER_COLOR_COLOR; + hint = css_hint_advance(hint); + } + } + dom_string_unref(attr); + } + + exc = dom_element_get_attribute(tablenode, + corestring_dom_cellpadding, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + uint32_t hint_prop; + css_hint_length hint_length; + + if (parse_dimension( + dom_string_data(attr), false, + &hint_length.value, + &hint_length.unit)) { + + for (hint_prop = CSS_PROP_PADDING_TOP; + hint_prop <= CSS_PROP_PADDING_LEFT; + hint_prop++) { + hint->prop = hint_prop; + hint->data.length.value = hint_length.value; + hint->data.length.unit = hint_length.unit; + hint->status = CSS_PADDING_SET; + hint = css_hint_advance(hint); + } + } + dom_string_unref(attr); + } +} + +static void css_hint_vertical_align_table_cells( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_valign, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_top)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_TOP; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_middle)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_MIDDLE; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_bottom)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_BOTTOM; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_baseline)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_BASELINE; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_vertical_align_replaced( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_valign, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_top)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_TOP; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_bottom) || + dom_string_caseless_lwc_isequal(attr, + corestring_lwc_baseline)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_BASELINE; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_texttop)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_TEXT_TOP; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_absmiddle) || + dom_string_caseless_lwc_isequal(attr, + corestring_lwc_abscenter)) { + hint->prop = CSS_PROP_VERTICAL_ALIGN; + hint->status = CSS_VERTICAL_ALIGN_MIDDLE; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_text_align_normal( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *align = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_align, &align); + if (err == DOM_NO_ERR && align != NULL) { + if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_left)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_LEFT; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_center)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_CENTER; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_right)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_RIGHT; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_justify)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_JUSTIFY; + hint = css_hint_advance(hint); + } + dom_string_unref(align); + } +} + +static void css_hint_text_align_center( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER; + hint = css_hint_advance(hint); +} + +static void css_hint_margin_left_right_align_center( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr; + dom_exception exc; + + exc = dom_element_get_attribute(node, + corestring_dom_align, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_center) || + dom_string_caseless_lwc_isequal(attr, + corestring_lwc_abscenter) || + dom_string_caseless_lwc_isequal(attr, + corestring_lwc_middle) || + dom_string_caseless_lwc_isequal(attr, + corestring_lwc_absmiddle)) { + hint->prop = CSS_PROP_MARGIN_LEFT; + hint->status = CSS_MARGIN_AUTO; + hint = css_hint_advance(hint); + + hint->prop = CSS_PROP_MARGIN_RIGHT; + hint->status = CSS_MARGIN_AUTO; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_text_align_special( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *align = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_align, &align); + + if (err == DOM_NO_ERR && align != NULL) { + if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_center)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_left)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_LIBCSS_LEFT; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_right)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_LIBCSS_RIGHT; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_justify)) { + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_JUSTIFY; + hint = css_hint_advance(hint); + } + dom_string_unref(align); + } +} + +static void css_hint_text_align_table_special( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + + hint->prop = CSS_PROP_TEXT_ALIGN; + hint->status = CSS_TEXT_ALIGN_INHERIT_IF_NON_MAGIC; + hint = css_hint_advance(hint); +} + +static void css_hint_margin_hspace_vspace( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr = NULL; + dom_exception exc; + + exc = dom_element_get_attribute(node, + corestring_dom_vspace, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + css_hint_length hint_length; + if (parse_dimension( + dom_string_data(attr), false, + &hint_length.value, + &hint_length.unit)) { + hint->prop = CSS_PROP_MARGIN_TOP; + hint->data.length.value = hint_length.value; + hint->data.length.unit = hint_length.unit; + hint->status = CSS_MARGIN_SET; + hint = css_hint_advance(hint); + + hint->prop = CSS_PROP_MARGIN_BOTTOM; + hint->data.length.value = hint_length.value; + hint->data.length.unit = hint_length.unit; + hint->status = CSS_MARGIN_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } + + exc = dom_element_get_attribute(node, + corestring_dom_hspace, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + css_hint_length hint_length; + if (parse_dimension( + dom_string_data(attr), false, + &hint_length.value, + &hint_length.unit)) { + hint->prop = CSS_PROP_MARGIN_LEFT; + hint->data.length.value = hint_length.value; + hint->data.length.unit = hint_length.unit; + hint->status = CSS_MARGIN_SET; + hint = css_hint_advance(hint); + + hint->prop = CSS_PROP_MARGIN_RIGHT; + hint->data.length.value = hint_length.value; + hint->data.length.unit = hint_length.unit; + hint->status = CSS_MARGIN_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_margin_left_right_hr( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr; + dom_exception exc; + + exc = dom_element_get_attribute(node, + corestring_dom_align, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_left)) { + hint->prop = CSS_PROP_MARGIN_LEFT; + hint->data.length.value = 0; + hint->data.length.unit = CSS_UNIT_PX; + hint->status = CSS_MARGIN_SET; + hint = css_hint_advance(hint); + + hint->prop = CSS_PROP_MARGIN_RIGHT; + hint->status = CSS_MARGIN_AUTO; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_center)) { + hint->prop = CSS_PROP_MARGIN_LEFT; + hint->status = CSS_MARGIN_AUTO; + hint = css_hint_advance(hint); + + hint->prop = CSS_PROP_MARGIN_RIGHT; + hint->status = CSS_MARGIN_AUTO; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(attr, + corestring_lwc_right)) { + hint->prop = CSS_PROP_MARGIN_LEFT; + hint->status = CSS_MARGIN_AUTO; + hint = css_hint_advance(hint); + + hint->prop = CSS_PROP_MARGIN_RIGHT; + hint->data.length.value = 0; + hint->data.length.unit = CSS_UNIT_PX; + hint->status = CSS_MARGIN_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_table_spacing_border( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception exc; + dom_string *attr = NULL; + + exc = dom_element_get_attribute(node, corestring_dom_border, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + uint32_t hint_prop; + css_hint_length hint_length; + + for (hint_prop = CSS_PROP_BORDER_TOP_STYLE; + hint_prop <= CSS_PROP_BORDER_LEFT_STYLE; + hint_prop++) { + hint->prop = hint_prop; + hint->status = CSS_BORDER_STYLE_OUTSET; + hint = css_hint_advance(hint); + } + + if (parse_dimension( + dom_string_data(attr), false, + &hint_length.value, + &hint_length.unit)) { + + for (hint_prop = CSS_PROP_BORDER_TOP_WIDTH; + hint_prop <= CSS_PROP_BORDER_LEFT_WIDTH; + hint_prop++) { + hint->prop = hint_prop; + hint->data.length.value = hint_length.value; + hint->data.length.unit = hint_length.unit; + hint->status = CSS_BORDER_WIDTH_WIDTH; + hint = css_hint_advance(hint); + } + } + dom_string_unref(attr); + } + + exc = dom_element_get_attribute(node, + corestring_dom_bordercolor, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + uint32_t hint_prop; + css_color hint_color; + + if (nscss_parse_colour( + (const char *)dom_string_data(attr), + &hint_color)) { + + for (hint_prop = CSS_PROP_BORDER_TOP_COLOR; + hint_prop <= CSS_PROP_BORDER_LEFT_COLOR; + hint_prop++) { + hint->prop = hint_prop; + hint->data.color = hint_color; + hint->status = CSS_BORDER_COLOR_COLOR; + hint = css_hint_advance(hint); + } + } + dom_string_unref(attr); + } + + exc = dom_element_get_attribute(node, + corestring_dom_cellspacing, &attr); + + if (exc == DOM_NO_ERR && attr != NULL) { + if (parse_dimension( + (const char *)dom_string_data(attr), false, + &hint->data.position.h.value, + &hint->data.position.h.unit)) { + hint->prop = CSS_PROP_BORDER_SPACING; + hint->data.position.v = hint->data.position.h; + hint->status = CSS_BORDER_SPACING_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_height( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_height, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (parse_dimension( + (const char *)dom_string_data(attr), false, + &hint->data.length.value, + &hint->data.length.unit)) { + hint->prop = CSS_PROP_HEIGHT; + hint->status = CSS_HEIGHT_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_width( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_width, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (parse_dimension( + (const char *)dom_string_data(attr), false, + &hint->data.length.value, + &hint->data.length.unit)) { + hint->prop = CSS_PROP_WIDTH; + hint->status = CSS_WIDTH_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_height_width_textarea( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_string *attr = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_rows, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (parse_dimension( + (const char *)dom_string_data(attr), false, + &hint->data.length.value, + &hint->data.length.unit)) { + hint->prop = CSS_PROP_HEIGHT; + hint->data.length.unit = CSS_UNIT_EM; + hint->status = CSS_HEIGHT_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } + + err = dom_element_get_attribute(node, + corestring_dom_cols, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (parse_dimension( + (const char *)dom_string_data(attr), false, + &hint->data.length.value, + &hint->data.length.unit)) { + hint->prop = CSS_PROP_WIDTH; + hint->data.length.unit = CSS_UNIT_EX; + hint->status = CSS_WIDTH_SET; + hint = css_hint_advance(hint); + } + dom_string_unref(attr); + } +} + +static void css_hint_width_input( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &(hint_ctx.hints[hint_ctx.len]); + dom_string *attr = NULL; + dom_exception err; + + err = dom_element_get_attribute(node, + corestring_dom_size, &attr); + + if (err == DOM_NO_ERR && attr != NULL) { + if (parse_dimension( + (const char *)dom_string_data(attr), false, + &hint->data.length.value, + &hint->data.length.unit)) { + dom_string *attr2 = NULL; + + err = dom_element_get_attribute(node, + corestring_dom_type, &attr2); + if (err == DOM_NO_ERR) { + + hint->prop = CSS_PROP_WIDTH; + hint->status = CSS_WIDTH_SET; + + if (attr2 == NULL || + dom_string_caseless_lwc_isequal( + attr2, + corestring_lwc_text) || + dom_string_caseless_lwc_isequal( + attr2, + corestring_lwc_search) || + dom_string_caseless_lwc_isequal( + attr2, + corestring_lwc_password) || + dom_string_caseless_lwc_isequal( + attr2, + corestring_lwc_file)) { + hint->data.length.unit = CSS_UNIT_EX; + } + if (attr2 != NULL) { + dom_string_unref(attr2); + } + hint = css_hint_advance(hint); + } + } + dom_string_unref(attr); + } +} + +static void css_hint_anchor_color( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + css_error error; + dom_exception err; + dom_string *color; + dom_node *bodynode = NULL; + + /* find body node */ + css_qname qs; + bool is_visited; + + qs.ns = NULL; + qs.name = lwc_string_ref(corestring_lwc_body); + if (named_ancestor_node(ctx, node, &qs, + (void *)&bodynode) != CSS_OK) { + /* Didn't find, or had error */ + lwc_string_unref(qs.name); + return ; + } + lwc_string_unref(qs.name); + + if (bodynode == NULL) { + return; + } + + error = node_is_visited(ctx, node, &is_visited); + if (error != CSS_OK) + return; + + if (is_visited) { + err = dom_element_get_attribute(bodynode, + corestring_dom_vlink, &color); + } else { + err = dom_element_get_attribute(bodynode, + corestring_dom_link, &color); + } + + if (err == DOM_NO_ERR && color != NULL) { + if (nscss_parse_colour( + (const char *)dom_string_data(color), + &hint->data.color)) { + hint->prop = CSS_PROP_COLOR; + hint->status = CSS_COLOR_COLOR; + hint = css_hint_advance(hint); + } + dom_string_unref(color); + } +} + +static void css_hint_body_color( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception err; + dom_string *color; + + err = dom_element_get_attribute(node, corestring_dom_text, &color); + + if (err == DOM_NO_ERR && color != NULL) { + if (nscss_parse_colour( + (const char *)dom_string_data(color), + &hint->data.color)) { + hint->prop = CSS_PROP_COLOR; + hint->status = CSS_COLOR_COLOR; + hint = css_hint_advance(hint); + } + dom_string_unref(color); + } +} + +static void css_hint_color( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception err; + dom_string *color; + + err = dom_element_get_attribute(node, corestring_dom_color, &color); + + if (err == DOM_NO_ERR && color != NULL) { + if (nscss_parse_colour( + (const char *)dom_string_data(color), + &hint->data.color)) { + hint->prop = CSS_PROP_COLOR; + hint->status = CSS_COLOR_COLOR; + hint = css_hint_advance(hint); + } + dom_string_unref(color); + } +} + +static void css_hint_font_size( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception err; + dom_string *size; + + err = dom_element_get_attribute(node, corestring_dom_size, &size); + if (err == DOM_NO_ERR && size != NULL) { + if (parse_font_size( + (const char *)dom_string_data(size), + &hint->status, + &hint->data.length.value, + &hint->data.length.unit)) { + hint->prop = CSS_PROP_FONT_SIZE; + hint = css_hint_advance(hint); + } + dom_string_unref(size); + } +} + +static void css_hint_float( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception err; + dom_string *align; + + err = dom_element_get_attribute(node, corestring_dom_align, &align); + if (err == DOM_NO_ERR && align != NULL) { + if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_left)) { + hint->prop = CSS_PROP_FLOAT; + hint->status = CSS_FLOAT_LEFT; + hint = css_hint_advance(hint); + + } else if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_right)) { + hint->prop = CSS_PROP_FLOAT; + hint->status = CSS_FLOAT_RIGHT; + hint = css_hint_advance(hint); + } + dom_string_unref(align); + } +} + +static void css_hint_caption_side( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception err; + dom_string *align = NULL; + + err = dom_element_get_attribute(node, corestring_dom_align, &align); + if (err == DOM_NO_ERR && align != NULL) { + if (dom_string_caseless_lwc_isequal(align, + corestring_lwc_bottom)) { + hint->prop = CSS_PROP_CAPTION_SIDE; + hint->status = CSS_CAPTION_SIDE_BOTTOM; + hint = css_hint_advance(hint); + } + dom_string_unref(align); + } +} + +static void css_hint_bg_color( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &hint_ctx.hints[hint_ctx.len]; + dom_exception err; + dom_string *bgcolor; + + err = dom_element_get_attribute(node, + corestring_dom_bgcolor, &bgcolor); + if (err == DOM_NO_ERR && bgcolor != NULL) { + if (nscss_parse_colour( + (const char *)dom_string_data(bgcolor), + &hint->data.color)) { + hint->prop = CSS_PROP_BACKGROUND_COLOR; + hint->status = CSS_BACKGROUND_COLOR_COLOR; + hint = css_hint_advance(hint); + } + dom_string_unref(bgcolor); + } +} + +static void css_hint_bg_image( + nscss_select_ctx *ctx, + dom_node *node) +{ + struct css_hint *hint = &(hint_ctx.hints[hint_ctx.len]); + dom_exception err; + dom_string *attr; + + err = dom_element_get_attribute(node, + corestring_dom_background, &attr); + if (err == DOM_NO_ERR && attr != NULL) { + nsurl *url; + nserror error = nsurl_join(ctx->base_url, + (const char *)dom_string_data(attr), &url); + dom_string_unref(attr); + + if (error == NSERROR_OK) { + lwc_string *iurl; + lwc_error lerror = lwc_intern_string(nsurl_access(url), + nsurl_length(url), &iurl); + nsurl_unref(url); + + if (lerror == lwc_error_ok) { + hint->prop = CSS_PROP_BACKGROUND_IMAGE; + hint->data.string = iurl; + hint->status = CSS_BACKGROUND_IMAGE_IMAGE; + hint = css_hint_advance(hint); + } + } + } +} + + +/* Exported function, documeted in css/hints.h */ +css_error node_presentational_hint(void *pw, void *node, + uint32_t *nhints, css_hint **hints) +{ + dom_exception exc; + dom_html_element_type tag_type; + + css_hint_clean(); + + exc = dom_html_element_get_tag_type(node, &tag_type); + if (exc != DOM_NO_ERR) { + tag_type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; + } + + switch (tag_type) { + case DOM_HTML_ELEMENT_TYPE_TH: + case DOM_HTML_ELEMENT_TYPE_TD: + css_hint_width(pw, node); + css_hint_table_cell_border_padding(pw, node); + /* fallthrough */ + case DOM_HTML_ELEMENT_TYPE_TR: + css_hint_height(pw, node); + /* fallthrough */ + case DOM_HTML_ELEMENT_TYPE_THEAD: + case DOM_HTML_ELEMENT_TYPE_TBODY: + case DOM_HTML_ELEMENT_TYPE_TFOOT: + css_hint_text_align_special(pw, node); + /* fallthrough */ + case DOM_HTML_ELEMENT_TYPE_COL: + css_hint_vertical_align_table_cells(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_APPLET: + case DOM_HTML_ELEMENT_TYPE_IMG: + css_hint_margin_hspace_vspace(pw, node); + /* fallthrough */ + case DOM_HTML_ELEMENT_TYPE_EMBED: + case DOM_HTML_ELEMENT_TYPE_IFRAME: + case DOM_HTML_ELEMENT_TYPE_OBJECT: + css_hint_height(pw, node); + css_hint_width(pw, node); + css_hint_vertical_align_replaced(pw, node); + css_hint_float(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_P: + case DOM_HTML_ELEMENT_TYPE_H1: + case DOM_HTML_ELEMENT_TYPE_H2: + case DOM_HTML_ELEMENT_TYPE_H3: + case DOM_HTML_ELEMENT_TYPE_H4: + case DOM_HTML_ELEMENT_TYPE_H5: + case DOM_HTML_ELEMENT_TYPE_H6: + css_hint_text_align_normal(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_CENTER: + css_hint_text_align_center(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_CAPTION: + css_hint_caption_side(pw, node); + /* fallthrough */ + case DOM_HTML_ELEMENT_TYPE_DIV: + css_hint_text_align_special(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_TABLE: + css_hint_text_align_table_special(pw, node); + css_hint_table_spacing_border(pw, node); + css_hint_float(pw, node); + css_hint_margin_left_right_align_center(pw, node); + css_hint_width(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_HR: + css_hint_margin_left_right_hr(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_TEXTAREA: + css_hint_height_width_textarea(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_INPUT: + css_hint_width_input(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_A: + css_hint_anchor_color(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_FONT: + css_hint_font_size(pw, node); + break; + case DOM_HTML_ELEMENT_TYPE_BODY: + css_hint_body_color(pw, node); + break; + default: + break; + } + + if (tag_type != DOM_HTML_ELEMENT_TYPE__UNKNOWN) { + css_hint_color(pw, node); + css_hint_bg_color(pw, node); + css_hint_bg_image(pw, node); + } + +#ifdef LOG_STATS + LOG("Properties with hints: %i", hint_ctx.len); +#endif + + css_hint_get_hints(hints, nhints); + + return CSS_OK; +} diff --git a/content/handlers/css/hints.h b/content/handlers/css/hints.h new file mode 100644 index 000000000..2c1835970 --- /dev/null +++ b/content/handlers/css/hints.h @@ -0,0 +1,55 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NETSURF_CSS_HINTS_H_ +#define NETSURF_CSS_HINTS_H_ + +#include + +#include + +nserror css_hint_init(void); +void css_hint_fini(void); + +/** + * Callback to retrieve presentational hints for a node + * + * \param[in] pw HTML document + * \param[in] node DOM node + * \param[out] nhints number of hints retrived + * \param[out] hints retrived hints + * \return CSS_OK on success, + * CSS_PROPERTY_NOT_SET if there is no hint for the requested property, + * CSS_NOMEM on memory exhaustion. + */ +css_error node_presentational_hint( + void *pw, + void *node, + uint32_t *nhints, + css_hint **hints); + +/** + * Parser for colours specified in attribute values. + * + * \param data Data to parse (NUL-terminated) + * \param result Pointer to location to receive resulting css_color + * \return true on success, false on invalid input + */ +bool nscss_parse_colour(const char *data, css_color *result); + +#endif diff --git a/content/handlers/css/internal.c b/content/handlers/css/internal.c new file mode 100644 index 000000000..d66b87a54 --- /dev/null +++ b/content/handlers/css/internal.c @@ -0,0 +1,63 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "utils/nsurl.h" + +#include "internal.h" + +/* exported interface documented in content/handlers/css/internal.h */ +css_error nscss_resolve_url(void *pw, const char *base, + lwc_string *rel, lwc_string **abs) +{ + lwc_error lerror; + nserror error; + nsurl *nsbase; + nsurl *nsabs; + + /* Create nsurl from base */ + /* TODO: avoid this */ + error = nsurl_create(base, &nsbase); + if (error != NSERROR_OK) { + return error == NSERROR_NOMEM ? CSS_NOMEM : CSS_INVALID; + } + + /* Resolve URI */ + error = nsurl_join(nsbase, lwc_string_data(rel), &nsabs); + if (error != NSERROR_OK) { + nsurl_unref(nsbase); + return error == NSERROR_NOMEM ? CSS_NOMEM : CSS_INVALID; + } + + nsurl_unref(nsbase); + + /* Intern it */ + lerror = lwc_intern_string(nsurl_access(nsabs), + nsurl_length(nsabs), abs); + if (lerror != lwc_error_ok) { + *abs = NULL; + nsurl_unref(nsabs); + return lerror == lwc_error_oom ? CSS_NOMEM : CSS_INVALID; + } + + nsurl_unref(nsabs); + + return CSS_OK; +} diff --git a/content/handlers/css/internal.h b/content/handlers/css/internal.h new file mode 100644 index 000000000..5c539c843 --- /dev/null +++ b/content/handlers/css/internal.h @@ -0,0 +1,35 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NETSURF_CSS_INTERNAL_H_ +#define NETSURF_CSS_INTERNAL_H_ + +/** + * URL resolution callback for libcss + * + * \param pw Resolution context + * \param base Base URI + * \param rel Relative URL + * \param abs Pointer to location to receive resolved URL + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion, + * CSS_INVALID if resolution failed. + */ +css_error nscss_resolve_url(void *pw, const char *base, lwc_string *rel, lwc_string **abs); + +#endif diff --git a/content/handlers/css/select.c b/content/handlers/css/select.c new file mode 100644 index 000000000..ea324e773 --- /dev/null +++ b/content/handlers/css/select.c @@ -0,0 +1,1854 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "content/urldb.h" +#include "desktop/system_colour.h" +#include "utils/nsoption.h" +#include "utils/corestrings.h" +#include "utils/log.h" + +#include "internal.h" +#include "hints.h" +#include "select.h" + +static css_error node_name(void *pw, void *node, css_qname *qname); +static css_error node_classes(void *pw, void *node, + lwc_string ***classes, uint32_t *n_classes); +static css_error node_id(void *pw, void *node, lwc_string **id); +static css_error named_parent_node(void *pw, void *node, + const css_qname *qname, void **parent); +static css_error named_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling); +static css_error named_generic_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling); +static css_error parent_node(void *pw, void *node, void **parent); +static css_error sibling_node(void *pw, void *node, void **sibling); +static css_error node_has_name(void *pw, void *node, + const css_qname *qname, bool *match); +static css_error node_has_class(void *pw, void *node, + lwc_string *name, bool *match); +static css_error node_has_id(void *pw, void *node, + lwc_string *name, bool *match); +static css_error node_has_attribute(void *pw, void *node, + const css_qname *qname, bool *match); +static css_error node_has_attribute_equal(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_dashmatch(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_includes(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_prefix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_suffix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match); +static css_error node_is_root(void *pw, void *node, bool *match); +static css_error node_count_siblings(void *pw, void *node, + bool same_name, bool after, int32_t *count); +static css_error node_is_empty(void *pw, void *node, bool *match); +static css_error node_is_link(void *pw, void *node, bool *match); +static css_error node_is_hover(void *pw, void *node, bool *match); +static css_error node_is_active(void *pw, void *node, bool *match); +static css_error node_is_focus(void *pw, void *node, bool *match); +static css_error node_is_enabled(void *pw, void *node, bool *match); +static css_error node_is_disabled(void *pw, void *node, bool *match); +static css_error node_is_checked(void *pw, void *node, bool *match); +static css_error node_is_target(void *pw, void *node, bool *match); +static css_error node_is_lang(void *pw, void *node, + lwc_string *lang, bool *match); +static css_error ua_default_for_property(void *pw, uint32_t property, + css_hint *hint); +static css_error set_libcss_node_data(void *pw, void *node, + void *libcss_node_data); +static css_error get_libcss_node_data(void *pw, void *node, + void **libcss_node_data); + +static css_error nscss_compute_font_size(void *pw, const css_hint *parent, + css_hint *size); + + +/** + * Selection callback table for libcss + */ +static css_select_handler selection_handler = { + CSS_SELECT_HANDLER_VERSION_1, + + node_name, + node_classes, + node_id, + named_ancestor_node, + named_parent_node, + named_sibling_node, + named_generic_sibling_node, + parent_node, + sibling_node, + node_has_name, + node_has_class, + node_has_id, + node_has_attribute, + node_has_attribute_equal, + node_has_attribute_dashmatch, + node_has_attribute_includes, + node_has_attribute_prefix, + node_has_attribute_suffix, + node_has_attribute_substring, + node_is_root, + node_count_siblings, + node_is_empty, + node_is_link, + node_is_visited, + node_is_hover, + node_is_active, + node_is_focus, + node_is_enabled, + node_is_disabled, + node_is_checked, + node_is_target, + node_is_lang, + node_presentational_hint, + ua_default_for_property, + nscss_compute_font_size, + set_libcss_node_data, + get_libcss_node_data +}; + +/** + * Create an inline style + * + * \param data Source data + * \param len Length of data in bytes + * \param charset Charset of data, or NULL if unknown + * \param url Base URL of document containing data + * \param allow_quirks True to permit CSS parsing quirks + * \return Pointer to stylesheet, or NULL on failure. + */ +css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len, + const char *charset, const char *url, bool allow_quirks) +{ + css_stylesheet_params params; + css_stylesheet *sheet; + css_error error; + + params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1; + params.level = CSS_LEVEL_DEFAULT; + params.charset = charset; + params.url = url; + params.title = NULL; + params.allow_quirks = allow_quirks; + params.inline_style = true; + params.resolve = nscss_resolve_url; + params.resolve_pw = NULL; + params.import = NULL; + params.import_pw = NULL; + params.color = ns_system_colour; + params.color_pw = NULL; + params.font = NULL; + params.font_pw = NULL; + + error = css_stylesheet_create(¶ms, &sheet); + if (error != CSS_OK) { + LOG("Failed creating sheet: %d", error); + return NULL; + } + + error = css_stylesheet_append_data(sheet, data, len); + if (error != CSS_OK && error != CSS_NEEDDATA) { + LOG("failed appending data: %d", error); + css_stylesheet_destroy(sheet); + return NULL; + } + + error = css_stylesheet_data_done(sheet); + if (error != CSS_OK) { + LOG("failed completing parse: %d", error); + css_stylesheet_destroy(sheet); + return NULL; + } + + return sheet; +} + +/* Handler for libcss_node_data, stored as libdom node user data */ +static void nscss_dom_user_data_handler(dom_node_operation operation, + dom_string *key, void *data, struct dom_node *src, + struct dom_node *dst) +{ + css_error error; + + if (dom_string_isequal(corestring_dom___ns_key_libcss_node_data, + key) == false || data == NULL) { + return; + } + + switch (operation) { + case DOM_NODE_CLONED: + error = css_libcss_node_data_handler(&selection_handler, + CSS_NODE_CLONED, + NULL, src, dst, data); + if (error != CSS_OK) + LOG("Failed to clone libcss_node_data."); + break; + + case DOM_NODE_RENAMED: + error = css_libcss_node_data_handler(&selection_handler, + CSS_NODE_MODIFIED, + NULL, src, NULL, data); + if (error != CSS_OK) + LOG("Failed to update libcss_node_data."); + break; + + case DOM_NODE_IMPORTED: + case DOM_NODE_ADOPTED: + case DOM_NODE_DELETED: + error = css_libcss_node_data_handler(&selection_handler, + CSS_NODE_DELETED, + NULL, src, NULL, data); + if (error != CSS_OK) + LOG("Failed to delete libcss_node_data."); + break; + + default: + LOG("User data operation not handled."); + assert(0); + } +} + +/** + * Get style selection results for an element + * + * \param ctx CSS selection context + * \param n Element to select for + * \param media Permitted media types + * \param inline_style Inline style associated with element, or NULL + * \return Pointer to selection results (containing computed styles), + * or NULL on failure + */ +css_select_results *nscss_get_style(nscss_select_ctx *ctx, dom_node *n, + uint64_t media, const css_stylesheet *inline_style) +{ + css_select_results *styles; + int pseudo_element; + css_error error; + + /* Select style for node */ + error = css_select_style(ctx->ctx, n, media, inline_style, + &selection_handler, ctx, &styles); + + if (error != CSS_OK || styles == NULL) { + /* Failed selecting partial style -- bail out */ + return NULL; + } + + /* If there's a parent style, compose with partial to obtain + * complete computed style for element */ + if (ctx->parent_style != NULL) { + /* Complete the computed style, by composing with the parent + * element's style */ + error = css_computed_style_compose(ctx->parent_style, + styles->styles[CSS_PSEUDO_ELEMENT_NONE], + nscss_compute_font_size, NULL, + styles->styles[CSS_PSEUDO_ELEMENT_NONE]); + if (error != CSS_OK) { + css_select_results_destroy(styles); + return NULL; + } + } + + for (pseudo_element = CSS_PSEUDO_ELEMENT_NONE + 1; + pseudo_element < CSS_PSEUDO_ELEMENT_COUNT; + pseudo_element++) { + + if (pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LETTER || + pseudo_element == CSS_PSEUDO_ELEMENT_FIRST_LINE) + /* TODO: Handle first-line and first-letter pseudo + * element computed style completion */ + continue; + + if (styles->styles[pseudo_element] == NULL) + /* There were no rules concerning this pseudo element */ + continue; + + /* Complete the pseudo element's computed style, by composing + * with the base element's style */ + error = css_computed_style_compose( + styles->styles[CSS_PSEUDO_ELEMENT_NONE], + styles->styles[pseudo_element], + nscss_compute_font_size, NULL, + styles->styles[pseudo_element]); + if (error != CSS_OK) { + /* TODO: perhaps this shouldn't be quite so + * catastrophic? */ + css_select_results_destroy(styles); + return NULL; + } + } + + return styles; +} + +/** + * Get an initial style + * + * \param ctx CSS selection context + * \return Pointer to partial computed style, or NULL on failure + */ +static css_computed_style *nscss_get_initial_style(nscss_select_ctx *ctx) +{ + css_computed_style *style; + css_error error; + + error = css_computed_style_create(&style); + if (error != CSS_OK) + return NULL; + + error = css_computed_style_initialise(style, &selection_handler, ctx); + if (error != CSS_OK) { + css_computed_style_destroy(style); + return NULL; + } + + return style; +} + +/** + * Get a blank style + * + * \param ctx CSS selection context + * \param parent Parent style to cascade inherited properties from + * \return Pointer to blank style, or NULL on failure + */ +css_computed_style *nscss_get_blank_style(nscss_select_ctx *ctx, + const css_computed_style *parent) +{ + css_computed_style *partial; + css_error error; + + partial = nscss_get_initial_style(ctx); + if (partial == NULL) + return NULL; + + error = css_computed_style_compose(parent, partial, + nscss_compute_font_size, NULL, partial); + if (error != CSS_OK) { + css_computed_style_destroy(partial); + return NULL; + } + + return partial; +} + +/** + * Font size computation callback for libcss + * + * \param pw Computation context + * \param parent Parent font size (absolute) + * \param size Font size to compute + * \return CSS_OK on success + * + * \post \a size will be an absolute font size + */ +css_error nscss_compute_font_size(void *pw, const css_hint *parent, + css_hint *size) +{ + /** + * Table of font-size keyword scale factors + * + * These are multiplied by the configured default font size + * to produce an absolute size for the relevant keyword + */ + static const css_fixed factors[] = { + FLTTOFIX(0.5625), /* xx-small */ + FLTTOFIX(0.6250), /* x-small */ + FLTTOFIX(0.8125), /* small */ + FLTTOFIX(1.0000), /* medium */ + FLTTOFIX(1.1250), /* large */ + FLTTOFIX(1.5000), /* x-large */ + FLTTOFIX(2.0000) /* xx-large */ + }; + css_hint_length parent_size; + + /* Grab parent size, defaulting to medium if none */ + if (parent == NULL) { + parent_size.value = FDIV(FMUL(factors[CSS_FONT_SIZE_MEDIUM - 1], + INTTOFIX(nsoption_int(font_size))), + INTTOFIX(10)); + parent_size.unit = CSS_UNIT_PT; + } else { + assert(parent->status == CSS_FONT_SIZE_DIMENSION); + assert(parent->data.length.unit != CSS_UNIT_EM); + assert(parent->data.length.unit != CSS_UNIT_EX); + assert(parent->data.length.unit != CSS_UNIT_PCT); + + parent_size = parent->data.length; + } + + assert(size->status != CSS_FONT_SIZE_INHERIT); + + if (size->status < CSS_FONT_SIZE_LARGER) { + /* Keyword -- simple */ + size->data.length.value = FDIV(FMUL(factors[size->status - 1], + INTTOFIX(nsoption_int(font_size))), F_10); + size->data.length.unit = CSS_UNIT_PT; + } else if (size->status == CSS_FONT_SIZE_LARGER) { + /** \todo Step within table, if appropriate */ + size->data.length.value = + FMUL(parent_size.value, FLTTOFIX(1.2)); + size->data.length.unit = parent_size.unit; + } else if (size->status == CSS_FONT_SIZE_SMALLER) { + /** \todo Step within table, if appropriate */ + size->data.length.value = + FDIV(parent_size.value, FLTTOFIX(1.2)); + size->data.length.unit = parent_size.unit; + } else if (size->data.length.unit == CSS_UNIT_EM || + size->data.length.unit == CSS_UNIT_EX) { + size->data.length.value = + FMUL(size->data.length.value, parent_size.value); + + if (size->data.length.unit == CSS_UNIT_EX) { + /* 1ex = 0.6em in NetSurf */ + size->data.length.value = FMUL(size->data.length.value, + FLTTOFIX(0.6)); + } + + size->data.length.unit = parent_size.unit; + } else if (size->data.length.unit == CSS_UNIT_PCT) { + size->data.length.value = FDIV(FMUL(size->data.length.value, + parent_size.value), INTTOFIX(100)); + size->data.length.unit = parent_size.unit; + } + + size->status = CSS_FONT_SIZE_DIMENSION; + + return CSS_OK; +} + +/****************************************************************************** + * Style selection callbacks * + ******************************************************************************/ + +/** + * Callback to retrieve a node's name. + * + * \param pw HTML document + * \param node DOM node + * \param qname Pointer to location to receive node name + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + */ +css_error node_name(void *pw, void *node, css_qname *qname) +{ + dom_node *n = node; + dom_string *name; + dom_exception err; + + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + qname->ns = NULL; + + err = dom_string_intern(name, &qname->name); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + return CSS_NOMEM; + } + + dom_string_unref(name); + + return CSS_OK; +} + +/** + * Callback to retrieve a node's classes. + * + * \param pw HTML document + * \param node DOM node + * \param classes Pointer to location to receive class name array + * \param n_classes Pointer to location to receive length of class name array + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \note The returned array will be destroyed by libcss. Therefore, it must + * be allocated using the same allocator as used by libcss during style + * selection. + */ +css_error node_classes(void *pw, void *node, + lwc_string ***classes, uint32_t *n_classes) +{ + dom_node *n = node; + dom_exception err; + + *classes = NULL; + *n_classes = 0; + + err = dom_element_get_classes(n, classes, n_classes); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + return CSS_OK; +} + +/** + * Callback to retrieve a node's ID. + * + * \param pw HTML document + * \param node DOM node + * \param id Pointer to location to receive id value + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + */ +css_error node_id(void *pw, void *node, lwc_string **id) +{ + dom_node *n = node; + dom_string *attr; + dom_exception err; + + *id = NULL; + + /** \todo Assumes an HTML DOM */ + err = dom_html_element_get_id(n, &attr); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + if (attr != NULL) { + err = dom_string_intern(attr, id); + if (err != DOM_NO_ERR) { + dom_string_unref(attr); + return CSS_NOMEM; + } + dom_string_unref(attr); + } + + return CSS_OK; +} + +/** + * Callback to find a named ancestor node. + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param ancestor Pointer to location to receive ancestor + * \return CSS_OK. + * + * \post \a ancestor will contain the result, or NULL if there is no match + */ +css_error named_ancestor_node(void *pw, void *node, + const css_qname *qname, void **ancestor) +{ + dom_element_named_ancestor_node(node, qname->name, + (struct dom_element **)ancestor); + + return CSS_OK; +} + +/** + * Callback to find a named parent node + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param parent Pointer to location to receive parent + * \return CSS_OK. + * + * \post \a parent will contain the result, or NULL if there is no match + */ +css_error named_parent_node(void *pw, void *node, + const css_qname *qname, void **parent) +{ + dom_element_named_parent_node(node, qname->name, + (struct dom_element **)parent); + + return CSS_OK; +} + +/** + * Callback to find a named sibling node. + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param sibling Pointer to location to receive sibling + * \return CSS_OK. + * + * \post \a sibling will contain the result, or NULL if there is no match + */ +css_error named_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling) +{ + dom_node *n = node; + dom_node *prev; + dom_exception err; + + *sibling = NULL; + + /* Find sibling element */ + err = dom_node_get_previous_sibling(n, &n); + if (err != DOM_NO_ERR) + return CSS_OK; + + while (n != NULL) { + dom_node_type type; + + err = dom_node_get_node_type(n, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (type == DOM_ELEMENT_NODE) + break; + + err = dom_node_get_previous_sibling(n, &prev); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + n = prev; + } + + if (n != NULL) { + dom_string *name; + + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + + if (dom_string_caseless_lwc_isequal(name, qname->name)) { + *sibling = n; + } + + dom_string_unref(name); + } + + return CSS_OK; +} + +/** + * Callback to find a named generic sibling node. + * + * \param pw HTML document + * \param node DOM node + * \param qname Node name to search for + * \param sibling Pointer to location to receive ancestor + * \return CSS_OK. + * + * \post \a sibling will contain the result, or NULL if there is no match + */ +css_error named_generic_sibling_node(void *pw, void *node, + const css_qname *qname, void **sibling) +{ + dom_node *n = node; + dom_node *prev; + dom_exception err; + + *sibling = NULL; + + err = dom_node_get_previous_sibling(n, &n); + if (err != DOM_NO_ERR) + return CSS_OK; + + while (n != NULL) { + dom_node_type type; + dom_string *name; + + err = dom_node_get_node_type(n, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (type == DOM_ELEMENT_NODE) { + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (dom_string_caseless_lwc_isequal(name, + qname->name)) { + dom_string_unref(name); + dom_node_unref(n); + *sibling = n; + break; + } + dom_string_unref(name); + } + + err = dom_node_get_previous_sibling(n, &prev); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + n = prev; + } + + return CSS_OK; +} + +/** + * Callback to retrieve the parent of a node. + * + * \param pw HTML document + * \param node DOM node + * \param parent Pointer to location to receive parent + * \return CSS_OK. + * + * \post \a parent will contain the result, or NULL if there is no match + */ +css_error parent_node(void *pw, void *node, void **parent) +{ + dom_element_parent_node(node, (struct dom_element **)parent); + + return CSS_OK; +} + +/** + * Callback to retrieve the preceding sibling of a node. + * + * \param pw HTML document + * \param node DOM node + * \param sibling Pointer to location to receive sibling + * \return CSS_OK. + * + * \post \a sibling will contain the result, or NULL if there is no match + */ +css_error sibling_node(void *pw, void *node, void **sibling) +{ + dom_node *n = node; + dom_node *prev; + dom_exception err; + + *sibling = NULL; + + /* Find sibling element */ + err = dom_node_get_previous_sibling(n, &n); + if (err != DOM_NO_ERR) + return CSS_OK; + + while (n != NULL) { + dom_node_type type; + + err = dom_node_get_node_type(n, &type); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + if (type == DOM_ELEMENT_NODE) + break; + + err = dom_node_get_previous_sibling(n, &prev); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_OK; + } + + dom_node_unref(n); + n = prev; + } + + if (n != NULL) { + /** \todo Sort out reference counting */ + dom_node_unref(n); + + *sibling = n; + } + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given name. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_name(void *pw, void *node, + const css_qname *qname, bool *match) +{ + nscss_select_ctx *ctx = pw; + dom_node *n = node; + + if (lwc_string_isequal(qname->name, ctx->universal, match) == + lwc_error_ok && *match == false) { + dom_string *name; + dom_exception err; + + err = dom_node_get_node_name(n, &name); + if (err != DOM_NO_ERR) + return CSS_OK; + + /* Element names are case insensitive in HTML */ + *match = dom_string_caseless_lwc_isequal(name, qname->name); + + dom_string_unref(name); + } + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given class. + * + * \param pw HTML document + * \param node DOM node + * \param name Name to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_class(void *pw, void *node, + lwc_string *name, bool *match) +{ + dom_node *n = node; + dom_exception err; + + /** \todo: Ensure that libdom performs case-insensitive + * matching in quirks mode */ + err = dom_element_has_class(n, name, match); + + assert(err == DOM_NO_ERR); + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given id. + * + * \param pw HTML document + * \param node DOM node + * \param name Name to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_id(void *pw, void *node, + lwc_string *name, bool *match) +{ + dom_node *n = node; + dom_string *attr; + dom_exception err; + + *match = false; + + /** \todo Assumes an HTML DOM */ + err = dom_html_element_get_id(n, &attr); + if (err != DOM_NO_ERR) + return CSS_OK; + + if (attr != NULL) { + *match = dom_string_lwc_isequal(attr, name); + + dom_string_unref(attr); + } + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute(void *pw, void *node, + const css_qname *qname, bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_exception err; + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_has_attribute(n, name, match); + if (err != DOM_NO_ERR) { + dom_string_unref(name); + return CSS_OK; + } + + dom_string_unref(name); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with given name and value. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_equal(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value dashmatches that given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_dashmatch(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for dashmatch */ + if (*match == false) { + const char *vdata = lwc_string_data(value); + const char *data = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + + if (len > vlen && data[vlen] == '-' && + strncasecmp(data, vdata, vlen) == 0) { + *match = true; + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value includes that given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_includes(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + size_t vlen = lwc_string_length(value); + const char *p; + const char *start; + const char *end; + + *match = false; + + if (vlen == 0) { + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for match */ + start = (const char *) dom_string_data(atr_val); + end = start + dom_string_byte_length(atr_val); + + for (p = start; p <= end; p++) { + if (*p == ' ' || *p == '\0') { + if ((size_t) (p - start) == vlen && + strncasecmp(start, + lwc_string_data(value), + vlen) == 0) { + *match = true; + break; + } + + start = p + 1; + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value has the prefix given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_prefix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for prefix match */ + if (*match == false) { + const char *data = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + + if ((len >= vlen) && + (strncasecmp(data, lwc_string_data(value), vlen) == 0)) { + *match = true; + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value has the suffix given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_suffix(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for prefix match */ + if (*match == false) { + const char *data = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + + const char *start = (char *) data + len - vlen; + + if ((len >= vlen) && + (strncasecmp(start, lwc_string_data(value), vlen) == 0)) { + *match = true; + } + + + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node has an attribute with the given name whose + * value contains the substring given. + * + * \param pw HTML document + * \param node DOM node + * \param qname Name to match + * \param value Value to match + * \param match Pointer to location to receive result + * \return CSS_OK on success, + * CSS_NOMEM on memory exhaustion. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_has_attribute_substring(void *pw, void *node, + const css_qname *qname, lwc_string *value, + bool *match) +{ + dom_node *n = node; + dom_string *name; + dom_string *atr_val; + dom_exception err; + + size_t vlen = lwc_string_length(value); + + if (vlen == 0) { + *match = false; + return CSS_OK; + } + + err = dom_string_create_interned( + (const uint8_t *) lwc_string_data(qname->name), + lwc_string_length(qname->name), &name); + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + err = dom_element_get_attribute(n, name, &atr_val); + if ((err != DOM_NO_ERR) || (atr_val == NULL)) { + dom_string_unref(name); + *match = false; + return CSS_OK; + } + + dom_string_unref(name); + + /* check for exact match */ + *match = dom_string_caseless_lwc_isequal(atr_val, value); + + /* check for prefix match */ + if (*match == false) { + const char *vdata = lwc_string_data(value); + const char *start = (const char *) dom_string_data(atr_val); + size_t len = dom_string_byte_length(atr_val); + const char *last_start = start + len - vlen; + + if (len >= vlen) { + while (start <= last_start) { + if (strncasecmp(start, vdata, + vlen) == 0) { + *match = true; + break; + } + + start++; + } + } + } + + dom_string_unref(atr_val); + + return CSS_OK; +} + +/** + * Callback to determine if a node is the root node of the document. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_root(void *pw, void *node, bool *match) +{ + dom_node *n = node; + dom_node *parent; + dom_node_type type; + dom_exception err; + + err = dom_node_get_parent_node(n, &parent); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + if (parent != NULL) { + err = dom_node_get_node_type(parent, &type); + + dom_node_unref(parent); + + if (err != DOM_NO_ERR) + return CSS_NOMEM; + + if (type != DOM_DOCUMENT_NODE) { + *match = false; + return CSS_OK; + } + } + + *match = true; + + return CSS_OK; +} + +static int +node_count_siblings_check(dom_node *node, + bool check_name, + dom_string *name) +{ + dom_node_type type; + int ret = 0; + dom_exception exc; + + if (node == NULL) + return 0; + + exc = dom_node_get_node_type(node, &type); + if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) { + return 0; + } + + if (check_name) { + dom_string *node_name = NULL; + exc = dom_node_get_node_name(node, &node_name); + + if ((exc == DOM_NO_ERR) && (node_name != NULL)) { + + if (dom_string_caseless_isequal(name, + node_name)) { + ret = 1; + } + dom_string_unref(node_name); + } + } else { + ret = 1; + } + + return ret; +} + +/** + * Callback to count a node's siblings. + * + * \param pw HTML document + * \param n DOM node + * \param same_name Only count siblings with the same name, or all + * \param after Count anteceding instead of preceding siblings + * \param count Pointer to location to receive result + * \return CSS_OK. + * + * \post \a count will contain the number of siblings + */ +css_error node_count_siblings(void *pw, void *n, bool same_name, + bool after, int32_t *count) +{ + int32_t cnt = 0; + dom_exception exc; + dom_string *node_name = NULL; + + if (same_name) { + dom_node *node = n; + exc = dom_node_get_node_name(node, &node_name); + if ((exc != DOM_NO_ERR) || (node_name == NULL)) { + return CSS_NOMEM; + } + } + + if (after) { + dom_node *node = dom_node_ref(n); + dom_node *next; + + do { + exc = dom_node_get_next_sibling(node, &next); + if ((exc != DOM_NO_ERR)) + break; + + dom_node_unref(node); + node = next; + + cnt += node_count_siblings_check(node, same_name, node_name); + } while (node != NULL); + } else { + dom_node *node = dom_node_ref(n); + dom_node *next; + + do { + exc = dom_node_get_previous_sibling(node, &next); + if ((exc != DOM_NO_ERR)) + break; + + dom_node_unref(node); + node = next; + + cnt += node_count_siblings_check(node, same_name, node_name); + + } while (node != NULL); + } + + if (node_name != NULL) { + dom_string_unref(node_name); + } + + *count = cnt; + return CSS_OK; +} + +/** + * Callback to determine if a node is empty. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node is empty and false otherwise. + */ +css_error node_is_empty(void *pw, void *node, bool *match) +{ + dom_node *n = node, *next; + dom_exception err; + + *match = true; + + err = dom_node_get_first_child(n, &n); + if (err != DOM_NO_ERR) { + return CSS_BADPARM; + } + + while (n != NULL) { + dom_node_type ntype; + err = dom_node_get_node_type(n, &ntype); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_BADPARM; + } + + if (ntype == DOM_ELEMENT_NODE || + ntype == DOM_TEXT_NODE) { + *match = false; + dom_node_unref(n); + break; + } + + err = dom_node_get_next_sibling(n, &next); + if (err != DOM_NO_ERR) { + dom_node_unref(n); + return CSS_BADPARM; + } + dom_node_unref(n); + n = next; + } + + return CSS_OK; +} + +/** + * Callback to determine if a node is a linking element. + * + * \param pw HTML document + * \param n DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_link(void *pw, void *n, bool *match) +{ + dom_node *node = n; + dom_exception exc; + dom_string *node_name = NULL; + + exc = dom_node_get_node_name(node, &node_name); + if ((exc != DOM_NO_ERR) || (node_name == NULL)) { + return CSS_NOMEM; + } + + if (dom_string_caseless_lwc_isequal(node_name, corestring_lwc_a)) { + bool has_href; + exc = dom_element_has_attribute(node, corestring_dom_href, + &has_href); + if ((exc == DOM_NO_ERR) && (has_href)) { + *match = true; + } else { + *match = false; + } + } else { + *match = false; + } + dom_string_unref(node_name); + + return CSS_OK; +} + +/** + * Callback to determine if a node is a linking element whose target has been + * visited. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_visited(void *pw, void *node, bool *match) +{ + nscss_select_ctx *ctx = pw; + nsurl *url; + nserror error; + const struct url_data *data; + + dom_exception exc; + dom_node *n = node; + dom_string *s = NULL; + + *match = false; + + exc = dom_node_get_node_name(n, &s); + if ((exc != DOM_NO_ERR) || (s == NULL)) { + return CSS_NOMEM; + } + + if (!dom_string_caseless_lwc_isequal(s, corestring_lwc_a)) { + /* Can't be visited; not ancher element */ + dom_string_unref(s); + return CSS_OK; + } + + /* Finished with node name string */ + dom_string_unref(s); + s = NULL; + + exc = dom_element_get_attribute(n, corestring_dom_href, &s); + if ((exc != DOM_NO_ERR) || (s == NULL)) { + /* Can't be visited; not got a URL */ + return CSS_OK; + } + + /* Make href absolute */ + /* TODO: this duplicates what we do for box->href + * should we put the absolute URL on the dom node? */ + error = nsurl_join(ctx->base_url, dom_string_data(s), &url); + + /* Finished with href string */ + dom_string_unref(s); + + if (error != NSERROR_OK) { + /* Couldn't make nsurl object */ + return CSS_NOMEM; + } + + data = urldb_get_url_data(url); + + /* Visited if in the db and has + * non-zero visit count */ + if (data != NULL && data->visits > 0) + *match = true; + + nsurl_unref(url); + + return CSS_OK; +} + +/** + * Callback to determine if a node is currently being hovered over. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_hover(void *pw, void *node, bool *match) +{ + /** \todo Support hovering */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is currently activated. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_active(void *pw, void *node, bool *match) +{ + /** \todo Support active nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node has the input focus. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_focus(void *pw, void *node, bool *match) +{ + /** \todo Support focussed nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is enabled. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node is enabled and false otherwise. + */ +css_error node_is_enabled(void *pw, void *node, bool *match) +{ + /** \todo Support enabled nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is disabled. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node is disabled and false otherwise. + */ +css_error node_is_disabled(void *pw, void *node, bool *match) +{ + /** \todo Support disabled nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is checked. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node is checked and false otherwise. + */ +css_error node_is_checked(void *pw, void *node, bool *match) +{ + /** \todo Support checked nodes */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node is the target of the document URL. + * + * \param pw HTML document + * \param node DOM node + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match with contain true if the node matches and false otherwise. + */ +css_error node_is_target(void *pw, void *node, bool *match) +{ + /** \todo Support target */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to determine if a node has the given language + * + * \param pw HTML document + * \param node DOM node + * \param lang Language specifier to match + * \param match Pointer to location to receive result + * \return CSS_OK. + * + * \post \a match will contain true if the node matches and false otherwise. + */ +css_error node_is_lang(void *pw, void *node, + lwc_string *lang, bool *match) +{ + /** \todo Support languages */ + + *match = false; + + return CSS_OK; +} + +/** + * Callback to retrieve the User-Agent defaults for a CSS property. + * + * \param pw HTML document + * \param property Property to retrieve defaults for + * \param hint Pointer to hint object to populate + * \return CSS_OK on success, + * CSS_INVALID if the property should not have a user-agent default. + */ +css_error ua_default_for_property(void *pw, uint32_t property, css_hint *hint) +{ + if (property == CSS_PROP_COLOR) { + hint->data.color = 0xff000000; + hint->status = CSS_COLOR_COLOR; + } else if (property == CSS_PROP_FONT_FAMILY) { + hint->data.strings = NULL; + switch (nsoption_int(font_default)) { + case PLOT_FONT_FAMILY_SANS_SERIF: + hint->status = CSS_FONT_FAMILY_SANS_SERIF; + break; + case PLOT_FONT_FAMILY_SERIF: + hint->status = CSS_FONT_FAMILY_SERIF; + break; + case PLOT_FONT_FAMILY_MONOSPACE: + hint->status = CSS_FONT_FAMILY_MONOSPACE; + break; + case PLOT_FONT_FAMILY_CURSIVE: + hint->status = CSS_FONT_FAMILY_CURSIVE; + break; + case PLOT_FONT_FAMILY_FANTASY: + hint->status = CSS_FONT_FAMILY_FANTASY; + break; + } + } else if (property == CSS_PROP_QUOTES) { + /** \todo Not exactly useful :) */ + hint->data.strings = NULL; + hint->status = CSS_QUOTES_NONE; + } else if (property == CSS_PROP_VOICE_FAMILY) { + /** \todo Fix this when we have voice-family done */ + hint->data.strings = NULL; + hint->status = 0; + } else { + return CSS_INVALID; + } + + return CSS_OK; +} + +css_error set_libcss_node_data(void *pw, void *node, void *libcss_node_data) +{ + dom_node *n = node; + dom_exception err; + void *old_node_data; + + /* Set this node's node data */ + err = dom_node_set_user_data(n, + corestring_dom___ns_key_libcss_node_data, + libcss_node_data, nscss_dom_user_data_handler, + (void *) &old_node_data); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + assert(old_node_data == NULL); + + return CSS_OK; +} + +css_error get_libcss_node_data(void *pw, void *node, void **libcss_node_data) +{ + dom_node *n = node; + dom_exception err; + + /* Get this node's node data */ + err = dom_node_get_user_data(n, + corestring_dom___ns_key_libcss_node_data, + libcss_node_data); + if (err != DOM_NO_ERR) { + return CSS_NOMEM; + } + + return CSS_OK; +} diff --git a/content/handlers/css/select.h b/content/handlers/css/select.h new file mode 100644 index 000000000..abfb85814 --- /dev/null +++ b/content/handlers/css/select.h @@ -0,0 +1,58 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NETSURF_CSS_SELECT_H_ +#define NETSURF_CSS_SELECT_H_ + +#include + +#include + +#include + +struct content; +struct nsurl; + +/** + * Selection context + */ +typedef struct nscss_select_ctx +{ + css_select_ctx *ctx; + bool quirks; + struct nsurl *base_url; + lwc_string *universal; + const css_computed_style *parent_style; +} nscss_select_ctx; + +css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len, + const char *charset, const char *url, bool allow_quirks); + +css_select_results *nscss_get_style(nscss_select_ctx *ctx, dom_node *n, + uint64_t media, const css_stylesheet *inline_style); + +css_computed_style *nscss_get_blank_style(nscss_select_ctx *ctx, + const css_computed_style *parent); + + +css_error named_ancestor_node(void *pw, void *node, + const css_qname *qname, void **ancestor); + +css_error node_is_visited(void *pw, void *node, bool *match); + +#endif diff --git a/content/handlers/css/utils.c b/content/handlers/css/utils.c new file mode 100644 index 000000000..59ad41216 --- /dev/null +++ b/content/handlers/css/utils.c @@ -0,0 +1,127 @@ +/* + * Copyright 2004 James Bursa + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "utils/nsoption.h" +#include "utils/log.h" + +#include "utils.h" + +/** Screen DPI in fixed point units: defaults to 90, which RISC OS uses */ +css_fixed nscss_screen_dpi = F_90; + +/* exported interface documented in content/handlers/css/utils.h */ +css_fixed nscss_len2pt(css_fixed length, css_unit unit) +{ + /* Length must not be relative */ + assert(unit != CSS_UNIT_EM && unit != CSS_UNIT_EX); + + switch (unit) { + /* We assume the screen and any other output has the same dpi */ + /* 1in = DPIpx => 1px = (72/DPI)pt */ + case CSS_UNIT_PX: return FDIV(FMUL(length, F_72), nscss_screen_dpi); + /* 1in = 72pt */ + case CSS_UNIT_IN: return FMUL(length, F_72); + /* 1in = 2.54cm => 1cm = (72/2.54)pt */ + case CSS_UNIT_CM: return FMUL(length, + FDIV(F_72, FLTTOFIX(2.54))); + /* 1in = 25.4mm => 1mm = (72/25.4)pt */ + case CSS_UNIT_MM: return FMUL(length, + FDIV(F_72, FLTTOFIX(25.4))); + case CSS_UNIT_PT: return length; + /* 1pc = 12pt */ + case CSS_UNIT_PC: return FMUL(length, INTTOFIX(12)); + default: break; + } + + return 0; +} + + +/* exported interface documented in content/handlers/css/utils.h */ +css_fixed nscss_len2px(css_fixed length, css_unit unit, + const css_computed_style *style) +{ + /* We assume the screen and any other output has the same dpi */ + css_fixed px_per_unit; + + assert(style != NULL || (unit != CSS_UNIT_EM && unit != CSS_UNIT_EX)); + + switch (unit) { + case CSS_UNIT_EM: + case CSS_UNIT_EX: + { + css_fixed font_size = 0; + css_unit font_unit = CSS_UNIT_PT; + + css_computed_font_size(style, &font_size, &font_unit); + + /* Convert to points */ + font_size = nscss_len2pt(font_size, font_unit); + + /* Clamp to configured minimum */ + if (font_size < FDIV(INTTOFIX(nsoption_int(font_min_size)), F_10)) { + font_size = FDIV(INTTOFIX(nsoption_int(font_min_size)), F_10); + } + + /* Convert to pixels (manually, to maximise precision) + * 1in = 72pt => 1pt = (DPI/72)px */ + px_per_unit = FDIV(FMUL(font_size, nscss_screen_dpi), F_72); + + /* Scale ex units: we use a fixed ratio of 1ex = 0.6em */ + if (unit == CSS_UNIT_EX) + px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.6)); + } + break; + case CSS_UNIT_PX: + px_per_unit = F_1; + break; + /* 1in = DPIpx */ + case CSS_UNIT_IN: + px_per_unit = nscss_screen_dpi; + break; + /* 1in = 2.54cm => 1cm = (DPI/2.54)px */ + case CSS_UNIT_CM: + px_per_unit = FDIV(nscss_screen_dpi, FLTTOFIX(2.54)); + break; + /* 1in = 25.4mm => 1mm = (DPI/25.4)px */ + case CSS_UNIT_MM: + px_per_unit = FDIV(nscss_screen_dpi, FLTTOFIX(25.4)); + break; + /* 1in = 72pt => 1pt = (DPI/72)px */ + case CSS_UNIT_PT: + px_per_unit = FDIV(nscss_screen_dpi, F_72); + break; + /* 1pc = 12pt => 1in = 6pc => 1pc = (DPI/6)px */ + case CSS_UNIT_PC: + px_per_unit = FDIV(nscss_screen_dpi, INTTOFIX(6)); + break; + default: + px_per_unit = 0; + break; + } + + /* Ensure we round px_per_unit to the nearest whole number of pixels: + * the use of FIXTOINT() below will truncate. */ + px_per_unit += F_0_5; + + /* Calculate total number of pixels */ + return FMUL(length, TRUNCATEFIX(px_per_unit)); +} diff --git a/content/handlers/css/utils.h b/content/handlers/css/utils.h new file mode 100644 index 000000000..58a5ea6e6 --- /dev/null +++ b/content/handlers/css/utils.h @@ -0,0 +1,49 @@ +/* + * Copyright 2009 John-Mark Bell + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NETSURF_CSS_UTILS_H_ +#define NETSURF_CSS_UTILS_H_ + +#include + +#include "netsurf/css.h" + +/** DPI of the screen, in fixed point units */ +extern css_fixed nscss_screen_dpi; + +/** + * Convert an absolute CSS length to points. + * + * \param[in] length Absolute CSS length. + * \param[in] unit Unit of the length. + * \return length in points + */ +css_fixed nscss_len2pt(css_fixed length, css_unit unit); + +/** + * Convert a CSS length to pixels. + * + * \param length Length to convert + * \param unit Corresponding unit + * \param style Computed style applying to length. May be NULL if unit is + * neither em nor ex + * \return length in pixels + */ +css_fixed nscss_len2px(css_fixed length, css_unit unit, const css_computed_style *style); + +#endif -- cgit v1.2.3