提交 a12d43dc 编写于 作者: H Hixie

Turn on wavy underlines. The waves aren't very pretty yet (they are too short...

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.
上级 8f20fb6e
...@@ -796,10 +796,25 @@ static void adjustStepToDecorationLength(float& step, float& controlPointDistanc ...@@ -796,10 +796,25 @@ static void adjustStepToDecorationLength(float& step, float& controlPointDistanc
controlPointDistance += adjustment; 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. * Draw one cubic Bezier curve and repeat the same pattern along the
* The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve * the decoration's axis. The start point (p1), controlPoint1,
* form a diamond shape: * controlPoint2 and end point (p2) of the Bezier curve form a diamond
* shape, as follows (the four points marked +):
* *
* step * step
* |-----------| * |-----------|
...@@ -822,84 +837,62 @@ static void adjustStepToDecorationLength(float& step, float& controlPointDistanc ...@@ -822,84 +837,62 @@ static void adjustStepToDecorationLength(float& step, float& controlPointDistanc
* *
* |-----------| * |-----------|
* step * 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()); context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
Path path; Path path;
path.moveTo(p1); path.moveTo(p1);
// Distance between decoration's axis and Bezier curve's control points. float controlPointDistance = 2 * strokeThickness;
// The height of the curve is based on this distance. Use a minimum of 6 pixels distance since float step = controlPointDistance;
// 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);
// 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);
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); float yAxis = Curve::y(p1);
FloatPoint controlPoint1(xAxis + controlPointDistance, 0); float x1;
FloatPoint controlPoint2(xAxis - controlPointDistance, 0); float x2;
for (float y = y1; y + 2 * step <= y2;) { if (Curve::x(p1) < Curve::x(p2)) {
controlPoint1.setY(y + step); x1 = Curve::x(p1);
controlPoint2.setY(y + step); x2 = Curve::x(p2);
y += 2 * step;
path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y));
}
} else { } else {
ASSERT(p1.y() == p2.y()); x1 = Curve::x(p2);
x2 = Curve::x(p1);
float yAxis = p1.y(); }
float x1;
float x2;
if (p1.x() < p2.x()) {
x1 = p1.x();
x2 = p2.x();
} else {
x1 = p2.x();
x2 = p1.x();
}
adjustStepToDecorationLength(step, controlPointDistance, x2 - x1); adjustStepToDecorationLength(step, controlPointDistance, x2 - x1);
FloatPoint controlPoint1(0, yAxis + controlPointDistance);
FloatPoint controlPoint2(0, yAxis - controlPointDistance);
for (float x = x1; x + 2 * step <= x2;) { FloatPoint controlPoint1 = Curve::p(0, yAxis + controlPointDistance);
controlPoint1.setX(x + step); FloatPoint controlPoint2 = Curve::p(0, yAxis - controlPointDistance);
controlPoint2.setX(x + step);
x += 2 * step; for (float x = x1; x + 2 * step <= x2;) {
path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis)); 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->setShouldAntialias(true);
context->strokePath(path); 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) static bool shouldSetDecorationAntialias(TextDecorationStyle decorationStyle)
{ {
return decorationStyle == TextDecorationStyleDotted || decorationStyle == TextDecorationStyleDashed; return decorationStyle == TextDecorationStyleDotted || decorationStyle == TextDecorationStyleDashed;
...@@ -962,7 +955,7 @@ void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& ...@@ -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. // 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. // 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); int fontHeightInt = (int)(styleToUse->fontMetrics().floatHeight() + 0.5);
if ((textDecorationThickness == 0.f) || (textDecorationThickness >= (fontHeightInt >> 1))) if ((textDecorationThickness == 0.f) || (textDecorationThickness >= (fontHeightInt >> 1)))
textDecorationThickness = std::max(1.f, styleToUse->computedFontSize() / 10.f); textDecorationThickness = std::max(1.f, styleToUse->computedFontSize() / 10.f);
......
...@@ -36,7 +36,7 @@ CSSAttributeCaseSensitivity status=experimental ...@@ -36,7 +36,7 @@ CSSAttributeCaseSensitivity status=experimental
CSSTouchActionDelay status=test CSSTouchActionDelay status=test
CSSViewport status=experimental CSSViewport status=experimental
CSS3Text status=experimental CSS3Text status=experimental
CSS3TextDecorations status=experimental CSS3TextDecorations status=stable
CustomSchemeHandler depends_on=NavigatorContentUtils, status=experimental CustomSchemeHandler depends_on=NavigatorContentUtils, status=experimental
Database status=stable Database status=stable
DeviceLight status=experimental DeviceLight status=experimental
......
...@@ -44,7 +44,11 @@ HAL: This mission is too important for me to allow you to jeopardize it.'''; ...@@ -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 daveStyle = new TextStyle(color: Indigo[400]);
final TextStyle halStyle = new TextStyle(color: Red[400], fontFamily: "monospace"); final TextStyle halStyle = new TextStyle(color: Red[400], fontFamily: "monospace");
final TextStyle boldStyle = const TextStyle(fontWeight: bold); 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) { Component toStyledText(String name, String text) {
TextStyle lineStyle = (name == "Dave") ? daveStyle : halStyle; TextStyle lineStyle = (name == "Dave") ? daveStyle : halStyle;
......
...@@ -39,9 +39,9 @@ enum TextDecoration { ...@@ -39,9 +39,9 @@ enum TextDecoration {
lineThrough lineThrough
} }
const underline = const <TextDecoration> [TextDecoration.underline]; const underline = const <TextDecoration>[TextDecoration.underline];
const overline = const <TextDecoration> [TextDecoration.overline]; const overline = const <TextDecoration>[TextDecoration.overline];
const lineThrough = const <TextDecoration> [TextDecoration.lineThrough]; const lineThrough = const <TextDecoration>[TextDecoration.lineThrough];
enum TextDecorationStyle { enum TextDecorationStyle {
solid, solid,
...@@ -58,7 +58,7 @@ class TextStyle { ...@@ -58,7 +58,7 @@ class TextStyle {
this.fontSize, this.fontSize,
this.fontWeight, this.fontWeight,
this.textAlign, this.textAlign,
List<TextDecoration> this.decoration, this.decoration,
this.decorationColor, this.decorationColor,
this.decorationStyle this.decorationStyle
}); });
...@@ -68,21 +68,10 @@ class TextStyle { ...@@ -68,21 +68,10 @@ class TextStyle {
final double fontSize; // in pixels final double fontSize; // in pixels
final FontWeight fontWeight; final FontWeight fontWeight;
final TextAlign textAlign; 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 Color decorationColor;
final TextDecorationStyle decorationStyle; 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({ TextStyle copyWith({
Color color, Color color,
double fontSize, double fontSize,
...@@ -104,41 +93,44 @@ class TextStyle { ...@@ -104,41 +93,44 @@ class TextStyle {
); );
} }
bool operator ==(other) { static String _colorToCSSString(Color color) {
if (identical(this, other)) return 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})';
return true;
return other is TextStyle &&
color == other.color &&
fontFamily == other.fontFamily &&
fontSize == other.fontSize &&
fontWeight == other.fontWeight &&
textAlign == other.textAlign &&
decoration == other.decoration &&
decorationColor == other.decorationColor &&
decorationStyle == other.decorationStyle;
} }
int get hashCode { static String _fontFamilyToCSSString(String fontFamily) {
// Use Quiver: https://github.com/domokit/mojo/issues/236 // TODO(hansmuller): escape the fontFamily string.
int value = 373; return fontFamily;
value = 37 * value + color.hashCode; }
value = 37 * value + fontFamily.hashCode;
value = 37 * value + fontSize.hashCode; static String _decorationToCSSString(List<TextDecoration> decoration) {
value = 37 * value + fontWeight.hashCode; assert(decoration != null);
value = 37 * value + textAlign.hashCode; const toCSS = const <TextDecoration, String>{
value = 37 * value + decoration.hashCode; TextDecoration.none: 'none',
value = 37 * value + decorationColor.hashCode; TextDecoration.underline: 'underline',
value = 37 * value + decorationStyle.hashCode; TextDecoration.overline: 'overline',
return value; 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) { void applyToCSSStyle(CSSStyleDeclaration cssStyle) {
if (color != null) { if (color != null) {
cssStyle['color'] = 'rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha / 255.0})'; cssStyle['color'] = _colorToCSSString(color);
} }
// TODO(hansmuller): escape the fontFamily string.
if (fontFamily != null) { if (fontFamily != null) {
cssStyle['font-family'] = fontFamily; cssStyle['font-family'] = _fontFamilyToCSSString(fontFamily);
} }
if (fontSize != null) { if (fontSize != null) {
cssStyle['font-size'] = "${fontSize}px"; cssStyle['font-size'] = "${fontSize}px";
...@@ -164,9 +156,40 @@ class TextStyle { ...@@ -164,9 +156,40 @@ class TextStyle {
}[textAlign]; }[textAlign];
} }
if (decoration != null) { if (decoration != null) {
cssStyle['text-decoration'] = _decorationToString(); cssStyle['text-decoration'] = _decorationToCSSString(decoration);
if (decorationColor != null)
cssStyle['text-decoration-color'] = _colorToCSSString(decorationColor);
if (decorationStyle != null)
cssStyle['text-decoration-style'] = _decorationStyleToCSSString(decorationStyle);
} }
// TODO(hansmuller): add support for decoration color and style. }
bool operator ==(other) {
if (identical(this, other))
return true;
return other is TextStyle &&
color == other.color &&
fontFamily == other.fontFamily &&
fontSize == other.fontSize &&
fontWeight == other.fontWeight &&
textAlign == other.textAlign &&
decoration == other.decoration &&
decorationColor == other.decorationColor &&
decorationStyle == other.decorationStyle;
}
int get hashCode {
// Use Quiver: https://github.com/domokit/mojo/issues/236
int value = 373;
value = 37 * value + color.hashCode;
value = 37 * value + fontFamily.hashCode;
value = 37 * value + fontSize.hashCode;
value = 37 * value + fontWeight.hashCode;
value = 37 * value + textAlign.hashCode;
value = 37 * value + decoration.hashCode;
value = 37 * value + decorationColor.hashCode;
value = 37 * value + decorationStyle.hashCode;
return value;
} }
String toString([String prefix = '']) { String toString([String prefix = '']) {
...@@ -183,7 +206,7 @@ class TextStyle { ...@@ -183,7 +206,7 @@ class TextStyle {
if (textAlign != null) if (textAlign != null)
result.add('${prefix}textAlign: $textAlign'); result.add('${prefix}textAlign: $textAlign');
if (decoration != null) if (decoration != null)
result.add('${prefix}decoration: ${_decorationToString()}'); result.add('${prefix}decoration: $decoration');
if (decorationColor != null) if (decorationColor != null)
result.add('${prefix}decorationColor: $decorationColor'); result.add('${prefix}decorationColor: $decorationColor');
if (decorationStyle != null) if (decorationStyle != null)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册