1 /*
2 * This file is part of LibCSS
3 * Licensed under the MIT License,
4 * http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright 2018 Michael Drake <tlsa@netsurf-browser.org>
7 */
8
9 #ifndef css_select_mq_h_
10 #define css_select_mq_h_
11
css_len2px(css_fixed length,css_unit unit,const css_media * media)12 static inline css_fixed css_len2px(
13 css_fixed length,
14 css_unit unit,
15 const css_media *media)
16 {
17 css_fixed px_per_unit;
18
19 switch (unit) {
20 case CSS_UNIT_VI:
21 /* TODO: Assumes writing mode. */
22 unit = CSS_UNIT_VW;
23 break;
24 case CSS_UNIT_VB:
25 /* TODO: Assumes writing mode. */
26 unit = CSS_UNIT_VH;
27 break;
28 case CSS_UNIT_VMIN:
29 unit = (media->height < media->width) ?
30 CSS_UNIT_VH : CSS_UNIT_VW;
31 break;
32 case CSS_UNIT_VMAX:
33 unit = (media->width > media->height) ?
34 CSS_UNIT_VH : CSS_UNIT_VW;
35 break;
36 default:
37 break;
38 }
39
40 switch (unit) {
41 case CSS_UNIT_EM:
42 case CSS_UNIT_EX:
43 case CSS_UNIT_CAP:
44 case CSS_UNIT_CH:
45 case CSS_UNIT_IC:
46 {
47 px_per_unit = FDIV(FMUL(media->client_font_size, F_96), F_72);
48
49 /* TODO: Handling these as fixed ratios of CSS_UNIT_EM. */
50 switch (unit) {
51 case CSS_UNIT_EX:
52 px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.6));
53 break;
54 case CSS_UNIT_CAP:
55 px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.9));
56 break;
57 case CSS_UNIT_CH:
58 px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.4));
59 break;
60 case CSS_UNIT_IC:
61 px_per_unit = FMUL(px_per_unit, FLTTOFIX(1.1));
62 break;
63 default:
64 break;
65 }
66 }
67 break;
68 case CSS_UNIT_PX:
69 return length;
70 case CSS_UNIT_IN:
71 px_per_unit = F_96;
72 break;
73 case CSS_UNIT_CM:
74 px_per_unit = FDIV(F_96, FLTTOFIX(2.54));
75 break;
76 case CSS_UNIT_MM:
77 px_per_unit = FDIV(F_96, FLTTOFIX(25.4));
78 break;
79 case CSS_UNIT_Q:
80 px_per_unit = FDIV(F_96, FLTTOFIX(101.6));
81 break;
82 case CSS_UNIT_PT:
83 px_per_unit = FDIV(F_96, F_72);
84 break;
85 case CSS_UNIT_PC:
86 px_per_unit = FDIV(F_96, INTTOFIX(6));
87 break;
88 case CSS_UNIT_REM:
89 px_per_unit = FDIV(FMUL(media->client_font_size, F_96), F_72);
90 break;
91 case CSS_UNIT_RLH:
92 px_per_unit = media->client_line_height;
93 break;
94 case CSS_UNIT_VH:
95 px_per_unit = FDIV(media->height, F_100);
96 break;
97 case CSS_UNIT_VW:
98 px_per_unit = FDIV(media->width, F_100);
99 break;
100 default:
101 px_per_unit = 0;
102 break;
103 }
104
105 /* Ensure we round px_per_unit to the nearest whole number of pixels:
106 * the use of FIXTOINT() below will truncate. */
107 px_per_unit += F_0_5;
108
109 /* Calculate total number of pixels */
110 return FMUL(length, TRUNCATEFIX(px_per_unit));
111 }
112
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)113 static inline bool mq_match_feature_range_length_op1(
114 css_mq_feature_op op,
115 const css_mq_value *value,
116 const css_fixed client_len,
117 const css_media *media)
118 {
119 css_fixed v;
120
121 if (value->type != CSS_MQ_VALUE_TYPE_DIM) {
122 return false;
123 }
124
125 if (value->data.dim.unit != CSS_UNIT_PX) {
126 v = css_len2px(value->data.dim.len,
127 value->data.dim.unit, media);
128 } else {
129 v = value->data.dim.len;
130 }
131
132 switch (op) {
133 case CSS_MQ_FEATURE_OP_BOOL: return false;
134 case CSS_MQ_FEATURE_OP_LT: return v < client_len;
135 case CSS_MQ_FEATURE_OP_LTE: return v <= client_len;
136 case CSS_MQ_FEATURE_OP_EQ: return v == client_len;
137 case CSS_MQ_FEATURE_OP_GTE: return v >= client_len;
138 case CSS_MQ_FEATURE_OP_GT: return v > client_len;
139 default:
140 return false;
141 }
142 }
143
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)144 static inline bool mq_match_feature_range_length_op2(
145 css_mq_feature_op op,
146 const css_mq_value *value,
147 const css_fixed client_len,
148 const css_media *media)
149 {
150 css_fixed v;
151
152 if (op == CSS_MQ_FEATURE_OP_UNUSED) {
153 return true;
154 }
155 if (value->type != CSS_MQ_VALUE_TYPE_DIM) {
156 return false;
157 }
158
159 if (value->data.dim.unit != CSS_UNIT_PX) {
160 v = css_len2px(value->data.dim.len,
161 value->data.dim.unit, media);
162 } else {
163 v = value->data.dim.len;
164 }
165
166 switch (op) {
167 case CSS_MQ_FEATURE_OP_LT: return client_len < v;
168 case CSS_MQ_FEATURE_OP_LTE: return client_len <= v;
169 case CSS_MQ_FEATURE_OP_EQ: return client_len == v;
170 case CSS_MQ_FEATURE_OP_GTE: return client_len >= v;
171 case CSS_MQ_FEATURE_OP_GT: return client_len > v;
172 default:
173 return false;
174 }
175 }
176
177 /**
178 * Match media query features.
179 *
180 * \param[in] feat Condition to match.
181 * \param[in] media Current media spec, to check against feat.
182 * \return true if condition matches, otherwise false.
183 */
mq_match_feature(const css_mq_feature * feat,const css_media * media)184 static inline bool mq_match_feature(
185 const css_mq_feature *feat,
186 const css_media *media)
187 {
188 /* TODO: Use interned string for comparison. */
189 if (strcmp(lwc_string_data(feat->name), "width") == 0) {
190 if (!mq_match_feature_range_length_op1(feat->op, &feat->value,
191 media->width, media)) {
192 return false;
193 }
194 return mq_match_feature_range_length_op2(feat->op2,
195 &feat->value2, media->width, media);
196
197 } else if (strcmp(lwc_string_data(feat->name), "height") == 0) {
198 if (!mq_match_feature_range_length_op1(feat->op, &feat->value,
199 media->height, media)) {
200 return false;
201 }
202
203 return mq_match_feature_range_length_op2(feat->op2,
204 &feat->value2, media->height, media);
205 }
206
207 /* TODO: Look at other feature names. */
208
209 return false;
210 }
211
212 /**
213 * Match media query conditions.
214 *
215 * \param[in] cond Condition to match.
216 * \param[in] media Current media spec, to check against cond.
217 * \return true if condition matches, otherwise false.
218 */
mq_match_condition(const css_mq_cond * cond,const css_media * media)219 static inline bool mq_match_condition(
220 const css_mq_cond *cond,
221 const css_media *media)
222 {
223 bool matched = !cond->op;
224
225 for (uint32_t i = 0; i < cond->nparts; i++) {
226 bool part_matched;
227 if (cond->parts[i]->type == CSS_MQ_FEATURE) {
228 part_matched = mq_match_feature(
229 cond->parts[i]->data.feat, media);
230 } else {
231 assert(cond->parts[i]->type == CSS_MQ_COND);
232 part_matched = mq_match_condition(
233 cond->parts[i]->data.cond, media);
234 }
235
236 if (cond->op) {
237 /* OR */
238 matched |= part_matched;
239 if (matched) {
240 break; /* Short-circuit */
241 }
242 } else {
243 /* AND */
244 matched &= part_matched;
245 if (!matched) {
246 break; /* Short-circuit */
247 }
248 }
249 }
250
251 return matched != cond->negate;
252 }
253
254 /**
255 * Test whether media query list matches current media.
256 *
257 * If anything in the list matches, the list matches. If none match
258 * it doesn't match.
259 *
260 * \param[in] m Media query list.
261 * \param[in] media Current media spec, to check against m.
262 * \return true if media query list matches media
263 */
mq__list_match(const css_mq_query * m,const css_media * media)264 static inline bool mq__list_match(
265 const css_mq_query *m,
266 const css_media *media)
267 {
268 for (; m != NULL; m = m->next) {
269 /* Check type */
270 if (!!(m->type & media->type) != m->negate_type) {
271 if (m->cond == NULL ||
272 mq_match_condition(m->cond, media)) {
273 /* We have a match, no need to look further. */
274 return true;
275 }
276 }
277 }
278
279 return false;
280 }
281
282 /**
283 * Test whether the rule applies for current media.
284 *
285 * \param rule Rule to test
286 * \param media Current media spec
287 * \return true iff chain's rule applies for media
288 */
mq_rule_good_for_media(const css_rule * rule,const css_media * media)289 static inline bool mq_rule_good_for_media(const css_rule *rule, const css_media *media)
290 {
291 bool applies = true;
292 const css_rule *ancestor = rule;
293
294 while (ancestor != NULL) {
295 const css_rule_media *m = (const css_rule_media *) ancestor;
296
297 if (ancestor->type == CSS_RULE_MEDIA) {
298 applies = mq__list_match(m->media, media);
299 if (applies == false) {
300 break;
301 }
302 }
303
304 if (ancestor->ptype != CSS_RULE_PARENT_STYLESHEET) {
305 ancestor = ancestor->parent;
306 } else {
307 ancestor = NULL;
308 }
309 }
310
311 return applies;
312 }
313
314 #endif
315