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