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 "include/core/SkColor.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkRefCnt.h"
14 #include "include/core/SkScalar.h"
15 #include "include/core/SkString.h"
16 #include "include/core/SkTypeface.h"
17 #include "include/utils/SkTextUtils.h"
18 #include "modules/skottie/include/Skottie.h"
19 #include "modules/skottie/include/TextShaper.h"
20 #include "modules/skottie/src/SkottieJson.h"
21 #include "modules/skottie/src/SkottiePriv.h"
22 #include "modules/skottie/src/SkottieValue.h"
23 #include "src/utils/SkJSON.h"
24
25 #include <algorithm>
26 #include <array>
27 #include <cstddef>
28 #include <limits>
29
30 namespace skottie::internal {
31
Parse(const skjson::Value & jv,const internal::AnimationBuilder & abuilder,TextValue * v)32 bool Parse(const skjson::Value& jv, const internal::AnimationBuilder& abuilder, TextValue* v) {
33 const skjson::ObjectValue* jtxt = jv;
34 if (!jtxt) {
35 return false;
36 }
37
38 const skjson::StringValue* font_name = (*jtxt)["f"];
39 const skjson::StringValue* text = (*jtxt)["t"];
40 const skjson::NumberValue* text_size = (*jtxt)["s"];
41 const skjson::NumberValue* line_height = (*jtxt)["lh"];
42 if (!font_name || !text || !text_size || !line_height) {
43 return false;
44 }
45
46 const auto* font = abuilder.findFont(SkString(font_name->begin(), font_name->size()));
47 if (!font) {
48 abuilder.log(Logger::Level::kError, nullptr, "Unknown font: \"%s\".", font_name->begin());
49 return false;
50 }
51
52 v->fText.set(text->begin(), text->size());
53 v->fTextSize = **text_size;
54 v->fLineHeight = **line_height;
55 v->fTypeface = font->fTypeface;
56 v->fFontFamily = font->fFamily;
57 v->fAscent = font->fAscentPct * -0.01f * v->fTextSize; // negative ascent per SkFontMetrics
58 v->fLineShift = ParseDefault((*jtxt)["ls"], 0.0f);
59
60 static constexpr SkTextUtils::Align gAlignMap[] = {
61 SkTextUtils::kLeft_Align, // 'j': 0
62 SkTextUtils::kRight_Align, // 'j': 1
63 SkTextUtils::kCenter_Align // 'j': 2
64 };
65 v->fHAlign = gAlignMap[std::min<size_t>(ParseDefault<size_t>((*jtxt)["j"], 0),
66 std::size(gAlignMap) - 1)];
67
68 // Optional text box size.
69 if (const skjson::ArrayValue* jsz = (*jtxt)["sz"]) {
70 if (jsz->size() == 2) {
71 v->fBox.setWH(ParseDefault<SkScalar>((*jsz)[0], 0),
72 ParseDefault<SkScalar>((*jsz)[1], 0));
73 }
74 }
75
76 // Optional text box position.
77 if (const skjson::ArrayValue* jps = (*jtxt)["ps"]) {
78 if (jps->size() == 2) {
79 v->fBox.offset(ParseDefault<SkScalar>((*jps)[0], 0),
80 ParseDefault<SkScalar>((*jps)[1], 0));
81 }
82 }
83
84 static constexpr Shaper::Direction gDirectionMap[] = {
85 Shaper::Direction::kLTR, // 'd': 0
86 Shaper::Direction::kRTL, // 'd': 1
87 };
88 v->fDirection = gDirectionMap[std::min(ParseDefault<size_t>((*jtxt)["d"], 0),
89 std::size(gDirectionMap) - 1)];
90
91 static constexpr Shaper::ResizePolicy gResizeMap[] = {
92 Shaper::ResizePolicy::kNone, // 'rs': 0
93 Shaper::ResizePolicy::kScaleToFit, // 'rs': 1
94 Shaper::ResizePolicy::kDownscaleToFit, // 'rs': 2
95 };
96 // TODO: remove "sk_rs" support after migrating clients.
97 v->fResize = gResizeMap[std::min(std::max(ParseDefault<size_t>((*jtxt)[ "rs"], 0),
98 ParseDefault<size_t>((*jtxt)["sk_rs"], 0)),
99 std::size(gResizeMap) - 1)];
100
101 // Optional min/max font size and line count (used when aute-resizing)
102 v->fMinTextSize = ParseDefault<SkScalar>((*jtxt)["mf"], 0.0f);
103 v->fMaxTextSize = ParseDefault<SkScalar>((*jtxt)["xf"], std::numeric_limits<float>::max());
104 v->fMaxLines = ParseDefault<size_t> ((*jtxt)["xl"], 0);
105
106 // At the moment, BM uses the paragraph box to discriminate point mode vs. paragraph mode.
107 v->fLineBreak = v->fBox.isEmpty()
108 ? Shaper::LinebreakPolicy::kExplicit
109 : Shaper::LinebreakPolicy::kParagraph;
110
111 // Optional explicit text mode.
112 // N.b.: this is not being exported by BM, only used for testing.
113 auto text_mode = ParseDefault((*jtxt)["m"], -1);
114 if (text_mode >= 0) {
115 // Explicit text mode.
116 v->fLineBreak = (text_mode == 0)
117 ? Shaper::LinebreakPolicy::kExplicit // 'm': 0 -> point text
118 : Shaper::LinebreakPolicy::kParagraph; // 'm': 1 -> paragraph text
119 }
120
121 // Optional capitalization.
122 static constexpr Shaper::Capitalization gCapMap[] = {
123 Shaper::Capitalization::kNone, // 'ca': 0
124 Shaper::Capitalization::kUpperCase, // 'ca': 1
125 };
126 v->fCapitalization = gCapMap[std::min<size_t>(ParseDefault<size_t>((*jtxt)["ca"], 0),
127 std::size(gCapMap) - 1)];
128
129 // In point mode, the text is baseline-aligned.
130 v->fVAlign = v->fBox.isEmpty() ? Shaper::VAlign::kTopBaseline
131 : Shaper::VAlign::kTop;
132
133 static constexpr Shaper::VAlign gVAlignMap[] = {
134 Shaper::VAlign::kHybridTop, // 'vj': 0
135 Shaper::VAlign::kHybridCenter, // 'vj': 1
136 Shaper::VAlign::kHybridBottom, // 'vj': 2
137 Shaper::VAlign::kVisualTop, // 'vj': 3
138 Shaper::VAlign::kVisualCenter, // 'vj': 4
139 Shaper::VAlign::kVisualBottom, // 'vj': 5
140 };
141 size_t vj;
142 if (skottie::Parse((*jtxt)["vj"], &vj)) {
143 if (vj < std::size(gVAlignMap)) {
144 v->fVAlign = gVAlignMap[vj];
145 } else {
146 abuilder.log(Logger::Level::kWarning, nullptr, "Ignoring unknown 'vj' value: %zu", vj);
147 }
148 } else if (skottie::Parse((*jtxt)["sk_vj"], &vj)) {
149 // Legacy sk_vj values.
150 // TODO: remove after clients update.
151 switch (vj) {
152 case 0:
153 case 1:
154 case 2:
155 static_assert(std::size(gVAlignMap) > 2);
156 v->fVAlign = gVAlignMap[vj];
157 break;
158 case 3:
159 // 'sk_vj': 3 -> kHybridCenter/kScaleToFit
160 v->fVAlign = Shaper::VAlign::kHybridCenter;
161 v->fResize = Shaper::ResizePolicy::kScaleToFit;
162 break;
163 case 4:
164 // 'sk_vj': 4 -> kHybridCenter/kDownscaleToFit
165 v->fVAlign = Shaper::VAlign::kHybridCenter;
166 v->fResize = Shaper::ResizePolicy::kDownscaleToFit;
167 break;
168 default:
169 abuilder.log(Logger::Level::kWarning, nullptr,
170 "Ignoring unknown 'sk_vj' value: %zu", vj);
171 break;
172 }
173 }
174
175 const auto& parse_color = [] (const skjson::ArrayValue* jcolor,
176 SkColor* c) {
177 if (!jcolor) {
178 return false;
179 }
180
181 ColorValue color_vec;
182 if (!skottie::Parse(*jcolor, static_cast<VectorValue*>(&color_vec))) {
183 return false;
184 }
185
186 *c = color_vec;
187 return true;
188 };
189
190 v->fHasFill = parse_color((*jtxt)["fc"], &v->fFillColor);
191 v->fHasStroke = parse_color((*jtxt)["sc"], &v->fStrokeColor);
192
193 if (v->fHasStroke) {
194 v->fStrokeWidth = ParseDefault((*jtxt)["sw"], 1.0f);
195 v->fPaintOrder = ParseDefault((*jtxt)["of"], true)
196 ? TextPaintOrder::kFillStroke
197 : TextPaintOrder::kStrokeFill;
198
199 static constexpr SkPaint::Join gJoins[] = {
200 SkPaint::kMiter_Join, // lj: 1
201 SkPaint::kRound_Join, // lj: 2
202 SkPaint::kBevel_Join, // lj: 3
203 };
204 v->fStrokeJoin = gJoins[std::min<size_t>(ParseDefault<size_t>((*jtxt)["lj"], 1) - 1,
205 std::size(gJoins) - 1)];
206 }
207
208 return true;
209 }
210
211 } // namespace skottie::internal
212