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
28namespace flutter {
29namespace {
30
31// TextStyle
32
33const int tsColorIndex = 1;
34const int tsTextDecorationIndex = 2;
35const int tsTextDecorationColorIndex = 3;
36const int tsTextDecorationStyleIndex = 4;
37const int tsFontWeightIndex = 5;
38const int tsFontStyleIndex = 6;
39const int tsTextBaselineIndex = 7;
40const int tsTextDecorationThicknessIndex = 8;
41const int tsFontFamilyIndex = 9;
42const int tsFontSizeIndex = 10;
43const int tsLetterSpacingIndex = 11;
44const int tsWordSpacingIndex = 12;
45const int tsHeightIndex = 13;
46const int tsLocaleIndex = 14;
47const int tsBackgroundIndex = 15;
48const int tsForegroundIndex = 16;
49const int tsTextShadowsIndex = 17;
50const int tsFontFeaturesIndex = 18;
51
52const int tsColorMask = 1 << tsColorIndex;
53const int tsTextDecorationMask = 1 << tsTextDecorationIndex;
54const int tsTextDecorationColorMask = 1 << tsTextDecorationColorIndex;
55const int tsTextDecorationStyleMask = 1 << tsTextDecorationStyleIndex;
56const int tsTextDecorationThicknessMask = 1 << tsTextDecorationThicknessIndex;
57const int tsFontWeightMask = 1 << tsFontWeightIndex;
58const int tsFontStyleMask = 1 << tsFontStyleIndex;
59const int tsTextBaselineMask = 1 << tsTextBaselineIndex;
60const int tsFontFamilyMask = 1 << tsFontFamilyIndex;
61const int tsFontSizeMask = 1 << tsFontSizeIndex;
62const int tsLetterSpacingMask = 1 << tsLetterSpacingIndex;
63const int tsWordSpacingMask = 1 << tsWordSpacingIndex;
64const int tsHeightMask = 1 << tsHeightIndex;
65const int tsLocaleMask = 1 << tsLocaleIndex;
66const int tsBackgroundMask = 1 << tsBackgroundIndex;
67const int tsForegroundMask = 1 << tsForegroundIndex;
68const int tsTextShadowsMask = 1 << tsTextShadowsIndex;
69const int tsFontFeaturesMask = 1 << tsFontFeaturesIndex;
70
71// ParagraphStyle
72
73const int psTextAlignIndex = 1;
74const int psTextDirectionIndex = 2;
75const int psFontWeightIndex = 3;
76const int psFontStyleIndex = 4;
77const int psMaxLinesIndex = 5;
78const int psTextHeightBehaviorIndex = 6;
79const int psFontFamilyIndex = 7;
80const int psFontSizeIndex = 8;
81const int psHeightIndex = 9;
82const int psStrutStyleIndex = 10;
83const int psEllipsisIndex = 11;
84const int psLocaleIndex = 12;
85
86const int psTextAlignMask = 1 << psTextAlignIndex;
87const int psTextDirectionMask = 1 << psTextDirectionIndex;
88const int psFontWeightMask = 1 << psFontWeightIndex;
89const int psFontStyleMask = 1 << psFontStyleIndex;
90const int psMaxLinesMask = 1 << psMaxLinesIndex;
91const int psFontFamilyMask = 1 << psFontFamilyIndex;
92const int psFontSizeMask = 1 << psFontSizeIndex;
93const int psHeightMask = 1 << psHeightIndex;
94const int psTextHeightBehaviorMask = 1 << psTextHeightBehaviorIndex;
95const int psStrutStyleMask = 1 << psStrutStyleIndex;
96const int psEllipsisMask = 1 << psEllipsisIndex;
97const int psLocaleMask = 1 << psLocaleIndex;
98
99// TextShadows decoding
100
101constexpr uint32_t kColorDefault = 0xFF000000;
102constexpr uint32_t kBytesPerShadow = 16;
103constexpr uint32_t kShadowPropertiesCount = 4;
104constexpr uint32_t kColorOffset = 0;
105constexpr uint32_t kXOffset = 1;
106constexpr uint32_t kYOffset = 2;
107constexpr uint32_t kBlurOffset = 3;
108
109// FontFeature decoding
110constexpr uint32_t kBytesPerFontFeature = 8;
111constexpr uint32_t kFontFeatureTagLength = 4;
112
113// Strut decoding
114const int sFontWeightIndex = 0;
115const int sFontStyleIndex = 1;
116const int sFontFamilyIndex = 2;
117const int sFontSizeIndex = 3;
118const int sHeightIndex = 4;
119const int sLeadingIndex = 5;
120const int sForceStrutHeightIndex = 6;
121
122const int sFontWeightMask = 1 << sFontWeightIndex;
123const int sFontStyleMask = 1 << sFontStyleIndex;
124const int sFontFamilyMask = 1 << sFontFamilyIndex;
125const int sFontSizeMask = 1 << sFontSizeIndex;
126const int sHeightMask = 1 << sHeightIndex;
127const int sLeadingMask = 1 << sLeadingIndex;
128const int sForceStrutHeightMask = 1 << sForceStrutHeightIndex;
129
130} // namespace
131
132static void ParagraphBuilder_constructor(Dart_NativeArguments args) {
133 UIDartState::ThrowIfUIOperationsProhibited();
134 DartCallConstructor(&ParagraphBuilder::create, args);
135}
136
137IMPLEMENT_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
146FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
147
148void ParagraphBuilder::RegisterNatives(tonic::DartLibraryNatives* natives) {
149 natives->Register(
150 {{"ParagraphBuilder_constructor", ParagraphBuilder_constructor, 9, true},
151 FOR_EACH_BINDING(DART_REGISTER_NATIVE)});
152}
153
154fml::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.
170void 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
229ParagraphBuilder::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
306ParagraphBuilder::~ParagraphBuilder() = default;
307
308void 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
333void 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
351void 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
469void ParagraphBuilder::pop() {
470 m_paragraphBuilder->Pop();
471}
472
473Dart_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
493Dart_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
507void ParagraphBuilder::build(Dart_Handle paragraph_handle) {
508 Paragraph::Create(paragraph_handle, m_paragraphBuilder->Build());
509}
510
511} // namespace flutter
512