1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "bridge/common/media_query/media_queryer.h"
17
18 #include "bridge/common/media_query/media_query_info.h"
19 #include "bridge/common/utils/utils.h"
20 #include "core/pipeline/pipeline_base.h"
21 #include "core/common/container.h"
22
23 namespace OHOS::Ace::Framework {
24 namespace {
25
26 constexpr double NOT_FOUND = -1.0;
27 enum class MediaError {
28 NONE,
29 SYNTAX,
30 };
31 using ConditionParser =
32 std::function<bool(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason)>;
33
34 class MediaQueryerRule {
35 public:
MediaQueryerRule(const std::regex & regex,const ConditionParser & parser,uint32_t matchResultSize)36 MediaQueryerRule(const std::regex& regex, const ConditionParser& parser, uint32_t matchResultSize)
37 : regex_(regex), parser_(parser), matchResultSize_(matchResultSize)
38 {}
MediaQueryerRule(const std::regex & regex)39 explicit MediaQueryerRule(const std::regex& regex) : regex_(regex) {}
40 ~MediaQueryerRule() = default;
41
ParseCondition(std::smatch & matchResults,const MediaFeature & mediaFeature,MediaError & failReason) const42 bool ParseCondition(std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) const
43 {
44 CHECK_NULL_RETURN(parser_, false);
45 if (matchResults.size() != matchResultSize_) {
46 failReason = MediaError::SYNTAX;
47 return false;
48 }
49 return parser_(matchResults, mediaFeature, failReason);
50 }
Match(const std::string & condition,std::smatch & matchResults) const51 bool Match(const std::string& condition, std::smatch& matchResults) const
52 {
53 return std::regex_match(condition, matchResults, regex_);
54 }
Match(const std::string & condition) const55 bool Match(const std::string& condition) const
56 {
57 return std::regex_match(condition, regex_);
58 }
59
60 private:
61 const std::regex regex_;
62 const ConditionParser parser_;
63 const uint32_t matchResultSize_ = 0;
64 };
65
66 namespace RelationShip {
67 const std::string GREAT_OR_EQUAL(">=");
68 const std::string GREAT_NOT_EQUAL = ">";
69 const std::string LESS_OR_EQUAL = "<=";
70 const std::string LESS_NOT_EQUAL = "<";
71 }; // namespace RelationShip
72
73 /**
74 * transfer unit the same with condition value unit
75 * @param value: device value should be transfer unit the same with condition value
76 * @param unit: condition value unit, such as: dpi/dpcm/dppx
77 */
TransferValue(double value,const std::string & unit)78 double TransferValue(double value, const std::string& unit)
79 {
80 double transfer = 1.0;
81 if (unit == "dpi") {
82 transfer = 96.0; // 1px = 96 dpi
83 } else if (unit == "vp") {
84 auto container = Container::Current();
85 if (container) {
86 auto pipeline = container->GetPipelineContext();
87 if (pipeline && !NearZero(pipeline->GetDipScale())) {
88 transfer = 1.0 / pipeline->GetDipScale();
89 }
90 }
91 } else if (unit == "dpcm") {
92 transfer = 36.0; // 1px = 36 dpcm
93 } else {
94 transfer = 1.0; // default same with device unit: px
95 }
96 return value * transfer;
97 }
98
CalculateExpression(double lvalue,const std::string & relationship,double rvalue,MediaError & failReason)99 bool CalculateExpression(double lvalue, const std::string& relationship, double rvalue, MediaError& failReason)
100 {
101 if (relationship == RelationShip::GREAT_OR_EQUAL) {
102 return GreatOrEqualCustomPrecision(lvalue, rvalue);
103 } else if (relationship == RelationShip::GREAT_NOT_EQUAL) {
104 return GreatNotEqualCustomPrecision(lvalue, rvalue);
105 } else if (relationship == RelationShip::LESS_OR_EQUAL) {
106 return LessOrEqualCustomPrecision(lvalue, rvalue);
107 } else if (relationship == RelationShip::LESS_NOT_EQUAL) {
108 return LessNotEqualCustomPrecision(lvalue, rvalue);
109 } else {
110 failReason = MediaError::SYNTAX;
111 }
112 return false;
113 }
114
115 const MediaQueryerRule CONDITION_WITH_SCREEN(
116 std::regex("(((only|not)screen)|screen)((and|or|,)\\([\\w\\.:><=-]+\\))*"));
117 const MediaQueryerRule CONDITION_WITHOUT_SCREEN(std::regex("\\([\\w\\.:><=-]+\\)((and|or|,)\\([\\w\\.:><=-]+\\))*"));
118 const MediaQueryerRule CONDITION_WITH_AND(std::regex("(\\([\\.a-z0-9:>=<-]+\\))(and\\([\\.a-z0-9:>=<-]+\\))+"));
119
120 // condition such as: (100 < width < 1000)
121 const MediaQueryerRule CSS_LEVEL4_MULTI(
122 std::regex(
123 "\\(([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?(>=|<=|>|<)([a-z0-9:-]+)(>|<|>=|<=)([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?\\)"),
__anon9df5b1ee0202(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 124 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
125 static constexpr int32_t LEFT_CONDITION_VALUE = 6;
126 static constexpr int32_t LEFT_UNIT = 7;
127 static constexpr int32_t LEFT_RELATIONSHIP = 5;
128 static constexpr int32_t MEDIA_FEATURE = 4;
129 static constexpr int32_t RIGHT_CONDITION_VALUE = 1;
130 static constexpr int32_t RIGHT_UNIT = 2;
131 static constexpr int32_t RIGHT_RELATIONSHIP = 3;
132
133 auto mediaFeatureValue = mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND);
134 return CalculateExpression(TransferValue(mediaFeatureValue, matchResults[LEFT_UNIT]),
135 matchResults[LEFT_RELATIONSHIP], StringToDouble(matchResults[LEFT_CONDITION_VALUE]), failReason) &&
136 CalculateExpression(StringToDouble(matchResults[RIGHT_CONDITION_VALUE]),
137 matchResults[RIGHT_RELATIONSHIP], TransferValue(mediaFeatureValue, matchResults[RIGHT_UNIT]),
138 failReason);
139 },
140 8);
141
142 // condition such as: width < 1000
143 const MediaQueryerRule CSS_LEVEL4_LEFT(
144 std::regex("\\(([^m][a-z-]+)(>=|<=|>|<)([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?\\)"),
__anon9df5b1ee0302(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 145 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
146 static constexpr int32_t CONDITION_VALUE = 3;
147 static constexpr int32_t UNIT = 4;
148 static constexpr int32_t RELATIONSHIP = 2;
149 static constexpr int32_t MEDIA_FEATURE = 1;
150
151 return CalculateExpression(
152 TransferValue(mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND), matchResults[UNIT]),
153 matchResults[RELATIONSHIP], StringToDouble(matchResults[CONDITION_VALUE]), failReason);
154 },
155 5);
156
157 // condition such as: 1000 < width
158 const MediaQueryerRule CSS_LEVEL4_RIGHT(
159 std::regex("\\(([\\d\\.]+)(dpi|dppx|dpcm|px|vp)?(>=|<=|>|<)([^m][a-z-]+)\\)"),
__anon9df5b1ee0402(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 160 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
161 static constexpr int32_t CONDITION_VALUE = 1;
162 static constexpr int32_t UNIT = 2;
163 static constexpr int32_t RELATIONSHIP = 3;
164 static constexpr int32_t MEDIA_FEATURE = 4;
165 return CalculateExpression(StringToDouble(matchResults[CONDITION_VALUE]), matchResults[RELATIONSHIP],
166 TransferValue(mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND), matchResults[UNIT]),
167 failReason);
168 },
169 5);
170
171 // condition such as: min-width: 1000
172 const MediaQueryerRule CSS_LEVEL3_RULE(
173 std::regex("\\((min|max)-([a-z-]+):([\\d\\.]+)(dpi|dppx|dpcm|vp)?\\)"),
__anon9df5b1ee0502(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 174 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
175 static constexpr int32_t RELATIONSHIP = 1;
176 static constexpr int32_t MEDIA_FEATURE = 2;
177 static constexpr int32_t CONDITION_VALUE = 3;
178 static constexpr int32_t UNIT = 4;
179 std::string relationship;
180 if (matchResults[RELATIONSHIP] == "max") {
181 relationship = RelationShip::LESS_OR_EQUAL;
182 } else if (matchResults[RELATIONSHIP] == "min") {
183 relationship = RelationShip::GREAT_OR_EQUAL;
184 } else {
185 return false;
186 }
187
188 return CalculateExpression(
189 TransferValue(mediaFeature->GetDouble(matchResults[MEDIA_FEATURE], NOT_FOUND), matchResults[UNIT]),
190 relationship, StringToDouble(matchResults[CONDITION_VALUE]), failReason);
191 },
192 5);
193
194 const MediaQueryerRule SCREEN_SHAPE_RULE(
195 std::regex("\\(round-screen:([a-z]+)\\)"),
__anon9df5b1ee0602(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 196 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
197 static constexpr int32_t CONDITION_VALUE = 1;
198 return StringToBool(matchResults[CONDITION_VALUE]) == mediaFeature->GetBool("round-screen", false);
199 },
200 2);
201
202 const MediaQueryerRule ORIENTATION_RULE(
203 std::regex("\\(orientation:([a-z]+)\\)"),
__anon9df5b1ee0702(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 204 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
205 static constexpr int32_t CONDITION_VALUE = 1;
206 return matchResults[CONDITION_VALUE] == mediaFeature->GetString("orientation", "");
207 },
208 2);
209
210 const MediaQueryerRule DEVICE_TYPE_RULE(
211 std::regex("\\(device-type:([a-z]+)\\)"),
__anon9df5b1ee0802(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 212 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
213 static constexpr int32_t CONDITION_VALUE = 1;
214 auto matchDeviceType = mediaFeature->GetString("device-type", "");
215 if (matchResults[CONDITION_VALUE] == "default") {
216 return matchDeviceType == "phone";
217 } else {
218 return matchResults[CONDITION_VALUE] == matchDeviceType;
219 }
220 },
221 2);
222
223 const MediaQueryerRule DEVICE_BRAND_RULE(
224 std::regex("\\(device-brand:([A-Z]+)\\)"),
__anon9df5b1ee0902(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 225 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
226 static constexpr int32_t CONDITION_VALUE = 1;
227 auto value = matchResults[CONDITION_VALUE] == mediaFeature->GetString("device-brand", "");
228 return value;
229 },
230 2);
231
232 const MediaQueryerRule DARK_MODE_RULE(
233 std::regex("\\(dark-mode:([a-z]+)\\)"),
__anon9df5b1ee0a02(const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) 234 [](const std::smatch& matchResults, const MediaFeature& mediaFeature, MediaError& failReason) {
235 static constexpr int32_t CONDITION_VALUE = 1;
236 return StringToBool(matchResults[CONDITION_VALUE]) == mediaFeature->GetBool("dark-mode", false);
237 },
238 2);
239
240 const std::list<MediaQueryerRule> SINGLE_CONDITION_RULES = {
241 CSS_LEVEL4_MULTI,
242 CSS_LEVEL4_LEFT,
243 CSS_LEVEL4_RIGHT,
244 CSS_LEVEL3_RULE,
245 ORIENTATION_RULE,
246 DEVICE_TYPE_RULE,
247 DEVICE_BRAND_RULE,
248 SCREEN_SHAPE_RULE,
249 DARK_MODE_RULE,
250
251 };
252
ParseSingleCondition(const std::string & condition,const MediaFeature & mediaFeature,MediaError & failReason)253 bool ParseSingleCondition(const std::string& condition, const MediaFeature& mediaFeature, MediaError& failReason)
254 {
255 for (const auto& rule : SINGLE_CONDITION_RULES) {
256 std::smatch matchResults;
257 if (rule.Match(condition, matchResults)) {
258 return rule.ParseCondition(matchResults, mediaFeature, failReason);
259 }
260 }
261 failReason = MediaError::SYNTAX;
262 return false;
263 }
264
ParseAndCondition(const std::string & condition,const MediaFeature & mediaFeature,MediaError & failReason)265 bool ParseAndCondition(const std::string& condition, const MediaFeature& mediaFeature, MediaError& failReason)
266 {
267 auto noAnd = std::regex_replace(condition, std::regex("and[^a-z]"), ",(");
268 std::vector<std::string> conditionArr;
269 StringUtils::SplitStr(noAnd, ",", conditionArr);
270 if (conditionArr.empty()) {
271 failReason = MediaError::SYNTAX;
272 return false;
273 }
274
275 for (const auto& item : conditionArr) {
276 if (!ParseSingleCondition(item, mediaFeature, failReason)) {
277 return false;
278 }
279 }
280 return true;
281 }
282
DoMatchCondition(const std::string & condition,const MediaFeature & mediaFeature)283 bool DoMatchCondition(const std::string& condition, const MediaFeature& mediaFeature)
284 {
285 // remove space from condition string
286 std::string noSpace = std::regex_replace(condition, std::regex("\\s"), "");
287 bool inverse = false;
288 std::string noScreen;
289 if (CONDITION_WITH_SCREEN.Match(noSpace)) {
290 if (noSpace.find("notscreen") != std::string::npos) {
291 inverse = true;
292 }
293 MediaQueryerRule screenPatten(std::regex("screen[^and:]"));
294 if (screenPatten.Match(noSpace)) {
295 return !inverse;
296 }
297 noScreen = std::regex_replace(noSpace, std::regex("^(only|not)?screen(and)?"), "");
298 } else if (CONDITION_WITHOUT_SCREEN.Match(noSpace)) {
299 noScreen = noSpace;
300 } else {
301 return false;
302 }
303 MediaError failReason = MediaError::NONE;
304 // replace 'or' with comma ','
305 auto commaCondition = std::regex_replace(noScreen, std::regex("or[(]"), ",(");
306 // remove screen and modifier
307 std::vector<std::string> conditionArr;
308 StringUtils::SplitStr(commaCondition, ",", conditionArr);
309 int32_t len = static_cast<int32_t>(conditionArr.size());
310 for (int32_t i = 0; i < len; i++) {
311 if (CONDITION_WITH_AND.Match(conditionArr[i])) {
312 bool result = ParseAndCondition(conditionArr[i], mediaFeature, failReason);
313 if (failReason == MediaError::SYNTAX) {
314 return false;
315 }
316 if (i + 1 == len) {
317 return (inverse && !result) || (!inverse && result);
318 }
319 } else {
320 if (ParseSingleCondition(conditionArr[i], mediaFeature, failReason)) {
321 return !inverse;
322 }
323 if (failReason == MediaError::SYNTAX) {
324 return false;
325 }
326 }
327 }
328 return inverse;
329 }
330
331 } // namespace
332
MatchCondition(const std::string & condition,const MediaFeature & mediaFeature)333 bool MediaQueryer::MatchCondition(const std::string& condition, const MediaFeature& mediaFeature)
334 {
335 if (condition.empty()) {
336 return false;
337 }
338
339 // If width and height are not initialized, and the query condition includes "width" or "height",
340 // return false directly.
341 if (mediaFeature->GetInt("width", 0) == 0 &&
342 (condition.find("width") != std::string::npos || condition.find("height") != std::string::npos)) {
343 return false;
344 }
345
346 auto iter = queryHistories.find(condition);
347 if (iter != queryHistories.end()) {
348 auto queryHistory = iter->second;
349 if (queryHistory.mediaFeatureValue == mediaFeature->ToString()) {
350 return queryHistory.result;
351 }
352 }
353
354 auto result = DoMatchCondition(condition, mediaFeature);
355 queryHistories[condition] = { mediaFeature->ToString(), result };
356 return result;
357 }
358
359 /* card info */
GetMediaFeature() const360 std::unique_ptr<JsonValue> MediaQueryer::GetMediaFeature() const
361 {
362 auto json = MediaQueryInfo::GetMediaQueryJsonInfo();
363
364 /* cover the following aspects with card specified values */
365 double aspectRatio = (height_ != 0) ? (static_cast<double>(width_) / height_) : 1.0;
366 json->Replace("width", width_);
367 json->Replace("height", height_);
368 json->Replace("aspect-ratio", aspectRatio);
369 json->Replace("dark-mode", colorMode_ == ColorMode::DARK);
370 json->Put("device-brand", SystemProperties::GetBrand().c_str());
371 return json;
372 }
373
374 } // namespace OHOS::Ace::Framework
375