1 package org.daisy.pipeline.css;
2
3 import java.util.Arrays;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.HashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10
11 import com.google.common.collect.Lists;
12
13 import cz.vutbr.web.css.CSSFactory;
14 import cz.vutbr.web.css.MediaExpression;
15 import cz.vutbr.web.css.MediaSpec;
16 import cz.vutbr.web.css.MediaQuery;
17 import cz.vutbr.web.css.Term;
18 import cz.vutbr.web.css.TermIdent;
19 import cz.vutbr.web.css.TermInteger;
20 import cz.vutbr.web.csskit.antlr.CSSParserFactory;
21 import cz.vutbr.web.domassign.Analyzer;
22
23 public class Medium {
24
25 public enum Type {
26 EMBOSSED,
27 BRAILLE,
28 SPEECH,
29 SCREEN,
30 PRINT;
31 public String toString() {
32 return super.toString().toLowerCase();
33 }
34 }
35
36 private final Type type;
37 private final Integer width;
38 private final Integer height;
39 private final Map<String,String> customFeatures;
40
41
42 private final static Set<String> knownFeatures = new HashSet<>(
43 Arrays.asList(
44 "any-hover", "any-pointer", "aspect-ratio", "color", "color-gamut", "color-index",
45 "device-aspect-ratio", "device-height", "device-width", "grid", "height", "hover",
46 "monochrome", "orientation", "overflow-block", "overflow-inline", "pointer", "resolution",
47 "scan", "update", "width"));
48
49
50
51
52
53
54
55 private Medium(Type type, Integer width, Integer height, Map<String,String> customFeatures) {
56 switch (type) {
57 case EMBOSSED:
58 this.type = type;
59 this.width = width;
60 this.height = height;
61 break;
62 case BRAILLE:
63 case SPEECH:
64 case SCREEN:
65 case PRINT:
66 if (width != null) {
67 throw new IllegalArgumentException("Unexpected 'width' argument for medium '" + type + "'");
68 }
69 if (height != null) {
70 throw new IllegalArgumentException("Unexpected 'height' argument for medium '" + type + "'");
71 }
72 this.type = type;
73 this.width = this.height = null;
74 break;
75 default:
76 throw new IllegalArgumentException("Unexpected medium: " + type);
77 }
78 this.customFeatures = customFeatures != null
79 ? Collections.unmodifiableMap(customFeatures)
80 : Collections.emptyMap();
81 }
82
83
84
85
86 public Type getType() {
87 return type;
88 }
89
90
91
92
93
94 public Integer getWidth() {
95 return width;
96 }
97
98
99
100
101
102 public Integer getHeight() {
103 return height;
104 }
105
106
107
108
109 public Map<String,String> getCustomFeatures() {
110 return customFeatures;
111 }
112
113 public boolean matches(String mediaQuery) {
114 if (mediaQuery != null)
115 return asMediaSpec().matches(CSSParserFactory.getInstance().parseMediaQuery(mediaQuery.trim()));
116 else
117 return asMediaSpec().matchesEmpty();
118 }
119
120 public String toString() {
121 StringBuilder s = new StringBuilder();
122 s.append(type.toString());
123 if (width != null)
124 s.append(" AND (width: ").append(width).append(")");
125 if (height != null)
126 s.append(" AND (height: ").append(height).append(")");
127 for (String f : customFeatures.keySet())
128 s.append(" AND (").append(f).append(": ").append(customFeatures.get(f)).append(")");
129 return s.toString();
130 }
131
132 public boolean equals(Object o) {
133 if (!(o instanceof Medium))
134 return false;
135 Medium that = (Medium)o;
136 return this.type == that.type
137 && this.width == that.width
138 && this.height == that.height;
139 }
140
141 public int hashCode() {
142 final int prime = 31;
143 int result = 1;
144 result = prime * result + type.hashCode();
145 result = prime * result + width;
146 result = prime * result + height;
147 return result;
148 }
149
150 public static Medium parse(String medium) throws IllegalArgumentException {
151 List<MediaQuery> query = CSSParserFactory.getInstance().parseMediaQuery(medium);
152 if (query.size() != 1)
153 throw new IllegalArgumentException("Unexpected medium: " + medium);
154 return fromMediaQuery(query.get(0));
155 }
156
157
158
159
160 public static List<Medium> parseMultiple(String media) throws IllegalArgumentException {
161 return Lists.transform(
162 CSSParserFactory.getInstance().parseMediaQuery(media),
163 Medium::fromMediaQuery);
164 }
165
166 private static Medium fromMediaQuery(MediaQuery query) throws IllegalArgumentException {
167 Type type = null;
168 try {
169 type = Type.valueOf(query.getType().toUpperCase());
170 } catch (IllegalArgumentException e) {
171 throw new IllegalArgumentException("Unexpected medium type: " + query.getType());
172 }
173 if (query.isNegative())
174 throw new IllegalArgumentException("Unexpected medium: contains NOT: " + query);
175 Integer width = null;
176 Integer height = null;
177 Map<String,String> customFeatures = null;
178 for (MediaExpression e : query) {
179 String feature = e.getFeature();
180 if ("width".equals(feature) || "height".equals(feature)) {
181 if (e.size() != 1)
182 throw new IllegalArgumentException("Unexpected value for medium feature: " + e);
183 Term<?> v = e.get(0);
184 if (!(v instanceof TermInteger))
185 throw new IllegalArgumentException("Unexpected value for medium feature: " + e);
186 Integer i = ((TermInteger)v).getIntValue();
187 if ("width".equals(feature))
188 width = i;
189 else
190 height = i;
191 } else if (knownFeatures.contains(feature)) {
192 throw new IllegalArgumentException("Unsupported medium feature: " + feature);
193 } else {
194 if (e.size() != 1)
195 throw new IllegalArgumentException("Unexpected value for medium feature: " + e);
196 Term<?> v = e.get(0);
197 if (!(v instanceof TermIdent || v instanceof TermInteger))
198 throw new IllegalArgumentException("Unexpected value for medium feature: " + e);
199 if (customFeatures == null)
200 customFeatures = new HashMap<>();
201 customFeatures.put(feature, v.toString());
202 }
203 }
204 return new Medium(type, width, height, customFeatures);
205 }
206
207 private MediaSpec mediaSpec;
208
209
210
211
212
213
214 public MediaSpec asMediaSpec() {
215 if (mediaSpec == null)
216 switch (type) {
217 case EMBOSSED:
218 mediaSpec = new MediaSpec(type.toString()) {
219
220
221 @Override
222 protected Float getExpressionLengthPx(MediaExpression e) {
223 return (float)getExpressionInteger(e);
224 }
225 @Override
226 public boolean matches(MediaExpression e) {
227 String f = e.getFeature();
228 if (knownFeatures.contains(f) || knownFeatures.contains(f.replaceAll("^(min|max)-", "")))
229 return super.matches(e);
230 else {
231 if (e.size() != 1)
232 return false;
233 Term<?> v = e.get(0);
234 if (!(v instanceof TermIdent || v instanceof TermInteger))
235 return false;
236 if (!customFeatures.isEmpty())
237 for (String ff : customFeatures.keySet())
238 if (ff.equals(f))
239 return customFeatures.get(ff).equals(v.toString());
240 return false;
241 }
242 }
243 };
244 if (width != null)
245 mediaSpec.setWidth((float)width);
246 if (height != null)
247 mediaSpec.setWidth((float)height);
248 break;
249 default:
250 mediaSpec = new MediaSpec(type.toString()) {
251 @Override
252 public boolean matches(MediaExpression e) {
253 String f = e.getFeature();
254 if (knownFeatures.contains(f) || knownFeatures.contains(f.replaceAll("^(min|max)-", "")))
255 return super.matches(e);
256 else {
257 if (e.size() != 1)
258 return false;
259 Term<?> v = e.get(0);
260 if (!(v instanceof TermIdent || v instanceof TermInteger))
261 return false;
262 if (!customFeatures.isEmpty())
263 for (String ff : customFeatures.keySet())
264 if (ff.equals(f))
265 return customFeatures.get(ff).equals(v.toString());
266 return false;
267 }
268 }
269 };
270 break;
271 }
272 return mediaSpec;
273 }
274 }