未验证 提交 a4abfb23 编写于 作者: G Gary Qian 提交者: GitHub

Text inline widget LibTxt/dart:ui implementation (#8207)

上级 f9ce0167
......@@ -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
......
......@@ -1195,8 +1195,9 @@ enum BoxHeightStyle {
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
/// 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],
......@@ -1242,12 +1243,57 @@ enum BoxWidthStyle {
/// 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.
/// 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.
///
/// A paragraph retains the size and position of each glyph in the text and can
......@@ -1490,6 +1536,9 @@ class Paragraph {
/// 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.
......@@ -1536,6 +1585,17 @@ class Paragraph {
);
}
/// 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() {
// TODO(garyq): Implement stub_ui version of this.
throw UnimplementedError();
}
/// Returns the text position closest to the given offset.
///
/// It does so by performing a binary search to find where the tap occurred
......@@ -1628,6 +1688,7 @@ class ParagraphBuilder {
/// [Paragraph].
ParagraphBuilder(ParagraphStyle style) : _paragraphStyle = style {
// TODO(b/128317744): Implement support for strut font families.
_placeholderCount = 0;
List<String> strutFontFamilies;
if (style._strutStyle != null) {
strutFontFamilies = <String>[];
......@@ -1642,6 +1703,14 @@ class ParagraphBuilder {
element: _paragraphElement, style: _paragraphStyle);
}
/// 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.
......@@ -1650,7 +1719,7 @@ class ParagraphBuilder {
}
// TODO(yjbanov): do we need to do this?
// static String _encodeLocale(Locale locale) => locale?.toString() ?? '';
// static String _encodeLocale(Locale locale) => locale?.toString() ?? '';
/// Ends the effect of the most recent call to [pushStyle].
///
......@@ -1669,6 +1738,58 @@ class ParagraphBuilder {
_ops.add(text);
}
/// 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.
void addPlaceholder(double width, double height, PlaceholderAlignment alignment, {
double scale,
double baselineOffset,
TextBaseline baseline,
}) {
// TODO(garyq): Implement stub_ui version of this.
throw UnimplementedError();
}
/// Applies the given paragraph style and returns a [Paragraph] containing the
/// added text and associated styling.
///
......
......@@ -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,
/// 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 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,
/// 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,
/// 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,
/// 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 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.
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,
/// 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,
// 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,
}
/// 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;
......
......@@ -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_;
......
/*
* 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
/*
* 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_
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册