1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "flutter/lib/ui/text/paragraph_builder.h" |
6 | |
7 | #include "flutter/common/settings.h" |
8 | #include "flutter/common/task_runners.h" |
9 | #include "flutter/fml/logging.h" |
10 | #include "flutter/fml/task_runner.h" |
11 | #include "flutter/lib/ui/text/font_collection.h" |
12 | #include "flutter/lib/ui/ui_dart_state.h" |
13 | #include "flutter/lib/ui/window/platform_configuration.h" |
14 | #include "flutter/third_party/txt/src/txt/font_style.h" |
15 | #include "flutter/third_party/txt/src/txt/font_weight.h" |
16 | #include "flutter/third_party/txt/src/txt/paragraph_style.h" |
17 | #include "flutter/third_party/txt/src/txt/text_baseline.h" |
18 | #include "flutter/third_party/txt/src/txt/text_decoration.h" |
19 | #include "flutter/third_party/txt/src/txt/text_style.h" |
20 | #include "third_party/icu/source/common/unicode/ustring.h" |
21 | #include "third_party/skia/include/core/SkColor.h" |
22 | #include "third_party/tonic/converter/dart_converter.h" |
23 | #include "third_party/tonic/dart_args.h" |
24 | #include "third_party/tonic/dart_binding_macros.h" |
25 | #include "third_party/tonic/dart_library_natives.h" |
26 | #include "third_party/tonic/typed_data/dart_byte_data.h" |
27 | |
28 | namespace flutter { |
29 | namespace { |
30 | |
31 | // TextStyle |
32 | |
33 | const int tsColorIndex = 1; |
34 | const int tsTextDecorationIndex = 2; |
35 | const int tsTextDecorationColorIndex = 3; |
36 | const int tsTextDecorationStyleIndex = 4; |
37 | const int tsFontWeightIndex = 5; |
38 | const int tsFontStyleIndex = 6; |
39 | const int tsTextBaselineIndex = 7; |
40 | const int tsTextDecorationThicknessIndex = 8; |
41 | const int tsFontFamilyIndex = 9; |
42 | const int tsFontSizeIndex = 10; |
43 | const int tsLetterSpacingIndex = 11; |
44 | const int tsWordSpacingIndex = 12; |
45 | const int tsHeightIndex = 13; |
46 | const int tsLocaleIndex = 14; |
47 | const int tsBackgroundIndex = 15; |
48 | const int tsForegroundIndex = 16; |
49 | const int tsTextShadowsIndex = 17; |
50 | const int tsFontFeaturesIndex = 18; |
51 | |
52 | const int tsColorMask = 1 << tsColorIndex; |
53 | const int tsTextDecorationMask = 1 << tsTextDecorationIndex; |
54 | const int tsTextDecorationColorMask = 1 << tsTextDecorationColorIndex; |
55 | const int tsTextDecorationStyleMask = 1 << tsTextDecorationStyleIndex; |
56 | const int tsTextDecorationThicknessMask = 1 << tsTextDecorationThicknessIndex; |
57 | const int tsFontWeightMask = 1 << tsFontWeightIndex; |
58 | const int tsFontStyleMask = 1 << tsFontStyleIndex; |
59 | const int tsTextBaselineMask = 1 << tsTextBaselineIndex; |
60 | const int tsFontFamilyMask = 1 << tsFontFamilyIndex; |
61 | const int tsFontSizeMask = 1 << tsFontSizeIndex; |
62 | const int tsLetterSpacingMask = 1 << tsLetterSpacingIndex; |
63 | const int tsWordSpacingMask = 1 << tsWordSpacingIndex; |
64 | const int tsHeightMask = 1 << tsHeightIndex; |
65 | const int tsLocaleMask = 1 << tsLocaleIndex; |
66 | const int tsBackgroundMask = 1 << tsBackgroundIndex; |
67 | const int tsForegroundMask = 1 << tsForegroundIndex; |
68 | const int tsTextShadowsMask = 1 << tsTextShadowsIndex; |
69 | const int tsFontFeaturesMask = 1 << tsFontFeaturesIndex; |
70 | |
71 | // ParagraphStyle |
72 | |
73 | const int psTextAlignIndex = 1; |
74 | const int psTextDirectionIndex = 2; |
75 | const int psFontWeightIndex = 3; |
76 | const int psFontStyleIndex = 4; |
77 | const int psMaxLinesIndex = 5; |
78 | const int psTextHeightBehaviorIndex = 6; |
79 | const int psFontFamilyIndex = 7; |
80 | const int psFontSizeIndex = 8; |
81 | const int psHeightIndex = 9; |
82 | const int psStrutStyleIndex = 10; |
83 | const int psEllipsisIndex = 11; |
84 | const int psLocaleIndex = 12; |
85 | |
86 | const int psTextAlignMask = 1 << psTextAlignIndex; |
87 | const int psTextDirectionMask = 1 << psTextDirectionIndex; |
88 | const int psFontWeightMask = 1 << psFontWeightIndex; |
89 | const int psFontStyleMask = 1 << psFontStyleIndex; |
90 | const int psMaxLinesMask = 1 << psMaxLinesIndex; |
91 | const int psFontFamilyMask = 1 << psFontFamilyIndex; |
92 | const int psFontSizeMask = 1 << psFontSizeIndex; |
93 | const int psHeightMask = 1 << psHeightIndex; |
94 | const int psTextHeightBehaviorMask = 1 << psTextHeightBehaviorIndex; |
95 | const int psStrutStyleMask = 1 << psStrutStyleIndex; |
96 | const int psEllipsisMask = 1 << psEllipsisIndex; |
97 | const int psLocaleMask = 1 << psLocaleIndex; |
98 | |
99 | // TextShadows decoding |
100 | |
101 | constexpr uint32_t kColorDefault = 0xFF000000; |
102 | constexpr uint32_t kBytesPerShadow = 16; |
103 | constexpr uint32_t kShadowPropertiesCount = 4; |
104 | constexpr uint32_t kColorOffset = 0; |
105 | constexpr uint32_t kXOffset = 1; |
106 | constexpr uint32_t kYOffset = 2; |
107 | constexpr uint32_t kBlurOffset = 3; |
108 | |
109 | // FontFeature decoding |
110 | constexpr uint32_t kBytesPerFontFeature = 8; |
111 | constexpr uint32_t kFontFeatureTagLength = 4; |
112 | |
113 | // Strut decoding |
114 | const int sFontWeightIndex = 0; |
115 | const int sFontStyleIndex = 1; |
116 | const int sFontFamilyIndex = 2; |
117 | const int sFontSizeIndex = 3; |
118 | const int sHeightIndex = 4; |
119 | const int sLeadingIndex = 5; |
120 | const int sForceStrutHeightIndex = 6; |
121 | |
122 | const int sFontWeightMask = 1 << sFontWeightIndex; |
123 | const int sFontStyleMask = 1 << sFontStyleIndex; |
124 | const int sFontFamilyMask = 1 << sFontFamilyIndex; |
125 | const int sFontSizeMask = 1 << sFontSizeIndex; |
126 | const int sHeightMask = 1 << sHeightIndex; |
127 | const int sLeadingMask = 1 << sLeadingIndex; |
128 | const int sForceStrutHeightMask = 1 << sForceStrutHeightIndex; |
129 | |
130 | } // namespace |
131 | |
132 | static void ParagraphBuilder_constructor(Dart_NativeArguments args) { |
133 | UIDartState::ThrowIfUIOperationsProhibited(); |
134 | DartCallConstructor(&ParagraphBuilder::create, args); |
135 | } |
136 | |
137 | IMPLEMENT_WRAPPERTYPEINFO(ui, ParagraphBuilder); |
138 | |
139 | #define FOR_EACH_BINDING(V) \ |
140 | V(ParagraphBuilder, pushStyle) \ |
141 | V(ParagraphBuilder, pop) \ |
142 | V(ParagraphBuilder, addText) \ |
143 | V(ParagraphBuilder, addPlaceholder) \ |
144 | V(ParagraphBuilder, build) |
145 | |
146 | FOR_EACH_BINDING(DART_NATIVE_CALLBACK) |
147 | |
148 | void ParagraphBuilder::RegisterNatives(tonic::DartLibraryNatives* natives) { |
149 | natives->Register( |
150 | {{"ParagraphBuilder_constructor" , ParagraphBuilder_constructor, 9, true}, |
151 | FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); |
152 | } |
153 | |
154 | fml::RefPtr<ParagraphBuilder> ParagraphBuilder::create( |
155 | tonic::Int32List& encoded, |
156 | Dart_Handle strutData, |
157 | const std::string& fontFamily, |
158 | const std::vector<std::string>& strutFontFamilies, |
159 | double fontSize, |
160 | double height, |
161 | const std::u16string& ellipsis, |
162 | const std::string& locale) { |
163 | return fml::MakeRefCounted<ParagraphBuilder>(encoded, strutData, fontFamily, |
164 | strutFontFamilies, fontSize, |
165 | height, ellipsis, locale); |
166 | } |
167 | |
168 | // returns true if there is a font family defined. Font family is the only |
169 | // parameter passed directly. |
170 | void decodeStrut(Dart_Handle strut_data, |
171 | const std::vector<std::string>& strut_font_families, |
172 | txt::ParagraphStyle& paragraph_style) { // NOLINT |
173 | if (strut_data == Dart_Null()) { |
174 | return; |
175 | } |
176 | |
177 | tonic::DartByteData byte_data(strut_data); |
178 | if (byte_data.length_in_bytes() == 0) { |
179 | return; |
180 | } |
181 | paragraph_style.strut_enabled = true; |
182 | |
183 | const uint8_t* uint8_data = static_cast<const uint8_t*>(byte_data.data()); |
184 | uint8_t mask = uint8_data[0]; |
185 | |
186 | // Data is stored in order of increasing size, eg, 8 bit ints will be before |
187 | // any 32 bit ints. In addition, the order of decoding is the same order |
188 | // as it is encoded, and the order is used to maintain consistency. |
189 | size_t byte_count = 1; |
190 | if (mask & sFontWeightMask) { |
191 | paragraph_style.strut_font_weight = |
192 | static_cast<txt::FontWeight>(uint8_data[byte_count++]); |
193 | } |
194 | if (mask & sFontStyleMask) { |
195 | paragraph_style.strut_font_style = |
196 | static_cast<txt::FontStyle>(uint8_data[byte_count++]); |
197 | } |
198 | |
199 | std::vector<float> float_data; |
200 | float_data.resize((byte_data.length_in_bytes() - byte_count) / 4); |
201 | memcpy(float_data.data(), |
202 | static_cast<const char*>(byte_data.data()) + byte_count, |
203 | byte_data.length_in_bytes() - byte_count); |
204 | size_t float_count = 0; |
205 | if (mask & sFontSizeMask) { |
206 | paragraph_style.strut_font_size = float_data[float_count++]; |
207 | } |
208 | if (mask & sHeightMask) { |
209 | paragraph_style.strut_height = float_data[float_count++]; |
210 | paragraph_style.strut_has_height_override = true; |
211 | } |
212 | if (mask & sLeadingMask) { |
213 | paragraph_style.strut_leading = float_data[float_count++]; |
214 | } |
215 | if (mask & sForceStrutHeightMask) { |
216 | // The boolean is stored as the last bit in the bitmask. |
217 | paragraph_style.force_strut_height = (mask & 1 << 7) != 0; |
218 | } |
219 | |
220 | if (mask & sFontFamilyMask) { |
221 | paragraph_style.strut_font_families = strut_font_families; |
222 | } else { |
223 | // Provide an empty font name so that the platform default font will be |
224 | // used. |
225 | paragraph_style.strut_font_families.push_back("" ); |
226 | } |
227 | } |
228 | |
229 | ParagraphBuilder::ParagraphBuilder( |
230 | tonic::Int32List& encoded, |
231 | Dart_Handle strutData, |
232 | const std::string& fontFamily, |
233 | const std::vector<std::string>& strutFontFamilies, |
234 | double fontSize, |
235 | double height, |
236 | const std::u16string& ellipsis, |
237 | const std::string& locale) { |
238 | int32_t mask = encoded[0]; |
239 | txt::ParagraphStyle style; |
240 | |
241 | if (mask & psTextAlignMask) { |
242 | style.text_align = txt::TextAlign(encoded[psTextAlignIndex]); |
243 | } |
244 | |
245 | if (mask & psTextDirectionMask) { |
246 | style.text_direction = txt::TextDirection(encoded[psTextDirectionIndex]); |
247 | } |
248 | |
249 | if (mask & psFontWeightMask) { |
250 | style.font_weight = |
251 | static_cast<txt::FontWeight>(encoded[psFontWeightIndex]); |
252 | } |
253 | |
254 | if (mask & psFontStyleMask) { |
255 | style.font_style = static_cast<txt::FontStyle>(encoded[psFontStyleIndex]); |
256 | } |
257 | |
258 | if (mask & psFontFamilyMask) { |
259 | style.font_family = fontFamily; |
260 | } |
261 | |
262 | if (mask & psFontSizeMask) { |
263 | style.font_size = fontSize; |
264 | } |
265 | |
266 | if (mask & psHeightMask) { |
267 | style.height = height; |
268 | style.has_height_override = true; |
269 | } |
270 | |
271 | if (mask & psTextHeightBehaviorMask) { |
272 | style.text_height_behavior = encoded[psTextHeightBehaviorIndex]; |
273 | } |
274 | |
275 | if (mask & psStrutStyleMask) { |
276 | decodeStrut(strutData, strutFontFamilies, style); |
277 | } |
278 | |
279 | if (mask & psMaxLinesMask) { |
280 | style.max_lines = encoded[psMaxLinesIndex]; |
281 | } |
282 | |
283 | if (mask & psEllipsisMask) { |
284 | style.ellipsis = ellipsis; |
285 | } |
286 | |
287 | if (mask & psLocaleMask) { |
288 | style.locale = locale; |
289 | } |
290 | |
291 | FontCollection& font_collection = UIDartState::Current() |
292 | ->platform_configuration() |
293 | ->client() |
294 | ->GetFontCollection(); |
295 | |
296 | #if FLUTTER_ENABLE_SKSHAPER |
297 | #define FLUTTER_PARAGRAPH_BUILDER txt::ParagraphBuilder::CreateSkiaBuilder |
298 | #else |
299 | #define FLUTTER_PARAGRAPH_BUILDER txt::ParagraphBuilder::CreateTxtBuilder |
300 | #endif |
301 | |
302 | m_paragraphBuilder = |
303 | FLUTTER_PARAGRAPH_BUILDER(style, font_collection.GetFontCollection()); |
304 | } |
305 | |
306 | ParagraphBuilder::~ParagraphBuilder() = default; |
307 | |
308 | void decodeTextShadows( |
309 | Dart_Handle shadows_data, |
310 | std::vector<txt::TextShadow>& decoded_shadows) { // NOLINT |
311 | decoded_shadows.clear(); |
312 | |
313 | tonic::DartByteData byte_data(shadows_data); |
314 | FML_CHECK(byte_data.length_in_bytes() % kBytesPerShadow == 0); |
315 | |
316 | const uint32_t* uint_data = static_cast<const uint32_t*>(byte_data.data()); |
317 | const float* float_data = static_cast<const float*>(byte_data.data()); |
318 | |
319 | size_t shadow_count = byte_data.length_in_bytes() / kBytesPerShadow; |
320 | size_t shadow_count_offset = 0; |
321 | for (size_t shadow_index = 0; shadow_index < shadow_count; ++shadow_index) { |
322 | shadow_count_offset = shadow_index * kShadowPropertiesCount; |
323 | SkColor color = |
324 | uint_data[shadow_count_offset + kColorOffset] ^ kColorDefault; |
325 | decoded_shadows.emplace_back( |
326 | color, |
327 | SkPoint::Make(float_data[shadow_count_offset + kXOffset], |
328 | float_data[shadow_count_offset + kYOffset]), |
329 | float_data[shadow_count_offset + kBlurOffset]); |
330 | } |
331 | } |
332 | |
333 | void decodeFontFeatures(Dart_Handle font_features_data, |
334 | txt::FontFeatures& font_features) { // NOLINT |
335 | tonic::DartByteData byte_data(font_features_data); |
336 | FML_CHECK(byte_data.length_in_bytes() % kBytesPerFontFeature == 0); |
337 | |
338 | size_t feature_count = byte_data.length_in_bytes() / kBytesPerFontFeature; |
339 | for (size_t feature_index = 0; feature_index < feature_count; |
340 | ++feature_index) { |
341 | size_t feature_offset = feature_index * kBytesPerFontFeature; |
342 | const char* feature_bytes = |
343 | static_cast<const char*>(byte_data.data()) + feature_offset; |
344 | std::string tag(feature_bytes, kFontFeatureTagLength); |
345 | int32_t value = *(reinterpret_cast<const int32_t*>(feature_bytes + |
346 | kFontFeatureTagLength)); |
347 | font_features.SetFeature(tag, value); |
348 | } |
349 | } |
350 | |
351 | void ParagraphBuilder::pushStyle(tonic::Int32List& encoded, |
352 | const std::vector<std::string>& fontFamilies, |
353 | double fontSize, |
354 | double letterSpacing, |
355 | double wordSpacing, |
356 | double height, |
357 | double decorationThickness, |
358 | const std::string& locale, |
359 | Dart_Handle background_objects, |
360 | Dart_Handle background_data, |
361 | Dart_Handle foreground_objects, |
362 | Dart_Handle foreground_data, |
363 | Dart_Handle shadows_data, |
364 | Dart_Handle font_features_data) { |
365 | FML_DCHECK(encoded.num_elements() == 8); |
366 | |
367 | int32_t mask = encoded[0]; |
368 | |
369 | // Set to use the properties of the previous style if the property is not |
370 | // explicitly given. |
371 | txt::TextStyle style = m_paragraphBuilder->PeekStyle(); |
372 | |
373 | // Only change the style property from the previous value if a new explicitly |
374 | // set value is available |
375 | if (mask & tsColorMask) { |
376 | style.color = encoded[tsColorIndex]; |
377 | } |
378 | |
379 | if (mask & tsTextDecorationMask) { |
380 | style.decoration = |
381 | static_cast<txt::TextDecoration>(encoded[tsTextDecorationIndex]); |
382 | } |
383 | |
384 | if (mask & tsTextDecorationColorMask) { |
385 | style.decoration_color = encoded[tsTextDecorationColorIndex]; |
386 | } |
387 | |
388 | if (mask & tsTextDecorationStyleMask) { |
389 | style.decoration_style = static_cast<txt::TextDecorationStyle>( |
390 | encoded[tsTextDecorationStyleIndex]); |
391 | } |
392 | |
393 | if (mask & tsTextDecorationThicknessMask) { |
394 | style.decoration_thickness_multiplier = decorationThickness; |
395 | } |
396 | |
397 | if (mask & tsTextBaselineMask) { |
398 | // TODO(abarth): Implement TextBaseline. The CSS version of this |
399 | // property wasn't wired up either. |
400 | } |
401 | |
402 | if (mask & (tsFontWeightMask | tsFontStyleMask | tsFontSizeMask | |
403 | tsLetterSpacingMask | tsWordSpacingMask)) { |
404 | if (mask & tsFontWeightMask) { |
405 | style.font_weight = |
406 | static_cast<txt::FontWeight>(encoded[tsFontWeightIndex]); |
407 | } |
408 | |
409 | if (mask & tsFontStyleMask) { |
410 | style.font_style = static_cast<txt::FontStyle>(encoded[tsFontStyleIndex]); |
411 | } |
412 | |
413 | if (mask & tsFontSizeMask) { |
414 | style.font_size = fontSize; |
415 | } |
416 | |
417 | if (mask & tsLetterSpacingMask) { |
418 | style.letter_spacing = letterSpacing; |
419 | } |
420 | |
421 | if (mask & tsWordSpacingMask) { |
422 | style.word_spacing = wordSpacing; |
423 | } |
424 | } |
425 | |
426 | if (mask & tsHeightMask) { |
427 | style.height = height; |
428 | style.has_height_override = true; |
429 | } |
430 | |
431 | if (mask & tsLocaleMask) { |
432 | style.locale = locale; |
433 | } |
434 | |
435 | if (mask & tsBackgroundMask) { |
436 | Paint background(background_objects, background_data); |
437 | if (background.paint()) { |
438 | style.has_background = true; |
439 | style.background = *background.paint(); |
440 | } |
441 | } |
442 | |
443 | if (mask & tsForegroundMask) { |
444 | Paint foreground(foreground_objects, foreground_data); |
445 | if (foreground.paint()) { |
446 | style.has_foreground = true; |
447 | style.foreground = *foreground.paint(); |
448 | } |
449 | } |
450 | |
451 | if (mask & tsTextShadowsMask) { |
452 | decodeTextShadows(shadows_data, style.text_shadows); |
453 | } |
454 | |
455 | if (mask & tsFontFamilyMask) { |
456 | // The child style's font families override the parent's font families. |
457 | // If the child's fonts are not available, then the font collection will |
458 | // use the system fallback fonts (not the parent's fonts). |
459 | style.font_families = fontFamilies; |
460 | } |
461 | |
462 | if (mask & tsFontFeaturesMask) { |
463 | decodeFontFeatures(font_features_data, style.font_features); |
464 | } |
465 | |
466 | m_paragraphBuilder->PushStyle(style); |
467 | } |
468 | |
469 | void ParagraphBuilder::pop() { |
470 | m_paragraphBuilder->Pop(); |
471 | } |
472 | |
473 | Dart_Handle ParagraphBuilder::addText(const std::u16string& text) { |
474 | if (text.empty()) { |
475 | return Dart_Null(); |
476 | } |
477 | |
478 | // Use ICU to validate the UTF-16 input. Calling u_strToUTF8 with a null |
479 | // output buffer will return U_BUFFER_OVERFLOW_ERROR if the input is well |
480 | // formed. |
481 | const UChar* text_ptr = reinterpret_cast<const UChar*>(text.data()); |
482 | UErrorCode error_code = U_ZERO_ERROR; |
483 | u_strToUTF8(nullptr, 0, nullptr, text_ptr, text.size(), &error_code); |
484 | if (error_code != U_BUFFER_OVERFLOW_ERROR) { |
485 | return tonic::ToDart("string is not well-formed UTF-16" ); |
486 | } |
487 | |
488 | m_paragraphBuilder->AddText(text); |
489 | |
490 | return Dart_Null(); |
491 | } |
492 | |
493 | Dart_Handle ParagraphBuilder::addPlaceholder(double width, |
494 | double height, |
495 | unsigned alignment, |
496 | double baseline_offset, |
497 | unsigned baseline) { |
498 | txt::PlaceholderRun placeholder_run( |
499 | width, height, static_cast<txt::PlaceholderAlignment>(alignment), |
500 | static_cast<txt::TextBaseline>(baseline), baseline_offset); |
501 | |
502 | m_paragraphBuilder->AddPlaceholder(placeholder_run); |
503 | |
504 | return Dart_Null(); |
505 | } |
506 | |
507 | void ParagraphBuilder::build(Dart_Handle paragraph_handle) { |
508 | Paragraph::Create(paragraph_handle, m_paragraphBuilder->Build()); |
509 | } |
510 | |
511 | } // namespace flutter |
512 | |