]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-2213 use kern table when no GPOS kern lookups are present
authorGlenn Adams <gadams@apache.org>
Tue, 9 Sep 2014 23:43:58 +0000 (23:43 +0000)
committerGlenn Adams <gadams@apache.org>
Tue, 9 Sep 2014 23:43:58 +0000 (23:43 +0000)
FOP-2094 preliminary support for mapping OTF script tags from @script property
FOP-2094 when no lookups match specified or auto script, retry using DFLT script
FOP-2093 preliminary support for mapping OTF language tags from @language property

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1623885 13f79535-47bb-0310-9956-ffa450edef68

18 files changed:
build.xml
src/java/org/apache/fop/apps/FopConfParser.java
src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java
src/java/org/apache/fop/complexscripts/fonts/OTFLanguage.java [new file with mode: 0644]
src/java/org/apache/fop/complexscripts/fonts/OTFScript.java [new file with mode: 0644]
src/java/org/apache/fop/fonts/Font.java
src/java/org/apache/fop/fonts/FontMetrics.java
src/java/org/apache/fop/fonts/GlyphMapping.java
src/java/org/apache/fop/fonts/LazyFont.java
src/java/org/apache/fop/fonts/MultiByteFont.java
src/java/org/apache/fop/fonts/Typeface.java
test/java/org/apache/fop/complexscripts/layout/ComplexScriptsLayoutTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/complexscripts/layout/ComplexScriptsLayoutTestSuite.java [new file with mode: 0644]
test/java/org/apache/fop/intermediate/TestAssistant.java
test/java/org/apache/fop/layoutengine/LayoutEngineChecksFactory.java
test/resources/complexscripts/layout/standard-testcases/inline_kerning_jira2213.xml [new file with mode: 0644]
test/resources/complexscripts/layout/testcase2checks.xsl [new file with mode: 0644]
test/resources/complexscripts/layout/testcase2fo.xsl [new file with mode: 0644]

index e1899926eb5bfac78136d25e23904a37ba163e26..e5d9278d037d91ec1b18e8ad715bcc6a3a68f7c7 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -898,9 +898,12 @@ list of possible build targets.
   <target name="junit-complexscripts" depends="junit-compile">
     <junit-run title="complexscripts" testsuite="org.apache.fop.complexscripts.ComplexScriptsTestSuite" outfile="TEST-complexscripts"/>
   </target>
+  <target name="junit-complexscripts-layout" depends="junit-compile" if="junit.present">
+    <junit-run title="standard layout engine" testsuite="org.apache.fop.complexscripts.layout.ComplexScriptsLayoutTestSuite" outfile="TEST-complexscripts-layout"/>
+  </target>
   <target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder, 
     junit-text-linebreak, junit-fotree, junit-fonts, junit-render-pdf, junit-render-ps, 
-    junit-complexscripts"/>
+    junit-complexscripts, junit-complexscripts-layout"/>
   <target name="junit" depends="junit-all" description="Runs all of FOP's JUnit tests" 
     if="junit.present">
     <fail><condition><or><isset property="fop.junit.error"/><isset 
index b0fa40f451752aad3d139d6ead2626769241dcb3..01e43ed29685fa1ce5f2366e150f9a87ceb9458e 100644 (file)
@@ -141,7 +141,7 @@ public class FopConfParser {
         this(new FileInputStream(fopConfFile), fopConfFile.toURI(), resourceResolver);
     }
 
-    private FopConfParser(InputStream fopConfStream, URI baseURI, EnvironmentProfile enviro)
+    public FopConfParser(InputStream fopConfStream, URI baseURI, EnvironmentProfile enviro)
             throws SAXException, IOException {
         DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
         Configuration cfg;
index d130e654ab652c88af75f057de9d6fcd341af29e..de5d8f824c95d2441c625ec00351cff0ff0fe169 100644 (file)
@@ -223,7 +223,11 @@ public class GlyphTable {
             }
             matchedLookups.put(lsm, lm);
         }
