8262470: Printed GlyphVector outline with low DPI has bad quality on Windows
Reviewed-by: serb, psadhukhan
This commit is contained in:
parent
3997c99e0a
commit
0228734902
src/java.desktop/windows
test/jdk/java/awt/print/PathPrecisionScaleFactor
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2021, 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
|
||||
@ -85,6 +85,8 @@ final class WPathGraphics extends PathGraphics {
|
||||
private static final float MIN_DEVICE_LINEWIDTH = 1.2f;
|
||||
private static final float MAX_THINLINE_INCHES = 0.014f;
|
||||
|
||||
private static final float precisionScale = 1000.0f;
|
||||
|
||||
/* Note that preferGDITextLayout implies useGDITextLayout.
|
||||
* "prefer" is used to override cases where would otherwise
|
||||
* choose not to use it. Note that non-layout factors may
|
||||
@ -1719,6 +1721,11 @@ final class WPathGraphics extends PathGraphics {
|
||||
}
|
||||
}
|
||||
|
||||
private void precisionScaleUp(float[] values, int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
values[i] = values[i] * precisionScale;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a Java2D {@code PathIterator} instance,
|
||||
@ -1743,6 +1750,7 @@ final class WPathGraphics extends PathGraphics {
|
||||
}
|
||||
wPrinterJob.setPolyFillMode(polyFillRule);
|
||||
|
||||
wPrinterJob.scaleTransform(1.0f / precisionScale);
|
||||
wPrinterJob.beginPath();
|
||||
|
||||
while (pathIter.isDone() == false) {
|
||||
@ -1750,16 +1758,19 @@ final class WPathGraphics extends PathGraphics {
|
||||
|
||||
switch (segmentType) {
|
||||
case PathIterator.SEG_MOVETO:
|
||||
precisionScaleUp(segment, 2);
|
||||
wPrinterJob.moveTo(segment[0], segment[1]);
|
||||
break;
|
||||
|
||||
case PathIterator.SEG_LINETO:
|
||||
precisionScaleUp(segment, 2);
|
||||
wPrinterJob.lineTo(segment[0], segment[1]);
|
||||
break;
|
||||
|
||||
/* Convert the quad path to a bezier.
|
||||
*/
|
||||
case PathIterator.SEG_QUADTO:
|
||||
precisionScaleUp(segment, 4);
|
||||
int lastX = wPrinterJob.getPenX();
|
||||
int lastY = wPrinterJob.getPenY();
|
||||
float c1x = lastX + (segment[0] - lastX) * 2 / 3;
|
||||
@ -1772,6 +1783,7 @@ final class WPathGraphics extends PathGraphics {
|
||||
break;
|
||||
|
||||
case PathIterator.SEG_CUBICTO:
|
||||
precisionScaleUp(segment, 6);
|
||||
wPrinterJob.polyBezierTo(segment[0], segment[1],
|
||||
segment[2], segment[3],
|
||||
segment[4], segment[5]);
|
||||
@ -1787,6 +1799,7 @@ final class WPathGraphics extends PathGraphics {
|
||||
}
|
||||
|
||||
wPrinterJob.endPath();
|
||||
wPrinterJob.restoreTransform();
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2021, 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
|
||||
@ -362,6 +362,9 @@ public final class WPrinterJob extends RasterPrinterJob
|
||||
|
||||
private java.awt.peer.ComponentPeer dialogOwnerPeer = null;
|
||||
|
||||
private int graphicsMode;
|
||||
private double[] worldTransform = new double[6];
|
||||
|
||||
/* Static Initializations */
|
||||
|
||||
static {
|
||||
@ -960,6 +963,17 @@ public final class WPrinterJob extends RasterPrinterJob
|
||||
endPath(getPrintDC());
|
||||
}
|
||||
|
||||
protected void scaleTransform(float scale) {
|
||||
graphicsMode = setAdvancedGraphicsMode();
|
||||
getWorldTransform(worldTransform);
|
||||
scale(scale, scale);
|
||||
}
|
||||
|
||||
protected void restoreTransform() {
|
||||
setWorldTransform(worldTransform);
|
||||
setGraphicsMode(graphicsMode);
|
||||
}
|
||||
|
||||
protected void closeFigure() {
|
||||
closeFigure(getPrintDC());
|
||||
}
|
||||
@ -995,6 +1009,44 @@ public final class WPrinterJob extends RasterPrinterJob
|
||||
setPolyFillMode(getPrintDC(), fillRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the GDI graphics mode to {@code GM_ADVANCED}.
|
||||
*/
|
||||
private int setAdvancedGraphicsMode() {
|
||||
return setAdvancedGraphicsMode(getPrintDC());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the GDI graphics mode.
|
||||
* The {@code mode} should
|
||||
* be one of the following Windows constants:
|
||||
* {@code GM_COMPATIBLE} or {@code GM_ADVANCED}.
|
||||
*/
|
||||
private void setGraphicsMode(int mode) {
|
||||
setGraphicsMode(getPrintDC(), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale the GDI World Transform.
|
||||
*/
|
||||
private void scale(double scaleX, double scaleY) {
|
||||
scale(getPrintDC(), scaleX, scaleY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GDI World Transform.
|
||||
*/
|
||||
private void getWorldTransform(double[] transform) {
|
||||
getWorldTransform(getPrintDC(), transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the GDI World Transform.
|
||||
*/
|
||||
private void setWorldTransform(double[] transform) {
|
||||
setWorldTransform(getPrintDC(), transform);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a Window's solid brush for the color specified
|
||||
* by {@code (red, green, blue)}. Once the brush
|
||||
@ -1470,6 +1522,39 @@ public final class WPrinterJob extends RasterPrinterJob
|
||||
*/
|
||||
protected native void setPolyFillMode(long printDC, int fillRule);
|
||||
|
||||
/**
|
||||
* Set the GDI graphics mode to {@code GM_ADVANCED}
|
||||
* into the device context {@code printDC}.
|
||||
*/
|
||||
protected native int setAdvancedGraphicsMode(long printDC);
|
||||
|
||||
/**
|
||||
* Set the GDI graphics {@code mode}
|
||||
* into the device context {@code printDC}.
|
||||
* The {@code mode} should
|
||||
* be one of the following Windows constants:
|
||||
* {@code GM_COMPATIBLE} or {@code GM_ADVANCED}.
|
||||
*/
|
||||
protected native void setGraphicsMode(long printDC, int mode);
|
||||
|
||||
/**
|
||||
* Scale the GDI World Transform
|
||||
* of the device context {@code printDC}.
|
||||
*/
|
||||
protected native void scale(long printDC, double scaleX, double scaleY);
|
||||
|
||||
/**
|
||||
* Get the GDI World Transform
|
||||
* from the device context {@code printDC}.
|
||||
*/
|
||||
protected native void getWorldTransform(long printDC, double[] transform);
|
||||
|
||||
/**
|
||||
* Set the GDI World Transform
|
||||
* into the device context {@code printDC}.
|
||||
*/
|
||||
protected native void setWorldTransform(long printDC, double[] transform);
|
||||
|
||||
/**
|
||||
* Create a Window's solid brush for the color specified
|
||||
* by {@code (red, green, blue)}. Once the brush
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2021, 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
|
||||
@ -1931,6 +1931,117 @@ JNIEXPORT void JNICALL Java_sun_awt_windows_WPrinterJob_setPolyFillMode
|
||||
CATCH_BAD_ALLOC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_windows_WPrinterJob
|
||||
* Method: setAdvancedGraphicsMode
|
||||
* Signature: (J)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_sun_awt_windows_WPrinterJob_setAdvancedGraphicsMode
|
||||
(JNIEnv *env, jobject self, jlong printDC) {
|
||||
TRY;
|
||||
|
||||
int oldGraphicsMode = ::SetGraphicsMode((HDC)printDC, GM_ADVANCED);
|
||||
DASSERT(oldGraphicsMode != 0);
|
||||
return (jint) oldGraphicsMode;
|
||||
|
||||
CATCH_BAD_ALLOC_RET(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_windows_WPrinterJob
|
||||
* Method: setGraphicsMode
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_awt_windows_WPrinterJob_setGraphicsMode
|
||||
(JNIEnv *env, jobject self, jlong printDC, jint mode) {
|
||||
TRY;
|
||||
|
||||
int oldGraphicsMode = ::SetGraphicsMode((HDC)printDC, mode);
|
||||
DASSERT(oldGraphicsMode != 0);
|
||||
|
||||
CATCH_BAD_ALLOC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_windows_WPrinterJob
|
||||
* Method: scale
|
||||
* Signature: (JDD)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_awt_windows_WPrinterJob_scale
|
||||
(JNIEnv *env, jobject self, jlong printDC, jdouble scaleX, jdouble scaleY) {
|
||||
TRY;
|
||||
|
||||
XFORM xForm;
|
||||
|
||||
xForm.eM11 = (FLOAT) scaleX;
|
||||
xForm.eM12 = (FLOAT) 0;
|
||||
xForm.eM21 = (FLOAT) 0;
|
||||
xForm.eM22 = (FLOAT) scaleY;
|
||||
xForm.eDx = (FLOAT) 0;
|
||||
xForm.eDy = (FLOAT) 0;
|
||||
|
||||
BOOL result = ::ModifyWorldTransform((HDC)printDC, &xForm, MWT_RIGHTMULTIPLY);
|
||||
DASSERT(result);
|
||||
|
||||
CATCH_BAD_ALLOC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_windows_WPrinterJob
|
||||
* Method: getWorldTransform
|
||||
* Signature: (J[D)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_awt_windows_WPrinterJob_getWorldTransform
|
||||
(JNIEnv* env, jobject self, jlong printDC, jdoubleArray transform) {
|
||||
TRY;
|
||||
|
||||
double elems[6];
|
||||
XFORM xForm;
|
||||
|
||||
BOOL result = ::GetWorldTransform((HDC)printDC, &xForm);
|
||||
DASSERT(result);
|
||||
|
||||
elems[0] = (double) xForm.eM11;
|
||||
elems[1] = (double) xForm.eM12;
|
||||
elems[2] = (double) xForm.eM21;
|
||||
elems[3] = (double) xForm.eM22;
|
||||
elems[4] = (double) xForm.eDx;
|
||||
elems[5] = (double) xForm.eDy;
|
||||
|
||||
env->SetDoubleArrayRegion(transform, 0, 6, elems);
|
||||
|
||||
CATCH_BAD_ALLOC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_windows_WPrinterJob
|
||||
* Method: setWorldTransform
|
||||
* Signature: (J[D)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_sun_awt_windows_WPrinterJob_setWorldTransform
|
||||
(JNIEnv* env, jobject self, jlong printDC, jdoubleArray transform) {
|
||||
TRY;
|
||||
|
||||
double *elems;
|
||||
XFORM xForm;
|
||||
|
||||
elems = env->GetDoubleArrayElements(transform, 0);
|
||||
|
||||
xForm.eM11 = (FLOAT) elems[0];
|
||||
xForm.eM12 = (FLOAT) elems[1];
|
||||
xForm.eM21 = (FLOAT) elems[2];
|
||||
xForm.eM22 = (FLOAT) elems[3];
|
||||
xForm.eDx = (FLOAT) elems[4];
|
||||
xForm.eDy = (FLOAT) elems[5];
|
||||
|
||||
env->ReleaseDoubleArrayElements(transform, elems, 0);
|
||||
|
||||
BOOL result = ::SetWorldTransform((HDC)printDC, &xForm);
|
||||
DASSERT(result);
|
||||
|
||||
CATCH_BAD_ALLOC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: sun_awt_windows_WPrinterJob
|
||||
* Method: selectSolidBrush
|
||||
|
265
test/jdk/java/awt/print/PathPrecisionScaleFactor/PathPrecisionScaleFactorShapeTest.java
Normal file
265
test/jdk/java/awt/print/PathPrecisionScaleFactor/PathPrecisionScaleFactorShapeTest.java
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, BELLSOFT. 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
|
||||
* @bug 8262470
|
||||
* @requires (os.family == "windows")
|
||||
* @summary Check thay shapes are properly painted with the precision scale factor
|
||||
* @run main/othervm/manual PathPrecisionScaleFactorShapeTest
|
||||
*/
|
||||
|
||||
import javax.print.PrintServiceLookup;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.QuadCurve2D;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.print.PageFormat;
|
||||
import java.awt.print.Printable;
|
||||
import java.awt.print.PrinterException;
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PathPrecisionScaleFactorShapeTest {
|
||||
|
||||
private static final String DESCRIPTION =
|
||||
" 1. Setup 'Microsoft Print to PDF' printer on Windows.\n" +
|
||||
" 2. Press Print button to print shapes with labels to PDF.\n" +
|
||||
" 3. Choose 'Microsoft Print to PDF' on the print dialog and press OK\n" +
|
||||
" 3. Open the PDF file and check that following shapes with labels are printed:\n" +
|
||||
" Line\n" +
|
||||
" Line2D\n" +
|
||||
" QuadCurve2D\n" +
|
||||
" CubicCurve2D\n" +
|
||||
" 4. If so, press PASS button, otherwise press FAIL button.\n";
|
||||
|
||||
private static final CountDownLatch testEndedSignal = new CountDownLatch(1);
|
||||
private static final int testTimeout = 300000;
|
||||
private static volatile String testFailureMsg;
|
||||
private static volatile boolean testPassed;
|
||||
private static volatile boolean testFinished;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
SwingUtilities.invokeLater(() -> createAndShowTestDialog());
|
||||
|
||||
try {
|
||||
if (!testEndedSignal.await(testTimeout, TimeUnit.MILLISECONDS)) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Test timeout '%d ms' elapsed.", testTimeout));
|
||||
}
|
||||
if (!testPassed) {
|
||||
String failureMsg = testFailureMsg;
|
||||
if ((failureMsg != null) && (!failureMsg.trim().isEmpty())) {
|
||||
throw new RuntimeException(failureMsg);
|
||||
} else {
|
||||
throw new RuntimeException("Test failed.");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException(ie);
|
||||
} finally {
|
||||
testFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void pass() {
|
||||
testPassed = true;
|
||||
testEndedSignal.countDown();
|
||||
}
|
||||
|
||||
private static void fail(String failureMsg) {
|
||||
testFailureMsg = failureMsg;
|
||||
testPassed = false;
|
||||
testEndedSignal.countDown();
|
||||
}
|
||||
|
||||
private static String convertMillisToTimeStr(int millis) {
|
||||
if (millis < 0) {
|
||||
return "00:00:00";
|
||||
}
|
||||
int hours = millis / 3600000;
|
||||
int minutes = (millis - hours * 3600000) / 60000;
|
||||
int seconds = (millis - hours * 3600000 - minutes * 60000) / 1000;
|
||||
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
private static void createAndShowTestDialog() {
|
||||
|
||||
final JDialog dialog = new JDialog();
|
||||
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
dialog.dispose();
|
||||
fail("Main dialog was closed.");
|
||||
}
|
||||
});
|
||||
|
||||
final JLabel testTimeoutLabel = new JLabel(String.format(
|
||||
"Test timeout: %s", convertMillisToTimeStr(testTimeout)));
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final Timer timer = new Timer(0, null);
|
||||
timer.setDelay(1000);
|
||||
timer.addActionListener((e) -> {
|
||||
int leftTime = testTimeout - (int) (System.currentTimeMillis() - startTime);
|
||||
if ((leftTime < 0) || testFinished) {
|
||||
timer.stop();
|
||||
dialog.dispose();
|
||||
}
|
||||
testTimeoutLabel.setText(String.format(
|
||||
"Test timeout: %s", convertMillisToTimeStr(leftTime)));
|
||||
});
|
||||
timer.start();
|
||||
|
||||
JTextArea textArea = new JTextArea(DESCRIPTION);
|
||||
textArea.setEditable(false);
|
||||
|
||||
final JButton testButton = new JButton("Print");
|
||||
final JButton passButton = new JButton("PASS");
|
||||
final JButton failButton = new JButton("FAIL");
|
||||
testButton.addActionListener((e) -> {
|
||||
testButton.setEnabled(false);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
doTest();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
passButton.setEnabled(true);
|
||||
failButton.setEnabled(true);
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
dialog.dispose();
|
||||
fail("Exception occurred in a thread executing the test.");
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
passButton.setEnabled(false);
|
||||
passButton.addActionListener((e) -> {
|
||||
dialog.dispose();
|
||||
pass();
|
||||
});
|
||||
failButton.setEnabled(false);
|
||||
failButton.addActionListener((e) -> {
|
||||
dialog.dispose();
|
||||
fail("TitledBorder label is cut off");
|
||||
});
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
JPanel labelPanel = new JPanel(new FlowLayout());
|
||||
labelPanel.add(testTimeoutLabel);
|
||||
mainPanel.add(labelPanel, BorderLayout.NORTH);
|
||||
mainPanel.add(textArea, BorderLayout.CENTER);
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout());
|
||||
buttonPanel.add(testButton);
|
||||
buttonPanel.add(passButton);
|
||||
buttonPanel.add(failButton);
|
||||
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
dialog.add(mainPanel);
|
||||
|
||||
dialog.pack();
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
private static void doTest() throws Exception {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
try {
|
||||
new PathPrecisionScaleFactorPrintable();
|
||||
} catch (PrinterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class PathPrecisionScaleFactorPrintable implements Printable {
|
||||
|
||||
PathPrecisionScaleFactorPrintable() throws PrinterException {
|
||||
PrinterJob job = PrinterJob.getPrinterJob();
|
||||
job.setPrintService(PrintServiceLookup.lookupDefaultPrintService());
|
||||
job.setPrintable(this);
|
||||
|
||||
if (job.printDialog()) {
|
||||
job.print();
|
||||
} else {
|
||||
throw new RuntimeException("Printing was canceled!");
|
||||
}
|
||||
}
|
||||
|
||||
void paint(Graphics2D g) {
|
||||
|
||||
Rectangle clip = g.getClipBounds();
|
||||
|
||||
int x = (int) clip.getX() + 10;
|
||||
int y = (int) clip.getY() + 20;
|
||||
int dy = ((int) clip.getHeight() - y) / 8;
|
||||
|
||||
int l = 80;
|
||||
int sx = x + l + 10;
|
||||
|
||||
g.drawLine(x, y, x + l, y);
|
||||
drawGlyphVector(g, sx, y, "Line");
|
||||
|
||||
y+= dy;
|
||||
g.draw(new Line2D.Float(x, y, x + l, y));
|
||||
drawGlyphVector(g, sx, y, "Line2D");
|
||||
|
||||
y+= dy;
|
||||
g.draw(new QuadCurve2D.Float(x, y, x + l / 2, y + l / 2, x + l, y));
|
||||
drawGlyphVector(g, sx, y, "QuadCurve2D");
|
||||
|
||||
y+= dy;
|
||||
g.draw(new CubicCurve2D.Float(x, y, x + l / 3, y + l / 3, x + 2 * l / 3, y - l / 3, x + l, y));
|
||||
drawGlyphVector(g, sx, y, "CubicCurve2D");
|
||||
}
|
||||
|
||||
private static void drawGlyphVector(Graphics2D g, int x, int y, String text) {
|
||||
|
||||
Font font = new Font("Times New Roman", Font.PLAIN, 8);
|
||||
g.setFont(font);
|
||||
FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, true);
|
||||
|
||||
GlyphVector gv = font.createGlyphVector(frc, text);
|
||||
g.fill(gv.getOutline(x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int print(Graphics graphics, PageFormat pageFormat, int index) {
|
||||
if (index == 0) {
|
||||
paint((Graphics2D) graphics);
|
||||
return PAGE_EXISTS;
|
||||
} else {
|
||||
return NO_SUCH_PAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
254
test/jdk/java/awt/print/PathPrecisionScaleFactor/PathPrecisionScaleFactorTextTest.java
Normal file
254
test/jdk/java/awt/print/PathPrecisionScaleFactor/PathPrecisionScaleFactorTextTest.java
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, BELLSOFT. 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
|
||||
* @bug 8262470
|
||||
* @requires (os.family == "windows")
|
||||
* @summary Check that a GlyphVector outline is printed with good quility on low dpi printers
|
||||
* @run main/othervm/manual PathPrecisionScaleFactorTextTest
|
||||
*/
|
||||
|
||||
import javax.print.PrintServiceLookup;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.print.PageFormat;
|
||||
import java.awt.print.Printable;
|
||||
import java.awt.print.PrinterException;
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PathPrecisionScaleFactorTextTest {
|
||||
|
||||
private static final String DESCRIPTION =
|
||||
" 1. Setup 'Microsoft Print to PDF' printer on Windows.\n" +
|
||||
" 2. Press Print button to print the text to PDF.\n" +
|
||||
" 3. Choose 'Microsoft Print to PDF' on the print dialog and press OK\n" +
|
||||
" Two strings should be printed.\n" +
|
||||
" The first line is printed using drawString() method\n" +
|
||||
" and the second line is printed using filling glyph vector outline.\n" +
|
||||
" 3. Open the PDF file, zoom in the text and check that chars on the second line\n" +
|
||||
" (especially 'a' and 's') are not distorted and have the similar quality\n" +
|
||||
" as on the first line.\n" +
|
||||
" 4. If so, press PASS button, otherwise press FAIL button.\n";
|
||||
|
||||
private static final CountDownLatch testEndedSignal = new CountDownLatch(1);
|
||||
private static final int testTimeout = 300000;
|
||||
private static volatile String testFailureMsg;
|
||||
private static volatile boolean testPassed;
|
||||
private static volatile boolean testFinished;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
SwingUtilities.invokeLater(() -> createAndShowTestDialog());
|
||||
|
||||
try {
|
||||
if (!testEndedSignal.await(testTimeout, TimeUnit.MILLISECONDS)) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Test timeout '%d ms' elapsed.", testTimeout));
|
||||
}
|
||||
if (!testPassed) {
|
||||
String failureMsg = testFailureMsg;
|
||||
if ((failureMsg != null) && (!failureMsg.trim().isEmpty())) {
|
||||
throw new RuntimeException(failureMsg);
|
||||
} else {
|
||||
throw new RuntimeException("Test failed.");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException(ie);
|
||||
} finally {
|
||||
testFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void pass() {
|
||||
testPassed = true;
|
||||
testEndedSignal.countDown();
|
||||
}
|
||||
|
||||
private static void fail(String failureMsg) {
|
||||
testFailureMsg = failureMsg;
|
||||
testPassed = false;
|
||||
testEndedSignal.countDown();
|
||||
}
|
||||
|
||||
private static String convertMillisToTimeStr(int millis) {
|
||||
if (millis < 0) {
|
||||
return "00:00:00";
|
||||
}
|
||||
int hours = millis / 3600000;
|
||||
int minutes = (millis - hours * 3600000) / 60000;
|
||||
int seconds = (millis - hours * 3600000 - minutes * 60000) / 1000;
|
||||
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
private static void createAndShowTestDialog() {
|
||||
|
||||
final JDialog dialog = new JDialog();
|
||||
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
dialog.dispose();
|
||||
fail("Main dialog was closed.");
|
||||
}
|
||||
});
|
||||
|
||||
final JLabel testTimeoutLabel = new JLabel(String.format(
|
||||
"Test timeout: %s", convertMillisToTimeStr(testTimeout)));
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final Timer timer = new Timer(0, null);
|
||||
timer.setDelay(1000);
|
||||
timer.addActionListener((e) -> {
|
||||
int leftTime = testTimeout - (int) (System.currentTimeMillis() - startTime);
|
||||
if ((leftTime < 0) || testFinished) {
|
||||
timer.stop();
|
||||
dialog.dispose();
|
||||
}
|
||||
testTimeoutLabel.setText(String.format(
|
||||
"Test timeout: %s", convertMillisToTimeStr(leftTime)));
|
||||
});
|
||||
timer.start();
|
||||
|
||||
JTextArea textArea = new JTextArea(DESCRIPTION);
|
||||
textArea.setEditable(false);
|
||||
|
||||
final JButton testButton = new JButton("Print");
|
||||
final JButton passButton = new JButton("PASS");
|
||||
final JButton failButton = new JButton("FAIL");
|
||||
testButton.addActionListener((e) -> {
|
||||
testButton.setEnabled(false);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
doTest();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
passButton.setEnabled(true);
|
||||
failButton.setEnabled(true);
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
dialog.dispose();
|
||||
fail("Exception occurred in a thread executing the test.");
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
passButton.setEnabled(false);
|
||||
passButton.addActionListener((e) -> {
|
||||
dialog.dispose();
|
||||
pass();
|
||||
});
|
||||
failButton.setEnabled(false);
|
||||
failButton.addActionListener((e) -> {
|
||||
dialog.dispose();
|
||||
fail("TitledBorder label is cut off");
|
||||
});
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
JPanel labelPanel = new JPanel(new FlowLayout());
|
||||
labelPanel.add(testTimeoutLabel);
|
||||
mainPanel.add(labelPanel, BorderLayout.NORTH);
|
||||
mainPanel.add(textArea, BorderLayout.CENTER);
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout());
|
||||
buttonPanel.add(testButton);
|
||||
buttonPanel.add(passButton);
|
||||
buttonPanel.add(failButton);
|
||||
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
dialog.add(mainPanel);
|
||||
|
||||
dialog.pack();
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
private static void doTest() throws Exception {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
try {
|
||||
new PathPrecisionScaleFactorPrintable();
|
||||
} catch (PrinterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class PathPrecisionScaleFactorPrintable implements Printable {
|
||||
|
||||
PathPrecisionScaleFactorPrintable() throws PrinterException {
|
||||
PrinterJob job = PrinterJob.getPrinterJob();
|
||||
job.setPrintService(PrintServiceLookup.lookupDefaultPrintService());
|
||||
job.setPrintable(this);
|
||||
|
||||
if (job.printDialog()) {
|
||||
job.print();
|
||||
} else {
|
||||
throw new RuntimeException("Printing was canceled!");
|
||||
}
|
||||
}
|
||||
|
||||
void paint(Graphics2D g) {
|
||||
|
||||
String text = "abcdefghijklmnopqrstuvwxyz";
|
||||
Font font = new Font("Times New Roman", Font.PLAIN, 8);
|
||||
drawText(g, font, text);
|
||||
}
|
||||
|
||||
private static void drawText(Graphics2D g, Font font, String text) {
|
||||
|
||||
g.setFont(font);
|
||||
FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, true);
|
||||
|
||||
Rectangle clip = g.getClipBounds();
|
||||
int cx = (int) clip.getCenterX();
|
||||
int cy = (int) clip.getCenterY();
|
||||
|
||||
FontMetrics metrics = g.getFontMetrics();
|
||||
int w = metrics.stringWidth(text);
|
||||
int h = metrics.getHeight();
|
||||
|
||||
int x = cx - w / 2;
|
||||
int y = cy - h / 2;
|
||||
|
||||
g.drawString(text + " [draw string]", x, y);
|
||||
GlyphVector gv = font.createGlyphVector(frc, text + " [glyph vector]");
|
||||
g.fill(gv.getOutline(x, y + h));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int print(Graphics graphics, PageFormat pageFormat, int index) {
|
||||
if (index == 0) {
|
||||
paint((Graphics2D) graphics);
|
||||
return PAGE_EXISTS;
|
||||
} else {
|
||||
return NO_SUCH_PAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user