* 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 <sstream>
#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<std::string> ParseFontFamily(const std::string& fontFamily)
{
std::vector<std::string> 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<TextDecoration> StringToTextDecoration(const std::string& textDecoration)
{
std::istringstream ss(textDecoration);
std::string tmp;
std::vector<std::string> decorations;
while (std::getline(ss, tmp, ' ')) {
decorations.emplace_back(tmp);
}
std::vector<TextDecoration> 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<class T>
T* HtmlToSpan::Get(StyleValue* styleValue) const
{
auto v = std::get_if<T>(styleValue);
if (v == nullptr) {
return nullptr;
}
return static_cast<T*>(v);
}
Dimension HtmlToSpan::FromString(const std::string& str)
{
static const int32_t PERCENT_UNIT = 100;
static const std::unordered_map<std::string, DimensionUnit> 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<int32_t>(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<int32_t>(str.length()));
auto subStr = str.substr(startIndex);
if (subStr == "pt") {
value = static_cast<int>(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<Font>(index, values);
if (!ret) {
return;
}
Font* font = Get<Font>(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") {
} 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<TextBackgroundStyle>(index, values);
if (!ret) {
return;
}
TextBackgroundStyle* style = Get<TextBackgroundStyle>(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<Font>(index, values);
if (!ret) {
return;
}
Font* font = Get<Font>(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<SpanParagraphStyle>(index, values);
if (!ret) {
return;
}
SpanParagraphStyle* style = Get<SpanParagraphStyle>(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<DecorationSpanParam>(index, values);
if (!ret) {
return;
}
DecorationSpanParam* decoration = Get<DecorationSpanParam>(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") {
} else if (key == "text-decoration") {
std::istringstream ss1(value);
std::string word;
std::vector<std::string> 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<class T>
void HtmlToSpan::InitDimension(
const std::string& key, const std::string& value, const std::string& index, StyleValues& values)
{
(void)key;
if (value.compare(0, strlen("normal"), "normal") == 0) {
return;
}
auto [ret, styleValue] = GetStyleValue<T>(index, values);
if (!ret) {
return;
}
T* obj = Get<T>(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<LineHeightSpanSparam>(key, value, "line-height", values);
return;
}
auto it = values.find("font");
if (it == values.end()) {
return;
}
Font* font = Get<Font>(&it->second);
if (font != nullptr) {
size = size * font->fontSize->Value();
InitDimension<LineHeightSpanSparam>(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);
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)
{
(void)key;
auto [ret, styleValue] = GetStyleValue<std::vector<Shadow>>(index, values);
if (!ret) {
return;
}
std::vector<Shadow>* shadow = Get<std::vector<Shadow>>(styleValue);
if (shadow == nullptr) {
return;
}
std::istringstream ss(value);
std::string tmp;
std::vector<std::vector<std::string>> shadows;
while (std::getline(ss, tmp, ',')) {
std::istringstream iss(tmp);
std::string word;
std::vector<std::string> 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<std::string> 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<std::string> &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<NG::PaddingProperty>();
}
auto& paddings = options.imageAttribute->paddingProp;
if (key == "padding") {
std::istringstream ss(value);
std::string word;
std::vector<std::string> 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<NG::MarginProperty>();
}
auto& marginProp = options.imageAttribute->marginProp;
if (key == "margin") {
std::istringstream ss(value);
std::string word;
std::vector<std::string> 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<NG::BorderRadiusProperty>();
options.imageAttribute->borderRadius->multiValued = true;
}
auto& borderRadius = options.imageAttribute->borderRadius;
if (key == "border-radius") {
std::istringstream ss(value);
std::string word;
std::vector<std::string> 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<NG::ImageLoadingContext> ctx =
AceType::MakeRefPtr<NG::ImageLoadingContext>(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<ImageSpanSize>();
}
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;
}
template<class T>
std::pair<bool, HtmlToSpan::StyleValue*> HtmlToSpan::GetStyleValue(
const std::string& key, std::map<std::string, StyleValue>& 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<SpanInfo>& 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<std::string, double> HtmlToSpan::GetUnitAndSize(const std::string& str)
{
double value = 0.0;
for (int32_t i = static_cast<int32_t>(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<std::string, HtmlToSpan::StyleValue> HtmlToSpan::ToTextSpanStyle(xmlAttrPtr curNode)
{
auto attrContent = xmlGetProp(curNode->parent, curNode->name);
if (attrContent == nullptr) {
return {};
}
std::string strStyle(reinterpret_cast<const char*>(attrContent));
xmlFree(attrContent);
Styles styleMap = ParseStyleAttr(strStyle);
std::map<std::string, StyleValue> 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<LetterSpacingSpanParam>(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<std::string, StyleValue> 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<SpanInfo>& 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<SpanInfo>& 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<const char*>(curNode->name);
if (attrName == "href") {
auto attrContent = xmlGetProp(curNode->parent, curNode->name);
if (attrContent != nullptr) {
std::string hrefValue = reinterpret_cast<const char*>(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<std::string, std::string>& styles, ImageSpanOptions& option)
{
option.imageAttribute = std::make_optional<ImageSpanAttribute>();
for (auto& [key, value] : styles) {
MakeImageSpanOptions(key, value, option);
}
}
void HtmlToSpan::ToImage(xmlNodePtr node, size_t len, size_t& pos, std::vector<SpanInfo>& spanInfos,
bool isProcessImageOptions)
{
std::map<std::string, std::string> styleMap;
xmlAttrPtr curNode = node->properties;
for (; curNode; curNode = curNode->next) {
auto attrContent = xmlGetProp(curNode->parent, curNode->name);
if (attrContent != nullptr) {
styleMap[reinterpret_cast<const char*>(curNode->name)] = reinterpret_cast<const char*>(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<SpanInfo>& spanInfos,
bool isNeedLoadPixelMap)
{
size_t curNodeLen = 0;
if (curNode->content) {
std::string curNodeContent = reinterpret_cast<const char*>(curNode->content);
allContent += curNodeContent;
curNodeLen = StringUtils::ToWstring(curNodeContent).length();
}
std::string htmlTag = reinterpret_cast<const char*>(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) {
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<SpanInfo>& 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<SpanInfo>& spanInfos)
{
for (auto& info : spanInfos) {
LOGI("span type %{public}d start:%{public}zu end:%{public}zu, style size:%{public}zu",
static_cast<int>(info.type), info.start, info.end, info.values.size());
}
}
void HtmlToSpan::AfterProcSpanInfos(std::vector<SpanInfo>& spanInfos)
{
std::vector<std::pair<size_t, size_t>> 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<SpanBase> HtmlToSpan::CreateSpan(size_t index, const SpanInfo& info, StyleValue& value)
{
if (index == static_cast<uint32_t>(StyleIndex::STYLE_FONT)) {
return MakeSpan<Font, FontSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_DECORATION)) {
return MakeDecorationSpan(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_BASELINE)) {
return MakeDimensionSpan<BaseLineSpanParam, BaselineOffsetSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_LETTERSPACE)) {
return MakeDimensionSpan<LetterSpacingSpanParam, LetterSpacingSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_LINEHEIGHT)) {
return MakeDimensionSpan<LineHeightSpanSparam, LineHeightSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_SHADOWS)) {
return MakeSpan<std::vector<Shadow>, TextShadowSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_PARAGRAPH)) {
return MakeSpan<SpanParagraphStyle, ParagraphStyleSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_BACKGROUND_COLOR)) {
return MakeSpan<TextBackgroundStyle, BackgroundColorSpan>(info, value);
}
if (index == static_cast<uint32_t>(StyleIndex::STYLE_URL)) {
return MakeSpan<std::string, UrlSpan>(info, value);
}
return nullptr;
}
template<class T, class P>
RefPtr<SpanBase> HtmlToSpan::MakeSpan(const SpanInfo& info, StyleValue& value)
{
auto style = Get<T>(&value);
if (style != nullptr) {
return AceType::MakeRefPtr<P>(*style, info.start, info.end);
}
return nullptr;
}
template<class T, class P>
RefPtr<SpanBase> HtmlToSpan::MakeDimensionSpan(const SpanInfo& info, StyleValue& value)
{
auto style = Get<T>(&value);
if (style != nullptr) {
return AceType::MakeRefPtr<P>(style->dimension, info.start, info.end);
}
return nullptr;
}
RefPtr<SpanBase> HtmlToSpan::MakeDecorationSpan(const SpanInfo& info, StyleValue& value)
{
auto style = Get<DecorationSpanParam>(&value);
std::optional<TextDecorationOptions> options = TextDecorationOptions();
if (style != nullptr) {
options->enableMultiType = true;
return AceType::MakeRefPtr<DecorationSpan>(
std::vector<TextDecoration>({style->decorationType}), style->color,
style->decorationSytle, 1.0f, options, info.start, info.end, nullptr);
}
return nullptr;
}
void HtmlToSpan::AddSpans(const SpanInfo& info, RefPtr<MutableSpanString> mutableSpan)
{
for (auto value : info.values) {
size_t index = value.index();
RefPtr<SpanBase> span;
if (index >= 0 && index < static_cast<size_t>(StyleIndex::STYLE_MAX)) {
span = CreateSpan(index, info, value);
}
if (span != nullptr) {
mutableSpan->AddSpan(span, true, true, false);
}
}
}
void HtmlToSpan::AddImageSpans(const SpanInfo& info, RefPtr<MutableSpanString> mutableSpan)
{
for (auto value : info.values) {
auto style = Get<ImageSpanOptions>(&value);
if (style == nullptr) {
continue;
}
auto span = AceType::MakeRefPtr<MutableSpanString>(*style);
mutableSpan->InsertSpanString(info.start, span);
}
}
RefPtr<MutableSpanString> HtmlToSpan::GenerateSpans(
const std::string& allContent, const std::vector<SpanInfo>& spanInfos)
{
auto mutableSpan = AceType::MakeRefPtr<MutableSpanString>(UtfUtils::Str8DebugToStr16(allContent));
if (spanInfos.empty()) {
return mutableSpan;
}
for (int32_t i = 0; i < static_cast<int32_t>(spanInfos.size()); ++i) {
auto info = spanInfos[i];
if (info.type == HtmlType::IMAGE) {
AddImageSpans(info, mutableSpan);
}
}
for (int32_t i = static_cast<int32_t>(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<MutableSpanString> 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<xmlDoc>(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<SpanInfo> spanInfos;
ParseHtmlToSpanInfo(root, pos, content, spanInfos, isNeedLoadPixelMap);
AfterProcSpanInfos(spanInfos);
PrintSpanInfos(spanInfos);
return GenerateSpans(content, spanInfos);
}
RefPtr<MutableSpanString> HtmlUtils::FromHtml(const std::string& html)
{
HtmlToSpan hts;
auto styledString = hts.ToSpanString(html);
return styledString;
}
}