Implement text-shadow rendering (bug 10713). r+sr=roc. Relanding with fixes to make tests pass on Mac

This commit is contained in:
Michael Ventnor 2008-06-13 10:02:32 +12:00
Родитель e5412538bd
Коммит 129d4dc805
14 изменённых файлов: 587 добавлений и 50 удалений

Просмотреть файл

@ -25,6 +25,7 @@
* Takeshi Ichimaru <ayakawa.m@gmail.com> * Takeshi Ichimaru <ayakawa.m@gmail.com>
* Masayuki Nakano <masayuki@d-toybox.com> * Masayuki Nakano <masayuki@d-toybox.com>
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation
* Michael Ventnor <m.ventnor@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"), * either of the GNU General Public License Version 2 or later (the "GPL"),
@ -4707,3 +4708,155 @@ nsCSSRendering::GetTextDecorationRectInternal(const gfxPoint& aPt,
r.pos.y = baseline - NS_floor(offset + 0.5); r.pos.y = baseline - NS_floor(offset + 0.5);
return r; return r;
} }
// -----
// nsContextBoxBlur
// -----
void
nsContextBoxBlur::BoxBlurHorizontal(unsigned char* aInput,
unsigned char* aOutput,
PRUint32 aLeftLobe,
PRUint32 aRightLobe)
{
// Box blur involves looking at one pixel, and setting its value to the average of
// its neighbouring pixels. leftLobe is how many pixels to the left to include
// in the average, rightLobe is to the right.
// boxSize is how many pixels total will be averaged when looking at each pixel.
PRUint32 boxSize = aLeftLobe + aRightLobe + 1;
long stride = mImageSurface->Stride();
PRUint32 rows = mRect.Height();
for (PRUint32 y = 0; y < rows; y++) {
PRUint32 alphaSum = 0;
for (PRUint32 i = 0; i < boxSize; i++) {
PRInt32 pos = i - aLeftLobe;
pos = PR_MAX(pos, 0);
pos = PR_MIN(pos, stride - 1);
alphaSum += aInput[stride * y + pos];
}
for (PRInt32 x = 0; x < stride; x++) {
PRInt32 tmp = x - aLeftLobe;
PRInt32 last = PR_MAX(tmp, 0);
PRInt32 next = PR_MIN(tmp + boxSize, stride - 1);
aOutput[stride * y + x] = alphaSum/boxSize;
alphaSum += aInput[stride * y + next] -
aInput[stride * y + last];
}
}
}
void
nsContextBoxBlur::BoxBlurVertical(unsigned char* aInput,
unsigned char* aOutput,
PRUint32 aTopLobe,
PRUint32 aBottomLobe)
{
PRUint32 boxSize = aTopLobe + aBottomLobe + 1;
long stride = mImageSurface->Stride();
PRUint32 rows = mRect.Height();
for (PRInt32 x = 0; x < stride; x++) {
PRUint32 alphaSum = 0;
for (PRUint32 i = 0; i < boxSize; i++) {
PRInt32 pos = i - aTopLobe;
pos = PR_MAX(pos, 0);
pos = PR_MIN(pos, stride - 1);
alphaSum += aInput[stride * pos + x];
}
for (PRUint32 y = 0; y < rows; y++) {
PRInt32 tmp = y - aTopLobe;
PRInt32 last = PR_MAX(tmp, 0);
PRInt32 next = PR_MIN(tmp + boxSize, rows - 1);
aOutput[stride * y + x] = alphaSum/boxSize;
alphaSum += aInput[stride * next + x] -
aInput[stride * last + x];
}
}
}
gfxContext*
nsContextBoxBlur::Init(const gfxRect& aRect, nscoord aBlurRadius,
PRInt32 aAppUnitsPerDevPixel,
gfxContext* aDestinationCtx)
{
mBlurRadius = aBlurRadius / aAppUnitsPerDevPixel;
if (mBlurRadius <= 0) {
mContext = aDestinationCtx;
return mContext;
}
mDestinationCtx = aDestinationCtx;
// Convert from app units to device pixels
mRect = aRect;
mRect.Outset(aBlurRadius);
mRect.ScaleInverse(aAppUnitsPerDevPixel);
mRect.RoundOut();
// Make an alpha-only surface to draw on. We will play with the data after everything is drawn
// to create a blur effect.
mImageSurface = new gfxImageSurface(gfxIntSize(mRect.Width(), mRect.Height()),
gfxASurface::ImageFormatA8);
if (!mImageSurface)
return nsnull;
// Use a device offset so callers don't need to worry about translating coordinates,
// they can draw as if this was part of the destination context at the coordinates
// of mRect.
mImageSurface->SetDeviceOffset(-mRect.TopLeft());
mContext = new gfxContext(mImageSurface);
return mContext;
}
void
nsContextBoxBlur::DoPaint()
{
if (mBlurRadius <= 0)
return;
unsigned char* boxData = mImageSurface->Data();
// A blur radius of 1 achieves nothing (1/2 = 0 in int terms),
// but we still want a blur!
mBlurRadius = PR_MAX(mBlurRadius, 2);
nsTArray<unsigned char> tempAlphaDataBuf;
if (!tempAlphaDataBuf.SetLength(mImageSurface->GetDataSize()))
return; // OOM
// Here we do like what the SVG gaussian blur filter does in calculating
// the lobes.
if (mBlurRadius & 1) {
// blur radius is odd
BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2);
BoxBlurHorizontal(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2);
BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2);
BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2);
BoxBlurVertical(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2);
BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2);
} else {
// blur radius is even
BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2 - 1);
BoxBlurHorizontal(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2 - 1, mBlurRadius/2);
BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2);
BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2 - 1);
BoxBlurVertical(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2 - 1, mBlurRadius/2);
BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2);
}
mDestinationCtx->Mask(mImageSurface);
}
gfxContext*
nsContextBoxBlur::GetContext()
{
return mContext;
}

