blob: e9bbe83b8c945a66ee9dc9e063b4805bb21c5869 [file] [log] [blame]
/*
* Copyright (c) 2015, 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.
*/
/**
* Copyright (C) 2013-2014 IBM Corporation and Others. All Rights Reserved.
*/
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* This test runs against a test XML file. It opens the fonts and attempts
* to shape and layout glyphs.
* Note that the test is highly environment dependent- you must have
* the same versions of the same fonts available or the test will fail.
*
* It is similar to letest which is part of ICU.
* For reference, here are some reference items:
* ICU's test file:
* http://source.icu-project.org/repos/icu/icu/trunk/source/test/testdata/letest.xml
* ICU's readme for the similar test:
* http://source.icu-project.org/repos/icu/icu/trunk/source/test/letest/readme.html
*
* @bug 8054203
* @test
* @summary manual test of layout engine behavior. Takes an XML control file.
* @compile TestLayoutVsICU.java
* @author srl
* @run main/manual
*/
public class TestLayoutVsICU {
public static boolean OPT_DRAW = false;
public static boolean OPT_VERBOSE = false;
public static boolean OPT_FAILMISSING = false;
public static boolean OPT_NOTHROW= false; // if true - don't stop on failure
public static int docs = 0; // # docs processed
public static int skipped = 0; // cases skipped due to bad font
public static int total = 0; // cases processed
public static int bad = 0; // cases with errs
public static final String XML_LAYOUT_TESTS = "layout-tests"; // top level
public static final String XML_TEST_CASE = "test-case";
public static final String XML_TEST_FONT = "test-font";
public static final String XML_TEST_TEXT = "test-text";
public static final String XML_RESULT_GLYPHS = "result-glyphs";
public static final String XML_ID = "id";
public static final String XML_SCRIPT = "script";
public static final String XML_NAME = "name";
public static final String XML_VERSION = "version";
public static final String XML_CHECKSUM = "checksum";
public static final String XML_RESULT_INDICES = "result-indices";
public static final String XML_RESULT_POSITIONS = "result-positions";
/**
* @param args
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
System.out.println("Java " + System.getProperty("java.version") + " from " + System.getProperty("java.vendor"));
TestLayoutVsICU tlvi = null;
for(String arg : args) {
if(arg.equals("-d")) {
OPT_DRAW = true;
} else if(arg.equals("-n")) {
OPT_NOTHROW = true;
} else if(arg.equals("-v")) {
OPT_VERBOSE = true;
} else if(arg.equals("-f")) {
OPT_FAILMISSING = true;
} else {
if(tlvi == null) {
tlvi = new TestLayoutVsICU();
}
try {
tlvi.show(arg);
} finally {
if(OPT_VERBOSE) {
System.out.println("# done with " + arg);
}
}
}
}
if(tlvi == null) {
throw new IllegalArgumentException("No XML input. Usage: " + TestLayoutVsICU.class.getSimpleName() + " [-d][-v][-f] letest.xml ...");
} else {
System.out.println("\n\nRESULTS:\n");
System.out.println(skipped+"\tskipped due to missing font");
System.out.println(total+"\ttested of which:");
System.out.println(bad+"\twere bad");
if(bad>0) {
throw new InternalError("One or more failure(s)");
}
}
}
String id;
private void show(String arg) throws ParserConfigurationException, SAXException, IOException {
id = "<none>";
File xmlFile = new File(arg);
if(!xmlFile.exists()) {
throw new FileNotFoundException("Can't open input XML file " + arg);
}
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
if(OPT_VERBOSE) {
System.out.println("# Parsing " + xmlFile.getAbsolutePath());
}
Document doc = db.parse(xmlFile);
Element e = doc.getDocumentElement();
if(!XML_LAYOUT_TESTS.equals(e.getNodeName())) {
throw new IllegalArgumentException("Document " + xmlFile.getAbsolutePath() + " does not have <layout-tests> as its base");
}
NodeList testCases = e.getElementsByTagName(XML_TEST_CASE);
for(int caseNo=0;caseNo<testCases.getLength();caseNo++) {
final Node testCase = testCases.item(caseNo);
final Map<String,String> testCaseAttrs = attrs(testCase);
id = testCaseAttrs.get(XML_ID);
final String script = testCaseAttrs.get(XML_SCRIPT);
String testText = null;
Integer[] expectGlyphs = null;
Integer[] expectIndices = null;
Map<String,String> fontAttrs = null;
if(OPT_VERBOSE) {
System.out.println("#"+caseNo+" id="+id + ", script="+script);
}
NodeList children = testCase.getChildNodes();
for(int sub=0;sub<children.getLength();sub++) {
Node n = children.item(sub);
if(n.getNodeType()!=Node.ELEMENT_NODE) continue;
String nn = n.getNodeName();
if(nn.equals(XML_TEST_FONT)) {
fontAttrs = attrs(n);
} else if(nn.equals(XML_TEST_TEXT)) {
testText = n.getTextContent();
} else if(nn.equals(XML_RESULT_GLYPHS)) {
String hex = n.getTextContent();
expectGlyphs = parseHexArray(hex);
} else if(nn.equals(XML_RESULT_INDICES)) {
String hex = n.getTextContent();
expectIndices = parseHexArray(hex);
} else if(OPT_VERBOSE) {
System.out.println("Ignoring node " + nn);
}
}
if(fontAttrs == null) {
throw new IllegalArgumentException(id + " Missing node " + XML_TEST_FONT);
}
if(testText == null) {
throw new IllegalArgumentException(id + " Missing node " + XML_TEST_TEXT);
}
String fontName = fontAttrs.get(XML_NAME);
Font f = getFont(fontName, fontAttrs);
if(f==null) {
if(OPT_FAILMISSING) {
throw new MissingResourceException("Missing font, abort test", Font.class.getName(), fontName);
}
System.out.println("Skipping " + id + " because font is missing: " + fontName);
skipped++;
continue;
}
FontRenderContext frc = new FontRenderContext(null, true, true);
TextLayout tl = new TextLayout(testText,f,frc);
final List<GlyphVector> glyphs = new ArrayList<GlyphVector>();
Graphics2D myg2 = new Graphics2D(){
@Override
public void draw(Shape s) {
// TODO Auto-generated method stub
}
@Override
public boolean drawImage(Image img, AffineTransform xform,
ImageObserver obs) {
// TODO Auto-generated method stub
return false;
}
@Override
public void drawImage(BufferedImage img,
BufferedImageOp op, int x, int y) {
// TODO Auto-generated method stub
}
@Override
public void drawRenderedImage(RenderedImage img,
AffineTransform xform) {
// TODO Auto-generated method stub
}
@Override
public void drawRenderableImage(RenderableImage img,
AffineTransform xform) {
// TODO Auto-generated method stub
}
@Override
public void drawString(String str, int x, int y) {
// TODO Auto-generated method stub
}
@Override
public void drawString(String str, float x, float y) {
// TODO Auto-generated method stub
}
@Override
public void drawString(
AttributedCharacterIterator iterator, int x, int y) {
// TODO Auto-generated method stub
}
@Override
public void drawString(
AttributedCharacterIterator iterator, float x,
float y) {
// TODO Auto-generated method stub
}
@Override
public void drawGlyphVector(GlyphVector g, float x, float y) {
if(x!=0.0 || y!=0.0) {
throw new InternalError("x,y should be 0 but got " + x+","+y);
}
//System.err.println("dGV : " + g.toString() + " @ "+x+","+y);
glyphs.add(g);
}
@Override
public void fill(Shape s) {
// TODO Auto-generated method stub
}
@Override
public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
// TODO Auto-generated method stub
return false;
}
@Override
public GraphicsConfiguration getDeviceConfiguration() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setComposite(Composite comp) {
// TODO Auto-generated method stub
}
@Override
public void setPaint(Paint paint) {
// TODO Auto-generated method stub
}
@Override
public void setStroke(Stroke s) {
// TODO Auto-generated method stub
}
@Override
public void setRenderingHint(Key hintKey, Object hintValue) {
// TODO Auto-generated method stub
}
@Override
public Object getRenderingHint(Key hintKey) {
// TODO Auto-generated method stub
return null;
}
@Override
public void setRenderingHints(Map<?, ?> hints) {
// TODO Auto-generated method stub
}
@Override
public void addRenderingHints(Map<?, ?> hints) {
// TODO Auto-generated method stub
}
@Override
public RenderingHints getRenderingHints() {
// TODO Auto-generated method stub
return null;
}
@Override
public void translate(int x, int y) {
// TODO Auto-generated method stub
}
@Override
public void translate(double tx, double ty) {
// TODO Auto-generated method stub
}
@Override
public void rotate(double theta) {
// TODO Auto-generated method stub
}
@Override
public void rotate(double theta, double x, double y) {
// TODO Auto-generated method stub
}
@Override
public void scale(double sx, double sy) {
// TODO Auto-generated method stub
}
@Override
public void shear(double shx, double shy) {
// TODO Auto-generated method stub
}
@Override
public void transform(AffineTransform Tx) {
// TODO Auto-generated method stub
}
@Override
public void setTransform(AffineTransform Tx) {
// TODO Auto-generated method stub
}
@Override
public AffineTransform getTransform() {
// TODO Auto-generated method stub
return null;
}
@Override
public Paint getPaint() {
// TODO Auto-generated method stub
return null;
}
@Override
public Composite getComposite() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setBackground(Color color) {
// TODO Auto-generated method stub
}
@Override
public Color getBackground() {
// TODO Auto-generated method stub
return null;
}
@Override
public Stroke getStroke() {
// TODO Auto-generated method stub
return null;
}
@Override
public void clip(Shape s) {
// TODO Auto-generated method stub
}
@Override
public FontRenderContext getFontRenderContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public Graphics create() {
// TODO Auto-generated method stub
return null;
}
@Override
public Color getColor() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setColor(Color c) {
// TODO Auto-generated method stub
}
@Override
public void setPaintMode() {
// TODO Auto-generated method stub
}
@Override
public void setXORMode(Color c1) {
// TODO Auto-generated method stub
}
@Override
public Font getFont() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setFont(Font font) {
// TODO Auto-generated method stub
}
@Override
public FontMetrics getFontMetrics(Font f) {
// TODO Auto-generated method stub
return null;
}
@Override
public Rectangle getClipBounds() {
// TODO Auto-generated method stub
return null;
}
@Override
public void clipRect(int x, int y, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void setClip(int x, int y, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public Shape getClip() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setClip(Shape clip) {
// TODO Auto-generated method stub
}
@Override
public void copyArea(int x, int y, int width, int height,
int dx, int dy) {
// TODO Auto-generated method stub
}
@Override
public void drawLine(int x1, int y1, int x2, int y2) {
// TODO Auto-generated method stub
}
@Override
public void fillRect(int x, int y, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void clearRect(int x, int y, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void drawRoundRect(int x, int y, int width,
int height, int arcWidth, int arcHeight) {
// TODO Auto-generated method stub
}
@Override
public void fillRoundRect(int x, int y, int width,
int height, int arcWidth, int arcHeight) {
// TODO Auto-generated method stub
}
@Override
public void drawOval(int x, int y, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void fillOval(int x, int y, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void drawArc(int x, int y, int width, int height,
int startAngle, int arcAngle) {
// TODO Auto-generated method stub
}
@Override
public void fillArc(int x, int y, int width, int height,
int startAngle, int arcAngle) {
// TODO Auto-generated method stub
}
@Override
public void drawPolyline(int[] xPoints, int[] yPoints,
int nPoints) {
// TODO Auto-generated method stub
}
@Override
public void drawPolygon(int[] xPoints, int[] yPoints,
int nPoints) {
// TODO Auto-generated method stub
}
@Override
public void fillPolygon(int[] xPoints, int[] yPoints,
int nPoints) {
// TODO Auto-generated method stub
}
@Override
public boolean drawImage(Image img, int x, int y,
ImageObserver observer) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean drawImage(Image img, int x, int y,
int width, int height, ImageObserver observer) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean drawImage(Image img, int x, int y,
Color bgcolor, ImageObserver observer) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean drawImage(Image img, int x, int y,
int width, int height, Color bgcolor,
ImageObserver observer) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean drawImage(Image img, int dx1, int dy1,
int dx2, int dy2, int sx1, int sy1, int sx2,
int sy2, ImageObserver observer) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean drawImage(Image img, int dx1, int dy1,
int dx2, int dy2, int sx1, int sy1, int sx2,
int sy2, Color bgcolor, ImageObserver observer) {
// TODO Auto-generated method stub
return false;
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
};
tl.draw(myg2, 0, 0);
if(glyphs.size() != 1) {
err("drew " + glyphs.size() + " times - expected 1");
total++;
bad++;
continue;
}
boolean isBad = false;
GlyphVector gv = glyphs.get(0);
// GLYPHS
int gotGlyphs[] = gv.getGlyphCodes(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]);
int count = Math.min(gotGlyphs.length, expectGlyphs.length); // go up to this count
for(int i=0;i<count;i++) {
if(gotGlyphs[i]!=expectGlyphs[i]) {
err("@"+i+" - got \tglyph 0x" + Integer.toHexString(gotGlyphs[i]) + " wanted 0x" + Integer.toHexString(expectGlyphs[i]));
isBad=true;
break;
}
}
// INDICES
int gotIndices[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]);
for(int i=0;i<count;i++) {
if(gotIndices[i]!=expectIndices[i]) {
err("@"+i+" - got \tindex 0x" + Integer.toHexString(gotGlyphs[i]) + " wanted 0x" + Integer.toHexString(expectGlyphs[i]));
isBad=true;
break;
}
}
// COUNT
if(gotGlyphs.length != expectGlyphs.length) {
System.out.println("Got " + gotGlyphs.length + " wanted " + expectGlyphs.length + " glyphs");
isBad=true;
} else {
if(OPT_VERBOSE) {
System.out.println(">> OK: " + gotGlyphs.length + " glyphs");
}
}
if(isBad) {
bad++;
System.out.println("* FAIL: " + id + " /\t" + fontName);
} else {
System.out.println("* OK : " + id + " /\t" + fontName);
}
total++;
}
}
private boolean verifyFont(File f, Map<String, String> fontAttrs) {
InputStream fis = null;
String fontName = fontAttrs.get(XML_NAME);
int count=0;
try {
fis = new BufferedInputStream(new FileInputStream(f));
int i = 0;
int r;
try {
while((r=fis.read())!=-1) {
i+=(int)r;
count++;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
if(OPT_VERBOSE) {
System.out.println("for " + f.getAbsolutePath() + " chks = 0x" + Integer.toHexString(i) + " size=" + count);
}
String theirStr = fontAttrs.get("rchecksum");
String ourStr = Integer.toHexString(i).toLowerCase();
if(theirStr!=null) {
if(theirStr.startsWith("0x")) {
theirStr = theirStr.substring(2).toLowerCase();
} else {
theirStr = theirStr.toLowerCase();
}
long theirs = Integer.parseInt(theirStr, 16);
if(theirs != i) {
err("WARNING: rchecksum for " + fontName + " was " + i + " (0x"+ourStr+") "+ " but file said " + theirs +" (0x"+theirStr+") - perhaps a different font?");
return false;
} else {
if(OPT_VERBOSE) {
System.out.println(" rchecksum for " + fontName + " OK");
}
return true;
}
} else {
//if(OPT_VERBOSE) {
System.err.println("WARNING: rchecksum for " + fontName + " was " + i + " (0x"+ourStr+") "+ " but rchecksum was MISSING. Old ICU data?");
//}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} finally {
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return true;
}
private Integer[] parseHexArray(String hex) {
List<Integer> ret = new ArrayList<Integer>();
String items[] = hex.split("[\\s,]");
for(String i : items) {
if(i.isEmpty()) continue;
if(i.startsWith("0x")) {
i = i.substring(2);
}
ret.add(Integer.parseInt(i, 16));
}
return ret.toArray(new Integer[0]);
}
private void err(String string) {
if(OPT_NOTHROW) {
System.out.println(id+" ERROR: " + string +" (continuing due to -n)");
} else {
throw new InternalError(id+ ": " + string);
}
}
private Font getFont(String fontName, Map<String, String> fontAttrs) {
Font f;
if(false)
try {
f = Font.getFont(fontName);
if(f!=null) {
if(OPT_VERBOSE) {
System.out.println("Loaded default path to " + fontName);
}
return f;
}
} catch(Throwable t) {
if(OPT_VERBOSE) {
t.printStackTrace();
System.out.println("problem loading font " + fontName + " - " + t.toString());
}
}
File homeDir = new File(System.getProperty("user.home"));
File fontDir = new File(homeDir, "fonts");
File fontFile = new File(fontDir, fontName);
//System.out.println("## trying " + fontFile.getAbsolutePath());
if(fontFile.canRead()) {
try {
if(!verifyFont(fontFile,fontAttrs)) {
System.out.println("Warning: failed to verify " + fontName);
}
f = Font.createFont(Font.TRUETYPE_FONT, fontFile);
if(f!=null & OPT_VERBOSE) {
System.out.println("> loaded from " + fontFile.getAbsolutePath() + " - " + f.toString());
}
return f;
} catch (FontFormatException e) {
if(OPT_VERBOSE) {
e.printStackTrace();
System.out.println("problem loading font " + fontName + " - " + e.toString());
}
} catch (IOException e) {
if(OPT_VERBOSE) {
e.printStackTrace();
System.out.println("problem loading font " + fontName + " - " + e.toString());
}
}
}
return null;
}
private static Map<String, String> attrs(Node testCase) {
Map<String,String> rv = new TreeMap<String,String>();
NamedNodeMap nnm = testCase.getAttributes();
for(int i=0;i<nnm.getLength();i++) {
Node n = nnm.item(i);
String k = n.getNodeName();
String v = n.getNodeValue();
rv.put(k, v);
}
return rv;
}
}