Modified: trunk/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp (293031 => 293032)
--- trunk/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp 2022-04-19 19:24:15 UTC (rev 293031)
+++ trunk/Source/WebCore/platform/graphics/coretext/FontCascadeCoreText.cpp 2022-04-19 20:08:37 UTC (rev 293032)
@@ -60,44 +60,57 @@
return result;
}
-AffineTransform computeOverallTextMatrix(const Font& font)
+AffineTransform computeBaseOverallTextMatrix(const std::optional<AffineTransform>& syntheticOblique)
{
- auto& platformData = font.platformData();
AffineTransform result;
- if (!platformData.isColorBitmapFont())
- result = CTFontGetMatrix(platformData.ctFont());
+
+ // This is a Y-flip, because text's coordinate system is increasing-Y-goes-up,
+ // but WebKit's coordinate system is increasing-Y-goes-down.
result.setB(-result.b());
result.setD(-result.d());
+
+ if (syntheticOblique)
+ result = *syntheticOblique * result;
+
+ return result;
+}
+
+AffineTransform computeOverallTextMatrix(const Font& font)
+{
+ std::optional<AffineTransform> syntheticOblique;
+ auto& platformData = font.platformData();
if (platformData.syntheticOblique()) {
float obliqueSkew = tanf(deg2rad(FontCascade::syntheticObliqueAngle()));
if (platformData.orientation() == FontOrientation::Vertical) {
if (font.isTextOrientationFallback())
- result = AffineTransform(1, obliqueSkew, 0, 1, 0, 0) * result;
+ syntheticOblique = AffineTransform(1, obliqueSkew, 0, 1, 0, 0);
else
- result = AffineTransform(1, -obliqueSkew, 0, 1, 0, 0) * result;
+ syntheticOblique = AffineTransform(1, -obliqueSkew, 0, 1, 0, 0);
} else
- result = AffineTransform(1, 0, -obliqueSkew, 1, 0, 0) * result;
+ syntheticOblique = AffineTransform(1, 0, -obliqueSkew, 1, 0, 0);
}
- // We're emulating the behavior of CGContextSetTextPosition() by adding constant amounts to each glyph's position
- // (see fillVectorWithHorizontalGlyphPositions() and fillVectorWithVerticalGlyphPositions()).
- // CGContextSetTextPosition() has the side effect of clobbering the E and F fields of the text matrix,
- // so we do that explicitly here.
- result.setE(0);
- result.setF(0);
- return result;
+ return computeBaseOverallTextMatrix(syntheticOblique);
}
-AffineTransform computeVerticalTextMatrix(const Font& font, const AffineTransform& previousTextMatrix)
+AffineTransform computeBaseVerticalTextMatrix(const AffineTransform& previousTextMatrix)
{
- ASSERT_UNUSED(font, font.platformData().orientation() == FontOrientation::Vertical);
// The translation here ("e" and "f" fields) are irrelevant, because
// this matrix is inverted in fillVectorWithVerticalGlyphPositions to place the glyphs in the CTM's coordinate system.
// All we're trying to do here is rotate the text matrix so glyphs appear visually upright.
// We have to include the previous text matrix because it includes things like synthetic oblique.
+ //
+ // Because this is a left-multiply, we're taking the points from user coordinates, which are increasing-Y-goes-down,
+ // and we're rotating the points to the left in that coordinate system, to put them physically upright.
return rotateLeftTransform() * previousTextMatrix;
}
+AffineTransform computeVerticalTextMatrix(const Font& font, const AffineTransform& previousTextMatrix)
+{
+ ASSERT_UNUSED(font, font.platformData().orientation() == FontOrientation::Vertical);
+ return computeBaseVerticalTextMatrix(previousTextMatrix);
+}
+
#if !PLATFORM(WIN)
// Confusingly, even when CGFontRenderingGetFontSmoothingDisabled() returns true, CGContextSetShouldSmoothFonts() still impacts text
@@ -138,15 +151,132 @@
}
}
-static void fillVectorWithVerticalGlyphPositions(Vector<CGPoint, 256>& positions, CGContextRef context, const CGSize* translations, const CGSize* advances, unsigned count, const FloatPoint& point, float ascentDelta)
+static void fillVectorWithVerticalGlyphPositions(Vector<CGPoint, 256>& positions, const CGSize translations[], const CGSize advances[], unsigned count, const FloatPoint& point, float ascentDelta, CGAffineTransform textMatrix)
{
- // Keep this in sync as the inverse of `DrawGlyphsRecorder::recordDrawGlyphs`.
- CGAffineTransform transform = CGAffineTransformInvert(CGContextGetTextMatrix(context));
+ // Keep this function in sync as the inverse of `DrawGlyphsRecorder::recordDrawGlyphs`.
+ // It's important to realize we're dealing with 4 coordinate systems here:
+ // 1. Physical coordinate system. This is what the user sees.
+ // 2. User coordinate system. This is the coordinate system of just the CTM. For vertical text, this is just like the normal WebKit increasing-Y-down
+ // coordinate system, except we're rotated right, so logical right is physical down. (We do this so logical inline progression proceeds in the
+ // logically increasing-X dimension, just as it would if we weren't doing vertical stuff.)
+ // 3. Text coordinate system. This is the coordinate of the text matrix concatenated with the CTM. For vertical text, this is rotated such that
+ // increasing Y goes physically up, and increasing X goes physically right. The control points in font contours are authored according to this
+ // increasing-Y-up coordinate system.
+ // 4. Synthetic-oblique-less text coordinate system. This would be identical to the text coordinate system if synthetic oblique was not in effect. This
+ // is useful because, when we're moving glyphs around, we usually don't want to consider synthetic oblique. Instead, synthetic oblique is just
+ // a rasterization-time effect, and not used for glyph positioning/layout.
+ // FIXME: Does this mean that synthetic oblique should always be applied on the result of rotateLeftTransform() in computeVerticalTextMatrix(),
+ // rather than the other way around?
+
+ // Imagine an vertical upright glyph:
+ // +--------------------------+
+ // | ___ ___ |
+ // | \ \ / / |
+ // | \ \ / / |
+ // | \ \ / / |
+ // | \ V / |
+ // | \ / |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | |___| |
+ // | |
+ // +--------------------------+
+ //
+ // The ideographic baseline lies in the center of the glyph, and the alphabetic baseline lies to the left of it:
+ // | |
+ // +---|--------|-------------+
+ // | | ___ | ___ |
+ // | | \ \ | / / |
+ // | | \ \ | / / |
+ // | | \ \|/ / |
+ // | | \ | / |
+ // | | \ | / |
+ // | | | | | |
+ // | | | | | |
+ // | | | | | |
+ // | | | | | |
+ // | | |_|_| |
+ // | | | |
+ // +---|--------|-------------+
+ // | | <== ideographic baseline
+ // | <== alphabetic baseline
+ //
+ // The glyph itself has a local origin, which is the position sent to Core Text. The control points of the contours are defined relative to this point.
+ // +--------------------------+
+ // | ___ ___ |
+ // | \ \ / / |
+ // | \ \ / / |
+ // | \ \ / / |
+ // | \ V / |
+ // | \ / |
+ // | | | |
+ // | | | |
+ // | | | |
+ // * <= here | | |
+ // | |___| |
+ // | |
+ // +--------------------------+
+ //
+ // Now, for horizontal text, we can do the simple thing of just:
+ // 1. Place the pen at a position. Record this position as the local origin of the first glyph
+ // 2. Move the pen according to the glyph's advance
+ // 3. Record a new position as the local origin of the next glyph
+ // 4. Go to 2
+ // However, for vertical text, we can't get away with this because the glyph origins are not on the baseline.
+ // This is what the "vertical translation for a glyph" is. It contains this vector:
+ // +---A--------B-------------+
+ // | / |
+ // | / |
+ // | -- |
+ // | / |
+ // | / |
+ // | -- |
+ // | / |
+ // | / |
+ // ||_ |
+ // C |
+ // | |
+ // | |
+ // +--------------------------+
+ // It points from the pen position on the ideographic baseline to the glyph's local origin. This is (usually) physically
+ // down-and-to-the-left. Core Text gives us these vectors in the text coordinate system, and so therefore these vectors (usually) have
+ // both X and Y components negative.
+
+ // The goal of this function is to produce glyph origins in the text coordinate system, because that's what Core Text expects. The "advances"
+ // and "point" parameters to this function are in the user coordinate system. The "translations" parameter is in the "synthetic-oblique-less
+ // text coordinate system."
+
+ // CGContextGetTextMatrix() transforms points from text coordinates to user coordinates. However, we're trying to produce text coordinates from
+ // user coordinates, so we invert it.
+ CGAffineTransform transform = CGAffineTransformInvert(textMatrix);
+
+ // Because the "vertical translation for a glyph" vector starts at the ideographic baseline (the point B in the above diagram), we have to
+ // adjust the pen position to start there. WebKit's text routines start out using the alphabetic baseline (point A in the diagram above) so we
+ // adjust the start position here, which has the effect of shifting the whole run altogether.
+ //
+ // ascentDelta is (usually) a negative number, and represents the distance between the ideographic baseline to the alphabetic baseline.
+ // In user coordinates, we want to adjust the Y component to make a horizontal physical change. And, because the user coordinate system is
+ // logically increasing-Y-down, we add the value, which is negative, to move us logically up, which is physically to the right. Now our position
+ // is at the point labeled B in the above diagram, in user coordinates.
auto position = CGPointMake(point.x(), point.y() + ascentDelta);
+
for (unsigned i = 0; i < count; ++i) {
- CGSize translation = CGSizeApplyAffineTransform(translations[i], rotateLeftTransform());
- positions[i] = CGPointApplyAffineTransform(CGPointMake(position.x - translation.width, position.y + translation.height), transform);
+ // The "translations" parameter is in the "synthetic-oblique-less text coordinate system" and we want to add it to the position in the user
+ // coordinate system. Luckily, the text matrix (or, at least the version of the text matrix that doesn't include synthetic oblique) does exactly
+ // this. So, we just create the synthetic-oblique-less text matrix, and run the translation through it. This gives us the translation in user
+ // coordinates.
+ auto translation = CGSizeApplyAffineTransform(translations[i], computeBaseVerticalTextMatrix(computeBaseOverallTextMatrix(std::nullopt)));
+
+ // Now we can add the position in user coordinates with the translation in user coordinates.
+ auto positionInUserCoordinates = CGPointMake(position.x + translation.width, position.y + translation.height);
+
+ // And then put it back in font coordinates for submission to Core Text. Yay!
+ positions[i] = CGPointApplyAffineTransform(positionInUserCoordinates, transform);
+
+ // Advance the position to the next position in user coordinates. Both the advances and position are in user coordinates.
position.x += advances[i].width;
position.y += advances[i].height;
}
@@ -166,7 +296,7 @@
CTFontGetVerticalTranslationsForGlyphs(platformData.ctFont(), glyphs, translations.data(), count);
auto ascentDelta = font.fontMetrics().floatAscent(IdeographicBaseline) - font.fontMetrics().floatAscent();
- fillVectorWithVerticalGlyphPositions(positions, context, translations.data(), advances, count, point, ascentDelta);
+ fillVectorWithVerticalGlyphPositions(positions, translations.data(), advances, count, point, ascentDelta, CGContextGetTextMatrix(context));
CTFontDrawGlyphs(platformData.ctFont(), glyphs, positions.data(), count, context);
} else {
fillVectorWithHorizontalGlyphPositions(positions, context, advances, count, point);