Просмотреть файл

@ -43,6 +43,7 @@
#include "nsIRenderingContext.h" #include "nsIRenderingContext.h"
#include "nsStyleConsts.h" #include "nsStyleConsts.h"
#include "gfxContext.h" #include "gfxContext.h"
#include "gfxImageSurface.h"
struct nsPoint; struct nsPoint;
class nsStyleContext; class nsStyleContext;
class nsPresContext; class nsPresContext;
@ -306,5 +307,85 @@ protected:
const PRUint8 aStyle); const PRUint8 aStyle);
}; };
/*
* nsContextBoxBlur
* Creates an 8-bit alpha channel context for callers to draw in, blurs the
* contents of that context and applies it as a 1-color mask on a
* different existing context.
*
* You must call Init() first to create a suitable temporary surface to draw on.
* You must then draw any desired content onto the given context, then call DoPaint()
* to apply the blurred content as a single-color mask. You can only call Init() once,
* so objects cannot be reused.
*
* This is very useful for creating drop shadows or silhouettes.
*/
class nsContextBoxBlur {
public:
/**
* Prepares a gfxContext to draw on. Do not call this twice; if you want to
* get the gfxContext again use GetContext().
*
* @param aRect The coordinates of the surface to create.
* All coordinates must be in app units.
* This must not include the blur radius, pass it as the
* second parameter and everything is taken care of.
*
* @param aBlurRadius The blur radius in app units.
*
* @param aAppUnitsPerDevPixel The number of app units in a device pixel, for conversion.
* Most of the time you'll pass this from the current
* PresContext if available.
*
* @param aDestinationCtx The graphics context to apply the blurred mask to
* when you call DoPaint(). Make sure it is not destroyed
* before you call DoPaint(). To set the color of the resulting
* blurred graphic mask, you must set the color on this
* context before calling Init().
*
* @return A blank 8-bit alpha-channel-only graphics context to draw on, or null on
* error. Must not be freed. The context has a device offset applied to it given
* by aRect. This means you can use coordinates as if it were at the desired position
* at aRect and you don't need to worry about translating any coordinates to draw
* on this temporary surface.
*
* If aBlurRadius is 0, the returned context is aDestinationCtx, because no blurring is required.
*/
gfxContext* Init(const gfxRect& aRect, nscoord aBlurRadius, PRInt32 aAppUnitsPerDevPixel,
gfxContext* aDestinationCtx);
/**
* Does the actual blurring and mask applying. Users of this object *must*
* have called Init() first, then have drawn whatever they want to be
* blurred onto the internal gfxContext before calling this.
*/
void DoPaint();
/**
* Gets the internal gfxContext at any time. Must not be freed. Avoid calling
* this before calling Init() since the context would not be constructed at that
* point.
*/
gfxContext* GetContext();
protected:
void BoxBlurHorizontal(unsigned char* aInput,
unsigned char* aOutput,
PRUint32 aLeftLobe,
PRUint32 aRightLobe);
void BoxBlurVertical(unsigned char* aInput,
unsigned char* aOutput,
PRUint32 aTopLobe,
PRUint32 aBottomLobe);
nsRefPtr<gfxContext> mContext;
nsRefPtr<gfxImageSurface> mImageSurface;
gfxContext* mDestinationCtx;
// Contrary to what is passed as parameters, these are in device pixels
gfxRect mRect;
PRInt32 mBlurRadius;
};
#endif /* nsCSSRendering_h___ */ #endif /* nsCSSRendering_h___ */

Просмотреть файл

@ -1286,6 +1286,30 @@ nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo) {
: accumulator.mResultRect; : accumulator.mResultRect;
} }
nsRect
nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
nsIFrame* aFrame)
{
const nsStyleText* textStyle = aFrame->GetStyleText();
if (!textStyle->mShadowArray)
return aTextAndDecorationsRect;
nsRect resultRect = aTextAndDecorationsRect;
for (PRUint32 i = 0; i < textStyle->mShadowArray->Length(); ++i) {
nsRect tmpRect(aTextAndDecorationsRect);
nsTextShadowItem* shadow = textStyle->mShadowArray->ShadowAt(i);
nscoord xOffset = shadow->mXOffset.GetCoordValue();
nscoord yOffset = shadow->mYOffset.GetCoordValue();
nscoord blurRadius = shadow->mRadius.GetCoordValue();
tmpRect.MoveBy(nsPoint(xOffset, yOffset));
tmpRect.Inflate(blurRadius, blurRadius);
resultRect.UnionRect(resultRect, tmpRect);
}
return resultRect;
}
nsresult nsresult
nsLayoutUtils::GetFontMetricsForFrame(nsIFrame* aFrame, nsLayoutUtils::GetFontMetricsForFrame(nsIFrame* aFrame,
nsIFontMetrics** aFontMetrics) nsIFontMetrics** aFontMetrics)

Просмотреть файл

