• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 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 "html_to_span.h"
17 
18 #include "core/text/html_utils.h"
19 
20 namespace OHOS::Ace {
21 constexpr int ONE_PARAM = 1;
22 constexpr int TWO_PARAM = 2;
23 constexpr int THREE_PARAM = 3;
24 constexpr int FOUR_PARAM = 4;
25 
26 constexpr int TOP_PARAM = 0;
27 constexpr int RIGHT_PARAM = 1;
28 constexpr int BOTTOM_PARAM = 2;
29 constexpr int LEFT_PARAM = 3;
30 constexpr int FIRST_PARAM = 0;
31 constexpr int SECOND_PARAM = 1;
32 constexpr int THIRD_PARAM = 2;
33 constexpr int FOURTH_PARAM = 3;
34 
35 constexpr int MAX_STYLE_FORMAT_NUMBER = 3;
36 
ToLowerCase(std::string & str)37 void ToLowerCase(std::string& str)
38 {
39     for (auto& c : str) {
40         c = tolower(c);
41     }
42 }
43 
ParseFontFamily(const std::string & fontFamily)44 std::vector<std::string> ParseFontFamily(const std::string& fontFamily)
45 {
46     std::vector<std::string> fonts;
47     std::stringstream ss(fontFamily);
48     std::string token;
49     while (std::getline(ss, token, ',')) {
50         std::string font = std::string(token.begin(), token.end());
51         font.erase(std::remove_if(font.begin(), font.end(), isspace), font.end());
52 
53         if (!font.empty()) {
54             fonts.push_back(font);
55         }
56     }
57 
58     return fonts;
59 }
60 
StringToTextVerticalAlign(const std::string & align)61 VerticalAlign StringToTextVerticalAlign(const std::string& align)
62 {
63     if (align == "bottom") {
64         return VerticalAlign::BOTTOM;
65     }
66     if (align == "middle") {
67         return VerticalAlign::CENTER;
68     }
69     if (align == "top") {
70         return VerticalAlign::TOP;
71     }
72     return VerticalAlign::NONE;
73 }
74 
StringToFontStyle(const std::string & fontStyle)75 FontStyle StringToFontStyle(const std::string& fontStyle)
76 {
77     return fontStyle == "italic" ? FontStyle::ITALIC : FontStyle::NORMAL;
78 }
79 
StringToTextDecorationStyle(const std::string & textDecorationStyle)80 TextDecorationStyle StringToTextDecorationStyle(const std::string& textDecorationStyle)
81 {
82     std::string value = StringUtils::TrimStr(textDecorationStyle);
83     if (value == "dashed") {
84         return TextDecorationStyle::DASHED;
85     }
86     if (value == "dotted") {
87         return TextDecorationStyle::DOTTED;
88     }
89     if (value == "double") {
90         return TextDecorationStyle::DOUBLE;
91     }
92     if (value == "solid") {
93         return TextDecorationStyle::SOLID;
94     }
95     if (value == "wavy") {
96         return TextDecorationStyle::WAVY;
97     }
98 
99     return TextDecorationStyle::SOLID;
100 }
101 
StringToTextDecoration(const std::string & textDecoration)102 TextDecoration StringToTextDecoration(const std::string& textDecoration)
103 {
104     std::string value = StringUtils::TrimStr(textDecoration);
105     if (value == "inherit") {
106         return TextDecoration::INHERIT;
107     }
108     if (value == "line-through") {
109         return TextDecoration::LINE_THROUGH;
110     }
111     if (value == "none") {
112         return TextDecoration::NONE;
113     }
114     if (value == "overline") {
115         return TextDecoration::OVERLINE;
116     }
117     if (value == "underline") {
118         return TextDecoration::UNDERLINE;
119     }
120     return TextDecoration::NONE;
121 }
122 
ConvertStrToFit(const std::string & fit)123 ImageFit ConvertStrToFit(const std::string& fit)
124 {
125     if (fit == "fill") {
126         return ImageFit::FILL;
127     }
128     if (fit == "contain") {
129         return ImageFit::CONTAIN;
130     }
131     if (fit == "cover") {
132         return ImageFit::COVER;
133     }
134     if (fit == "scaledown") {
135         return ImageFit::SCALE_DOWN;
136     }
137     if (fit == "none") {
138         return ImageFit::NONE;
139     }
140 
141     return ImageFit::CONTAIN;
142 }
143 
ParseStyleAttr(const std::string & style)144 HtmlToSpan::Styles HtmlToSpan::ParseStyleAttr(const std::string& style)
145 {
146     Styles styles;
147     if (style.find(':') == std::string::npos) {
148         return styles;
149     }
150     std::regex pattern(R"(\s*([^:]+):([^;]+);?)");
151     std::smatch match;
152     std::string::const_iterator searchStart(style.begin());
153 
154     while (std::regex_search(searchStart, style.end(), match, pattern)) {
155         if (match.size() < MAX_STYLE_FORMAT_NUMBER) {
156             continue;
157         }
158         std::string key = std::regex_replace(match[1].str(), std::regex(R"(\s+)"), "");
159         std::string value = std::regex_replace(match[2].str(), std::regex(R"(\s+)"), " ");
160         ToLowerCase(key);
161         styles.emplace_back(key, value);
162         searchStart = match[0].second;
163     }
164 
165     return styles;
166 }
167 
168 template<class T>
Get(StyleValue * styleValue) const169 T* HtmlToSpan::Get(StyleValue* styleValue) const
170 {
171     auto v = std::get_if<T>(styleValue);
172     if (v == nullptr) {
173         return nullptr;
174     }
175     return static_cast<T*>(v);
176 }
177 
178 // for example str = 0.00px
FromString(const std::string & str)179 Dimension HtmlToSpan::FromString(const std::string& str)
180 {
181     static const int32_t PERCENT_UNIT = 100;
182     static const std::unordered_map<std::string, DimensionUnit> uMap {
183         { "px", DimensionUnit::PX },
184         { "vp", DimensionUnit::VP },
185         { "fp", DimensionUnit::FP },
186         { "%", DimensionUnit::PERCENT },
187         { "lpx", DimensionUnit::LPX },
188         { "auto", DimensionUnit::AUTO },
189         { "rem", DimensionUnit::INVALID },
190         { "em", DimensionUnit::INVALID },
191     };
192 
193     double value = 0.0;
194     DimensionUnit unit = DimensionUnit::VP;
195     if (str.empty()) {
196         LOGE("UITree |ERROR| empty string");
197         return Dimension(NG::TEXT_DEFAULT_FONT_SIZE);
198     }
199 
200     for (int32_t i = static_cast<int32_t>(str.length()) - 1; i >= 0; --i) {
201         if (str[i] >= '0' && str[i] <= '9') {
202             auto startIndex = i + 1;
203             value = StringUtils::StringToDouble(str.substr(0, startIndex));
204             startIndex = std::clamp(startIndex, 0, static_cast<int32_t>(str.length()));
205             auto subStr = str.substr(startIndex);
206             if (subStr == "pt") {
207                 value = static_cast<int>(value * PT_TO_PX + ROUND_TO_INT);
208                 break;
209             }
210             auto iter = uMap.find(subStr);
211             if (iter != uMap.end()) {
212                 unit = iter->second;
213             }
214             value = unit == DimensionUnit::PERCENT ? value / PERCENT_UNIT : value;
215             break;
216         }
217     }
218     if (unit == DimensionUnit::PX) {
219         return Dimension(value, DimensionUnit::VP);
220     } else if (unit == DimensionUnit::INVALID) {
221         return Dimension(NG::TEXT_DEFAULT_FONT_SIZE);
222     }
223 
224     return Dimension(value, unit);
225 }
226 
InitFont(const std::string & key,const std::string & value,const std::string & index,StyleValues & values)227 void HtmlToSpan::InitFont(
228     const std::string& key, const std::string& value, const std::string& index, StyleValues& values)
229 {
230     auto [ret, styleValue] = GetStyleValue<Font>(index, values);
231     if (!ret) {
232         return;
233     }
234 
235     Font* font = Get<Font>(styleValue);
236     if (font == nullptr) {
237         return;
238     }
239 
240     if (key == "color") {
241         font->fontColor = ToSpanColor(value);
242     } else if (key == "font-size") {
243         font->fontSize = FromString(value);
244     } else if (key == "font-weight") {
245         font->fontWeight = StringUtils::StringToFontWeight(value);
246     } else if (key == "font-style") {
247         font->fontStyle = StringToFontStyle(value);
248     } else if (key == "font-family") {
249         font->fontFamilies = ParseFontFamily(value);
250     } else if (key == "font-variant") { // not support
251     }
252 }
253 
IsFontAttr(const std::string & key)254 bool HtmlToSpan::IsFontAttr(const std::string& key)
255 {
256     if (key == "font-size" || key == "font-weight" || key == "font-style" || key == "font-family" || key == "color") {
257         return true;
258     }
259     return false;
260 }
261 
InitParagraph(const std::string & key,const std::string & value,const std::string & index,StyleValues & values)262 void HtmlToSpan::InitParagraph(
263     const std::string& key, const std::string& value, const std::string& index, StyleValues& values)
264 {
265     auto [ret, styleValue] = GetStyleValue<SpanParagraphStyle>(index, values);
266     if (!ret) {
267         return;
268     }
269 
270     SpanParagraphStyle* style = Get<SpanParagraphStyle>(styleValue);
271     if (style == nullptr) {
272         return;
273     }
274 
275     if (key == "text-align") {
276         style->align = StringToTextAlign(value);
277     } else if (key == "word-break") {
278         style->wordBreak = StringToWordBreak(value);
279     } else if (key == "text-overflow") {
280         style->textOverflow = StringToTextOverflow(value);
281     } else if (IsTextIndentAttr(key)) {
282         style->textIndent = FromString(value);
283     } else {
284     }
285 }
286 
IsParagraphAttr(const std::string & key)287 bool HtmlToSpan::IsParagraphAttr(const std::string& key)
288 {
289     if (key == "text-align" || key == "word-break" || key == "text-overflow" || key == "text-indent") {
290         return true;
291     }
292     return false;
293 }
294 
IsDecorationLine(const std::string & key)295 bool HtmlToSpan::IsDecorationLine(const std::string& key)
296 {
297     if (key == "none" || key == "underline" || key == "overline" || key == "line-through" || key == "blink" ||
298         key == "inherit") {
299         return true;
300     }
301     return false;
302 }
303 
IsDecorationStyle(const std::string & key)304 bool HtmlToSpan::IsDecorationStyle(const std::string& key)
305 {
306     if (key == "solid" || key == "double" || key == "dotted" || key == "dashed" || key == "wavy" || key == "inherit") {
307         return true;
308     }
309     return false;
310 }
311 
InitDecoration(const std::string & key,const std::string & value,const std::string & index,StyleValues & values)312 void HtmlToSpan::InitDecoration(
313     const std::string& key, const std::string& value, const std::string& index, StyleValues& values)
314 {
315     auto [ret, styleValue] = GetStyleValue<DecorationSpanParam>(index, values);
316     if (!ret) {
317         return;
318     }
319     DecorationSpanParam* decoration = Get<DecorationSpanParam>(styleValue);
320     if (decoration == nullptr) {
321         return;
322     }
323 
324     if (key == "text-decoration-line") {
325         decoration->decorationType = StringToTextDecoration(value);
326     } else if (key == "text-decoration-style") {
327         decoration->decorationSytle = StringToTextDecorationStyle(value);
328     } else if (key == "text-decoration-color") {
329         decoration->color = ToSpanColor(value);
330     } else if (key == "text-decoration-thickness") { // not support
331     } else if (key == "text-decoration") {
332         std::istringstream ss1(value);
333         std::string word;
334         std::vector<std::string> words;
335         while (ss1 >> word) {
336             words.push_back(word);
337             if (IsDecorationLine(word)) {
338                 decoration->decorationType = StringToTextDecoration(word);
339             } else if (IsDecorationStyle(word)) {
340                 decoration->decorationSytle = StringToTextDecorationStyle(word);
341             } else {
342                 decoration->color = ToSpanColor(word);
343             }
344         }
345     }
346 }
347 
IsDecorationAttr(const std::string & key)348 bool HtmlToSpan::IsDecorationAttr(const std::string& key)
349 {
350     return key.compare(0, strlen("text-decoration"), "text-decoration") == 0;
351 }
352 
353 template<class T>
InitDimension(const std::string & key,const std::string & value,const std::string & index,StyleValues & values)354 void HtmlToSpan::InitDimension(
355     const std::string& key, const std::string& value, const std::string& index, StyleValues& values)
356 {
357     if (value.compare(0, strlen("normal"), "normal") == 0) {
358         return;
359     }
360     auto [ret, styleValue] = GetStyleValue<T>(index, values);
361     if (!ret) {
362         return;
363     }
364     T* obj = Get<T>(styleValue);
365     if (obj == nullptr) {
366         return;
367     }
368     obj->dimension = FromString(value);
369 }
370 
InitLineHeight(const std::string & key,const std::string & value,StyleValues & values)371 void HtmlToSpan::InitLineHeight(const std::string& key, const std::string& value, StyleValues& values)
372 {
373     auto [unit, size] = GetUnitAndSize(value);
374     if (!unit.empty()) {
375         InitDimension<LineHeightSpanSparam>(key, value, "line-height", values);
376         return;
377     }
378 
379     auto it = values.find("font");
380     if (it == values.end()) {
381         return;
382     }
383     Font* font = Get<Font>(&it->second);
384     if (font != nullptr) {
385         size = size * font->fontSize->Value();
386         InitDimension<LineHeightSpanSparam>(key, std::to_string(size) + unit, "line-height", values);
387     }
388 }
389 
IsLetterSpacingAttr(const std::string & key)390 bool HtmlToSpan::IsLetterSpacingAttr(const std::string& key)
391 {
392     return key.compare(0, strlen("letter-spacing"), "letter-spacing") == 0;
393 }
394 
ToSpanColor(const std::string & value)395 Color HtmlToSpan::ToSpanColor(const std::string& value)
396 {
397     std::smatch matches;
398     std::string color = value;
399     std::string tmp = value;
400     tmp.erase(std::remove(tmp.begin(), tmp.end(), ' '), tmp.end());
401     auto regStr = "#[0-9A-Fa-f]{6,8}";
402     constexpr auto tmpLeastLength = 3;
403     if (std::regex_match(tmp, matches, std::regex(regStr)) && tmp.length() >= tmpLeastLength) {
404         auto rgb = tmp.substr(1);
405         // remove last 2 character rgba -> argb
406         rgb.erase(rgb.length() - 2, 2);
407         auto alpha = tmp.substr(tmp.length() - 2);
408         color = "#" + alpha + rgb;
409     }
410 
411     return Color::FromString(color);
412 }
413 
InitTextShadow(const std::string & key,const std::string & value,const std::string & index,StyleValues & values)414 void HtmlToSpan::InitTextShadow(
415     const std::string& key, const std::string& value, const std::string& index, StyleValues& values)
416 {
417     auto [ret, styleValue] = GetStyleValue<std::vector<Shadow>>(index, values);
418     if (!ret) {
419         return;
420     }
421     std::vector<Shadow>* shadow = Get<std::vector<Shadow>>(styleValue);
422     if (shadow == nullptr) {
423         return;
424     }
425     std::istringstream ss(value);
426     std::string tmp;
427     std::vector<std::vector<std::string>> shadows;
428     while (std::getline(ss, tmp, ',')) {
429         std::istringstream iss(tmp);
430         std::string word;
431         std::vector<std::string> words;
432         while (iss >> word) {
433             words.emplace_back(word);
434         }
435         if (words.size() > FOUR_PARAM || words.size() < TWO_PARAM) {
436             return;
437         }
438         shadows.emplace_back(words);
439     }
440     for (const auto &its : shadows) {
441         std::vector<std::string> attribute(FOUR_PARAM);
442         uint8_t num = 0;
443         for (const auto &it : its) {
444             if (IsLength(it)) {
445                 attribute[num] = it;
446                 num++;
447                 continue;
448             }
449             attribute[FOURTH_PARAM] = it;
450         }
451         Shadow textShadow;
452         InitShadow(textShadow, attribute);
453         shadow->emplace_back(std::move(textShadow));
454     }
455 }
456 
InitShadow(Shadow & textShadow,std::vector<std::string> & attribute)457 void HtmlToSpan::InitShadow(Shadow &textShadow, std::vector<std::string> &attribute)
458 {
459     if (!attribute[FIRST_PARAM].empty()) {
460         textShadow.SetOffsetX(FromString(attribute[FIRST_PARAM]).Value());
461     }
462     if (!attribute[SECOND_PARAM].empty()) {
463         textShadow.SetOffsetY(FromString(attribute[SECOND_PARAM]).Value());
464     }
465     if (!attribute[THIRD_PARAM].empty()) {
466         textShadow.SetBlurRadius(FromString(attribute[THIRD_PARAM]).Value());
467     }
468     if (!attribute[FOURTH_PARAM].empty()) {
469         textShadow.SetColor(ToSpanColor(attribute[FOURTH_PARAM]));
470     }
471 }
472 
IsLength(const std::string & str)473 bool HtmlToSpan::IsLength(const std::string& str)
474 {
475     return !str.empty() &&
476         (std::all_of(str.begin(), str.end(), ::isdigit) || str.find("px") != std::string::npos);
477 }
478 
IsTextShadowAttr(const std::string & key)479 bool HtmlToSpan::IsTextShadowAttr(const std::string& key)
480 {
481     return key.compare(0, strlen("text-shadow"), "text-shadow") == 0;
482 }
483 
IsTextIndentAttr(const std::string & key)484 bool HtmlToSpan::IsTextIndentAttr(const std::string& key)
485 {
486     return key.compare(0, strlen("text-indent"), "text-indent") == 0;
487 }
488 
IsLineHeightAttr(const std::string & key)489 bool HtmlToSpan::IsLineHeightAttr(const std::string& key)
490 {
491     return key.compare(0, strlen("line-height"), "line-height") == 0;
492 }
493 
IsPaddingAttr(const std::string & key)494 bool HtmlToSpan::IsPaddingAttr(const std::string& key)
495 {
496     if (key == "padding" || key == "padding-top" || key == "padding-right" || key == "padding-bottom" ||
497         key == "padding-left") {
498         return true;
499     }
500     return false;
501 }
502 
IsMarginAttr(const std::string & key)503 bool HtmlToSpan::IsMarginAttr(const std::string& key)
504 {
505     if (key == "margin" || key == "margin-top" || key == "margin-right" || key == "margin-bottom" ||
506         key == "margin-left") {
507         return true;
508     }
509     return false;
510 }
511 
IsBorderAttr(const std::string & key)512 bool HtmlToSpan::IsBorderAttr(const std::string& key)
513 {
514     if (key == "border-radius" || key == "border-top-left-radius" || key == "border-top-right-radius" ||
515         key == "border-bottom-right-radius" || key == "border-bottom-left-radius") {
516         return true;
517     }
518     return false;
519 }
520 
SetPaddingOption(const std::string & key,const std::string & value,ImageSpanOptions & options)521 void HtmlToSpan::SetPaddingOption(const std::string& key, const std::string& value, ImageSpanOptions& options)
522 {
523     if (!options.imageAttribute->paddingProp) {
524         options.imageAttribute->paddingProp = std::make_optional<NG::PaddingProperty>();
525     }
526     auto& paddings = options.imageAttribute->paddingProp;
527     if (key == "padding") {
528         std::istringstream ss(value);
529         std::string word;
530         std::vector<std::string> words;
531         while (ss >> word) {
532             words.push_back(word);
533         }
534 
535         size_t size = words.size();
536         if (size == ONE_PARAM) {
537             paddings->top = NG::CalcLength(FromString(words[TOP_PARAM]));
538             paddings->right = NG::CalcLength(FromString(words[TOP_PARAM]));
539             paddings->bottom = NG::CalcLength(FromString(words[TOP_PARAM]));
540             paddings->left = NG::CalcLength(FromString(words[TOP_PARAM]));
541         } else if (size == TWO_PARAM) {
542             paddings->top = NG::CalcLength(FromString(words[TOP_PARAM]));
543             paddings->right = NG::CalcLength(FromString(words[RIGHT_PARAM]));
544             paddings->bottom = NG::CalcLength(FromString(words[TOP_PARAM]));
545             paddings->left = NG::CalcLength(FromString(words[RIGHT_PARAM]));
546         } else if (size == THREE_PARAM) {
547             paddings->top = NG::CalcLength(FromString(words[TOP_PARAM]));
548             paddings->right = NG::CalcLength(FromString(words[RIGHT_PARAM]));
549             paddings->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM]));
550             paddings->left = NG::CalcLength(FromString(words[RIGHT_PARAM]));
551         } else if (size == FOUR_PARAM) {
552             paddings->top = NG::CalcLength(FromString(words[TOP_PARAM]));
553             paddings->right = NG::CalcLength(FromString(words[RIGHT_PARAM]));
554             paddings->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM]));
555             paddings->left = NG::CalcLength(FromString(words[LEFT_PARAM]));
556         }
557     } else if (key == "padding-top") {
558         paddings->top = NG::CalcLength(FromString(value));
559     } else if (key == "padding-right") {
560         paddings->right = NG::CalcLength(FromString(value));
561     } else if (key == "padding-bottom") {
562         paddings->bottom = NG::CalcLength(FromString(value));
563     } else if (key == "padding-left") {
564         paddings->left = NG::CalcLength(FromString(value));
565     }
566 }
SetMarginOption(const std::string & key,const std::string & value,ImageSpanOptions & options)567 void HtmlToSpan::SetMarginOption(const std::string& key, const std::string& value, ImageSpanOptions& options)
568 {
569     if (!options.imageAttribute->marginProp) {
570         options.imageAttribute->marginProp = std::make_optional<NG::MarginProperty>();
571     }
572     auto& marginProp = options.imageAttribute->marginProp;
573     if (key == "margin") {
574         std::istringstream ss(value);
575         std::string word;
576         std::vector<std::string> words;
577         while (ss >> word) {
578             words.push_back(word);
579         }
580 
581         size_t size = words.size();
582         if (size == ONE_PARAM) {
583             marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM]));
584             marginProp->right = NG::CalcLength(FromString(words[TOP_PARAM]));
585             marginProp->bottom = NG::CalcLength(FromString(words[TOP_PARAM]));
586             marginProp->left = NG::CalcLength(FromString(words[TOP_PARAM]));
587         } else if (size == TWO_PARAM) {
588             marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM]));
589             marginProp->right = NG::CalcLength(FromString(words[RIGHT_PARAM]));
590             marginProp->bottom = NG::CalcLength(FromString(words[TOP_PARAM]));
591             marginProp->left = NG::CalcLength(FromString(words[RIGHT_PARAM]));
592         } else if (size == THREE_PARAM) {
593             marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM]));
594             marginProp->right = NG::CalcLength(FromString(words[RIGHT_PARAM]));
595             marginProp->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM]));
596             marginProp->left = NG::CalcLength(FromString(words[RIGHT_PARAM]));
597         } else if (size == FOUR_PARAM) {
598             marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM]));
599             marginProp->right = NG::CalcLength(FromString(words[RIGHT_PARAM]));
600             marginProp->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM]));
601             marginProp->left = NG::CalcLength(FromString(words[LEFT_PARAM]));
602         }
603     } else if (key == "margin-top") {
604         marginProp->top = NG::CalcLength(FromString(value));
605     } else if (key == "margin-right") {
606         marginProp->right = NG::CalcLength(FromString(value));
607     } else if (key == "margin-bottom") {
608         marginProp->bottom = NG::CalcLength(FromString(value));
609     } else if (key == "margin-left") {
610         marginProp->left = NG::CalcLength(FromString(value));
611     }
612 }
SetBorderOption(const std::string & key,const std::string & value,ImageSpanOptions & options)613 void HtmlToSpan::SetBorderOption(const std::string& key, const std::string& value, ImageSpanOptions& options)
614 {
615     if (!options.imageAttribute->borderRadius) {
616         options.imageAttribute->borderRadius = std::make_optional<NG::BorderRadiusProperty>();
617         options.imageAttribute->borderRadius->multiValued = true;
618     }
619     auto& borderRadius = options.imageAttribute->borderRadius;
620     if (key == "border-radius") {
621         std::istringstream ss(value);
622         std::string word;
623         std::vector<std::string> words;
624         while (ss >> word) {
625             words.push_back(word);
626         }
627         size_t size = words.size();
628         if (size == ONE_PARAM) {
629             borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]);
630             borderRadius->radiusTopRight = FromString(words[TOP_PARAM]);
631             borderRadius->radiusBottomRight = FromString(words[TOP_PARAM]);
632             borderRadius->radiusBottomLeft = FromString(words[TOP_PARAM]);
633         } else if (size == TWO_PARAM) {
634             borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]);
635             borderRadius->radiusTopRight = FromString(words[RIGHT_PARAM]);
636             borderRadius->radiusBottomRight = FromString(words[TOP_PARAM]);
637             borderRadius->radiusBottomLeft = FromString(words[RIGHT_PARAM]);
638         } else if (size == THREE_PARAM) {
639             borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]);
640             borderRadius->radiusTopRight = FromString(words[RIGHT_PARAM]);
641             borderRadius->radiusBottomRight = FromString(words[BOTTOM_PARAM]);
642             borderRadius->radiusBottomLeft = FromString(words[RIGHT_PARAM]);
643         } else if (size == FOUR_PARAM) {
644             borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]);
645             borderRadius->radiusTopRight = FromString(words[RIGHT_PARAM]);
646             borderRadius->radiusBottomRight = FromString(words[BOTTOM_PARAM]);
647             borderRadius->radiusBottomLeft = FromString(words[LEFT_PARAM]);
648         }
649     } else if (key == "border-top-left-radius") {
650         borderRadius->radiusTopLeft = FromString(value);
651     } else if (key == "border-top-right-radius") {
652         borderRadius->radiusTopRight = FromString(value);
653     } else if (key == "border-bottom-right-radius") {
654         borderRadius->radiusBottomRight = FromString(value);
655     } else if (key == "border-bottom-left-radius") {
656         borderRadius->radiusBottomLeft = FromString(value);
657     }
658 }
HandleImgSpanOption(const Styles & styleMap,ImageSpanOptions & options)659 void HtmlToSpan::HandleImgSpanOption(const Styles& styleMap, ImageSpanOptions& options)
660 {
661     for (const auto& [key, value] : styleMap) {
662         auto trimVal = StringUtils::TrimStr(value);
663         if (IsPaddingAttr(key)) {
664             SetPaddingOption(key, trimVal, options);
665         } else if (IsMarginAttr(key)) {
666             SetMarginOption(key, trimVal, options);
667         } else if (IsBorderAttr(key)) {
668             SetBorderOption(key, trimVal, options);
669         } else if (key == "object-fit") {
670             options.imageAttribute->objectFit = ConvertStrToFit(trimVal);
671         } else if (key == "vertical-align") {
672             options.imageAttribute->verticalAlign = StringToTextVerticalAlign(trimVal);
673         } else if (key == "width" || key == "height") {
674             HandleImageSize(key, trimVal, options);
675         } else if (key == "sync-load") {
676             options.imageAttribute->syncLoad = V2::ConvertStringToBool(trimVal);
677         }
678     }
679 }
HandleImagePixelMap(const std::string & src,ImageSpanOptions & option)680 void HtmlToSpan::HandleImagePixelMap(const std::string& src, ImageSpanOptions& option)
681 {
682     if (src.empty()) {
683         return;
684     }
685     NG::LoadNotifier loadNotifier(nullptr, nullptr, nullptr);
686     RefPtr<NG::ImageLoadingContext> ctx =
687         AceType::MakeRefPtr<NG::ImageLoadingContext>(ImageSourceInfo(src), std::move(loadNotifier), true);
688     CHECK_NULL_VOID(ctx);
689     ctx->LoadImageData();
690     ctx->MakeCanvasImageIfNeed(ctx->GetImageSize(), true, ImageFit::NONE);
691     auto image = ctx->MoveCanvasImage();
692     if (image != nullptr) {
693         auto pixelMap = image->GetPixelMap();
694         if (pixelMap) {
695             option.imagePixelMap = pixelMap;
696         }
697     }
698     if (option.imagePixelMap.has_value() && option.imagePixelMap.value() != nullptr) {
699         auto pixel = option.imagePixelMap.value();
700         LOGI("img height: %{public}d, width: %{public}d, size:%{public}d", pixel->GetHeight(),
701             pixel->GetWidth(), pixel->GetByteCount());
702     } else {
703         option.image = src;
704     }
705 }
706 
HandleImageSize(const std::string & key,const std::string & value,ImageSpanOptions & options)707 void HtmlToSpan::HandleImageSize(const std::string& key, const std::string& value, ImageSpanOptions& options)
708 {
709     if (!options.imageAttribute->size) {
710         options.imageAttribute->size = std::make_optional<ImageSpanSize>();
711     }
712     if (key == "width") {
713         options.imageAttribute->size->width = FromString(value);
714     } else {
715         options.imageAttribute->size->height = FromString(value);
716     }
717 }
718 
MakeImageSpanOptions(const std::string & key,const std::string & value,ImageSpanOptions & options)719 void HtmlToSpan::MakeImageSpanOptions(const std::string& key, const std::string& value, ImageSpanOptions& options)
720 {
721     if (key == "src") {
722         options.image = value;
723         HandleImagePixelMap(value, options);
724     } else if (key == "style") {
725         Styles styleMap = ParseStyleAttr(value);
726         HandleImgSpanOption(styleMap, options);
727     } else if (key == "width" || key == "height") {
728         HandleImageSize(key, value, options);
729     }
730 }
731 
StringToTextAlign(const std::string & value)732 TextAlign HtmlToSpan::StringToTextAlign(const std::string& value)
733 {
734     if (value == "left") {
735         return TextAlign::LEFT;
736     }
737     if (value == "right") {
738         return TextAlign::RIGHT;
739     }
740     if (value == "center") {
741         return TextAlign::CENTER;
742     }
743     if (value == "justify") {
744         return TextAlign::JUSTIFY;
745     }
746     return TextAlign::LEFT;
747 }
748 
StringToWordBreak(const std::string & value)749 WordBreak HtmlToSpan::StringToWordBreak(const std::string& value)
750 {
751     if (value == "normal") {
752         return WordBreak::NORMAL;
753     }
754     if (value == "break-all") {
755         return WordBreak::BREAK_ALL;
756     }
757     if (value == "keep-all") {
758         return WordBreak::BREAK_WORD;
759     }
760     return WordBreak::NORMAL;
761 }
762 
StringToTextOverflow(const std::string & value)763 TextOverflow HtmlToSpan::StringToTextOverflow(const std::string& value)
764 {
765     if (value == "clip") {
766         return TextOverflow::CLIP;
767     }
768     if (value == "ellipsis") {
769         return TextOverflow::ELLIPSIS;
770     }
771     return TextOverflow::NONE;
772 }
773 
ToDefalutSpan(xmlNodePtr node,size_t len,size_t & pos,std::vector<SpanInfo> & spanInfos)774 void HtmlToSpan::ToDefalutSpan(xmlNodePtr node, size_t len, size_t& pos, std::vector<SpanInfo>& spanInfos)
775 {
776     if (len == 0) {
777         return;
778     }
779 
780     SpanInfo info;
781     info.type = HtmlType::DEFAULT;
782     info.start = pos;
783     info.end = pos + len;
784     spanInfos.emplace_back(std::move(info));
785 }
786 
787 template<class T>
GetStyleValue(const std::string & key,std::map<std::string,StyleValue> & values)788 std::pair<bool, HtmlToSpan::StyleValue*> HtmlToSpan::GetStyleValue(
789     const std::string& key, std::map<std::string, StyleValue>& values)
790 {
791     auto it = values.find(key);
792     if (it == values.end()) {
793         StyleValue value = T();
794         it = values.emplace(key, value).first;
795     }
796 
797     if (it == values.end()) {
798         return std::make_pair(false, nullptr);
799     }
800 
801     return std::make_pair(true, &it->second);
802 }
803 
ToParagraphSpan(xmlNodePtr node,size_t len,size_t & pos,std::vector<SpanInfo> & spanInfos)804 void HtmlToSpan::ToParagraphSpan(xmlNodePtr node, size_t len, size_t& pos, std::vector<SpanInfo>& spanInfos)
805 {
806     SpanInfo info;
807     info.type = HtmlType::PARAGRAPH;
808     info.start = pos;
809     info.end = pos + len;
810     xmlAttrPtr curNode = node->properties;
811     if (curNode == nullptr) {
812         SpanParagraphStyle style;
813         info.values.emplace_back(style);
814     } else {
815         for (; curNode; curNode = curNode->next) {
816             auto styles = ToTextSpanStyle(curNode);
817             for (auto [key, value] : styles) {
818                 info.values.emplace_back(value);
819             }
820         }
821     }
822 
823     spanInfos.emplace_back(std::move(info));
824 }
825 
GetUnitAndSize(const std::string & str)826 std::pair<std::string, double> HtmlToSpan::GetUnitAndSize(const std::string& str)
827 {
828     double value = 0.0;
829     for (int32_t i = static_cast<int32_t>(str.length() - 1); i >= 0; --i) {
830         if (str[i] >= '0' && str[i] <= '9') {
831             value = StringUtils::StringToDouble(str.substr(0, i + 1));
832             auto subStr = str.substr(i + 1);
833             return { subStr, value };
834         }
835     }
836     return { "", value };
837 }
838 
ToTextSpanStyle(xmlAttrPtr curNode)839 std::map<std::string, HtmlToSpan::StyleValue> HtmlToSpan::ToTextSpanStyle(xmlAttrPtr curNode)
840 {
841     auto attrContent = xmlGetProp(curNode->parent, curNode->name);
842     if (attrContent == nullptr) {
843         return {};
844     }
845     std::string strStyle(reinterpret_cast<const char*>(attrContent));
846     xmlFree(attrContent);
847     Styles styleMap = ParseStyleAttr(strStyle);
848     std::map<std::string, StyleValue> styleValues;
849     for (auto& [key, value] : styleMap) {
850         auto trimVal = StringUtils::TrimStr(value);
851         if (IsFontAttr(key)) {
852             InitFont(key, trimVal, "font", styleValues);
853         } else if (IsDecorationAttr(key)) {
854             InitDecoration(key, trimVal, "decoration", styleValues);
855         } else if (IsLetterSpacingAttr(key)) {
856             InitDimension<LetterSpacingSpanParam>(key, trimVal, "letterSpacing", styleValues);
857         } else if (IsTextShadowAttr(key)) {
858             InitTextShadow(key, trimVal, "shadow", styleValues);
859         } else if (IsLineHeightAttr(key)) {
860             InitLineHeight(key, trimVal, styleValues);
861         } else if (IsParagraphAttr(key)) {
862             InitParagraph(key, trimVal, "paragrap", styleValues);
863         }
864     }
865 
866     return styleValues;
867 }
868 
AddStyleSpan(const std::string & element,SpanInfo & info)869 void HtmlToSpan::AddStyleSpan(const std::string& element, SpanInfo& info)
870 {
871     std::map<std::string, StyleValue> styles;
872     if (element == "strong") {
873         InitFont("font-weight", "bold", "font", styles);
874     }
875 
876     for (auto [key, value] : styles) {
877         info.values.emplace_back(value);
878     }
879 }
880 
ToTextSpan(const std::string & element,xmlNodePtr node,size_t len,size_t & pos,std::vector<SpanInfo> & spanInfos)881 void HtmlToSpan::ToTextSpan(
882     const std::string& element, xmlNodePtr node, size_t len, size_t& pos, std::vector<SpanInfo>& spanInfos)
883 {
884     SpanInfo info;
885     info.type = HtmlType::TEXT;
886     info.start = pos;
887     info.end = pos + len;
888     xmlAttrPtr curNode = node->properties;
889     for (; curNode; curNode = curNode->next) {
890         auto styles = ToTextSpanStyle(curNode);
891         for (auto [key, value] : styles) {
892             info.values.emplace_back(value);
893         }
894     }
895     if (!element.empty()) {
896         AddStyleSpan(element, info);
897     }
898     if (info.values.empty()) {
899         return;
900     }
901     spanInfos.emplace_back(std::move(info));
902 }
903 
ToImageOptions(const std::map<std::string,std::string> & styles,ImageSpanOptions & option)904 void HtmlToSpan::ToImageOptions(const std::map<std::string, std::string>& styles, ImageSpanOptions& option)
905 {
906     option.imageAttribute = std::make_optional<ImageSpanAttribute>();
907     for (auto& [key, value] : styles) {
908         MakeImageSpanOptions(key, value, option);
909     }
910 }
911 
ToImage(xmlNodePtr node,size_t len,size_t & pos,std::vector<SpanInfo> & spanInfos,bool isProcessImageOptions)912 void HtmlToSpan::ToImage(xmlNodePtr node, size_t len, size_t& pos, std::vector<SpanInfo>& spanInfos,
913     bool isProcessImageOptions)
914 {
915     std::map<std::string, std::string> styleMap;
916     xmlAttrPtr curNode = node->properties;
917     for (; curNode; curNode = curNode->next) {
918         auto attrContent = xmlGetProp(curNode->parent, curNode->name);
919         if (attrContent != nullptr) {
920             styleMap[reinterpret_cast<const char*>(curNode->name)] = reinterpret_cast<const char*>(attrContent);
921             xmlFree(attrContent);
922         }
923     }
924 
925     ImageSpanOptions option;
926     if (isProcessImageOptions) {
927         ToImageOptions(styleMap, option);
928     }
929 
930     SpanInfo info;
931     info.type = HtmlType::IMAGE;
932     info.start = pos;
933     info.end = pos + len;
934     info.values.emplace_back(std::move(option));
935     spanInfos.emplace_back(std::move(info));
936 }
937 
ToSpan(xmlNodePtr curNode,size_t & pos,std::string & allContent,std::vector<SpanInfo> & spanInfos,bool isNeedLoadPixelMap)938 void HtmlToSpan::ToSpan(
939     xmlNodePtr curNode, size_t& pos, std::string& allContent, std::vector<SpanInfo>& spanInfos,
940     bool isNeedLoadPixelMap)
941 {
942     size_t curNodeLen = 0;
943     if (curNode->content) {
944         std::string curNodeContent = reinterpret_cast<const char*>(curNode->content);
945         allContent += curNodeContent;
946         curNodeLen = StringUtils::ToWstring(curNodeContent).length();
947     }
948 
949     std::string htmlTag = reinterpret_cast<const char*>(curNode->name);
950     size_t childPos = pos + curNodeLen;
951     ParseHtmlToSpanInfo(curNode->children, childPos, allContent, spanInfos);
952     if (curNode->type == XML_ELEMENT_NODE) {
953         if (htmlTag == "p") {
954             if (curNode->parent == nullptr || curNode->parent->type != XML_ELEMENT_NODE ||
955                 xmlStrcmp(curNode->parent->name, (const xmlChar*)"span") != 0) {
956                 // The <p> contained in <span> is discarded. It is not considered as a standard writing method.
957                 allContent += "\n";
958                 childPos++;
959                 ToParagraphSpan(curNode, childPos - pos, pos, spanInfos);
960             }
961         } else if (htmlTag == "img") {
962             childPos++;
963             ToImage(curNode, childPos - pos, pos, spanInfos, isNeedLoadPixelMap);
964         } else {
965             ToTextSpan(htmlTag, curNode, childPos - pos, pos, spanInfos);
966         }
967     }
968     pos = childPos;
969 }
970 
ParseHtmlToSpanInfo(xmlNodePtr node,size_t & pos,std::string & allContent,std::vector<SpanInfo> & spanInfos,bool isNeedLoadPixelMap)971 void HtmlToSpan::ParseHtmlToSpanInfo(
972     xmlNodePtr node, size_t& pos, std::string& allContent, std::vector<SpanInfo>& spanInfos, bool isNeedLoadPixelMap)
973 {
974     xmlNodePtr curNode = nullptr;
975     for (curNode = node; curNode; curNode = curNode->next) {
976         if (curNode->type == XML_ELEMENT_NODE || curNode->type == XML_TEXT_NODE) {
977             ToSpan(curNode, pos, allContent, spanInfos, isNeedLoadPixelMap);
978         }
979     }
980 }
981 
PrintSpanInfos(const std::vector<SpanInfo> & spanInfos)982 void HtmlToSpan::PrintSpanInfos(const std::vector<SpanInfo>& spanInfos)
983 {
984     for (auto& info : spanInfos) {
985         LOGI("span type %{public}d start:%{public}zu end:%{public}zu, style size:%{public}zu",
986             static_cast<int>(info.type), info.start, info.end, info.values.size());
987     }
988 }
989 
AfterProcSpanInfos(std::vector<SpanInfo> & spanInfos)990 void HtmlToSpan::AfterProcSpanInfos(std::vector<SpanInfo>& spanInfos)
991 {
992     std::vector<std::pair<size_t, size_t>> paragraphPos;
993     for (auto& info : spanInfos) {
994         if (info.type == HtmlType::PARAGRAPH) {
995             paragraphPos.push_back({ info.start, info.end });
996         }
997     }
998 
999     for (auto& pos : paragraphPos) {
1000         for (auto& info : spanInfos) {
1001             if (info.type != HtmlType::PARAGRAPH && info.type != HtmlType::IMAGE && pos.second == info.end + 1) {
1002                 info.end += 1;
1003                 break;
1004             }
1005         }
1006     }
1007 }
1008 
CreateSpan(size_t index,const SpanInfo & info,StyleValue & value)1009 RefPtr<SpanBase> HtmlToSpan::CreateSpan(size_t index, const SpanInfo& info, StyleValue& value)
1010 {
1011     if (index == static_cast<uint32_t>(StyleIndex::STYLE_FONT)) {
1012         return MakeSpan<Font, FontSpan>(info, value);
1013     }
1014 
1015     if (index == static_cast<uint32_t>(StyleIndex::STYLE_DECORATION)) {
1016         return MakeDecorationSpan(info, value);
1017     }
1018 
1019     if (index == static_cast<uint32_t>(StyleIndex::STYLE_BASELINE)) {
1020         return MakeDimensionSpan<BaseLineSpanParam, BaselineOffsetSpan>(info, value);
1021     }
1022 
1023     if (index == static_cast<uint32_t>(StyleIndex::STYLE_LETTERSPACE)) {
1024         return MakeDimensionSpan<LetterSpacingSpanParam, LetterSpacingSpan>(info, value);
1025     }
1026 
1027     if (index == static_cast<uint32_t>(StyleIndex::STYLE_LINEHEIGHT)) {
1028         return MakeDimensionSpan<LineHeightSpanSparam, LineHeightSpan>(info, value);
1029     }
1030 
1031     if (index == static_cast<uint32_t>(StyleIndex::STYLE_SHADOWS)) {
1032         return MakeSpan<std::vector<Shadow>, TextShadowSpan>(info, value);
1033     }
1034 
1035     if (index == static_cast<uint32_t>(StyleIndex::STYLE_PARAGRAPH)) {
1036         return MakeSpan<SpanParagraphStyle, ParagraphStyleSpan>(info, value);
1037     }
1038 
1039     return nullptr;
1040 }
1041 
1042 template<class T, class P>
MakeSpan(const SpanInfo & info,StyleValue & value)1043 RefPtr<SpanBase> HtmlToSpan::MakeSpan(const SpanInfo& info, StyleValue& value)
1044 {
1045     auto style = Get<T>(&value);
1046     if (style != nullptr) {
1047         return AceType::MakeRefPtr<P>(*style, info.start, info.end);
1048     }
1049 
1050     return nullptr;
1051 }
1052 
1053 template<class T, class P>
MakeDimensionSpan(const SpanInfo & info,StyleValue & value)1054 RefPtr<SpanBase> HtmlToSpan::MakeDimensionSpan(const SpanInfo& info, StyleValue& value)
1055 {
1056     auto style = Get<T>(&value);
1057     if (style != nullptr) {
1058         return AceType::MakeRefPtr<P>(style->dimension, info.start, info.end);
1059     }
1060 
1061     return nullptr;
1062 }
1063 
MakeDecorationSpan(const SpanInfo & info,StyleValue & value)1064 RefPtr<SpanBase> HtmlToSpan::MakeDecorationSpan(const SpanInfo& info, StyleValue& value)
1065 {
1066     auto style = Get<DecorationSpanParam>(&value);
1067     if (style != nullptr) {
1068         return AceType::MakeRefPtr<DecorationSpan>(
1069             style->decorationType, style->color, style->decorationSytle, info.start, info.end);
1070     }
1071 
1072     return nullptr;
1073 }
1074 
AddSpans(const SpanInfo & info,RefPtr<MutableSpanString> mutableSpan)1075 void HtmlToSpan::AddSpans(const SpanInfo& info, RefPtr<MutableSpanString> mutableSpan)
1076 {
1077     for (auto value : info.values) {
1078         size_t index = value.index();
1079         RefPtr<SpanBase> span;
1080         if (index >= 0 && index < static_cast<size_t>(StyleIndex::STYLE_MAX)) {
1081             span = CreateSpan(index, info, value);
1082         }
1083         if (span != nullptr) {
1084             mutableSpan->AddSpan(span);
1085         }
1086     }
1087 }
1088 
AddImageSpans(const SpanInfo & info,RefPtr<MutableSpanString> mutableSpan)1089 void HtmlToSpan::AddImageSpans(const SpanInfo& info, RefPtr<MutableSpanString> mutableSpan)
1090 {
1091     for (auto value : info.values) {
1092         auto style = Get<ImageSpanOptions>(&value);
1093         if (style == nullptr) {
1094             continue;
1095         }
1096         auto span = AceType::MakeRefPtr<MutableSpanString>(*style);
1097         mutableSpan->InsertSpanString(info.start, span);
1098     }
1099 }
1100 
GenerateSpans(const std::string & allContent,const std::vector<SpanInfo> & spanInfos)1101 RefPtr<MutableSpanString> HtmlToSpan::GenerateSpans(
1102     const std::string& allContent, const std::vector<SpanInfo>& spanInfos)
1103 {
1104     auto mutableSpan = AceType::MakeRefPtr<MutableSpanString>(UtfUtils::Str8DebugToStr16(allContent));
1105     RefPtr<MutableSpanString> span;
1106     for (auto& info : spanInfos) {
1107         if (info.type == HtmlType::PARAGRAPH) {
1108             AddSpans(info, mutableSpan);
1109         } else if (info.type == HtmlType::IMAGE) {
1110             AddImageSpans(info, mutableSpan);
1111         } else {
1112             AddSpans(info, mutableSpan);
1113         }
1114     }
1115 
1116     return mutableSpan;
1117 }
1118 
ToSpanString(const std::string & html,const bool isNeedLoadPixelMap)1119 RefPtr<MutableSpanString> HtmlToSpan::ToSpanString(const std::string& html, const bool isNeedLoadPixelMap)
1120 {
1121     htmlDocPtr doc = htmlReadMemory(html.c_str(), html.length(), nullptr, "UTF-8", 0);
1122     if (doc == nullptr) {
1123         return nullptr;
1124     }
1125 
1126     auto docSharedPtr = std::shared_ptr<xmlDoc>(doc, [](htmlDocPtr doc) { xmlFreeDoc(doc); });
1127     if (docSharedPtr == nullptr) {
1128         return nullptr;
1129     }
1130 
1131     xmlNode* root = xmlDocGetRootElement(docSharedPtr.get());
1132     if (root == nullptr) {
1133         return nullptr;
1134     }
1135 
1136     size_t pos = 0;
1137     std::string content;
1138     std::vector<SpanInfo> spanInfos;
1139     ParseHtmlToSpanInfo(root, pos, content, spanInfos, isNeedLoadPixelMap);
1140     AfterProcSpanInfos(spanInfos);
1141     PrintSpanInfos(spanInfos);
1142     return GenerateSpans(content, spanInfos);
1143 }
1144 
FromHtml(const std::string & html)1145 RefPtr<MutableSpanString> HtmlUtils::FromHtml(const std::string& html)
1146 {
1147     HtmlToSpan hts;
1148     auto styledString = hts.ToSpanString(html);
1149     return styledString;
1150 }
1151 } // namespace OHOS::Ace
1152