Bug 902799 - Support textruns with vertical writing modes when drawing Canvas2D text. r=bas
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3086,21 +3086,28 @@ CanvasRenderingContext2D::GetHitRegionRe
*/
struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
{
typedef CanvasRenderingContext2D::ContextState ContextState;
virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction)
{
mFontgrp->UpdateUserFonts(); // ensure user font generation is current
+ // adjust flags for current direction run
+ uint32_t flags = mTextRunFlags;
+ if (direction & 1) {
+ flags |= gfxTextRunFactory::TEXT_IS_RTL;
+ } else {
+ flags &= ~gfxTextRunFactory::TEXT_IS_RTL;
+ }
mTextRun = mFontgrp->MakeTextRun(text,
length,
mThebes,
mAppUnitsPerDevPixel,
- direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
+ flags);
}
virtual nscoord GetWidth()
{
gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
mTextRun->GetLength(),
mDoMeasureBoundingBox ?
gfxFont::TIGHT_INK_EXTENTS :
@@ -3116,34 +3123,38 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
}
return NSToCoordRound(textRunMetrics.mAdvanceWidth);
}
virtual void DrawText(nscoord xOffset, nscoord width)
{
gfxPoint point = mPt;
- point.x += xOffset;
+ bool rtl = mTextRun->IsRightToLeft();
+ bool verticalRun = mTextRun->IsVertical();
+
+ gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
+ inlineCoord += xOffset;
// offset is given in terms of left side of string
- if (mTextRun->IsRightToLeft()) {
+ if (rtl) {
// Bug 581092 - don't use rounded pixel width to advance to
// right-hand end of run, because this will cause different
// glyph positioning for LTR vs RTL drawing of the same
// glyph string on OS X and DWrite where textrun widths may
// involve fractional pixels.
gfxTextRun::Metrics textRunMetrics =
mTextRun->MeasureText(0,
mTextRun->GetLength(),
mDoMeasureBoundingBox ?
gfxFont::TIGHT_INK_EXTENTS :
gfxFont::LOOSE_INK_EXTENTS,
mThebes,
nullptr);
- point.x += textRunMetrics.mAdvanceWidth;
+ inlineCoord += textRunMetrics.mAdvanceWidth;
// old code was:
// point.x += width * mAppUnitsPerDevPixel;
// TODO: restore this if/when we move to fractional coords
// throughout the text layout process
}
uint32_t numRuns;
const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns);
@@ -3152,16 +3163,25 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
Point baselineOrigin =
Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit);
float advanceSum = 0;
mCtx->EnsureTarget();
for (uint32_t c = 0; c < numRuns; c++) {
gfxFont *font = runs[c].mFont;
+
+ bool verticalFont =
+ runs[c].mOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ const float& baselineOriginInline =
+ verticalFont ? baselineOrigin.y : baselineOrigin.x;
+ const float& baselineOriginBlock =
+ verticalFont ? baselineOrigin.x : baselineOrigin.y;
+
uint32_t endRun = 0;
if (c + 1 < numRuns) {
endRun = runs[c + 1].mCharacterOffset;
} else {
endRun = mTextRun->GetLength();
}
const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs();
@@ -3169,70 +3189,100 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
RefPtr<ScaledFont> scaledFont =
gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font);
if (!scaledFont) {
// This can occur when something switched DirectWrite off.
return;
}
+ AutoRestoreTransform sidewaysRestore;
+ if (runs[c].mOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) {
+ sidewaysRestore.Init(mCtx->mTarget);
+ // TODO: The baseline adjustment here is kinda ad-hoc; eventually
+ // perhaps we should check for horizontal and vertical baseline data
+ // in the font, and adjust accordingly.
+ // (The same will be true for HTML text layout.)
+ const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()->
+ GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
+ mCtx->mTarget->SetTransform(mCtx->mTarget->GetTransform().Copy().
+ PreTranslate(baselineOrigin). // translate origin for rotation
+ PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise
+ PreTranslate(-baselineOrigin). // undo the translation
+ PreTranslate(Point(0, metrics.emAscent - metrics.emDescent) / 2));
+ // and offset the (alphabetic) baseline of the
+ // horizontally-shaped text from the (centered)
+ // default baseline used for vertical
+ }
+
RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions();
GlyphBuffer buffer;
std::vector<Glyph> glyphBuf;
+ // TODO:
+ // This more-or-less duplicates the code found in gfxTextRun::Draw
+ // and the gfxFont methods that uses (Draw, DrawGlyphs, DrawOneGlyph);
+ // it would be nice to refactor and share that code.
for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) {
Glyph newGlyph;
+
+ float& inlinePos =
+ verticalFont ? newGlyph.mPosition.y : newGlyph.mPosition.x;
+ float& blockPos =
+ verticalFont ? newGlyph.mPosition.x : newGlyph.mPosition.y;
+
if (glyphs[i].IsSimpleGlyph()) {
newGlyph.mIndex = glyphs[i].GetSimpleGlyph();
- if (mTextRun->IsRightToLeft()) {
- newGlyph.mPosition.x = baselineOrigin.x - advanceSum -
+ if (rtl) {
+ inlinePos = baselineOriginInline - advanceSum -
glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
} else {
- newGlyph.mPosition.x = baselineOrigin.x + advanceSum;
+ inlinePos = baselineOriginInline + advanceSum;
}
- newGlyph.mPosition.y = baselineOrigin.y;
+ blockPos = baselineOriginBlock;
advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
glyphBuf.push_back(newGlyph);
continue;
}
if (!glyphs[i].GetGlyphCount()) {
continue;
}
- gfxTextRun::DetailedGlyph *detailedGlyphs =
- mTextRun->GetDetailedGlyphs(i);
+ const gfxTextRun::DetailedGlyph *d = mTextRun->GetDetailedGlyphs(i);
if (glyphs[i].IsMissing()) {
newGlyph.mIndex = 0;
- if (mTextRun->IsRightToLeft()) {
- newGlyph.mPosition.x = baselineOrigin.x - advanceSum -
- detailedGlyphs[0].mAdvance * devUnitsPerAppUnit;
+ if (rtl) {
+ inlinePos = baselineOriginInline - advanceSum -
+ d->mAdvance * devUnitsPerAppUnit;
} else {
- newGlyph.mPosition.x = baselineOrigin.x + advanceSum;
+ inlinePos = baselineOriginInline + advanceSum;
}
- newGlyph.mPosition.y = baselineOrigin.y;
- advanceSum += detailedGlyphs[0].mAdvance * devUnitsPerAppUnit;
+ blockPos = baselineOriginBlock;
+ advanceSum += d->mAdvance * devUnitsPerAppUnit;
glyphBuf.push_back(newGlyph);
continue;
}
- for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) {
- newGlyph.mIndex = detailedGlyphs[c].mGlyphID;
- if (mTextRun->IsRightToLeft()) {
- newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit -
- advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit;
+ for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++, d++) {
+ newGlyph.mIndex = d->mGlyphID;
+ if (rtl) {
+ inlinePos = baselineOriginInline - advanceSum -
+ d->mAdvance * devUnitsPerAppUnit;
} else {
- newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum;
+ inlinePos = baselineOriginInline + advanceSum;
}
- newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit;
+ inlinePos += d->mXOffset * devUnitsPerAppUnit;
+ blockPos = baselineOriginBlock + d->mYOffset * devUnitsPerAppUnit;
glyphBuf.push_back(newGlyph);
- advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit;
+ advanceSum += d->mAdvance * devUnitsPerAppUnit;
}
}
if (!glyphBuf.size()) {
// This may happen for glyph runs for a 0 size font.
continue;
}
@@ -3297,16 +3347,19 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
CanvasRenderingContext2D::TextDrawOperation mOp;
// context state
ContextState *mState;
// union of bounding boxes of all runs, needed for shadows
gfxRect mBoundingBox;
+ // flags to use when creating textrun, based on CSS style
+ uint32_t mTextRunFlags;
+
// true iff the bounding box should be measured
bool mDoMeasureBoundingBox;
};
nsresult
CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
float aX,
float aY,
@@ -3336,19 +3389,20 @@ CanvasRenderingContext2D::DrawOrMeasureT
// replace all the whitespace characters with U+0020 SPACE
nsAutoString textToDraw(aRawText);
TextReplaceWhitespaceCharacters(textToDraw);
// for now, default to ltr if not in doc
bool isRTL = false;
+ nsRefPtr<nsStyleContext> canvasStyle;
if (mCanvasElement && mCanvasElement->IsInDoc()) {
// try to find the closest context
- nsRefPtr<nsStyleContext> canvasStyle =
+ canvasStyle =
nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
nullptr,
presShell);
if (!canvasStyle) {
return NS_ERROR_FAILURE;
}
isRTL = canvasStyle->StyleVisibility()->mDirection ==
@@ -3373,16 +3427,24 @@ CanvasRenderingContext2D::DrawOrMeasureT
const ContextState &state = CurrentState();
// This is only needed to know if we can know the drawing bounding box easily.
bool doCalculateBounds = NeedToCalculateBounds();
CanvasBidiProcessor processor;
+ // If we don't have a style context, we can't set up vertical-text flags
+ // (for now, at least; perhaps we need new Canvas API to control this).
+ processor.mTextRunFlags = canvasStyle ?
+ nsLayoutUtils::GetTextRunFlagsForStyle(canvasStyle,
+ canvasStyle->StyleFont(),
+ canvasStyle->StyleText(),
+ 0) : 0;
+
GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
processor.mPt = gfxPoint(aX, aY);
processor.mThebes =
new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget());
// If we don't have a target then we don't have a transform. A target won't
// be needed in the case where we're measuring the text size. This allows
// to avoid creating a target if it's only being used to measure text sizes.
@@ -3439,17 +3501,20 @@ CanvasRenderingContext2D::DrawOrMeasureT
anchorX = 1;
}
processor.mPt.x -= anchorX * totalWidth;
// offset pt.y based on text baseline
processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
const gfxFont::Metrics& fontMetrics =
- processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal); // XXX vertical?
+ processor.mFontgrp->GetFirstValidFont()->GetMetrics(
+ ((processor.mTextRunFlags & gfxTextRunFactory::TEXT_ORIENT_MASK) ==
+ gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL)
+ ? gfxFont::eHorizontal : gfxFont::eVertical);
gfxFloat anchorY;
switch (state.textBaseline)
{
case TextBaseline::HANGING:
// fall through; best we can do with the information available
case TextBaseline::TOP:
--- a/gfx/2d/Helpers.h
+++ b/gfx/2d/Helpers.h
@@ -9,16 +9,20 @@
#include "2D.h"
namespace mozilla {
namespace gfx {
class AutoRestoreTransform
{
public:
+ AutoRestoreTransform()
+ {
+ }
+
explicit AutoRestoreTransform(DrawTarget *aTarget)
: mDrawTarget(aTarget),
mOldTransform(aTarget->GetTransform())
{
}
void Init(DrawTarget *aTarget)
{