@ -502,6 +502,14 @@ public:
*/ */
static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo); static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo);
/**
* Takes a text-shadow array from the style properties of a given nsIFrame and
* computes the union of those shadows along with the given initial rect.
* If there are no shadows, the initial rect is returned.
*/
static nsRect GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
nsIFrame* aFrame);
/** /**
* Get the font metrics corresponding to the frame's style data. * Get the font metrics corresponding to the frame's style data.
* @param aFrame the frame * @param aFrame the frame

Просмотреть файл

@ -1439,10 +1439,20 @@ nsBlockFrame::ComputeCombinedArea(const nsHTMLReflowState& aReflowState,
// XXX_perf: This can be done incrementally. It is currently one of // XXX_perf: This can be done incrementally. It is currently one of
// the things that makes incremental reflow O(N^2). // the things that makes incremental reflow O(N^2).
nsRect area(0, 0, aMetrics.width, aMetrics.height); nsRect area(0, 0, aMetrics.width, aMetrics.height);
if (NS_STYLE_OVERFLOW_CLIP != aReflowState.mStyleDisplay->mOverflowX) { if (NS_STYLE_OVERFLOW_CLIP != aReflowState.mStyleDisplay->mOverflowX) {
PRBool inQuirks = (PresContext()->CompatibilityMode() == eCompatibility_NavQuirks);
for (line_iterator line = begin_lines(), line_end = end_lines(); for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end; line != line_end;
++line) { ++line) {
// Text-shadow overflows
if (!inQuirks && line->IsInline()) {
nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(line->GetCombinedArea(),
this);
area.UnionRect(area, shadowRect);
}
area.UnionRect(area, line->GetCombinedArea()); area.UnionRect(area, line->GetCombinedArea());
} }
@ -5904,7 +5914,7 @@ nsBlockFrame::IsVisibleInSelection(nsISelection* aSelection)
} }
/* virtual */ void /* virtual */ void
nsBlockFrame::PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, nsBlockFrame::PaintTextDecorationLine(gfxContext* aCtx,
const nsPoint& aPt, const nsPoint& aPt,
nsLineBox* aLine, nsLineBox* aLine,
nscolor aColor, nscolor aColor,
@ -5945,12 +5955,11 @@ nsBlockFrame::PaintTextDecorationLine(nsIRenderingContext& aRenderingContext,
// Only paint if we have a positive width // Only paint if we have a positive width
if (width > 0) { if (width > 0) {
gfxContext *ctx = aRenderingContext.ThebesContext();
gfxPoint pt(PresContext()->AppUnitsToGfxUnits(start + aPt.x), gfxPoint pt(PresContext()->AppUnitsToGfxUnits(start + aPt.x),
PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y)); PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y));
gfxSize size(PresContext()->AppUnitsToGfxUnits(width), aSize); gfxSize size(PresContext()->AppUnitsToGfxUnits(width), aSize);
nsCSSRendering::PaintDecorationLine( nsCSSRendering::PaintDecorationLine(
ctx, aColor, pt, size, aCtx, aColor, pt, size,
PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()), PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()),
aOffset, aDecoration, NS_STYLE_BORDER_STYLE_SOLID); aOffset, aDecoration, NS_STYLE_BORDER_STYLE_SOLID);
} }

Просмотреть файл

