• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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