From a12d43dc587911318752be38899ee2214549b766 Mon Sep 17 00:00:00 2001 From: Hixie Date: Fri, 19 Jun 2015 16:11:18 -0700 Subject: [PATCH] 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. --- engine/core/rendering/InlineTextBox.cpp | 119 +++++++++----------- engine/platform/RuntimeEnabledFeatures.in | 2 +- examples/widgets/styled_text.dart | 6 +- sdk/lib/painting/text_style.dart | 131 +++++++++++++--------- 4 files changed, 139 insertions(+), 119 deletions(-) diff --git a/engine/core/rendering/InlineTextBox.cpp b/engine/core/rendering/InlineTextBox.cpp index 3132df88be7..f8bbbe78eff 100644 --- a/engine/core/rendering/InlineTextBox.cpp +++ b/engine/core/rendering/InlineTextBox.cpp @@ -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 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(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(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(context, p1, p2, strokeThickness); + else // vertical line + strokeWavyTextDecorationInternal(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); diff --git a/engine/platform/RuntimeEnabledFeatures.in b/engine/platform/RuntimeEnabledFeatures.in index e891706ae19..4edc8107fe9 100644 --- a/engine/platform/RuntimeEnabledFeatures.in +++ b/engine/platform/RuntimeEnabledFeatures.in @@ -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 diff --git a/examples/widgets/styled_text.dart b/examples/widgets/styled_text.dart index f9c98045498..4ce79bd63a6 100644 --- a/examples/widgets/styled_text.dart +++ b/examples/widgets/styled_text.dart @@ -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; diff --git a/sdk/lib/painting/text_style.dart b/sdk/lib/painting/text_style.dart index c9fda03bff7..19d3c547e04 100644 --- a/sdk/lib/painting/text_style.dart +++ b/sdk/lib/painting/text_style.dart @@ -39,9 +39,9 @@ enum TextDecoration { lineThrough } -const underline = const [TextDecoration.underline]; -const overline = const [TextDecoration.overline]; -const lineThrough = const [TextDecoration.lineThrough]; +const underline = const [TextDecoration.underline]; +const overline = const [TextDecoration.overline]; +const lineThrough = const [TextDecoration.lineThrough]; enum TextDecorationStyle { solid, @@ -58,7 +58,7 @@ class TextStyle { this.fontSize, this.fontWeight, this.textAlign, - List 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 decoration; + final List 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 decoration) { + 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(' '); + } + + static String _decorationStyleToCSSString(TextDecorationStyle decorationStyle) { + assert(decorationStyle != null); + const toCSS = const { + 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 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)