blob: 405605240d6ff594510115c98062443bfed45917 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
class SkParagraphStyle implements ui.ParagraphStyle {
SkParagraphStyle({
ui.TextAlign textAlign,
ui.TextDirection textDirection,
int maxLines,
String fontFamily,
double fontSize,
double height,
ui.TextHeightBehavior textHeightBehavior,
ui.FontWeight fontWeight,
ui.FontStyle fontStyle,
ui.StrutStyle strutStyle,
String ellipsis,
ui.Locale locale,
}) {
skParagraphStyle = toSkParagraphStyle(
textAlign,
textDirection,
maxLines,
fontFamily,
fontSize,
height,
textHeightBehavior,
fontWeight,
fontStyle,
ellipsis,
);
assert(skParagraphStyle != null);
_textDirection = textDirection ?? ui.TextDirection.ltr;
_fontFamily = fontFamily;
}
js.JsObject skParagraphStyle;
ui.TextDirection _textDirection;
String _fontFamily;
static Map<String, dynamic> toSkTextStyle(
String fontFamily,
double fontSize,
ui.FontWeight fontWeight,
ui.FontStyle fontStyle,
) {
final Map<String, dynamic> skTextStyle = <String, dynamic>{};
if (fontWeight != null || fontStyle != null) {
skTextStyle['fontStyle'] = toSkFontStyle(fontWeight, fontStyle);
}
if (fontSize != null) {
skTextStyle['fontSize'] = fontSize;
}
if (fontFamily == null ||
!skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto';
}
if (skiaFontCollection.fontFamilyOverrides.containsKey(fontFamily)) {
fontFamily = skiaFontCollection.fontFamilyOverrides[fontFamily];
}
skTextStyle['fontFamilies'] = [fontFamily];
return skTextStyle;
}
static js.JsObject toSkParagraphStyle(
ui.TextAlign textAlign,
ui.TextDirection textDirection,
int maxLines,
String fontFamily,
double fontSize,
double height,
ui.TextHeightBehavior textHeightBehavior,
ui.FontWeight fontWeight,
ui.FontStyle fontStyle,
String ellipsis,
) {
final Map<String, dynamic> skParagraphStyle = <String, dynamic>{};
if (textAlign != null) {
switch (textAlign) {
case ui.TextAlign.left:
skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Left'];
break;
case ui.TextAlign.right:
skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Right'];
break;
case ui.TextAlign.center:
skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Center'];
break;
case ui.TextAlign.justify:
skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Justify'];
break;
case ui.TextAlign.start:
skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Start'];
break;
case ui.TextAlign.end:
skParagraphStyle['textAlign'] = canvasKit['TextAlign']['End'];
break;
}
}
if (textDirection != null) {
switch (textDirection) {
case ui.TextDirection.ltr:
skParagraphStyle['textDirection'] = canvasKit['TextDirection']['LTR'];
break;
case ui.TextDirection.rtl:
skParagraphStyle['textDirection'] = canvasKit['TextDirection']['RTL'];
break;
}
}
if (height != null) {
skParagraphStyle['heightMultiplier'] = height;
}
if (textHeightBehavior != null) {
skParagraphStyle['textHeightBehavior'] = textHeightBehavior.encode();
}
if (maxLines != null) {
skParagraphStyle['maxLines'] = maxLines;
}
if (ellipsis != null) {
skParagraphStyle['ellipsis'] = ellipsis;
}
skParagraphStyle['textStyle'] =
toSkTextStyle(fontFamily, fontSize, fontWeight, fontStyle);
return canvasKit.callMethod(
'ParagraphStyle', <js.JsObject>[js.JsObject.jsify(skParagraphStyle)]);
}
}
class SkTextStyle implements ui.TextStyle {
js.JsObject skTextStyle;
SkTextStyle({
ui.Color color,
ui.TextDecoration decoration,
ui.Color decorationColor,
ui.TextDecorationStyle decorationStyle,
double decorationThickness,
ui.FontWeight fontWeight,
ui.FontStyle fontStyle,
ui.TextBaseline textBaseline,
String fontFamily,
List<String> fontFamilyFallback,
double fontSize,
double letterSpacing,
double wordSpacing,
double height,
ui.Locale locale,
SkPaint background,
SkPaint foreground,
List<ui.Shadow> shadows,
List<ui.FontFeature> fontFeatures,
}) {
final Map<String, dynamic> style = <String, dynamic>{};
if (background != null) {
style['backgroundColor'] = background.skiaObject;
}
if (color != null) {
style['color'] = color.value;
}
if (decoration != null) {
int decorationValue = canvasKit['NoDecoration'];
if (decoration.contains(ui.TextDecoration.underline)) {
decorationValue |= canvasKit['UnderlineDecoration'];
}
if (decoration.contains(ui.TextDecoration.overline)) {
decorationValue |= canvasKit['OverlineDecoration'];
}
if (decoration.contains(ui.TextDecoration.lineThrough)) {
decorationValue |= canvasKit['LineThroughDecoration'];
}
style['decoration'] = decorationValue;
}
if (decorationThickness != null) {
style['decorationThickness'] = decorationThickness;
}
if (fontSize != null) {
style['fontSize'] = fontSize;
}
if (fontFamily == null ||
!skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto';
}
if (skiaFontCollection.fontFamilyOverrides.containsKey(fontFamily)) {
fontFamily = skiaFontCollection.fontFamilyOverrides[fontFamily];
}
List<String> fontFamilies = <String>[fontFamily];
if (fontFamilyFallback != null &&
!fontFamilyFallback.every((font) => fontFamily == font)) {
fontFamilies.addAll(fontFamilyFallback);
}
style['fontFamilies'] = fontFamilies;
if (fontWeight != null || fontStyle != null) {
style['fontStyle'] = toSkFontStyle(fontWeight, fontStyle);
}
if (foreground != null) {
style['foreground'] = foreground.skiaObject;
}
// TODO(hterkelsen): Add support for
// - decorationColor
// - decorationStyle
// - textBaseline
// - letterSpacing
// - wordSpacing
// - height
// - locale
// - shadows
// - fontFeatures
skTextStyle = canvasKit
.callMethod('TextStyle', <js.JsObject>[js.JsObject.jsify(style)]);
assert(skTextStyle != null);
}
}
Map<String, js.JsObject> toSkFontStyle(
ui.FontWeight fontWeight, ui.FontStyle fontStyle) {
Map<String, js.JsObject> style = <String, js.JsObject>{};
if (fontWeight != null) {
switch (fontWeight) {
case ui.FontWeight.w100:
style['weight'] = canvasKit['FontWeight']['Thin'];
break;
case ui.FontWeight.w200:
style['weight'] = canvasKit['FontWeight']['ExtraLight'];
break;
case ui.FontWeight.w300:
style['weight'] = canvasKit['FontWeight']['Light'];
break;
case ui.FontWeight.w400:
style['weight'] = canvasKit['FontWeight']['Normal'];
break;
case ui.FontWeight.w500:
style['weight'] = canvasKit['FontWeight']['Medium'];
break;
case ui.FontWeight.w600:
style['weight'] = canvasKit['FontWeight']['SemiBold'];
break;
case ui.FontWeight.w700:
style['weight'] = canvasKit['FontWeight']['Bold'];
break;
case ui.FontWeight.w800:
style['weight'] = canvasKit['FontWeight']['ExtraBold'];
break;
case ui.FontWeight.w900:
style['weight'] = canvasKit['FontWeight']['ExtraBlack'];
break;
}
}
if (fontStyle != null) {
switch (fontStyle) {
case ui.FontStyle.normal:
style['slant'] = canvasKit['FontSlant']['Upright'];
break;
case ui.FontStyle.italic:
style['slant'] = canvasKit['FontSlant']['Italic'];
break;
}
}
return style;
}
class SkParagraph implements ui.Paragraph {
SkParagraph(this.skParagraph, this._textDirection, this._fontFamily);
final js.JsObject skParagraph;
final ui.TextDirection _textDirection;
final String _fontFamily;
@override
double get alphabeticBaseline =>
skParagraph.callMethod('getAlphabeticBaseline');
@override
bool get didExceedMaxLines => skParagraph.callMethod('didExceedMaxLines');
@override
double get height => skParagraph.callMethod('getHeight');
@override
double get ideographicBaseline =>
skParagraph.callMethod('getIdeographicBaseline');
@override
double get longestLine => skParagraph.callMethod('getLongestLine');
@override
double get maxIntrinsicWidth =>
skParagraph.callMethod('getMaxIntrinsicWidth');
@override
double get minIntrinsicWidth =>
skParagraph.callMethod('getMinIntrinsicWidth');
@override
double get width => skParagraph.callMethod('getMaxWidth');
// TODO(hterkelsen): Implement placeholders once it's in CanvasKit
@override
List<ui.TextBox> getBoxesForPlaceholders() {
return const <ui.TextBox>[];
}
@override
List<ui.TextBox> getBoxesForRange(
int start,
int end, {
ui.BoxHeightStyle boxHeightStyle: ui.BoxHeightStyle.tight,
ui.BoxWidthStyle boxWidthStyle: ui.BoxWidthStyle.tight,
}) {
if (start < 0 || end < 0) {
return const <ui.TextBox>[];
}
js.JsObject heightStyle;
switch (boxHeightStyle) {
case ui.BoxHeightStyle.tight:
heightStyle = canvasKit['RectHeightStyle']['Tight'];
break;
case ui.BoxHeightStyle.max:
heightStyle = canvasKit['RectHeightStyle']['Max'];
break;
default:
// TODO(hterkelsen): Support all height styles
html.window.console.warn(
'We do not support $boxHeightStyle. Defaulting to BoxHeightStyle.tight');
heightStyle = canvasKit['RectHeightStyle']['Tight'];
break;
}
js.JsObject widthStyle;
switch (boxWidthStyle) {
case ui.BoxWidthStyle.tight:
widthStyle = canvasKit['RectWidthStyle']['Tight'];
break;
case ui.BoxWidthStyle.max:
widthStyle = canvasKit['RectWidthStyle']['Max'];
break;
}
List<js.JsObject> skRects =
skParagraph.callMethod('getRectsForRange', <dynamic>[
start,
end,
heightStyle,
widthStyle,
]);
List<ui.TextBox> result = List<ui.TextBox>(skRects.length);
for (int i = 0; i < skRects.length; i++) {
final js.JsObject rect = skRects[i];
result[i] = ui.TextBox.fromLTRBD(
rect['fLeft'],
rect['fTop'],
rect['fRight'],
rect['fBottom'],
_textDirection,
);
}
return result;
}
@override
ui.TextPosition getPositionForOffset(ui.Offset offset) {
js.JsObject positionWithAffinity =
skParagraph.callMethod('getGlyphPositionAtCoordinate', <double>[
offset.dx,
offset.dy,
]);
return fromPositionWithAffinity(positionWithAffinity);
}
@override
ui.TextRange getWordBoundary(ui.TextPosition position) {
js.JsObject skRange =
skParagraph.callMethod('getWordBoundary', <int>[position.offset]);
return ui.TextRange(start: skRange['start'], end: skRange['end']);
}
@override
void layout(ui.ParagraphConstraints constraints) {
assert(constraints.width != null);
// Infinite width breaks layout, just use a very large number instead.
// TODO(het): Remove this once https://bugs.chromium.org/p/skia/issues/detail?id=9874
// is fixed.
double width;
const double largeFiniteWidth = 1000000;
if (constraints.width.isInfinite) {
width = largeFiniteWidth;
} else {
width = constraints.width;
}
// TODO(het): CanvasKit throws an exception when laid out with
// a font that wasn't registered.
try {
skParagraph.callMethod('layout', <double>[width]);
} catch (e) {
html.window.console.warn('CanvasKit threw an exception while laying '
'out the paragraph. The font was "$_fontFamily". Exception:\n$e');
rethrow;
}
}
@override
ui.TextRange getLineBoundary(ui.TextPosition position) {
// TODO(hterkelsen): Implement this when it's added to CanvasKit
throw UnimplementedError('getLineBoundary');
}
@override
List<ui.LineMetrics> computeLineMetrics() {
// TODO(hterkelsen): Implement this when it's added to CanvasKit
throw UnimplementedError('computeLineMetrics');
}
}
class SkParagraphBuilder implements ui.ParagraphBuilder {
js.JsObject _paragraphBuilder;
ui.TextDirection _textDirection;
String _fontFamily;
SkParagraphBuilder(ui.ParagraphStyle style) {
SkParagraphStyle skStyle = style;
_textDirection = skStyle._textDirection;
_fontFamily = skStyle._fontFamily;
_paragraphBuilder = canvasKit['ParagraphBuilder'].callMethod(
'Make',
<js.JsObject>[
skStyle.skParagraphStyle,
skiaFontCollection.skFontMgr,
],
);
}
// TODO(hterkelsen): Implement placeholders.
@override
void addPlaceholder(
double width,
double height,
ui.PlaceholderAlignment alignment, {
double scale,
double baselineOffset,
ui.TextBaseline baseline,
}) {
throw UnimplementedError('addPlaceholder');
}
@override
void addText(String text) {
_paragraphBuilder.callMethod('addText', <String>[text]);
}
@override
ui.Paragraph build() {
final SkParagraph paragraph = SkParagraph(
_paragraphBuilder.callMethod('build'), _textDirection, _fontFamily);
_paragraphBuilder.callMethod('delete');
_paragraphBuilder = null;
return paragraph;
}
@override
int get placeholderCount => throw UnimplementedError('placeholderCount');
// TODO(hterkelsen): Implement this once CanvasKit exposes placeholders.
@override
List<double> get placeholderScales => const <double>[];
@override
void pop() {
_paragraphBuilder.callMethod('pop');
}
@override
void pushStyle(ui.TextStyle style) {
final SkTextStyle skStyle = style;
_paragraphBuilder
.callMethod('pushStyle', <js.JsObject>[skStyle.skTextStyle]);
}
}