blob: 056c52c859c816ceed92321df29bd191858437d6 [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.
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart';
import '../paragraph/helper.dart';
final EngineTextStyle defaultStyle = EngineTextStyle.only(
fontFamily: StyleManager.defaultFontFamily,
fontSize: StyleManager.defaultFontSize,
);
final EngineTextStyle style1 = defaultStyle.copyWith(fontSize: 20);
final EngineTextStyle style2 = defaultStyle.copyWith(color: blue);
final EngineTextStyle style3 = defaultStyle.copyWith(fontFamily: 'Roboto');
void main() {
internalBootstrapBrowserTest(() => testMain);
}
Future<void> testMain() async {
group('$LayoutFragmenter', () {
test('empty paragraph', () {
final CanvasParagraph paragraph1 = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {},
);
expect(split(paragraph1), <_Fragment>[
_Fragment('', endOfText, null, ffPrevious, defaultStyle),
]);
final CanvasParagraph paragraph2 = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.addText('');
},
);
expect(split(paragraph2), <_Fragment>[
_Fragment('', endOfText, null, ffPrevious, defaultStyle),
]);
final CanvasParagraph paragraph3 = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.pushStyle(style1);
builder.addText('');
},
);
expect(split(paragraph3), <_Fragment>[
_Fragment('', endOfText, null, ffPrevious, style1),
]);
});
test('single span', () {
final CanvasParagraph paragraph =
plain(EngineParagraphStyle(), 'Lorem 12 $rtlWord1 ipsum34');
expect(split(paragraph), <_Fragment>[
_Fragment('Lorem', prohibited, ltr, ffLtr, defaultStyle),
_Fragment(' ', opportunity, null, ffSandwich, defaultStyle, sp: 1),
_Fragment('12', prohibited, ltr, ffPrevious, defaultStyle),
_Fragment(' ', opportunity, null, ffSandwich, defaultStyle, sp: 1),
_Fragment(rtlWord1, prohibited, rtl, ffRtl, defaultStyle),
_Fragment(' ', opportunity, null, ffSandwich, defaultStyle, sp: 3),
_Fragment('ipsum34', endOfText, ltr, ffLtr, defaultStyle),
]);
});
test('multi span', () {
final CanvasParagraph paragraph = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.pushStyle(style1);
builder.addText('Lorem');
builder.pop();
builder.pushStyle(style2);
builder.addText(' ipsum 12 ');
builder.pop();
builder.pushStyle(style3);
builder.addText(' $rtlWord1 foo.');
builder.pop();
},
);
expect(split(paragraph), <_Fragment>[
_Fragment('Lorem', prohibited, ltr, ffLtr, style1),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 1),
_Fragment('ipsum', prohibited, ltr, ffLtr, style2),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 1),
_Fragment('12', prohibited, ltr, ffPrevious, style2),
_Fragment(' ', prohibited, null, ffSandwich, style2, sp: 1),
_Fragment(' ', opportunity, null, ffSandwich, style3, sp: 1),
_Fragment(rtlWord1, prohibited, rtl, ffRtl, style3),
_Fragment(' ', opportunity, null, ffSandwich, style3, sp: 1),
_Fragment('foo', prohibited, ltr, ffLtr, style3),
_Fragment('.', endOfText, null, ffSandwich, style3),
]);
});
test('new lines', () {
final CanvasParagraph paragraph = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.pushStyle(style1);
builder.addText('Lor\nem \n');
builder.pop();
builder.pushStyle(style2);
builder.addText(' \n ipsum 12 ');
builder.pop();
builder.pushStyle(style3);
builder.addText(' $rtlWord1 fo');
builder.pop();
builder.pushStyle(style1);
builder.addText('o.');
builder.pop();
},
);
expect(split(paragraph), <_Fragment>[
_Fragment('Lor', prohibited, ltr, ffLtr, style1),
_Fragment('\n', mandatory, null, ffSandwich, style1, nl: 1, sp: 1),
_Fragment('em', prohibited, ltr, ffLtr, style1),
_Fragment(' \n', mandatory, null, ffSandwich, style1, nl: 1, sp: 2),
_Fragment(' \n', mandatory, null, ffSandwich, style2, nl: 1, sp: 2),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 2),
_Fragment('ipsum', prohibited, ltr, ffLtr, style2),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 1),
_Fragment('12', prohibited, ltr, ffPrevious, style2),
_Fragment(' ', prohibited, null, ffSandwich, style2, sp: 1),
_Fragment(' ', opportunity, null, ffSandwich, style3, sp: 1),
_Fragment(rtlWord1, prohibited, rtl, ffRtl, style3),
_Fragment(' ', opportunity, null, ffSandwich, style3, sp: 1),
_Fragment('fo', prohibited, ltr, ffLtr, style3),
_Fragment('o', prohibited, ltr, ffLtr, style1),
_Fragment('.', endOfText, null, ffSandwich, style1),
]);
});
test('last line is empty', () {
final CanvasParagraph paragraph = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.pushStyle(style1);
builder.addText('Lorem \n');
builder.pop();
builder.pushStyle(style2);
builder.addText(' \n ipsum \n');
builder.pop();
},
);
expect(split(paragraph), <_Fragment>[
_Fragment('Lorem', prohibited, ltr, ffLtr, style1),
_Fragment(' \n', mandatory, null, ffSandwich, style1, nl: 1, sp: 2),
_Fragment(' \n', mandatory, null, ffSandwich, style2, nl: 1, sp: 2),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 2),
_Fragment('ipsum', prohibited, ltr, ffLtr, style2),
_Fragment(' \n', mandatory, null, ffSandwich, style2, nl: 1, sp: 2),
_Fragment('', endOfText, null, ffSandwich, style2),
]);
});
test('space-only spans', () {
final CanvasParagraph paragraph = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.addText('Lorem ');
builder.pushStyle(style1);
builder.addText(' ');
builder.pop();
builder.pushStyle(style2);
builder.addText(' ');
builder.pop();
builder.addText('ipsum');
},
);
expect(split(paragraph), <_Fragment>[
_Fragment('Lorem', prohibited, ltr, ffLtr, defaultStyle),
_Fragment(' ', prohibited, null, ffSandwich, defaultStyle, sp: 1),
_Fragment(' ', prohibited, null, ffSandwich, style1, sp: 3),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 2),
_Fragment('ipsum', endOfText, ltr, ffLtr, defaultStyle),
]);
});
test('placeholders', () {
final CanvasParagraph paragraph = rich(
EngineParagraphStyle(),
(CanvasParagraphBuilder builder) {
builder.pushStyle(style1);
builder.addPlaceholder(100, 100, PlaceholderAlignment.top);
builder.addText('Lorem');
builder.addPlaceholder(100, 100, PlaceholderAlignment.top);
builder.addText('ipsum\n');
builder.addPlaceholder(100, 100, PlaceholderAlignment.top);
builder.pop();
builder.pushStyle(style2);
builder.addText('$rtlWord1 ');
builder.addPlaceholder(100, 100, PlaceholderAlignment.top);
builder.addText('\nsit');
builder.pop();
builder.addPlaceholder(100, 100, PlaceholderAlignment.top);
},
);
expect(split(paragraph), <_Fragment>[
_Fragment(placeholderChar, opportunity, ltr, ffLtr, style1),
_Fragment('Lorem', opportunity, ltr, ffLtr, style1),
_Fragment(placeholderChar, opportunity, ltr, ffLtr, style1),
_Fragment('ipsum', prohibited, ltr, ffLtr, style1),
_Fragment('\n', mandatory, null, ffSandwich, style1, nl: 1, sp: 1),
_Fragment(placeholderChar, opportunity, ltr, ffLtr, style1),
_Fragment(rtlWord1, prohibited, rtl, ffRtl, style2),
_Fragment(' ', opportunity, null, ffSandwich, style2, sp: 1),
_Fragment(placeholderChar, prohibited, ltr, ffLtr, style2),
_Fragment('\n', mandatory, null, ffSandwich, style2, nl: 1, sp: 1),
_Fragment('sit', opportunity, ltr, ffLtr, style2),
_Fragment(placeholderChar, endOfText, ltr, ffLtr, defaultStyle),
]);
});
});
}
/// Holds information about how a fragment.
class _Fragment {
_Fragment(this.text, this.type, this.textDirection, this.fragmentFlow, this.style, {
this.nl = 0,
this.sp = 0,
});
factory _Fragment._fromLayoutFragment(String text, LayoutFragment layoutFragment) {
return _Fragment(
text.substring(layoutFragment.start, layoutFragment.end),
layoutFragment.type,
layoutFragment.textDirection,
layoutFragment.fragmentFlow,
layoutFragment.style,
nl: layoutFragment.trailingNewlines,
sp: layoutFragment.trailingSpaces,
);
}
final String text;
final LineBreakType type;
final TextDirection? textDirection;
final FragmentFlow fragmentFlow;
final EngineTextStyle style;
/// The number of trailing new line characters.
final int nl;
/// The number of trailing spaces.
final int sp;
@override
int get hashCode => Object.hash(text, type, textDirection, fragmentFlow, style, nl, sp);
@override
bool operator ==(Object other) {
return other is _Fragment &&
other.text == text &&
other.type == type &&
other.textDirection == textDirection &&
other.fragmentFlow == fragmentFlow &&
other.style == style &&
other.nl == nl &&
other.sp == sp;
}
@override
String toString() {
return '"$text" ($type, $textDirection, $fragmentFlow, nl: $nl, sp: $sp)';
}
}
List<_Fragment> split(CanvasParagraph paragraph) {
return <_Fragment>[
for (final LayoutFragment layoutFragment
in computeLayoutFragments(paragraph))
_Fragment._fromLayoutFragment(paragraph.plainText, layoutFragment)
];
}
List<LayoutFragment> computeLayoutFragments(CanvasParagraph paragraph) {
return LayoutFragmenter(paragraph.plainText, paragraph.spans).fragment();
}
extension _EngineTextStyle on EngineTextStyle {
EngineTextStyle copyWith({
Color? color,
TextDecoration? decoration,
Color? decorationColor,
TextDecorationStyle? decorationStyle,
double? decorationThickness,
FontWeight? fontWeight,
FontStyle? fontStyle,
TextBaseline? textBaseline,
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
TextLeadingDistribution? leadingDistribution,
Locale? locale,
Paint? background,
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
List<FontVariation>? fontVariations,
}) {
return EngineTextStyle(
color: color ?? this.color,
decoration: decoration ?? this.decoration,
decorationColor: decorationColor ?? this.decorationColor,
decorationStyle: decorationStyle ?? this.decorationStyle,
decorationThickness: decorationThickness ?? this.decorationThickness,
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
textBaseline: textBaseline ?? this.textBaseline,
fontFamily: fontFamily ?? this.fontFamily,
fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback,
fontSize: fontSize ?? this.fontSize,
letterSpacing: letterSpacing ?? this.letterSpacing,
wordSpacing: wordSpacing ?? this.wordSpacing,
height: height ?? this.height,
leadingDistribution: leadingDistribution,
locale: locale ?? this.locale,
background: background ?? this.background,
foreground: foreground ?? this.foreground,
shadows: shadows ?? this.shadows,
fontFeatures: fontFeatures ?? this.fontFeatures,
fontVariations: fontVariations ?? this.fontVariations,
);
}
}