1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/**
25 * Copyright (C) 2013-2014 IBM Corporation and Others. All Rights Reserved.
26 */
27
28import java.awt.Color;
29import java.awt.Composite;
30import java.awt.Font;
31import java.awt.FontFormatException;
32import java.awt.FontMetrics;
33import java.awt.Graphics;
34import java.awt.Graphics2D;
35import java.awt.GraphicsConfiguration;
36import java.awt.Image;
37import java.awt.Paint;
38import java.awt.Rectangle;
39import java.awt.RenderingHints;
40import java.awt.RenderingHints.Key;
41import java.awt.Shape;
42import java.awt.Stroke;
43import java.awt.font.FontRenderContext;
44import java.awt.font.GlyphVector;
45import java.awt.font.TextLayout;
46import java.awt.geom.AffineTransform;
47import java.awt.image.BufferedImage;
48import java.awt.image.BufferedImageOp;
49import java.awt.image.ImageObserver;
50import java.awt.image.RenderedImage;
51import java.awt.image.renderable.RenderableImage;
52import java.io.BufferedInputStream;
53import java.io.File;
54import java.io.FileInputStream;
55import java.io.FileNotFoundException;
56import java.io.IOException;
57import java.io.InputStream;
58import java.text.AttributedCharacterIterator;
59import java.util.ArrayList;
60import java.util.List;
61import java.util.Map;
62import java.util.MissingResourceException;
63import java.util.TreeMap;
64
65import javax.xml.parsers.DocumentBuilder;
66import javax.xml.parsers.DocumentBuilderFactory;
67import javax.xml.parsers.ParserConfigurationException;
68
69import org.w3c.dom.Document;
70import org.w3c.dom.Element;
71import org.w3c.dom.NamedNodeMap;
72import org.w3c.dom.Node;
73import org.w3c.dom.NodeList;
74import org.xml.sax.SAXException;
75
76/**
77 * This test runs against a test XML file. It opens the fonts and attempts
78 * to shape and layout glyphs.
79 * Note that the test is highly environment dependent- you must have
80 * the same versions of the same fonts available or the test will fail.
81 *
82 * It is similar to letest which is part of ICU.
83 * For reference, here are some reference items:
84 * ICU's test file:
85 *  http://source.icu-project.org/repos/icu/icu/trunk/source/test/testdata/letest.xml
86 * ICU's readme for the similar test:
87 *  http://source.icu-project.org/repos/icu/icu/trunk/source/test/letest/readme.html
88 *
89 * @bug 8054203
90 * @test
91 * @summary manual test of layout engine behavior. Takes an XML control file.
92 * @compile TestLayoutVsICU.java
93 * @author srl
94 * @run main/manual
95 */
96public class TestLayoutVsICU {
97
98    public static boolean OPT_DRAW = false;
99    public static boolean OPT_VERBOSE = false;
100    public static boolean OPT_FAILMISSING = false;
101    public static boolean OPT_NOTHROW= false; // if true - don't stop on failure
102
103    public static int docs = 0; // # docs processed
104    public static int skipped = 0; // cases skipped due to bad font
105    public static int total = 0; // cases processed
106    public static int bad = 0; // cases with errs
107
108    public static final String XML_LAYOUT_TESTS = "layout-tests"; // top level
109    public static final String XML_TEST_CASE = "test-case";
110    public static final String XML_TEST_FONT = "test-font";
111    public static final String XML_TEST_TEXT = "test-text";
112    public static final String XML_RESULT_GLYPHS = "result-glyphs";
113    public static final String XML_ID = "id";
114    public static final String XML_SCRIPT = "script";
115    public static final String XML_NAME = "name";
116    public static final String XML_VERSION = "version";
117    public static final String XML_CHECKSUM = "checksum";
118    public static final String XML_RESULT_INDICES = "result-indices";
119    public static final String XML_RESULT_POSITIONS = "result-positions";
120
121    /**
122     * @param args
123     * @throws IOException
124     * @throws SAXException
125     * @throws ParserConfigurationException
126     */
127    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
128        System.out.println("Java " + System.getProperty("java.version") + " from " + System.getProperty("java.vendor"));
129        TestLayoutVsICU tlvi = null;
130        for(String arg : args) {
131            if(arg.equals("-d")) {
132                OPT_DRAW = true;
133            } else if(arg.equals("-n")) {
134                OPT_NOTHROW = true;
135            } else if(arg.equals("-v")) {
136                OPT_VERBOSE = true;
137            } else if(arg.equals("-f")) {
138                OPT_FAILMISSING = true;
139            } else {
140                if(tlvi == null) {
141                    tlvi = new TestLayoutVsICU();
142                }
143                try {
144                    tlvi.show(arg);
145                } finally {
146                    if(OPT_VERBOSE) {
147                        System.out.println("# done with " + arg);
148                    }
149                }
150            }
151        }
152
153        if(tlvi == null) {
154            throw new IllegalArgumentException("No XML input. Usage: " + TestLayoutVsICU.class.getSimpleName() + " [-d][-v][-f] letest.xml ...");
155        } else {
156            System.out.println("\n\nRESULTS:\n");
157            System.out.println(skipped+"\tskipped due to missing font");
158            System.out.println(total+"\ttested of which:");
159            System.out.println(bad+"\twere bad");
160
161            if(bad>0) {
162                throw new InternalError("One or more failure(s)");
163            }
164        }
165    }
166
167    String id;
168
169    private void show(String arg) throws ParserConfigurationException, SAXException, IOException {
170        id = "<none>";
171        File xmlFile = new File(arg);
172        if(!xmlFile.exists()) {
173            throw new FileNotFoundException("Can't open input XML file " + arg);
174        }
175        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
176        DocumentBuilder db = dbf.newDocumentBuilder();
177        if(OPT_VERBOSE) {
178            System.out.println("# Parsing " + xmlFile.getAbsolutePath());
179        }
180        Document doc = db.parse(xmlFile);
181        Element e = doc.getDocumentElement();
182        if(!XML_LAYOUT_TESTS.equals(e.getNodeName())) {
183            throw new IllegalArgumentException("Document " + xmlFile.getAbsolutePath() + " does not have <layout-tests> as its base");
184        }
185
186        NodeList testCases = e.getElementsByTagName(XML_TEST_CASE);
187        for(int caseNo=0;caseNo<testCases.getLength();caseNo++) {
188            final Node testCase = testCases.item(caseNo);
189            final Map<String,String> testCaseAttrs = attrs(testCase);
190            id = testCaseAttrs.get(XML_ID);
191            final String script = testCaseAttrs.get(XML_SCRIPT);
192            String testText = null;
193            Integer[] expectGlyphs = null;
194            Integer[] expectIndices = null;
195            Map<String,String> fontAttrs = null;
196            if(OPT_VERBOSE) {
197                System.out.println("#"+caseNo+" id="+id + ", script="+script);
198            }
199            NodeList children = testCase.getChildNodes();
200            for(int sub=0;sub<children.getLength();sub++) {
201                Node n = children.item(sub);
202                if(n.getNodeType()!=Node.ELEMENT_NODE) continue;
203                String nn = n.getNodeName();
204                if(nn.equals(XML_TEST_FONT)) {
205                    fontAttrs = attrs(n);
206                } else if(nn.equals(XML_TEST_TEXT)) {
207                    testText = n.getTextContent();
208                } else if(nn.equals(XML_RESULT_GLYPHS)) {
209                    String hex = n.getTextContent();
210                    expectGlyphs = parseHexArray(hex);
211                } else if(nn.equals(XML_RESULT_INDICES)) {
212                    String hex = n.getTextContent();
213                    expectIndices = parseHexArray(hex);
214                } else if(OPT_VERBOSE) {
215                    System.out.println("Ignoring node " + nn);
216                }
217            }
218            if(fontAttrs == null) {
219                throw new IllegalArgumentException(id + " Missing node " + XML_TEST_FONT);
220            }
221            if(testText == null) {
222                throw new IllegalArgumentException(id + " Missing node " + XML_TEST_TEXT);
223            }
224
225            String fontName = fontAttrs.get(XML_NAME);
226            Font f = getFont(fontName, fontAttrs);
227            if(f==null) {
228                if(OPT_FAILMISSING) {
229                    throw new MissingResourceException("Missing font,  abort test", Font.class.getName(), fontName);
230                }
231                System.out.println("Skipping " + id + " because font is missing: " + fontName);
232                skipped++;
233                continue;
234            }
235            FontRenderContext frc = new FontRenderContext(null, true, true);
236            TextLayout tl = new TextLayout(testText,f,frc);
237            final List<GlyphVector> glyphs = new ArrayList<GlyphVector>();
238            Graphics2D myg2 = new Graphics2D(){
239
240                    @Override
241                    public void draw(Shape s) {
242                        // TODO Auto-generated method stub
243
244                    }
245
246                    @Override
247                    public boolean drawImage(Image img, AffineTransform xform,
248                                             ImageObserver obs) {
249                        // TODO Auto-generated method stub
250                        return false;
251                    }
252
253                    @Override
254                    public void drawImage(BufferedImage img,
255                                          BufferedImageOp op, int x, int y) {
256                        // TODO Auto-generated method stub
257
258                    }
259
260                    @Override
261                    public void drawRenderedImage(RenderedImage img,
262                                                  AffineTransform xform) {
263                        // TODO Auto-generated method stub
264
265                    }
266
267                    @Override
268                    public void drawRenderableImage(RenderableImage img,
269                                                    AffineTransform xform) {
270                        // TODO Auto-generated method stub
271
272                    }
273
274                    @Override
275                    public void drawString(String str, int x, int y) {
276                        // TODO Auto-generated method stub
277
278                    }
279
280                    @Override
281                    public void drawString(String str, float x, float y) {
282                        // TODO Auto-generated method stub
283
284                    }
285
286                    @Override
287                    public void drawString(
288                                           AttributedCharacterIterator iterator, int x, int y) {
289                        // TODO Auto-generated method stub
290
291                    }
292
293                    @Override
294                    public void drawString(
295                                           AttributedCharacterIterator iterator, float x,
296                                           float y) {
297                        // TODO Auto-generated method stub
298
299                    }
300
301                    @Override
302                    public void drawGlyphVector(GlyphVector g, float x, float y) {
303                        if(x!=0.0 || y!=0.0) {
304                            throw new InternalError("x,y should be 0 but got " + x+","+y);
305                        }
306                        //System.err.println("dGV : " + g.toString() + " @ "+x+","+y);
307                        glyphs.add(g);
308                    }
309
310                    @Override
311                    public void fill(Shape s) {
312                        // TODO Auto-generated method stub
313
314                    }
315
316                    @Override
317                    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
318                        // TODO Auto-generated method stub
319                        return false;
320                    }
321
322                    @Override
323                    public GraphicsConfiguration getDeviceConfiguration() {
324                        // TODO Auto-generated method stub
325                        return null;
326                    }
327
328                    @Override
329                    public void setComposite(Composite comp) {
330                        // TODO Auto-generated method stub
331
332                    }
333
334                    @Override
335                    public void setPaint(Paint paint) {
336                        // TODO Auto-generated method stub
337
338                    }
339
340                    @Override
341                    public void setStroke(Stroke s) {
342                        // TODO Auto-generated method stub
343
344                    }
345
346                    @Override
347                    public void setRenderingHint(Key hintKey, Object hintValue) {
348                        // TODO Auto-generated method stub
349
350                    }
351
352                    @Override
353                    public Object getRenderingHint(Key hintKey) {
354                        // TODO Auto-generated method stub
355                        return null;
356                    }
357
358                    @Override
359                    public void setRenderingHints(Map<?, ?> hints) {
360                        // TODO Auto-generated method stub
361
362                    }
363
364                    @Override
365                    public void addRenderingHints(Map<?, ?> hints) {
366                        // TODO Auto-generated method stub
367
368                    }
369
370                    @Override
371                    public RenderingHints getRenderingHints() {
372                        // TODO Auto-generated method stub
373                        return null;
374                    }
375
376                    @Override
377                    public void translate(int x, int y) {
378                        // TODO Auto-generated method stub
379
380                    }
381
382                    @Override
383                    public void translate(double tx, double ty) {
384                        // TODO Auto-generated method stub
385
386                    }
387
388                    @Override
389                    public void rotate(double theta) {
390                        // TODO Auto-generated method stub
391
392                    }
393
394                    @Override
395                    public void rotate(double theta, double x, double y) {
396                        // TODO Auto-generated method stub
397
398                    }
399
400                    @Override
401                    public void scale(double sx, double sy) {
402                        // TODO Auto-generated method stub
403
404                    }
405
406                    @Override
407                    public void shear(double shx, double shy) {
408                        // TODO Auto-generated method stub
409
410                    }
411
412                    @Override
413                    public void transform(AffineTransform Tx) {
414                        // TODO Auto-generated method stub
415
416                    }
417
418                    @Override
419                    public void setTransform(AffineTransform Tx) {
420                        // TODO Auto-generated method stub
421
422                    }
423
424                    @Override
425                    public AffineTransform getTransform() {
426                        // TODO Auto-generated method stub
427                        return null;
428                    }
429
430                    @Override
431                    public Paint getPaint() {
432                        // TODO Auto-generated method stub
433                        return null;
434                    }
435
436                    @Override
437                    public Composite getComposite() {
438                        // TODO Auto-generated method stub
439                        return null;
440                    }
441
442                    @Override
443                    public void setBackground(Color color) {
444                        // TODO Auto-generated method stub
445
446                    }
447
448                    @Override
449                    public Color getBackground() {
450                        // TODO Auto-generated method stub
451                        return null;
452                    }
453
454                    @Override
455                    public Stroke getStroke() {
456                        // TODO Auto-generated method stub
457                        return null;
458                    }
459
460                    @Override
461                    public void clip(Shape s) {
462                        // TODO Auto-generated method stub
463
464                    }
465
466                    @Override
467                    public FontRenderContext getFontRenderContext() {
468                        // TODO Auto-generated method stub
469                        return null;
470                    }
471
472                    @Override
473                    public Graphics create() {
474                        // TODO Auto-generated method stub
475                        return null;
476                    }
477
478                    @Override
479                    public Color getColor() {
480                        // TODO Auto-generated method stub
481                        return null;
482                    }
483
484                    @Override
485                    public void setColor(Color c) {
486                        // TODO Auto-generated method stub
487
488                    }
489
490                    @Override
491                    public void setPaintMode() {
492                        // TODO Auto-generated method stub
493
494                    }
495
496                    @Override
497                    public void setXORMode(Color c1) {
498                        // TODO Auto-generated method stub
499
500                    }
501
502                    @Override
503                    public Font getFont() {
504                        // TODO Auto-generated method stub
505                        return null;
506                    }
507
508                    @Override
509                    public void setFont(Font font) {
510                        // TODO Auto-generated method stub
511
512                    }
513
514                    @Override
515                    public FontMetrics getFontMetrics(Font f) {
516                        // TODO Auto-generated method stub
517                        return null;
518                    }
519
520                    @Override
521                    public Rectangle getClipBounds() {
522                        // TODO Auto-generated method stub
523                        return null;
524                    }
525
526                    @Override
527                    public void clipRect(int x, int y, int width, int height) {
528                        // TODO Auto-generated method stub
529
530                    }
531
532                    @Override
533                    public void setClip(int x, int y, int width, int height) {
534                        // TODO Auto-generated method stub
535
536                    }
537
538                    @Override
539                    public Shape getClip() {
540                        // TODO Auto-generated method stub
541                        return null;
542                    }
543
544                    @Override
545                    public void setClip(Shape clip) {
546                        // TODO Auto-generated method stub
547
548                    }
549
550                    @Override
551                    public void copyArea(int x, int y, int width, int height,
552                                         int dx, int dy) {
553                        // TODO Auto-generated method stub
554
555                    }
556
557                    @Override
558                    public void drawLine(int x1, int y1, int x2, int y2) {
559                        // TODO Auto-generated method stub
560
561                    }
562
563                    @Override
564                    public void fillRect(int x, int y, int width, int height) {
565                        // TODO Auto-generated method stub
566
567                    }
568
569                    @Override
570                    public void clearRect(int x, int y, int width, int height) {
571                        // TODO Auto-generated method stub
572
573                    }
574
575                    @Override
576                    public void drawRoundRect(int x, int y, int width,
577                                              int height, int arcWidth, int arcHeight) {
578                        // TODO Auto-generated method stub
579
580                    }
581
582                    @Override
583                    public void fillRoundRect(int x, int y, int width,
584                                              int height, int arcWidth, int arcHeight) {
585                        // TODO Auto-generated method stub
586
587                    }
588
589                    @Override
590                    public void drawOval(int x, int y, int width, int height) {
591                        // TODO Auto-generated method stub
592
593                    }
594
595                    @Override
596                    public void fillOval(int x, int y, int width, int height) {
597                        // TODO Auto-generated method stub
598
599                    }
600
601                    @Override
602                    public void drawArc(int x, int y, int width, int height,
603                                        int startAngle, int arcAngle) {
604                        // TODO Auto-generated method stub
605
606                    }
607
608                    @Override
609                    public void fillArc(int x, int y, int width, int height,
610                                        int startAngle, int arcAngle) {
611                        // TODO Auto-generated method stub
612
613                    }
614
615                    @Override
616                    public void drawPolyline(int[] xPoints, int[] yPoints,
617                                             int nPoints) {
618                        // TODO Auto-generated method stub
619
620                    }
621
622                    @Override
623                    public void drawPolygon(int[] xPoints, int[] yPoints,
624                                            int nPoints) {
625                        // TODO Auto-generated method stub
626
627                    }
628
629                    @Override
630                    public void fillPolygon(int[] xPoints, int[] yPoints,
631                                            int nPoints) {
632                        // TODO Auto-generated method stub
633
634                    }
635
636                    @Override
637                    public boolean drawImage(Image img, int x, int y,
638                                             ImageObserver observer) {
639                        // TODO Auto-generated method stub
640                        return false;
641                    }
642
643                    @Override
644                    public boolean drawImage(Image img, int x, int y,
645                                             int width, int height, ImageObserver observer) {
646                        // TODO Auto-generated method stub
647                        return false;
648                    }
649
650                    @Override
651                    public boolean drawImage(Image img, int x, int y,
652                                             Color bgcolor, ImageObserver observer) {
653                        // TODO Auto-generated method stub
654                        return false;
655                    }
656
657                    @Override
658                    public boolean drawImage(Image img, int x, int y,
659                                             int width, int height, Color bgcolor,
660                                             ImageObserver observer) {
661                        // TODO Auto-generated method stub
662                        return false;
663                    }
664
665                    @Override
666                    public boolean drawImage(Image img, int dx1, int dy1,
667                                             int dx2, int dy2, int sx1, int sy1, int sx2,
668                                             int sy2, ImageObserver observer) {
669                        // TODO Auto-generated method stub
670                        return false;
671                    }
672
673                    @Override
674                    public boolean drawImage(Image img, int dx1, int dy1,
675                                             int dx2, int dy2, int sx1, int sy1, int sx2,
676                                             int sy2, Color bgcolor, ImageObserver observer) {
677                        // TODO Auto-generated method stub
678                        return false;
679                    }
680
681                    @Override
682                    public void dispose() {
683                        // TODO Auto-generated method stub
684
685                    }
686
687                };
688            tl.draw(myg2, 0, 0);
689            if(glyphs.size() != 1) {
690                err("drew " + glyphs.size() + " times - expected 1");
691                total++;
692                bad++;
693                continue;
694            }
695            boolean isBad = false;
696            GlyphVector gv = glyphs.get(0);
697
698            // GLYPHS
699            int gotGlyphs[] = gv.getGlyphCodes(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]);
700
701            int count = Math.min(gotGlyphs.length, expectGlyphs.length); // go up to this count
702
703            for(int i=0;i<count;i++) {
704                if(gotGlyphs[i]!=expectGlyphs[i]) {
705                    err("@"+i+" - got \tglyph 0x" + Integer.toHexString(gotGlyphs[i]) + " wanted 0x" + Integer.toHexString(expectGlyphs[i]));
706                    isBad=true;
707                    break;
708                }
709            }
710
711            // INDICES
712            int gotIndices[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]);
713            for(int i=0;i<count;i++) {
714                if(gotIndices[i]!=expectIndices[i]) {
715                    err("@"+i+" - got \tindex 0x" + Integer.toHexString(gotGlyphs[i]) + " wanted 0x" + Integer.toHexString(expectGlyphs[i]));
716                    isBad=true;
717                    break;
718                }
719            }
720
721
722            // COUNT
723            if(gotGlyphs.length != expectGlyphs.length) {
724                System.out.println("Got " + gotGlyphs.length + " wanted " + expectGlyphs.length + " glyphs");
725                isBad=true;
726            } else {
727                if(OPT_VERBOSE) {
728                    System.out.println(">> OK: " + gotGlyphs.length + " glyphs");
729                }
730            }
731
732
733            if(isBad) {
734                bad++;
735                System.out.println("* FAIL: " + id + "  /\t" + fontName);
736            } else {
737                System.out.println("* OK  : " + id + "  /\t" + fontName);
738            }
739            total++;
740        }
741    }
742
743
744    private boolean verifyFont(File f, Map<String, String> fontAttrs) {
745        InputStream fis = null;
746        String fontName = fontAttrs.get(XML_NAME);
747        int count=0;
748        try {
749            fis = new BufferedInputStream(new FileInputStream(f));
750
751            int i = 0;
752            int r;
753            try {
754                while((r=fis.read())!=-1) {
755                    i+=(int)r;
756                    count++;
757                }
758            } catch (IOException e) {
759                // TODO Auto-generated catch block
760                e.printStackTrace();
761                return false;
762            }
763            if(OPT_VERBOSE) {
764                System.out.println("for " + f.getAbsolutePath() + " chks = 0x" + Integer.toHexString(i) + " size=" + count);
765            }
766            String theirStr = fontAttrs.get("rchecksum");
767
768            String ourStr = Integer.toHexString(i).toLowerCase();
769
770            if(theirStr!=null) {
771                if(theirStr.startsWith("0x")) {
772                    theirStr = theirStr.substring(2).toLowerCase();
773                } else {
774                    theirStr = theirStr.toLowerCase();
775                }
776                long theirs = Integer.parseInt(theirStr, 16);
777                if(theirs != i) {
778                    err("WARNING: rchecksum for " + fontName + " was " + i  + " (0x"+ourStr+") "+ " but file said " + theirs +" (0x"+theirStr+")  - perhaps a different font?");
779                    return false;
780                } else {
781                    if(OPT_VERBOSE) {
782                        System.out.println(" rchecksum for " + fontName + " OK");
783                    }
784                    return true;
785                }
786            } else {
787                //if(OPT_VERBOSE) {
788                System.err.println("WARNING: rchecksum for " + fontName + " was " + i  + " (0x"+ourStr+") "+ " but rchecksum was MISSING. Old ICU data?");
789                //}
790            }
791        } catch (FileNotFoundException e) {
792            // TODO Auto-generated catch block
793            e.printStackTrace();
794            return false;
795        } finally {
796            try {
797                fis.close();
798            } catch (IOException e) {
799                // TODO Auto-generated catch block
800                e.printStackTrace();
801            }
802        }
803        return true;
804    }
805
806
807    private Integer[] parseHexArray(String hex) {
808        List<Integer> ret = new ArrayList<Integer>();
809        String items[] = hex.split("[\\s,]");
810        for(String i : items) {
811            if(i.isEmpty()) continue;
812            if(i.startsWith("0x")) {
813                i = i.substring(2);
814            }
815            ret.add(Integer.parseInt(i, 16));
816        }
817        return ret.toArray(new Integer[0]);
818    }
819
820
821    private void err(String string) {
822        if(OPT_NOTHROW) {
823            System.out.println(id+" ERROR: " + string +" (continuing due to -n)");
824        } else {
825            throw new InternalError(id+ ": " + string);
826        }
827    }
828
829
830    private Font getFont(String fontName, Map<String, String> fontAttrs) {
831        Font f;
832        if(false)
833            try {
834                f = Font.getFont(fontName);
835                if(f!=null)  {
836                    if(OPT_VERBOSE) {
837                        System.out.println("Loaded default path to " + fontName);
838                    }
839                    return f;
840                }
841            } catch(Throwable t) {
842                if(OPT_VERBOSE) {
843                    t.printStackTrace();
844                    System.out.println("problem loading font " + fontName + " - " + t.toString());
845                }
846            }
847
848        File homeDir = new File(System.getProperty("user.home"));
849        File fontDir = new File(homeDir, "fonts");
850        File fontFile = new File(fontDir, fontName);
851        //System.out.println("## trying " + fontFile.getAbsolutePath());
852        if(fontFile.canRead()) {
853            try {
854                if(!verifyFont(fontFile,fontAttrs)) {
855                    System.out.println("Warning: failed to verify " + fontName);
856                }
857                f = Font.createFont(Font.TRUETYPE_FONT, fontFile);
858                if(f!=null & OPT_VERBOSE) {
859                    System.out.println("> loaded from " + fontFile.getAbsolutePath() + " - " + f.toString());
860                }
861                return f;
862            } catch (FontFormatException e) {
863                if(OPT_VERBOSE) {
864                    e.printStackTrace();
865                    System.out.println("problem loading font " + fontName + " - " + e.toString());
866                }
867            } catch (IOException e) {
868                if(OPT_VERBOSE) {
869                    e.printStackTrace();
870                    System.out.println("problem loading font " + fontName + " - " + e.toString());
871                }
872            }
873        }
874        return null;
875    }
876
877
878    private static Map<String, String> attrs(Node testCase) {
879        Map<String,String> rv = new TreeMap<String,String>();
880        NamedNodeMap nnm = testCase.getAttributes();
881        for(int i=0;i<nnm.getLength();i++) {
882            Node n = nnm.item(i);
883            String k = n.getNodeName();
884            String v = n.getNodeValue();
885            rv.put(k, v);
886        }
887        return rv;
888    }
889}
890