Factor text size device mapping in SkScalerContext.
All of our font back-ends use the text size in some way other than simple concatentation with the current matrix. The code here finds the full device matrix and then decomposes it to extract the text size. FreeType, GDI, and DirectWrite use the text size as the pre-hint scale. CoreText will not scale color emoji above the requested text size, and certain features like 'trak' are performed on the text size. Review URL: https://codereview.chromium.org/748883005
This commit is contained in:
parent
03d3bb00bb
commit
5f14c5e038
@ -15,6 +15,7 @@
|
||||
#include "SkGlyph.h"
|
||||
#include "SkMaskFilter.h"
|
||||
#include "SkMaskGamma.h"
|
||||
#include "SkMatrix22.h"
|
||||
#include "SkReadBuffer.h"
|
||||
#include "SkWriteBuffer.h"
|
||||
#include "SkPathEffect.h"
|
||||
@ -722,6 +723,91 @@ void SkScalerContextRec::getSingleMatrixWithoutTextSize(SkMatrix* m) const {
|
||||
m->postConcat(deviceMatrix);
|
||||
}
|
||||
|
||||
void SkScalerContextRec::computeMatrices(PreMatrixScale preMatrixScale, SkVector* s, SkMatrix* sA,
|
||||
SkMatrix* GsA, SkMatrix* G_inv, SkMatrix* A_out)
|
||||
{
|
||||
// A is the 'total' matrix.
|
||||
SkMatrix A;
|
||||
this->getSingleMatrix(&A);
|
||||
|
||||
// The caller may find the 'total' matrix useful when dealing directly with EM sizes.
|
||||
if (A_out) {
|
||||
*A_out = A;
|
||||
}
|
||||
|
||||
// GA is the matrix A with rotation removed.
|
||||
SkMatrix GA;
|
||||
bool skewedOrFlipped = A.getSkewX() || A.getSkewY() || A.getScaleX() < 0 || A.getScaleY() < 0;
|
||||
if (skewedOrFlipped) {
|
||||
// h is where A maps the horizontal baseline.
|
||||
SkPoint h = SkPoint::Make(SK_Scalar1, 0);
|
||||
A.mapPoints(&h, 1);
|
||||
|
||||
// G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0).
|
||||
SkMatrix G;
|
||||
SkComputeGivensRotation(h, &G);
|
||||
|
||||
GA = G;
|
||||
GA.preConcat(A);
|
||||
|
||||
// The 'remainingRotation' is G inverse, which is fairly simple since G is 2x2 rotational.
|
||||
if (G_inv) {
|
||||
G_inv->setAll(
|
||||
G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX),
|
||||
-G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY),
|
||||
G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2));
|
||||
}
|
||||
} else {
|
||||
GA = A;
|
||||
if (G_inv) {
|
||||
G_inv->reset();
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, given GA, create s.
|
||||
switch (preMatrixScale) {
|
||||
case kFull_PreMatrixScale:
|
||||
s->fX = SkScalarAbs(GA.get(SkMatrix::kMScaleX));
|
||||
s->fY = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
|
||||
break;
|
||||
case kVertical_PreMatrixScale: {
|
||||
SkScalar yScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
|
||||
s->fX = yScale;
|
||||
s->fY = yScale;
|
||||
break;
|
||||
}
|
||||
case kVerticalInteger_PreMatrixScale: {
|
||||
SkScalar realYScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
|
||||
SkScalar intYScale = SkScalarRoundToScalar(realYScale);
|
||||
if (intYScale == 0) {
|
||||
intYScale = SK_Scalar1;
|
||||
}
|
||||
s->fX = intYScale;
|
||||
s->fY = intYScale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The 'remaining' matrix sA is the total matrix A without the scale.
|
||||
if (!skewedOrFlipped && kFull_PreMatrixScale == preMatrixScale) {
|
||||
// If GA == A and kFull_PreMatrixScale, sA is identity.
|
||||
sA->reset();
|
||||
} else {
|
||||
// TODO: If GA == A and kVertical_PreMatrixScale, sA.scaleY is SK_Scalar1.
|
||||
// TODO: If GA == A and kVertical_PreMatrixScale and A.scaleX == A.scaleY, sA is identity.
|
||||
// TODO: like kVertical_PreMatrixScale, kVerticalInteger_PreMatrixScale with int scales.
|
||||
*sA = A;
|
||||
sA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY));
|
||||
}
|
||||
|
||||
// The 'remainingWithoutRotation' matrix GsA is the non-rotational part of A without the scale.
|
||||
if (GsA) {
|
||||
*GsA = GA;
|
||||
// G is rotational so reorders with the scale.
|
||||
GsA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY));
|
||||
}
|
||||
}
|
||||
|
||||
SkAxisAlignment SkComputeAxisAlignmentForHText(const SkMatrix& matrix) {
|
||||
SkASSERT(!matrix.hasPerspective());
|
||||
|
||||
|
@ -86,6 +86,50 @@ struct SkScalerContextRec {
|
||||
void getSingleMatrix(SkMatrix*) const;
|
||||
void getSingleMatrixWithoutTextSize(SkMatrix*) const;
|
||||
|
||||
/** The kind of scale which will be applied by the underlying port (pre-matrix). */
|
||||
enum PreMatrixScale {
|
||||
kFull_PreMatrixScale, // The underlying port can apply both x and y scale.
|
||||
kVertical_PreMatrixScale, // The underlying port can only apply a y scale.
|
||||
kVerticalInteger_PreMatrixScale // The underlying port can only apply an integer y scale.
|
||||
};
|
||||
/**
|
||||
* Compute useful matrices for use with sizing in underlying libraries.
|
||||
*
|
||||
* There are two kinds of text size, a 'requested/logical size' which is like asking for size
|
||||
* '12' and a 'real' size which is the size after the matrix is applied. The matrices produced
|
||||
* by this method are based on the 'real' size. This method effectively finds the total device
|
||||
* matrix and decomposes it in various ways.
|
||||
*
|
||||
* The most useful decomposition is into 'scale' and 'remaining'. The 'scale' is applied first
|
||||
* and then the 'remaining' to fully apply the total matrix. This decomposition is useful when
|
||||
* the text size ('scale') may have meaning apart from the total matrix. This is true when
|
||||
* hinting, and sometimes true for other properties as well.
|
||||
*
|
||||
* The second (optional) decomposition is of 'remaining' into a non-rotational part
|
||||
* 'remainingWithoutRotation' and a rotational part 'remainingRotation'. The 'scale' is applied
|
||||
* first, then 'remainingWithoutRotation', then 'remainingRotation' to fully apply the total
|
||||
* matrix. This decomposition is helpful when only horizontal metrics can be trusted, so the
|
||||
* 'scale' and 'remainingWithoutRotation' will be handled by the underlying library, but
|
||||
* the final rotation 'remainingRotation' will be handled manually.
|
||||
*
|
||||
* The 'total' matrix is also (optionally) available. This is useful in cases where the
|
||||
* underlying library will not be used, often when working directly with font data.
|
||||
*
|
||||
* The parameters 'scale' and 'remaining' are required, the other pointers may be NULL.
|
||||
*
|
||||
* @param preMatrixScale the kind of scale to extract from the total matrix.
|
||||
* @param scale the scale extracted from the total matrix (both values positive).
|
||||
* @param remaining apply after scale to apply the total matrix.
|
||||
* @param remainingWithoutRotation apply after scale to apply the total matrix sans rotation.
|
||||
* @param remainingRotation apply after remainingWithoutRotation to apply the total matrix.
|
||||
* @param total the total matrix.
|
||||
*/
|
||||
void computeMatrices(PreMatrixScale preMatrixScale,
|
||||
SkVector* scale, SkMatrix* remaining,
|
||||
SkMatrix* remainingWithoutRotation = NULL,
|
||||
SkMatrix* remainingRotation = NULL,
|
||||
SkMatrix* total = NULL);
|
||||
|
||||
inline SkPaint::Hinting getHinting() const;
|
||||
inline void setHinting(SkPaint::Hinting);
|
||||
|
||||
|
@ -817,56 +817,12 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface,
|
||||
}
|
||||
fFace = fFaceRec->fFace;
|
||||
|
||||
// A is the total matrix.
|
||||
SkMatrix A;
|
||||
fRec.getSingleMatrix(&A);
|
||||
fRec.computeMatrices(SkScalerContextRec::kFull_PreMatrixScale, &fScale, &fMatrix22Scalar);
|
||||
fMatrix22Scalar.setSkewX(-fMatrix22Scalar.getSkewX());
|
||||
fMatrix22Scalar.setSkewY(-fMatrix22Scalar.getSkewY());
|
||||
|
||||
SkScalar sx = A.getScaleX();
|
||||
SkScalar sy = A.getScaleY();
|
||||
fMatrix22Scalar.reset();
|
||||
|
||||
// In GDI, the hinter is aware of the current transformation
|
||||
// (the transform is in some sense applied before/with the hinting).
|
||||
// The bytecode can then test if it is rotated or stretched and decide
|
||||
// to apply instructions or not.
|
||||
//
|
||||
// FreeType, however, always does the transformation strictly after hinting.
|
||||
// It just sets 'rotated' and 'stretched' to false and only applies the
|
||||
// size before hinting.
|
||||
//
|
||||
// Also, FreeType respects the head::flags::IntegerScaling flag,
|
||||
// (although this is patched out on most major distros)
|
||||
// so it is critical to get the size correct on the request.
|
||||
//
|
||||
// This also gets us the actual closest size on bitmap fonts as well.
|
||||
if (A.getSkewX() || A.getSkewY() || sx < 0 || sy < 0) {
|
||||
// h is where A maps the horizontal baseline.
|
||||
SkPoint h = SkPoint::Make(SK_Scalar1, 0);
|
||||
A.mapPoints(&h, 1);
|
||||
|
||||
// G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0).
|
||||
SkMatrix G;
|
||||
SkComputeGivensRotation(h, &G);
|
||||
|
||||
// GA is the matrix A with rotation removed.
|
||||
SkMatrix GA(G);
|
||||
GA.preConcat(A);
|
||||
|
||||
sx = SkScalarAbs(GA.get(SkMatrix::kMScaleX));
|
||||
sy = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
|
||||
|
||||
// sA is the total matrix A without the text scale.
|
||||
SkMatrix sA(A);
|
||||
sA.preScale(SkScalarInvert(sx), SkScalarInvert(sy)); //remove text size
|
||||
|
||||
fMatrix22Scalar.setScaleX(sA.getScaleX());
|
||||
fMatrix22Scalar.setSkewX(-sA.getSkewX());
|
||||
fMatrix22Scalar.setSkewY(-sA.getSkewY());
|
||||
fMatrix22Scalar.setScaleY(sA.getScaleY());
|
||||
}
|
||||
fScale.set(sx, sy);
|
||||
fScaleX = SkScalarToFixed(sx);
|
||||
fScaleY = SkScalarToFixed(sy);
|
||||
fScaleX = SkScalarToFixed(fScale.fX);
|
||||
fScaleY = SkScalarToFixed(fScale.fY);
|
||||
fMatrix22.xx = SkScalarToFixed(fMatrix22Scalar.getScaleX());
|
||||
fMatrix22.xy = SkScalarToFixed(fMatrix22Scalar.getSkewX());
|
||||
fMatrix22.yx = SkScalarToFixed(fMatrix22Scalar.getSkewY());
|
||||
|
@ -617,55 +617,27 @@ SkScalerContext_GDI::SkScalerContext_GDI(SkTypeface* rawTypeface,
|
||||
SetGraphicsMode(fDDC, GM_ADVANCED);
|
||||
SetBkMode(fDDC, TRANSPARENT);
|
||||
|
||||
SkPoint h = SkPoint::Make(SK_Scalar1, 0);
|
||||
// A is the total matrix.
|
||||
SkMatrix A;
|
||||
fRec.getSingleMatrix(&A);
|
||||
A.mapPoints(&h, 1);
|
||||
|
||||
// G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0).
|
||||
SkMatrix G;
|
||||
SkComputeGivensRotation(h, &G);
|
||||
|
||||
// GA is the matrix A with rotation removed.
|
||||
SkMatrix GA(G);
|
||||
GA.preConcat(A);
|
||||
|
||||
// realTextSize is the actual device size we want (as opposed to the size the user requested).
|
||||
// gdiTextSize is the size we request from GDI.
|
||||
// If the scale is negative, this means the matrix will do the flip anyway.
|
||||
SkScalar realTextSize = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
|
||||
SkScalar gdiTextSize = SkScalarRoundToScalar(realTextSize);
|
||||
if (gdiTextSize == 0) {
|
||||
gdiTextSize = SK_Scalar1;
|
||||
}
|
||||
|
||||
// When not hinting, remove only the gdiTextSize scale which will be applied by GDI.
|
||||
// When GDI hinting, remove the entire Y scale to prevent 'subpixel' metrics.
|
||||
SkScalar scale = (fRec.getHinting() == SkPaint::kNo_Hinting ||
|
||||
fRec.getHinting() == SkPaint::kSlight_Hinting)
|
||||
? SkScalarInvert(gdiTextSize)
|
||||
: SkScalarInvert(realTextSize);
|
||||
|
||||
// sA is the total matrix A without the textSize (so GDI knows the text size separately).
|
||||
// When this matrix is used with GetGlyphOutline, no further processing is needed.
|
||||
SkMatrix sA(A);
|
||||
sA.preScale(scale, scale); //remove text size
|
||||
|
||||
// GsA is the non-rotational part of A without the text height scale.
|
||||
// This is what is used to find the magnitude of advances.
|
||||
SkMatrix GsA(GA);
|
||||
GsA.preScale(scale, scale); //remove text size, G is rotational so reorders with the scale.
|
||||
// When not hinting, remove only the gdiTextSize scale which will be applied by GDI.
|
||||
SkScalerContextRec::PreMatrixScale scaleConstraints =
|
||||
(fRec.getHinting() == SkPaint::kNo_Hinting || fRec.getHinting() == SkPaint::kSlight_Hinting)
|
||||
? SkScalerContextRec::kVerticalInteger_PreMatrixScale
|
||||
: SkScalerContextRec::kVertical_PreMatrixScale;
|
||||
SkVector scale;
|
||||
SkMatrix sA;
|
||||
SkMatrix GsA;
|
||||
SkMatrix A;
|
||||
fRec.computeMatrices(scaleConstraints, &scale, &sA, &GsA, &fG_inv, &A);
|
||||
|
||||
fGsA.eM11 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleX));
|
||||
fGsA.eM12 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewY)); // This should be ~0.
|
||||
fGsA.eM21 = SkScalarToFIXED(-GsA.get(SkMatrix::kMSkewX));
|
||||
fGsA.eM22 = SkScalarToFIXED(GsA.get(SkMatrix::kMScaleY));
|
||||
|
||||
// fG_inv is G inverse, which is fairly simple since G is 2x2 rotational.
|
||||
fG_inv.setAll(G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX),
|
||||
-G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY),
|
||||
G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2));
|
||||
SkScalar gdiTextSize = scale.fY;
|
||||
if (gdiTextSize == 0) {
|
||||
gdiTextSize = SK_Scalar1;
|
||||
}
|
||||
|
||||
LOGFONT lf = typeface->fLogFont;
|
||||
lf.lfHeight = -SkScalarTruncToInt(gdiTextSize);
|
||||
|
@ -210,26 +210,29 @@ SkScalerContext_DW::SkScalerContext_DW(DWriteFontTypeface* typeface,
|
||||
// Also, rotated glyphs should have the same absolute advance widths as
|
||||
// horizontal glyphs and the subpixel flag should not affect glyph shapes.
|
||||
|
||||
// A is the total matrix.
|
||||
SkMatrix A;
|
||||
fRec.getSingleMatrix(&A);
|
||||
SkVector scale;
|
||||
SkMatrix GsA;
|
||||
fRec.computeMatrices(SkScalerContextRec::kVertical_PreMatrixScale,
|
||||
&scale, &fSkXform, &GsA, &fG_inv);
|
||||
|
||||
// h is where A maps the horizontal baseline.
|
||||
SkPoint h = SkPoint::Make(SK_Scalar1, 0);
|
||||
A.mapPoints(&h, 1);
|
||||
fXform.m11 = SkScalarToFloat(fSkXform.getScaleX());
|
||||
fXform.m12 = SkScalarToFloat(fSkXform.getSkewY());
|
||||
fXform.m21 = SkScalarToFloat(fSkXform.getSkewX());
|
||||
fXform.m22 = SkScalarToFloat(fSkXform.getScaleY());
|
||||
fXform.dx = 0;
|
||||
fXform.dy = 0;
|
||||
|
||||
// G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0).
|
||||
SkMatrix G;
|
||||
SkComputeGivensRotation(h, &G);
|
||||
|
||||
// GA is the matrix A with rotation removed.
|
||||
SkMatrix GA(G);
|
||||
GA.preConcat(A);
|
||||
fGsA.m11 = SkScalarToFloat(GsA.get(SkMatrix::kMScaleX));
|
||||
fGsA.m12 = SkScalarToFloat(GsA.get(SkMatrix::kMSkewY)); // This should be ~0.
|
||||
fGsA.m21 = SkScalarToFloat(GsA.get(SkMatrix::kMSkewX));
|
||||
fGsA.m22 = SkScalarToFloat(GsA.get(SkMatrix::kMScaleY));
|
||||
fGsA.dx = 0;
|
||||
fGsA.dy = 0;
|
||||
|
||||
// realTextSize is the actual device size we want (as opposed to the size the user requested).
|
||||
// gdiTextSize is the size we request when GDI compatible.
|
||||
// If the scale is negative, this means the matrix will do the flip anyway.
|
||||
SkScalar realTextSize = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
|
||||
const SkScalar realTextSize = scale.fY;
|
||||
// Due to floating point math, the lower bits are suspect. Round carefully.
|
||||
SkScalar gdiTextSize = SkScalarRoundToScalar(realTextSize * 64.0f) / 64.0f;
|
||||
if (gdiTextSize == 0) {
|
||||
@ -300,36 +303,6 @@ SkScalerContext_DW::SkScalerContext_DW(DWriteFontTypeface* typeface,
|
||||
fTextSizeMeasure = realTextSize;
|
||||
fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
|
||||
}
|
||||
|
||||
// Remove the realTextSize, as that is the text height scale currently in A.
|
||||
SkScalar scale = SkScalarInvert(realTextSize);
|
||||
|
||||
// fSkXform is the total matrix A without the text height scale.
|
||||
fSkXform = A;
|
||||
fSkXform.preScale(scale, scale); //remove the text height scale.
|
||||
|
||||
fXform.m11 = SkScalarToFloat(fSkXform.getScaleX());
|
||||
fXform.m12 = SkScalarToFloat(fSkXform.getSkewY());
|
||||
fXform.m21 = SkScalarToFloat(fSkXform.getSkewX());
|
||||
fXform.m22 = SkScalarToFloat(fSkXform.getScaleY());
|
||||
fXform.dx = 0;
|
||||
fXform.dy = 0;
|
||||
|
||||
// GsA is the non-rotational part of A without the text height scale.
|
||||
SkMatrix GsA(GA);
|
||||
GsA.preScale(scale, scale); //remove text height scale, G is rotational so reorders with scale.
|
||||
|
||||
fGsA.m11 = SkScalarToFloat(GsA.get(SkMatrix::kMScaleX));
|
||||
fGsA.m12 = SkScalarToFloat(GsA.get(SkMatrix::kMSkewY)); // This should be ~0.
|
||||
fGsA.m21 = SkScalarToFloat(GsA.get(SkMatrix::kMSkewX));
|
||||
fGsA.m22 = SkScalarToFloat(GsA.get(SkMatrix::kMScaleY));
|
||||
fGsA.dx = 0;
|
||||
fGsA.dy = 0;
|
||||
|
||||
// fG_inv is G inverse, which is fairly simple since G is 2x2 rotational.
|
||||
fG_inv.setAll(G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX),
|
||||
-G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY),
|
||||
G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2));
|
||||
}
|
||||
|
||||
SkScalerContext_DW::~SkScalerContext_DW() {
|
||||
|
Loading…
Reference in New Issue
Block a user