mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Text inline widget LibTxt/dart:ui implementation (flutter/engine#8207)
This commit is contained in:
parent
b1012f5dc8
commit
f7100ebdd4
@ -1059,6 +1059,8 @@ FILE: ../../../flutter/third_party/txt/src/txt/paragraph_builder.cc
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/paragraph_builder.h
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/paragraph_style.cc
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/paragraph_style.h
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/placeholder_run.cc
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/placeholder_run.h
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/styled_runs.cc
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/styled_runs.h
|
||||
FILE: ../../../flutter/third_party/txt/src/txt/test_font_manager.cc
|
||||
|
||||
@ -1381,72 +1381,116 @@ class ParagraphConstraints {
|
||||
/// Defines various ways to vertically bound the boxes returned by
|
||||
/// [Paragraph.getBoxesForRange].
|
||||
enum BoxHeightStyle {
|
||||
/// Provide tight bounding boxes that fit heights per run. This style may result
|
||||
/// in uneven bounding boxes that do not nicely connect with adjacent boxes.
|
||||
tight,
|
||||
/// Provide tight bounding boxes that fit heights per run. This style may result
|
||||
/// in uneven bounding boxes that do not nicely connect with adjacent boxes.
|
||||
tight,
|
||||
|
||||
/// The height of the boxes will be the maximum height of all runs in the
|
||||
/// line. All boxes in the same line will be the same height. This does not
|
||||
/// guarantee that the boxes will cover the entire vertical height of the line
|
||||
/// when there is additional line spacing.
|
||||
///
|
||||
/// See [RectHeightStyle.includeLineSpacingTop], [RectHeightStyle.includeLineSpacingMiddle],
|
||||
/// and [RectHeightStyle.includeLineSpacingBottom] for styles that will cover
|
||||
/// the entire line.
|
||||
max,
|
||||
/// The height of the boxes will be the maximum height of all runs in the
|
||||
/// line. All boxes in the same line will be the same height.
|
||||
///
|
||||
/// This does not guarantee that the boxes will cover the entire vertical height of the line
|
||||
/// when there is additional line spacing.
|
||||
///
|
||||
/// See [RectHeightStyle.includeLineSpacingTop], [RectHeightStyle.includeLineSpacingMiddle],
|
||||
/// and [RectHeightStyle.includeLineSpacingBottom] for styles that will cover
|
||||
/// the entire line.
|
||||
max,
|
||||
|
||||
/// Extends the top and bottom edge of the bounds to fully cover any line
|
||||
/// spacing.
|
||||
///
|
||||
/// The top and bottom of each box will cover half of the
|
||||
/// space above and half of the space below the line.
|
||||
///
|
||||
/// {@template flutter.dart:ui.boxHeightStyle.includeLineSpacing}
|
||||
/// The top edge of each line should be the same as the bottom edge
|
||||
/// of the line above. There should be no gaps in vertical coverage given any
|
||||
/// amount of line spacing. Line spacing is not included above the first line
|
||||
/// and below the last line due to no additional space present there.
|
||||
/// {@endtemplate}
|
||||
includeLineSpacingMiddle,
|
||||
/// Extends the top and bottom edge of the bounds to fully cover any line
|
||||
/// spacing.
|
||||
///
|
||||
/// The top and bottom of each box will cover half of the
|
||||
/// space above and half of the space below the line.
|
||||
///
|
||||
/// {@template flutter.dart:ui.boxHeightStyle.includeLineSpacing}
|
||||
/// The top edge of each line should be the same as the bottom edge
|
||||
/// of the line above. There should be no gaps in vertical coverage given any
|
||||
/// amount of line spacing. Line spacing is not included above the first line
|
||||
/// and below the last line due to no additional space present there.
|
||||
/// {@endtemplate}
|
||||
includeLineSpacingMiddle,
|
||||
|
||||
/// Extends the top edge of the bounds to fully cover any line spacing.
|
||||
///
|
||||
/// The line spacing will be added to the top of the box.
|
||||
///
|
||||
/// {@macro flutter.dart:ui.rectHeightStyle.includeLineSpacing}
|
||||
includeLineSpacingTop,
|
||||
/// Extends the top edge of the bounds to fully cover any line spacing.
|
||||
///
|
||||
/// The line spacing will be added to the top of the box.
|
||||
///
|
||||
/// {@macro flutter.dart:ui.rectHeightStyle.includeLineSpacing}
|
||||
includeLineSpacingTop,
|
||||
|
||||
/// Extends the bottom edge of the bounds to fully cover any line spacing.
|
||||
///
|
||||
/// The line spacing will be added to the bottom of the box.
|
||||
///
|
||||
/// {@macro flutter.dart:ui.boxHeightStyle.includeLineSpacing}
|
||||
includeLineSpacingBottom,
|
||||
/// Extends the bottom edge of the bounds to fully cover any line spacing.
|
||||
///
|
||||
/// The line spacing will be added to the bottom of the box.
|
||||
///
|
||||
/// {@macro flutter.dart:ui.boxHeightStyle.includeLineSpacing}
|
||||
includeLineSpacingBottom,
|
||||
|
||||
/// Calculate box heights based on the metrics of this paragraph's [StrutStyle].
|
||||
///
|
||||
/// Boxes based on the strut will have consistent heights throughout the
|
||||
/// entire paragraph. The top edge of each line will align with the bottom
|
||||
/// edge of the previous line. It is possible for glyphs to extend outside
|
||||
/// these boxes.
|
||||
///
|
||||
/// Will fall back to tight bounds if the strut is disabled or invalid.
|
||||
strut,
|
||||
/// Calculate box heights based on the metrics of this paragraph's [StrutStyle].
|
||||
///
|
||||
/// Boxes based on the strut will have consistent heights throughout the
|
||||
/// entire paragraph. The top edge of each line will align with the bottom
|
||||
/// edge of the previous line. It is possible for glyphs to extend outside
|
||||
/// these boxes.
|
||||
strut,
|
||||
}
|
||||
|
||||
/// Defines various ways to horizontally bound the boxes returned by
|
||||
/// [Paragraph.getBoxesForRange].
|
||||
enum BoxWidthStyle {
|
||||
// Provide tight bounding boxes that fit widths to the runs of each line
|
||||
// independently.
|
||||
tight,
|
||||
// Provide tight bounding boxes that fit widths to the runs of each line
|
||||
// independently.
|
||||
tight,
|
||||
|
||||
/// Adds up to two additional boxes as needed at the beginning and/or end
|
||||
/// of each line so that the widths of the boxes in line are the same width
|
||||
/// as the widest line in the paragraph. The additional boxes on each line
|
||||
/// are only added when the relevant box at the relevant edge of that line
|
||||
/// does not span the maximum width of the paragraph.
|
||||
max,
|
||||
/// Adds up to two additional boxes as needed at the beginning and/or end
|
||||
/// of each line so that the widths of the boxes in line are the same width
|
||||
/// as the widest line in the paragraph.
|
||||
///
|
||||
/// The additional boxes on each line are only added when the relevant box
|
||||
/// at the relevant edge of that line does not span the maximum width of
|
||||
/// the paragraph.
|
||||
max,
|
||||
}
|
||||
|
||||
/// Where to vertically align the placeholder relative to the surrounding text.
|
||||
///
|
||||
/// Used by [ParagraphBuilder.addPlaceholder].
|
||||
enum PlaceholderAlignment {
|
||||
/// Match the baseline of the placeholder with the baseline.
|
||||
///
|
||||
/// The [TextBaseline] to use must be specified and non-null when using this
|
||||
/// alignment mode.
|
||||
baseline,
|
||||
|
||||
/// Align the bottom edge of the placeholder with the baseline such that the
|
||||
/// placeholder sits on top of the baseline.
|
||||
///
|
||||
/// The [TextBaseline] to use must be specified and non-null when using this
|
||||
/// alignment mode.
|
||||
aboveBaseline,
|
||||
|
||||
/// Align the top edge of the placeholder with the baseline specified
|
||||
/// such that the placeholder hangs below the baseline.
|
||||
///
|
||||
/// The [TextBaseline] to use must be specified and non-null when using this
|
||||
/// alignment mode.
|
||||
belowBaseline,
|
||||
|
||||
/// Align the top edge of the placeholder with the top edge of the font.
|
||||
///
|
||||
/// When the placeholder is very tall, the extra space will hang from
|
||||
/// the top and extend through the bottom of the line.
|
||||
top,
|
||||
|
||||
/// Align the bottom edge of the placeholder with the top edge of the font.
|
||||
///
|
||||
/// When the placeholder is very tall, the extra space will rise from the
|
||||
/// bottom and extend through the top of the line.
|
||||
bottom,
|
||||
|
||||
/// Align the middle of the placeholder with the middle of the text.
|
||||
///
|
||||
/// When the placeholder is very tall, the extra space will grow equally
|
||||
/// from the top and bottom of the line.
|
||||
middle,
|
||||
}
|
||||
|
||||
/// A paragraph of text.
|
||||
@ -1525,6 +1569,9 @@ class Paragraph extends NativeFieldWrapperClass2 {
|
||||
/// parameters default to the tight option, which will provide close-fitting
|
||||
/// boxes and will not account for any line spacing.
|
||||
///
|
||||
/// Coordinates of the TextBox are relative to the upper-left corner of the paragraph,
|
||||
/// where positive y values indicate down.
|
||||
///
|
||||
/// The [boxHeightStyle] and [boxWidthStyle] parameters must not be null.
|
||||
///
|
||||
/// See [BoxHeightStyle] and [BoxWidthStyle] for full descriptions of each option.
|
||||
@ -1533,9 +1580,16 @@ class Paragraph extends NativeFieldWrapperClass2 {
|
||||
assert(boxWidthStyle != null);
|
||||
return _getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index);
|
||||
}
|
||||
|
||||
List<TextBox> _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.
|
||||
///
|
||||
/// The order of the boxes are in the same order as passed in through [addPlaceholder].
|
||||
///
|
||||
/// Coordinates of the [TextBox] are relative to the upper-left corner of the paragraph,
|
||||
/// where positive y values indicate down.
|
||||
List<TextBox> getBoxesForPlaceholders() native 'Paragraph_getRectsForPlaceholders';
|
||||
|
||||
/// Returns the text position closest to the given offset.
|
||||
TextPosition getPositionForOffset(Offset offset) {
|
||||
final List<int> encoded = _getPositionForOffset(offset.dx, offset.dy);
|
||||
@ -1576,6 +1630,7 @@ class ParagraphBuilder extends NativeFieldWrapperClass2 {
|
||||
/// [Paragraph].
|
||||
@pragma('vm:entry-point')
|
||||
ParagraphBuilder(ParagraphStyle style) {
|
||||
_placeholderCount = 0;
|
||||
List<String> strutFontFamilies;
|
||||
if (style._strutStyle != null) {
|
||||
strutFontFamilies = <String>[];
|
||||
@ -1607,6 +1662,14 @@ class ParagraphBuilder extends NativeFieldWrapperClass2 {
|
||||
String locale
|
||||
) native 'ParagraphBuilder_constructor';
|
||||
|
||||
/// The number of placeholders currently in the paragraph.
|
||||
int get placeholderCount => _placeholderCount;
|
||||
int _placeholderCount;
|
||||
|
||||
/// The scales of the placeholders in the paragraph.
|
||||
List<double> get placeholderScales => _placeholderScales;
|
||||
List<double> _placeholderScales = <double>[];
|
||||
|
||||
/// Applies the given style to the added text until [pop] is called.
|
||||
///
|
||||
/// See [pop] for details.
|
||||
@ -1682,6 +1745,71 @@ class ParagraphBuilder extends NativeFieldWrapperClass2 {
|
||||
}
|
||||
String _addText(String text) native 'ParagraphBuilder_addText';
|
||||
|
||||
/// Adds an inline placeholder space to the paragraph.
|
||||
///
|
||||
/// The paragraph will contain a rectangular space with no text of the dimensions
|
||||
/// specified.
|
||||
///
|
||||
/// The `width` and `height` parameters specify the size of the placeholder rectangle.
|
||||
///
|
||||
/// The `alignment` parameter specifies how the placeholder rectangle will be vertically
|
||||
/// aligned with the surrounding text. When [PlaceholderAlignment.baseline],
|
||||
/// [PlaceholderAlignment.aboveBaseline], and [PlaceholderAlignment.belowBaseline]
|
||||
/// alignment modes are used, the baseline needs to be set with the `baseline`.
|
||||
/// When using [PlaceholderAlignment.baseline], `baselineOffset` indicates the distance
|
||||
/// of the baseline down from the top of of the rectangle. The default `baselineOffset`
|
||||
/// is the `height`.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// * For a 30x50 placeholder with the bottom edge aligned with the bottom of the text, use:
|
||||
/// `addPlaceholder(30, 50, PlaceholderAlignment.bottom);`
|
||||
/// * For a 30x50 placeholder that is vertically centered around the text, use:
|
||||
/// `addPlaceholder(30, 50, PlaceholderAlignment.middle);`.
|
||||
/// * For a 30x50 placeholder that sits completely on top of the alphabetic baseline, use:
|
||||
/// `addPlaceholder(30, 50, PlaceholderAlignment.aboveBaseline, baseline: TextBaseline.alphabetic)`.
|
||||
/// * For a 30x50 placeholder with 40 pixels above and 10 pixels below the alphabetic baseline, use:
|
||||
/// `addPlaceholder(30, 50, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic, baselineOffset: 40)`.
|
||||
///
|
||||
/// Lines are permitted to break around each placeholder.
|
||||
///
|
||||
/// Decorations will be drawn based on the font defined in the most recently
|
||||
/// pushed [TextStyle]. The decorations are drawn as if unicode text were present
|
||||
/// in the placeholder space, and will draw the same regardless of the height and
|
||||
/// alignment of the placeholder. To hide or manually adjust decorations to fit,
|
||||
/// a text style with the desired decoration behavior should be pushed before
|
||||
/// adding a placeholder.
|
||||
///
|
||||
/// Any decorations drawn through a placeholder will exist on the same canvas/layer
|
||||
/// as the text. This means any content drawn on top of the space reserved by
|
||||
/// the placeholder will be drawn over the decoration, possibly obscuring the
|
||||
/// decoration.
|
||||
///
|
||||
/// Placeholders are represented by a unicode 0xFFFC "object replacement character"
|
||||
/// in the text buffer. For each placeholder, one object replacement character is
|
||||
/// added on to the text buffer.
|
||||
///
|
||||
/// The `scale` parameter will scale the `width` and `height` by the specified amount,
|
||||
/// and keep track of the scale. The scales of placeholders added can be accessed
|
||||
/// through [placeholderScales]. This is primarily used for acessibility scaling.
|
||||
void addPlaceholder(double width, double height, PlaceholderAlignment alignment, {
|
||||
double scale = 1.0,
|
||||
double baselineOffset,
|
||||
TextBaseline baseline,
|
||||
}) {
|
||||
// Require a baseline to be specified if using a baseline-based alignment.
|
||||
assert((alignment == PlaceholderAlignment.aboveBaseline ||
|
||||
alignment == PlaceholderAlignment.belowBaseline ||
|
||||
alignment == PlaceholderAlignment.baseline) ? baseline != null : true);
|
||||
// Default the baselineOffset to height if null. This will place the placeholder
|
||||
// fully above the baseline, similar to [PlaceholderAlignment.aboveBaseline].
|
||||
baselineOffset = baselineOffset ?? height;
|
||||
_addPlaceholder(width * scale, height * scale, alignment.index, (baselineOffset == null ? height : baselineOffset) * scale, baseline == null ? null : baseline.index);
|
||||
_placeholderCount++;
|
||||
_placeholderScales.add(scale);
|
||||
}
|
||||
String _addPlaceholder(double width, double height, int alignment, double baselineOffset, int baseline) native 'ParagraphBuilder_addPlaceholder';
|
||||
|
||||
/// Applies the given paragraph style and returns a [Paragraph] containing the
|
||||
/// added text and associated styling.
|
||||
///
|
||||
|
||||
@ -19,19 +19,20 @@ namespace flutter {
|
||||
|
||||
IMPLEMENT_WRAPPERTYPEINFO(ui, Paragraph);
|
||||
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(Paragraph, width) \
|
||||
V(Paragraph, height) \
|
||||
V(Paragraph, longestLine) \
|
||||
V(Paragraph, minIntrinsicWidth) \
|
||||
V(Paragraph, maxIntrinsicWidth) \
|
||||
V(Paragraph, alphabeticBaseline) \
|
||||
V(Paragraph, ideographicBaseline) \
|
||||
V(Paragraph, didExceedMaxLines) \
|
||||
V(Paragraph, layout) \
|
||||
V(Paragraph, paint) \
|
||||
V(Paragraph, getWordBoundary) \
|
||||
V(Paragraph, getRectsForRange) \
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(Paragraph, width) \
|
||||
V(Paragraph, height) \
|
||||
V(Paragraph, longestLine) \
|
||||
V(Paragraph, minIntrinsicWidth) \
|
||||
V(Paragraph, maxIntrinsicWidth) \
|
||||
V(Paragraph, alphabeticBaseline) \
|
||||
V(Paragraph, ideographicBaseline) \
|
||||
V(Paragraph, didExceedMaxLines) \
|
||||
V(Paragraph, layout) \
|
||||
V(Paragraph, paint) \
|
||||
V(Paragraph, getWordBoundary) \
|
||||
V(Paragraph, getRectsForRange) \
|
||||
V(Paragraph, getRectsForPlaceholders) \
|
||||
V(Paragraph, getPositionForOffset)
|
||||
|
||||
DART_BIND_ALL(Paragraph, FOR_EACH_BINDING)
|
||||
@ -98,6 +99,10 @@ std::vector<TextBox> Paragraph::getRectsForRange(unsigned start,
|
||||
static_cast<txt::Paragraph::RectWidthStyle>(boxWidthStyle));
|
||||
}
|
||||
|
||||
std::vector<TextBox> Paragraph::getRectsForPlaceholders() {
|
||||
return m_paragraphImpl->getRectsForPlaceholders();
|
||||
}
|
||||
|
||||
Dart_Handle Paragraph::getPositionForOffset(double dx, double dy) {
|
||||
return m_paragraphImpl->getPositionForOffset(dx, dy);
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ class Paragraph : public RefCountedDartWrappable<Paragraph> {
|
||||
unsigned end,
|
||||
unsigned boxHeightStyle,
|
||||
unsigned boxWidthStyle);
|
||||
std::vector<TextBox> getRectsForPlaceholders();
|
||||
Dart_Handle getPositionForOffset(double dx, double dy);
|
||||
Dart_Handle getWordBoundary(unsigned offset);
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "flutter/third_party/txt/src/txt/font_style.h"
|
||||
#include "flutter/third_party/txt/src/txt/font_weight.h"
|
||||
#include "flutter/third_party/txt/src/txt/paragraph_style.h"
|
||||
#include "flutter/third_party/txt/src/txt/text_baseline.h"
|
||||
#include "flutter/third_party/txt/src/txt/text_decoration.h"
|
||||
#include "flutter/third_party/txt/src/txt/text_style.h"
|
||||
#include "third_party/icu/source/common/unicode/ustring.h"
|
||||
@ -132,10 +133,11 @@ static void ParagraphBuilder_constructor(Dart_NativeArguments args) {
|
||||
|
||||
IMPLEMENT_WRAPPERTYPEINFO(ui, ParagraphBuilder);
|
||||
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(ParagraphBuilder, pushStyle) \
|
||||
V(ParagraphBuilder, pop) \
|
||||
V(ParagraphBuilder, addText) \
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(ParagraphBuilder, pushStyle) \
|
||||
V(ParagraphBuilder, pop) \
|
||||
V(ParagraphBuilder, addText) \
|
||||
V(ParagraphBuilder, addPlaceholder) \
|
||||
V(ParagraphBuilder, build)
|
||||
|
||||
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
|
||||
@ -459,6 +461,20 @@ Dart_Handle ParagraphBuilder::addText(const std::u16string& text) {
|
||||
return Dart_Null();
|
||||
}
|
||||
|
||||
Dart_Handle ParagraphBuilder::addPlaceholder(double width,
|
||||
double height,
|
||||
unsigned alignment,
|
||||
double baseline_offset,
|
||||
unsigned baseline) {
|
||||
txt::PlaceholderRun placeholder_run(
|
||||
width, height, static_cast<txt::PlaceholderAlignment>(alignment),
|
||||
static_cast<txt::TextBaseline>(baseline), baseline_offset);
|
||||
|
||||
m_paragraphBuilder->AddPlaceholder(placeholder_run);
|
||||
|
||||
return Dart_Null();
|
||||
}
|
||||
|
||||
fml::RefPtr<Paragraph> ParagraphBuilder::build() {
|
||||
return Paragraph::Create(m_paragraphBuilder->Build());
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#define FLUTTER_LIB_UI_TEXT_PARAGRAPH_BUILDER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/lib/ui/dart_wrapper.h"
|
||||
#include "flutter/lib/ui/painting/paint.h"
|
||||
#include "flutter/lib/ui/text/paragraph.h"
|
||||
@ -56,6 +57,18 @@ class ParagraphBuilder : public RefCountedDartWrappable<ParagraphBuilder> {
|
||||
|
||||
Dart_Handle addText(const std::u16string& text);
|
||||
|
||||
// Pushes the information requried to leave an open space, where Flutter may
|
||||
// draw a custom placeholder into.
|
||||
//
|
||||
// Internally, this method adds a single object replacement character (0xFFFC)
|
||||
// and emplaces a new PlaceholderRun instance to the vector of inline
|
||||
// placeholders.
|
||||
Dart_Handle addPlaceholder(double width,
|
||||
double height,
|
||||
unsigned alignment,
|
||||
double baseline_offset,
|
||||
unsigned baseline);
|
||||
|
||||
fml::RefPtr<Paragraph> build();
|
||||
|
||||
static void RegisterNatives(tonic::DartLibraryNatives* natives);
|
||||
|
||||
@ -41,6 +41,8 @@ class ParagraphImpl {
|
||||
txt::Paragraph::RectHeightStyle rect_height_style,
|
||||
txt::Paragraph::RectWidthStyle rect_width_style) = 0;
|
||||
|
||||
virtual std::vector<TextBox> getRectsForPlaceholders() = 0;
|
||||
|
||||
virtual Dart_Handle getPositionForOffset(double dx, double dy) = 0;
|
||||
|
||||
virtual Dart_Handle getWordBoundary(unsigned offset) = 0;
|
||||
|
||||
@ -79,6 +79,17 @@ std::vector<TextBox> ParagraphImplTxt::getRectsForRange(
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<TextBox> ParagraphImplTxt::getRectsForPlaceholders() {
|
||||
std::vector<TextBox> result;
|
||||
std::vector<txt::Paragraph::TextBox> boxes =
|
||||
m_paragraph->GetRectsForPlaceholders();
|
||||
for (const txt::Paragraph::TextBox& box : boxes) {
|
||||
result.emplace_back(box.rect,
|
||||
static_cast<flutter::TextDirection>(box.direction));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Dart_Handle ParagraphImplTxt::getPositionForOffset(double dx, double dy) {
|
||||
Dart_Handle result = Dart_NewListOf(Dart_CoreType_Int, 2);
|
||||
txt::Paragraph::PositionWithAffinity pos =
|
||||
|
||||
@ -34,6 +34,7 @@ class ParagraphImplTxt : public ParagraphImpl {
|
||||
unsigned end,
|
||||
txt::Paragraph::RectHeightStyle rect_height_style,
|
||||
txt::Paragraph::RectWidthStyle rect_width_style) override;
|
||||
std::vector<TextBox> getRectsForPlaceholders() override;
|
||||
Dart_Handle getPositionForOffset(double dx, double dy) override;
|
||||
Dart_Handle getWordBoundary(unsigned offset) override;
|
||||
|
||||
|
||||
2
engine/src/flutter/third_party/txt/BUILD.gn
vendored
2
engine/src/flutter/third_party/txt/BUILD.gn
vendored
@ -95,6 +95,8 @@ source_set("txt") {
|
||||
"src/txt/paragraph_builder.h",
|
||||
"src/txt/paragraph_style.cc",
|
||||
"src/txt/paragraph_style.h",
|
||||
"src/txt/placeholder_run.cc",
|
||||
"src/txt/placeholder_run.h",
|
||||
"src/txt/platform.h",
|
||||
"src/txt/styled_runs.cc",
|
||||
"src/txt/styled_runs.h",
|
||||
|
||||
@ -356,6 +356,14 @@ void LineBreaker::pushBreak(int offset, float width, uint8_t hyphenEdit) {
|
||||
mFirstTabIndex = INT_MAX;
|
||||
}
|
||||
|
||||
// libtxt: Add ability to set custom char widths. This allows manual definition
|
||||
// of the widths of arbitrary glyphs. To linebreak properly, call addStyleRun
|
||||
// with nullptr as the paint property, which will lead it to assume the width
|
||||
// has already been calculated. Used for properly breaking inline widgets.
|
||||
void LineBreaker::setCustomCharWidth(size_t offset, float width) {
|
||||
mCharWidths[offset] = (width);
|
||||
}
|
||||
|
||||
void LineBreaker::addReplacement(size_t start, size_t end, float width) {
|
||||
mCharWidths[start] = width;
|
||||
std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f);
|
||||
|
||||
@ -149,6 +149,13 @@ class LineBreaker {
|
||||
|
||||
size_t computeBreaks();
|
||||
|
||||
// libtxt: Add ability to set custom char widths. This allows manual
|
||||
// definition of the widths of arbitrary glyphs. To linebreak properly, call
|
||||
// addStyleRun with nullptr as the paint property, which will lead it to
|
||||
// assume the width has already been calculated. Used for properly breaking
|
||||
// inline placeholders.
|
||||
void setCustomCharWidth(size_t offset, float width);
|
||||
|
||||
const int* getBreaks() const { return mBreaks.data(); }
|
||||
|
||||
const float* getWidths() const { return mWidths.data(); }
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "paint_record.h"
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
|
||||
namespace txt {
|
||||
@ -38,6 +39,25 @@ PaintRecord::PaintRecord(TextStyle style,
|
||||
x_end_(x_end),
|
||||
is_ghost_(is_ghost) {}
|
||||
|
||||
PaintRecord::PaintRecord(TextStyle style,
|
||||
SkPoint offset,
|
||||
sk_sp<SkTextBlob> text,
|
||||
SkFontMetrics metrics,
|
||||
size_t line,
|
||||
double x_start,
|
||||
double x_end,
|
||||
bool is_ghost,
|
||||
PlaceholderRun* placeholder_run)
|
||||
: style_(style),
|
||||
offset_(offset),
|
||||
text_(std::move(text)),
|
||||
metrics_(metrics),
|
||||
line_(line),
|
||||
x_start_(x_start),
|
||||
x_end_(x_end),
|
||||
is_ghost_(is_ghost),
|
||||
placeholder_run_(placeholder_run) {}
|
||||
|
||||
PaintRecord::PaintRecord(TextStyle style,
|
||||
sk_sp<SkTextBlob> text,
|
||||
SkFontMetrics metrics,
|
||||
@ -59,6 +79,7 @@ PaintRecord::PaintRecord(PaintRecord&& other) {
|
||||
text_ = std::move(other.text_);
|
||||
metrics_ = other.metrics_;
|
||||
line_ = other.line_;
|
||||
placeholder_run_ = other.placeholder_run_;
|
||||
x_start_ = other.x_start_;
|
||||
x_end_ = other.x_end_;
|
||||
is_ghost_ = other.is_ghost_;
|
||||
@ -73,6 +94,7 @@ PaintRecord& PaintRecord::operator=(PaintRecord&& other) {
|
||||
x_start_ = other.x_start_;
|
||||
x_end_ = other.x_end_;
|
||||
is_ghost_ = other.is_ghost_;
|
||||
placeholder_run_ = other.placeholder_run_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "placeholder_run.h"
|
||||
#include "text_style.h"
|
||||
#include "third_party/skia/include/core/SkFontMetrics.h"
|
||||
#include "third_party/skia/include/core/SkTextBlob.h"
|
||||
@ -43,6 +44,16 @@ class PaintRecord {
|
||||
double x_end,
|
||||
bool is_ghost);
|
||||
|
||||
PaintRecord(TextStyle style,
|
||||
SkPoint offset,
|
||||
sk_sp<SkTextBlob> text,
|
||||
SkFontMetrics metrics,
|
||||
size_t line,
|
||||
double x_start,
|
||||
double x_end,
|
||||
bool is_ghost,
|
||||
PlaceholderRun* placeholder_run);
|
||||
|
||||
PaintRecord(TextStyle style,
|
||||
sk_sp<SkTextBlob> text,
|
||||
SkFontMetrics metrics,
|
||||
@ -71,8 +82,12 @@ class PaintRecord {
|
||||
double x_end() const { return x_end_; }
|
||||
double GetRunWidth() const { return x_end_ - x_start_; }
|
||||
|
||||
PlaceholderRun* GetPlaceholderRun() const { return placeholder_run_; }
|
||||
|
||||
bool isGhost() const { return is_ghost_; }
|
||||
|
||||
bool isPlaceholder() const { return placeholder_run_ == nullptr; }
|
||||
|
||||
private:
|
||||
TextStyle style_;
|
||||
// offset_ is the overall offset of the origin of the SkTextBlob.
|
||||
@ -87,6 +102,10 @@ class PaintRecord {
|
||||
// 'Ghost' runs represent trailing whitespace. 'Ghost' runs should not have
|
||||
// decorations painted on them and do not impact layout of visible glyphs.
|
||||
bool is_ghost_ = false;
|
||||
// Stores the corresponding PlaceholderRun that the record corresponds to.
|
||||
// When this is nullptr, then the record is of normal text and does not
|
||||
// represent an inline placeholder.
|
||||
PlaceholderRun* placeholder_run_ = nullptr;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(PaintRecord);
|
||||
};
|
||||
|
||||
@ -209,13 +209,15 @@ Paragraph::CodeUnitRun::CodeUnitRun(std::vector<GlyphPosition>&& p,
|
||||
Range<double> x,
|
||||
size_t line,
|
||||
const SkFontMetrics& metrics,
|
||||
TextDirection dir)
|
||||
TextDirection dir,
|
||||
const PlaceholderRun* placeholder)
|
||||
: positions(std::move(p)),
|
||||
code_units(cu),
|
||||
x_pos(x),
|
||||
line_number(line),
|
||||
font_metrics(metrics),
|
||||
direction(dir) {}
|
||||
direction(dir),
|
||||
placeholder_run(placeholder) {}
|
||||
|
||||
void Paragraph::CodeUnitRun::Shift(double delta) {
|
||||
x_pos.Shift(delta);
|
||||
@ -237,21 +239,33 @@ void Paragraph::SetText(std::vector<uint16_t> text, StyledRuns runs) {
|
||||
runs_ = std::move(runs);
|
||||
}
|
||||
|
||||
void Paragraph::SetInlinePlaceholders(
|
||||
std::vector<PlaceholderRun> inline_placeholders,
|
||||
std::unordered_set<size_t> obj_replacement_char_indexes) {
|
||||
needs_layout_ = true;
|
||||
inline_placeholders_ = std::move(inline_placeholders);
|
||||
obj_replacement_char_indexes_ = std::move(obj_replacement_char_indexes);
|
||||
}
|
||||
|
||||
bool Paragraph::ComputeLineBreaks() {
|
||||
line_ranges_.clear();
|
||||
line_widths_.clear();
|
||||
max_intrinsic_width_ = 0;
|
||||
|
||||
std::vector<size_t> newline_positions;
|
||||
// Discover and add all hard breaks.
|
||||
for (size_t i = 0; i < text_.size(); ++i) {
|
||||
ULineBreak ulb = static_cast<ULineBreak>(
|
||||
u_getIntPropertyValue(text_[i], UCHAR_LINE_BREAK));
|
||||
if (ulb == U_LB_LINE_FEED || ulb == U_LB_MANDATORY_BREAK)
|
||||
newline_positions.push_back(i);
|
||||
}
|
||||
// Break at the end of the paragraph.
|
||||
newline_positions.push_back(text_.size());
|
||||
|
||||
// Calculate and add any breaks due to a line being too long.
|
||||
size_t run_index = 0;
|
||||
size_t inline_placeholder_index = 0;
|
||||
for (size_t newline_index = 0; newline_index < newline_positions.size();
|
||||
++newline_index) {
|
||||
size_t block_start =
|
||||
@ -266,6 +280,9 @@ bool Paragraph::ComputeLineBreaks() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Setup breaker. We wait to set the line width in order to account for the
|
||||
// widths of the inline placeholders, which are calcualted in the loop over
|
||||
// the runs.
|
||||
breaker_.setLineWidths(0.0f, 0, width_);
|
||||
breaker_.setJustified(paragraph_style_.text_align == TextAlign::justify);
|
||||
breaker_.setStrategy(paragraph_style_.break_strategy);
|
||||
@ -301,15 +318,38 @@ bool Paragraph::ComputeLineBreaks() {
|
||||
size_t run_start = std::max(run.start, block_start) - block_start;
|
||||
size_t run_end = std::min(run.end, block_end) - block_start;
|
||||
bool isRtl = (paragraph_style_.text_direction == TextDirection::rtl);
|
||||
double run_width = breaker_.addStyleRun(&paint, collection, font,
|
||||
run_start, run_end, isRtl);
|
||||
block_total_width += run_width;
|
||||
|
||||
// Check if the run is an object replacement character-only run. We should
|
||||
// leave space for inline placeholder and break around it if appropriate.
|
||||
if (run.end - run.start == 1 &&
|
||||
obj_replacement_char_indexes_.count(run.start) != 0 &&
|
||||
text_[run.start] == objReplacementChar &&
|
||||
inline_placeholder_index < inline_placeholders_.size()) {
|
||||
// Is a inline placeholder run.
|
||||
PlaceholderRun placeholder_run =
|
||||
inline_placeholders_[inline_placeholder_index];
|
||||
block_total_width += placeholder_run.width;
|
||||
|
||||
// Inject custom width into minikin breaker. (Uses LibTxt-minikin
|
||||
// patch).
|
||||
breaker_.setCustomCharWidth(run.start, placeholder_run.width);
|
||||
|
||||
// Called with nullptr as paint in order to use the custom widths passed
|
||||
// above.
|
||||
breaker_.addStyleRun(nullptr, collection, font, run_start, run_end,
|
||||
isRtl);
|
||||
inline_placeholder_index++;
|
||||
} else {
|
||||
// Is a regular text run.
|
||||
double run_width = breaker_.addStyleRun(&paint, collection, font,
|
||||
run_start, run_end, isRtl);
|
||||
block_total_width += run_width;
|
||||
}
|
||||
|
||||
if (run.end > block_end)
|
||||
break;
|
||||
run_index++;
|
||||
}
|
||||
|
||||
max_intrinsic_width_ = std::max(max_intrinsic_width_, block_total_width);
|
||||
|
||||
size_t breaks_count = breaker_.computeBreaks();
|
||||
@ -480,6 +520,80 @@ void Paragraph::ComputeStrut(StrutMetrics* strut, SkFont& font) {
|
||||
}
|
||||
}
|
||||
|
||||
void Paragraph::ComputePlaceholder(PlaceholderRun* placeholder_run,
|
||||
double& ascent,
|
||||
double& descent) {
|
||||
if (placeholder_run != nullptr) {
|
||||
// Calculate how much to shift the ascent and descent to account
|
||||
// for the baseline choice.
|
||||
//
|
||||
// TODO(garyq): implement for various baselines. Currently only
|
||||
// supports for alphabetic and ideographic
|
||||
double baseline_adjustment = 0;
|
||||
switch (placeholder_run->baseline) {
|
||||
case TextBaseline::kAlphabetic: {
|
||||
baseline_adjustment = 0;
|
||||
break;
|
||||
}
|
||||
case TextBaseline::kIdeographic: {
|
||||
baseline_adjustment = -descent / 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Convert the ascent and descent from the font's to the placeholder
|
||||
// rect's.
|
||||
switch (placeholder_run->alignment) {
|
||||
case PlaceholderAlignment::kBaseline: {
|
||||
ascent = baseline_adjustment + placeholder_run->baseline_offset;
|
||||
descent = -baseline_adjustment + placeholder_run->height -
|
||||
placeholder_run->baseline_offset;
|
||||
break;
|
||||
}
|
||||
case PlaceholderAlignment::kAboveBaseline: {
|
||||
ascent = baseline_adjustment + placeholder_run->height;
|
||||
descent = -baseline_adjustment;
|
||||
break;
|
||||
}
|
||||
case PlaceholderAlignment::kBelowBaseline: {
|
||||
descent = baseline_adjustment + placeholder_run->height;
|
||||
ascent = -baseline_adjustment;
|
||||
break;
|
||||
}
|
||||
case PlaceholderAlignment::kTop: {
|
||||
descent = placeholder_run->height - ascent;
|
||||
break;
|
||||
}
|
||||
case PlaceholderAlignment::kBottom: {
|
||||
ascent = placeholder_run->height - descent;
|
||||
break;
|
||||
}
|
||||
case PlaceholderAlignment::kMiddle: {
|
||||
double mid = (ascent - descent) / 2;
|
||||
ascent = mid + placeholder_run->height / 2;
|
||||
descent = -mid + placeholder_run->height / 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
placeholder_run->baseline_offset = ascent;
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation outline:
|
||||
//
|
||||
// -For each line:
|
||||
// -Compute Bidi runs, convert into line_runs (keeps in-line-range runs, adds
|
||||
// special runs)
|
||||
// -For each line_run (runs in the line):
|
||||
// -Calculate ellipsis
|
||||
// -Obtain font
|
||||
// -layout.doLayout(...), genereates glyph blobs
|
||||
// -For each glyph blob:
|
||||
// -Convert glyph blobs into pixel metrics/advances
|
||||
// -Store as paint records (for painting) and code unit runs (for metrics
|
||||
// and boxes).
|
||||
// -Apply letter spacing, alignment, justification, etc
|
||||
// -Calculate line vertical layout (ascent, descent, etc)
|
||||
// -Store per-line metrics
|
||||
void Paragraph::Layout(double width, bool force) {
|
||||
double rounded_width = floor(width);
|
||||
// Do not allow calling layout multiple times without changing anything.
|
||||
@ -508,6 +622,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
line_baselines_.clear();
|
||||
glyph_lines_.clear();
|
||||
code_unit_runs_.clear();
|
||||
inline_placeholder_code_unit_runs_.clear();
|
||||
line_max_spacings_.clear();
|
||||
line_max_descent_.clear();
|
||||
line_max_ascent_.clear();
|
||||
@ -527,6 +642,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
size_t line_limit = std::min(paragraph_style_.max_lines, line_ranges_.size());
|
||||
did_exceed_max_lines_ = (line_ranges_.size() > paragraph_style_.max_lines);
|
||||
|
||||
size_t placeholder_run_index = 0;
|
||||
for (size_t line_number = 0; line_number < line_limit; ++line_number) {
|
||||
const LineRange& line_range = line_ranges_[line_number];
|
||||
|
||||
@ -583,9 +699,21 @@ void Paragraph::Layout(double width, bool force) {
|
||||
// Emplace a normal line run.
|
||||
if (bidi_run.start() < line_end_index &&
|
||||
bidi_run.end() > line_range.start) {
|
||||
line_runs.emplace_back(std::max(bidi_run.start(), line_range.start),
|
||||
std::min(bidi_run.end(), line_end_index),
|
||||
bidi_run.direction(), bidi_run.style());
|
||||
// The run is a placeholder run.
|
||||
if (bidi_run.size() == 1 &&
|
||||
text_[bidi_run.start()] == objReplacementChar &&
|
||||
obj_replacement_char_indexes_.count(bidi_run.start()) != 0 &&
|
||||
placeholder_run_index < inline_placeholders_.size()) {
|
||||
line_runs.emplace_back(std::max(bidi_run.start(), line_range.start),
|
||||
std::min(bidi_run.end(), line_end_index),
|
||||
bidi_run.direction(), bidi_run.style(),
|
||||
inline_placeholders_[placeholder_run_index]);
|
||||
placeholder_run_index++;
|
||||
} else {
|
||||
line_runs.emplace_back(std::max(bidi_run.start(), line_range.start),
|
||||
std::min(bidi_run.end(), line_end_index),
|
||||
bidi_run.direction(), bidi_run.style());
|
||||
}
|
||||
}
|
||||
// Include the ghost run after normal run if LTR
|
||||
if (bidi_run.direction() == TextDirection::ltr && ghost_run != nullptr) {
|
||||
@ -603,6 +731,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
|
||||
std::vector<GlyphPosition> line_glyph_positions;
|
||||
std::vector<CodeUnitRun> line_code_unit_runs;
|
||||
std::vector<CodeUnitRun> line_inline_placeholder_code_unit_runs;
|
||||
double run_x_offset = 0;
|
||||
double justify_x_offset = 0;
|
||||
std::vector<PaintRecord> paint_records;
|
||||
@ -814,15 +943,25 @@ void Paragraph::Layout(double width, bool force) {
|
||||
|
||||
if (glyph_positions.empty())
|
||||
continue;
|
||||
|
||||
SkFontMetrics metrics;
|
||||
font.getMetrics(&metrics);
|
||||
Range<double> record_x_pos(
|
||||
glyph_positions.front().x_pos.start - run_x_offset,
|
||||
glyph_positions.back().x_pos.end - run_x_offset);
|
||||
paint_records.emplace_back(
|
||||
run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0),
|
||||
builder.make(), metrics, line_number, record_x_pos.start,
|
||||
record_x_pos.end, run.is_ghost());
|
||||
if (run.is_placeholder_run()) {
|
||||
paint_records.emplace_back(
|
||||
run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0),
|
||||
builder.make(), metrics, line_number, record_x_pos.start,
|
||||
record_x_pos.start + run.placeholder_run()->width, run.is_ghost(),
|
||||
run.placeholder_run());
|
||||
run_x_offset += run.placeholder_run()->width;
|
||||
} else {
|
||||
paint_records.emplace_back(
|
||||
run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0),
|
||||
builder.make(), metrics, line_number, record_x_pos.start,
|
||||
record_x_pos.end, run.is_ghost());
|
||||
}
|
||||
justify_x_offset += justify_x_offset_delta;
|
||||
|
||||
line_glyph_positions.insert(line_glyph_positions.end(),
|
||||
@ -840,20 +979,29 @@ void Paragraph::Layout(double width, bool force) {
|
||||
std::move(code_unit_positions),
|
||||
Range<size_t>(run.start(), run.end()),
|
||||
Range<double>(glyph_positions.front().x_pos.start,
|
||||
glyph_positions.back().x_pos.end),
|
||||
line_number, metrics, run.direction());
|
||||
run.is_placeholder_run()
|
||||
? glyph_positions.back().x_pos.start +
|
||||
run.placeholder_run()->width
|
||||
: glyph_positions.back().x_pos.end),
|
||||
line_number, metrics, run.direction(), run.placeholder_run());
|
||||
if (run.is_placeholder_run()) {
|
||||
line_inline_placeholder_code_unit_runs.push_back(
|
||||
line_code_unit_runs.back());
|
||||
}
|
||||
|
||||
if (!run.is_ghost()) {
|
||||
min_left_ = std::min(min_left_, glyph_positions.front().x_pos.start);
|
||||
max_right_ = std::max(max_right_, glyph_positions.back().x_pos.end);
|
||||
}
|
||||
} // for each in glyph_blobs
|
||||
|
||||
// Do not increase x offset for LTR trailing ghost runs as it should not
|
||||
// impact the layout of visible glyphs. RTL tailing ghost runs have the
|
||||
// advance subtracted, so we do add the advance here to reset the
|
||||
// run_x_offset. We do keep the record though so GetRectsForRange() can
|
||||
// find metrics for trailing spaces.
|
||||
if (!run.is_ghost() || run.is_rtl()) {
|
||||
// if (!run.is_ghost() || run.is_rtl()) {
|
||||
if ((!run.is_ghost() || run.is_rtl()) && !run.is_placeholder_run()) {
|
||||
run_x_offset += layout.getAdvance();
|
||||
}
|
||||
} // for each in line_runs
|
||||
@ -864,6 +1012,10 @@ void Paragraph::Layout(double width, bool force) {
|
||||
for (CodeUnitRun& code_unit_run : line_code_unit_runs) {
|
||||
code_unit_run.Shift(line_x_offset);
|
||||
}
|
||||
for (CodeUnitRun& code_unit_run :
|
||||
line_inline_placeholder_code_unit_runs) {
|
||||
code_unit_run.Shift(line_x_offset);
|
||||
}
|
||||
for (GlyphPosition& position : line_glyph_positions) {
|
||||
position.Shift(line_x_offset);
|
||||
}
|
||||
@ -876,28 +1028,39 @@ void Paragraph::Layout(double width, bool force) {
|
||||
next_line_start - line_range.start);
|
||||
code_unit_runs_.insert(code_unit_runs_.end(), line_code_unit_runs.begin(),
|
||||
line_code_unit_runs.end());
|
||||
inline_placeholder_code_unit_runs_.insert(
|
||||
inline_placeholder_code_unit_runs_.end(),
|
||||
line_inline_placeholder_code_unit_runs.begin(),
|
||||
line_inline_placeholder_code_unit_runs.end());
|
||||
|
||||
// Calculate the amount to advance in the y direction. This is done by
|
||||
// computing the maximum ascent and descent with respect to the strut.
|
||||
double max_ascent = strut_.ascent + strut_.half_leading;
|
||||
double max_descent = strut_.descent + strut_.half_leading;
|
||||
SkScalar max_unscaled_ascent = 0;
|
||||
double max_unscaled_ascent = 0;
|
||||
auto update_line_metrics = [&](const SkFontMetrics& metrics,
|
||||
const TextStyle& style) {
|
||||
const TextStyle& style,
|
||||
PlaceholderRun* placeholder_run) {
|
||||
if (!strut_.force_strut) {
|
||||
double ascent =
|
||||
(-metrics.fAscent + metrics.fLeading / 2) * style.height;
|
||||
max_ascent = std::max(ascent, max_ascent);
|
||||
|
||||
double descent =
|
||||
(metrics.fDescent + metrics.fLeading / 2) * style.height;
|
||||
|
||||
ComputePlaceholder(placeholder_run, ascent, descent);
|
||||
|
||||
max_ascent = std::max(ascent, max_ascent);
|
||||
max_descent = std::max(descent, max_descent);
|
||||
}
|
||||
|
||||
max_unscaled_ascent = std::max(-metrics.fAscent, max_unscaled_ascent);
|
||||
max_unscaled_ascent = std::max(placeholder_run == nullptr
|
||||
? -metrics.fAscent
|
||||
: placeholder_run->baseline_offset,
|
||||
max_unscaled_ascent);
|
||||
};
|
||||
for (const PaintRecord& paint_record : paint_records) {
|
||||
update_line_metrics(paint_record.metrics(), paint_record.style());
|
||||
update_line_metrics(paint_record.metrics(), paint_record.style(),
|
||||
paint_record.GetPlaceholderRun());
|
||||
}
|
||||
|
||||
// If no fonts were actually rendered, then compute a baseline based on the
|
||||
@ -908,7 +1071,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
font.setTypeface(GetDefaultSkiaTypeface(style));
|
||||
font.setSize(style.font_size);
|
||||
font.getMetrics(&metrics);
|
||||
update_line_metrics(metrics, style);
|
||||
update_line_metrics(metrics, style, nullptr);
|
||||
}
|
||||
|
||||
// Calculate the baselines. This is only done on the first line.
|
||||
@ -1063,8 +1226,10 @@ void Paragraph::Paint(SkCanvas* canvas, double x, double y) {
|
||||
paint.setColor(record.style().color);
|
||||
}
|
||||
SkPoint offset = base_offset + record.offset();
|
||||
PaintShadow(canvas, record, offset);
|
||||
canvas->drawTextBlob(record.text(), offset.x(), offset.y(), paint);
|
||||
if (record.GetPlaceholderRun() == nullptr) {
|
||||
PaintShadow(canvas, record, offset);
|
||||
canvas->drawTextBlob(record.text(), offset.x(), offset.y(), paint);
|
||||
}
|
||||
PaintDecorations(canvas, record, base_offset);
|
||||
}
|
||||
}
|
||||
@ -1300,6 +1465,13 @@ std::vector<Paragraph::TextBox> Paragraph::GetRectsForRange(
|
||||
SkScalar top = baseline + run.font_metrics.fAscent;
|
||||
SkScalar bottom = baseline + run.font_metrics.fDescent;
|
||||
|
||||
if (run.placeholder_run !=
|
||||
nullptr) { // Use inline placeholder size as height.
|
||||
top = baseline - run.placeholder_run->baseline_offset;
|
||||
bottom = baseline + run.placeholder_run->height -
|
||||
run.placeholder_run->baseline_offset;
|
||||
}
|
||||
|
||||
max_line = std::max(run.line_number, max_line);
|
||||
min_line = std::min(run.line_number, min_line);
|
||||
|
||||
@ -1538,6 +1710,46 @@ Paragraph::PositionWithAffinity Paragraph::GetGlyphPositionAtCoordinate(
|
||||
}
|
||||
}
|
||||
|
||||
// We don't cache this because since this returns all boxes, it is usually
|
||||
// unnecessary to call this multiple times in succession.
|
||||
std::vector<Paragraph::TextBox> Paragraph::GetRectsForPlaceholders() const {
|
||||
// Struct that holds calculated metrics for each line.
|
||||
struct LineBoxMetrics {
|
||||
std::vector<Paragraph::TextBox> boxes;
|
||||
// Per-line metrics for max and min coordinates for left and right boxes.
|
||||
// These metrics cannot be calculated in layout generically because of
|
||||
// selections that do not cover the whole line.
|
||||
SkScalar max_right = FLT_MIN;
|
||||
SkScalar min_left = FLT_MAX;
|
||||
};
|
||||
|
||||
std::vector<Paragraph::TextBox> boxes;
|
||||
|
||||
// Generate initial boxes and calculate metrics.
|
||||
for (const CodeUnitRun& run : inline_placeholder_code_unit_runs_) {
|
||||
// Check to see if we are finished.
|
||||
double baseline = line_baselines_[run.line_number];
|
||||
SkScalar top = baseline + run.font_metrics.fAscent;
|
||||
SkScalar bottom = baseline + run.font_metrics.fDescent;
|
||||
|
||||
if (run.placeholder_run !=
|
||||
nullptr) { // Use inline placeholder size as height.
|
||||
top = baseline - run.placeholder_run->baseline_offset;
|
||||
bottom = baseline + run.placeholder_run->height -
|
||||
run.placeholder_run->baseline_offset;
|
||||
}
|
||||
|
||||
// Calculate left and right.
|
||||
SkScalar left, right;
|
||||
left = run.x_pos.start;
|
||||
right = run.x_pos.end;
|
||||
|
||||
boxes.emplace_back(SkRect::MakeLTRB(left, top, right, bottom),
|
||||
run.direction);
|
||||
}
|
||||
return boxes;
|
||||
}
|
||||
|
||||
Paragraph::Range<size_t> Paragraph::GetWordBoundary(size_t offset) const {
|
||||
if (text_.size() == 0)
|
||||
return Range<size_t>(0, 0);
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
#include "minikin/LineBreaker.h"
|
||||
#include "paint_record.h"
|
||||
#include "paragraph_style.h"
|
||||
#include "placeholder_run.h"
|
||||
#include "styled_runs.h"
|
||||
#include "third_party/googletest/googletest/include/gtest/gtest_prod.h" // nogncheck
|
||||
#include "third_party/skia/include/core/SkFontMetrics.h"
|
||||
@ -39,6 +40,14 @@ namespace txt {
|
||||
|
||||
using GlyphID = uint32_t;
|
||||
|
||||
// Constant with the unicode codepoint for the "Object replacement character".
|
||||
// Used as a stand-in character for Placeholder boxes.
|
||||
const int objReplacementChar = 0xFFFC;
|
||||
// Constant with the unicode codepoint for the "Replacement character". This is
|
||||
// the character that commonly renders as a black diamond with a white question
|
||||
// mark. Used to replace non-placeholder instances of 0xFFFC in the text buffer.
|
||||
const int replacementChar = 0xFFFD;
|
||||
|
||||
// Paragraph provides Layout, metrics, and painting capabilities for text. Once
|
||||
// a Paragraph is constructed with ParagraphBuilder::Build(), an example basic
|
||||
// workflow can be this:
|
||||
@ -190,6 +199,17 @@ class Paragraph {
|
||||
// with the top left corner as the origin, and +y direction as down.
|
||||
PositionWithAffinity GetGlyphPositionAtCoordinate(double dx, double dy) const;
|
||||
|
||||
// Returns a vector of bounding boxes that bound all inline placeholders in
|
||||
// the paragraph.
|
||||
//
|
||||
// There will be one box for each inline placeholder. The boxes will be in the
|
||||
// same order as they were added to the paragraph. The bounds will always be
|
||||
// tight and should fully enclose the area where the placeholder should be.
|
||||
//
|
||||
// More granular boxes may be obtained through GetRectsForRange, which will
|
||||
// return bounds on both text as well as inline placeholders.
|
||||
std::vector<Paragraph::TextBox> GetRectsForPlaceholders() const;
|
||||
|
||||
// Finds the first and last glyphs that define a word containing the glyph at
|
||||
// index offset.
|
||||
Range<size_t> GetWordBoundary(size_t offset) const;
|
||||
@ -236,10 +256,23 @@ class Paragraph {
|
||||
FRIEND_TEST(ParagraphTest, SimpleShadow);
|
||||
FRIEND_TEST(ParagraphTest, ComplexShadow);
|
||||
FRIEND_TEST(ParagraphTest, FontFallbackParagraph);
|
||||
FRIEND_TEST(ParagraphTest, InlinePlaceholder0xFFFCParagraph);
|
||||
FRIEND_TEST(ParagraphTest, FontFeaturesParagraph);
|
||||
|
||||
// Starting data to layout.
|
||||
std::vector<uint16_t> text_;
|
||||
// A vector of PlaceholderRuns, which detail the sizes, positioning and break
|
||||
// behavior of the empty spaces to leave. Each placeholder span corresponds to
|
||||
// a 0xFFFC (object replacement character) in text_, which indicates the
|
||||
// position in the text where the placeholder will occur. There should be an
|
||||
// equal number of 0xFFFC characters and elements in this vector.
|
||||
std::vector<PlaceholderRun> inline_placeholders_;
|
||||
// The indexes of the boxes that correspond to an inline placeholder.
|
||||
std::vector<size_t> inline_placeholder_boxes_;
|
||||
// The indexes of instances of 0xFFFC that correspond to placeholders. This is
|
||||
// necessary since the user may pass in manually entered 0xFFFC values using
|
||||
// AddText().
|
||||
std::unordered_set<size_t> obj_replacement_char_indexes_;
|
||||
StyledRuns runs_;
|
||||
ParagraphStyle paragraph_style_;
|
||||
std::shared_ptr<FontCollection> font_collection_;
|
||||
@ -304,19 +337,35 @@ class Paragraph {
|
||||
bool is_ghost)
|
||||
: start_(s), end_(e), direction_(d), style_(&st), is_ghost_(is_ghost) {}
|
||||
|
||||
// Constructs a placeholder bidi run.
|
||||
BidiRun(size_t s,
|
||||
size_t e,
|
||||
TextDirection d,
|
||||
const TextStyle& st,
|
||||
PlaceholderRun& placeholder)
|
||||
: start_(s),
|
||||
end_(e),
|
||||
direction_(d),
|
||||
style_(&st),
|
||||
placeholder_run_(&placeholder) {}
|
||||
|
||||
size_t start() const { return start_; }
|
||||
size_t end() const { return end_; }
|
||||
size_t size() const { return end_ - start_; }
|
||||
TextDirection direction() const { return direction_; }
|
||||
const TextStyle& style() const { return *style_; }
|
||||
PlaceholderRun* placeholder_run() const { return placeholder_run_; }
|
||||
bool is_rtl() const { return direction_ == TextDirection::rtl; }
|
||||
// Tracks if the run represents trailing whitespace.
|
||||
bool is_ghost() const { return is_ghost_; }
|
||||
bool is_placeholder_run() const { return placeholder_run_ != nullptr; }
|
||||
|
||||
private:
|
||||
size_t start_, end_;
|
||||
TextDirection direction_;
|
||||
const TextStyle* style_;
|
||||
bool is_ghost_;
|
||||
PlaceholderRun* placeholder_run_ = nullptr;
|
||||
};
|
||||
|
||||
struct GlyphPosition {
|
||||
@ -347,13 +396,15 @@ class Paragraph {
|
||||
size_t line_number;
|
||||
SkFontMetrics font_metrics;
|
||||
TextDirection direction;
|
||||
const PlaceholderRun* placeholder_run;
|
||||
|
||||
CodeUnitRun(std::vector<GlyphPosition>&& p,
|
||||
Range<size_t> cu,
|
||||
Range<double> x,
|
||||
size_t line,
|
||||
const SkFontMetrics& metrics,
|
||||
TextDirection dir);
|
||||
TextDirection dir,
|
||||
const PlaceholderRun* placeholder);
|
||||
|
||||
void Shift(double delta);
|
||||
};
|
||||
@ -364,6 +415,8 @@ class Paragraph {
|
||||
// Holds the positions of each range of code units in the text.
|
||||
// Sorted in code unit index order.
|
||||
std::vector<CodeUnitRun> code_unit_runs_;
|
||||
// Holds the positions of the inline placeholders.
|
||||
std::vector<CodeUnitRun> inline_placeholder_code_unit_runs_;
|
||||
|
||||
// The max width of the paragraph as provided in the most recent Layout()
|
||||
// call.
|
||||
@ -394,6 +447,10 @@ class Paragraph {
|
||||
|
||||
void SetFontCollection(std::shared_ptr<FontCollection> font_collection);
|
||||
|
||||
void SetInlinePlaceholders(
|
||||
std::vector<PlaceholderRun> inline_placeholders,
|
||||
std::unordered_set<size_t> obj_replacement_char_indexes);
|
||||
|
||||
// Break the text into lines.
|
||||
bool ComputeLineBreaks();
|
||||
|
||||
@ -403,6 +460,13 @@ class Paragraph {
|
||||
// Calculates and populates strut based on paragraph_style_ strut info.
|
||||
void ComputeStrut(StrutMetrics* strut, SkFont& font);
|
||||
|
||||
// Adjusts the ascent and descent based on the existence and type of
|
||||
// placeholder. This method sets the proper metrics to achieve the different
|
||||
// PlaceholderAlignment options.
|
||||
void ComputePlaceholder(PlaceholderRun* placeholder_run,
|
||||
double& ascent,
|
||||
double& descent);
|
||||
|
||||
bool IsStrutValid() const;
|
||||
|
||||
// Calculate the starting X offset of a line based on the line's width and
|
||||
|
||||
@ -77,11 +77,21 @@ void ParagraphBuilder::AddText(const char* text) {
|
||||
AddText(u16_text);
|
||||
}
|
||||
|
||||
void ParagraphBuilder::AddPlaceholder(PlaceholderRun& span) {
|
||||
obj_replacement_char_indexes_.insert(text_.size());
|
||||
runs_.StartRun(PeekStyleIndex(), text_.size());
|
||||
AddText(std::u16string(1ull, objReplacementChar));
|
||||
runs_.StartRun(PeekStyleIndex(), text_.size());
|
||||
inline_placeholders_.push_back(span);
|
||||
}
|
||||
|
||||
std::unique_ptr<Paragraph> ParagraphBuilder::Build() {
|
||||
runs_.EndRunIfNeeded(text_.size());
|
||||
|
||||
std::unique_ptr<Paragraph> paragraph = std::make_unique<Paragraph>();
|
||||
paragraph->SetText(std::move(text_), std::move(runs_));
|
||||
paragraph->SetInlinePlaceholders(std::move(inline_placeholders_),
|
||||
std::move(obj_replacement_char_indexes_));
|
||||
paragraph->SetParagraphStyle(paragraph_style_);
|
||||
paragraph->SetFontCollection(font_collection_);
|
||||
SetParagraphStyle(paragraph_style_);
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include "font_collection.h"
|
||||
#include "paragraph.h"
|
||||
#include "paragraph_style.h"
|
||||
#include "placeholder_run.h"
|
||||
#include "styled_runs.h"
|
||||
#include "text_style.h"
|
||||
|
||||
@ -66,6 +67,14 @@ class ParagraphBuilder {
|
||||
// Converts to u16string before adding.
|
||||
void AddText(const char* text);
|
||||
|
||||
// Pushes the information requried to leave an open space, where Flutter may
|
||||
// draw a custom placeholder into.
|
||||
//
|
||||
// Internally, this method adds a single object replacement character (0xFFFC)
|
||||
// and emplaces a new PlaceholderRun instance to the vector of inline
|
||||
// placeholders.
|
||||
void AddPlaceholder(PlaceholderRun& span);
|
||||
|
||||
void SetParagraphStyle(const ParagraphStyle& style);
|
||||
|
||||
// Constructs a Paragraph object that can be used to layout and paint the text
|
||||
@ -74,6 +83,15 @@ class ParagraphBuilder {
|
||||
|
||||
private:
|
||||
std::vector<uint16_t> text_;
|
||||
// A vector of PlaceholderRuns, which detail the sizes, positioning and break
|
||||
// behavior of the empty spaces to leave. Each placeholder span corresponds to
|
||||
// a 0xFFFC (object replacement character) in text_, which indicates the
|
||||
// position in the text where the placeholder will occur. There should be an
|
||||
// equal number of 0xFFFC characters and elements in this vector.
|
||||
std::vector<PlaceholderRun> inline_placeholders_;
|
||||
// The indexes of the obj replacement characters added through
|
||||
// ParagraphBuilder::addPlaceholder().
|
||||
std::unordered_set<size_t> obj_replacement_char_indexes_;
|
||||
std::vector<size_t> style_stack_;
|
||||
std::shared_ptr<FontCollection> font_collection_;
|
||||
StyledRuns runs_;
|
||||
|
||||
34
engine/src/flutter/third_party/txt/src/txt/placeholder_run.cc
vendored
Normal file
34
engine/src/flutter/third_party/txt/src/txt/placeholder_run.cc
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2019 Google, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "placeholder_run.h"
|
||||
|
||||
namespace txt {
|
||||
|
||||
PlaceholderRun::PlaceholderRun() {}
|
||||
|
||||
PlaceholderRun::PlaceholderRun(double width,
|
||||
double height,
|
||||
PlaceholderAlignment alignment,
|
||||
TextBaseline baseline,
|
||||
double baseline_offset)
|
||||
: width(width),
|
||||
height(height),
|
||||
alignment(alignment),
|
||||
baseline(baseline),
|
||||
baseline_offset(baseline_offset) {}
|
||||
|
||||
} // namespace txt
|
||||
89
engine/src/flutter/third_party/txt/src/txt/placeholder_run.h
vendored
Normal file
89
engine/src/flutter/third_party/txt/src/txt/placeholder_run.h
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef LIB_TXT_SRC_PLACEHOLDER_RUN_H_
|
||||
#define LIB_TXT_SRC_PLACEHOLDER_RUN_H_
|
||||
|
||||
#include "text_baseline.h"
|
||||
|
||||
namespace txt {
|
||||
|
||||
/// Where to vertically align the placeholder relative to the surrounding text.
|
||||
enum class PlaceholderAlignment {
|
||||
/// Match the baseline of the placeholder with the baseline.
|
||||
kBaseline,
|
||||
|
||||
/// Align the bottom edge of the placeholder with the baseline such that the
|
||||
/// placeholder sits on top of the baseline.
|
||||
kAboveBaseline,
|
||||
|
||||
/// Align the top edge of the placeholder with the baseline specified in
|
||||
/// such that the placeholder hangs below the baseline.
|
||||
kBelowBaseline,
|
||||
|
||||
/// Align the top edge of the placeholder with the top edge of the font.
|
||||
/// When the placeholder is very tall, the extra space will hang from
|
||||
/// the top and extend through the bottom of the line.
|
||||
kTop,
|
||||
|
||||
/// Align the bottom edge of the placeholder with the top edge of the font.
|
||||
/// When the placeholder is very tall, the extra space will rise from
|
||||
/// the bottom and extend through the top of the line.
|
||||
kBottom,
|
||||
|
||||
/// Align the middle of the placeholder with the middle of the text. When the
|
||||
/// placeholder is very tall, the extra space will grow equally from
|
||||
/// the top and bottom of the line.
|
||||
kMiddle,
|
||||
};
|
||||
|
||||
// Represents the metrics required to fully define a rect that will fit a
|
||||
// placeholder.
|
||||
//
|
||||
// LibTxt will leave an empty space in the layout of the text of the size
|
||||
// defined by this class. After layout, the framework will draw placeholders
|
||||
// into the reserved space.
|
||||
class PlaceholderRun {
|
||||
public:
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
|
||||
PlaceholderAlignment alignment;
|
||||
|
||||
TextBaseline baseline;
|
||||
|
||||
// Distance from the top edge of the rect to the baseline position. This
|
||||
// baseline will be aligned against the alphabetic baseline of the surrounding
|
||||
// text.
|
||||
//
|
||||
// Positive values drop the baseline lower (positions the rect higher) and
|
||||
// small or negative values will cause the rect to be positioned underneath
|
||||
// the line. When baseline == height, the bottom edge of the rect will rest on
|
||||
// the alphabetic baseline.
|
||||
double baseline_offset = 0;
|
||||
|
||||
PlaceholderRun();
|
||||
|
||||
PlaceholderRun(double width,
|
||||
double height,
|
||||
PlaceholderAlignment alignment,
|
||||
TextBaseline baseline,
|
||||
double baseline_offset);
|
||||
};
|
||||
|
||||
} // namespace txt
|
||||
|
||||
#endif // LIB_TXT_SRC_PLACEHOLDER_RUN_H_
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user