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