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