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
14 namespace skottie::internal {
15
Parse(const skjson::Value & jv,const internal::AnimationBuilder & abuilder,TextValue * v)16 bool 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 v->fLineShift = ParseDefault((*jtxt)["ls"], 0.0f);
42
43 static constexpr SkTextUtils::Align gAlignMap[] = {
44 SkTextUtils::kLeft_Align, // 'j': 0
45 SkTextUtils::kRight_Align, // 'j': 1
46 SkTextUtils::kCenter_Align // 'j': 2
47 };
48 v->fHAlign = gAlignMap[std::min<size_t>(ParseDefault<size_t>((*jtxt)["j"], 0),
49 SK_ARRAY_COUNT(gAlignMap) - 1)];
50
51 // Optional text box size.
52 if (const skjson::ArrayValue* jsz = (*jtxt)["sz"]) {
53 if (jsz->size() == 2) {
54 v->fBox.setWH(ParseDefault<SkScalar>((*jsz)[0], 0),
55 ParseDefault<SkScalar>((*jsz)[1], 0));
56 }
57 }
58
59 // Optional text box position.
60 if (const skjson::ArrayValue* jps = (*jtxt)["ps"]) {
61 if (jps->size() == 2) {
62 v->fBox.offset(ParseDefault<SkScalar>((*jps)[0], 0),
63 ParseDefault<SkScalar>((*jps)[1], 0));
64 }
65 }
66
67 static constexpr Shaper::ResizePolicy gResizeMap[] = {
68 Shaper::ResizePolicy::kNone, // 'rs': 0
69 Shaper::ResizePolicy::kScaleToFit, // 'rs': 1
70 Shaper::ResizePolicy::kDownscaleToFit, // 'rs': 2
71 };
72 // TODO: remove "sk_rs" support after migrating clients.
73 v->fResize = gResizeMap[std::min(std::max(ParseDefault<size_t>((*jtxt)[ "rs"], 0),
74 ParseDefault<size_t>((*jtxt)["sk_rs"], 0)),
75 SK_ARRAY_COUNT(gResizeMap) - 1)];
76
77 // Optional min/max font size (used when aute-resizing)
78 v->fMinTextSize = ParseDefault<SkScalar>((*jtxt)["mf"], 0.0f);
79 v->fMaxTextSize = ParseDefault<SkScalar>((*jtxt)["xf"], std::numeric_limits<float>::max());
80
81 // At the moment, BM uses the paragraph box to discriminate point mode vs. paragraph mode.
82 v->fLineBreak = v->fBox.isEmpty()
83 ? Shaper::LinebreakPolicy::kExplicit
84 : Shaper::LinebreakPolicy::kParagraph;
85
86 // Optional explicit text mode.
87 // N.b.: this is not being exported by BM, only used for testing.
88 auto text_mode = ParseDefault((*jtxt)["m"], -1);
89 if (text_mode >= 0) {
90 // Explicit text mode.
91 v->fLineBreak = (text_mode == 0)
92 ? Shaper::LinebreakPolicy::kExplicit // 'm': 0 -> point text
93 : Shaper::LinebreakPolicy::kParagraph; // 'm': 1 -> paragraph text
94 }
95
96 // Optional capitalization.
97 static constexpr Shaper::Capitalization gCapMap[] = {
98 Shaper::Capitalization::kNone, // 'ca': 0
99 Shaper::Capitalization::kUpperCase, // 'ca': 1
100 };
101 v->fCapitalization = gCapMap[std::min<size_t>(ParseDefault<size_t>((*jtxt)["ca"], 0),
102 SK_ARRAY_COUNT(gCapMap) - 1)];
103
104 // In point mode, the text is baseline-aligned.
105 v->fVAlign = v->fBox.isEmpty() ? Shaper::VAlign::kTopBaseline
106 : Shaper::VAlign::kTop;
107
108 static constexpr Shaper::VAlign gVAlignMap[] = {
109 Shaper::VAlign::kVisualTop, // 'vj': 0
110 Shaper::VAlign::kVisualCenter, // 'vj': 1
111 Shaper::VAlign::kVisualBottom, // 'vj': 2
112 };
113 size_t vj;
114 if (skottie::Parse((*jtxt)[ "vj"], &vj) ||
115 skottie::Parse((*jtxt)["sk_vj"], &vj)) { // TODO: remove after migrating clients.
116 if (vj < SK_ARRAY_COUNT(gVAlignMap)) {
117 v->fVAlign = gVAlignMap[vj];
118 } else {
119 // Legacy sk_vj values.
120 // TODO: remove after clients update.
121 switch (vj) {
122 case 3:
123 // 'sk_vj': 3 -> kVisualCenter/kScaleToFit
124 v->fVAlign = Shaper::VAlign::kVisualCenter;
125 v->fResize = Shaper::ResizePolicy::kScaleToFit;
126 break;
127 case 4:
128 // 'sk_vj': 4 -> kVisualCenter/kDownscaleToFit
129 v->fVAlign = Shaper::VAlign::kVisualCenter;
130 v->fResize = Shaper::ResizePolicy::kDownscaleToFit;
131 break;
132 default:
133 abuilder.log(Logger::Level::kWarning, nullptr,
134 "Ignoring unknown 'vj' value: %zu", vj);
135 break;
136 }
137 }
138 }
139
140 const auto& parse_color = [] (const skjson::ArrayValue* jcolor,
141 SkColor* c) {
142 if (!jcolor) {
143 return false;
144 }
145
146 VectorValue color_vec;
147 if (!skottie::Parse(*jcolor, &color_vec)) {
148 return false;
149 }
150
151 *c = color_vec;
152 return true;
153 };
154
155 v->fHasFill = parse_color((*jtxt)["fc"], &v->fFillColor);
156 v->fHasStroke = parse_color((*jtxt)["sc"], &v->fStrokeColor);
157
158 if (v->fHasStroke) {
159 v->fStrokeWidth = ParseDefault((*jtxt)["sw"], 1.0f);
160 v->fPaintOrder = ParseDefault((*jtxt)["of"], true)
161 ? TextPaintOrder::kFillStroke
162 : TextPaintOrder::kStrokeFill;
163 }
164
165 return true;
166 }
167
168 } // namespace skottie::internal
169