-        return lm;
+        if (lm.isEmpty() && !OTFScript.isDefault(script) && !OTFScript.isWildCard(script)) {
+            return matchLookups(OTFScript.DEFAULT, OTFLanguage.DEFAULT, feature);
+        } else {
+            return lm;
+        }
     }
 
     /**
@@ -274,6 +278,19 @@ public class GlyphTable {
         return (UseSpec[]) uss.toArray(new UseSpec [ uss.size() ]);
     }
 
+    /**
+     * Determine if table supports specific feature, i.e., supports at least one lookup.
+     *
+     * @param script to qualify feature lookup
+     * @param language to qualify feature lookup
+     * @param feature to test
+     * @return true if feature supported (has at least one lookup)
+     */
+    public boolean hasFeature(String script, String language, String feature) {
+        UseSpec[] usa = assembleLookups(new String[] { feature }, matchLookups(script, language, feature));
+        return usa.length > 0;
+    }
+
     /** {@inheritDoc} */
     public String toString() {
         StringBuffer sb = new StringBuffer(super.toString());
@@ -350,8 +367,8 @@ public class GlyphTable {
          * @param script a script identifier
          * @param language a language identifier
          * @param feature a feature identifier
-         * @param permitEmpty if true the permit empty script, language, or feature
-         * @param permitWildcard if true the permit wildcard script, language, or feature
+         * @param permitEmpty if true then permit empty script, language, or feature
+         * @param permitWildcard if true then permit wildcard script, language, or feature
          */
         LookupSpec(String script, String language, String feature, boolean permitEmpty, boolean permitWildcard) {
             if ((script == null) || (!permitEmpty && (script.length() == 0))) {
diff --git a/src/java/org/apache/fop/complexscripts/fonts/OTFLanguage.java b/src/java/org/apache/fop/complexscripts/fonts/OTFLanguage.java
new file mode 100644 (file)
index 0000000..49f67df
--- /dev/null
@@ -0,0 +1,434 @@
+/*
+ * 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;
+
+/**
+ * <p>Language system tags defined by OTF specification. Note that this set and their
+ * values do not correspond with ISO639* or any other language registry.</p>
+ *
+ * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
+ */
+public final class OTFLanguage {
+    public static final String ABAZA                            = "ABA";
+    public static final String ABKHAZIAN                        = "ABK";
+    public static final String ADYGHE                           = "ADY";
+    public static final String AFRIKAANS                        = "AFK";
+    public static final String AFAR                             = "AFR";
+    public static final String AGAW                             = "AGW";
+    public static final String ALSATIAN                         = "ALS";
+    public static final String ALTAI                            = "ALT";
+    public static final String AMHARIC                          = "AMH";
+    public static final String PHONETIC_AMERICANIST             = "APPH";
+    public static final String ARABIC                           = "ARA";
+    public static final String AARI                             = "ARI";
+    public static final String ARAKANESE                        = "ARK";
+    public static final String ASSAMESE                         = "ASM";
+    public static final String ATHAPASKAN                       = "ATH";
+    public static final String AVAR                             = "AVR";
+    public static final String AWADHI                           = "AWA";
+    public static final String AYMARA                           = "AYM";
+    public static final String AZERI                            = "AZE";
+    public static final String BADAGA                           = "BAD";
+    public static final String BAGHELKHANDI                     = "BAG";
+    public static final String BALKAR                           = "BAL";
+    public static final String BAULE                            = "BAU";
+    public static final String BERBER                           = "BBR";
+    public static final String BENCH                            = "BCH";
+    public static final String BIBLE_CREE                       = "BCR";
+    public static final String BELARUSSIAN                      = "BEL";
+    public static final String BEMBA                            = "BEM";
+    public static final String BENGALI                          = "BEN";
+    public static final String BULGARIAN                        = "BGR";
+    public static final String BHILI                            = "BHI";
+    public static final String BHOJPURI                         = "BHO";
+    public static final String BIKOL                            = "BIK";
+    public static final String BILEN                            = "BIL";
+    public static final String BLACKFOOT                        = "BKF";
+    public static final String BALOCHI                          = "BLI";
+    public static final String BALANTE                          = "BLN";
+    public static final String BALTI                            = "BLT";
+    public static final String BAMBARA                          = "BMB";
+    public static final String BAMILEKE                         = "BML";
+    public static final String BOSNIAN                          = "BOS";
+    public static final String BRETON                           = "BRE";
+    public static final String BRAHUI                           = "BRH";
+    public static final String BRAJ_BHASHA                      = "BRI";
+    public static final String BURMESE                          = "BRM";
+    public static final String BASHKIR                          = "BSH";
+    public static final String BETI                             = "BTI";
+    public static final String CATALAN                          = "CAT";
+    public static final String CEBUANO                          = "CEB";
+    public static final String CHECHEN                          = "CHE";
+    public static final String CHAHA_GURAGE                     = "CHG";
+    public static final String CHATTISGARHI                     = "CHH";
+    public static final String CHICHEWA                         = "CHI";
+    public static final String CHUKCHI                          = "CHK";
+    public static final String CHIPEWYAN                        = "CHP";
+    public static final String CHEROKEE                         = "CHR";
+    public static final String CHUVASH                          = "CHU";
+    public static final String COMORIAN                         = "CMR";
+    public static final String COPTIC                           = "COP";
+    public static final String CORSICAN                         = "COS";
+    public static final String CREE                             = "CRE";
+    public static final String CARRIER                          = "CRR";
+    public static final String CRIMEAN_TATAR                    = "CRT";
+    public static final String CHURCH_SLAVONIC                  = "CSL";
+    public static final String CZECH                            = "CSY";
+    public static final String DANISH                           = "DAN";
+    public static final String DARGWA                           = "DAR";
+    public static final String WOODS_CREE                       = "DCR";
+    public static final String GERMAN                           = "DEU";
+    public static final String DEFAULT                          = "dflt";
+    public static final String DOGRI                            = "DGR";
+    public static final String DHIVEHI_DEPRECATED               = "DHV";
+    public static final String DHIVEHI                          = "DIV";
+    public static final String DJERMA                           = "DJR";
+    public static final String DANGME                           = "DNG";
+    public static final String DINKA                            = "DNK";
+    public static final String DARI                             = "DRI";
+    public static final String DUNGAN                           = "DUN";
+    public static final String DZONGKHA                         = "DZN";
+    public static final String EBIRA                            = "EBI";
+    public static final String EASTERN_CREE                     = "ECR";
+    public static final String EDO                              = "EDO";
+    public static final String EFIK                             = "EFI";
+    public static final String GREEK                            = "ELL";
+    public static final String ENGLISH                          = "ENG";
+    public static final String ERZYA                            = "ERZ";
+    public static final String SPANISH                          = "ESP";
+    public static final String ESTONIAN                         = "ETI";
+    public static final String BASQUE                           = "EUQ";
+    public static final String EVENKI                           = "EVK";
+    public static final String EVEN                             = "EVN";
+    public static final String EWE                              = "EWE";
+    public static final String FRENCH_ANTILLEAN                 = "FAN";
+    public static final String FARSI                            = "FAR";
+    public static final String FINNISH                          = "FIN";
+    public static final String FIJIAN                           = "FJI";
+    public static final String FLEMISH                          = "FLE";
+    public static final String FOREST_NENETS                    = "FNE";
+    public static final String FON                              = "FON";
+    public static final String FAROESE                          = "FOS";
+    public static final String FRENCH                           = "FRA";
+    public static final String FRISIAN                          = "FRI";
+    public static final String FRIULIAN                         = "FRL";
+    public static final String FUTA                             = "FTA";
+    public static final String FULANI                           = "FUL";
+    public static final String GA                               = "GAD";
+    public static final String GAELIC                           = "GAE";
+    public static final String GAGAUZ                           = "GAG";
+    public static final String GALICIAN                         = "GAL";
+    public static final String GARSHUNI                         = "GAR";
+    public static final String GARHWALI                         = "GAW";
+    public static final String GEEZ                             = "GEZ";
+    public static final String GILYAK                           = "GIL";
+    public static final String GUMUZ                            = "GMZ";
+    public static final String GONDI                            = "GON";
+    public static final String GREENLANDIC                      = "GRN";
+    public static final String GARO                             = "GRO";
+    public static final String GUARANI                          = "GUA";
+    public static final String GUJARATI                         = "GUJ";
+    public static final String HAITIAN                          = "HAI";
+    public static final String HALAM                            = "HAL";
+    public static final String HARAUTI                          = "HAR";
+    public static final String HAUSA                            = "HAU";
+    public static final String HAWAIIN                          = "HAW";
+    public static final String HAMMER_BANNA                     = "HBN";
+    public static final String HILIGAYNON                       = "HIL";
+    public static final String HINDI                            = "HIN";
+    public static final String HIGH_MARI                        = "HMA";
+    public static final String HINDKO                           = "HND";
+    public static final String HO                               = "HO";
+    public static final String HARARI                           = "HRI";
+    public static final String CROATIAN                         = "HRV";
+    public static final String HUNGARIAN                        = "HUN";
+    public static final String ARMENIAN                         = "HYE";
+    public static final String IGBO                             = "IBO";
+    public static final String IJO                              = "IJO";
+    public static final String ILOKANO                          = "ILO";
+    public static final String INDONESIAN                       = "IND";
+    public static final String INGUSH                           = "ING";
+    public static final String INUKTITUT                        = "INU";
+    public static final String PHONETIC_IPA                     = "IPPH";
+    public static final String IRISH                            = "IRI";
+    public static final String IRISH_TRADITIONAL                = "IRT";
+    public static final String ICELANDIC                        = "ISL";
+    public static final String INARI_SAMI                       = "ISM";
+    public static final String ITALIAN                          = "ITA";
+    public static final String HEBREW                           = "IWR";
+    public static final String JAVANESE                         = "JAV";
+    public static final String YIDDISH                          = "JII";
+    public static final String JAPANESE                         = "JAN";
+    public static final String JUDEZMO                          = "JUD";
+    public static final String JULA                             = "JUL";
+    public static final String KABARDIAN                        = "KAB";
+    public static final String KACHCHI                          = "KAC";
+    public static final String KALENJIN                         = "KAL";
+    public static final String KANNADA                          = "KAN";
+    public static final String KARACHAY                         = "KAR";
+    public static final String GEORGIAN                         = "KAT";
+    public static final String KAZAKH                           = "KAZ";
+    public static final String KEBENA                           = "KEB";
+    public static final String KHUTSURI_GEORGIAN                = "KGE";
+    public static final String KHAKASS                          = "KHA";
+    public static final String KHANTY_KAZIM                     = "KHK";
+    public static final String KHMER                            = "KHM";
+    public static final String KHANTY_SHURISHKAR                = "KHS";
+    public static final String KHANTY_VAKHI                     = "KHV";
+    public static final String KHOWAR                           = "KHW";
+    public static final String KIKUYU                           = "KIK";
+    public static final String KIRGHIZ                          = "KIR";
+    public static final String KISII                            = "KIS";
+    public static final String KOKNI                            = "KKN";
+    public static final String KALMYK                           = "KLM";
+    public static final String KAMBA                            = "KMB";
+    public static final String KUMAONI                          = "KMN";
+    public static final String KOMO                             = "KMO";
+    public static final String KOMSO                            = "KMS";
+    public static final String KANURI                           = "KNR";
+    public static final String KODAGU                           = "KOD";
+    public static final String KOREAN_OLD_HANGUL                = "KOH";
+    public static final String KONKANI                          = "KOK";
+    public static final String KIKONGO                          = "KON";
+    public static final String KOMI_PERMYAK                     = "KOP";
+    public static final String KOREAN                           = "KOR";
+    public static final String KOMI_ZYRIAN                      = "KOZ";
+    public static final String KPELLE                           = "KPL";
+    public static final String KRIO                             = "KRI";
+    public static final String KARAKALPAK                       = "KRK";
+    public static final String KARELIAN                         = "KRL";
+    public static final String KARAIM                           = "KRM";
+    public static final String KAREN                            = "KRN";
+    public static final String KOORETE                          = "KRT";
+    public static final String KASHMIRI                         = "KSH";
+    public static final String KHASI                            = "KSI";
+    public static final String KILDIN_SAMI                      = "KSM";
+    public static final String KUI                              = "KUI";
+    public static final String KULVI                            = "KUL";
+    public static final String KUMYK                            = "KUM";
+    public static final String KURDISH                          = "KUR";
+    public static final String KURUKH                           = "KUU";
+    public static final String KUY                              = "KUY";
+    public static final String KORYAK                           = "KYK";
+    public static final String LADIN                            = "LAD";
+    public static final String LAHULI                           = "LAH";
+    public static final String LAK                              = "LAK";
+    public static final String LAMBANI                          = "LAM";
+    public static final String LAO                              = "LAO";
+    public static final String LATIN                            = "LAT";
+    public static final String LAZ                              = "LAZ";
+    public static final String L_CREE                           = "LCR";
+    public static final String LADAKHI                          = "LDK";
+    public static final String LEZGI                            = "LEZ";
+    public static final String LINGALA                          = "LIN";
+    public static final String LOW_MARI                         = "LMA";
+    public static final String LIMBU                            = "LMB";
+    public static final String LOMWE                            = "LMW";
+    public static final String LOWER_SORBIAN                    = "LSB";
+    public static final String LULE_SAMI                        = "LSM";
+    public static final String LITHUANIAN                       = "LTH";
+    public static final String LUXEMBOURGISH                    = "LTZ";
+    public static final String LUBA                             = "LUB";
+    public static final String LUGANDA                          = "LUG";
+    public static final String LUHYA                            = "LUH";
+    public static final String LUO                              = "LUO";
+    public static final String LATVIAN                          = "LVI";
+    public static final String MAJANG                           = "MAJ";
+    public static final String MAKUA                            = "MAK";
+    public static final String MALAYALAM_TRADITIONAL            = "MAL";
+    public static final String MANSI                            = "MAN";
+    public static final String MAPUDUNGUN                       = "MAP";
+    public static final String MARATHI                          = "MAR";
+    public static final String MARWARI                          = "MAW";
+    public static final String MBUNDU                           = "MBN";
+    public static final String MANCHU                           = "MCH";
+    public static final String MOOSE_CREE                       = "MCR";
+    public static final String MENDE                            = "MDE";
+    public static final String MEEN                             = "MEN";
+    public static final String MIZO                             = "MIZ";
+    public static final String MACEDONIAN                       = "MKD";
+    public static final String MALE                             = "MLE";
+    public static final String MALAGASY                         = "MLG";
+    public static final String MALINKE                          = "MLN";
+    public static final String MALAYALAM_REFORMED               = "MLR";
+    public static final String MALAY                            = "MLY";
+    public static final String MANDINKA                         = "MND";
+    public static final String MONGOLIAN                        = "MNG";
+    public static final String MANIPURI                         = "MNI";
+    public static final String MANINKA                          = "MNK";
+    public static final String MANX_GAELIC                      = "MNX";
+    public static final String MOHAWK                           = "MOH";
+    public static final String MOKSHA                           = "MOK";
+    public static final String MOLDAVIAN                        = "MOL";
+    public static final String MON                              = "MON";
+    public static final String MOROCCAN                         = "MOR";
+    public static final String MAORI                            = "MRI";
+    public static final String MAITHILI                         = "MTH";
+    public static final String MALTESE                          = "MTS";
+    public static final String MUNDARI                          = "MUN";
+    public static final String NAGA_ASSAMESE                    = "NAG";
+    public static final String NANAI                            = "NAN";
+    public static final String NASKAPI                          = "NAS";
+    public static final String N_CREE                           = "NCR";
+    public static final String NDEBELE                          = "NDB";
+    public static final String NDONGA                           = "NDG";
+    public static final String NEPALI                           = "NEP";
+    public static final String NEWARI                           = "NEW";
+    public static final String NAGARI                           = "NGR";
+    public static final String NORWAY_HOUSE_CREE                = "NHC";
+    public static final String NISI                             = "NIS";
+    public static final String NIUEAN                           = "NIU";
+    public static final String NKOLE                            = "NKL";
+    public static final String NKO                              = "NKO";
+    public static final String DUTCH                            = "NLD";
+    public static final String NOGAI                            = "NOG";
+    public static final String NORWEGIAN                        = "NOR";
+    public static final String NORTHERN_SAMI                    = "NSM";
+    public static final String NORTHERN_TAI                     = "NTA";
+    public static final String ESPERANTO                        = "NTO";
+    public static final String NYNORSK                          = "NYN";
+    public static final String OCCITAN                          = "OCI";
+    public static final String OJI_CREE                         = "OCR";
+    public static final String OJIBWAY                          = "OJB";
+    public static final String ORIYA                            = "ORI";
+    public static final String OROMO                            = "ORO";
+    public static final String OSSETIAN                         = "OSS";
+    public static final String PALESTINIAN_ARAMAIC              = "PAA";
+    public static final String PALI                             = "PAL";
+    public static final String PUNJABI                          = "PAN";
+    public static final String PALPA                            = "PAP";
+    public static final String PASHTO                           = "PAS";
+    public static final String POLYTONIC_GREEK                  = "PGR";
+    public static final String FILIPINO                         = "PIL";
+    public static final String PALAUNG                          = "PLG";
+    public static final String POLISH                           = "PLK";
+    public static final String PROVENCAL                        = "PRO";
+    public static final String PORTUGUESE                       = "PTG";
+    public static final String CHIN                             = "QIN";
+    public static final String RAJASTHANI                       = "RAJ";
+    public static final String R_CREE                           = "RCR";
+    public static final String RUSSIAN_BURIAT                   = "RBU";
+    public static final String RIANG                            = "RIA";
+    public static final String RHAETO_ROMANIC                   = "RMS";
+    public static final String ROMANIAN                         = "ROM";
+    public static final String ROMANY                           = "ROY";
+    public static final String RUSYN                            = "RSY";
+    public static final String RUANDA                           = "RUA";
+    public static final String RUSSIAN                          = "RUS";
+    public static final String SADRI                            = "SAD";
+    public static final String SANSKRIT                         = "SAN";
+    public static final String SANTALI                          = "SAT";
+    public static final String SAYISI                           = "SAY";
+    public static final String SEKOTA                           = "SEK";
+    public static final String SELKUP                           = "SEL";
+    public static final String SANGO                            = "SGO";
+    public static final String SHAN                             = "SHN";
+    public static final String SIBE                             = "SIB";
+    public static final String SIDAMO                           = "SID";
+    public static final String SILTE_GURAGE                     = "SIG";
+    public static final String SKOLT_SAMI                       = "SKS";
+    public static final String SLOVAK                           = "SKY";
+    public static final String SLAVEY                           = "SLA";
+    public static final String SLOVENIAN                        = "SLV";
+    public static final String SOMALI                           = "SML";
+    public static final String SAMOAN                           = "SMO";
+    public static final String SENA                             = "SNA";
+    public static final String SINDHI                           = "SND";
+    public static final String SINHALESE                        = "SNH";
+    public static final String SONINKE                          = "SNK";
+    public static final String SODO_GURAGE                      = "SOG";
+    public static final String SOTHO                            = "SOT";
+    public static final String ALBANIAN                         = "SQI";
+    public static final String SERBIAN                          = "SRB";
+    public static final String SARAIKI                          = "SRK";
+    public static final String SERER                            = "SRR";
+    public static final String SOUTH_SLAVEY                     = "SSL";
+    public static final String SOUTHERN_SAMI                    = "SSM";
+    public static final String SURI                             = "SUR";
+    public static final String SVAN                             = "SVA";
+    public static final String SWEDISH                          = "SVE";
+    public static final String SWADAYA_ARAMAIC                  = "SWA";
+    public static final String SWAHILI                          = "SWK";
+    public static final String SWAZI                            = "SWZ";
+    public static final String SUTU                             = "SXT";
+    public static final String SYRIAC                           = "SYR";
+    public static final String TABASARAN                        = "TAB";
+    public static final String TAJIKI                           = "TAJ";
+    public static final String TAMIL                            = "TAM";
+    public static final String TATAR                            = "TAT";
+    public static final String TH_CREE                          = "TCR";
+    public static final String TELUGU                           = "TEL";
+    public static final String TONGAN                           = "TGN";
+    public static final String TIGRE                            = "TGR";
+    public static final String TIGRINYA                         = "TGY";
+    public static final String THAI                             = "THA";
+    public static final String TAHITIAN                         = "THT";
+    public static final String TIBETAN                          = "TIB";
+    public static final String TURKMEN                          = "TKM";
+    public static final String TEMNE                            = "TMN";
+    public static final String TSWANA                           = "TNA";
+    public static final String TUNDRA_NENETS                    = "TNE";
+    public static final String TONGA                            = "TNG";
+    public static final String TODO                             = "TOD";
+    public static final String TURKISH                          = "TRK";
+    public static final String TSONGA                           = "TSG";
+    public static final String TUROYO_ARAMAIC                   = "TUA";
+    public static final String TULU                             = "TUL";
+    public static final String TUVIN                            = "TUV";
+    public static final String TWI                              = "TWI";
+    public static final String UDMURT                           = "UDM";
+    public static final String UKRAINIAN                        = "UKR";
+    public static final String URDU                             = "URD";
+    public static final String UPPER_SORBIAN                    = "USB";
+    public static final String UYGHUR                           = "UYG";
+    public static final String UZBEK                            = "UZB";
+    public static final String VENDA                            = "VEN";
+    public static final String VIETNAMESE                       = "VIT";
+    public static final String WA                               = "WA";
+    public static final String WAGDI                            = "WAG";
+    public static final String WEST_CREE                        = "WCR";
+    public static final String WELSH                            = "WEL";
+    public static final String WILDCARD                         = "*";
+    public static final String WOLOF                            = "WLF";
+    public static final String TAI_LUE                          = "XBD";
+    public static final String XHOSA                            = "XHS";
+    public static final String SAKHA                            = "YAK";
+    public static final String YORUBA                           = "YBA";
+    public static final String Y_CREE                           = "YCR";
+    public static final String YI_CLASSIC                       = "YIC";
+    public static final String YI_MODERN                        = "YIM";
+    public static final String CHINESE_HONG_KONG_SAR            = "ZHH";
+    public static final String CHINESE_PHONETIC                 = "ZHP";
+    public static final String CHINESE_SIMPLIFIED               = "ZHS";
+    public static final String CHINESE_TRADITIONAL              = "ZHT";
+    public static final String ZANDE                            = "ZND";
+    public static final String ZULU                             = "ZUL";
+
+    public static boolean isDefault(String language) {
+        return (language != null) && language.equals(DEFAULT);
+    }
+
+    public static boolean isWildCard(String language) {
+        return (language != null) && language.equals(WILDCARD);
+    }
+
+    private OTFLanguage() {
+    }
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/OTFScript.java b/src/java/org/apache/fop/complexscripts/fonts/OTFScript.java
new file mode 100644 (file)
index 0000000..a43f1c5
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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;
+
+/**
+ * <p>Script tags defined by OTF specification. Note that this set and their
+ * values do not correspond with ISO 15924 or Unicode Script names.</p>
+ *
+ * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
+ */
+public final class OTFScript {
+    public static final String ARABIC                           = "arab";
+    public static final String ARMENIAN                         = "armn";
+    public static final String AVESTAN                          = "avst";
+    public static final String BALINESE                         = "bali";
+    public static final String BAMUM                            = "bamu";
+    public static final String BATAK                            = "batk";
+    public static final String BENGALI                          = "beng";
+    public static final String BENGALI_V2                       = "bng2";
+    public static final String BOPOMOFO                         = "bopo";
+    public static final String BRAILLE                          = "brai";
+    public static final String BRAHMI                           = "brah";
+    public static final String BUGINESE                         = "bugi";
+    public static final String BUHID                            = "buhd";
+    public static final String BYZANTINE_MUSIC                  = "byzm";
+    public static final String CANADIAN_SYLLABICS               = "cans";
+    public static final String CARIAN                           = "cari";
+    public static final String CHAKMA                           = "cakm";
+    public static final String CHAM                             = "cham";
+    public static final String CHEROKEE                         = "cher";
+    public static final String CJK_IDEOGRAPHIC                  = "hani";
+    public static final String COPTIC                           = "copt";
+    public static final String CYPRIOT_SYLLABARY                = "cprt";
+    public static final String CYRILLIC                         = "cyrl";
+    public static final String DEFAULT                          = "DFLT";
+    public static final String DESERET                          = "dsrt";
+    public static final String DEVANAGARI                       = "deva";
+    public static final String DEVANAGARI_V2                    = "dev2";
+    public static final String EGYPTIAN_HEIROGLYPHS             = "egyp";
+    public static final String ETHIOPIC                         = "ethi";
+    public static final String GEORGIAN                         = "geor";
+    public static final String GLAGOLITIC                       = "glag";
+    public static final String GOTHIC                           = "goth";
+    public static final String GREEK                            = "grek";
+    public static final String GUJARATI                         = "gujr";
+    public static final String GUJARATI_V2                      = "gjr2";
+    public static final String GURMUKHI                         = "guru";
+    public static final String GURMUKHI_V2                      = "gur2";
+    public static final String HANGUL                           = "hang";
+    public static final String HANGUL_JAMO                      = "jamo";
+    public static final String HANUNOO                          = "hano";
+    public static final String HEBREW                           = "hebr";
+    public static final String HIRAGANA                         = "kana";
+    public static final String IMPERIAL_ARAMAIC                 = "armi";
+    public static final String INSCRIPTIONAL_PAHLAVI            = "phli";
+    public static final String INSCRIPTIONAL_PARTHIAN           = "prti";
+    public static final String JAVANESE                         = "java";
+    public static final String KAITHI                           = "kthi";
+    public static final String KANNADA                          = "knda";
+    public static final String KANNADA_V2                       = "knd2";
+    public static final String KATAKANA                         = "kana";
+    public static final String KAYAH_LI                         = "kali";
+    public static final String KHAROSTHI                        = "khar";
+    public static final String KHMER                            = "khmr";
+    public static final String LAO                              = "lao";
+    public static final String LATIN                            = "latn";
+    public static final String LEPCHA                           = "lepc";
+    public static final String LIMBU                            = "limb";
+    public static final String LINEAR_B                         = "linb";
+    public static final String LISU                             = "lisu";
+    public static final String LYCIAN                           = "lyci";
+    public static final String LYDIAN                           = "lydi";
+    public static final String MALAYALAM                        = "mlym";
+    public static final String MALAYALAM_V2                     = "mlm2";
+    public static final String MANDAIC                          = "mand";
+    public static final String MATHEMATICAL_ALPHANUMERIC_SYMBOLS = "math";
+    public static final String MEITEI                           = "mtei";
+    public static final String MEROITIC_CURSIVE                 = "merc";
+    public static final String MEROITIC_HIEROGLYPHS             = "mero";
+    public static final String MONGOLIAN                        = "mong";
+    public static final String MUSICAL_SYMBOLS                  = "musc";
+    public static final String MYANMAR                          = "mymr";
+    public static final String NEW_TAI_LUE                      = "talu";
+    public static final String NKO                              = "nko";
+    public static final String OGHAM                            = "ogam";
+    public static final String OL_CHIKI                         = "olck";
+    public static final String OLD_ITALIC                       = "ital";
+    public static final String OLD_PERSIAN_CUNEIFORM            = "xpeo";
+    public static final String OLD_SOUTH_ARABIAN                = "sarb";
+    public static final String OLD_TURKIC                       = "orkh";
+    public static final String ORIYA                            = "orya";
+    public static final String ORIYA_V2                         = "ory2";
+    public static final String OSMANYA                          = "osma";
+    public static final String PHAGS_PA                         = "phag";
+    public static final String PHOENICIAN                       = "phnx";
+    public static final String REJANG                           = "rjng";
+    public static final String RUNIC                            = "runr";
+    public static final String SAMARITAN                        = "samr";
+    public static final String SAURASHTRA                       = "saur";
+    public static final String SHARADA                          = "shrd";
+    public static final String SHAVIAN                          = "shaw";
+    public static final String SINHALA                          = "sinh";
+    public static final String SORA_SOMPENG                     = "sora";
+    public static final String SUMERO_AKKADIAN_CUNEIFORM        = "xsux";
+    public static final String SUNDANESE                        = "sund";
+    public static final String SYLOTI_NAGRI                     = "sylo";
+    public static final String SYRIAC                           = "syrc";
+    public static final String TAGALOG                          = "tglg";
+    public static final String TAGBANWA                         = "tagb";
+    public static final String TAI_LE                           = "tale";
+    public static final String TAI_THAM                         = "lana";
+    public static final String TAI_VIET                         = "tavt";
+    public static final String TAKRI                            = "takr";
+    public static final String TAMIL                            = "taml";
+    public static final String TAMIL_V2                         = "tml2";
+    public static final String TELUGU                           = "telu";
+    public static final String TELUGU_V2                        = "tel2";
+    public static final String THAANA                           = "thaa";
+    public static final String THAI                             = "thai";
+    public static final String TIBETAN                          = "tibt";
+    public static final String TIFINAGH                         = "tfng";
+    public static final String UGARITIC_CUNEIFORM               = "ugar";
+    public static final String VAI                              = "vai";
+    public static final String WILDCARD                         = "*";
+    public static final String YI                               = "yi";
+
+    public static boolean isDefault(String script) {
+        return (script != null) && script.equals(DEFAULT);
+    }
+
+    public static boolean isWildCard(String script) {
+        return (script != null) && script.equals(DEFAULT);
+    }
+
+    private OTFScript() {
+    }
+}
index 296d2ef8e9821015f6ce835afc764417ee0687c7..305e8f78a563e1c8fe536afdde71550786cc5943 100644 (file)
@@ -160,6 +160,11 @@ public class Font implements Substitutable, Positionable {
         return metric.hasKerningInfo();
     }
 
+    /** @return true if the font has feature (i.e., at least one lookup matches) */
+    public boolean hasFeature(int tableType, String script, String language, String feature) {
+        return metric.hasFeature(tableType, script, language, feature);
+    }
+
     /**
      * Returns the font's kerning table
      * @return the kerning table
index ed59cf5af7006bf73848eaf0e165a3afd181a6e9..4b1fb1f1f16103426f642de26bb4254c5a52cdfd 100644 (file)
@@ -135,8 +135,8 @@ public interface FontMetrics {
     Rectangle getBoundingBox(int glyphIndex, int size);
 
     /**
-     * Indicates if the font has kering information.
-     * @return True, if kerning is available.
+     * Indicates if the font has kerning information.
+     * @return true if kerning is available.
      */
     boolean hasKerningInfo();
 
@@ -180,4 +180,14 @@ public interface FontMetrics {
      */
     int getStrikeoutThickness(int size);
 
+    /**
+     * Determine if metrics supports specific feature in specified font table.
+     *
+     * @param tableType type of table (GSUB, GPOS, ...), see GlyphTable.GLYPH_TABLE_TYPE_*
+     * @param script to qualify feature lookup
+     * @param language to qualify feature lookup
+     * @param feature to test
+     * @return true if feature supported (and has at least one lookup)
+     */
+    boolean hasFeature(int tableType, String script, String language, String feature);
 }
index 8c80eeb7c46ec2e6c2ebccca4e02745da47afc75..59bf9f14801e0c21018c1ba5968990059d83e1d1 100644 (file)
@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphTable;
 import org.apache.fop.complexscripts.util.CharScript;
 import org.apache.fop.traits.MinOptMax;
 import org.apache.fop.util.CharUtilities;
@@ -128,15 +129,14 @@ public class GlyphMapping {
         CharSequence mcs = font.performSubstitution(ics, script, language, associations);
 
         // 4. compute glyph position adjustments on (substituted) characters.
-        int[][] gpa;
+        int[][] gpa = null;
         if (font.performsPositioning()) {
             // handle GPOS adjustments
             gpa = font.performPositioning(mcs, script, language);
-        } else if (font.hasKerning()) {
+        }
+        if (useKerningAdjustments(font, script, language)) {
             // handle standard (non-GPOS) kerning adjustments
-            gpa = getKerningAdjustments(mcs, font);
-        } else {
-            gpa = null;
+            gpa = getKerningAdjustments(mcs, font, gpa);
         }
 
         // 5. reorder combining marks so that they precede (within the mapped char sequence) the
@@ -166,6 +166,10 @@ public class GlyphMapping {
                 associations);
     }
 
+    private static boolean useKerningAdjustments(final Font font, String script, String language) {
+        return font.hasKerning() && !font.hasFeature(GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, script, language, "kern");
+    }
+
     /**
      * Given a mapped character sequence MCS, obtain glyph position adjustments from the
      * font's kerning data.
@@ -174,7 +178,7 @@ public class GlyphMapping {
      * @param font applicable font
      * @return glyph position adjustments (or null if no kerning)
      */
-    private static int[][] getKerningAdjustments(CharSequence mcs, final Font font) {
+    private static int[][] getKerningAdjustments(CharSequence mcs, final Font font, int[][] gpa) {
         int nc = mcs.length();
         // extract kerning array
         int[] ka = new int[nc]; // kerning array
@@ -196,10 +200,12 @@ public class GlyphMapping {
         }
         // if non-zero kerning, then create and return glyph position adjustment array
         if (hasKerning) {
-            int[][] gpa = new int[nc][4];
+            if (gpa == null) {
+                gpa = new int[nc][4];
+            }
             for (int i = 0, n = nc; i < n; i++) {
                 if (i > 0) {
-                    gpa [i - 1][GlyphPositioningTable.Value.IDX_X_ADVANCE] = ka[i];
+                    gpa [i - 1][GlyphPositioningTable.Value.IDX_X_ADVANCE] += ka[i];
                 }
             }
             return gpa;
index acce1e0f9c874ad9be226db2f312203f2d95351b..fb0d8f06fa05189f7f016e9f074bd41873cbc444 100644 (file)
@@ -317,6 +317,12 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
         return realFont.getKerningInfo();
     }
 
+    /** {@inheritDoc} */
+    public boolean hasFeature(int tableType, String script, String language, String feature) {
+        load(true);
+        return realFont.hasFeature(tableType, script, language, feature);
+    }
+
     // ---- FontDescriptor interface ----
     /**
      * {@inheritDoc}
index 718a14fb24447118ae580dda875f8d9ee9a27a41..5b2f62a26363234c6cbc678a4104bc4f3a43d8cf 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.fop.apps.io.InternalResourceResolver;
 import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
 import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
 import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.GlyphTable;
 import org.apache.fop.complexscripts.fonts.Positionable;
 import org.apache.fop.complexscripts.fonts.Substitutable;
 import org.apache.fop.complexscripts.util.GlyphSequence;
@@ -651,6 +652,21 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
         return cb;
     }
 
+    @Override
+    public boolean hasFeature(int tableType, String script, String language, String feature) {
+        GlyphTable table;
+        if (tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) {
+            table = getGSUB();
+        } else if (tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) {
+            table = getGPOS();
+        } else if (tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) {
+            table = getGDEF();
+        } else {
+            table = null;
+        }
+        return (table != null) && table.hasFeature(script, language, feature);
+    }
+
     public Map<Integer, Integer> getWidthsMap() {
         return null;
     }
index 571c402bf26de9a64138e3573bda6216755760a1..3232d7605e80605fcd6c16f3087d831bc1e56bce 100644 (file)
@@ -103,6 +103,11 @@ public abstract class Typeface implements FontMetrics {
         return getAscender(size);
     }
 
+    /** {@inheritDoc} */
+    public boolean hasFeature(int tableType, String script, String language, String feature) {
+        return false;
+    }
+
     /**
      * Sets the font event listener that can be used to receive events about particular events
      * in this class.
diff --git a/test/java/org/apache/fop/complexscripts/layout/ComplexScriptsLayoutTestCase.java b/test/java/org/apache/fop/complexscripts/layout/ComplexScriptsLayoutTestCase.java
new file mode 100644 (file)
index 0000000..01dbaea
--- /dev/null
@@ -0,0 +1,388 @@
+/*
+ * 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.layout;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+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.ContentHandler;
+import org.xml.sax.SAXException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.AndFileFilter;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.io.filefilter.NameFileFilter;
+import org.apache.commons.io.filefilter.PrefixFileFilter;
+import org.apache.commons.io.filefilter.SuffixFileFilter;
+import org.apache.commons.io.filefilter.TrueFileFilter;
+
+import org.apache.fop.DebugHelper;
+
+import org.apache.fop.apps.EnvironmentProfile;
+import org.apache.fop.apps.EnvironmentalProfileFactory;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+import org.apache.fop.apps.FopConfBuilder;
+import org.apache.fop.apps.FopConfParser;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.apps.FopFactoryBuilder;
+import org.apache.fop.apps.FormattingResults;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.apps.PDFRendererConfBuilder;
+import org.apache.fop.apps.io.ResourceResolverFactory;
+import org.apache.fop.area.AreaTreeModel;
+import org.apache.fop.area.AreaTreeParser;
+import org.apache.fop.area.RenderPagesModel;
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventListener;
+import org.apache.fop.events.model.EventSeverity;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.intermediate.IFTester;
+import org.apache.fop.intermediate.TestAssistant;
+import org.apache.fop.layoutengine.ElementListCollector;
+import org.apache.fop.layoutengine.LayoutEngineCheck;
+import org.apache.fop.layoutengine.LayoutEngineChecksFactory;
+import org.apache.fop.layoutengine.LayoutResult;
+import org.apache.fop.layoutengine.TestFilesConfiguration;
+import org.apache.fop.layoutmgr.ElementListObserver;
+import org.apache.fop.render.Renderer;
+import org.apache.fop.render.intermediate.IFContext;
+import org.apache.fop.render.intermediate.IFRenderer;
+import org.apache.fop.render.intermediate.IFSerializer;
+import org.apache.fop.render.xml.XMLRenderer;
+import org.apache.fop.util.ConsoleEventListenerForTests;
+import org.apache.fop.util.DelegatingContentHandler;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Test complex script layout (end-to-end) functionality.
+ */
+@RunWith(Parameterized.class)
+public class ComplexScriptsLayoutTestCase {
+
+    private static final boolean DEBUG = false;
+    private static final String AREA_TREE_OUTPUT_DIRECTORY = "build/test-results/complexscripts";
+    private static File areaTreeOutputDir;
+
+    private TestAssistant testAssistant = new TestAssistant();
+    private LayoutEngineChecksFactory layoutEngineChecksFactory = new LayoutEngineChecksFactory();
+    private TestFilesConfiguration testConfig;
+    private File testFile;
+    private IFTester ifTester;
+    private TransformerFactory tfactory = TransformerFactory.newInstance();
+
+    public ComplexScriptsLayoutTestCase(TestFilesConfiguration testConfig, File testFile) {
+        this.testConfig = testConfig;
+        this.testFile = testFile;
+        this.ifTester = new IFTester(tfactory, areaTreeOutputDir);
+    }
+
+    @Parameters
+    public static Collection<Object[]> getParameters() throws IOException {
+        return getTestFiles();
+    }
+
+    @BeforeClass
+    public static void makeDirAndRegisterDebugHelper() throws IOException {
+        DebugHelper.registerStandardElementListObservers();
+        areaTreeOutputDir = new File(AREA_TREE_OUTPUT_DIRECTORY);
+        if (!areaTreeOutputDir.mkdirs() && !areaTreeOutputDir.exists()) {
+            throw new IOException("Failed to create the AT output directory at " + AREA_TREE_OUTPUT_DIRECTORY);
+        }
+    }
+
+    @Test
+    public void runTest() throws TransformerException, SAXException, IOException, ParserConfigurationException {
+        DOMResult domres = new DOMResult();
+        ElementListCollector elCollector = new ElementListCollector();
+        ElementListObserver.addObserver(elCollector);
+        Fop fop;
+        FopFactory effFactory;
+        EventsChecker eventsChecker = new EventsChecker(new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.WARN));
+        try {
+            Document testDoc = testAssistant.loadTestCase(testFile);
+            effFactory = getFopFactory(testConfig, testDoc);
+            // Setup Transformer to convert the testcase XML to XSL-FO
+            Transformer transformer = testAssistant.getTestcase2FOStylesheet().newTransformer();
+            Source src = new DOMSource(testDoc);
+            // Setup Transformer to convert the area tree to a DOM
+            TransformerHandler athandler;
+            athandler = testAssistant.getTransformerFactory().newTransformerHandler();
+            athandler.setResult(domres);
+            // Setup FOP for area tree rendering
+            FOUserAgent ua = effFactory.newFOUserAgent();
+            ua.getEventBroadcaster().addEventListener(eventsChecker);
+            XMLRenderer atrenderer = new XMLRenderer(ua);
+            Renderer targetRenderer = ua.getRendererFactory().createRenderer(ua, MimeConstants.MIME_PDF);
+            atrenderer.mimicRenderer(targetRenderer);
+            atrenderer.setContentHandler(athandler);
+            ua.setRendererOverride(atrenderer);
+            fop = effFactory.newFop(ua);
+            SAXResult fores = new SAXResult(fop.getDefaultHandler());
+            transformer.transform(src, fores);
+        } finally {
+            ElementListObserver.removeObserver(elCollector);
+        }
+        Document doc = (Document)domres.getNode();
+        if (areaTreeOutputDir != null) {
+            testAssistant.saveDOM(doc, new File(areaTreeOutputDir, testFile.getName() + ".at.xml"));
+        }
+        FormattingResults results = fop.getResults();
+        LayoutResult result = new LayoutResult(doc, elCollector, results);
+        checkAll(effFactory, testFile, result, eventsChecker);
+    }
+
+    private FopFactory getFopFactory(TestFilesConfiguration testConfig, Document testDoc)  throws SAXException, IOException {
+        EnvironmentProfile profile = EnvironmentalProfileFactory.createRestrictedIO(
+            testConfig.getTestDirectory().getParentFile().toURI(),
+            ResourceResolverFactory.createDefaultResourceResolver());
+        InputStream confStream =
+            new FopConfBuilder().setStrictValidation(true)
+                                .setFontBaseURI("test/resources/fonts/ttf/")
+                                .startRendererConfig(PDFRendererConfBuilder.class)
+                                  .startFontsConfig()
+                                    .startFont(null, "DejaVuLGCSerif.ttf")
+                                      .addTriplet("DejaVu LGC Serif", "normal", "normal")
+                                    .endFont()
+                                  .endFontConfig()
+                                .endRendererConfig().build();
+        FopFactoryBuilder builder =
+            new FopConfParser(confStream, new File(".").toURI(), profile).getFopFactoryBuilder();
+        // builder.setStrictFOValidation(isStrictValidation(testDoc));
+        // builder.getFontManager().setBase14KerningEnabled(isBase14KerningEnabled(testDoc));
+        return builder.build();
+    }
+
+    private void checkAll(FopFactory fopFactory, File testFile, LayoutResult result, EventsChecker eventsChecker) throws TransformerException {
+        Element testRoot = testAssistant.getTestRoot(testFile);
+        NodeList nodes;
+        nodes = testRoot.getElementsByTagName("at-checks");
+        if (nodes.getLength() > 0) {
+            Element atChecks = (Element)nodes.item(0);
+            doATChecks(atChecks, result);
+        }
+        nodes = testRoot.getElementsByTagName("if-checks");
+        if (nodes.getLength() > 0) {
+            Element ifChecks = (Element)nodes.item(0);
+            Document ifDocument = createIF(fopFactory, testFile, result.getAreaTree());
+            ifTester.doIFChecks(testFile.getName(), ifChecks, ifDocument);
+        }
+        nodes = testRoot.getElementsByTagName("event-checks");
+        if (nodes.getLength() > 0) {
+            Element eventChecks = (Element) nodes.item(0);
+            doEventChecks(eventChecks, eventsChecker);
+        }
+        eventsChecker.emitUncheckedEvents();
+    }
+
+    private Document createIF(FopFactory fopFactory, File testFile, Document areaTreeXML) throws TransformerException {
+        try {
+            FOUserAgent ua = fopFactory.newFOUserAgent();
+            ua.getEventBroadcaster().addEventListener(new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.WARN));
+            IFRenderer ifRenderer = new IFRenderer(ua);
+            IFSerializer serializer = new IFSerializer(new IFContext(ua));
+            DOMResult result = new DOMResult();
+            serializer.setResult(result);
+            ifRenderer.setDocumentHandler(serializer);
+            ua.setRendererOverride(ifRenderer);
+            FontInfo fontInfo = new FontInfo();
+            //Construct the AreaTreeModel that will received the individual pages
+            final AreaTreeModel treeModel = new RenderPagesModel(ua, null, fontInfo, null);
+            //Iterate over all intermediate files
+            AreaTreeParser parser = new AreaTreeParser();
+            ContentHandler handler = parser.getContentHandler(treeModel, ua);
+            DelegatingContentHandler proxy = new DelegatingContentHandler() {
+                public void endDocument() throws SAXException {
+                    super.endDocument();
+                    treeModel.endDocument();
+                }
+            };
+            proxy.setDelegateContentHandler(handler);
+            Transformer transformer = tfactory.newTransformer();
+            transformer.transform(new DOMSource(areaTreeXML), new SAXResult(proxy));
+            return (Document)result.getNode();
+        } catch (Exception e) {
+            throw new TransformerException("Error while generating intermediate format file: " + e.getMessage(), e);
+        }
+    }
+
+    private void doATChecks(Element checksRoot, LayoutResult result) {
+        List<LayoutEngineCheck> checks = layoutEngineChecksFactory.createCheckList(checksRoot);
+        if (checks.size() == 0) {
+            throw new RuntimeException("No available area tree check");
+        }
+        for (LayoutEngineCheck check : checks) {
+            check.check(result);
+        }
+    }
+
+    private void doEventChecks(Element eventChecks, EventsChecker eventsChecker) {
+        NodeList events = eventChecks.getElementsByTagName("event");
+        for (int i = 0; i < events.getLength(); i++) {
+            Element event = (Element) events.item(i);
+            NamedNodeMap attributes = event.getAttributes();
+            Map<String, String> params = new HashMap<String, String>();
+            String key = null;
+            for (int j = 0; j < attributes.getLength(); j++) {
+                Node attribute = attributes.item(j);
+                String name = attribute.getNodeName();
+                String value = attribute.getNodeValue();
+                if ("key".equals(name)) {
+                    key = value;
+                } else {
+                    params.put(name, value);
+                }
+            }
+            if (key == null) {
+                throw new RuntimeException("An event element must have a \"key\" attribute");
+            }
+            eventsChecker.checkEvent(key, params);
+        }
+    }
+
+    private static Collection<Object[]> getTestFiles(TestFilesConfiguration testConfig) {
+        File mainDir = testConfig.getTestDirectory();
+        IOFileFilter filter;
+        String single = testConfig.getSingleTest();
+        String startsWith = testConfig.getStartsWith();
+        if (single != null) {
+            filter = new NameFileFilter(single);
+        } else if (startsWith != null) {
+            filter = new PrefixFileFilter(startsWith);
+            filter = new AndFileFilter(filter, new SuffixFileFilter(testConfig.getFileSuffix()));
+        } else {
+            filter = new SuffixFileFilter(testConfig.getFileSuffix());
+        }
+        String testset = testConfig.getTestSet();
+        Collection<File> files = FileUtils.listFiles(new File(mainDir, testset), filter, TrueFileFilter.INSTANCE);
+        if (testConfig.hasPrivateTests()) {
+            Collection<File> privateFiles =
+                FileUtils.listFiles(new File(mainDir, "private-testcases"), filter, TrueFileFilter.INSTANCE);
+            files.addAll(privateFiles);
+        }
+        Collection<Object[]> parametersForJUnit4 = new ArrayList<Object[]>();
+        int index = 0;
+        for (File f : files) {
+            parametersForJUnit4.add(new Object[] { testConfig, f });
+            if (DEBUG) {
+                System.out.println(String.format("%3d %s", index++, f));
+            }
+        }
+        return parametersForJUnit4;
+    }
+
+    private static Collection<Object[]> getTestFiles() {
+        String testSet = System.getProperty("fop.complexscripts.testset");
+        testSet = (testSet != null ? testSet : "standard") + "-testcases";
+        return getTestFiles(testSet);
+    }
+
+    private static Collection<Object[]> getTestFiles(String testSetName) {
+        TestFilesConfiguration.Builder builder = new TestFilesConfiguration.Builder();
+        builder.testDir("test/resources/complexscripts/layout")
+               .singleProperty("fop.complexscripts.single")
+               .startsWithProperty("fop.complexscripts.starts-with")
+               .suffix(".xml")
+               .testSet(testSetName)
+               .privateTestsProperty("fop.complexscripts.private");
+        return getTestFiles(builder.build());
+    }
+
+    private static class EventsChecker implements EventListener {
+
+        private final List<Event> events = new ArrayList<Event>();
+        private final EventListener defaultListener;
+
+        public EventsChecker(EventListener fallbackListener) {
+            this.defaultListener = fallbackListener;
+        }
+
+        public void processEvent(Event event) {
+            events.add(event);
+        }
+
+        public void checkEvent(String expectedKey, Map<String, String> expectedParams) {
+            boolean eventFound = false;
+            for (Iterator<Event> iter = events.iterator(); !eventFound && iter.hasNext();) {
+                Event event = iter.next();
+                if (event.getEventKey().equals(expectedKey)) {
+                    eventFound = true;
+                    iter.remove();
+                    checkParameters(event, expectedParams);
+                }
+            }
+            if (!eventFound) {
+                fail("Event did not occur but was expected to: " + expectedKey + expectedParams);
+            }
+        }
+
+        private void checkParameters(Event event, Map<String, String> expectedParams) {
+            Map<String, Object> actualParams = event.getParams();
+            for (Map.Entry<String, String> expectedParam : expectedParams.entrySet()) {
+                assertTrue("Event \"" + event.getEventKey()
+                        + "\" is missing parameter \"" + expectedParam.getKey() + '"',
+                        actualParams.containsKey(expectedParam.getKey()));
+                assertEquals("Event \"" + event.getEventKey()
+                        + "\" has wrong value for parameter \"" + expectedParam.getKey() + "\";",
+                        actualParams.get(expectedParam.getKey()).toString(),
+                        expectedParam.getValue());
+            }
+        }
+
+        public void emitUncheckedEvents() {
+            for (Event event : events) {
+                defaultListener.processEvent(event);
+            }
+        }
+    }
+
+}
diff --git a/test/java/org/apache/fop/complexscripts/layout/ComplexScriptsLayoutTestSuite.java b/test/java/org/apache/fop/complexscripts/layout/ComplexScriptsLayoutTestSuite.java
new file mode 100644 (file)
index 0000000..e55ead6
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.layout;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * Test suite for running complex script layout tests under JUnit control.
+ */
+@RunWith(Suite.class)
+@SuiteClasses(ComplexScriptsLayoutTestCase.class)
+public class ComplexScriptsLayoutTestSuite {
+}
index 2b138de55b2f963cdc6e33970c0e39a5563da990..d13f88959b360766bba762decff43407b73ac2ca 100644 (file)
@@ -116,14 +116,12 @@ public class TestAssistant {
     }
 
     public FopFactory getFopFactory(Document testDoc) {
-        boolean base14KerningEnabled = isBase14KerningEnabled(testDoc);
-        boolean strictValidation = isStrictValidation(testDoc);
         EnvironmentProfile envProfile = EnvironmentalProfileFactory.createRestrictedIO(
                 testDir.getParentFile().toURI(),
                 ResourceResolverFactory.createDefaultResourceResolver());
         FopFactoryBuilder builder = new FopFactoryBuilder(envProfile);
-        builder.setStrictFOValidation(strictValidation);
-        builder.getFontManager().setBase14KerningEnabled(base14KerningEnabled);
+        builder.setStrictFOValidation(isStrictValidation(testDoc));
+        builder.getFontManager().setBase14KerningEnabled(isBase14KerningEnabled(testDoc));
         return builder.build();
     }
 
index bea54c5f88dd90233f9e479cd76e7edad58597ab..9883a9a7413d1c446f376a6d3f4208743cd3ecb8 100644 (file)
@@ -26,9 +26,9 @@ import org.apache.fop.check.ChecksFactory;
 /**
  * A factory class for creating {@link LayoutEngineCheck} instances.
  */
-final class LayoutEngineChecksFactory extends ChecksFactory<LayoutEngineCheck> {
+public final class LayoutEngineChecksFactory extends ChecksFactory<LayoutEngineCheck> {
 
-    LayoutEngineChecksFactory() {
+    public LayoutEngineChecksFactory() {
         registerCheckFactory("true", new CheckFactory<LayoutEngineCheck>() {
 
             public LayoutEngineCheck createCheck(Element element) {
diff --git a/test/resources/complexscripts/layout/standard-testcases/inline_kerning_jira2213.xml b/test/resources/complexscripts/layout/standard-testcases/inline_kerning_jira2213.xml
new file mode 100644 (file)
index 0000000..8eb4dc0
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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$ -->
+<testcase>
+  <info>
+    <p>
+      Test fix for https://issues.apache.org/jira/browse/FOP-2213.
+    </p>
+  </info>
+  <fo>
+    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
+      <fo:layout-master-set>
+        <fo:simple-page-master master-name="page"
+          page-height="300pt" page-width="400pt" margin="10pt">
+          <fo:region-body display-align="center"/>
+        </fo:simple-page-master>
+      </fo:layout-master-set>
+      <fo:page-sequence master-reference="page">
+        <fo:flow flow-name="xsl-region-body">
+          <fo:block font-size="200pt" text-align="center" font-family="DejaVu LGC Serif">Y.</fo:block>
+        </fo:flow>
+      </fo:page-sequence>
+    </fo:root>
+  </fo>
+  <checks>
+    <eval expected="2 Z2 -25600 Z5" xpath="(//word)[1]/@position-adjust"/>
+  </checks>
+</testcase>
diff --git a/test/resources/complexscripts/layout/testcase2checks.xsl b/test/resources/complexscripts/layout/testcase2checks.xsl
new file mode 100644 (file)
index 0000000..25dfcc8
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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$ -->
+<!-- This stylesheet extracts the checks from the testcase so the list of checks can be built in Java code. -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+<xsl:variable name="basic-checks" select="document('basic-checks.xml')/checks/*" />
+
+<xsl:template match="testcase">
+  <checks>
+    <xsl:apply-templates select="checks"/>
+    <xsl:apply-templates select="if-checks"/>
+    <xsl:apply-templates select="event-checks"/>
+  </checks>
+</xsl:template>
+
+<xsl:template match="checks">
+  <at-checks>
+    <xsl:copy-of select="$basic-checks" />
+    <xsl:copy-of select="*" />
+  </at-checks>
+</xsl:template>
+
+<xsl:template match="if-checks">
+  <if-checks>
+    <xsl:copy-of select="*"/>
+  </if-checks>
+</xsl:template>
+
+<xsl:template match="event-checks">
+  <event-checks>
+    <xsl:copy-of select="*"/>
+  </event-checks>
+</xsl:template>
+
+<xsl:template match="text()" />
+
+</xsl:stylesheet>
diff --git a/test/resources/complexscripts/layout/testcase2fo.xsl b/test/resources/complexscripts/layout/testcase2fo.xsl
new file mode 100644 (file)
index 0000000..522f53f
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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$ -->
+<!-- This stylesheet extracts the FO part from the testcase so it can be passed to FOP for layout. -->
+<!--
+Variable substitution:
+
+For any attribute value that starts with a "##" the stylesheet looks for an element with the variable 
+name under /testcase/variables, ex. "##img" looks for /testcase/variables/img and uses its element
+value as subsitution value.
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+  <xsl:template match="testcase">
+    <xsl:apply-templates select="fo/*" mode="copy"/>
+  </xsl:template>
+  
+  <xsl:template match="node()" mode="copy">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()" mode="copy"/>
+    </xsl:copy>
+  </xsl:template>
+  
+  <xsl:template match="@*" mode="copy">
+    <xsl:choose>
+      <xsl:when test="starts-with(., '##')">
+        <!-- variable substitution -->
+        <xsl:variable name="nodename" select="name()"/>
+        <xsl:variable name="varname" select="substring(., 3)"/>
+        <xsl:choose>
+          <xsl:when test="boolean(//variables/child::*[local-name() = $varname])">
+            <xsl:attribute name="{name(.)}">
+              <xsl:value-of select="//variables/child::*[local-name() = $varname]"/>
+            </xsl:attribute>
+          </xsl:when>
+          <xsl:otherwise>
+            <!-- if variable isn't defined, just copy -->
+            <xsl:copy-of select="." />
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:copy-of select="." />
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+</xsl:stylesheet>