Changeset 158467 in webkit


Ignore:
Timestamp:
Nov 1, 2013 4:46:54 PM (10 years ago)
Author:
commit-queue@webkit.org
Message:

Initial implementation of text-decoration-skip ink
https://bugs.webkit.org/show_bug.cgi?id=121806

Patch by Myles C. Maxfield <mmaxfield@apple.com> on 2013-11-01
Reviewed by Darin Adler.

Source/WebCore:

text-decoration-skip: ink is implemented by the following steps:

  1. Before drawing any decorations, figure out the bounding box for the decorations that will be drawn
  2. Create an ImageBuffer with these dimensions
  3. Draw text into this ImageBuffer with a thicker stroke
  4. Apply the ImageBuffer as a mask to the context
  5. Draw decorations like normal
  6. Clean up

Test: fast/css3-text/css3-text-decoration/text-decoration-skip/text-decoration-skip-ink.html

  • platform/graphics/cg/GraphicsContextCG.cpp:

(WebCore::computeLineBoundsAndAntialiasingModeForText): Don't call GraphicsContext::roundToDevicePixels
when painting is disabled

  • rendering/InlineTextBox.cpp:

(WebCore::InlineTextBox::paint): Pass the TextPainter to paintDecoration
(WebCore::computeUnderlineOffset): Small cleanup
(WebCore::getWavyStrokeControlPointDistance): Pulling out of strokeWavyTextDecoration()
(WebCore::getWavyStrokeStep): Ditto
(WebCore::strokeWavyTextDecoration): Use the previous 2 functions
(WebCore::getSingleDecorationBoundingBox): Pulling out repeated code into a function
(WebCore::getDecorationBoundingBox): Compute the bounding box for an underline which
hasn't been drawn yet
(WebCore::InlineTextBox::paintDecoration): Construct a mask and apply it to the GraphicsContext

  • rendering/InlineTextBox.h: paintDecoration needs the TextPainter
  • rendering/style/RenderStyle.cpp:

(WebCore::RenderStyle::changeRequiresRepaintIfTextOrBorderOrOutline): Redraw the underline when
text-decoration-skip changes

LayoutTests:

Adding a test which draws a very large "p" with text-decoration-skip:ink. The
viewport is restricted to be just hugging the place where an underline would
intersect the descender of the "p". This should look the same as if there was
no underline at all (because of the skipping underline).

  • fast/css3-text/css3-text-decoration/text-decoration-skip/text-decoration-skip-ink-expected.html: Added.
  • fast/css3-text/css3-text-decoration/text-decoration-skip/text-decoration-skip-ink.html: Added.
