/* * 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; } }