#include "SkGlyph.h"
#include "SkMaskFilter.h"
#include "SkMaskGamma.h"
+#include "SkMatrix22.h"
#include "SkReadBuffer.h"
#include "SkWriteBuffer.h"
#include "SkPathEffect.h"
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());
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);
}
fFace = fFaceRec->fFace;
- // A is the total matrix.
- SkMatrix A;
- fRec.getSingleMatrix(&A);
-
- 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);
+ fRec.computeMatrices(SkScalerContextRec::kFull_PreMatrixScale, &fScale, &fMatrix22Scalar);
+ fMatrix22Scalar.setSkewX(-fMatrix22Scalar.getSkewX());
+ fMatrix22Scalar.setSkewY(-fMatrix22Scalar.getSkewY());
+
+ fScaleX = SkScalarToFixed(fScale.fX);
+ fScaleY = SkScalarToFixed(fScale.fY);
fMatrix22.xx = SkScalarToFixed(fMatrix22Scalar.getScaleX());
fMatrix22.xy = SkScalarToFixed(fMatrix22Scalar.getSkewX());
fMatrix22.yx = SkScalarToFixed(fMatrix22Scalar.getSkewY());
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);
// 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);
-
- // G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0).
- SkMatrix G;
- SkComputeGivensRotation(h, &G);
+ 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;
- // 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) {
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() {