• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "span_to_html.h"
17 
18 #include <cstdint>
19 #include <string>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 
23 #include "base/image/file_uri_helper.h"
24 #include "base/image/image_packer.h"
25 #include "base/utils/macros.h"
26 #include "core/components_v2/inspector/utils.h"
27 #include "core/text/html_utils.h"
28 
29 namespace OHOS::Ace {
30 const constexpr char* CONVERT_PNG_FORMAT = "image/png";
31 const constexpr char* DEFAULT_SUFFIX = ".png";
32 const mode_t CHOWN_RW_UG = 0660;
33 const constexpr size_t COLOR_MIN_LENGHT = 3;
34 const constexpr char* TEMP_HTML_CONVERT_DATA_ROOT_PATH = "data/storage/el2/base/temp/htmlconvert";
OHOS_ACE_ConvertHmtlToSpanString(std::vector<uint8_t> & span,std::string & html)35 extern "C" ACE_FORCE_EXPORT int OHOS_ACE_ConvertHmtlToSpanString(std::vector<uint8_t>& span, std::string& html)
36 {
37     SpanToHtml convert;
38     html = convert.ToHtml(span);
39     return 0;
40 }
41 
42 using namespace OHOS::Ace::NG;
FontStyleToHtml(const std::optional<Ace::FontStyle> & value)43 std::string SpanToHtml::FontStyleToHtml(const std::optional<Ace::FontStyle>& value)
44 {
45     return ToHtmlStyleFormat(
46         "font-style", value.value_or(Ace::FontStyle::NORMAL) == Ace::FontStyle::NORMAL ? "normal" : "italic");
47 }
48 
FontSizeToHtml(const std::optional<Dimension> & value)49 std::string SpanToHtml::FontSizeToHtml(const std::optional<Dimension>& value)
50 {
51     return ToHtmlStyleFormat("font-size", DimensionToString(value.value_or(TEXT_DEFAULT_FONT_SIZE)));
52 }
53 
FontWeightToHtml(const std::optional<FontWeight> & value)54 std::string SpanToHtml::FontWeightToHtml(const std::optional<FontWeight>& value)
55 {
56     static const LinearEnumMapNode<FontWeight, std::string> table[] = {
57         { FontWeight::W100, "100" },
58         { FontWeight::W200, "200" },
59         { FontWeight::W300, "300" },
60         { FontWeight::W400, "400" },
61         { FontWeight::W500, "500" },
62         { FontWeight::W600, "600" },
63         { FontWeight::W700, "700" },
64         { FontWeight::W800, "800" },
65         { FontWeight::W900, "900" },
66         { FontWeight::BOLD, "bold" },
67         { FontWeight::NORMAL, "normal" },
68         { FontWeight::BOLDER, "bolder" },
69         { FontWeight::LIGHTER, "lighter" },
70         { FontWeight::MEDIUM, "medium" },
71         { FontWeight::REGULAR, "regular" },
72     };
73 
74     auto index = BinarySearchFindIndex(table, ArraySize(table), value.value_or(FontWeight::NORMAL));
75     return ToHtmlStyleFormat("font-weight", index < 0 ? "normal" : table[index].value);
76 }
77 
ToHtmlColor(std::string & color)78 void SpanToHtml::ToHtmlColor(std::string& color)
79 {
80     if (color.length() < COLOR_MIN_LENGHT) {
81         return;
82     }
83     // argb -> rgda
84     char second = color[1];
85     char third = color[2];
86     // earse 2 character after # and apped at end
87     color.erase(1, 2);
88     color.push_back(second);
89     color.push_back(third);
90 }
91 
ColorToHtml(const std::optional<Color> & value)92 std::string SpanToHtml::ColorToHtml(const std::optional<Color>& value)
93 {
94     auto color = value.value_or(Color::BLACK).ColorToString();
95     ToHtmlColor(color);
96     return ToHtmlStyleFormat("color", color);
97 }
98 
FontFamilyToHtml(const std::optional<std::vector<std::string>> & value)99 std::string SpanToHtml::FontFamilyToHtml(const std::optional<std::vector<std::string>>& value)
100 {
101     return ToHtmlStyleFormat("font-family", GetFontFamilyInJson(value));
102 }
103 
TextDecorationToHtml(TextDecoration decoration)104 std::string SpanToHtml::TextDecorationToHtml(TextDecoration decoration)
105 {
106     static const LinearEnumMapNode<TextDecoration, std::string> decorationTable[] = {
107         { TextDecoration::NONE, "none" },
108         { TextDecoration::UNDERLINE, "underline" },
109         { TextDecoration::OVERLINE, "overline" },
110         { TextDecoration::LINE_THROUGH, "line-through" },
111         { TextDecoration::INHERIT, "inherit" },
112     };
113 
114     auto index = BinarySearchFindIndex(decorationTable, ArraySize(decorationTable), decoration);
115     if (index < 0) {
116         return "";
117     }
118 
119     return ToHtmlStyleFormat("text-decoration-line", decorationTable[index].value);
120 }
121 
TextDecorationStyleToHtml(TextDecorationStyle decorationStyle)122 std::string SpanToHtml::TextDecorationStyleToHtml(TextDecorationStyle decorationStyle)
123 {
124     static const LinearEnumMapNode<TextDecorationStyle, std::string> table[] = {
125         { TextDecorationStyle::SOLID, "solid" },
126         { TextDecorationStyle::DOUBLE, "double" },
127         { TextDecorationStyle::DOTTED, "dotted" },
128         { TextDecorationStyle::DASHED, "dashed" },
129         { TextDecorationStyle::WAVY, "wavy" },
130     };
131 
132     auto index = BinarySearchFindIndex(table, ArraySize(table), decorationStyle);
133     if (index < 0) {
134         return "";
135     }
136 
137     return ToHtmlStyleFormat("text-decoration-style", table[index].value);
138 }
139 
DimensionToString(const Dimension & dimension)140 std::string SpanToHtml::DimensionToString(const Dimension& dimension)
141 {
142     return StringUtils::DoubleToString(dimension.ConvertToVp()).append("px");
143 }
144 
DimensionToStringWithoutUnit(const Dimension & dimension)145 std::string SpanToHtml::DimensionToStringWithoutUnit(const Dimension& dimension)
146 {
147     return StringUtils::DoubleToString(dimension.ConvertToVp());
148 }
149 
ToHtml(const std::string & key,const std::optional<Dimension> & dimension)150 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<Dimension>& dimension)
151 {
152     if (!dimension) {
153         return "";
154     }
155     auto& value = *dimension;
156     if (!value.IsValid()) {
157         return "";
158     }
159     return ToHtmlStyleFormat(key, DimensionToString(value));
160 }
161 
DeclarationToHtml(const NG::FontStyle & fontStyle)162 std::string SpanToHtml::DeclarationToHtml(const NG::FontStyle& fontStyle)
163 {
164     auto type = fontStyle.GetTextDecoration().value_or(TextDecoration::NONE);
165     if (type == TextDecoration::NONE) {
166         return "";
167     }
168     std::string html;
169     auto color = fontStyle.GetTextDecorationColor();
170     if (color) {
171         auto htmlColor = color->ColorToString();
172         ToHtmlColor(htmlColor);
173         html += ToHtmlStyleFormat("text-decoration-color", htmlColor);
174     }
175     html += TextDecorationToHtml(type);
176     auto style = fontStyle.GetTextDecorationStyle();
177     if (style) {
178         html += TextDecorationStyleToHtml(*style);
179     }
180 
181     return html;
182 }
183 
ToHtml(const std::optional<std::vector<Shadow>> & shadows)184 std::string SpanToHtml::ToHtml(const std::optional<std::vector<Shadow>>& shadows)
185 {
186     if (!shadows.has_value()) {
187         return "";
188     }
189 
190     if (shadows.value().empty()) {
191         return "";
192     }
193 
194     std::string style;
195     for (const auto& shadow : shadows.value()) {
196         if (!shadow.IsValid()) {
197             continue;
198         }
199 
200         auto htmlColor = shadow.GetColor().ColorToString();
201         ToHtmlColor(htmlColor);
202 
203         style += Dimension(shadow.GetOffset().GetX()).ToString() + " " +
204                  Dimension(shadow.GetOffset().GetY()).ToString() + " " + Dimension(shadow.GetBlurRadius()).ToString() +
205                  " " + htmlColor + ",";
206     }
207     style.pop_back();
208 
209     return ToHtmlStyleFormat("text-shadow", style);
210 }
211 
ToHtml(const std::string & key,const std::optional<CalcDimension> & dimesion)212 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<CalcDimension>& dimesion)
213 {
214     if (!dimesion) {
215         return "";
216     }
217 
218     return ToHtmlStyleFormat(key, DimensionToString(*dimesion));
219 }
220 
ToHtml(const std::string & key,bool syncLoad)221 std::string SpanToHtml::ToHtml(const std::string& key, bool syncLoad)
222 {
223     return ToHtmlStyleFormat(key, V2::ConvertBoolToString(syncLoad));
224 }
225 
ToHtmlImgSizeAttribute(const std::string & key,const std::optional<CalcDimension> & dimesion)226 std::string SpanToHtml::ToHtmlImgSizeAttribute(const std::string& key, const std::optional<CalcDimension>& dimesion)
227 {
228     if (!dimesion) {
229         return "";
230     }
231 
232     return ToHtmlAttributeFormat(key, DimensionToStringWithoutUnit(*dimesion));
233 }
234 
ToHtml(const std::optional<ImageSpanSize> & size)235 std::string SpanToHtml::ToHtml(const std::optional<ImageSpanSize>& size)
236 {
237     if (!size) {
238         return "";
239     }
240 
241     std::string style = ToHtmlImgSizeAttribute("width", size->width);
242     style += ToHtmlImgSizeAttribute("height", size->height);
243     return style;
244 }
245 
ToHtml(const std::optional<VerticalAlign> & verticalAlign)246 std::string SpanToHtml::ToHtml(const std::optional<VerticalAlign>& verticalAlign)
247 {
248     if (!verticalAlign) {
249         return "";
250     }
251 
252     static const LinearEnumMapNode<VerticalAlign, std::string> table[] = {
253         { VerticalAlign::TOP, "top" },
254         { VerticalAlign::CENTER, "center" },
255         { VerticalAlign::BOTTOM, "bottom" },
256         { VerticalAlign::BASELINE, "baseline" },
257         { VerticalAlign::NONE, "" },
258     };
259     auto iter = BinarySearchFindIndex(table, ArraySize(table), *verticalAlign);
260     if (iter < 0) {
261         return "";
262     }
263 
264     return ToHtmlStyleFormat("vertical-align", table[iter].value);
265 }
266 
ToHtml(const std::optional<ImageFit> & objectFit)267 std::string SpanToHtml::ToHtml(const std::optional<ImageFit>& objectFit)
268 {
269     if (!objectFit) {
270         return "";
271     }
272 
273     static const LinearEnumMapNode<ImageFit, std::string> table[] = {
274         { ImageFit::FILL, "fill" },
275         { ImageFit::CONTAIN, "contain" },
276         { ImageFit::COVER, "cover" },
277         { ImageFit::FITWIDTH, "none" },
278         { ImageFit::FITHEIGHT, "none" },
279         { ImageFit::NONE, "none" },
280         { ImageFit::SCALE_DOWN, "scale-down" },
281     };
282 
283     auto index = BinarySearchFindIndex(table, ArraySize(table), *objectFit);
284     if (index < 0) {
285         return "";
286     }
287 
288     return ToHtmlStyleFormat("object-fit", table[index].value);
289 }
290 
ToHtml(const std::string & key,const std::optional<OHOS::Ace::NG::MarginProperty> & prop)291 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<OHOS::Ace::NG::MarginProperty>& prop)
292 {
293     if (!prop) {
294         return "";
295     }
296 
297     if (prop->top == prop->right && prop->right == prop->bottom && prop->bottom == prop->left) {
298         if (!prop->top) {
299             return "";
300         }
301         return ToHtmlStyleFormat(key, DimensionToString(prop->top->GetDimension()));
302     }
303 
304     auto padding = prop->top.has_value() ? DimensionToString(prop->top->GetDimension()) : "0";
305     padding += " " + (prop->right.has_value() ? DimensionToString(prop->right->GetDimension()) : "0");
306     padding += " " + (prop->bottom.has_value() ? DimensionToString(prop->bottom->GetDimension()) : "0");
307     padding += " " + (prop->left.has_value() ? DimensionToString(prop->left->GetDimension()) : "0");
308 
309     return ToHtmlStyleFormat(key, padding);
310 }
311 
ToHtml(const std::optional<OHOS::Ace::NG::BorderRadiusProperty> & borderRadius)312 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::NG::BorderRadiusProperty>& borderRadius)
313 {
314     if (!borderRadius) {
315         return "";
316     }
317 
318     std::string radius;
319     if (borderRadius->radiusTopLeft) {
320         radius += ToHtmlStyleFormat("border-top-left-radius", DimensionToString(*borderRadius->radiusTopLeft));
321     }
322     if (borderRadius->radiusTopRight) {
323         radius += ToHtmlStyleFormat("border-top-right-radius", DimensionToString(*borderRadius->radiusTopRight));
324     }
325     if (borderRadius->radiusBottomRight) {
326         radius += ToHtmlStyleFormat("border-bottom-right-radius", DimensionToString(*borderRadius->radiusBottomRight));
327     }
328     if (borderRadius->radiusBottomLeft) {
329         radius += ToHtmlStyleFormat("border-bottom-left-radius", DimensionToString(*borderRadius->radiusBottomLeft));
330     }
331 
332     return radius;
333 }
334 
CreateDirectory(const std::string & path)335 bool SpanToHtml::CreateDirectory(const std::string& path)
336 {
337     if (access(path.c_str(), F_OK) == 0) {
338         return true;
339     }
340 
341     std::string::size_type index = 0;
342     do {
343         std::string subPath;
344         index = path.find('/', index + 1);
345         if (index == std::string::npos) {
346             subPath = path;
347         } else {
348             subPath = path.substr(0, index);
349         }
350 
351         if (access(subPath.c_str(), F_OK) != 0) {
352             if (mkdir(subPath.c_str(), (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) != 0) {
353                 return false;
354             }
355         }
356     } while (index != std::string::npos);
357 
358     if (access(path.c_str(), F_OK) == 0) {
359         return true;
360     }
361 
362     return false;
363 }
364 
WriteLocalFile(RefPtr<PixelMap> pixelMap,std::string & filePath,std::string & fileUri)365 int SpanToHtml::WriteLocalFile(RefPtr<PixelMap> pixelMap, std::string& filePath, std::string& fileUri)
366 {
367     std::string fileName;
368     auto pos = filePath.rfind("/");
369     if (pos == std::string::npos) {
370         fileName = filePath;
371     } else {
372         fileName = filePath.substr(pos + 1);
373     }
374 
375     if (fileName.empty()) {
376         int64_t now =
377             std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
378                 .count();
379         fileName = std::to_string(now) + DEFAULT_SUFFIX;
380     }
381 
382     CreateDirectory(TEMP_HTML_CONVERT_DATA_ROOT_PATH);
383     std::string localPath = TEMP_HTML_CONVERT_DATA_ROOT_PATH + std::string("/") + fileName;
384     RefPtr<ImagePacker> imagePacker = ImagePacker::Create();
385     if (imagePacker == nullptr) {
386         return -1;
387     }
388     PackOption option;
389     option.format = CONVERT_PNG_FORMAT;
390     imagePacker->StartPacking(localPath, option);
391     imagePacker->AddImage(*pixelMap);
392     int64_t packedSize = 0;
393     if (imagePacker->FinalizePacking(packedSize)) {
394         return -1;
395     }
396 
397     if (chmod(localPath.c_str(), CHOWN_RW_UG) != 0) {
398     }
399 
400     fileUri = "file:///" + localPath;
401     return 0;
402 }
403 
ImageToHtml(RefPtr<NG::SpanItem> item)404 std::string SpanToHtml::ImageToHtml(RefPtr<NG::SpanItem> item)
405 {
406     auto image = AceType::DynamicCast<ImageSpanItem>(item);
407     if (image == nullptr) {
408         return "";
409     }
410 
411     auto options = image->options;
412     if (!options.image && !options.imagePixelMap) {
413         return "";
414     }
415 
416     std::string urlName;
417     auto pixelMap = options.imagePixelMap.value_or(nullptr);
418     if (pixelMap) {
419         int ret = WriteLocalFile(pixelMap, *options.image, urlName);
420         LOGI("img write ret: %{public}d height: %{public}d, width: %{public}d, size:%{public}d", ret,
421             pixelMap->GetHeight(), pixelMap->GetWidth(), pixelMap->GetByteCount());
422     } else if (options.image.has_value()) {
423         urlName = options.image.value();
424     }
425     std::string imgHtml = "<img src=\"" + urlName + "\" ";
426     imgHtml += ToHtml(options.imageAttribute->size);
427     if (options.imageAttribute) {
428         imgHtml += " style=\"";
429         imgHtml += ToHtml(options.imageAttribute->verticalAlign);
430         imgHtml += ToHtml(options.imageAttribute->objectFit);
431         imgHtml += ToHtml("margin", options.imageAttribute->marginProp);
432         imgHtml += ToHtml(options.imageAttribute->borderRadius);
433         imgHtml += ToHtml("padding", options.imageAttribute->paddingProp);
434         if (!pixelMap) {
435             imgHtml += ToHtml("sync-load", options.imageAttribute->syncLoad);
436         }
437         imgHtml += "\"";
438     }
439 
440     imgHtml += ">";
441     return imgHtml;
442 }
443 
NormalStyleToHtml(const NG::FontStyle & fontStyle,const OHOS::Ace::NG::TextLineStyle & textLineStyle)444 std::string SpanToHtml::NormalStyleToHtml(
445     const NG::FontStyle& fontStyle, const OHOS::Ace::NG::TextLineStyle& textLineStyle)
446 {
447     std::string style = FontSizeToHtml(fontStyle.GetFontSize());
448     style += FontStyleToHtml(fontStyle.GetItalicFontStyle());
449     style += FontWeightToHtml(fontStyle.GetFontWeight());
450     style += ColorToHtml(fontStyle.GetTextColor());
451     style += FontFamilyToHtml(fontStyle.GetFontFamily());
452     style += DeclarationToHtml(fontStyle);
453     style += ToHtml("vertical-align", textLineStyle.GetBaselineOffset());
454     style += ToHtml("line-height", textLineStyle.GetLineHeight());
455     style += ToHtml("letter-spacing", fontStyle.GetLetterSpacing());
456     style += ToHtml(fontStyle.GetTextShadow());
457     if (style.empty()) {
458         return "";
459     }
460     return "style=\"" + style + "\"";
461 }
462 
ToHtml(const std::optional<OHOS::Ace::TextAlign> & object)463 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextAlign>& object)
464 {
465     if (!object.has_value()) {
466         return "";
467     }
468 
469     static const LinearEnumMapNode<TextAlign, std::string> table[] = {
470         { TextAlign::START, "start" },
471         { TextAlign::CENTER, "center" },
472         { TextAlign::END, "end" },
473         { TextAlign::JUSTIFY, "justify" },
474     };
475     auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
476     if (index < 0) {
477         return "";
478     }
479 
480     return ToHtmlStyleFormat("text-align", table[index].value);
481 }
482 
ToHtml(const std::optional<OHOS::Ace::WordBreak> & object)483 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::WordBreak>& object)
484 {
485     if (!object.has_value()) {
486         return "";
487     }
488 
489     // no keep_all
490     static const LinearEnumMapNode<WordBreak, std::string> table[] = {
491         { WordBreak::NORMAL, "normal" },
492         { WordBreak::BREAK_ALL, "break_all" },
493         { WordBreak::BREAK_WORD, "break_word" },
494     };
495     auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
496     if (index < 0) {
497         return "";
498     }
499 
500     return ToHtmlStyleFormat("word-break", table[index].value);
501 }
502 
ToHtml(const std::optional<OHOS::Ace::TextOverflow> & object)503 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextOverflow>& object)
504 {
505     if (!object.has_value()) {
506         return "";
507     }
508 
509     static const LinearEnumMapNode<TextOverflow, std::string> table[] = {
510         { TextOverflow::CLIP, "clip" },
511         { TextOverflow::ELLIPSIS, "ellipsis" },
512         { TextOverflow::MARQUEE, "marquee" },
513     };
514     auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
515     if (index < 0) {
516         return "";
517     }
518 
519     return ToHtmlStyleFormat("text-overflow", table[index].value);
520 }
521 
LeadingMarginToHtml(const OHOS::Ace::NG::TextLineStyle & style)522 std::string SpanToHtml::LeadingMarginToHtml(const OHOS::Ace::NG::TextLineStyle& style)
523 {
524     auto object = style.GetLeadingMargin();
525     if (!object) {
526         return "";
527     }
528 
529     if (!object.has_value()) {
530         return "";
531     }
532 
533     return "";
534 }
535 
ParagraphStyleToHtml(const OHOS::Ace::NG::TextLineStyle & textLineStyle)536 std::string SpanToHtml::ParagraphStyleToHtml(const OHOS::Ace::NG::TextLineStyle& textLineStyle)
537 {
538     auto details = ToHtml(textLineStyle.GetTextAlign());
539     details += ToHtml("text-indent", textLineStyle.GetTextIndent());
540     details += ToHtml(textLineStyle.GetWordBreak());
541     details += ToHtml(textLineStyle.GetTextOverflow());
542     if (details.empty()) {
543         return "";
544     }
545     return "style=\"" + details + "\"";
546 }
547 
ToHtml(const SpanString & spanString)548 std::string SpanToHtml::ToHtml(const SpanString& spanString)
549 {
550     auto items = spanString.GetSpanItems();
551     bool newLine = true;
552     size_t paragrapStart = 0;
553     std::string out = "<div >";
554     for (const auto& item : items) {
555         auto paragraphStyle = ParagraphStyleToHtml(*item->textLineStyle);
556         if (newLine && !paragraphStyle.empty()) {
557             out += "<p " + paragraphStyle + ">";
558             newLine = false;
559         }
560         if (item->spanItemType == OHOS::Ace::NG::SpanItemType::NORMAL) {
561             if (paragrapStart == 0) {
562                 paragrapStart = out.length();
563             }
564             out += "<span " + NormalStyleToHtml(*item->fontStyle, *item->textLineStyle) + ">";
565             auto content = item->GetSpanContent();
566             auto wContent = StringUtils::ToWstring(content);
567             if (wContent.back() == L'\n') {
568                 if (newLine) {
569                     out.insert(paragrapStart, "<p>");
570                     paragrapStart = 0;
571                 }
572                 content.pop_back();
573                 out += content + "</span>";
574                 out += "</p>";
575                 newLine = true;
576             } else {
577                 out += content + "</span>";
578             }
579         } else if (item->spanItemType == OHOS::Ace::NG::SpanItemType::IMAGE) {
580             out += ImageToHtml(item);
581         }
582     }
583 
584     if (!newLine) {
585         out += "</p>";
586     }
587 
588     out += "</div>";
589     return out;
590 }
591 
ToHtml(std::vector<uint8_t> & values)592 std::string SpanToHtml::ToHtml(std::vector<uint8_t>& values)
593 {
594     auto spanString = SpanString::DecodeTlv(values);
595     return ToHtml(*spanString);
596 }
597 
ToHtml(const SpanString * str)598 std::string HtmlUtils::ToHtml(const SpanString* str)
599 {
600     SpanToHtml sth;
601     const std::string html = sth.ToHtml(*str);
602     return html;
603 }
604 } // namespace OHOS::Ace