mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Turn on wavy underlines. The waves aren't very pretty yet (they are too short somehow), I'll fix that in a subsequent CL.
I abstracted out the wavy underline code so that it doesn't duplicate the code for horizontal and vertical lines. R=abarth@chromium.org Review URL: https://codereview.chromium.org/1201503003.
This commit is contained in:
parent
8f20fb6e9a
commit
a12d43dc58
@ -796,10 +796,25 @@ static void adjustStepToDecorationLength(float& step, float& controlPointDistanc
|
||||
controlPointDistance += adjustment;
|
||||
}
|
||||
|
||||
struct CurveAlongX {
|
||||
static inline float x(const FloatPoint& p) { return p.x(); }
|
||||
static inline float y(const FloatPoint& p) { return p.y(); }
|
||||
static inline FloatPoint p(float x, float y) { return FloatPoint(x, y); }
|
||||
static inline void setX(FloatPoint& p, double x) { p.setX(x); }
|
||||
};
|
||||
|
||||
struct CurveAlongY {
|
||||
static inline float x(const FloatPoint& p) { return p.y(); }
|
||||
static inline float y(const FloatPoint& p) { return p.x(); }
|
||||
static inline FloatPoint p(float x, float y) { return FloatPoint(y, x); }
|
||||
static inline void setX(FloatPoint& p, double x) { p.setY(x); }
|
||||
};
|
||||
|
||||
/*
|
||||
* Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis.
|
||||
* The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve
|
||||
* form a diamond shape:
|
||||
* Draw one cubic Bezier curve and repeat the same pattern along the
|
||||
* the decoration's axis. The start point (p1), controlPoint1,
|
||||
* controlPoint2 and end point (p2) of the Bezier curve form a diamond
|
||||
* shape, as follows (the four points marked +):
|
||||
*
|
||||
* step
|
||||
* |-----------|
|
||||
@ -822,84 +837,62 @@ static void adjustStepToDecorationLength(float& step, float& controlPointDistanc
|
||||
*
|
||||
* |-----------|
|
||||
* step
|
||||
*
|
||||
* strokeWavyTextDecorationInternal() takes two points, p1 and p2.
|
||||
* These must be axis-aligned. If they are horizontally-aligned,
|
||||
* specialize it with CurveAlongX; if they are vertically aligned,
|
||||
* specialize it with CurveAlongY. The function is written as if it
|
||||
* was doing everything along the X axis; CurveAlongY just flips the
|
||||
* coordinates around.
|
||||
*/
|
||||
static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint p1, FloatPoint p2, float strokeThickness)
|
||||
template <class Curve> static void strokeWavyTextDecorationInternal(GraphicsContext* context, FloatPoint p1, FloatPoint p2, float strokeThickness)
|
||||
{
|
||||
ASSERT(Curve::y(p1) == Curve::y(p2)); // verify that this is indeed axis-aligned
|
||||
|
||||
context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
|
||||
|
||||
Path path;
|
||||
path.moveTo(p1);
|
||||
|
||||
// Distance between decoration's axis and Bezier curve's control points.
|
||||
// The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
|
||||
// the actual curve passes approximately at half of that distance, that is 3 pixels.
|
||||
// The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
|
||||
// as strockThickness increases to make the curve looks better.
|
||||
float controlPointDistance = 3 * std::max<float>(2, strokeThickness);
|
||||
float controlPointDistance = 2 * strokeThickness;
|
||||
float step = controlPointDistance;
|
||||
|
||||
// Increment used to form the diamond shape between start point (p1), control
|
||||
// points and end point (p2) along the axis of the decoration. Makes the
|
||||
// curve wider as strockThickness increases to make the curve looks better.
|
||||
float step = 2 * std::max<float>(2, strokeThickness);
|
||||
float yAxis = Curve::y(p1);
|
||||
float x1;
|
||||
float x2;
|
||||
|
||||
bool isVerticalLine = (p1.x() == p2.x());
|
||||
|
||||
if (isVerticalLine) {
|
||||
ASSERT(p1.x() == p2.x());
|
||||
|
||||
float xAxis = p1.x();
|
||||
float y1;
|
||||
float y2;
|
||||
|
||||
if (p1.y() < p2.y()) {
|
||||
y1 = p1.y();
|
||||
y2 = p2.y();
|
||||
} else {
|
||||
y1 = p2.y();
|
||||
y2 = p1.y();
|
||||
}
|
||||
|
||||
adjustStepToDecorationLength(step, controlPointDistance, y2 - y1);
|
||||
FloatPoint controlPoint1(xAxis + controlPointDistance, 0);
|
||||
FloatPoint controlPoint2(xAxis - controlPointDistance, 0);
|
||||
|
||||
for (float y = y1; y + 2 * step <= y2;) {
|
||||
controlPoint1.setY(y + step);
|
||||
controlPoint2.setY(y + step);
|
||||
y += 2 * step;
|
||||
path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
|
||||
}
|
||||
if (Curve::x(p1) < Curve::x(p2)) {
|
||||
x1 = Curve::x(p1);
|
||||
x2 = Curve::x(p2);
|
||||
} else {
|
||||
ASSERT(p1.y() == p2.y());
|
||||
x1 = Curve::x(p2);
|
||||
x2 = Curve::x(p1);
|
||||
}
|
||||
|
||||
float yAxis = p1.y();
|
||||
float x1;
|
||||
float x2;
|
||||
adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
|
||||
|
||||
if (p1.x() < p2.x()) {
|
||||
x1 = p1.x();
|
||||
x2 = p2.x();
|
||||
} else {
|
||||
x1 = p2.x();
|
||||
x2 = p1.x();
|
||||
}
|
||||
FloatPoint controlPoint1 = Curve::p(0, yAxis + controlPointDistance);
|
||||
FloatPoint controlPoint2 = Curve::p(0, yAxis - controlPointDistance);
|
||||
|
||||
adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
|
||||
FloatPoint controlPoint1(0, yAxis + controlPointDistance);
|
||||
FloatPoint controlPoint2(0, yAxis - controlPointDistance);
|
||||
|
||||
for (float x = x1; x + 2 * step <= x2;) {
|
||||
controlPoint1.setX(x + step);
|
||||
controlPoint2.setX(x + step);
|
||||
x += 2 * step;
|
||||
path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis));
|
||||
}
|
||||
for (float x = x1; x + 2 * step <= x2;) {
|
||||
Curve::setX(controlPoint1, x + step);
|
||||
Curve::setX(controlPoint2, x + step);
|
||||
x += 2 * step;
|
||||
path.addBezierCurveTo(controlPoint1, controlPoint2, Curve::p(x, yAxis));
|
||||
}
|
||||
|
||||
context->setShouldAntialias(true);
|
||||
context->strokePath(path);
|
||||
}
|
||||
|
||||
static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint p1, FloatPoint p2, float strokeThickness)
|
||||
{
|
||||
if (p1.y() == p2.y()) // horizontal line
|
||||
strokeWavyTextDecorationInternal<CurveAlongX>(context, p1, p2, strokeThickness);
|
||||
else // vertical line
|
||||
strokeWavyTextDecorationInternal<CurveAlongY>(context, p1, p2, strokeThickness);
|
||||
}
|
||||
|
||||
static bool shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)
|
||||
{
|
||||
return decorationStyle == TextDecorationStyleDotted || decorationStyle == TextDecorationStyleDashed;
|
||||
@ -962,7 +955,7 @@ void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint&
|
||||
// Set the thick of the line to be 10% (or something else ?)of the computed font size and not less than 1px.
|
||||
|
||||
// Update Underline thickness, in case we have Faulty Font Metrics calculating underline thickness by old method.
|
||||
float textDecorationThickness = styleToUse->fontMetrics().underlineThickness();
|
||||
float textDecorationThickness = styleToUse->fontMetrics().underlineThickness(); // TODO(ianh): Make this author-controllable
|
||||
int fontHeightInt = (int)(styleToUse->fontMetrics().floatHeight() + 0.5);
|
||||
if ((textDecorationThickness == 0.f) || (textDecorationThickness >= (fontHeightInt >> 1)))
|
||||
textDecorationThickness = std::max(1.f, styleToUse->computedFontSize() / 10.f);
|
||||
|
||||
@ -36,7 +36,7 @@ CSSAttributeCaseSensitivity status=experimental
|
||||
CSSTouchActionDelay status=test
|
||||
CSSViewport status=experimental
|
||||
CSS3Text status=experimental
|
||||
CSS3TextDecorations status=experimental
|
||||
CSS3TextDecorations status=stable
|
||||
CustomSchemeHandler depends_on=NavigatorContentUtils, status=experimental
|
||||
Database status=stable
|
||||
DeviceLight status=experimental
|
||||
|
||||
@ -44,7 +44,11 @@ HAL: This mission is too important for me to allow you to jeopardize it.''';
|
||||
final TextStyle daveStyle = new TextStyle(color: Indigo[400]);
|
||||
final TextStyle halStyle = new TextStyle(color: Red[400], fontFamily: "monospace");
|
||||
final TextStyle boldStyle = const TextStyle(fontWeight: bold);
|
||||
final TextStyle underlineStyle = const TextStyle(decoration: underline);
|
||||
final TextStyle underlineStyle = const TextStyle(
|
||||
decoration: underline,
|
||||
decorationColor: const Color(0xFF000000),
|
||||
decorationStyle: TextDecorationStyle.wavy
|
||||
);
|
||||
|
||||
Component toStyledText(String name, String text) {
|
||||
TextStyle lineStyle = (name == "Dave") ? daveStyle : halStyle;
|
||||
|
||||
@ -39,9 +39,9 @@ enum TextDecoration {
|
||||
lineThrough
|
||||
}
|
||||
|
||||
const underline = const <TextDecoration> [TextDecoration.underline];
|
||||
const overline = const <TextDecoration> [TextDecoration.overline];
|
||||
const lineThrough = const <TextDecoration> [TextDecoration.lineThrough];
|
||||
const underline = const <TextDecoration>[TextDecoration.underline];
|
||||
const overline = const <TextDecoration>[TextDecoration.overline];
|
||||
const lineThrough = const <TextDecoration>[TextDecoration.lineThrough];
|
||||
|
||||
enum TextDecorationStyle {
|
||||
solid,
|
||||
@ -58,7 +58,7 @@ class TextStyle {
|
||||
this.fontSize,
|
||||
this.fontWeight,
|
||||
this.textAlign,
|
||||
List<TextDecoration> this.decoration,
|
||||
this.decoration,
|
||||
this.decorationColor,
|
||||
this.decorationStyle
|
||||
});
|
||||
@ -68,21 +68,10 @@ class TextStyle {
|
||||
final double fontSize; // in pixels
|
||||
final FontWeight fontWeight;
|
||||
final TextAlign textAlign;
|
||||
final List<TextDecoration> decoration;
|
||||
final List<TextDecoration> decoration; // TODO(ianh): Switch this to a Set<> once Dart supports constant Sets
|
||||
final Color decorationColor;
|
||||
final TextDecorationStyle decorationStyle;
|
||||
|
||||
String _decorationToString() {
|
||||
assert(decoration != null);
|
||||
const toCSS = const {
|
||||
TextDecoration.none: 'none',
|
||||
TextDecoration.underline: 'underline',
|
||||
TextDecoration.overline: 'overline',
|
||||
TextDecoration.lineThrough: 'lineThrough'
|
||||
};
|
||||
return decoration.map((d) => toCSS[d]).join(' ');
|
||||
}
|
||||
|
||||
TextStyle copyWith({
|
||||
Color color,
|
||||
double fontSize,
|
||||
@ -104,6 +93,77 @@ class TextStyle {
|
||||
);
|
||||
}
|
||||
|
||||
static String _colorToCSSString(Color color) {
|
||||
return 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})';
|
||||
}
|
||||
|
||||
static String _fontFamilyToCSSString(String fontFamily) {
|
||||
// TODO(hansmuller): escape the fontFamily string.
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
static String _decorationToCSSString(List<TextDecoration> decoration) {
|
||||
assert(decoration != null);
|
||||
const toCSS = const <TextDecoration, String>{
|
||||
TextDecoration.none: 'none',
|
||||
TextDecoration.underline: 'underline',
|
||||
TextDecoration.overline: 'overline',
|
||||
TextDecoration.lineThrough: 'lineThrough'
|
||||
};
|
||||
return decoration.map((d) => toCSS[d]).join(' ');
|
||||
}
|
||||
|
||||
static String _decorationStyleToCSSString(TextDecorationStyle decorationStyle) {
|
||||
assert(decorationStyle != null);
|
||||
const toCSS = const <TextDecorationStyle, String>{
|
||||
TextDecorationStyle.solid: 'solid',
|
||||
TextDecorationStyle.double: 'double',
|
||||
TextDecorationStyle.dotted: 'dotted',
|
||||
TextDecorationStyle.dashed: 'dashed',
|
||||
TextDecorationStyle.wavy: 'wavy'
|
||||
};
|
||||
return toCSS[decorationStyle];
|
||||
}
|
||||
|
||||
void applyToCSSStyle(CSSStyleDeclaration cssStyle) {
|
||||
if (color != null) {
|
||||
cssStyle['color'] = _colorToCSSString(color);
|
||||
}
|
||||
if (fontFamily != null) {
|
||||
cssStyle['font-family'] = _fontFamilyToCSSString(fontFamily);
|
||||
}
|
||||
if (fontSize != null) {
|
||||
cssStyle['font-size'] = "${fontSize}px";
|
||||
}
|
||||
if (fontWeight != null) {
|
||||
cssStyle['font-weight'] = const {
|
||||
FontWeight.w100: '100',
|
||||
FontWeight.w200: '200',
|
||||
FontWeight.w300: '300',
|
||||
FontWeight.w400: '400',
|
||||
FontWeight.w500: '500',
|
||||
FontWeight.w600: '600',
|
||||
FontWeight.w700: '700',
|
||||
FontWeight.w800: '800',
|
||||
FontWeight.w900: '900'
|
||||
}[fontWeight];
|
||||
}
|
||||
if (textAlign != null) {
|
||||
cssStyle['text-align'] = const {
|
||||
TextAlign.left: 'left',
|
||||
TextAlign.right: 'right',
|
||||
TextAlign.center: 'center',
|
||||
}[textAlign];
|
||||
}
|
||||
if (decoration != null) {
|
||||
cssStyle['text-decoration'] = _decorationToCSSString(decoration);
|
||||
if (decorationColor != null)
|
||||
cssStyle['text-decoration-color'] = _colorToCSSString(decorationColor);
|
||||
if (decorationStyle != null)
|
||||
cssStyle['text-decoration-style'] = _decorationStyleToCSSString(decorationStyle);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator ==(other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
@ -132,43 +192,6 @@ class TextStyle {
|
||||
return value;
|
||||
}
|
||||
|
||||
void applyToCSSStyle(CSSStyleDeclaration cssStyle) {
|
||||
if (color != null) {
|
||||
cssStyle['color'] = 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})';
|
||||
}
|
||||
// TODO(hansmuller): escape the fontFamily string.
|
||||
if (fontFamily != null) {
|
||||
cssStyle['font-family'] = fontFamily;
|
||||
}
|
||||
if (fontSize != null) {
|
||||
cssStyle['font-size'] = "${fontSize}px";
|
||||
}
|
||||
if (fontWeight != null) {
|
||||
cssStyle['font-weight'] = const {
|
||||
FontWeight.w100: '100',
|
||||
FontWeight.w200: '200',
|
||||
FontWeight.w300: '300',
|
||||
FontWeight.w400: '400',
|
||||
FontWeight.w500: '500',
|
||||
FontWeight.w600: '600',
|
||||
FontWeight.w700: '700',
|
||||
FontWeight.w800: '800',
|
||||
FontWeight.w900: '900'
|
||||
}[fontWeight];
|
||||
}
|
||||
if (textAlign != null) {
|
||||
cssStyle['text-align'] = const {
|
||||
TextAlign.left: 'left',
|
||||
TextAlign.right: 'right',
|
||||
TextAlign.center: 'center',
|
||||
}[textAlign];
|
||||
}
|
||||
if (decoration != null) {
|
||||
cssStyle['text-decoration'] = _decorationToString();
|
||||
}
|
||||
// TODO(hansmuller): add support for decoration color and style.
|
||||
}
|
||||
|
||||
String toString([String prefix = '']) {
|
||||
List<String> result = [];
|
||||
if (color != null)
|
||||
@ -183,7 +206,7 @@ class TextStyle {
|
||||
if (textAlign != null)
|
||||
result.add('${prefix}textAlign: $textAlign');
|
||||
if (decoration != null)
|
||||
result.add('${prefix}decoration: ${_decorationToString()}');
|
||||
result.add('${prefix}decoration: $decoration');
|
||||
if (decorationColor != null)
|
||||
result.add('${prefix}decorationColor: $decorationColor');
|
||||
if (decorationStyle != null)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user