@ -341,7 +341,7 @@ protected:
* Overides member function of nsHTMLContainerFrame. Needed to handle the * Overides member function of nsHTMLContainerFrame. Needed to handle the
* lines in a nsBlockFrame properly. * lines in a nsBlockFrame properly.
*/ */
virtual void PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, virtual void PaintTextDecorationLine(gfxContext* aCtx,
const nsPoint& aPt, const nsPoint& aPt,
nsLineBox* aLine, nsLineBox* aLine,
nscolor aColor, nscolor aColor,

Просмотреть файл

@ -20,6 +20,7 @@
* the Initial Developer. All Rights Reserved. * the Initial Developer. All Rights Reserved.
* *
* Contributor(s): * Contributor(s):
* Michael Ventnor <m.ventnor@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"), * either of the GNU General Public License Version 2 or later (the "GPL"),
@ -131,15 +132,15 @@ nsDisplayTextDecoration::Paint(nsDisplayListBuilder* aBuilder,
nsHTMLContainerFrame* f = static_cast<nsHTMLContainerFrame*>(mFrame); nsHTMLContainerFrame* f = static_cast<nsHTMLContainerFrame*>(mFrame);
if (mDecoration == NS_STYLE_TEXT_DECORATION_UNDERLINE) { if (mDecoration == NS_STYLE_TEXT_DECORATION_UNDERLINE) {
gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
f->PaintTextDecorationLine(*aCtx, pt, mLine, mColor, f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
underlineOffset, ascent, underlineOffset, ascent,
metrics.underlineSize, mDecoration); metrics.underlineSize, mDecoration);
} else if (mDecoration == NS_STYLE_TEXT_DECORATION_OVERLINE) { } else if (mDecoration == NS_STYLE_TEXT_DECORATION_OVERLINE) {
f->PaintTextDecorationLine(*aCtx, pt, mLine, mColor, f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
metrics.maxAscent, ascent, metrics.maxAscent, ascent,
metrics.underlineSize, mDecoration); metrics.underlineSize, mDecoration);
} else { } else {
f->PaintTextDecorationLine(*aCtx, pt, mLine, mColor, f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor,
metrics.strikeoutOffset, ascent, metrics.strikeoutOffset, ascent,
metrics.strikeoutSize, mDecoration); metrics.strikeoutSize, mDecoration);
} }
@ -151,6 +152,95 @@ nsDisplayTextDecoration::GetBounds(nsDisplayListBuilder* aBuilder)
return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame); return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame);
} }
class nsDisplayTextShadow : public nsDisplayItem {
public:
nsDisplayTextShadow(nsHTMLContainerFrame* aFrame, const PRUint8 aDecoration,
const nscolor& aColor, nsLineBox* aLine,
const nscoord& aBlurRadius, const gfxPoint& aOffset)
: nsDisplayItem(aFrame), mLine(aLine), mColor(aColor),
mDecorationFlags(aDecoration),
mBlurRadius(aBlurRadius), mOffset(aOffset) {
MOZ_COUNT_CTOR(nsDisplayTextShadow);
}
virtual ~nsDisplayTextShadow() {
MOZ_COUNT_DTOR(nsDisplayTextShadow);
}
virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
const nsRect& aDirtyRect);
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
NS_DISPLAY_DECL_NAME("TextShadow")
private:
nsLineBox* mLine;
nscolor mColor;
PRUint8 mDecorationFlags;
nscoord mBlurRadius; // App units
gfxPoint mOffset; // App units
};
void
nsDisplayTextShadow::Paint(nsDisplayListBuilder* aBuilder,
nsIRenderingContext* aCtx,
const nsRect& aDirtyRect)
{
mBlurRadius = PR_MAX(mBlurRadius, 0);
nsCOMPtr<nsIFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm));
nsIThebesFontMetrics* tfm = static_cast<nsIThebesFontMetrics*>(fm.get());
gfxFontGroup* fontGroup = tfm->GetThebesFontGroup();
gfxFont* firstFont = fontGroup->GetFontAt(0);
if (!firstFont)
return; // OOM
const gfxFont::Metrics& metrics = firstFont->GetMetrics();
nsPoint pt = aBuilder->ToReferenceFrame(mFrame) + nsPoint(mOffset.x, mOffset.y);
nsHTMLContainerFrame* f = static_cast<nsHTMLContainerFrame*>(mFrame);
nsMargin bp = f->GetUsedBorderAndPadding();
nscoord innerWidthInAppUnits = (mFrame->GetSize().width - bp.LeftRight());
gfxRect shadowRect = gfxRect(pt.x, pt.y, innerWidthInAppUnits, mFrame->GetSize().height);
gfxContext* thebesCtx = aCtx->ThebesContext();
nsContextBoxBlur contextBoxBlur;
gfxContext* shadowCtx = contextBoxBlur.Init(shadowRect, mBlurRadius,
mFrame->PresContext()->AppUnitsPerDevPixel(),
thebesCtx);
if (!shadowCtx)
return;
thebesCtx->Save();
thebesCtx->NewPath();
thebesCtx->SetColor(gfxRGBA(mColor));
if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_UNDERLINE) {
gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
f->PaintTextDecorationLine(shadowCtx, pt, mLine, mColor,
underlineOffset, metrics.maxAscent,
metrics.underlineSize, NS_STYLE_TEXT_DECORATION_UNDERLINE);
}
if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_OVERLINE) {
f->PaintTextDecorationLine(shadowCtx, pt, mLine, mColor,
metrics.maxAscent, metrics.maxAscent,
metrics.underlineSize, NS_STYLE_TEXT_DECORATION_OVERLINE);
}
if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_THROUGH) {
f->PaintTextDecorationLine(shadowCtx, pt, mLine, mColor,
metrics.strikeoutOffset, metrics.maxAscent,
metrics.strikeoutSize, NS_STYLE_TEXT_DECORATION_LINE_THROUGH);
}
contextBoxBlur.DoPaint();
thebesCtx->Restore();
}
nsRect
nsDisplayTextShadow::GetBounds(nsDisplayListBuilder* aBuilder)
{
// Shadows are always painted in the overflow rect
return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame);
}
nsresult nsresult
nsHTMLContainerFrame::DisplayTextDecorations(nsDisplayListBuilder* aBuilder, nsHTMLContainerFrame::DisplayTextDecorations(nsDisplayListBuilder* aBuilder,
nsDisplayList* aBelowTextDecorations, nsDisplayList* aBelowTextDecorations,
@ -170,6 +260,34 @@ nsHTMLContainerFrame::DisplayTextDecorations(nsDisplayListBuilder* aBuilder,
GetTextDecorations(PresContext(), aLine != nsnull, decorations, underColor, GetTextDecorations(PresContext(), aLine != nsnull, decorations, underColor,
overColor, strikeColor); overColor, strikeColor);
if (decorations == NS_STYLE_TEXT_DECORATION_NONE)
return NS_OK;
// The text-shadow spec says that any text decorations must also have a shadow applied to
// it. So draw the shadows as part of the display list.
const nsStyleText* textStyle = GetStyleText();
if (textStyle->mShadowArray) {
for (PRUint32 i = textStyle->mShadowArray->Length(); i > 0; --i) {
nsTextShadowItem* shadow = textStyle->mShadowArray->ShadowAt(i - 1);
nscoord blurRadius = shadow->mRadius.GetCoordValue();
nscolor shadowColor;
if (shadow->mHasColor)
shadowColor = shadow->mColor;
else
shadowColor = GetStyleColor()->mColor;
gfxPoint offset = gfxPoint(shadow->mXOffset.GetCoordValue(),
shadow->mYOffset.GetCoordValue());
// Add it to the display list so it is painted underneath the text and all decorations
nsresult rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder)
nsDisplayTextShadow(this, decorations, shadowColor, aLine, blurRadius, offset));
NS_ENSURE_SUCCESS(rv, rv);
}
}
if (decorations & NS_STYLE_TEXT_DECORATION_UNDERLINE) { if (decorations & NS_STYLE_TEXT_DECORATION_UNDERLINE) {
nsresult rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder) nsresult rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder)
nsDisplayTextDecoration(this, NS_STYLE_TEXT_DECORATION_UNDERLINE, underColor, aLine)); nsDisplayTextDecoration(this, NS_STYLE_TEXT_DECORATION_UNDERLINE, underColor, aLine));
@ -221,7 +339,7 @@ HasTextFrameDescendantOrInFlow(nsIFrame* aFrame);
/*virtual*/ void /*virtual*/ void
nsHTMLContainerFrame::PaintTextDecorationLine( nsHTMLContainerFrame::PaintTextDecorationLine(
nsIRenderingContext& aRenderingContext, gfxContext* aCtx,
const nsPoint& aPt, const nsPoint& aPt,
nsLineBox* aLine, nsLineBox* aLine,
nscolor aColor, nscolor aColor,
@ -239,11 +357,10 @@ nsHTMLContainerFrame::PaintTextDecorationLine(
} }
} }
nscoord innerWidth = mRect.width - bp.left - bp.right; nscoord innerWidth = mRect.width - bp.left - bp.right;
gfxContext *ctx = aRenderingContext.ThebesContext();
gfxPoint pt(PresContext()->AppUnitsToGfxUnits(bp.left + aPt.x), gfxPoint pt(PresContext()->AppUnitsToGfxUnits(bp.left + aPt.x),
PresContext()->AppUnitsToGfxUnits(bp.top + aPt.y)); PresContext()->AppUnitsToGfxUnits(bp.top + aPt.y));
gfxSize size(PresContext()->AppUnitsToGfxUnits(innerWidth), aSize); gfxSize size(PresContext()->AppUnitsToGfxUnits(innerWidth), aSize);
nsCSSRendering::PaintDecorationLine(ctx, aColor, pt, size, aAscent, aOffset, nsCSSRendering::PaintDecorationLine(aCtx, aColor, pt, size, aAscent, aOffset,
aDecoration, NS_STYLE_BORDER_STYLE_SOLID); aDecoration, NS_STYLE_BORDER_STYLE_SOLID);
} }

