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