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