Просмотреть файл

@ -161,7 +161,7 @@ protected:
/** /**
* Function that does the actual drawing of the textdecoration. * Function that does the actual drawing of the textdecoration.
* input: * input:
* @param aRenderingContext * @param aCtx the Thebes graphics context to draw on
* @param aLine the line, or nsnull if this is an inline frame * @param aLine the line, or nsnull if this is an inline frame
* @param aColor the color of the text-decoration * @param aColor the color of the text-decoration
* @param aAscent ascent of the font from which the * @param aAscent ascent of the font from which the
@ -176,7 +176,7 @@ protected:
* NS_STYLE_TEXT_DECORATION_OVERLINE or * NS_STYLE_TEXT_DECORATION_OVERLINE or
* NS_STYLE_TEXT_DECORATION_LINE_THROUGH. * NS_STYLE_TEXT_DECORATION_LINE_THROUGH.
*/ */
virtual void PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, virtual void PaintTextDecorationLine(gfxContext* aCtx,
const nsPoint& aPt, const nsPoint& aPt,
nsLineBox* aLine, nsLineBox* aLine,
nscolor aColor, nscolor aColor,
@ -186,6 +186,7 @@ protected:
const PRUint8 aDecoration); const PRUint8 aDecoration);
friend class nsDisplayTextDecoration; friend class nsDisplayTextDecoration;
friend class nsDisplayTextShadow;
}; };
#endif /* nsHTMLContainerFrame_h___ */ #endif /* nsHTMLContainerFrame_h___ */

Просмотреть файл

