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:
Hixie 2015-06-19 16:11:18 -07:00
parent 8f20fb6e9a
commit a12d43dc58
4 changed files with 139 additions and 119 deletions

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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)