8233006: freetype incorrectly adjusts advances when emboldening rotated glyphs

Reviewed-by: serb, jdv
This commit is contained in:
Phil Race 2020-04-20 13:50:16 -07:00
parent 62a2354299
commit 9ad39392db
3 changed files with 238 additions and 15 deletions

View File

@ -50,7 +50,6 @@
#define FloatToFTFixed(f) (FT_Fixed)((f) * (float)(ftFixed1))
#define FTFixedToFloat(x) ((x) / (float)(ftFixed1))
#define FT26Dot6ToFloat(x) ((x) / ((float) (1<<6)))
#define FT26Dot6ToInt(x) (((int)(x)) >> 6)
typedef struct {
/* Important note:
@ -296,6 +295,71 @@ static void setInterpreterVersion(FT_Library library) {
#endif
}
/*
* FT_GlyphSlot_Embolden (ftsynth.c) uses FT_MulFix(upem, y_scale) / 24
* I prefer something a little less bold, so using 32 instead of 24.
*/
#define BOLD_DIVISOR (32)
#define BOLD_FACTOR(units_per_EM, y_scale) \
((FT_MulFix(units_per_EM, y_scale) / BOLD_DIVISOR ))
#define BOLD_MODIFIER(units_per_EM, y_scale) \
(context->doBold ? BOLD_FACTOR(units_per_EM, y_scale) : 0)
static void GlyphSlot_Embolden(FT_GlyphSlot slot, FT_Matrix transform) {
FT_Pos extra = 0;
/*
* Does it make sense to embolden an empty image, such as SPACE ?
* We'll say no. A fixed width font might be the one case, but
* nothing in freetype made provision for this. And freetype would also
* have adjusted the metrics of zero advance glyphs (we won't, see below).
*/
if (!slot ||
slot->format != FT_GLYPH_FORMAT_OUTLINE ||
slot->metrics.width == 0 ||
slot->metrics.height == 0)
{
return;
}
extra = BOLD_FACTOR(slot->face->units_per_EM,
slot->face->size->metrics.y_scale);
/*
* It should not matter that the outline is rotated already,
* since we are applying the strength equally in X and Y.
* If that changes, then it might.
*/
FT_Outline_Embolden(&slot->outline, extra);
slot->metrics.width += extra;
slot->metrics.height += extra;
// Some glyphs are meant to be used as marks or diacritics, so
// have a shape but do not have an advance.
// Let's not adjust the metrics of any glyph that is zero advance.
if (slot->linearHoriAdvance == 0) {
return;
}
if (slot->advance.x) {
slot->advance.x += FT_MulFix(extra, transform.xx);
}
if (slot->advance.y) {
slot->advance.y += FT_MulFix(extra, transform.yx);
}
// The following need to be adjusted but no rotation
// linear advance is in 16.16 format, extra is 26.6
slot->linearHoriAdvance += extra << 10;
// these are pixel values stored in 26.6 format.
slot->metrics.horiAdvance += extra;
slot->metrics.vertAdvance += extra;
slot->metrics.horiBearingY += extra;
}
/*
* Class: sun_font_FreetypeFontScaler
* Method: initNativeScaler
@ -523,13 +587,6 @@ static int setupFTContext(JNIEnv *env,
// using same values as for the transformation matrix
#define OBLIQUE_MODIFIER(y) (context->doItalize ? ((y)*FT_MATRIX_OBLIQUE_XY/FT_MATRIX_ONE) : 0)
/* FT_GlyphSlot_Embolden (ftsynth.c) uses FT_MulFix(units_per_EM, y_scale) / 24
* strength value when glyph format is FT_GLYPH_FORMAT_OUTLINE. This value has
* been taken from libfreetype version 2.6 and remain valid at least up to
* 2.9.1. */
#define BOLD_MODIFIER(units_per_EM, y_scale) \
(context->doBold ? FT_MulFix(units_per_EM, y_scale) / 24 : 0)
/*
* Class: sun_font_FreetypeFontScaler
* Method: getFontMetricsNative
@ -902,7 +959,7 @@ static jlong
/* apply styles */
if (context->doBold) { /* if bold style */
FT_GlyphSlot_Embolden(ftglyph);
GlyphSlot_Embolden(ftglyph, context->transform);
}
/* generate bitmap if it is not done yet
@ -973,13 +1030,11 @@ static jlong
(float) - (advh * FTFixedToFloat(context->transform.yx));
} else {
if (!ftglyph->advance.y) {
glyphInfo->advanceX =
(float) FT26Dot6ToInt(ftglyph->advance.x);
glyphInfo->advanceX = FT26Dot6ToFloat(ftglyph->advance.x);
glyphInfo->advanceY = 0;
} else if (!ftglyph->advance.x) {
glyphInfo->advanceX = 0;
glyphInfo->advanceY =
(float) FT26Dot6ToInt(-ftglyph->advance.y);
glyphInfo->advanceY = FT26Dot6ToFloat(-ftglyph->advance.y);
} else {
glyphInfo->advanceX = FT26Dot6ToFloat(ftglyph->advance.x);
glyphInfo->advanceY = FT26Dot6ToFloat(-ftglyph->advance.y);
@ -1153,7 +1208,7 @@ static FT_Outline* getFTOutline(JNIEnv* env, jobject font2D,
/* apply styles */
if (context->doBold) { /* if bold style */
FT_GlyphSlot_Embolden(ftglyph);
GlyphSlot_Embolden(ftglyph, context->transform);
}
FT_Outline_Translate(&ftglyph->outline,

View File

@ -0,0 +1,168 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test RotatedSyntheticBoldTest
* @bug 8233006
* @summary This test verifies that rotated synthetically bolded fonts
* do not have a wandering baseline
* @run main RotatedSyntheticBoldTest
*
* Note this is designed to be run headless. The creation of the UI
* is meant to be run outside the harness as an visualisaton aid to
* debugging any failure.
*/
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RotatedSyntheticBoldTest extends JPanel {
static String TEXT = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLM";
static int SZ = 1000;
static Font font;
public static void main(String[] args) throws Exception {
if (args.length > 1) {
if (args[0].equals("-family")) {
font = new Font(args[1], Font.BOLD, 20);
} else if (args[0].equals("-file")) {
font = Font.createFont(Font.TRUETYPE_FONT, new File(args[1]));
} else {
font = new Font(Font.DIALOG, Font.BOLD, 20);
}
System.out.println("Using " + font);
createUI();
} else {
doTest();
}
}
static void createUI() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Synthetic Text Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new RotatedSyntheticBoldTest());
frame.setSize(SZ, SZ);
frame.setVisible(true);
});
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
AffineTransform tx = g2d.getTransform();
int x = 40, y = 0;
for (int i=0; i<360; i+=10) {
g2d.translate(SZ/2, SZ/2);
g2d.rotate((double)i/360.0 * Math.PI*2.0);
g2d.setFont(font);
g2d.setColor(Color.BLUE);
g2d.drawString(TEXT, x, y);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, TEXT);
Rectangle2D r = gv.getVisualBounds();
FontMetrics fm = g2d.getFontMetrics();
if (r.getHeight() > 1.1 * fm.getHeight()) {
System.out.println("FAIL : r= " + r + " hgt=" + fm.getHeight());
}
g2d.setColor(Color.RED);
g2d.translate(x, y);
g2d.draw(r);
g2d.setTransform(tx);
}
}
static void test(Graphics2D g2d, Font font) {
int x = 40, y = 0;
g2d.setFont(font);
g2d.setColor(Color.BLUE);
g2d.drawString(TEXT, x, y);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, TEXT);
Rectangle2D r = gv.getVisualBounds();
FontMetrics fm = g2d.getFontMetrics();
if (r.getHeight() > 1.2 * fm.getHeight()) {
System.out.println("FAIL : " + r);
}
g2d.setColor(Color.RED);
g2d.translate(x, y);
g2d.draw(r);
}
static void doTest() {
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] families = ge.getAvailableFontFamilyNames();
BufferedImage bi =
new BufferedImage(SZ, SZ, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bi.createGraphics();
g2d.rotate( Math.PI/4.0);
FontRenderContext frc = g2d.getFontRenderContext();
boolean failed = false;
for (String s : families) {
Font font = new Font(s, Font.BOLD, 20);
g2d.setFont(font);
GlyphVector gv = font.createGlyphVector(frc, TEXT);
Rectangle2D r = gv.getVisualBounds();
FontMetrics fm = g2d.getFontMetrics();
if (r.getHeight() > 1.2 * fm.getHeight()) {
failed = true;
System.out.println("FAIL : r= " + r + " hgt=" + fm.getHeight() +
" font=" + font);
}
}
if (failed) {
throw new RuntimeException("test failed");
}
}
}

View File

@ -23,7 +23,7 @@
/*
* @test RotatedTextTest
* @bug 8203485
* @bug 8203485 8233006
* @summary This test verifies that rotated text preserves the width.
* @run main RotatedTextTest
*/