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