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 | |
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 | |
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 | |