Location:
trunk
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r158466 r158467  
     12013-11-01  Myles C. Maxfield  <mmaxfield@apple.com>
     2
     3        Initial implementation of text-decoration-skip ink
     4        https://bugs.webkit.org/show_bug.cgi?id=121806
     5
     6        Reviewed by Darin Adler.
     7
     8        Adding a test which draws a very large "p" with text-decoration-skip:ink. The
     9        viewport is restricted to be just hugging the place where an underline would
     10        intersect the descender of the "p". This should look the same as if there was
     11        no underline at all (because of the skipping underline).
     12
     13        * fast/css3-text/css3-text-decoration/text-decoration-skip/text-decoration-skip-ink-expected.html: Added.
     14        * fast/css3-text/css3-text-decoration/text-decoration-skip/text-decoration-skip-ink.html: Added.
     15
    1162013-11-01  Hans Muller  <hmuller@adobe.com>
    217
  • trunk/Source/WebCore/ChangeLog

    r158465 r158467  
     12013-11-01  Myles C. Maxfield  <mmaxfield@apple.com>
     2
     3        Initial implementation of text-decoration-skip ink
     4        https://bugs.webkit.org/show_bug.cgi?id=121806
     5
     6        Reviewed by Darin Adler.
     7
     8        text-decoration-skip: ink is implemented by the following steps:
     9        1. Before drawing any decorations, figure out the bounding box for the decorations that will be drawn
     10        2. Create an ImageBuffer with these dimensions
     11        3. Draw text into this ImageBuffer with a thicker stroke
     12        4. Apply the ImageBuffer as a mask to the context
     13        5. Draw decorations like normal
     14        6. Clean up
     15
     16        Test: fast/css3-text/css3-text-decoration/text-decoration-skip/text-decoration-skip-ink.html
     17
     18        * platform/graphics/cg/GraphicsContextCG.cpp:
     19        (WebCore::computeLineBoundsAndAntialiasingModeForText): Don't call GraphicsContext::roundToDevicePixels
     20        when painting is disabled
     21        * rendering/InlineTextBox.cpp:
     22        (WebCore::InlineTextBox::paint): Pass the TextPainter to paintDecoration
     23        (WebCore::computeUnderlineOffset): Small cleanup
     24        (WebCore::getWavyStrokeControlPointDistance): Pulling out of strokeWavyTextDecoration()
     25        (WebCore::getWavyStrokeStep): Ditto
     26        (WebCore::strokeWavyTextDecoration): Use the previous 2 functions
     27        (WebCore::getSingleDecorationBoundingBox): Pulling out repeated code into a function
     28        (WebCore::getDecorationBoundingBox): Compute the bounding box for an underline which
     29        hasn't been drawn yet
     30        (WebCore::InlineTextBox::paintDecoration): Construct a mask and apply it to the GraphicsContext
     31        * rendering/InlineTextBox.h: paintDecoration needs the TextPainter
     32        * rendering/style/RenderStyle.cpp:
     33        (WebCore::RenderStyle::changeRequiresRepaintIfTextOrBorderOrOutline): Redraw the underline when
     34        text-decoration-skip changes
     35
    1362013-11-01  Andreas Kling  <akling@apple.com>
    237
  • trunk/Source/WebCore/platform/graphics/cg/GraphicsContextCG.cpp

    r158392 r158467  
    12471247    FloatRect initialBounds(point, FloatSize(width, max(context.strokeThickness(), 0.5f)));
    12481248
    1249     if (printing || !context.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale).preservesAxisAlignment())
     1249    if (printing || context.paintingDisabled() || !context.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale).preservesAxisAlignment())
    12501250        return initialBounds;
    12511251
  • trunk/Source/WebCore/rendering/InlineTextBox.cpp

    r158461 r158467  
    3434#include "GraphicsContext.h"
    3535#include "HitTestResult.h"
     36#include "ImageBuffer.h"
    3637#include "Page.h"
    3738#include "PaintInfo.h"
     
    581582        if (combinedText)
    582583            context->concatCTM(rotation(boxRect, Clockwise));
    583         paintDecoration(context, boxOrigin, textDecorations, lineStyle.textDecorationStyle(), textShadow);
     584        paintDecoration(*context, boxOrigin, textDecorations, lineStyle.textDecorationStyle(), textShadow, textPainter);
    584585        if (combinedText)
    585586            context->concatCTM(rotation(boxRect, Counterclockwise));
     
    779780        // Position underline relative to the under edge of the lowest element's content box.
    780781        const float offset = inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop();
    781         if (offset > 0)
    782             return inlineTextBox->logicalHeight() + gap + offset;
    783         return inlineTextBox->logicalHeight() + gap;
     782        return inlineTextBox->logicalHeight() + gap + std::max<float>(offset, 0);
    784783    }
    785784    }
     
    805804    step += adjustment;
    806805    controlPointDistance += adjustment;
     806}
     807
     808static void getWavyStrokeParameters(float strokeThickness, float& controlPointDistance, float& step)
     809{
     810    // Distance between decoration's axis and Bezier curve's control points.
     811    // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
     812    // the actual curve passes approximately at half of that distance, that is 3 pixels.
     813    // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
     814    // as strockThickness increases to make the curve looks better.
     815    controlPointDistance = 3 * std::max<float>(2, strokeThickness);
     816
     817    // Increment used to form the diamond shape between start point (p1), control
     818    // points and end point (p2) along the axis of the decoration. Makes the
     819    // curve wider as strockThickness increases to make the curve looks better.
     820    step = 2 * std::max<float>(2, strokeThickness);
    807821}
    808822
     
    834848 *                 step
    835849 */
    836 static void strokeWavyTextDecoration(GraphicsContext* context, FloatPoint& p1, FloatPoint& p2, float strokeThickness)
    837 {
    838     context->adjustLineToPixelBoundaries(p1, p2, strokeThickness, context->strokeStyle());
     850static void strokeWavyTextDecoration(GraphicsContext& context, FloatPoint& p1, FloatPoint& p2, float strokeThickness)
     851{
     852    context.adjustLineToPixelBoundaries(p1, p2, strokeThickness, context.strokeStyle());
    839853
    840854    Path path;
    841855    path.moveTo(p1);
    842856
    843     // Distance between decoration's axis and Bezier curve's control points.
    844     // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since
    845     // the actual curve passes approximately at half of that distance, that is 3 pixels.
    846     // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height
    847     // as strockThickness increases to make the curve looks better.
    848     float controlPointDistance = 3 * max<float>(2, strokeThickness);
    849 
    850     // Increment used to form the diamond shape between start point (p1), control
    851     // points and end point (p2) along the axis of the decoration. Makes the
    852     // curve wider as strockThickness increases to make the curve looks better.
    853     float step = 2 * max<float>(2, strokeThickness);
     857    float controlPointDistance;
     858    float step;
     859    getWavyStrokeParameters(strokeThickness, controlPointDistance, step);
    854860
    855861    bool isVerticalLine = (p1.x() == p2.x());
     
    907913    }
    908914
    909     context->setShouldAntialias(true);
    910     context->strokePath(path);
     915    context.setShouldAntialias(true);
     916    context.strokePath(path);
     917}
     918
     919// Because finding the bounding box of an underline is structurally similar to finding
     920// the bounding box of a strikethrough, we can pull out the computation and parameterize
     921// by the location of the decoration (yOffset, underlineOffset, and doubleOffset).
     922static FloatRect boundingBoxForSingleDecoration(GraphicsContext& context, float textDecorationThickness,
     923    float width, FloatPoint localOrigin, TextDecorationStyle decorationStyle,
     924    bool isPrinting, float yOffset, float underlineOffset, float doubleOffset)
     925{
     926    FloatRect boundingBox;
     927
     928    switch (decorationStyle) {
     929    case TextDecorationStyleWavy: {
     930        FloatPoint start(localOrigin.x(), localOrigin.y() + yOffset);
     931        FloatPoint end = start + FloatSize(width, 0);
     932        context.adjustLineToPixelBoundaries(start, end, textDecorationThickness, context.strokeStyle());
     933
     934        float controlPointDistance;
     935        float step;
     936        getWavyStrokeParameters(textDecorationThickness, controlPointDistance, step);
     937
     938        adjustStepToDecorationLength(step, controlPointDistance, width);
     939
     940        controlPointDistance += textDecorationThickness;
     941        FloatPoint boundingBoxOrigin = start - FloatSize(0, controlPointDistance);
     942        FloatSize boundingBoxSize = FloatSize(width, 2 * controlPointDistance);
     943        boundingBox = FloatRect(boundingBoxOrigin, boundingBoxSize);
     944        break;
     945    }
     946    default:
     947        boundingBox = context.computeLineBoundsForText(localOrigin + FloatSize(0, underlineOffset), width, isPrinting);
     948        if (decorationStyle == TextDecorationStyleDouble)
     949            boundingBox.unite(context.computeLineBoundsForText(localOrigin + FloatSize(0, doubleOffset), width, isPrinting));
     950    }
     951    return boundingBox;
     952}
     953
     954static FloatRect boundingBoxForAllActiveDecorations(InlineTextBox& inlineTextBox, GraphicsContext& context, TextDecoration decoration, float textDecorationThickness, float width, float doubleOffset, TextDecorationStyle decorationStyle, const FloatPoint localOrigin, const RenderStyle& lineStyle, bool isPrinting, int baseline)
     955{
     956    FloatRect boundingBox;
     957    if (decoration & TextDecorationUnderline) {
     958        int underlineOffset = computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), &inlineTextBox, textDecorationThickness);
     959       
     960        boundingBox.unite(boundingBoxForSingleDecoration(context, textDecorationThickness, width, localOrigin, decorationStyle, isPrinting, underlineOffset + doubleOffset, underlineOffset, baseline + 1));
     961    }
     962    if (decoration & TextDecorationOverline)
     963        boundingBox.unite(boundingBoxForSingleDecoration(context, textDecorationThickness, width, localOrigin, decorationStyle, isPrinting, -doubleOffset, 0, -doubleOffset));
     964    if (decoration & TextDecorationLineThrough)
     965        boundingBox.unite(boundingBoxForSingleDecoration(context, textDecorationThickness, width, localOrigin, decorationStyle, isPrinting, 2 * baseline / 3, 2 * baseline / 3, doubleOffset + 2 * baseline / 3));
     966    return boundingBox;
    911967}
    912968#endif // CSS3_TEXT_DECORATION
    913969
    914 void InlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& boxOrigin, TextDecoration deco, TextDecorationStyle decorationStyle, const ShadowData* shadow)
    915 {
     970void InlineTextBox::paintDecoration(GraphicsContext& context, const FloatPoint& boxOrigin, TextDecoration decoration, TextDecorationStyle decorationStyle, const ShadowData* shadow, TextPainter& textPainter)
     971{
     972#if !ENABLE(CSS3_TEXT_DECORATION)
     973    UNUSED_PARAM(textPainter);
     974#endif
    916975    // FIXME: We should improve this rule and not always just assume 1.
    917976    const float textDecorationThickness = 1.f;
     
    931990    // Get the text decoration colors.
    932991    Color underline, overline, linethrough;
    933     renderer().getTextDecorationColors(deco, underline, overline, linethrough, true);
     992    renderer().getTextDecorationColors(decoration, underline, overline, linethrough, true);
    934993    if (isFirstLine())
    935         renderer().getTextDecorationColors(deco, underline, overline, linethrough, true, true);
     994        renderer().getTextDecorationColors(decoration, underline, overline, linethrough, true, true);
    936995   
    937996    // Use a special function for underlines to get the positioning exactly right.
    938997    bool isPrinting = renderer().document().printing();
    939     context->setStrokeThickness(textDecorationThickness);
    940 
    941     bool linesAreOpaque = !isPrinting && (!(deco & TextDecorationUnderline) || underline.alpha() == 255) && (!(deco & TextDecorationOverline) || overline.alpha() == 255) && (!(deco & TextDecorationLineThrough) || linethrough.alpha() == 255);
     998    context.setStrokeThickness(textDecorationThickness);
     999
     1000    bool linesAreOpaque = !isPrinting && (!(decoration & TextDecorationUnderline) || underline.alpha() == 255) && (!(decoration & TextDecorationOverline) || overline.alpha() == 255) && (!(decoration & TextDecorationLineThrough) || linethrough.alpha() == 255);
    9421001
    9431002    const RenderStyle& lineStyle = this->lineStyle();
     
    9581017            extraOffset = max(extraOffset, max(0, shadowY) + shadowExtent);
    9591018        }
    960         context->save();
    961         context->clip(clipRect);
     1019        context.save();
     1020        context.clip(clipRect);
    9621021        extraOffset += baseline + 2;
    9631022        localOrigin.move(0, extraOffset);
     
    9771036            int shadowX = isHorizontal() ? shadow->x() : shadow->y();
    9781037            int shadowY = isHorizontal() ? shadow->y() : -shadow->x();
    979             context->setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->radius(), shadow->color(), colorSpace);
     1038            context.setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->radius(), shadow->color(), colorSpace);
    9801039            setShadow = true;
    9811040            shadow = shadow->next();
     
    9841043#if ENABLE(CSS3_TEXT_DECORATION)
    9851044        // Offset between lines - always non-zero, so lines never cross each other.
    986         float doubleOffset = textDecorationThickness + 1.f;
     1045        float doubleOffset = textDecorationThickness + 1;
     1046       
     1047        bool clipDecorationToMask = lineStyle.textDecorationSkip() == TextDecorationSkipInk;
     1048       
     1049        GraphicsContextStateSaver stateSaver(context, false);
     1050       
     1051        if (clipDecorationToMask) {
     1052            const float skipInkGapWidth = 1;
     1053
     1054            stateSaver.save();
     1055
     1056            FloatRect underlineRect = boundingBoxForAllActiveDecorations(*this, context, decoration, textDecorationThickness, width, doubleOffset, decorationStyle, localOrigin, lineStyle, isPrinting, baseline);
     1057            IntRect enclosingDeviceRect = enclosingIntRect(underlineRect);
     1058            OwnPtr<ImageBuffer> imageBuffer = context.createCompatibleBuffer(enclosingDeviceRect.size());
     1059
     1060            if (imageBuffer.get()) {
     1061                GraphicsContext& maskContext = *imageBuffer->context();
     1062                maskContext.setFillColor(Color::black, ColorSpaceDeviceRGB);
     1063                maskContext.setLineJoin(RoundJoin);
     1064                maskContext.translate(FloatPoint() - enclosingDeviceRect.location());
     1065
     1066                maskContext.fillRect(enclosingDeviceRect);
     1067                maskContext.setCompositeOperation(CompositeClear);
     1068   
     1069                textPainter.paintTextInContext(maskContext, skipInkGapWidth);
     1070
     1071                context.clipToImageBuffer(imageBuffer.get(), enclosingDeviceRect);
     1072            }
     1073        }
    9871074#endif // CSS3_TEXT_DECORATION
    988         context->setStrokeStyle(textDecorationStyleToStrokeStyle(decorationStyle));
    989         if (deco & TextDecorationUnderline) {
    990             context->setStrokeColor(underline, colorSpace);
     1075        context.setStrokeStyle(textDecorationStyleToStrokeStyle(decorationStyle));
     1076        if (decoration & TextDecorationUnderline) {
     1077            context.setStrokeColor(underline, colorSpace);
    9911078#if ENABLE(CSS3_TEXT_DECORATION)
    9921079            TextUnderlinePosition underlinePosition = lineStyle.textUnderlinePosition();
     
    10011088            }
    10021089            default:
    1003                 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset), width, isPrinting);
     1090                context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset), width, isPrinting);
    10041091
    10051092                if (decorationStyle == TextDecorationStyleDouble)
    1006                     context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset), width, isPrinting);
     1093                    context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset), width, isPrinting);
    10071094            }
    10081095#else
    10091096            // Leave one pixel of white between the baseline and the underline.
    1010             context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + baseline + 1), width, isPrinting);
     1097            context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + baseline + 1), width, isPrinting);
    10111098#endif // CSS3_TEXT_DECORATION
    10121099        }
    1013         if (deco & TextDecorationOverline) {
    1014             context->setStrokeColor(overline, colorSpace);
     1100        if (decoration & TextDecorationOverline) {
     1101            context.setStrokeColor(overline, colorSpace);
    10151102#if ENABLE(CSS3_TEXT_DECORATION)
    10161103            switch (decorationStyle) {
     
    10231110            default:
    10241111#endif // CSS3_TEXT_DECORATION
    1025                 context->drawLineForText(localOrigin, width, isPrinting);
     1112                context.drawLineForText(localOrigin, width, isPrinting);
    10261113#if ENABLE(CSS3_TEXT_DECORATION)
    10271114                if (decorationStyle == TextDecorationStyleDouble)
    1028                     context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() - doubleOffset), width, isPrinting);
     1115                    context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() - doubleOffset), width, isPrinting);
    10291116            }
    10301117#endif // CSS3_TEXT_DECORATION
    10311118        }
    1032         if (deco & TextDecorationLineThrough) {
    1033             context->setStrokeColor(linethrough, colorSpace);
     1119#if ENABLE(CSS3_TEXT_DECORATION)
     1120        if (clipDecorationToMask)
     1121            stateSaver.restore();
     1122#endif
     1123        if (decoration & TextDecorationLineThrough) {
     1124            context.setStrokeColor(linethrough, colorSpace);
    10341125#if ENABLE(CSS3_TEXT_DECORATION)
    10351126            switch (decorationStyle) {
     
    10421133            default:
    10431134#endif // CSS3_TEXT_DECORATION
    1044                 context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting);
     1135                context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting);
    10451136#if ENABLE(CSS3_TEXT_DECORATION)
    10461137                if (decorationStyle == TextDecorationStyleDouble)
    1047                     context->drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + doubleOffset + 2 * baseline / 3), width, isPrinting);
     1138                    context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + doubleOffset + 2 * baseline / 3), width, isPrinting);
    10481139            }
    10491140#endif // CSS3_TEXT_DECORATION
     
    10521143
    10531144    if (setClip)
    1054         context->restore();
     1145        context.restore();
    10551146    else if (setShadow)
    1056         context->clearShadow();
     1147        context.clearShadow();
    10571148}
    10581149
  • trunk/Source/WebCore/rendering/InlineTextBox.h

    r158343 r158467  
    3333struct CompositionUnderline;
    3434class DocumentMarker;
     35class TextPainter;
    3536
    3637const unsigned short cNoTruncation = USHRT_MAX;
     
    170171
    171172private:
    172     void paintDecoration(GraphicsContext*, const FloatPoint& boxOrigin, TextDecoration, TextDecorationStyle, const ShadowData*);
     173    void paintDecoration(GraphicsContext&, const FloatPoint& boxOrigin, TextDecoration, TextDecorationStyle, const ShadowData*, TextPainter&);
    173174    void paintSelection(GraphicsContext*, const FloatPoint& boxOrigin, const RenderStyle&, const Font&, Color textColor);
    174175    void paintDocumentMarker(GraphicsContext*, const FloatPoint& boxOrigin, DocumentMarker*, const RenderStyle&, const Font&, bool grammar);
  • trunk/Source/WebCore/rendering/style/RenderStyle.cpp

    r158461 r158467  
    792792        || rareNonInheritedData->m_textDecorationStyle != other->rareNonInheritedData->m_textDecorationStyle
    793793        || rareNonInheritedData->m_textDecorationColor != other->rareNonInheritedData->m_textDecorationColor
     794        || rareInheritedData->m_textDecorationSkip != other->rareInheritedData->m_textDecorationSkip
    794795#endif // CSS3_TEXT_DECORATION
    795796        || rareInheritedData->textFillColor != other->rareInheritedData->textFillColor
Note: See TracChangeset for help on using the changeset viewer.