/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.complexscripts.fonts; import java.io.File; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.apache.fop.complexscripts.fonts.GlyphTable.LookupTable; import org.apache.fop.complexscripts.fonts.ttx.TTXFile; import org.apache.fop.complexscripts.util.GlyphContextTester; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; // CSOFF: LineLength public class GPOSTestCase implements ScriptContextTester, GlyphContextTester { private static String ttxFilesRoot = "test/resources/complexscripts"; private static String[][] ttxFonts = { { "f0", "arab/ttx/arab-001.ttx" }, // simplified arabic { "f1", "arab/ttx/arab-002.ttx" }, // traditional arabic { "f2", "arab/ttx/arab-003.ttx" }, // lateef { "f3", "arab/ttx/arab-004.ttx" }, // scheherazade }; private static Object[][] ltSingle = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE }, // arab-001.ttx { "f0", "lu1", "arab", "dflt", "mark", new Object[][] { { new String[] { "fathatan" }, new int[][] { { 0, 0, -412, 0 } } }, { new String[] { "fatha" }, new int[][] { { 0, 0, -410, 0 } } }, }, }, { "f0", "lu9", "arab", "*", "*", new Object[][] { { new String[] { "fathatan" }, new int[][] { { 50, 0, 0, 0 } } }, { new String[] { "fatha" }, new int[][] { { 50, 0, 0, 0 } } }, }, }, { "f0", "lu10", "arab", "*", "*", new Object[][] { { new String[] { "kasratan" }, new int[][] { { 0, -200, 0, 0 } } }, { new String[] { "kasra" }, new int[][] { { 0, -200, 0, 0 } } }, }, }, { "f0", "lu11", "arab", "*", "*", new Object[][] { { new String[] { "kasratan" }, new int[][] { { 0, -300, 0, 0 } } }, { new String[] { "kasra" }, new int[][] { { 0, -300, 0, 0 } } }, { new String[] { "uni0655" }, new int[][] { { 0, -250, 0, 0 } } }, }, }, // arab-002.ttx - maybe add tests // arab-003.ttx - maybe add tests // arab-004.ttx - maybe add tests }; private static Object[][] ltPair = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR }, // arab-001.ttx { "f0", "lu0", "arab", "dflt", "kern", new Object[][] { { new String[] { "wawwithhamzaabove", "hamza" }, new int[][] { { -300, 0, -300, 0 }, { 0, 0, 0, 0 } } }, { new String[] { "reh", "alefwithmaddaabove" }, new int[][] { { -500, 0, -500, 0 }, { 0, 0, 0, 0 } } }, { new String[] { "zain", "zain" }, new int[][] { { -190, 0, -190, 0 }, { 0, 0, 0, 0 } } }, { new String[] { "waw", "uni0649.init" }, new int[][] { { -145, 0, -145, 0 }, { 0, 0, 0, 0 } } }, { new String[] { "jeh", "uni06A5.init" }, new int[][] { { -345, 0, -345, 0 }, { 0, 0, 0, 0 } } }, }, }, // arab-002.ttx - maybe add tests // arab-003.ttx - maybe add tests // arab-004.ttx - maybe add tests }; private static Object[][] ltCursive = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE }, // arab-001.ttx - none used // arab-002.ttx - none used // arab-003.ttx - maybe add tests { "f2", "lu0", "arab", "dflt", "curs", new Object[][] { { new String[] { "uni0644.init.preAlef", "uni0622.fina.postLamIni" }, new int[][] { // { 576, 0, 0, 0 }, { 0, 0, 0, 0 } - with zero widths { 295, 0, 0, 0 }, { 0, 0, 0, 0 } } }, { new String[] { "uni0644.medi.preAlef", "uni0622.fina.postLamMed" }, new int[][] { // { 550, 0, 0, 0 }, { 0, 0, 0, 0 } - with zero widths { 282, 0, 0, 0 }, { 0, 0, 0, 0 } } }, }, }, // arab-004.ttx - none used }; private static Object[][] ltMarkToBase = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE }, // arab-001.ttx - maybe add tests // arab-002.ttx { "f1", "lu4", "arab", "dflt", "mark", new Object[][] { { new String[] { "beh", "fatha" }, new int[][] { // { 0, 0, 0, 0 }, { 266, -672, 0, 0 } - with zero widths { 0, 0, 0, 0 }, { 266, -672, -199, 0 } } }, { new String[] { "alefwithhamzabelow", "kasra" }, new int[][] { // { 0, 0, 0, 0 }, { -48, 344, 0, 0 } - with zero widths { 0, 0, 0, 0 }, { -48, 344, -199, 0 } } }, }, }, // arab-003.ttx - maybe add tests // arab-004.ttx - maybe add tests }; private static Object[][] ltMarkToLigature = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE }, // arab-001.ttx { "f0", "lu4", "arab", "dflt", "mark", new Object[][] { { new String[] { "rayaleflam", "fatha", "fatha", "fatha", "fatha" }, new int[][] { { 0, 0, 0, 0 }, { 1260, -1150, 0, 0 }, { 910, -1020, 0, 0 }, { 590, -630, 0, 0 }, { 110, -720, 0, 0 } } }, { new String[] { "rayaleflam", "kasra", "kasra", "kasra", "kasra" }, new int[][] { { 0, 0, 0, 0 }, { 1110 , 225, 0, 0 }, { 760, 275, 0, 0 }, { 520, 475, 0, 0 }, { 110, 425, 0, 0 } } }, }, }, // arab-002.ttx - maybe add tests // arab-003.ttx - maybe add tests // arab-004.ttx - maybe add tests }; private static Object[][] ltMarkToMark = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK }, // arab-001.ttx - maybe add tests // arab-002.ttx - maybe add tests // arab-003.ttx - maybe add tests // arab-004.ttx { "f3", "lu3", "arab", "dflt", "mkmk", new Object[][] { { new String[] { "uni064F", "uni064E" }, new int[][] { { 0, 0, 0, 0 }, { -15, 495, 0, 0 } } }, { new String[] { "uni0651", "uni0670" }, new int[][] { { 0, 0, 0, 0 }, { -30, 705, 0, 0 } } }, }, }, }; private static Object[][] ltContextual = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_CONTEXTUAL }, // arab-001.ttx - none used // arab-002.ttx - none used // arab-003.ttx - none used // arab-004.ttx - none used }; private static Object[][] ltChainedContextual = { { GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL }, // arab-001.ttx { "f0", "lu3", "arab", "dflt", "mark", new Object[][] { { new String[] { "behmedial", "fatha", "lam" }, new int[][] { { 0, 0, 0, 0 }, { 50, 0, 0, 0 }, { 0, 0, 0, 0 } } }, }, }, // arab-002.ttx { "f1", "lu6", "arab", "dflt", "mark", new Object[][] { { new String[] { "zain", "fatha", "kafinitial" }, new int[][] { { 0, 250, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } }, }, }, // arab-003.ttx - none used // arab-004.ttx { "f3", "lu5", "arab", "dflt", "mark", new Object[][] { { new String[] { "uni064D", "uni0622.fina.postLamIni", "uni0650" }, new int[][] { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 55, 424, 0, 0 } } }, }, }, }; @Test public void testGPOSSingle() throws Exception { performPositioning(ltSingle); } @Test public void testGPOSPair() throws Exception { performPositioning(ltPair); } @Test public void testGPOSCursive() throws Exception { performPositioning(ltCursive); } @Test public void testGPOSMarkToBase() throws Exception { performPositioning(ltMarkToBase); } @Test public void testGPOSMarkToLigature() throws Exception { performPositioning(ltMarkToLigature); } @Test public void testGPOSMarkToMark() throws Exception { performPositioning(ltMarkToMark); } @Test public void testGPOSContextual() throws Exception { performPositioning(ltContextual); } @Test public void testGPOSChainedContextual() throws Exception { performPositioning(ltChainedContextual); } /** * Perform positioning on all test data in test specification TS. * @param ts test specification */ private void performPositioning(Object[][] ts) { assert ts.length > 0; Object[] tp = ts[0]; for (int i = 1; i < ts.length; i++) { performPositioning(tp, ts[i]); } } /** * Perform positioning on all test data TD using test parameters TP. * @param tp test parameters * @param td test data */ private void performPositioning(Object[] tp, Object[] td) { assert tp.length > 0; if (td.length > 5) { String fid = (String) td[0]; String lid = (String) td[1]; String script = (String) td[2]; String language = (String) td[3]; String feature = (String) td[4]; TTXFile tf = findTTX(fid); assertTrue(tf != null); GlyphPositioningTable gpos = tf.getGPOS(); assertTrue(gpos != null); GlyphPositioningSubtable[] sta = findGPOSSubtables(gpos, script, language, feature, lid); assertTrue(sta != null); assertTrue(sta.length > 0); ScriptContextTester sct = findScriptContextTester(script, language, feature); Object[][] tia = (Object[][]) td[5]; // test instance array for (Object[] ti : tia) { // test instance if (ti != null) { if (ti.length > 0) { // must have at least input glyphs String[] igia = (String[]) ti[0]; // input glyph id array int[][] ogpa = (int[][]) ti[1]; // output glyph positioning array GlyphSequence igs = tf.getGlyphSequence(igia); int[] widths = tf.getWidths(); int[][] tgpa = new int [ igia.length ] [ 4 ]; boolean adjusted = GlyphPositioningSubtable.position(igs, script, language, feature, 1000, sta, widths, tgpa, sct); assertTrue(adjusted); assertSamePositions(ogpa, tgpa); } } } } } private String findTTXPath(String fid) { for (String[] fs : ttxFonts) { if ((fs != null) && (fs.length > 1)) { if (fs[0].equals(fid)) { return ttxFilesRoot + File.separator + fs[1]; } } } return null; } private TTXFile findTTX(String fid) { String pn = findTTXPath(fid); assertTrue(pn != null); try { TTXFile tf = TTXFile.getFromCache(pn); return tf; } catch (Exception e) { fail(e.getMessage()); return null; } } private GlyphPositioningSubtable[] findGPOSSubtables(GlyphPositioningTable gpos, String script, String language, String feature, String lid) { LookupTable lt = gpos.getLookupTable(lid); if (lt != null) { return (GlyphPositioningSubtable[]) lt.getSubtables(); } else { return null; } } private ScriptContextTester findScriptContextTester(String script, String language, String feature) { return this; } public GlyphContextTester getTester(String feature) { return this; } public boolean test(String script, String language, String feature, GlyphSequence gs, int index, int flags) { return true; } private void assertSamePositions(int[][] pa1, int[][] pa2) { assertNotNull(pa1); assertNotNull(pa2); assertEquals("unequal adjustment count", pa1.length, pa2.length); for (int i = 0; i < pa1.length; i++) { int[] a1 = pa1 [ i ]; int[] a2 = pa2 [ i ]; assertNotNull(a1); assertNotNull(a2); assertEquals("bad adjustment array length", 4, a1.length); assertEquals("bad adjustment array length", 4, a2.length); for (int k = 0; k < a1.length; k++) { int p1 = a1[k]; int p2 = a2[k]; assertEquals("bad adjustment", p1, p2); } } } }