1/*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "modules/skottie/src/text/TextValue.h"
9
10#include "modules/skottie/src/SkottieJson.h"
11#include "modules/skottie/src/SkottiePriv.h"
12#include "modules/skottie/src/SkottieValue.h"
13
14namespace skottie::internal {
15
16bool Parse(const skjson::Value& jv, const internal::AnimationBuilder& abuilder, TextValue* v) {
17 const skjson::ObjectValue* jtxt = jv;
18 if (!jtxt) {
19 return false;
20 }
21
22 const skjson::StringValue* font_name = (*jtxt)["f"];
23 const skjson::StringValue* text = (*jtxt)["t"];
24 const skjson::NumberValue* text_size = (*jtxt)["s"];
25 const skjson::NumberValue* line_height = (*jtxt)["lh"];
26 if (!font_name || !text || !text_size || !line_height) {
27 return false;
28 }
29
30 const auto* font = abuilder.findFont(SkString(font_name->begin(), font_name->size()));
31 if (!font) {
32 abuilder.log(Logger::Level::kError, nullptr, "Unknown font: \"%s\".", font_name->begin());
33 return false;
34 }
35
36 v->fText.set(text->begin(), text->size());
37 v->fTextSize = **text_size;
38 v->fLineHeight = **line_height;
39 v->fTypeface = font->fTypeface;
40 v->fAscent = font->fAscentPct * -0.01f * v->fTextSize; // negative ascent per SkFontMetrics
41
42 static constexpr SkTextUtils::Align gAlignMap[] = {
43 SkTextUtils::kLeft_Align, // 'j': 0
44 SkTextUtils::kRight_Align, // 'j': 1
45 SkTextUtils::kCenter_Align // 'j': 2
46 };
47 v->fHAlign = gAlignMap[std::min<size_t>(ParseDefault<size_t>((*jtxt)["j"], 0),
48 SK_ARRAY_COUNT(gAlignMap))];
49
50 // Optional text box size.
51 if (const skjson::ArrayValue* jsz = (*jtxt)["sz"]) {
52 if (jsz->size() == 2) {
53 v->fBox.setWH(ParseDefault<SkScalar>((*jsz)[0], 0),
54 ParseDefault<SkScalar>((*jsz)[1], 0));
55 }
56 }
57
58 // Optional text box position.
59 if (const skjson::ArrayValue* jps = (*jtxt)["ps"]) {
60 if (jps->size() == 2) {
61 v->fBox.offset(ParseDefault<SkScalar>((*jps)[0], 0),
62 ParseDefault<SkScalar>((*jps)[1], 0));
63 }
64 }
65
66 // Skia resizing extension "sk_rs":
67 static constexpr Shaper::ResizePolicy gResizeMap[] = {
68 Shaper::ResizePolicy::kNone, // 'sk_rs': 0
69 Shaper::ResizePolicy::kScaleToFit, // 'sk_rs': 1
70 Shaper::ResizePolicy::kDownscaleToFit, // 'sk_rs': 2
71 };
72 v->fResize = gResizeMap[std::min<size_t>(ParseDefault<size_t>((*jtxt)["sk_rs"], 0),
73 SK_ARRAY_COUNT(gResizeMap))];
74
75 // In point mode, the text is baseline-aligned.
76 v->fVAlign = v->fBox.isEmpty() ? Shaper::VAlign::kTopBaseline
77 : Shaper::VAlign::kTop;
78
79 // Skia vertical alignment extension "sk_vj":
80 static constexpr Shaper::VAlign gVAlignMap[] = {
81 Shaper::VAlign::kVisualTop, // 'sk_vj': 0
82 Shaper::VAlign::kVisualCenter, // 'sk_vj': 1
83 Shaper::VAlign::kVisualBottom, // 'sk_vj': 2
84 };
85 size_t sk_vj;
86 if (skottie::Parse((*jtxt)["sk_vj"], &sk_vj)) {
87 if (sk_vj < SK_ARRAY_COUNT(gVAlignMap)) {
88 v->fVAlign = gVAlignMap[sk_vj];
89 } else {
90 // Legacy sk_vj values.
91 // TODO: remove after clients update.
92 switch (sk_vj) {
93 case 3:
94 // 'sk_vj': 3 -> kVisualCenter/kScaleToFit
95 v->fVAlign = Shaper::VAlign::kVisualCenter;
96 v->fResize = Shaper::ResizePolicy::kScaleToFit;
97 break;
98 case 4:
99 // 'sk_vj': 4 -> kVisualCenter/kDownscaleToFit
100 v->fVAlign = Shaper::VAlign::kVisualCenter;
101 v->fResize = Shaper::ResizePolicy::kDownscaleToFit;
102 break;
103 default:
104 abuilder.log(Logger::Level::kWarning, nullptr,
105 "Ignoring unknown 'sk_vj' value: %zu", sk_vj);
106 break;
107 }
108 }
109 }
110
111 if (v->fResize != Shaper::ResizePolicy::kNone && v->fBox.isEmpty()) {
112 abuilder.log(Logger::Level::kWarning, jtxt, "Auto-scaled text requires a paragraph box.");
113 v->fResize = Shaper::ResizePolicy::kNone;
114 }
115
116 const auto& parse_color = [] (const skjson::ArrayValue* jcolor,
117 SkColor* c) {
118 if (!jcolor) {
119 return false;
120 }
121
122 VectorValue color_vec;
123 if (!skottie::Parse(*jcolor, &color_vec)) {
124 return false;
125 }
126
127 *c = color_vec;
128 return true;
129 };
130
131 v->fHasFill = parse_color((*jtxt)["fc"], &v->fFillColor);
132 v->fHasStroke = parse_color((*jtxt)["sc"], &v->fStrokeColor);
133
134 if (v->fHasStroke) {
135 v->fStrokeWidth = ParseDefault((*jtxt)["s"], 0.0f);
136 }
137
138 return true;
139}
140
141} // namespace skottie
142