@ -2556,6 +2556,14 @@ nsLineLayout::RelativePositionFrames(PerSpanData* psd, nsRect& aCombinedArea)
// bidi reordering can move and resize the frames. So use the frame's // bidi reordering can move and resize the frames. So use the frame's
// rect instead of mBounds. // rect instead of mBounds.
nsRect adjustedBounds(nsPoint(0, 0), psd->mFrame->mFrame->GetSize()); nsRect adjustedBounds(nsPoint(0, 0), psd->mFrame->mFrame->GetSize());
// Text-shadow overflow
if (mPresContext->CompatibilityMode() != eCompatibility_NavQuirks) {
nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(adjustedBounds,
psd->mFrame->mFrame);
adjustedBounds.UnionRect(adjustedBounds, shadowRect);
}
combinedAreaResult.UnionRect(psd->mFrame->mCombinedArea, adjustedBounds); combinedAreaResult.UnionRect(psd->mFrame->mCombinedArea, adjustedBounds);
} }
else { else {

Просмотреть файл

@ -54,6 +54,7 @@
#include "nsLineBox.h" #include "nsLineBox.h"
#include "gfxFont.h" #include "gfxFont.h"
#include "gfxSkipChars.h" #include "gfxSkipChars.h"
#include "gfxContext.h"
class nsTextPaintStyle; class nsTextPaintStyle;
class PropertyProvider; class PropertyProvider;
@ -253,6 +254,7 @@ public:
gfxFloat GetSnappedBaselineY(gfxContext* aContext, gfxFloat aY); gfxFloat GetSnappedBaselineY(gfxContext* aContext, gfxFloat aY);
// primary frame paint method called from nsDisplayText // primary frame paint method called from nsDisplayText
// The private DrawText() is what applies the text to a graphics context
void PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt, void PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt,
const nsRect& aDirtyRect); const nsRect& aDirtyRect);
// helper: paint quirks-mode CSS text decorations // helper: paint quirks-mode CSS text decorations
@ -260,7 +262,8 @@ public:
const gfxPoint& aFramePt, const gfxPoint& aFramePt,
const gfxPoint& aTextBaselinePt, const gfxPoint& aTextBaselinePt,
nsTextPaintStyle& aTextStyle, nsTextPaintStyle& aTextStyle,
PropertyProvider& aProvider); PropertyProvider& aProvider,
const nscolor& aOverrideColor = 0);
// helper: paint text frame when we're impacted by at least one selection. // helper: paint text frame when we're impacted by at least one selection.
// Return PR_FALSE if the text was not painted and we should continue with // Return PR_FALSE if the text was not painted and we should continue with
// the fast path. // the fast path.
@ -382,6 +385,25 @@ protected:
PropertyProvider& aProvider, PropertyProvider& aProvider,
nsRect* aOverflowRect); nsRect* aOverflowRect);
void DrawText(gfxContext* aCtx,
const gfxPoint& aTextBaselinePt,
PRUint32 aOffset,
PRUint32 aLength,
const gfxRect* aDirtyRect,
PropertyProvider* aProvider,
gfxFloat& aAdvanceWidth,
PRBool aDrawSoftHyphen);
void PaintOneShadow(PRUint32 aOffset,
PRUint32 aLength,
nsTextShadowItem* aShadowDetails,
PropertyProvider* aProvider,
const gfxRect& aDirtyRect,
const gfxPoint& aFramePt,
const gfxPoint& aTextBaselinePt,
gfxContext* aCtx,
const nscolor& aForegroundColor);
struct TextDecorations { struct TextDecorations {
PRUint8 mDecorations; PRUint8 mDecorations;
nscolor mOverColor; nscolor mOverColor;

Просмотреть файл

@ -32,6 +32,7 @@
* Mats Palmgren <mats.palmgren@bredband.net> * Mats Palmgren <mats.palmgren@bredband.net>
* Uri Bernstein <uriber@gmail.com> * Uri Bernstein <uriber@gmail.com>
* Stephen Blackheath <entangled.mooched.stephen@blacksapphire.com> * Stephen Blackheath <entangled.mooched.stephen@blacksapphire.com>
* Michael Ventnor <m.ventnor@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"), * either of the GNU General Public License Version 2 or later (the "GPL"),
@ -115,6 +116,7 @@
#include "gfxFont.h" #include "gfxFont.h"
#include "gfxContext.h" #include "gfxContext.h"
#include "gfxTextRunWordCache.h" #include "gfxTextRunWordCache.h"
#include "gfxImageSurface.h"
#ifdef NS_DEBUG #ifdef NS_DEBUG
#undef NOISY_BLINK #undef NOISY_BLINK
@ -3675,6 +3677,10 @@ nsTextFrame::UnionTextDecorationOverflow(nsPresContext* aPresContext,
PropertyProvider& aProvider, PropertyProvider& aProvider,
nsRect* aOverflowRect) nsRect* aOverflowRect)
{ {
// Text-shadow overflows
nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(*aOverflowRect, this);
aOverflowRect->UnionRect(*aOverflowRect, shadowRect);
if (IsFloatingFirstLetterChild()) { if (IsFloatingFirstLetterChild()) {
// The underline/overline drawable area must be contained in the overflow // The underline/overline drawable area must be contained in the overflow
// rect when this is in floating first letter frame at *both* modes. // rect when this is in floating first letter frame at *both* modes.
@ -3704,7 +3710,8 @@ nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect,
const gfxPoint& aFramePt, const gfxPoint& aFramePt,
const gfxPoint& aTextBaselinePt, const gfxPoint& aTextBaselinePt,
nsTextPaintStyle& aTextPaintStyle, nsTextPaintStyle& aTextPaintStyle,
PropertyProvider& aProvider) PropertyProvider& aProvider,
const nscolor& aOverrideColor)
{ {
TextDecorations decorations = TextDecorations decorations =
GetTextDecorations(aTextPaintStyle.PresContext()); GetTextDecorations(aTextPaintStyle.PresContext());
@ -3722,24 +3729,28 @@ nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect,
gfxSize size(GetRect().width / app, 0); gfxSize size(GetRect().width / app, 0);
gfxFloat ascent = gfxFloat(mAscent) / app; gfxFloat ascent = gfxFloat(mAscent) / app;
nscolor lineColor;
if (decorations.HasOverline()) { if (decorations.HasOverline()) {
lineColor = aOverrideColor ? aOverrideColor : decorations.mOverColor;
size.height = fontMetrics.underlineSize; size.height = fontMetrics.underlineSize;
nsCSSRendering::PaintDecorationLine( nsCSSRendering::PaintDecorationLine(
aCtx, decorations.mOverColor, pt, size, ascent, fontMetrics.maxAscent, aCtx, lineColor, pt, size, ascent, fontMetrics.maxAscent,
NS_STYLE_TEXT_DECORATION_OVERLINE, NS_STYLE_BORDER_STYLE_SOLID); NS_STYLE_TEXT_DECORATION_OVERLINE, NS_STYLE_BORDER_STYLE_SOLID);
} }
if (decorations.HasUnderline()) { if (decorations.HasUnderline()) {
lineColor = aOverrideColor ? aOverrideColor : decorations.mUnderColor;
size.height = fontMetrics.underlineSize; size.height = fontMetrics.underlineSize;
gfxFloat offset = aProvider.GetFontGroup()->GetUnderlineOffset(); gfxFloat offset = aProvider.GetFontGroup()->GetUnderlineOffset();
nsCSSRendering::PaintDecorationLine( nsCSSRendering::PaintDecorationLine(
aCtx, decorations.mUnderColor, pt, size, ascent, offset, aCtx, lineColor, pt, size, ascent, offset,
NS_STYLE_TEXT_DECORATION_UNDERLINE, NS_STYLE_BORDER_STYLE_SOLID); NS_STYLE_TEXT_DECORATION_UNDERLINE, NS_STYLE_BORDER_STYLE_SOLID);
} }
if (decorations.HasStrikeout()) { if (decorations.HasStrikeout()) {
lineColor = aOverrideColor ? aOverrideColor : decorations.mStrikeColor;
size.height = fontMetrics.strikeoutSize; size.height = fontMetrics.strikeoutSize;
gfxFloat offset = fontMetrics.strikeoutOffset; gfxFloat offset = fontMetrics.strikeoutOffset;
nsCSSRendering::PaintDecorationLine( nsCSSRendering::PaintDecorationLine(
aCtx, decorations.mStrikeColor, pt, size, ascent, offset, aCtx, lineColor, pt, size, ascent, offset,
NS_STYLE_TEXT_DECORATION_LINE_THROUGH, NS_STYLE_BORDER_STYLE_SOLID); NS_STYLE_TEXT_DECORATION_LINE_THROUGH, NS_STYLE_BORDER_STYLE_SOLID);
} }
} }
@ -3948,6 +3959,72 @@ PRBool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
return PR_TRUE; return PR_TRUE;
} }
void
nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength,
nsTextShadowItem* aShadowDetails,
PropertyProvider* aProvider, const gfxRect& aDirtyRect,
const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
gfxContext* aCtx, const nscolor& aForegroundColor)
{
nscoord xOffset = aShadowDetails->mXOffset.GetCoordValue();
nscoord yOffset = aShadowDetails->mYOffset.GetCoordValue();
nscoord blurRadius = PR_MAX(aShadowDetails->mRadius.GetCoordValue(), 0);
nsTextPaintStyle textPaintStyle(this);
gfxFloat advanceWidth;
gfxTextRun::Metrics shadowMetrics =
mTextRun->MeasureText(aOffset, aLength, PR_FALSE,
nsnull, aProvider);
// This rect is the box which is equivalent to where the shadow will be painted.
// X and Y are significant because they can affect the rounding.
// The origin of mBoundingBox is the text baseline point, so we must translate it by
// that much in order to make the origin the top-left corner of the text bounding box.
gfxRect shadowRect = shadowMetrics.mBoundingBox + aTextBaselinePt;
shadowRect.MoveBy(gfxPoint(xOffset, yOffset));
if (GetStateBits() & TEXT_HYPHEN_BREAK) {
// Add the width of the soft hyphen so it isn't cut off
shadowRect.size.width += aProvider->GetHyphenWidth();
}
nsContextBoxBlur contextBoxBlur;
gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, blurRadius,
PresContext()->AppUnitsPerDevPixel(),
aCtx);
if (!shadowContext)
return;
nscolor shadowColor;
if (aShadowDetails->mHasColor)
shadowColor = aShadowDetails->mColor;
else
shadowColor = aForegroundColor;
aCtx->Save();
aCtx->NewPath();
aCtx->SetColor(gfxRGBA(shadowColor));
// Draw the text onto our alpha-only surface to capture the alpha values.
// Remember that the box blur context has a device offset on it, so we don't need to
// translate any coordinates to fit on the surface.
DrawText(shadowContext,
aTextBaselinePt + gfxPoint(xOffset, yOffset),
aOffset, aLength, &aDirtyRect, aProvider, advanceWidth,
(GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
// This will only have an effect in quirks mode. Standards mode text-decoration shadow painting
// is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change
// any code behaviour here.
PaintTextDecorations(shadowContext, aDirtyRect, aFramePt + gfxPoint(xOffset, yOffset),
aTextBaselinePt + gfxPoint(xOffset, yOffset),
textPaintStyle, *aProvider, shadowColor);
contextBoxBlur.DoPaint();
aCtx->Restore();
}
// Paints selection backgrounds and text in the correct colors. Also computes // Paints selection backgrounds and text in the correct colors. Also computes
// aAllTypes, the union of all selection types that are applying to this text. // aAllTypes, the union of all selection types that are applying to this text.
void void
@ -4031,18 +4108,11 @@ nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
// Draw text segment // Draw text segment
aCtx->SetColor(gfxRGBA(foreground)); aCtx->SetColor(gfxRGBA(foreground));
gfxFloat advance; gfxFloat advance;
mTextRun->Draw(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y), offset, length,
&aDirtyRect, &aProvider, &advance); DrawText(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y),
offset, length, &aDirtyRect, &aProvider,
advance, hyphenWidth > 0);
if (hyphenWidth) { if (hyphenWidth) {
// Draw the hyphen
gfxFloat hyphenBaselineX = aFramePt.x + xOffset + mTextRun->GetDirection()*advance;
// Get a reference rendering context because aCtx might not have the
// reference matrix currently set
gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this));
if (hyphenTextRun.get()) {
hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
0, hyphenTextRun->GetLength(), &aDirtyRect, nsnull, nsnull);
}
advance += hyphenWidth; advance += hyphenWidth;
} }
iterator.UpdateWithAdvance(advance); iterator.UpdateWithAdvance(advance);
@ -4193,6 +4263,23 @@ nsTextFrame::PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt,
gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
aDirtyRect.width, aDirtyRect.height); aDirtyRect.width, aDirtyRect.height);
gfxFloat advanceWidth;
gfxRGBA foregroundColor = gfxRGBA(textPaintStyle.GetTextColor());
// Paint the text shadow before doing any foreground stuff
const nsStyleText* textStyle = GetStyleText();
if (textStyle->mShadowArray) {
// Text shadow happens with the last value being painted at the back,
// ie. it is painted first.
for (PRUint32 i = textStyle->mShadowArray->Length(); i > 0; --i) {
PaintOneShadow(provider.GetStart().GetSkippedOffset(),
ComputeTransformedLength(provider),
textStyle->mShadowArray->ShadowAt(i - 1), &provider,
dirtyRect, framePt, textBaselinePt, ctx,
textPaintStyle.GetTextColor());
}
}
// Fork off to the (slower) paint-with-selection path if necessary. // Fork off to the (slower) paint-with-selection path if necessary.
if (GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { if (GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT) {
if (PaintTextWithSelection(ctx, framePt, textBaselinePt, if (PaintTextWithSelection(ctx, framePt, textBaselinePt,
@ -4200,27 +4287,36 @@ nsTextFrame::PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt,
return; return;
} }
gfxFloat advanceWidth; ctx->SetColor(foregroundColor);
gfxFloat* needAdvanceWidth =
(GetStateBits() & TEXT_HYPHEN_BREAK) ? &advanceWidth : nsnull;
ctx->SetColor(gfxRGBA(textPaintStyle.GetTextColor()));
mTextRun->Draw(ctx, textBaselinePt, DrawText(ctx, textBaselinePt, provider.GetStart().GetSkippedOffset(),
provider.GetStart().GetSkippedOffset(), ComputeTransformedLength(provider), &dirtyRect,
ComputeTransformedLength(provider), &provider, advanceWidth,
&dirtyRect, &provider, needAdvanceWidth); (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
if (GetStateBits() & TEXT_HYPHEN_BREAK) { PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt,
gfxFloat hyphenBaselineX = textBaselinePt.x + mTextRun->GetDirection()*advanceWidth; textPaintStyle, provider);
}
void
nsTextFrame::DrawText(gfxContext* aCtx, const gfxPoint& aTextBaselinePt,
PRUint32 aOffset, PRUint32 aLength,
const gfxRect* aDirtyRect, PropertyProvider* aProvider,
gfxFloat& aAdvanceWidth, PRBool aDrawSoftHyphen)
{
// Paint the text and soft-hyphen (if any) onto the given graphics context
mTextRun->Draw(aCtx, aTextBaselinePt, aOffset, aLength,
aDirtyRect, aProvider, &aAdvanceWidth);
if (aDrawSoftHyphen) {
gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth;
// Don't use ctx as the context, because we need a reference context here, // Don't use ctx as the context, because we need a reference context here,
// ctx may be transformed. // ctx may be transformed.
gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this)); gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this));
if (hyphenTextRun.get()) { if (hyphenTextRun.get()) {
hyphenTextRun->Draw(ctx, gfxPoint(hyphenBaselineX, textBaselinePt.y), hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
0, hyphenTextRun->GetLength(), &dirtyRect, nsnull, nsnull); 0, hyphenTextRun->GetLength(), aDirtyRect, nsnull, nsnull);
} }
} }
PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt,
textPaintStyle, provider);
} }
PRInt16 PRInt16

