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