Factor text size device mapping in SkScalerContext.
authorbungeman <bungeman@google.com>
Fri, 5 Dec 2014 20:26:44 +0000 (12:26 -0800)
committerCommit bot <commit-bot@chromium.org>
Fri, 5 Dec 2014 20:26:44 +0000 (12:26 -0800)
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

src/core/SkScalerContext.cpp
src/core/SkScalerContext.h
src/ports/SkFontHost_FreeType.cpp
src/ports/SkFontHost_win.cpp
src/ports/SkScalerContext_win_dw.cpp

index b7409ffe9ecbf1e7aace9bcf86ff16d011d7a4ec..4d22fbec62c154671d62ca69a64a1920da3f5702 100644 (file)
@@ -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());
 
index b78efef9c23727049eaf55e7c321b7d10c3ddc72..88b52860fb88503fe69e7a64db7cc66dcbcb92cf 100644 (file)
@@ -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);
 
index d5b79855281cb05ee5edda36fc9ba75d3e2c5d08..94af9ec28515440fe417426691ec33fc486b6b8f 100644 (file)
@@ -817,56 +817,12 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface,
     }
     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());
index 7c0e3ffc252b842d437d825f141e2617e480a40f..5ea55bd7a43b5f5399e003c426359e5afbfcb927 100755 (executable)
@@ -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);
index f3f371c878aba8e06b6954280d742c8479d7adb5..1e10e174c0bb8596171dcecfa8a9e54a4bfc89fa 100644 (file)
@@ -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);
-
-    // 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) {
@@ -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() {