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