/* * This file is part of LibCSS * Licensed under the MIT License, * http://www.opensource.org/licenses/mit-license.php * * Copyright 2018 Michael Drake */ #ifndef css_select_mq_h_ #define css_select_mq_h_ #include "select/properties/properties.h" #include "select/properties/helpers.h" static inline css_fixed css_len2px( css_fixed length, css_unit unit, const css_media *media) { css_fixed px_per_unit; switch (unit) { case CSS_UNIT_VI: /* TODO: Assumes writing mode. */ unit = CSS_UNIT_VW; break; case CSS_UNIT_VB: /* TODO: Assumes writing mode. */ unit = CSS_UNIT_VH; break; case CSS_UNIT_VMIN: unit = (media->height < media->width) ? CSS_UNIT_VH : CSS_UNIT_VW; break; case CSS_UNIT_VMAX: unit = (media->width > media->height) ? CSS_UNIT_VH : CSS_UNIT_VW; break; default: break; } switch (unit) { case CSS_UNIT_EM: case CSS_UNIT_EX: case CSS_UNIT_CAP: case CSS_UNIT_CH: case CSS_UNIT_IC: { px_per_unit = FDIV(FMUL(media->client_font_size, F_96), F_72); /* TODO: Handling these as fixed ratios of CSS_UNIT_EM. */ switch (unit) { case CSS_UNIT_EX: px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.6)); break; case CSS_UNIT_CAP: px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.9)); break; case CSS_UNIT_CH: px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.4)); break; case CSS_UNIT_IC: px_per_unit = FMUL(px_per_unit, FLTTOFIX(1.1)); break; default: break; } } break; case CSS_UNIT_PX: return length; case CSS_UNIT_IN: px_per_unit = F_96; break; case CSS_UNIT_CM: px_per_unit = FDIV(F_96, FLTTOFIX(2.54)); break; case CSS_UNIT_MM: px_per_unit = FDIV(F_96, FLTTOFIX(25.4)); break; case CSS_UNIT_Q: px_per_unit = FDIV(F_96, FLTTOFIX(101.6)); break; case CSS_UNIT_PT: px_per_unit = FDIV(F_96, F_72); break; case CSS_UNIT_PC: px_per_unit = FDIV(F_96, INTTOFIX(6)); break; case CSS_UNIT_REM: px_per_unit = FDIV(FMUL(media->client_font_size, F_96), F_72); break; case CSS_UNIT_RLH: px_per_unit = media->client_line_height; break; case CSS_UNIT_VH: px_per_unit = FDIV(media->height, F_100); break; case CSS_UNIT_VW: px_per_unit = FDIV(media->width, F_100); 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)); } static inline bool mq_match_feature_range_length_op1( css_mq_feature_op op, const css_mq_value *value, const css_fixed client_len, const css_media *media) { css_fixed v; if (value->type != CSS_MQ_VALUE_TYPE_DIM) { return false; } if (value->data.dim.unit != UNIT_PX) { v = css_len2px(value->data.dim.len, css__to_css_unit(value->data.dim.unit), media); } else { v = value->data.dim.len; } switch (op) { case CSS_MQ_FEATURE_OP_BOOL: return false; case CSS_MQ_FEATURE_OP_LT: return v < client_len; case CSS_MQ_FEATURE_OP_LTE: return v <= client_len; case CSS_MQ_FEATURE_OP_EQ: return v == client_len; case CSS_MQ_FEATURE_OP_GTE: return v >= client_len; case CSS_MQ_FEATURE_OP_GT: return v > client_len; default: return false; } } static inline bool mq_match_feature_range_length_op2( css_mq_feature_op op, const css_mq_value *value, const css_fixed client_len, const css_media *media) { css_fixed v; if (op == CSS_MQ_FEATURE_OP_UNUSED) { return true; } if (value->type != CSS_MQ_VALUE_TYPE_DIM) { return false; } if (value->data.dim.unit != UNIT_PX) { v = css_len2px(value->data.dim.len, css__to_css_unit(value->data.dim.unit), media); } else { v = value->data.dim.len; } switch (op) { case CSS_MQ_FEATURE_OP_LT: return client_len < v; case CSS_MQ_FEATURE_OP_LTE: return client_len <= v; case CSS_MQ_FEATURE_OP_EQ: return client_len == v; case CSS_MQ_FEATURE_OP_GTE: return client_len >= v; case CSS_MQ_FEATURE_OP_GT: return client_len > v; default: return false; } } /** * Match media query features. * * \param[in] feat Condition to match. * \param[in] media Current media spec, to check against feat. * \return true if condition matches, otherwise false. */ static inline bool mq_match_feature( const css_mq_feature *feat, const css_media *media) { /* TODO: Use interned string for comparison. */ if (strcmp(lwc_string_data(feat->name), "width") == 0) { if (!mq_match_feature_range_length_op1(feat->op, &feat->value, media->width, media)) { return false; } return mq_match_feature_range_length_op2(feat->op2, &feat->value2, media->width, media); } else if (strcmp(lwc_string_data(feat->name), "height") == 0) { if (!mq_match_feature_range_length_op1(feat->op, &feat->value, media->height, media)) { return false; } return mq_match_feature_range_length_op2(feat->op2, &feat->value2, media->height, media); } /* TODO: Look at other feature names. */ return false; } /** * Match media query conditions. * * \param[in] cond Condition to match. * \param[in] media Current media spec, to check against cond. * \return true if condition matches, otherwise false. */ static inline bool mq_match_condition( const css_mq_cond *cond, const css_media *media) { bool matched = !cond->op; for (uint32_t i = 0; i < cond->nparts; i++) { bool part_matched; if (cond->parts[i]->type == CSS_MQ_FEATURE) { part_matched = mq_match_feature( cond->parts[i]->data.feat, media); } else { assert(cond->parts[i]->type == CSS_MQ_COND); part_matched = mq_match_condition( cond->parts[i]->data.cond, media); } if (cond->op) { /* OR */ matched |= part_matched; if (matched) { break; /* Short-circuit */ } } else { /* AND */ matched &= part_matched; if (!matched) { break; /* Short-circuit */ } } } return matched != cond->negate; } /** * Test whether media query list matches current media. * * If anything in the list matches, the list matches. If none match * it doesn't match. * * \param[in] m Media query list. * \param[in] media Current media spec, to check against m. * \return true if media query list matches media */ static inline bool mq__list_match( const css_mq_query *m, const css_media *media) { for (; m != NULL; m = m->next) { /* Check type */ if (!!(m->type & media->type) != m->negate_type) { if (m->cond == NULL || mq_match_condition(m->cond, media)) { /* We have a match, no need to look further. */ return true; } } } return false; } /** * Test whether the rule applies for current media. * * \param rule Rule to test * \param media Current media spec * \return true iff chain's rule applies for media */ static inline bool mq_rule_good_for_media(const css_rule *rule, const css_media *media) { bool applies = true; const css_rule *ancestor = rule; while (ancestor != NULL) { const css_rule_media *m = (const css_rule_media *) ancestor; if (ancestor->type == CSS_RULE_MEDIA) { applies = mq__list_match(m->media, media); if (applies == false) { break; } } if (ancestor->ptype != CSS_RULE_PARENT_STYLESHEET) { ancestor = ancestor->parent; } else { ancestor = NULL; } } return applies; } #endif