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 <sys/stat.h>
19 #include <unistd.h>
20
21 #include "base/image/image_packer.h"
22 #include "core/text/html_utils.h"
23
24 namespace OHOS::Ace {
25 const constexpr char* CONVERT_PNG_FORMAT = "image/png";
26 const constexpr char* DEFAULT_SUFFIX = ".png";
27 const mode_t CHOWN_RW_UG = 0660;
28 const constexpr size_t COLOR_MIN_LENGHT = 3;
29 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)30 extern "C" ACE_FORCE_EXPORT int OHOS_ACE_ConvertHmtlToSpanString(std::vector<uint8_t>& span, std::string& html)
31 {
32 SpanToHtml convert;
33 html = convert.ToHtml(span);
34 return 0;
35 }
36
37 using namespace OHOS::Ace::NG;
FontStyleToHtml(const std::optional<Ace::FontStyle> & value)38 std::string SpanToHtml::FontStyleToHtml(const std::optional<Ace::FontStyle>& value)
39 {
40 return ToHtmlStyleFormat(
41 "font-style", value.value_or(Ace::FontStyle::NORMAL) == Ace::FontStyle::NORMAL ? "normal" : "italic");
42 }
43
FontSizeToHtml(const std::optional<Dimension> & value)44 std::string SpanToHtml::FontSizeToHtml(const std::optional<Dimension>& value)
45 {
46 return ToHtmlStyleFormat("font-size", DimensionToString(value.value_or(TEXT_DEFAULT_FONT_SIZE)));
47 }
48
FontWeightToHtml(const std::optional<FontWeight> & value)49 std::string SpanToHtml::FontWeightToHtml(const std::optional<FontWeight>& value)
50 {
51 static const LinearEnumMapNode<FontWeight, std::string> table[] = {
52 { FontWeight::W100, "100" },
53 { FontWeight::W200, "200" },
54 { FontWeight::W300, "300" },
55 { FontWeight::W400, "400" },
56 { FontWeight::W500, "500" },
57 { FontWeight::W600, "600" },
58 { FontWeight::W700, "700" },
59 { FontWeight::W800, "800" },
60 { FontWeight::W900, "900" },
61 { FontWeight::BOLD, "bold" },
62 { FontWeight::NORMAL, "normal" },
63 { FontWeight::BOLDER, "bolder" },
64 { FontWeight::LIGHTER, "lighter" },
65 { FontWeight::MEDIUM, "medium" },
66 { FontWeight::REGULAR, "regular" },
67 };
68
69 auto index = BinarySearchFindIndex(table, ArraySize(table), value.value_or(FontWeight::NORMAL));
70 return ToHtmlStyleFormat("font-weight", index < 0 ? "normal" : table[index].value);
71 }
72
StrokeWidthToHtml(const std::optional<Dimension> & value)73 std::string SpanToHtml::StrokeWidthToHtml(const std::optional<Dimension>& value)
74 {
75 return ToHtmlStyleFormat("stroke-width", DimensionToString(value.value_or(TEXT_DEFAULT_STROKE_WIDTH)));
76 }
77
StrokeColorToHtml(const std::optional<Color> & value)78 std::string SpanToHtml::StrokeColorToHtml(const std::optional<Color>& value)
79 {
80 auto color = value.value_or(Color::BLACK).ColorToString();
81 ToHtmlColor(color);
82 return ToHtmlStyleFormat("stroke-color", color);
83 }
84
ToHtmlColor(std::string & color)85 void SpanToHtml::ToHtmlColor(std::string& color)
86 {
87 if (color.length() < COLOR_MIN_LENGHT) {
88 return;
89 }
90 // argb -> rgda
91 char second = color[1];
92 char third = color[2];
93 // earse 2 character after # and apped at end
94 color.erase(1, 2);
95 color.push_back(second);
96 color.push_back(third);
97 }
98
ColorToHtml(const std::optional<Color> & value)99 std::string SpanToHtml::ColorToHtml(const std::optional<Color>& value)
100 {
101 auto color = value.value_or(Color::BLACK).ColorToString();
102 ToHtmlColor(color);
103 return ToHtmlStyleFormat("color", color);
104 }
105
BackgroundColorToHtml(const std::optional<TextBackgroundStyle> & value)106 std::string SpanToHtml::BackgroundColorToHtml(const std::optional<TextBackgroundStyle>& value)
107 {
108 if (!value.has_value()) {
109 return "";
110 }
111
112 const auto& backgroundStyle = value.value();
113 if (!backgroundStyle.backgroundColor.has_value()) {
114 return "";
115 }
116
117 auto color = backgroundStyle.backgroundColor.value().ColorToString();
118 ToHtmlColor(color);
119 return ToHtmlStyleFormat("background-color", color);
120 }
121
FontFamilyToHtml(const std::optional<std::vector<std::string>> & value)122 std::string SpanToHtml::FontFamilyToHtml(const std::optional<std::vector<std::string>>& value)
123 {
124 return ToHtmlStyleFormat("font-family", GetFontFamilyInJson(value));
125 }
126
FontSuperscriptToHtml(const std::optional<SuperscriptStyle> & value)127 std::string SpanToHtml::FontSuperscriptToHtml(const std::optional<SuperscriptStyle>& value)
128 {
129 static const LinearEnumMapNode<SuperscriptStyle, std::string> table[] = {
130 { SuperscriptStyle::SUPERSCRIPT, "superscript" },
131 { SuperscriptStyle::SUBSCRIPT, "subscript" },
132 { SuperscriptStyle::NORMAL, "normal" },
133 };
134
135 auto index = BinarySearchFindIndex(table, ArraySize(table), value.value_or(SuperscriptStyle::NORMAL));
136 return ToHtmlStyleFormat("font-superscript", index < 0 ? "normal" : table[index].value);
137 }
138
TextDecorationToHtml(const std::vector<TextDecoration> & decorations)139 std::string SpanToHtml::TextDecorationToHtml(const std::vector<TextDecoration>& decorations)
140 {
141 static const LinearEnumMapNode<TextDecoration, std::string> decorationTable[] = {
142 { TextDecoration::NONE, "none" },
143 { TextDecoration::UNDERLINE, "underline" },
144 { TextDecoration::OVERLINE, "overline" },
145 { TextDecoration::LINE_THROUGH, "line-through" },
146 { TextDecoration::INHERIT, "inherit" },
147 };
148
149 std::string style;
150 for (TextDecoration decoration : decorations) {
151 auto index = BinarySearchFindIndex(decorationTable, ArraySize(decorationTable), decoration);
152 if (index < 0) {
153 continue;
154 }
155 style += decorationTable[index].value + " ";
156 }
157 style.pop_back();
158
159 return ToHtmlStyleFormat("text-decoration-line", style);
160 }
161
TextDecorationStyleToHtml(TextDecorationStyle decorationStyle)162 std::string SpanToHtml::TextDecorationStyleToHtml(TextDecorationStyle decorationStyle)
163 {
164 static const LinearEnumMapNode<TextDecorationStyle, std::string> table[] = {
165 { TextDecorationStyle::SOLID, "solid" },
166 { TextDecorationStyle::DOUBLE, "double" },
167 { TextDecorationStyle::DOTTED, "dotted" },
168 { TextDecorationStyle::DASHED, "dashed" },
169 { TextDecorationStyle::WAVY, "wavy" },
170 };
171
172 auto index = BinarySearchFindIndex(table, ArraySize(table), decorationStyle);
173 if (index < 0) {
174 return "";
175 }
176
177 return ToHtmlStyleFormat("text-decoration-style", table[index].value);
178 }
179
DimensionToString(const Dimension & dimension)180 std::string SpanToHtml::DimensionToString(const Dimension& dimension)
181 {
182 return StringUtils::DoubleToString(dimension.ConvertToVp()).append("px");
183 }
184
DimensionToStringWithoutUnit(const Dimension & dimension)185 std::string SpanToHtml::DimensionToStringWithoutUnit(const Dimension& dimension)
186 {
187 return StringUtils::DoubleToString(dimension.ConvertToVp());
188 }
189
ToHtml(const std::string & key,const std::optional<Dimension> & dimension)190 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<Dimension>& dimension)
191 {
192 if (!dimension) {
193 return "";
194 }
195 auto& value = *dimension;
196 if (!value.IsValid()) {
197 return "";
198 }
199 return ToHtmlStyleFormat(key, DimensionToString(value));
200 }
201
DecorationToHtml(const NG::FontStyle & fontStyle)202 std::string SpanToHtml::DecorationToHtml(const NG::FontStyle& fontStyle)
203 {
204 auto types = fontStyle.GetTextDecoration().value_or(
205 std::vector<TextDecoration>({TextDecoration::NONE}));
206 if (!V2::IsValidTextDecorations(types)) {
207 return "";
208 }
209 std::string html;
210 auto color = fontStyle.GetTextDecorationColor();
211 if (color) {
212 auto htmlColor = color->ColorToString();
213 ToHtmlColor(htmlColor);
214 html += ToHtmlStyleFormat("text-decoration-color", htmlColor);
215 }
216 html += TextDecorationToHtml(types);
217 auto style = fontStyle.GetTextDecorationStyle();
218 if (style) {
219 html += TextDecorationStyleToHtml(*style);
220 }
221
222 return html;
223 }
224
ToHtml(const std::optional<std::vector<Shadow>> & shadows)225 std::string SpanToHtml::ToHtml(const std::optional<std::vector<Shadow>>& shadows)
226 {
227 if (!shadows.has_value()) {
228 return "";
229 }
230
231 if (shadows.value().empty()) {
232 return "";
233 }
234
235 std::string style;
236 for (const auto& shadow : shadows.value()) {
237 if (!shadow.IsValid()) {
238 continue;
239 }
240
241 auto htmlColor = shadow.GetColor().ColorToString();
242 ToHtmlColor(htmlColor);
243
244 style += Dimension(shadow.GetOffset().GetX()).ToString() + " " +
245 Dimension(shadow.GetOffset().GetY()).ToString() + " " + Dimension(shadow.GetBlurRadius()).ToString() +
246 " " + htmlColor + ",";
247 }
248 style.pop_back();
249
250 return ToHtmlStyleFormat("text-shadow", style);
251 }
252
ToHtml(const std::string & key,const std::optional<CalcDimension> & dimesion)253 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<CalcDimension>& dimesion)
254 {
255 if (!dimesion) {
256 return "";
257 }
258
259 return ToHtmlStyleFormat(key, DimensionToString(*dimesion));
260 }
261
ToHtml(const std::string & key,bool syncLoad)262 std::string SpanToHtml::ToHtml(const std::string& key, bool syncLoad)
263 {
264 return ToHtmlStyleFormat(key, V2::ConvertBoolToString(syncLoad));
265 }
266
ToHtmlImgSizeAttribute(const std::string & key,const std::optional<CalcDimension> & dimesion)267 std::string SpanToHtml::ToHtmlImgSizeAttribute(const std::string& key, const std::optional<CalcDimension>& dimesion)
268 {
269 if (!dimesion) {
270 return "";
271 }
272
273 return ToHtmlAttributeFormat(key, DimensionToStringWithoutUnit(*dimesion));
274 }
275
ToHtml(const std::optional<ImageSpanSize> & size)276 std::string SpanToHtml::ToHtml(const std::optional<ImageSpanSize>& size)
277 {
278 if (!size) {
279 return "";
280 }
281
282 std::string style = ToHtmlImgSizeAttribute("width", size->width);
283 style += ToHtmlImgSizeAttribute("height", size->height);
284 return style;
285 }
286
ToHtml(const std::optional<VerticalAlign> & verticalAlign)287 std::string SpanToHtml::ToHtml(const std::optional<VerticalAlign>& verticalAlign)
288 {
289 if (!verticalAlign) {
290 return "";
291 }
292
293 static const LinearEnumMapNode<VerticalAlign, std::string> table[] = {
294 { VerticalAlign::TOP, "top" },
295 { VerticalAlign::CENTER, "center" },
296 { VerticalAlign::BOTTOM, "bottom" },
297 { VerticalAlign::BASELINE, "baseline" },
298 { VerticalAlign::NONE, "" },
299 };
300 auto iter = BinarySearchFindIndex(table, ArraySize(table), *verticalAlign);
301 if (iter < 0) {
302 return "";
303 }
304
305 return ToHtmlStyleFormat("vertical-align", table[iter].value);
306 }
307
ToHtml(const std::optional<ImageFit> & objectFit)308 std::string SpanToHtml::ToHtml(const std::optional<ImageFit>& objectFit)
309 {
310 if (!objectFit) {
311 return "";
312 }
313
314 static const LinearEnumMapNode<ImageFit, std::string> table[] = {
315 { ImageFit::FILL, "fill" },
316 { ImageFit::CONTAIN, "contain" },
317 { ImageFit::COVER, "cover" },
318 { ImageFit::FITWIDTH, "none" },
319 { ImageFit::FITHEIGHT, "none" },
320 { ImageFit::NONE, "none" },
321 { ImageFit::SCALE_DOWN, "scale-down" },
322 };
323
324 auto index = BinarySearchFindIndex(table, ArraySize(table), *objectFit);
325 if (index < 0) {
326 return "";
327 }
328
329 return ToHtmlStyleFormat("object-fit", table[index].value);
330 }
331
ToHtml(const std::string & key,const std::optional<OHOS::Ace::NG::MarginProperty> & prop)332 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<OHOS::Ace::NG::MarginProperty>& prop)
333 {
334 if (!prop) {
335 return "";
336 }
337
338 if (prop->top == prop->right && prop->right == prop->bottom && prop->bottom == prop->left) {
339 if (!prop->top) {
340 return "";
341 }
342 return ToHtmlStyleFormat(key, DimensionToString(prop->top->GetDimension()));
343 }
344
345 auto padding = prop->top.has_value() ? DimensionToString(prop->top->GetDimension()) : "0";
346 padding += " " + (prop->right.has_value() ? DimensionToString(prop->right->GetDimension()) : "0");
347 padding += " " + (prop->bottom.has_value() ? DimensionToString(prop->bottom->GetDimension()) : "0");
348 padding += " " + (prop->left.has_value() ? DimensionToString(prop->left->GetDimension()) : "0");
349
350 return ToHtmlStyleFormat(key, padding);
351 }
352
ToHtml(const std::optional<OHOS::Ace::NG::BorderRadiusProperty> & borderRadius)353 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::NG::BorderRadiusProperty>& borderRadius)
354 {
355 if (!borderRadius) {
356 return "";
357 }
358
359 std::string radius;
360 if (borderRadius->radiusTopLeft) {
361 radius += ToHtmlStyleFormat("border-top-left-radius", DimensionToString(*borderRadius->radiusTopLeft));
362 }
363 if (borderRadius->radiusTopRight) {
364 radius += ToHtmlStyleFormat("border-top-right-radius", DimensionToString(*borderRadius->radiusTopRight));
365 }
366 if (borderRadius->radiusBottomRight) {
367 radius += ToHtmlStyleFormat("border-bottom-right-radius", DimensionToString(*borderRadius->radiusBottomRight));
368 }
369 if (borderRadius->radiusBottomLeft) {
370 radius += ToHtmlStyleFormat("border-bottom-left-radius", DimensionToString(*borderRadius->radiusBottomLeft));
371 }
372
373 return radius;
374 }
375
CreateDirectory(const std::string & path)376 bool SpanToHtml::CreateDirectory(const std::string& path)
377 {
378 if (access(path.c_str(), F_OK) == 0) {
379 return true;
380 }
381
382 std::string::size_type index = 0;
383 do {
384 std::string subPath;
385 index = path.find('/', index + 1);
386 if (index == std::string::npos) {
387 subPath = path;
388 } else {
389 subPath = path.substr(0, index);
390 }
391
392 if (access(subPath.c_str(), F_OK) != 0) {
393 if (mkdir(subPath.c_str(), (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) != 0) {
394 return false;
395 }
396 }
397 } while (index != std::string::npos);
398
399 if (access(path.c_str(), F_OK) == 0) {
400 return true;
401 }
402
403 return false;
404 }
405
WriteLocalFile(RefPtr<PixelMap> pixelMap,std::string & filePath,std::string & fileUri)406 int SpanToHtml::WriteLocalFile(RefPtr<PixelMap> pixelMap, std::string& filePath, std::string& fileUri)
407 {
408 std::string fileName;
409 auto pos = filePath.rfind("/");
410 if (pos == std::string::npos) {
411 fileName = filePath;
412 } else {
413 fileName = filePath.substr(pos + 1);
414 }
415
416 if (fileName.empty()) {
417 int64_t now =
418 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
419 .count();
420 fileName = std::to_string(now) + DEFAULT_SUFFIX;
421 }
422
423 CreateDirectory(TEMP_HTML_CONVERT_DATA_ROOT_PATH);
424 std::string localPath = TEMP_HTML_CONVERT_DATA_ROOT_PATH + std::string("/") + fileName;
425 RefPtr<ImagePacker> imagePacker = ImagePacker::Create();
426 if (imagePacker == nullptr) {
427 return -1;
428 }
429 PackOption option;
430 option.format = CONVERT_PNG_FORMAT;
431 imagePacker->StartPacking(localPath, option);
432 imagePacker->AddImage(*pixelMap);
433 int64_t packedSize = 0;
434 if (imagePacker->FinalizePacking(packedSize)) {
435 return -1;
436 }
437
438 if (chmod(localPath.c_str(), CHOWN_RW_UG) != 0) {
439 }
440
441 fileUri = "file:///" + localPath;
442 return 0;
443 }
444
ImageToHtml(RefPtr<NG::SpanItem> item)445 std::string SpanToHtml::ImageToHtml(RefPtr<NG::SpanItem> item)
446 {
447 auto image = AceType::DynamicCast<ImageSpanItem>(item);
448 if (image == nullptr) {
449 return "";
450 }
451
452 auto options = image->options;
453 if (!options.image && !options.imagePixelMap) {
454 return "";
455 }
456
457 std::string urlName;
458 auto pixelMap = options.imagePixelMap.value_or(nullptr);
459 if (pixelMap) {
460 int ret = WriteLocalFile(pixelMap, *options.image, urlName);
461 LOGI("img write ret: %{public}d height: %{public}d, width: %{public}d, size:%{public}d", ret,
462 pixelMap->GetHeight(), pixelMap->GetWidth(), pixelMap->GetByteCount());
463 } else if (options.image.has_value()) {
464 urlName = options.image.value();
465 }
466 std::string imgHtml = "<img src=\"" + urlName + "\" ";
467 imgHtml += ToHtml(options.imageAttribute->size);
468 if (options.imageAttribute) {
469 imgHtml += " style=\"";
470 imgHtml += ToHtml(options.imageAttribute->verticalAlign);
471 imgHtml += ToHtml(options.imageAttribute->objectFit);
472 imgHtml += ToHtml("margin", options.imageAttribute->marginProp);
473 imgHtml += ToHtml(options.imageAttribute->borderRadius);
474 imgHtml += ToHtml("padding", options.imageAttribute->paddingProp);
475 if (!pixelMap) {
476 imgHtml += ToHtml("sync-load", options.imageAttribute->syncLoad);
477 }
478 imgHtml += "\"";
479 }
480
481 imgHtml += ">";
482 return imgHtml;
483 }
484
NormalStyleToHtml(const NG::FontStyle & fontStyle,const OHOS::Ace::NG::TextLineStyle & textLineStyle)485 std::string SpanToHtml::NormalStyleToHtml(
486 const NG::FontStyle& fontStyle, const OHOS::Ace::NG::TextLineStyle& textLineStyle)
487 {
488 std::string style = FontSizeToHtml(fontStyle.GetFontSize());
489 style += FontStyleToHtml(fontStyle.GetItalicFontStyle());
490 style += FontWeightToHtml(fontStyle.GetFontWeight());
491 style += ColorToHtml(fontStyle.GetTextColor());
492 style += FontFamilyToHtml(fontStyle.GetFontFamily());
493 style += StrokeWidthToHtml(fontStyle.GetStrokeWidth());
494 style += StrokeColorToHtml(fontStyle.GetStrokeColor());
495 style += FontSuperscriptToHtml(fontStyle.GetSuperscript());
496 style += DecorationToHtml(fontStyle);
497 style += ToHtml("vertical-align", textLineStyle.GetBaselineOffset());
498 style += ToHtml("line-height", textLineStyle.GetLineHeight());
499 style += ToHtml("letter-spacing", fontStyle.GetLetterSpacing());
500 style += ToHtml(fontStyle.GetTextShadow());
501 if (style.empty()) {
502 return "";
503 }
504 return "style=\"" + style + "\"";
505 }
506
NormalStyleToHtml(const RefPtr<NG::SpanItem> & item)507 std::string SpanToHtml::NormalStyleToHtml(const RefPtr<NG::SpanItem>& item)
508 {
509 if (!item || !item->fontStyle || !item->textLineStyle) {
510 return "";
511 }
512
513 const auto& fontStyle = *item->fontStyle;
514 const auto& textLineStyle = *item->textLineStyle;
515
516 std::string style = FontSizeToHtml(fontStyle.GetFontSize());
517 style += FontStyleToHtml(fontStyle.GetItalicFontStyle());
518 style += FontWeightToHtml(fontStyle.GetFontWeight());
519 style += ColorToHtml(fontStyle.GetTextColor());
520 style += FontFamilyToHtml(fontStyle.GetFontFamily());
521 style += StrokeWidthToHtml(fontStyle.GetStrokeWidth());
522 style += StrokeColorToHtml(fontStyle.GetStrokeColor());
523 style += FontSuperscriptToHtml(fontStyle.GetSuperscript());
524 style += DecorationToHtml(fontStyle);
525 style += ToHtml("vertical-align", textLineStyle.GetBaselineOffset());
526 style += ToHtml("line-height", textLineStyle.GetLineHeight());
527 style += ToHtml("letter-spacing", fontStyle.GetLetterSpacing());
528 style += ToHtml(fontStyle.GetTextShadow());
529
530 // Add backgroundStyle parsing
531 if (item->backgroundStyle) {
532 style += BackgroundColorToHtml(item->backgroundStyle);
533 }
534
535 if (style.empty()) {
536 return "";
537 }
538 return "style=\"" + style + "\"";
539 }
540
ToHtml(const std::optional<OHOS::Ace::TextAlign> & object)541 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextAlign>& object)
542 {
543 if (!object.has_value()) {
544 return "";
545 }
546
547 static const LinearEnumMapNode<TextAlign, std::string> table[] = {
548 { TextAlign::START, "start" },
549 { TextAlign::CENTER, "center" },
550 { TextAlign::END, "end" },
551 { TextAlign::JUSTIFY, "justify" },
552 };
553 auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
554 if (index < 0) {
555 return "";
556 }
557
558 return ToHtmlStyleFormat("text-align", table[index].value);
559 }
560
ToHtml(const std::optional<OHOS::Ace::TextVerticalAlign> & object)561 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextVerticalAlign>& object)
562 {
563 if (!object.has_value()) {
564 return "";
565 }
566
567 static const LinearEnumMapNode<TextVerticalAlign, std::string> table[] = {
568 { TextVerticalAlign::BASELINE, "baseline" },
569 { TextVerticalAlign::BOTTOM, "bottom" },
570 { TextVerticalAlign::CENTER, "middle" },
571 { TextVerticalAlign::TOP, "top" },
572 };
573 auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
574 if (index < 0) {
575 return "";
576 }
577
578 return ToHtmlStyleFormat("vertical-align", table[index].value);
579 }
580
ToHtml(const std::optional<OHOS::Ace::WordBreak> & object)581 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::WordBreak>& object)
582 {
583 if (!object.has_value()) {
584 return "";
585 }
586
587 // no keep_all
588 static const LinearEnumMapNode<WordBreak, std::string> table[] = {
589 { WordBreak::NORMAL, "normal" },
590 { WordBreak::BREAK_ALL, "break_all" },
591 { WordBreak::BREAK_WORD, "break_word" },
592 };
593 auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
594 if (index < 0) {
595 return "";
596 }
597
598 return ToHtmlStyleFormat("word-break", table[index].value);
599 }
600
ToHtml(const std::optional<OHOS::Ace::TextOverflow> & object)601 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextOverflow>& object)
602 {
603 if (!object.has_value()) {
604 return "";
605 }
606
607 static const LinearEnumMapNode<TextOverflow, std::string> table[] = {
608 { TextOverflow::CLIP, "clip" },
609 { TextOverflow::ELLIPSIS, "ellipsis" },
610 { TextOverflow::MARQUEE, "marquee" },
611 };
612 auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
613 if (index < 0) {
614 return "";
615 }
616
617 return ToHtmlStyleFormat("text-overflow", table[index].value);
618 }
619
LeadingMarginToHtml(const OHOS::Ace::NG::TextLineStyle & style)620 std::string SpanToHtml::LeadingMarginToHtml(const OHOS::Ace::NG::TextLineStyle& style)
621 {
622 auto object = style.GetLeadingMargin();
623 if (!object) {
624 return "";
625 }
626
627 if (!object.has_value()) {
628 return "";
629 }
630
631 return "";
632 }
633
ParagraphStyleToHtml(const OHOS::Ace::NG::TextLineStyle & textLineStyle)634 std::string SpanToHtml::ParagraphStyleToHtml(const OHOS::Ace::NG::TextLineStyle& textLineStyle)
635 {
636 auto details = ToHtml(textLineStyle.GetTextAlign());
637 details += ToHtml(textLineStyle.GetTextVerticalAlign());
638 details += ToHtml("text-indent", textLineStyle.GetTextIndent());
639 details += ToHtml(textLineStyle.GetWordBreak());
640 details += ToHtml(textLineStyle.GetTextOverflow());
641 if (details.empty()) {
642 return "";
643 }
644 return "style=\"" + details + "\"";
645 }
646
ToHtml(const SpanString & spanString)647 std::string SpanToHtml::ToHtml(const SpanString& spanString)
648 {
649 auto items = spanString.GetSpanItems();
650 bool newLine = true;
651 size_t paragrapStart = 0;
652 std::string out = "<div >";
653 for (const auto& item : items) {
654 auto paragraphStyle = ParagraphStyleToHtml(*item->textLineStyle);
655 if (newLine && !paragraphStyle.empty()) {
656 out += "<p " + paragraphStyle + ">";
657 newLine = false;
658 }
659 std::string str = "";
660 if (item->spanItemType == SpanItemType::NORMAL) {
661 if (paragrapStart == 0) {
662 paragrapStart = str.length();
663 }
664 if (item->backgroundStyle) {
665 str += "<span " + NormalStyleToHtml(item) + ">";
666 } else {
667 str += "<span " + NormalStyleToHtml(*item->fontStyle, *item->textLineStyle) + ">";
668 }
669 auto content = UtfUtils::Str16DebugToStr8(item->GetSpanContent());
670 auto wContent = StringUtils::ToWstring(content);
671 if (wContent.back() == L'\n') {
672 if (newLine) {
673 str.insert(paragrapStart, "<p>");
674 paragrapStart = 0;
675 }
676 content.pop_back();
677 str += WrapWithAnchorIfNeeded(item, content) + "</span>";
678 str += "</p>";
679 newLine = true;
680 } else {
681 str += WrapWithAnchorIfNeeded(item, content) + "</span>";
682 }
683 } else if (item->spanItemType == SpanItemType::IMAGE) {
684 str += ImageToHtml(item);
685 }
686 out += str;
687 }
688
689 if (!newLine) {
690 out += "</p>";
691 }
692
693 out += "</div>";
694 return out;
695 }
696
ToHtml(std::vector<uint8_t> & values)697 std::string SpanToHtml::ToHtml(std::vector<uint8_t>& values)
698 {
699 auto spanString = SpanString::DecodeTlv(values);
700 return ToHtml(*spanString);
701 }
702
HandleSingleSpanItemHtml(const RefPtr<NG::SpanItem> & item,std::string & out,size_t & paragraphStart,bool & newLine)703 void SpanToHtml::HandleSingleSpanItemHtml(const RefPtr<NG::SpanItem>& item, std::string& out,
704 size_t& paragraphStart, bool& newLine)
705 {
706 auto paragraphStyle = ParagraphStyleToHtml(*item->textLineStyle);
707 if (newLine && !paragraphStyle.empty()) {
708 out += "<p " + paragraphStyle + ">";
709 newLine = false;
710 }
711 std::string str = "";
712 if (item->spanItemType == SpanItemType::NORMAL) {
713 if (paragraphStart == 0) {
714 paragraphStart = str.length();
715 }
716 if (item->backgroundStyle) {
717 str += "<span " + NormalStyleToHtml(item) + ">";
718 } else {
719 str += "<span " + NormalStyleToHtml(*item->fontStyle, *item->textLineStyle) + ">";
720 }
721 auto content = UtfUtils::Str16DebugToStr8(item->GetSpanContent());
722 auto wContent = StringUtils::ToWstring(content);
723 if (wContent.back() == L'\n') {
724 if (newLine) {
725 str.insert(paragraphStart, "<p>");
726 paragraphStart = 0;
727 }
728 content.pop_back();
729 str += WrapWithAnchorIfNeeded(item, content) + "</span>";
730 str += "</p>";
731 newLine = true;
732 } else {
733 str += WrapWithAnchorIfNeeded(item, content) + "</span>";
734 }
735 } else if (item->spanItemType == SpanItemType::IMAGE) {
736 str += ImageToHtml(item);
737 }
738 out += str;
739 }
740
ToHtml(const std::list<RefPtr<SpanItem>> & spanItems)741 std::string SpanToHtml::ToHtml(const std::list<RefPtr<SpanItem>>& spanItems)
742 {
743 bool newLine = true;
744 size_t paragraphStart = 0;
745 std::string out = "<div >";
746 for (const auto& item : spanItems) {
747 HandleSingleSpanItemHtml(item, out, paragraphStart, newLine);
748 }
749 if (!newLine) {
750 out += "</p>";
751 }
752 out += "</div>";
753 return out;
754 }
755
ToHtmlForNormalType(const NG::FontStyle & fontStyle,const NG::TextLineStyle & textLineStyle,const std::u16string & contentStr)756 std::string SpanToHtml::ToHtmlForNormalType(const NG::FontStyle& fontStyle,
757 const NG::TextLineStyle& textLineStyle, const std::u16string& contentStr)
758 {
759 bool newLine = true;
760 size_t paragraphStart = 0;
761 std::string out = "<div >";
762 auto paragraphStyle = ParagraphStyleToHtml(textLineStyle);
763 if (newLine && !paragraphStyle.empty()) {
764 out += "<p " + paragraphStyle + ">";
765 newLine = false;
766 }
767 if (paragraphStart == 0) {
768 paragraphStart = out.length();
769 }
770 out += "<span " + NormalStyleToHtml(fontStyle, textLineStyle) + ">";
771 auto content = UtfUtils::Str16DebugToStr8(contentStr);
772 auto wContent = StringUtils::ToWstring(content);
773 if (wContent.back() == L'\n') {
774 if (newLine) {
775 out.insert(paragraphStart, "<p>");
776 paragraphStart = 0;
777 }
778 content.pop_back();
779 out += content + "</span>";
780 out += "</p>";
781 newLine = true;
782 } else {
783 out += content + "</span>";
784 }
785 if (!newLine) {
786 out += "</p>";
787 }
788 out += "</div>";
789 return out;
790 }
791
ToHtml(const SpanString * str)792 std::string HtmlUtils::ToHtml(const SpanString* str)
793 {
794 const std::string html = SpanToHtml::ToHtml(*str);
795 return html;
796 }
797
ToHtml(const std::list<RefPtr<NG::SpanItem>> & spanItems)798 std::string HtmlUtils::ToHtml(const std::list<RefPtr<NG::SpanItem>>& spanItems)
799 {
800 const std::string html = SpanToHtml::ToHtml(spanItems);
801 return html;
802 }
803
ToHtmlForNormalType(const NG::FontStyle & fontStyle,const NG::TextLineStyle & textLineStyle,const std::u16string & contentStr)804 std::string HtmlUtils::ToHtmlForNormalType(const NG::FontStyle& fontStyle,
805 const NG::TextLineStyle& textLineStyle, const std::u16string& contentStr)
806 {
807 const std::string html = SpanToHtml::ToHtmlForNormalType(fontStyle, textLineStyle, contentStr);
808 return html;
809 }
WrapWithAnchorIfNeeded(const RefPtr<NG::SpanItem> & item,const std::string & content)810 std::string SpanToHtml::WrapWithAnchorIfNeeded(const RefPtr<NG::SpanItem>& item, const std::string& content)
811 {
812 if (item->urlAddress) {
813 std::string href = UtfUtils::Str16DebugToStr8(item->urlAddress.value_or(std::u16string(u"")));
814 return "<a href=\"" + href + "\">" + content + "</a>";
815 }
816 return content;
817 }
818 } // namespace OHOS::Ace