Просмотреть файл

@ -856,6 +856,12 @@ nsMathMLContainerFrame::GatherAndStoreOverflow(nsHTMLReflowMetrics* aMetrics)
// frame rectangle. // frame rectangle.
nsRect frameRect(0, 0, aMetrics->width, aMetrics->height); nsRect frameRect(0, 0, aMetrics->width, aMetrics->height);
// Text-shadow overflows.
if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks) {
nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(frameRect, this);
frameRect.UnionRect(frameRect, shadowRect);
}
// All non-child-frame content such as nsMathMLChars (and most child-frame // All non-child-frame content such as nsMathMLChars (and most child-frame
// content) is included in mBoundingMetrics. // content) is included in mBoundingMetrics.
nsRect boundingBox(mBoundingMetrics.leftBearing, nsRect boundingBox(mBoundingMetrics.leftBearing,

Просмотреть файл

@ -55,7 +55,7 @@ include text/reftest.list
include text-decoration/reftest.list include text-decoration/reftest.list
# text-shadow/ # text-shadow/
# include text-shadow/reftest.list include text-shadow/reftest.list
# text-indent/ # text-indent/
include text-indent/reftest.list include text-indent/reftest.list

Просмотреть файл

@ -1,13 +1,25 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<!-- blue/green underline --> <!-- blue underline -->
<div style="position: absolute; top: 22px; left: 22px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div> <div style="position: absolute; top: 22px; left: 22px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testforquirks</span></div>
<div style="position: absolute; top: 20px; left: 20px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div> <div style="position: absolute; top: 20px; left: 20px; color: blue; text-decoration: underline;"><span style="color: rgba(0, 0, 0, 0);">testforquirks</span></div>
<!-- blue text -->
<div style="position: absolute; top: 22px; left: 22px;"><span style="color: blue;">test</span></div>
<div style="position: absolute; top: 20px; left: 20px;"><span style="color: blue;">test</span></div>
<!-- red overline --> <!-- red overline -->
<div style="position: absolute; top: 22px; left: 22px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div> <div style="position: absolute; top: 22px; left: 22px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div>
<div style="position: absolute; top: 20px; left: 20px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div> <div style="position: absolute; top: 20px; left: 20px; color: rgba(0, 0, 0, 0);">test<span style="text-decoration: overline; color: red;"><span style="color: rgba(0, 0, 0, 0);">forquirks</span></span></div>
<!-- the actual text --> <!-- red text -->
<div style="position: absolute; top: 22px; left: 22px;"><span style="color: blue;">test</span><span style="color: red;">for</span><span style="color: green;">quirks</span></div> <div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">test</span><span style="color: red;">for</span></div>
<div style="position: absolute; top: 20px; left: 20px;"><span style="color: blue;">test</span><span style="color: red;">for</span><span style="color: green;">quirks</span></div> <div style="position: absolute; top: 20px; left: 20px;"><span style="color: rgba(0, 0, 0, 0);">test</span><span style="color: red;">for</span></div>
<!-- green underline -->
<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div>
<div style="position: absolute; top: 20px; left: 20px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="text-decoration: underline; color: green;"><span style="color: rgba(0, 0, 0, 0);">quirks</span></span></div>
<!-- green text -->
<div style="position: absolute; top: 22px; left: 22px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="color: green;">quirks</span></div>
<div style="position: absolute; top: 20px; left: 20px;"><span style="color: rgba(0, 0, 0, 0);">testfor</span><span style="color: green;">quirks</span></div>