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))];
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))];
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     // In point mode, the text is baseline-aligned.
97     v->fVAlign = v->fBox.isEmpty() ? Shaper::VAlign::kTopBaseline
98                                    : Shaper::VAlign::kTop;
99 
100     static constexpr Shaper::VAlign gVAlignMap[] = {
101         Shaper::VAlign::kVisualTop,    // 'vj': 0
102         Shaper::VAlign::kVisualCenter, // 'vj': 1
103         Shaper::VAlign::kVisualBottom, // 'vj': 2
104     };
105     size_t vj;
106     if (skottie::Parse((*jtxt)[   "vj"], &vj) ||
107         skottie::Parse((*jtxt)["sk_vj"], &vj)) { // TODO: remove after migrating clients.
108         if (vj < SK_ARRAY_COUNT(gVAlignMap)) {
109             v->fVAlign = gVAlignMap[vj];
110         } else {
111             // Legacy sk_vj values.
112             // TODO: remove after clients update.
113             switch (vj) {
114             case 3:
115                 // 'sk_vj': 3 -> kVisualCenter/kScaleToFit
116                 v->fVAlign = Shaper::VAlign::kVisualCenter;
117                 v->fResize = Shaper::ResizePolicy::kScaleToFit;
118                 break;
119             case 4:
120                 // 'sk_vj': 4 -> kVisualCenter/kDownscaleToFit
121                 v->fVAlign = Shaper::VAlign::kVisualCenter;
122                 v->fResize = Shaper::ResizePolicy::kDownscaleToFit;
123                 break;
124             default:
125                 abuilder.log(Logger::Level::kWarning, nullptr,
126                              "Ignoring unknown 'vj' value: %zu", vj);
127                 break;
128             }
129         }
130     }
131 
132     const auto& parse_color = [] (const skjson::ArrayValue* jcolor,
133                                   SkColor* c) {
134         if (!jcolor) {
135             return false;
136         }
137 
138         VectorValue color_vec;
139         if (!skottie::Parse(*jcolor, &color_vec)) {
140             return false;
141         }
142 
143         *c = color_vec;
144         return true;
145     };
146 
147     v->fHasFill   = parse_color((*jtxt)["fc"], &v->fFillColor);
148     v->fHasStroke = parse_color((*jtxt)["sc"], &v->fStrokeColor);
149 
150     if (v->fHasStroke) {
151         v->fStrokeWidth = ParseDefault((*jtxt)["sw"], 1.0f);
152         v->fPaintOrder  = ParseDefault((*jtxt)["of"], true)
153                 ? TextPaintOrder::kFillStroke
154                 : TextPaintOrder::kStrokeFill;
155     }
156 
157     return true;
158 }
159 
160 }  // namespace skottie::internal
161