/* * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "html_to_span.h" #include #include "core/text/html_utils.h" namespace OHOS::Ace { constexpr int ONE_PARAM = 1; constexpr int TWO_PARAM = 2; constexpr int THREE_PARAM = 3; constexpr int FOUR_PARAM = 4; constexpr int TOP_PARAM = 0; constexpr int RIGHT_PARAM = 1; constexpr int BOTTOM_PARAM = 2; constexpr int LEFT_PARAM = 3; constexpr int FIRST_PARAM = 0; constexpr int SECOND_PARAM = 1; constexpr int THIRD_PARAM = 2; constexpr int FOURTH_PARAM = 3; constexpr int MAX_STYLE_FORMAT_NUMBER = 3; void ToLowerCase(std::string& str) { for (auto& c : str) { c = tolower(c); } } std::vector ParseFontFamily(const std::string& fontFamily) { std::vector fonts; std::stringstream ss(fontFamily); std::string token; while (std::getline(ss, token, ',')) { std::string font = std::string(token.begin(), token.end()); font.erase(std::remove_if(font.begin(), font.end(), isspace), font.end()); if (!font.empty()) { fonts.push_back(font); } } return fonts; } VerticalAlign StringToVerticalAlign(const std::string& align) { if (align == "bottom") { return VerticalAlign::BOTTOM; } if (align == "middle") { return VerticalAlign::CENTER; } if (align == "top") { return VerticalAlign::TOP; } return VerticalAlign::NONE; } FontStyle StringToFontStyle(const std::string& fontStyle) { return fontStyle == "italic" ? FontStyle::ITALIC : FontStyle::NORMAL; } SuperscriptStyle StringToSuperscriptStyle(const std::string& superscriptStyle) { if (superscriptStyle == "superscript") { return SuperscriptStyle::SUPERSCRIPT; } else if (superscriptStyle == "subscript") { return SuperscriptStyle::SUBSCRIPT; } else if (superscriptStyle == "normal") { return SuperscriptStyle::NORMAL; } return SuperscriptStyle::NONE; } TextDecorationStyle StringToTextDecorationStyle(const std::string& textDecorationStyle) { std::string value = StringUtils::TrimStr(textDecorationStyle); if (value == "dashed") { return TextDecorationStyle::DASHED; } if (value == "dotted") { return TextDecorationStyle::DOTTED; } if (value == "double") { return TextDecorationStyle::DOUBLE; } if (value == "solid") { return TextDecorationStyle::SOLID; } if (value == "wavy") { return TextDecorationStyle::WAVY; } return TextDecorationStyle::SOLID; } std::vector StringToTextDecoration(const std::string& textDecoration) { std::istringstream ss(textDecoration); std::string tmp; std::vector decorations; while (std::getline(ss, tmp, ' ')) { decorations.emplace_back(tmp); } std::vector result; for (const auto &its : decorations) { std::string value = StringUtils::TrimStr(its); TextDecoration decoration; if (value == "inherit") { decoration = TextDecoration::INHERIT; } if (value == "line-through") { decoration = TextDecoration::LINE_THROUGH; } if (value == "overline") { decoration = TextDecoration::OVERLINE; } if (value == "underline") { decoration = TextDecoration::UNDERLINE; } result.push_back(decoration); } return result; } ImageFit ConvertStrToFit(const std::string& fit) { if (fit == "fill") { return ImageFit::FILL; } if (fit == "contain") { return ImageFit::CONTAIN; } if (fit == "cover") { return ImageFit::COVER; } if (fit == "scaledown") { return ImageFit::SCALE_DOWN; } if (fit == "none") { return ImageFit::NONE; } return ImageFit::CONTAIN; } HtmlToSpan::Styles HtmlToSpan::ParseStyleAttr(const std::string& style) { Styles styles; if (style.find(':') == std::string::npos) { return styles; } std::regex pattern(R"(\s*([^:]+):([^;]+);?)"); std::smatch match; std::string::const_iterator searchStart(style.begin()); while (std::regex_search(searchStart, style.end(), match, pattern)) { if (match.size() < MAX_STYLE_FORMAT_NUMBER) { continue; } std::string key = std::regex_replace(match[1].str(), std::regex(R"(\s+)"), ""); std::string value = std::regex_replace(match[2].str(), std::regex(R"(\s+)"), " "); ToLowerCase(key); styles.emplace_back(key, value); searchStart = match[0].second; } return styles; } template T* HtmlToSpan::Get(StyleValue* styleValue) const { auto v = std::get_if(styleValue); if (v == nullptr) { return nullptr; } return static_cast(v); } // for example str = 0.00px Dimension HtmlToSpan::FromString(const std::string& str) { static const int32_t PERCENT_UNIT = 100; static const std::unordered_map uMap { { "px", DimensionUnit::PX }, { "vp", DimensionUnit::VP }, { "fp", DimensionUnit::FP }, { "%", DimensionUnit::PERCENT }, { "lpx", DimensionUnit::LPX }, { "auto", DimensionUnit::AUTO }, { "rem", DimensionUnit::INVALID }, { "em", DimensionUnit::INVALID }, }; double value = 0.0; DimensionUnit unit = DimensionUnit::VP; if (str.empty()) { LOGE("UITree |ERROR| empty string"); return Dimension(NG::TEXT_DEFAULT_FONT_SIZE); } for (int32_t i = static_cast(str.length()) - 1; i >= 0; --i) { if (str[i] >= '0' && str[i] <= '9') { auto startIndex = i + 1; value = StringUtils::StringToDouble(str.substr(0, startIndex)); startIndex = std::clamp(startIndex, 0, static_cast(str.length())); auto subStr = str.substr(startIndex); if (subStr == "pt") { value = static_cast(value * PT_TO_PX + ROUND_TO_INT); break; } auto iter = uMap.find(subStr); if (iter != uMap.end()) { unit = iter->second; } value = unit == DimensionUnit::PERCENT ? value / PERCENT_UNIT : value; break; } } if (unit == DimensionUnit::PX) { return Dimension(value, DimensionUnit::VP); } else if (unit == DimensionUnit::INVALID) { return Dimension(NG::TEXT_DEFAULT_FONT_SIZE); } return Dimension(value, unit); } void HtmlToSpan::InitFont( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { auto [ret, styleValue] = GetStyleValue(index, values); if (!ret) { return; } Font* font = Get(styleValue); if (font == nullptr) { return; } if (key == "color") { font->fontColor = ToSpanColor(value); } else if (key == "font-size") { font->fontSize = FromString(value); } else if (key == "font-weight") { font->fontWeight = StringUtils::StringToFontWeight(value); } else if (key == "font-style") { font->fontStyle = StringToFontStyle(value); } else if (key == "font-family") { font->fontFamilies = ParseFontFamily(value); } else if (key == "font-variant") { // not support } else if (key == "stroke-width") { font->strokeWidth = FromString(value); } else if (key == "stroke-color") { font->strokeColor = ToSpanColor(value); } else if (key == "font-superscript") { font->superscript = StringToSuperscriptStyle(value); } } bool HtmlToSpan::IsFontAttr(const std::string& key) { if (key == "font-size" || key == "font-weight" || key == "font-style" || key == "font-family" || key == "color" || key == "stroke-width" || key == "stroke-color" || key == "font-superscript") { return true; } return false; } bool HtmlToSpan::IsBackgroundColorAttr(const std::string& key) const { return key == "background-color"; } bool HtmlToSpan::IsForegroundColorAttr(const std::string& key) const { return key == "foreground-color"; } void HtmlToSpan::InitBackgroundColor( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { auto [ret, styleValue] = GetStyleValue(index, values); if (!ret) { return; } TextBackgroundStyle* style = Get(styleValue); if (style == nullptr) { return; } if (key == "background-color") { style->backgroundColor = ToSpanColor(value); } } void HtmlToSpan::InitForegroundColor( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { auto [ret, styleValue] = GetStyleValue(index, values); if (!ret) { return; } Font* font = Get(styleValue); if (font == nullptr) { return; } if (key == "foreground-color") { font->fontColor = ToSpanColor(value); } } void HtmlToSpan::InitParagraph( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { auto [ret, styleValue] = GetStyleValue(index, values); if (!ret) { return; } SpanParagraphStyle* style = Get(styleValue); if (style == nullptr) { return; } if (key == "text-align") { style->align = StringToTextAlign(value); } else if (key == "vertical-align") { style->textVerticalAlign = StringToTextVerticalAlign(value); } else if (key == "word-break") { style->wordBreak = StringToWordBreak(value); } else if (key == "text-overflow") { style->textOverflow = StringToTextOverflow(value); } else if (IsTextIndentAttr(key)) { style->textIndent = FromString(value); } else { } } bool HtmlToSpan::IsParagraphAttr(const std::string& key) { if (key == "text-align" || key == "word-break" || key == "text-overflow" || key == "text-indent") { return true; } return false; } bool HtmlToSpan::IsDecorationLine(const std::string& key) { if (key == "none" || key == "underline" || key == "overline" || key == "line-through" || key == "blink" || key == "inherit") { return true; } return false; } bool HtmlToSpan::IsDecorationStyle(const std::string& key) { if (key == "solid" || key == "double" || key == "dotted" || key == "dashed" || key == "wavy" || key == "inherit") { return true; } return false; } void HtmlToSpan::InitDecoration( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { auto [ret, styleValue] = GetStyleValue(index, values); if (!ret) { return; } DecorationSpanParam* decoration = Get(styleValue); if (decoration == nullptr) { return; } if (key == "text-decoration-line") { decoration->decorationType = StringToTextDecoration(value); } else if (key == "text-decoration-style") { decoration->decorationSytle = StringToTextDecorationStyle(value); } else if (key == "text-decoration-color") { decoration->color = ToSpanColor(value); } else if (key == "text-decoration-thickness") { // not supported: html has unit while lineThicknessScale is float } else if (key == "text-decoration") { std::istringstream ss1(value); std::string word; std::vector words; while (ss1 >> word) { words.push_back(word); if (IsDecorationLine(word)) { decoration->decorationType = StringToTextDecoration(word); } else if (IsDecorationStyle(word)) { decoration->decorationSytle = StringToTextDecorationStyle(word); } else { decoration->color = ToSpanColor(word); } } } } bool HtmlToSpan::IsDecorationAttr(const std::string& key) { return key.compare(0, strlen("text-decoration"), "text-decoration") == 0; } template void HtmlToSpan::InitDimension( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { if (value.compare(0, strlen("normal"), "normal") == 0) { return; } auto [ret, styleValue] = GetStyleValue(index, values); if (!ret) { return; } T* obj = Get(styleValue); if (obj == nullptr) { return; } obj->dimension = FromString(value); } void HtmlToSpan::InitLineHeight(const std::string& key, const std::string& value, StyleValues& values) { auto [unit, size] = GetUnitAndSize(value); if (!unit.empty()) { InitDimension(key, value, "line-height", values); return; } auto it = values.find("font"); if (it == values.end()) { return; } Font* font = Get(&it->second); if (font != nullptr) { size = size * font->fontSize->Value(); InitDimension(key, std::to_string(size) + unit, "line-height", values); } } bool HtmlToSpan::IsLetterSpacingAttr(const std::string& key) { return key.compare(0, strlen("letter-spacing"), "letter-spacing") == 0; } Color HtmlToSpan::ToSpanColor(const std::string& value) { std::smatch matches; std::string color = value; std::string tmp = value; tmp.erase(std::remove(tmp.begin(), tmp.end(), ' '), tmp.end()); auto regStr = "#[0-9A-Fa-f]{7,8}"; constexpr auto tmpLeastLength = 3; if (std::regex_match(tmp, matches, std::regex(regStr)) && tmp.length() >= tmpLeastLength) { auto rgb = tmp.substr(1); // remove last 2 character rgba -> argb rgb.erase(rgb.length() - 2, 2); auto alpha = tmp.substr(tmp.length() - 2); color = "#" + alpha + rgb; } return Color::FromString(color); } void HtmlToSpan::InitTextShadow( const std::string& key, const std::string& value, const std::string& index, StyleValues& values) { auto [ret, styleValue] = GetStyleValue>(index, values); if (!ret) { return; } std::vector* shadow = Get>(styleValue); if (shadow == nullptr) { return; } std::istringstream ss(value); std::string tmp; std::vector> shadows; while (std::getline(ss, tmp, ',')) { std::istringstream iss(tmp); std::string word; std::vector words; while (iss >> word) { words.emplace_back(word); } if (words.size() > FOUR_PARAM || words.size() < TWO_PARAM) { return; } shadows.emplace_back(words); } for (const auto &its : shadows) { std::vector attribute(FOUR_PARAM); uint8_t num = 0; for (const auto &it : its) { if (IsLength(it)) { attribute[num] = it; num++; continue; } attribute[FOURTH_PARAM] = it; } Shadow textShadow; InitShadow(textShadow, attribute); shadow->emplace_back(std::move(textShadow)); } } void HtmlToSpan::InitShadow(Shadow &textShadow, std::vector &attribute) { if (!attribute[FIRST_PARAM].empty()) { textShadow.SetOffsetX(FromString(attribute[FIRST_PARAM]).Value()); } if (!attribute[SECOND_PARAM].empty()) { textShadow.SetOffsetY(FromString(attribute[SECOND_PARAM]).Value()); } if (!attribute[THIRD_PARAM].empty()) { textShadow.SetBlurRadius(FromString(attribute[THIRD_PARAM]).Value()); } if (!attribute[FOURTH_PARAM].empty()) { textShadow.SetColor(ToSpanColor(attribute[FOURTH_PARAM])); } } bool HtmlToSpan::IsLength(const std::string& str) { return !str.empty() && (std::all_of(str.begin(), str.end(), ::isdigit) || str.find("px") != std::string::npos); } bool HtmlToSpan::IsTextShadowAttr(const std::string& key) { return key.compare(0, strlen("text-shadow"), "text-shadow") == 0; } bool HtmlToSpan::IsTextIndentAttr(const std::string& key) { return key.compare(0, strlen("text-indent"), "text-indent") == 0; } bool HtmlToSpan::IsLineHeightAttr(const std::string& key) { return key.compare(0, strlen("line-height"), "line-height") == 0; } bool HtmlToSpan::IsPaddingAttr(const std::string& key) { if (key == "padding" || key == "padding-top" || key == "padding-right" || key == "padding-bottom" || key == "padding-left") { return true; } return false; } bool HtmlToSpan::IsMarginAttr(const std::string& key) { if (key == "margin" || key == "margin-top" || key == "margin-right" || key == "margin-bottom" || key == "margin-left") { return true; } return false; } bool HtmlToSpan::IsBorderAttr(const std::string& key) { if (key == "border-radius" || key == "border-top-left-radius" || key == "border-top-right-radius" || key == "border-bottom-right-radius" || key == "border-bottom-left-radius") { return true; } return false; } void HtmlToSpan::SetPaddingOption(const std::string& key, const std::string& value, ImageSpanOptions& options) { if (!options.imageAttribute->paddingProp) { options.imageAttribute->paddingProp = std::make_optional(); } auto& paddings = options.imageAttribute->paddingProp; if (key == "padding") { std::istringstream ss(value); std::string word; std::vector words; while (ss >> word) { words.push_back(word); } size_t size = words.size(); if (size == ONE_PARAM) { paddings->top = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->right = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->bottom = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->left = NG::CalcLength(FromString(words[TOP_PARAM])); } else if (size == TWO_PARAM) { paddings->top = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->right = NG::CalcLength(FromString(words[RIGHT_PARAM])); paddings->bottom = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->left = NG::CalcLength(FromString(words[RIGHT_PARAM])); } else if (size == THREE_PARAM) { paddings->top = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->right = NG::CalcLength(FromString(words[RIGHT_PARAM])); paddings->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM])); paddings->left = NG::CalcLength(FromString(words[RIGHT_PARAM])); } else if (size == FOUR_PARAM) { paddings->top = NG::CalcLength(FromString(words[TOP_PARAM])); paddings->right = NG::CalcLength(FromString(words[RIGHT_PARAM])); paddings->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM])); paddings->left = NG::CalcLength(FromString(words[LEFT_PARAM])); } } else if (key == "padding-top") { paddings->top = NG::CalcLength(FromString(value)); } else if (key == "padding-right") { paddings->right = NG::CalcLength(FromString(value)); } else if (key == "padding-bottom") { paddings->bottom = NG::CalcLength(FromString(value)); } else if (key == "padding-left") { paddings->left = NG::CalcLength(FromString(value)); } } void HtmlToSpan::SetMarginOption(const std::string& key, const std::string& value, ImageSpanOptions& options) { if (!options.imageAttribute->marginProp) { options.imageAttribute->marginProp = std::make_optional(); } auto& marginProp = options.imageAttribute->marginProp; if (key == "margin") { std::istringstream ss(value); std::string word; std::vector words; while (ss >> word) { words.push_back(word); } size_t size = words.size(); if (size == ONE_PARAM) { marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->right = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->bottom = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->left = NG::CalcLength(FromString(words[TOP_PARAM])); } else if (size == TWO_PARAM) { marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->right = NG::CalcLength(FromString(words[RIGHT_PARAM])); marginProp->bottom = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->left = NG::CalcLength(FromString(words[RIGHT_PARAM])); } else if (size == THREE_PARAM) { marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->right = NG::CalcLength(FromString(words[RIGHT_PARAM])); marginProp->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM])); marginProp->left = NG::CalcLength(FromString(words[RIGHT_PARAM])); } else if (size == FOUR_PARAM) { marginProp->top = NG::CalcLength(FromString(words[TOP_PARAM])); marginProp->right = NG::CalcLength(FromString(words[RIGHT_PARAM])); marginProp->bottom = NG::CalcLength(FromString(words[BOTTOM_PARAM])); marginProp->left = NG::CalcLength(FromString(words[LEFT_PARAM])); } } else if (key == "margin-top") { marginProp->top = NG::CalcLength(FromString(value)); } else if (key == "margin-right") { marginProp->right = NG::CalcLength(FromString(value)); } else if (key == "margin-bottom") { marginProp->bottom = NG::CalcLength(FromString(value)); } else if (key == "margin-left") { marginProp->left = NG::CalcLength(FromString(value)); } } void HtmlToSpan::SetBorderOption(const std::string& key, const std::string& value, ImageSpanOptions& options) { if (!options.imageAttribute->borderRadius) { options.imageAttribute->borderRadius = std::make_optional(); options.imageAttribute->borderRadius->multiValued = true; } auto& borderRadius = options.imageAttribute->borderRadius; if (key == "border-radius") { std::istringstream ss(value); std::string word; std::vector words; while (ss >> word) { words.push_back(word); } size_t size = words.size(); if (size == ONE_PARAM) { borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]); borderRadius->radiusTopRight = FromString(words[TOP_PARAM]); borderRadius->radiusBottomRight = FromString(words[TOP_PARAM]); borderRadius->radiusBottomLeft = FromString(words[TOP_PARAM]); } else if (size == TWO_PARAM) { borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]); borderRadius->radiusTopRight = FromString(words[RIGHT_PARAM]); borderRadius->radiusBottomRight = FromString(words[TOP_PARAM]); borderRadius->radiusBottomLeft = FromString(words[RIGHT_PARAM]); } else if (size == THREE_PARAM) { borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]); borderRadius->radiusTopRight = FromString(words[RIGHT_PARAM]); borderRadius->radiusBottomRight = FromString(words[BOTTOM_PARAM]); borderRadius->radiusBottomLeft = FromString(words[RIGHT_PARAM]); } else if (size == FOUR_PARAM) { borderRadius->radiusTopLeft = FromString(words[TOP_PARAM]); borderRadius->radiusTopRight = FromString(words[RIGHT_PARAM]); borderRadius->radiusBottomRight = FromString(words[BOTTOM_PARAM]); borderRadius->radiusBottomLeft = FromString(words[LEFT_PARAM]); } } else if (key == "border-top-left-radius") { borderRadius->radiusTopLeft = FromString(value); } else if (key == "border-top-right-radius") { borderRadius->radiusTopRight = FromString(value); } else if (key == "border-bottom-right-radius") { borderRadius->radiusBottomRight = FromString(value); } else if (key == "border-bottom-left-radius") { borderRadius->radiusBottomLeft = FromString(value); } } void HtmlToSpan::HandleImgSpanOption(const Styles& styleMap, ImageSpanOptions& options) { for (const auto& [key, value] : styleMap) { auto trimVal = StringUtils::TrimStr(value); if (IsPaddingAttr(key)) { SetPaddingOption(key, trimVal, options); } else if (IsMarginAttr(key)) { SetMarginOption(key, trimVal, options); } else if (IsBorderAttr(key)) { SetBorderOption(key, trimVal, options); } else if (key == "object-fit") { options.imageAttribute->objectFit = ConvertStrToFit(trimVal); } else if (key == "vertical-align") { options.imageAttribute->verticalAlign = StringToVerticalAlign(trimVal); } else if (key == "width" || key == "height") { HandleImageSize(key, trimVal, options); } else if (key == "sync-load") { options.imageAttribute->syncLoad = V2::ConvertStringToBool(trimVal); } } } void HtmlToSpan::HandleImagePixelMap(const std::string& src, ImageSpanOptions& option) { if (src.empty()) { return; } NG::LoadNotifier loadNotifier(nullptr, nullptr, nullptr); RefPtr ctx = AceType::MakeRefPtr(ImageSourceInfo(src), std::move(loadNotifier), true); CHECK_NULL_VOID(ctx); ctx->LoadImageData(); ctx->MakeCanvasImageIfNeed(ctx->GetImageSize(), true, ImageFit::NONE); auto image = ctx->MoveCanvasImage(); if (image != nullptr) { auto pixelMap = image->GetPixelMap(); if (pixelMap) { option.imagePixelMap = pixelMap; } } if (option.imagePixelMap.has_value() && option.imagePixelMap.value() != nullptr) { auto pixel = option.imagePixelMap.value(); LOGI("img height: %{public}d, width: %{public}d, size:%{public}d", pixel->GetHeight(), pixel->GetWidth(), pixel->GetByteCount()); } else { option.image = src; } } void HtmlToSpan::HandleImageSize(const std::string& key, const std::string& value, ImageSpanOptions& options) { if (!options.imageAttribute->size) { options.imageAttribute->size = std::make_optional(); } if (key == "width") { options.imageAttribute->size->width = FromString(value); } else { options.imageAttribute->size->height = FromString(value); } } void HtmlToSpan::MakeImageSpanOptions(const std::string& key, const std::string& value, ImageSpanOptions& options) { if (key == "src") { options.image = value; HandleImagePixelMap(value, options); } else if (key == "style") { Styles styleMap = ParseStyleAttr(value); HandleImgSpanOption(styleMap, options); } else if (key == "width" || key == "height") { HandleImageSize(key, value, options); } } TextAlign HtmlToSpan::StringToTextAlign(const std::string& value) { if (value == "left") { return TextAlign::LEFT; } if (value == "right") { return TextAlign::RIGHT; } if (value == "center") { return TextAlign::CENTER; } if (value == "justify") { return TextAlign::JUSTIFY; } return TextAlign::LEFT; } TextVerticalAlign HtmlToSpan::StringToTextVerticalAlign(const std::string& value) { if (value == "baseline") { return TextVerticalAlign::BASELINE; } if (value == "bottom") { return TextVerticalAlign::BOTTOM; } if (value == "middle") { return TextVerticalAlign::CENTER; } if (value == "top") { return TextVerticalAlign::TOP; } return TextVerticalAlign::BASELINE; } WordBreak HtmlToSpan::StringToWordBreak(const std::string& value) { if (value == "normal") { return WordBreak::NORMAL; } if (value == "break-all") { return WordBreak::BREAK_ALL; } if (value == "keep-all") { return WordBreak::BREAK_WORD; } return WordBreak::NORMAL; } TextOverflow HtmlToSpan::StringToTextOverflow(const std::string& value) { if (value == "clip") { return TextOverflow::CLIP; } if (value == "ellipsis") { return TextOverflow::ELLIPSIS; } return TextOverflow::NONE; } void HtmlToSpan::ToDefalutSpan(xmlNodePtr node, size_t len, size_t& pos, std::vector& spanInfos) { if (len == 0) { return; } SpanInfo info; info.type = HtmlType::DEFAULT; info.start = pos; info.end = pos + len; spanInfos.emplace_back(std::move(info)); } template std::pair HtmlToSpan::GetStyleValue( const std::string& key, std::map& values) { auto it = values.find(key); if (it == values.end()) { StyleValue value = T(); it = values.emplace(key, value).first; } if (it == values.end()) { return std::make_pair(false, nullptr); } return std::make_pair(true, &it->second); } void HtmlToSpan::ToParagraphSpan(xmlNodePtr node, size_t len, size_t& pos, std::vector& spanInfos) { SpanInfo info; info.type = HtmlType::PARAGRAPH; info.start = pos; info.end = pos + len; xmlAttrPtr curNode = node->properties; if (curNode == nullptr) { SpanParagraphStyle style; info.values.emplace_back(style); } else { for (; curNode; curNode = curNode->next) { auto styles = ToTextSpanStyle(curNode); for (auto [key, value] : styles) { info.values.emplace_back(value); } } } spanInfos.emplace_back(std::move(info)); } std::pair HtmlToSpan::GetUnitAndSize(const std::string& str) { double value = 0.0; for (int32_t i = static_cast(str.length() - 1); i >= 0; --i) { if (str[i] >= '0' && str[i] <= '9') { value = StringUtils::StringToDouble(str.substr(0, i + 1)); auto subStr = str.substr(i + 1); return { subStr, value }; } } return { "", value }; } std::map HtmlToSpan::ToTextSpanStyle(xmlAttrPtr curNode) { auto attrContent = xmlGetProp(curNode->parent, curNode->name); if (attrContent == nullptr) { return {}; } std::string strStyle(reinterpret_cast(attrContent)); xmlFree(attrContent); Styles styleMap = ParseStyleAttr(strStyle); std::map styleValues; for (auto& [key, value] : styleMap) { auto trimVal = StringUtils::TrimStr(value); if (IsFontAttr(key)) { InitFont(key, trimVal, "font", styleValues); } else if (IsForegroundColorAttr(key)) { InitForegroundColor(key, trimVal, "font", styleValues); } else if (IsDecorationAttr(key)) { InitDecoration(key, trimVal, "decoration", styleValues); } else if (IsLetterSpacingAttr(key)) { InitDimension(key, trimVal, "letterSpacing", styleValues); } else if (IsTextShadowAttr(key)) { InitTextShadow(key, trimVal, "shadow", styleValues); } else if (IsLineHeightAttr(key)) { InitLineHeight(key, trimVal, styleValues); } else if (IsParagraphAttr(key)) { InitParagraph(key, trimVal, "paragrap", styleValues); } else if (IsBackgroundColorAttr(key)) { InitBackgroundColor(key, trimVal, "backgroundColor", styleValues); } } return styleValues; } void HtmlToSpan::AddStyleSpan(const std::string& element, SpanInfo& info) { std::map styles; if (element == "strong" || element == "b") { InitFont("font-weight", "bold", "font", styles); } else if (element == "sup") { InitFont("font-superscript", "superscript", "font", styles); } else if (element == "sub") { InitFont("font-superscript", "subscript", "font", styles); } else if (element == "del" || element == "s") { InitDecoration("text-decoration-line", "line-through", "decoration", styles); } else if (element == "u") { InitDecoration("text-decoration-line", "underline", "decoration", styles); } else if (element == "i" || element == "em") { InitFont("font-style", "italic", "font", styles); } for (auto [key, value] : styles) { info.values.emplace_back(value); } } void HtmlToSpan::ToTextSpan( const std::string& element, xmlNodePtr node, size_t len, size_t& pos, std::vector& spanInfos) { SpanInfo info; info.type = HtmlType::TEXT; info.start = pos; info.end = pos + len; xmlAttrPtr curNode = node->properties; for (; curNode; curNode = curNode->next) { auto styles = ToTextSpanStyle(curNode); for (auto [key, value] : styles) { info.values.emplace_back(value); } } if (!element.empty()) { AddStyleSpan(element, info); } if (info.values.empty()) { return; } spanInfos.emplace_back(std::move(info)); } void HtmlToSpan::ToAnchorSpan(xmlNodePtr node, size_t len, size_t& pos, std::vector& spanInfos) { SpanInfo info; info.type = HtmlType::ANCHOR; info.start = pos; info.end = pos + len; xmlAttrPtr curNode = node->properties; for (; curNode; curNode = curNode->next) { std::string attrName = reinterpret_cast(curNode->name); if (attrName == "href") { auto attrContent = xmlGetProp(curNode->parent, curNode->name); if (attrContent != nullptr) { std::string hrefValue = reinterpret_cast(attrContent); xmlFree(attrContent); info.values.emplace_back(hrefValue); } } if (attrName == "style") { auto styles = ToTextSpanStyle(curNode); for (auto [key, value] : styles) { info.values.emplace_back(value); } } } spanInfos.emplace_back(std::move(info)); } void HtmlToSpan::ToImageOptions(const std::map& styles, ImageSpanOptions& option) { option.imageAttribute = std::make_optional(); for (auto& [key, value] : styles) { MakeImageSpanOptions(key, value, option); } } void HtmlToSpan::ToImage(xmlNodePtr node, size_t len, size_t& pos, std::vector& spanInfos, bool isProcessImageOptions) { std::map styleMap; xmlAttrPtr curNode = node->properties; for (; curNode; curNode = curNode->next) { auto attrContent = xmlGetProp(curNode->parent, curNode->name); if (attrContent != nullptr) { styleMap[reinterpret_cast(curNode->name)] = reinterpret_cast(attrContent); xmlFree(attrContent); } } ImageSpanOptions option; if (isProcessImageOptions) { ToImageOptions(styleMap, option); } SpanInfo info; info.type = HtmlType::IMAGE; info.start = pos; info.end = pos + len; info.values.emplace_back(std::move(option)); spanInfos.emplace_back(std::move(info)); } void HtmlToSpan::ToSpan( xmlNodePtr curNode, size_t& pos, std::string& allContent, std::vector& spanInfos, bool isNeedLoadPixelMap) { size_t curNodeLen = 0; if (curNode->content) { std::string curNodeContent = reinterpret_cast(curNode->content); allContent += curNodeContent; curNodeLen = StringUtils::ToWstring(curNodeContent).length(); } std::string htmlTag = reinterpret_cast(curNode->name); size_t childPos = pos + curNodeLen; ParseHtmlToSpanInfo(curNode->children, childPos, allContent, spanInfos); if (curNode->type == XML_ELEMENT_NODE) { if (htmlTag == "p") { if (curNode->parent == nullptr || curNode->parent->type != XML_ELEMENT_NODE || xmlStrcmp(curNode->parent->name, (const xmlChar*)"span") != 0) { // The

contained in is discarded. It is not considered as a standard writing method. allContent += "\n"; childPos++; ToParagraphSpan(curNode, childPos - pos, pos, spanInfos); } } else if (htmlTag == "img") { childPos++; ToImage(curNode, childPos - pos, pos, spanInfos, isNeedLoadPixelMap); } else if (htmlTag == "a") { ToAnchorSpan(curNode, childPos - pos, pos, spanInfos); } else if (htmlTag == "br") { allContent += "\n"; childPos++; } else { ToTextSpan(htmlTag, curNode, childPos - pos, pos, spanInfos); } } pos = childPos; } void HtmlToSpan::ParseHtmlToSpanInfo( xmlNodePtr node, size_t& pos, std::string& allContent, std::vector& spanInfos, bool isNeedLoadPixelMap) { xmlNodePtr curNode = nullptr; for (curNode = node; curNode; curNode = curNode->next) { if (curNode->type == XML_ELEMENT_NODE || curNode->type == XML_TEXT_NODE) { ToSpan(curNode, pos, allContent, spanInfos, isNeedLoadPixelMap); } } } void HtmlToSpan::PrintSpanInfos(const std::vector& spanInfos) { for (auto& info : spanInfos) { LOGI("span type %{public}d start:%{public}zu end:%{public}zu, style size:%{public}zu", static_cast(info.type), info.start, info.end, info.values.size()); } } void HtmlToSpan::AfterProcSpanInfos(std::vector& spanInfos) { std::vector> paragraphPos; for (auto& info : spanInfos) { if (info.type == HtmlType::PARAGRAPH) { paragraphPos.push_back({ info.start, info.end }); } } for (auto& pos : paragraphPos) { for (auto& info : spanInfos) { if (info.type != HtmlType::PARAGRAPH && info.type != HtmlType::IMAGE && pos.second == info.end + 1) { info.end += 1; break; } } } } RefPtr HtmlToSpan::CreateSpan(size_t index, const SpanInfo& info, StyleValue& value) { if (index == static_cast(StyleIndex::STYLE_FONT)) { return MakeSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_DECORATION)) { return MakeDecorationSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_BASELINE)) { return MakeDimensionSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_LETTERSPACE)) { return MakeDimensionSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_LINEHEIGHT)) { return MakeDimensionSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_SHADOWS)) { return MakeSpan, TextShadowSpan>(info, value); } if (index == static_cast(StyleIndex::STYLE_PARAGRAPH)) { return MakeSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_BACKGROUND_COLOR)) { return MakeSpan(info, value); } if (index == static_cast(StyleIndex::STYLE_URL)) { return MakeSpan(info, value); } return nullptr; } template RefPtr HtmlToSpan::MakeSpan(const SpanInfo& info, StyleValue& value) { auto style = Get(&value); if (style != nullptr) { return AceType::MakeRefPtr

(*style, info.start, info.end); } return nullptr; } template RefPtr HtmlToSpan::MakeDimensionSpan(const SpanInfo& info, StyleValue& value) { auto style = Get(&value); if (style != nullptr) { return AceType::MakeRefPtr

(style->dimension, info.start, info.end); } return nullptr; } RefPtr HtmlToSpan::MakeDecorationSpan(const SpanInfo& info, StyleValue& value) { auto style = Get(&value); std::optional options = TextDecorationOptions(); if (style != nullptr) { // Enable multi-decoration line support by default options->enableMultiType = true; return AceType::MakeRefPtr( std::vector({style->decorationType}), style->color, style->decorationSytle, 1.0f, options, info.start, info.end); } return nullptr; } void HtmlToSpan::AddSpans(const SpanInfo& info, RefPtr mutableSpan) { for (auto value : info.values) { size_t index = value.index(); RefPtr span; if (index >= 0 && index < static_cast(StyleIndex::STYLE_MAX)) { span = CreateSpan(index, info, value); } if (span != nullptr) { mutableSpan->AddSpan(span, true, true, false); } } } void HtmlToSpan::AddImageSpans(const SpanInfo& info, RefPtr mutableSpan) { for (auto value : info.values) { auto style = Get(&value); if (style == nullptr) { continue; } auto span = AceType::MakeRefPtr(*style); mutableSpan->InsertSpanString(info.start, span); } } RefPtr HtmlToSpan::GenerateSpans( const std::string& allContent, const std::vector& spanInfos) { auto mutableSpan = AceType::MakeRefPtr(UtfUtils::Str8DebugToStr16(allContent)); if (spanInfos.empty()) { return mutableSpan; } for (int32_t i = 0; i < static_cast(spanInfos.size()); ++i) { auto info = spanInfos[i]; if (info.type == HtmlType::IMAGE) { AddImageSpans(info, mutableSpan); } } for (int32_t i = static_cast(spanInfos.size()) - 1; i >= 0; --i) { auto info = spanInfos[i]; if (info.type == HtmlType::PARAGRAPH) { AddSpans(info, mutableSpan); } else if (info.type != HtmlType::IMAGE) { AddSpans(info, mutableSpan); } } return mutableSpan; } RefPtr HtmlToSpan::ToSpanString(const std::string& html, const bool isNeedLoadPixelMap) { htmlDocPtr doc = htmlReadMemory(html.c_str(), html.length(), nullptr, "UTF-8", 0); if (doc == nullptr) { return nullptr; } auto docSharedPtr = std::shared_ptr(doc, [](htmlDocPtr doc) { xmlFreeDoc(doc); }); if (docSharedPtr == nullptr) { return nullptr; } xmlNode* root = xmlDocGetRootElement(docSharedPtr.get()); if (root == nullptr) { return nullptr; } size_t pos = 0; std::string content; std::vector spanInfos; ParseHtmlToSpanInfo(root, pos, content, spanInfos, isNeedLoadPixelMap); AfterProcSpanInfos(spanInfos); PrintSpanInfos(spanInfos); return GenerateSpans(content, spanInfos); } RefPtr HtmlUtils::FromHtml(const std::string& html) { HtmlToSpan hts; auto styledString = hts.ToSpanString(html); return styledString; } } // namespace OHOS::Ace