diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 21d438c4a7b41..d0182f4580ab0 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -354,13 +354,11 @@ FILE: ../../../flutter/lib/ui/text/asset_manager_font_provider.cc FILE: ../../../flutter/lib/ui/text/asset_manager_font_provider.h FILE: ../../../flutter/lib/ui/text/font_collection.cc FILE: ../../../flutter/lib/ui/text/font_collection.h -FILE: ../../../flutter/lib/ui/text/line_metrics.cc FILE: ../../../flutter/lib/ui/text/line_metrics.h FILE: ../../../flutter/lib/ui/text/paragraph.cc FILE: ../../../flutter/lib/ui/text/paragraph.h FILE: ../../../flutter/lib/ui/text/paragraph_builder.cc FILE: ../../../flutter/lib/ui/text/paragraph_builder.h -FILE: ../../../flutter/lib/ui/text/text_box.cc FILE: ../../../flutter/lib/ui/text/text_box.h FILE: ../../../flutter/lib/ui/ui.dart FILE: ../../../flutter/lib/ui/ui_dart_state.cc diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 4cc81dff2154d..c904141b338e8 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -81,13 +81,11 @@ source_set("ui") { "text/asset_manager_font_provider.h", "text/font_collection.cc", "text/font_collection.h", - "text/line_metrics.cc", "text/line_metrics.h", "text/paragraph.cc", "text/paragraph.h", "text/paragraph_builder.cc", "text/paragraph_builder.h", - "text/text_box.cc", "text/text_box.h", "ui_dart_state.cc", "ui_dart_state.h", diff --git a/lib/ui/text.dart b/lib/ui/text.dart index bba18ded716be..01b4b8c754beb 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -1306,16 +1306,6 @@ class TextBox { this.direction, ); - @pragma('vm:entry-point') - // ignore: unused_element - TextBox._( - this.left, - this.top, - this.right, - this.bottom, - int directionIndex, - ) : direction = TextDirection.values[directionIndex]; - /// The left edge of the text box, irrespective of direction. /// /// To get the leading edge (which may depend on the [direction]), consider [start]. @@ -1764,20 +1754,6 @@ class LineMetrics { this.lineNumber, }); - @pragma('vm:entry-point') - // ignore: unused_element - LineMetrics._( - this.hardBreak, - this.ascent, - this.descent, - this.unscaledAscent, - this.height, - this.width, - this.left, - this.baseline, - this.lineNumber, - ); - /// True if this line ends with an explicit line break (e.g. '\n') or is the end /// of the paragraph. False otherwise. final bool hardBreak; @@ -1843,6 +1819,39 @@ class LineMetrics { /// /// For example, the first line is line 0, second line is line 1. final int lineNumber; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is LineMetrics + && other.hardBreak == hardBreak + && other.ascent == ascent + && other.descent == descent + && other.unscaledAscent == unscaledAscent + && other.height == height + && other.width == width + && other.left == left + && other.baseline == baseline + && other.lineNumber == lineNumber; + } + + @override + int get hashCode => hashValues(hardBreak, ascent, descent, unscaledAscent, height, width, left, baseline, lineNumber); + + @override + String toString() { + return 'LineMetrics(hardBreak: $hardBreak, ' + 'ascent: $ascent, ' + 'descent: $descent, ' + 'unscaledAscent: $unscaledAscent, ' + 'height: $height, ' + 'width: $width, ' + 'left: $left, ' + 'baseline: $baseline, ' + 'lineNumber: $lineNumber)'; + } } /// A paragraph of text. @@ -1914,6 +1923,21 @@ class Paragraph extends NativeFieldWrapperClass2 { void layout(ParagraphConstraints constraints) => _layout(constraints.width); void _layout(double width) native 'Paragraph_layout'; + List _decodeTextBoxes(Float32List encoded) { + final int count = encoded.length ~/ 5; + final List boxes = List(count); + int position = 0; + for (int index = 0; index < count; index += 1) { + boxes[index] = TextBox.fromLTRBD( + encoded[position++], + encoded[position++], + encoded[position++], + encoded[position++], + TextDirection.values[encoded[position++].toInt()], + ); + } + return boxes; + } /// Returns a list of text boxes that enclose the given text range. /// /// The [boxHeightStyle] and [boxWidthStyle] parameters allow customization @@ -1930,9 +1954,10 @@ class Paragraph extends NativeFieldWrapperClass2 { List getBoxesForRange(int start, int end, {BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight}) { assert(boxHeightStyle != null); assert(boxWidthStyle != null); - return _getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index); + return _decodeTextBoxes(_getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index)); } - List _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle) native 'Paragraph_getRectsForRange'; + // See paragraph.cc for the layout of this return value. + Float32List _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle) native 'Paragraph_getRectsForRange'; /// Returns a list of text boxes that enclose all placeholders in the paragraph. /// @@ -1940,7 +1965,10 @@ class Paragraph extends NativeFieldWrapperClass2 { /// /// Coordinates of the [TextBox] are relative to the upper-left corner of the paragraph, /// where positive y values indicate down. - List getBoxesForPlaceholders() native 'Paragraph_getRectsForPlaceholders'; + List getBoxesForPlaceholders() { + return _decodeTextBoxes(_getBoxesForPlaceholders()); + } + Float32List _getBoxesForPlaceholders() native 'Paragraph_getRectsForPlaceholders'; /// Returns the text position closest to the given offset. TextPosition getPositionForOffset(Offset offset) { @@ -1987,7 +2015,27 @@ class Paragraph extends NativeFieldWrapperClass2 { /// /// This can potentially return a large amount of data, so it is not recommended /// to repeatedly call this. Instead, cache the results. - List computeLineMetrics() native 'Paragraph_computeLineMetrics'; + List computeLineMetrics() { + final Float64List encoded = _computeLineMetrics(); + final int count = encoded.length ~/ 9; + int position = 0; + final List metrics = List(count); + for (int index = 0; index < metrics.length; index += 1) { + metrics[index] = LineMetrics( + hardBreak: encoded[position++] != 0, + ascent: encoded[position++], + descent: encoded[position++], + unscaledAscent: encoded[position++], + height: encoded[position++], + width: encoded[position++], + left: encoded[position++], + baseline: encoded[position++], + lineNumber: encoded[position++].toInt(), + ); + } + return metrics; + } + Float64List _computeLineMetrics() native 'Paragraph_computeLineMetrics'; } /// Builds a [Paragraph] containing text with the given styling information. @@ -2195,7 +2243,12 @@ class ParagraphBuilder extends NativeFieldWrapperClass2 { /// /// After calling this function, the paragraph builder object is invalid and /// cannot be used further. - Paragraph build() native 'ParagraphBuilder_build'; + Paragraph build() { + final Paragraph paragraph = Paragraph._(); + _build(paragraph); + return paragraph; + } + void _build(Paragraph outParagraph) native 'ParagraphBuilder_build'; } /// Loads a font from a buffer and makes it available for rendering text. diff --git a/lib/ui/text/line_metrics.cc b/lib/ui/text/line_metrics.cc deleted file mode 100644 index a224e9b29b66c..0000000000000 --- a/lib/ui/text/line_metrics.cc +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/lib/ui/text/line_metrics.h" - -#include "flutter/fml/logging.h" -#include "third_party/tonic/dart_class_library.h" -#include "third_party/tonic/dart_state.h" -#include "third_party/tonic/logging/dart_error.h" - -using namespace flutter; - -namespace tonic { - -namespace { - -Dart_Handle GetLineMetricsType() { - DartClassLibrary& class_library = DartState::Current()->class_library(); - Dart_Handle type = - Dart_HandleFromPersistent(class_library.GetClass("ui", "LineMetrics")); - FML_DCHECK(!LogIfError(type)); - return type; -} - -} // anonymous namespace - -Dart_Handle DartConverter::ToDart( - const flutter::LineMetrics& val) { - constexpr int argc = 9; - - Dart_Handle argv[argc] = { - tonic::ToDart(*val.hard_break), tonic::ToDart(*val.ascent), - tonic::ToDart(*val.descent), tonic::ToDart(*val.unscaled_ascent), - // We add then round to get the height. The - // definition of height here is different - // than the one in LibTxt. - tonic::ToDart(round(*val.ascent + *val.descent)), - tonic::ToDart(*val.width), tonic::ToDart(*val.left), - tonic::ToDart(*val.baseline), tonic::ToDart(*val.line_number)}; - return Dart_New(GetLineMetricsType(), tonic::ToDart("_"), argc, argv); -} - -Dart_Handle DartListFactory::NewList(intptr_t length) { - return Dart_NewListOfType(GetLineMetricsType(), length); -} - -} // namespace tonic diff --git a/lib/ui/text/line_metrics.h b/lib/ui/text/line_metrics.h index bcd53c3d276a9..f5f656cd816f9 100644 --- a/lib/ui/text/line_metrics.h +++ b/lib/ui/text/line_metrics.h @@ -59,17 +59,4 @@ struct LineMetrics { } // namespace flutter -namespace tonic { -template <> -struct DartConverter { - static Dart_Handle ToDart(const flutter::LineMetrics& val); -}; - -template <> -struct DartListFactory { - static Dart_Handle NewList(intptr_t length); -}; - -} // namespace tonic - #endif // FLUTTER_LIB_UI_TEXT_LINE_METRICS_H_ diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 2618c3f08406a..92800db2ebd67 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -94,28 +94,40 @@ void Paragraph::paint(Canvas* canvas, double x, double y) { m_paragraph->Paint(sk_canvas, x, y); } -std::vector Paragraph::getRectsForRange(unsigned start, - unsigned end, - unsigned boxHeightStyle, - unsigned boxWidthStyle) { - std::vector result; +static tonic::Float32List EncodeTextBoxes( + const std::vector& boxes) { + // Layout: + // First value is the number of values. + // Then there are boxes.size() groups of 5 which are LTRBD, where D is the + // text direction index. + tonic::Float32List result( + Dart_NewTypedData(Dart_TypedData_kFloat32, boxes.size() * 5)); + unsigned long position = 0; + for (unsigned long i = 0; i < boxes.size(); i++) { + const txt::Paragraph::TextBox& box = boxes[i]; + result[position++] = box.rect.fLeft; + result[position++] = box.rect.fTop; + result[position++] = box.rect.fRight; + result[position++] = box.rect.fBottom; + result[position++] = static_cast(box.direction); + } + return result; +} + +tonic::Float32List Paragraph::getRectsForRange(unsigned start, + unsigned end, + unsigned boxHeightStyle, + unsigned boxWidthStyle) { std::vector boxes = m_paragraph->GetRectsForRange( start, end, static_cast(boxHeightStyle), static_cast(boxWidthStyle)); - for (const txt::Paragraph::TextBox& box : boxes) { - result.emplace_back(box.rect, static_cast(box.direction)); - } - return result; + return EncodeTextBoxes(boxes); } -std::vector Paragraph::getRectsForPlaceholders() { - std::vector result; +tonic::Float32List Paragraph::getRectsForPlaceholders() { std::vector boxes = m_paragraph->GetRectsForPlaceholders(); - for (const txt::Paragraph::TextBox& box : boxes) { - result.emplace_back(box.rect, static_cast(box.direction)); - } - return result; + return EncodeTextBoxes(boxes); } Dart_Handle Paragraph::getPositionForOffset(double dx, double dy) { @@ -152,14 +164,31 @@ Dart_Handle Paragraph::getLineBoundary(unsigned offset) { return result; } -std::vector Paragraph::computeLineMetrics() { - std::vector result; +tonic::Float64List Paragraph::computeLineMetrics() { std::vector metrics = m_paragraph->GetLineMetrics(); - for (txt::LineMetrics& line : metrics) { - result.emplace_back(&line.hard_break, &line.ascent, &line.descent, - &line.unscaled_ascent, &line.height, &line.width, - &line.left, &line.baseline, &line.line_number); + + // Layout: + // boxes.size() groups of 9 which are the line metrics + // properties + tonic::Float64List result( + Dart_NewTypedData(Dart_TypedData_kFloat64, metrics.size() * 9)); + unsigned long position = 0; + for (unsigned long i = 0; i < metrics.size(); i++) { + const txt::LineMetrics& line = metrics[i]; + result[position++] = static_cast(line.hard_break); + result[position++] = line.ascent; + result[position++] = line.descent; + result[position++] = line.unscaled_ascent; + // We add then round to get the height. The + // definition of height here is different + // than the one in LibTxt. + result[position++] = round(line.ascent + line.descent); + result[position++] = line.width; + result[position++] = line.left; + result[position++] = line.baseline; + result[position++] = static_cast(line.line_number); } + return result; } diff --git a/lib/ui/text/paragraph.h b/lib/ui/text/paragraph.h index 7aea6079378e1..73c79d3c67de3 100644 --- a/lib/ui/text/paragraph.h +++ b/lib/ui/text/paragraph.h @@ -23,9 +23,10 @@ class Paragraph : public RefCountedDartWrappable { FML_FRIEND_MAKE_REF_COUNTED(Paragraph); public: - static fml::RefPtr Create( - std::unique_ptr paragraph) { - return fml::MakeRefCounted(std::move(paragraph)); + static void Create(Dart_Handle paragraph_handle, + std::unique_ptr txt_paragraph) { + auto paragraph = fml::MakeRefCounted(std::move(txt_paragraph)); + paragraph->AssociateWithDartWrapper(paragraph_handle); } ~Paragraph() override; @@ -42,15 +43,15 @@ class Paragraph : public RefCountedDartWrappable { void layout(double width); void paint(Canvas* canvas, double x, double y); - std::vector getRectsForRange(unsigned start, - unsigned end, - unsigned boxHeightStyle, - unsigned boxWidthStyle); - std::vector getRectsForPlaceholders(); + tonic::Float32List getRectsForRange(unsigned start, + unsigned end, + unsigned boxHeightStyle, + unsigned boxWidthStyle); + tonic::Float32List getRectsForPlaceholders(); Dart_Handle getPositionForOffset(double dx, double dy); Dart_Handle getWordBoundary(unsigned offset); Dart_Handle getLineBoundary(unsigned offset); - std::vector computeLineMetrics(); + tonic::Float64List computeLineMetrics(); size_t GetAllocationSize() override; diff --git a/lib/ui/text/paragraph_builder.cc b/lib/ui/text/paragraph_builder.cc index 51739fc2cf3fd..0b6eea6e0abfd 100644 --- a/lib/ui/text/paragraph_builder.cc +++ b/lib/ui/text/paragraph_builder.cc @@ -493,8 +493,8 @@ Dart_Handle ParagraphBuilder::addPlaceholder(double width, return Dart_Null(); } -fml::RefPtr ParagraphBuilder::build() { - return Paragraph::Create(m_paragraphBuilder->Build()); +void ParagraphBuilder::build(Dart_Handle paragraph_handle) { + Paragraph::Create(paragraph_handle, m_paragraphBuilder->Build()); } } // namespace flutter diff --git a/lib/ui/text/paragraph_builder.h b/lib/ui/text/paragraph_builder.h index eebc5e7f26868..4f4a00b5b94ab 100644 --- a/lib/ui/text/paragraph_builder.h +++ b/lib/ui/text/paragraph_builder.h @@ -69,7 +69,7 @@ class ParagraphBuilder : public RefCountedDartWrappable { double baseline_offset, unsigned baseline); - fml::RefPtr build(); + void build(Dart_Handle paragraph_handle); static void RegisterNatives(tonic::DartLibraryNatives* natives); diff --git a/lib/ui/text/text_box.cc b/lib/ui/text/text_box.cc deleted file mode 100644 index 99438cf4b87cb..0000000000000 --- a/lib/ui/text/text_box.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/lib/ui/text/text_box.h" - -#include "flutter/fml/logging.h" -#include "third_party/tonic/dart_class_library.h" -#include "third_party/tonic/dart_state.h" -#include "third_party/tonic/logging/dart_error.h" - -using namespace flutter; - -namespace tonic { - -namespace { - -Dart_Handle GetTextBoxType() { - DartClassLibrary& class_library = DartState::Current()->class_library(); - Dart_Handle type = - Dart_HandleFromPersistent(class_library.GetClass("ui", "TextBox")); - FML_DCHECK(!LogIfError(type)); - return type; -} - -} // anonymous namespace - -Dart_Handle DartConverter::ToDart( - const flutter::TextBox& val) { - constexpr int argc = 5; - Dart_Handle argv[argc] = { - tonic::ToDart(val.rect.fLeft), - tonic::ToDart(val.rect.fTop), - tonic::ToDart(val.rect.fRight), - tonic::ToDart(val.rect.fBottom), - tonic::ToDart(static_cast(val.direction)), - }; - return Dart_New(GetTextBoxType(), tonic::ToDart("_"), argc, argv); -} - -Dart_Handle DartListFactory::NewList(intptr_t length) { - return Dart_NewListOfType(GetTextBoxType(), length); -} - -} // namespace tonic diff --git a/testing/dart/paragraph_builder_test.dart b/testing/dart/paragraph_builder_test.dart index 81862dd9772bd..cf8ccce0bf8b1 100644 --- a/testing/dart/paragraph_builder_test.dart +++ b/testing/dart/paragraph_builder_test.dart @@ -8,6 +8,9 @@ import 'dart:ui'; import 'package:test/test.dart'; void main() { + // The actual values for font measurements will vary by platform slightly. + const double epsillon = 0.0001; + test('Should be able to build and layout a paragraph', () { final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); builder.addText('Hello'); @@ -25,4 +28,46 @@ void main() { paragraphBuilder.build(); paragraphBuilder.pushStyle(TextStyle()); }); + + test('GetRectsForRange smoke test', () { + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); + builder.addText('Hello'); + final Paragraph paragraph = builder.build(); + expect(paragraph, isNotNull); + + paragraph.layout(const ParagraphConstraints(width: 800.0)); + expect(paragraph.width, isNonZero); + expect(paragraph.height, isNonZero); + + final List boxes = paragraph.getBoxesForRange(0, 3); + expect(boxes.length, 1); + expect(boxes.first.left, 0); + expect(boxes.first.top, closeTo(0, epsillon)); + expect(boxes.first.right, closeTo(42, epsillon)); + expect(boxes.first.bottom, closeTo(14, epsillon)); + expect(boxes.first.direction, TextDirection.ltr); + }); + + test('LineMetrics smoke test', () { + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); + builder.addText('Hello'); + final Paragraph paragraph = builder.build(); + expect(paragraph, isNotNull); + + paragraph.layout(const ParagraphConstraints(width: 800.0)); + expect(paragraph.width, isNonZero); + expect(paragraph.height, isNonZero); + + final List metrics = paragraph.computeLineMetrics(); + expect(metrics.length, 1); + expect(metrics.first.hardBreak, true); + expect(metrics.first.ascent, closeTo(11.200042724609375, epsillon)); + expect(metrics.first.descent, closeTo(2.799957275390625, epsillon)); + expect(metrics.first.unscaledAscent, closeTo(11.200042724609375, epsillon)); + expect(metrics.first.height, 14.0); + expect(metrics.first.width, 70.0); + expect(metrics.first.left, 0.0); + expect(metrics.first.baseline, closeTo(11.200042724609375, epsillon)); + expect(metrics.first.lineNumber, 0); + }); }