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