You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TTXFile.java 149KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.complexscripts.fonts.ttx;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.nio.IntBuffer;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Comparator;
  25. import java.util.HashMap;
  26. import java.util.LinkedHashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Set;
  30. import java.util.Stack;
  31. import java.util.TreeMap;
  32. import java.util.Vector;
  33. import javax.xml.parsers.FactoryConfigurationError;
  34. import javax.xml.parsers.ParserConfigurationException;
  35. import javax.xml.parsers.SAXParser;
  36. import javax.xml.parsers.SAXParserFactory;
  37. import org.xml.sax.Attributes;
  38. import org.xml.sax.Locator;
  39. import org.xml.sax.SAXException;
  40. import org.xml.sax.helpers.DefaultHandler;
  41. import org.apache.commons.logging.Log;
  42. import org.apache.commons.logging.LogFactory;
  43. import org.apache.fop.complexscripts.fonts.GlyphClassTable;
  44. import org.apache.fop.complexscripts.fonts.GlyphCoverageTable;
  45. import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
  46. import org.apache.fop.complexscripts.fonts.GlyphMappingTable;
  47. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
  48. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable.Anchor;
  49. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable.MarkAnchor;
  50. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable.PairValues;
  51. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable.Value;
  52. import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
  53. import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable.Ligature;
  54. import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable.LigatureSet;
  55. import org.apache.fop.complexscripts.fonts.GlyphSubtable;
  56. import org.apache.fop.complexscripts.fonts.GlyphTable;
  57. import org.apache.fop.complexscripts.fonts.GlyphTable.RuleLookup;
  58. import org.apache.fop.complexscripts.util.GlyphSequence;
  59. import org.apache.fop.complexscripts.util.UTF32;
  60. import org.apache.fop.util.CharUtilities;
  61. // CSOFF: LineLengthCheck
  62. /**
  63. * This class supports a subset of the <code>TTX</code> file as produced by the Adobe FLEX
  64. * SDK (AFDKO). In particular, it is used to parse a <code>TTX</code> file in order to
  65. * extract character to glyph code mapping data, glyph definition data, glyph substitution
  66. * data, and glyph positioning data.
  67. *
  68. * <code>TTX</code> files are used in FOP for testing and debugging purposes only. Such
  69. * files are used to represent font data employed by complex script processing, and
  70. * normally extracted directly from an opentype (or truetype) file. However, due to
  71. * copyright restrictions, it is not possible to include most opentype (or truetype) font
  72. * files directly in the FOP distribution. In such cases, <code>TTX</code> files are used
  73. * to distribute a subset of the complex script advanced table information contained in
  74. * certain font files to facilitate testing.
  75. */
  76. public class TTXFile {
  77. /** logging instance */
  78. private static final Log log = LogFactory.getLog(TTXFile.class); // CSOK: ConstantNameCheck
  79. /** default script tag */
  80. private static final String DEFAULT_SCRIPT_TAG = "dflt";
  81. /** default language tag */
  82. private static final String DEFAULT_LANGUAGE_TAG = "dflt";
  83. /** ttxfile cache */
  84. private static Map<String, TTXFile> cache = new HashMap<String, TTXFile>();
  85. // transient parsing state
  86. private Locator locator; // current document locator
  87. private Stack<String[]> elements; // stack of ttx elements being parsed
  88. private Map<String, Integer> glyphIds; // map of glyph names to glyph identifiers
  89. private List<int[]> cmapEntries; // list of <charCode,glyphCode> pairs
  90. private Vector<int[]> hmtxEntries; // vector of <width,lsb> pairs
  91. private Map<String, Integer> glyphClasses; // map of glyph names to glyph classes
  92. private Map<String, Map<String, List<String>>> scripts; // map of script tag to Map<language-tag,List<features-id>>>
  93. private Map<String, List<String>> languages; // map of language tag to List<feature-id>
  94. private Map<String, Object[]> features; // map of feature id to Object[2] : { feature-tag, List<lookup-id> }
  95. private List<String> languageFeatures; // list of language system feature ids, where first is (possibly null) required feature id
  96. private List<String> featureLookups; // list of lookup ids for feature being constructed
  97. private List<Integer> coverageEntries; // list of entries for coverage table being constructed
  98. private Map<String, GlyphCoverageTable> coverages; // map of coverage table keys to coverage tables
  99. private List subtableEntries; // list of lookup subtable entries
  100. private List<GlyphSubtable> subtables; // list of constructed subtables
  101. private List<Integer> alternates; // list of alternates in alternate set being constructed
  102. private List<Ligature> ligatures; // list of ligatures in ligature set being constructed
  103. private List<Integer> substitutes; // list of substitutes in (multiple substitution) sequence being constructed
  104. private List<PairValues> pairs; // list of pair value records being constructed
  105. private List<PairValues[]> pairSets; // list of pair value sets (as arrays) being constructed
  106. private List<Anchor> anchors; // list of anchors of base|mark|component record being constructed
  107. private List<Anchor[]> components; // list of ligature component anchors being constructed
  108. private List<MarkAnchor> markAnchors; // list of mark anchors being constructed
  109. private List<Anchor[]> baseOrMarkAnchors; // list of base|mark2 anchors being constructed
  110. private List<Anchor[][]> ligatureAnchors; // list of ligature anchors being constructed
  111. private List<Anchor[]> attachmentAnchors; // list of entry|exit attachment anchors being constructed
  112. private List<RuleLookup> ruleLookups; // list of rule lookups being constructed
  113. private int glyphIdMax; // maximum glyph id
  114. private int cmPlatform; // plaform id of cmap being constructed
  115. private int cmEncoding; // plaform id of cmap being constructed
  116. private int cmLanguage; // plaform id of cmap being constructed
  117. private int flIndex; // index of feature being constructed
  118. private int flSequence; // feature sequence within feature list
  119. private int ltIndex; // index of lookup table being constructed
  120. private int ltSequence; // lookup sequence within table
  121. private int ltFlags; // flags of current lookup being constructed
  122. private int stSequence; // subtable sequence number within lookup
  123. private int stFormat; // format of current subtable being constructed
  124. private int ctFormat; // format of coverage table being constructed
  125. private int ctIndex; // index of coverage table being constructed
  126. private int rlSequence; // rule lookup sequence index
  127. private int rlLookup; // rule lookup lookup index
  128. private int psIndex; // pair set index
  129. private int vf1; // value format 1 (used with pair pos and single pos)
  130. private int vf2; // value format 2 (used with pair pos)
  131. private int g2; // glyph id 2 (used with pair pos)
  132. private int xCoord; // x coordinate of anchor being constructed
  133. private int yCoord; // y coordinate of anchor being constructed
  134. private int markClass; // mark class of mark anchor being constructed
  135. private String defaultScriptTag; // tag of default script
  136. private String scriptTag; // tag of script being constructed
  137. private String defaultLanguageTag; // tag of default language system
  138. private String languageTag; // tag of language system being constructed
  139. private String featureTag; // tag of feature being constructed
  140. private Value v1; // positioining value 1
  141. private Value v2; // positioining value 2
  142. // resultant state
  143. private int upem; // units per em
  144. private Map<Integer, Integer> cmap; // constructed character map
  145. private Map<Integer, Integer> gmap; // constructed glyph map
  146. private int[][] hmtx; // constructed horizontal metrics - array of design { width, lsb } pairs, indexed by glyph code
  147. private int[] widths; // pdf normalized widths (millipoints)
  148. private GlyphDefinitionTable gdef; // constructed glyph definition table
  149. private GlyphSubstitutionTable gsub; // constructed glyph substitution table
  150. private GlyphPositioningTable gpos; // constructed glyph positioning table
  151. public TTXFile() {
  152. elements = new Stack<String[]>();
  153. glyphIds = new HashMap<String, Integer>();
  154. cmapEntries = new ArrayList<int[]>();
  155. hmtxEntries = new Vector<int[]>();
  156. glyphClasses = new HashMap<String, Integer>();
  157. scripts = new HashMap<String, Map<String, List<String>>>();
  158. languages = new HashMap<String, List<String>>();
  159. features = new HashMap<String, Object[]>();
  160. languageFeatures = new ArrayList<String>();
  161. featureLookups = new ArrayList<String>();
  162. coverageEntries = new ArrayList<Integer>();
  163. coverages = new HashMap<String, GlyphCoverageTable>();
  164. subtableEntries = new ArrayList();
  165. subtables = new ArrayList<GlyphSubtable>();
  166. alternates = new ArrayList<Integer>();
  167. ligatures = new ArrayList<Ligature>();
  168. substitutes = new ArrayList<Integer>();
  169. pairs = new ArrayList<PairValues>();
  170. pairSets = new ArrayList<PairValues[]>();
  171. anchors = new ArrayList<Anchor>();
  172. markAnchors = new ArrayList<MarkAnchor>();
  173. baseOrMarkAnchors = new ArrayList<Anchor[]>();
  174. ligatureAnchors = new ArrayList<Anchor[][]>();
  175. components = new ArrayList<Anchor[]>();
  176. attachmentAnchors = new ArrayList<Anchor[]>();
  177. ruleLookups = new ArrayList<RuleLookup>();
  178. glyphIdMax = -1;
  179. cmPlatform = -1;
  180. cmEncoding = -1;
  181. cmLanguage = -1;
  182. flIndex = -1;
  183. flSequence = 0;
  184. ltIndex = -1;
  185. ltSequence = 0;
  186. ltFlags = 0;
  187. stSequence = 0;
  188. stFormat = 0;
  189. ctFormat = -1;
  190. ctIndex = -1;
  191. rlSequence = -1;
  192. rlLookup = -1;
  193. psIndex = -1;
  194. vf1 = -1;
  195. vf2 = -1;
  196. g2 = -1;
  197. xCoord = Integer.MIN_VALUE;
  198. yCoord = Integer.MIN_VALUE;
  199. markClass = -1;
  200. defaultScriptTag = DEFAULT_SCRIPT_TAG;
  201. scriptTag = null;
  202. defaultLanguageTag = DEFAULT_LANGUAGE_TAG;
  203. languageTag = null;
  204. featureTag = null;
  205. v1 = null;
  206. v2 = null;
  207. upem = -1;
  208. }
  209. public void parse(String filename) {
  210. parse(new File(filename));
  211. }
  212. public void parse(File f) {
  213. assert f != null;
  214. try {
  215. SAXParserFactory spf = SAXParserFactory.newInstance();
  216. SAXParser sp = spf.newSAXParser();
  217. sp.parse(f, new Handler());
  218. } catch (FactoryConfigurationError e) {
  219. throw new RuntimeException(e.getMessage());
  220. } catch (ParserConfigurationException e) {
  221. throw new RuntimeException(e.getMessage());
  222. } catch (SAXException e) {
  223. throw new RuntimeException(e.getMessage());
  224. } catch (IOException e) {
  225. throw new RuntimeException(e.getMessage());
  226. }
  227. }
  228. public GlyphSequence mapCharsToGlyphs(String s) {
  229. Integer[] ca = UTF32.toUTF32(s, 0, true);
  230. int ng = ca.length;
  231. IntBuffer cb = IntBuffer.allocate(ng);
  232. IntBuffer gb = IntBuffer.allocate(ng);
  233. for (Integer c : ca) {
  234. int g = mapCharToGlyph((int) c);
  235. if (g >= 0) {
  236. cb.put(c);
  237. gb.put(g);
  238. } else {
  239. throw new IllegalArgumentException("character " + CharUtilities.format(c) + " has no corresponding glyph");
  240. }
  241. }
  242. cb.rewind();
  243. gb.rewind();
  244. return new GlyphSequence(cb, gb, null);
  245. }
  246. public int mapCharToGlyph(int c) {
  247. if (cmap != null) {
  248. Integer g = cmap.get(Integer.valueOf(c));
  249. if (g != null) {
  250. return (int) g;
  251. } else {
  252. return -1;
  253. }
  254. } else {
  255. return -1;
  256. }
  257. }
  258. public int getGlyph(String gid) {
  259. return mapGlyphId0(gid);
  260. }
  261. public GlyphSequence getGlyphSequence(String[] gids) {
  262. assert gids != null;
  263. int ng = gids.length;
  264. IntBuffer cb = IntBuffer.allocate(ng);
  265. IntBuffer gb = IntBuffer.allocate(ng);
  266. for (String gid : gids) {
  267. int g = mapGlyphId0(gid);
  268. if (g >= 0) {
  269. int c = mapGlyphIdToChar(gid);
  270. if (c < 0) {
  271. c = CharUtilities.NOT_A_CHARACTER;
  272. }
  273. cb.put(c);
  274. gb.put(g);
  275. } else {
  276. throw new IllegalArgumentException("unmapped glyph id \"" + gid + "\"");
  277. }
  278. }
  279. cb.rewind();
  280. gb.rewind();
  281. return new GlyphSequence(cb, gb, null);
  282. }
  283. public int[] getWidths(String[] gids) {
  284. assert gids != null;
  285. int ng = gids.length;
  286. int[] widths = new int [ ng ];
  287. int i = 0;
  288. for (String gid : gids) {
  289. int g = mapGlyphId0(gid);
  290. int w = 0;
  291. if (g >= 0) {
  292. if ((hmtx != null) && (g < hmtx.length)) {
  293. int[] mtx = hmtx [ g ];
  294. assert mtx != null;
  295. assert mtx.length > 0;
  296. w = mtx[0];
  297. }
  298. }
  299. widths [ i++ ] = w;
  300. }
  301. assert i == ng;
  302. return widths;
  303. }
  304. public int[] getWidths() {
  305. if (this.widths == null) {
  306. if ((hmtx != null) && (upem > 0)) {
  307. int[] widths = new int [ hmtx.length ];
  308. for (int i = 0, n = widths.length; i < n; i++) {
  309. widths [ i ] = getPDFWidth(hmtx [ i ] [ 0 ], upem);
  310. }
  311. this.widths = widths;
  312. }
  313. }
  314. return this.widths;
  315. }
  316. public static int getPDFWidth(int tw, int upem) {
  317. // N.B. The following is copied (with minor edits) from TTFFile to insure same results
  318. int pw;
  319. if (tw < 0) {
  320. long rest1 = tw % upem;
  321. long storrest = 1000 * rest1;
  322. long ledd2 = (storrest != 0) ? (rest1 / storrest) : 0;
  323. pw = -((-1000 * tw) / upem - (int) ledd2);
  324. } else {
  325. pw = (tw / upem) * 1000 + ((tw % upem) * 1000) / upem;
  326. }
  327. return pw;
  328. }
  329. public GlyphDefinitionTable getGDEF() {
  330. return gdef;
  331. }
  332. public GlyphSubstitutionTable getGSUB() {
  333. return gsub;
  334. }
  335. public GlyphPositioningTable getGPOS() {
  336. return gpos;
  337. }
  338. public static synchronized TTXFile getFromCache(String filename) {
  339. assert cache != null;
  340. TTXFile f;
  341. if ((f = (TTXFile) cache.get(filename)) == null) {
  342. f = new TTXFile();
  343. f.parse(filename);
  344. cache.put(filename, f);
  345. }
  346. return f;
  347. }
  348. public static synchronized void clearCache() {
  349. cache.clear();
  350. }
  351. private final class Handler extends DefaultHandler {
  352. private Handler() {
  353. }
  354. @Override
  355. public void startDocument() {
  356. }
  357. @Override
  358. public void endDocument() {
  359. }
  360. @Override
  361. public void setDocumentLocator(Locator locator) {
  362. TTXFile.this.locator = locator;
  363. }
  364. @Override
  365. public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
  366. String[] en = makeExpandedName(uri, localName, qName);
  367. if (en[0] != null) {
  368. unsupportedElement(en);
  369. } else if (en[1].equals("Alternate")) {
  370. String[] pn = new String[] { null, "AlternateSet" };
  371. if (isParent(pn)) {
  372. String glyph = attrs.getValue("glyph");
  373. if (glyph == null) {
  374. missingRequiredAttribute(en, "glyph");
  375. }
  376. int gid = mapGlyphId(glyph, en);
  377. alternates.add(Integer.valueOf(gid));
  378. } else {
  379. notPermittedInElementContext(en, getParent(), pn);
  380. }
  381. } else if (en[1].equals("AlternateSet")) {
  382. String[] pn = new String[] { null, "AlternateSubst" };
  383. if (isParent(pn)) {
  384. String glyph = attrs.getValue("glyph");
  385. if (glyph == null) {
  386. missingRequiredAttribute(en, "glyph");
  387. }
  388. int gid = mapGlyphId(glyph, en);
  389. coverageEntries.add(Integer.valueOf(gid));
  390. } else {
  391. notPermittedInElementContext(en, getParent(), pn);
  392. }
  393. } else if (en[1].equals("AlternateSubst")) {
  394. String[] pn = new String[] { null, "Lookup" };
  395. if (isParent(pn)) {
  396. String index = attrs.getValue("index");
  397. if (index == null) {
  398. missingRequiredAttribute(en, "index");
  399. }
  400. String format = attrs.getValue("Format");
  401. int sf = -1;
  402. if (format == null) {
  403. missingRequiredAttribute(en, "Format");
  404. } else {
  405. sf = Integer.parseInt(format);
  406. switch (sf) {
  407. case 1:
  408. break;
  409. default:
  410. unsupportedFormat(en, sf);
  411. break;
  412. }
  413. }
  414. assertCoverageClear();
  415. ctIndex = 0;
  416. ctFormat = 1;
  417. assertSubtableClear();
  418. assert sf >= 0;
  419. stFormat = sf;
  420. } else {
  421. notPermittedInElementContext(en, getParent(), pn);
  422. }
  423. } else if (en[1].equals("BacktrackCoverage")) {
  424. String[] pn1 = new String[] { null, "ChainContextSubst" };
  425. String[] pn2 = new String[] { null, "ChainContextPos" };
  426. String[][] pnx = new String[][] { pn1, pn2 };
  427. if (isParent(pnx)) {
  428. String index = attrs.getValue("index");
  429. int ci = -1;
  430. if (index == null) {
  431. missingRequiredAttribute(en, "index");
  432. } else {
  433. ci = Integer.parseInt(index);
  434. }
  435. String format = attrs.getValue("Format");
  436. int cf = -1;
  437. if (format == null) {
  438. missingRequiredAttribute(en, "Format");
  439. } else {
  440. cf = Integer.parseInt(format);
  441. switch (cf) {
  442. case 1:
  443. case 2:
  444. break;
  445. default:
  446. unsupportedFormat(en, cf);
  447. break;
  448. }
  449. }
  450. assertCoverageClear();
  451. ctIndex = ci;
  452. ctFormat = cf;
  453. } else {
  454. notPermittedInElementContext(en, getParent(), pnx);
  455. }
  456. } else if (en[1].equals("BaseAnchor")) {
  457. String[] pn = new String[] { null, "BaseRecord" };
  458. if (isParent(pn)) {
  459. String index = attrs.getValue("index");
  460. if (index == null) {
  461. missingRequiredAttribute(en, "index");
  462. }
  463. String format = attrs.getValue("Format");
  464. if (format == null) {
  465. missingRequiredAttribute(en, "Format");
  466. }
  467. assert xCoord == Integer.MIN_VALUE;
  468. assert yCoord == Integer.MIN_VALUE;
  469. } else {
  470. notPermittedInElementContext(en, getParent(), pn);
  471. }
  472. } else if (en[1].equals("BaseArray")) {
  473. String[] pn = new String[] { null, "MarkBasePos" };
  474. if (!isParent(pn)) {
  475. notPermittedInElementContext(en, getParent(), pn);
  476. }
  477. } else if (en[1].equals("BaseCoverage")) {
  478. String[] pn = new String[] { null, "MarkBasePos" };
  479. if (isParent(pn)) {
  480. String format = attrs.getValue("Format");
  481. int cf = -1;
  482. if (format == null) {
  483. missingRequiredAttribute(en, "Format");
  484. } else {
  485. cf = Integer.parseInt(format);
  486. switch (cf) {
  487. case 1:
  488. case 2:
  489. break;
  490. default:
  491. unsupportedFormat(en, cf);
  492. break;
  493. }
  494. }
  495. assertCoverageClear();
  496. ctIndex = 0;
  497. ctFormat = cf;
  498. } else {
  499. notPermittedInElementContext(en, getParent(), pn);
  500. }
  501. } else if (en[1].equals("BaseRecord")) {
  502. String[] pn = new String[] { null, "BaseArray" };
  503. if (isParent(pn)) {
  504. String index = attrs.getValue("index");
  505. if (index == null) {
  506. missingRequiredAttribute(en, "index");
  507. }
  508. } else {
  509. notPermittedInElementContext(en, getParent(), pn);
  510. }
  511. } else if (en[1].equals("ChainContextPos") || en[1].equals("ChainContextSubst")) {
  512. String[] pn = new String[] { null, "Lookup" };
  513. if (isParent(pn)) {
  514. String index = attrs.getValue("index");
  515. if (index == null) {
  516. missingRequiredAttribute(en, "index");
  517. }
  518. String format = attrs.getValue("Format");
  519. int sf = -1;
  520. if (format == null) {
  521. missingRequiredAttribute(en, "Format");
  522. } else {
  523. sf = Integer.parseInt(format);
  524. switch (sf) {
  525. case 1:
  526. case 2:
  527. case 3:
  528. break;
  529. default:
  530. unsupportedFormat(en, sf);
  531. break;
  532. }
  533. }
  534. assertSubtableClear();
  535. assert sf >= 0;
  536. stFormat = sf;
  537. } else {
  538. notPermittedInElementContext(en, getParent(), pn);
  539. }
  540. } else if (en[1].equals("Class")) {
  541. String[] pn = new String[] { null, "MarkRecord" };
  542. if (isParent(pn)) {
  543. String value = attrs.getValue("value");
  544. int v = -1;
  545. if (value == null) {
  546. missingRequiredAttribute(en, "value");
  547. } else {
  548. v = Integer.parseInt(value);
  549. }
  550. assert markClass == -1;
  551. markClass = v;
  552. } else {
  553. notPermittedInElementContext(en, getParent(), pn);
  554. }
  555. } else if (en[1].equals("ClassDef")) {
  556. String[] pn1 = new String[] { null, "GlyphClassDef" };
  557. String[] pn2 = new String[] { null, "MarkAttachClassDef" };
  558. String[][] pnx = new String[][] { pn1, pn2 };
  559. if (isParent(pnx)) {
  560. String glyph = attrs.getValue("glyph");
  561. if (glyph == null) {
  562. missingRequiredAttribute(en, "glyph");
  563. }
  564. String glyphClass = attrs.getValue("class");
  565. if (glyphClass == null) {
  566. missingRequiredAttribute(en, "class");
  567. }
  568. if (!glyphIds.containsKey(glyph)) {
  569. unsupportedGlyph(en, glyph);
  570. } else if (isParent(pn1)) {
  571. if (glyphClasses.containsKey(glyph)) {
  572. duplicateGlyphClass(en, glyph, glyphClass);
  573. } else {
  574. glyphClasses.put(glyph, Integer.parseInt(glyphClass));
  575. }
  576. } else if (isParent(pn2)) {
  577. if (glyphClasses.containsKey(glyph)) {
  578. duplicateGlyphClass(en, glyph, glyphClass);
  579. } else {
  580. glyphClasses.put(glyph, Integer.parseInt(glyphClass));
  581. }
  582. }
  583. } else {
  584. notPermittedInElementContext(en, getParent(), pnx);
  585. }
  586. } else if (en[1].equals("ComponentRecord")) {
  587. String[] pn = new String[] { null, "LigatureAttach" };
  588. if (isParent(pn)) {
  589. String index = attrs.getValue("index");
  590. if (index == null) {
  591. missingRequiredAttribute(en, "index");
  592. }
  593. assert anchors.size() == 0;
  594. } else {
  595. notPermittedInElementContext(en, getParent(), pn);
  596. }
  597. } else if (en[1].equals("Coverage")) {
  598. String[] pn1 = new String[] { null, "CursivePos" };
  599. String[] pn2 = new String[] { null, "LigCaretList" };
  600. String[] pn3 = new String[] { null, "MultipleSubst" };
  601. String[] pn4 = new String[] { null, "PairPos" };
  602. String[] pn5 = new String[] { null, "SinglePos" };
  603. String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5 };
  604. if (isParent(pnx)) {
  605. String format = attrs.getValue("Format");
  606. int cf = -1;
  607. if (format == null) {
  608. missingRequiredAttribute(en, "Format");
  609. } else {
  610. cf = Integer.parseInt(format);
  611. switch (cf) {
  612. case 1:
  613. case 2:
  614. break;
  615. default:
  616. unsupportedFormat(en, cf);
  617. break;
  618. }
  619. }
  620. assertCoverageClear();
  621. ctIndex = 0;
  622. ctFormat = cf;
  623. } else {
  624. notPermittedInElementContext(en, getParent(), pnx);
  625. }
  626. } else if (en[1].equals("CursivePos")) {
  627. String[] pn = new String[] { null, "Lookup" };
  628. if (isParent(pn)) {
  629. String index = attrs.getValue("index");
  630. if (index == null) {
  631. missingRequiredAttribute(en, "index");
  632. }
  633. String format = attrs.getValue("Format");
  634. int sf = -1;
  635. if (format == null) {
  636. missingRequiredAttribute(en, "Format");
  637. } else {
  638. sf = Integer.parseInt(format);
  639. switch (sf) {
  640. case 1:
  641. break;
  642. default:
  643. unsupportedFormat(en, sf);
  644. break;
  645. }
  646. }
  647. assertSubtableClear();
  648. assert sf >= 0;
  649. stFormat = sf;
  650. assert attachmentAnchors.size() == 0;
  651. } else {
  652. notPermittedInElementContext(en, getParent(), pn);
  653. }
  654. } else if (en[1].equals("DefaultLangSys")) {
  655. String[] pn = new String[] { null, "Script" };
  656. if (!isParent(pn)) {
  657. notPermittedInElementContext(en, getParent(), pn);
  658. } else {
  659. assertLanguageFeaturesClear();
  660. assert languageTag == null;
  661. languageTag = defaultLanguageTag;
  662. }
  663. } else if (en[1].equals("EntryAnchor")) {
  664. String[] pn = new String[] { null, "EntryExitRecord" };
  665. if (isParent(pn)) {
  666. String format = attrs.getValue("Format");
  667. if (format == null) {
  668. missingRequiredAttribute(en, "Format");
  669. }
  670. assert xCoord == Integer.MIN_VALUE;
  671. assert yCoord == Integer.MIN_VALUE;
  672. } else {
  673. notPermittedInElementContext(en, getParent(), pn);
  674. }
  675. } else if (en[1].equals("EntryExitRecord")) {
  676. String[] pn = new String[] { null, "CursivePos" };
  677. if (isParent(pn)) {
  678. String index = attrs.getValue("index");
  679. if (index == null) {
  680. missingRequiredAttribute(en, "index");
  681. }
  682. } else {
  683. notPermittedInElementContext(en, getParent(), pn);
  684. }
  685. } else if (en[1].equals("ExitAnchor")) {
  686. String[] pn = new String[] { null, "EntryExitRecord" };
  687. if (isParent(pn)) {
  688. String format = attrs.getValue("Format");
  689. if (format == null) {
  690. missingRequiredAttribute(en, "Format");
  691. }
  692. assert xCoord == Integer.MIN_VALUE;
  693. assert yCoord == Integer.MIN_VALUE;
  694. } else {
  695. notPermittedInElementContext(en, getParent(), pn);
  696. }
  697. } else if (en[1].equals("Feature")) {
  698. String[] pn = new String[] { null, "FeatureRecord" };
  699. if (!isParent(pn)) {
  700. notPermittedInElementContext(en, getParent(), pn);
  701. } else {
  702. assertFeatureLookupsClear();
  703. }
  704. } else if (en[1].equals("FeatureIndex")) {
  705. String[] pn1 = new String[] { null, "DefaultLangSys" };
  706. String[] pn2 = new String[] { null, "LangSys" };
  707. String[][] pnx = new String[][] { pn1, pn2 };
  708. if (isParent(pnx)) {
  709. String index = attrs.getValue("index");
  710. if (index == null) {
  711. missingRequiredAttribute(en, "index");
  712. }
  713. String value = attrs.getValue("value");
  714. int v = -1;
  715. if (value == null) {
  716. missingRequiredAttribute(en, "value");
  717. } else {
  718. v = Integer.parseInt(value);
  719. }
  720. if (languageFeatures.size() == 0) {
  721. languageFeatures.add(null);
  722. }
  723. if ((v >= 0) && (v < 65535)) {
  724. languageFeatures.add(makeFeatureId(v));
  725. }
  726. } else {
  727. notPermittedInElementContext(en, getParent(), pnx);
  728. }
  729. } else if (en[1].equals("FeatureList")) {
  730. String[] pn1 = new String[] { null, "GSUB" };
  731. String[] pn2 = new String[] { null, "GPOS" };
  732. String[][] pnx = new String[][] { pn1, pn2 };
  733. if (!isParent(pnx)) {
  734. notPermittedInElementContext(en, getParent(), pnx);
  735. }
  736. } else if (en[1].equals("FeatureRecord")) {
  737. String[] pn = new String[] { null, "FeatureList" };
  738. if (isParent(pn)) {
  739. String index = attrs.getValue("index");
  740. int fi = -1;
  741. if (index == null) {
  742. missingRequiredAttribute(en, "index");
  743. } else {
  744. fi = Integer.parseInt(index);
  745. }
  746. assertFeatureClear();
  747. assert flIndex == -1;
  748. flIndex = fi;
  749. } else {
  750. notPermittedInElementContext(en, getParent(), pn);
  751. }
  752. } else if (en[1].equals("FeatureTag")) {
  753. String[] pn = new String[] { null, "FeatureRecord" };
  754. if (isParent(pn)) {
  755. String value = attrs.getValue("value");
  756. if (value == null) {
  757. missingRequiredAttribute(en, "value");
  758. } else {
  759. assert featureTag == null;
  760. featureTag = value;
  761. }
  762. } else {
  763. notPermittedInElementContext(en, getParent(), pn);
  764. }
  765. } else if (en[1].equals("GDEF")) {
  766. String[] pn = new String[] { null, "ttFont" };
  767. if (isParent(pn)) {
  768. assertSubtablesClear();
  769. } else {
  770. notPermittedInElementContext(en, getParent(), pn);
  771. }
  772. } else if (en[1].equals("GPOS")) {
  773. String[] pn = new String[] { null, "ttFont" };
  774. if (isParent(pn)) {
  775. assertCoveragesClear();
  776. assertSubtablesClear();
  777. } else {
  778. notPermittedInElementContext(en, getParent(), pn);
  779. }
  780. } else if (en[1].equals("GSUB")) {
  781. String[] pn = new String[] { null, "ttFont" };
  782. if (isParent(pn)) {
  783. assertCoveragesClear();
  784. assertSubtablesClear();
  785. } else {
  786. notPermittedInElementContext(en, getParent(), pn);
  787. }
  788. } else if (en[1].equals("Glyph")) {
  789. String[] pn1 = new String[] { null, "Coverage" };
  790. String[] pn2 = new String[] { null, "InputCoverage" };
  791. String[] pn3 = new String[] { null, "LookAheadCoverage" };
  792. String[] pn4 = new String[] { null, "BacktrackCoverage" };
  793. String[] pn5 = new String[] { null, "MarkCoverage" };
  794. String[] pn6 = new String[] { null, "Mark1Coverage" };
  795. String[] pn7 = new String[] { null, "Mark2Coverage" };
  796. String[] pn8 = new String[] { null, "BaseCoverage" };
  797. String[] pn9 = new String[] { null, "LigatureCoverage" };
  798. String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5, pn6, pn7, pn8, pn9 };
  799. if (isParent(pnx)) {
  800. String value = attrs.getValue("value");
  801. if (value == null) {
  802. missingRequiredAttribute(en, "value");
  803. } else {
  804. int gid = mapGlyphId(value, en);
  805. coverageEntries.add(Integer.valueOf(gid));
  806. }
  807. } else {
  808. notPermittedInElementContext(en, getParent(), pnx);
  809. }
  810. } else if (en[1].equals("GlyphClassDef")) {
  811. String[] pn = new String[] { null, "GDEF" };
  812. if (isParent(pn)) {
  813. String format = attrs.getValue("Format");
  814. int sf = -1;
  815. if (format == null) {
  816. missingRequiredAttribute(en, "Format");
  817. } else {
  818. sf = Integer.parseInt(format);
  819. switch (sf) {
  820. case 1:
  821. case 2:
  822. break;
  823. default:
  824. unsupportedFormat(en, sf);
  825. break;
  826. }
  827. }
  828. assertSubtableClear();
  829. assert sf >= 0;
  830. // force format 1 since TTX always writes entries as non-range entries
  831. if (sf != 1) {
  832. sf = 1;
  833. }
  834. stFormat = sf;
  835. assert glyphClasses.isEmpty();
  836. } else {
  837. notPermittedInElementContext(en, getParent(), pn);
  838. }
  839. } else if (en[1].equals("GlyphID")) {
  840. String[] pn = new String[] { null, "GlyphOrder" };
  841. if (isParent(pn)) {
  842. String id = attrs.getValue("id");
  843. int gid = -1;
  844. if (id == null) {
  845. missingRequiredAttribute(en, "id");
  846. } else {
  847. gid = Integer.parseInt(id);
  848. }
  849. String name = attrs.getValue("name");
  850. if (name == null) {
  851. missingRequiredAttribute(en, "name");
  852. }
  853. if (glyphIds.containsKey(name)) {
  854. duplicateGlyph(en, name, gid);
  855. } else {
  856. if (gid > glyphIdMax) {
  857. glyphIdMax = gid;
  858. }
  859. glyphIds.put(name, gid);
  860. }
  861. } else {
  862. notPermittedInElementContext(en, getParent(), pn);
  863. }
  864. } else if (en[1].equals("GlyphOrder")) {
  865. String[] pn = new String[] { null, "ttFont" };
  866. if (!isParent(pn)) {
  867. notPermittedInElementContext(en, getParent(), pn);
  868. }
  869. } else if (en[1].equals("InputCoverage")) {
  870. String[] pn1 = new String[] { null, "ChainContextSubst" };
  871. String[] pn2 = new String[] { null, "ChainContextPos" };
  872. String[][] pnx = new String[][] { pn1, pn2 };
  873. if (isParent(pnx)) {
  874. String index = attrs.getValue("index");
  875. int ci = -1;
  876. if (index == null) {
  877. missingRequiredAttribute(en, "index");
  878. } else {
  879. ci = Integer.parseInt(index);
  880. }
  881. String format = attrs.getValue("Format");
  882. int cf = -1;
  883. if (format == null) {
  884. missingRequiredAttribute(en, "Format");
  885. } else {
  886. cf = Integer.parseInt(format);
  887. switch (cf) {
  888. case 1:
  889. case 2:
  890. break;
  891. default:
  892. unsupportedFormat(en, cf);
  893. break;
  894. }
  895. }
  896. assertCoverageClear();
  897. ctIndex = ci;
  898. ctFormat = cf;
  899. } else {
  900. notPermittedInElementContext(en, getParent(), pnx);
  901. }
  902. } else if (en[1].equals("LangSys")) {
  903. String[] pn = new String[] { null, "LangSysRecord" };
  904. if (!isParent(pn)) {
  905. notPermittedInElementContext(en, getParent(), pn);
  906. } else {
  907. assertLanguageFeaturesClear();
  908. }
  909. } else if (en[1].equals("LangSysRecord")) {
  910. String[] pn = new String[] { null, "Script" };
  911. if (isParent(pn)) {
  912. String index = attrs.getValue("index");
  913. if (index == null) {
  914. missingRequiredAttribute(en, "index");
  915. }
  916. } else {
  917. notPermittedInElementContext(en, getParent(), pn);
  918. }
  919. } else if (en[1].equals("LangSysTag")) {
  920. String[] pn = new String[] { null, "LangSysRecord" };
  921. if (isParent(pn)) {
  922. String value = attrs.getValue("value");
  923. if (value == null) {
  924. missingRequiredAttribute(en, "value");
  925. } else {
  926. assert languageTag == null;
  927. languageTag = value;
  928. }
  929. } else {
  930. notPermittedInElementContext(en, getParent(), pn);
  931. }
  932. } else if (en[1].equals("LigCaretList")) {
  933. String[] pn = new String[] { null, "GDEF" };
  934. if (!isParent(pn)) {
  935. notPermittedInElementContext(en, getParent(), pn);
  936. }
  937. } else if (en[1].equals("Ligature")) {
  938. String[] pn = new String[] { null, "LigatureSet" };
  939. if (isParent(pn)) {
  940. String components = attrs.getValue("components");
  941. if (components == null) {
  942. missingRequiredAttribute(en, "components");
  943. }
  944. int[] cids = mapGlyphIds(components, en);
  945. String glyph = attrs.getValue("glyph");
  946. if (glyph == null) {
  947. missingRequiredAttribute(en, "glyph");
  948. }
  949. int gid = mapGlyphId(glyph, en);
  950. ligatures.add(new Ligature(gid, cids));
  951. } else {
  952. notPermittedInElementContext(en, getParent(), pn);
  953. }
  954. } else if (en[1].equals("LigatureAnchor")) {
  955. String[] pn = new String[] { null, "ComponentRecord" };
  956. if (isParent(pn)) {
  957. String index = attrs.getValue("index");
  958. if (index == null) {
  959. missingRequiredAttribute(en, "index");
  960. }
  961. String format = attrs.getValue("Format");
  962. if (format == null) {
  963. missingRequiredAttribute(en, "Format");
  964. }
  965. assert xCoord == Integer.MIN_VALUE;
  966. assert yCoord == Integer.MIN_VALUE;
  967. } else {
  968. notPermittedInElementContext(en, getParent(), pn);
  969. }
  970. } else if (en[1].equals("LigatureArray")) {
  971. String[] pn = new String[] { null, "MarkLigPos" };
  972. if (!isParent(pn)) {
  973. notPermittedInElementContext(en, getParent(), pn);
  974. }
  975. } else if (en[1].equals("LigatureAttach")) {
  976. String[] pn = new String[] { null, "LigatureArray" };
  977. if (isParent(pn)) {
  978. String index = attrs.getValue("index");
  979. if (index == null) {
  980. missingRequiredAttribute(en, "index");
  981. }
  982. assert components.size() == 0;
  983. } else {
  984. notPermittedInElementContext(en, getParent(), pn);
  985. }
  986. } else if (en[1].equals("LigatureCoverage")) {
  987. String[] pn = new String[] { null, "MarkLigPos" };
  988. if (isParent(pn)) {
  989. String format = attrs.getValue("Format");
  990. int cf = -1;
  991. if (format == null) {
  992. missingRequiredAttribute(en, "Format");
  993. } else {
  994. cf = Integer.parseInt(format);
  995. switch (cf) {
  996. case 1:
  997. case 2:
  998. break;
  999. default:
  1000. unsupportedFormat(en, cf);
  1001. break;
  1002. }
  1003. }
  1004. assertCoverageClear();
  1005. ctIndex = 0;
  1006. ctFormat = cf;
  1007. } else {
  1008. notPermittedInElementContext(en, getParent(), pn);
  1009. }
  1010. } else if (en[1].equals("LigatureSet")) {
  1011. String[] pn = new String[] { null, "LigatureSubst" };
  1012. if (isParent(pn)) {
  1013. String glyph = attrs.getValue("glyph");
  1014. if (glyph == null) {
  1015. missingRequiredAttribute(en, "glyph");
  1016. }
  1017. int gid = mapGlyphId(glyph, en);
  1018. coverageEntries.add(Integer.valueOf(gid));
  1019. } else {
  1020. notPermittedInElementContext(en, getParent(), pn);
  1021. }
  1022. } else if (en[1].equals("LigatureSubst")) {
  1023. String[] pn = new String[] { null, "Lookup" };
  1024. if (isParent(pn)) {
  1025. String index = attrs.getValue("index");
  1026. if (index == null) {
  1027. missingRequiredAttribute(en, "index");
  1028. }
  1029. String format = attrs.getValue("Format");
  1030. int sf = -1;
  1031. if (format == null) {
  1032. missingRequiredAttribute(en, "Format");
  1033. } else {
  1034. sf = Integer.parseInt(format);
  1035. switch (sf) {
  1036. case 1:
  1037. break;
  1038. default:
  1039. unsupportedFormat(en, sf);
  1040. break;
  1041. }
  1042. }
  1043. assertCoverageClear();
  1044. ctIndex = 0;
  1045. ctFormat = 1;
  1046. assertSubtableClear();
  1047. assert sf >= 0;
  1048. stFormat = sf;
  1049. } else {
  1050. notPermittedInElementContext(en, getParent(), pn);
  1051. }
  1052. } else if (en[1].equals("LookAheadCoverage")) {
  1053. String[] pn1 = new String[] { null, "ChainContextSubst" };
  1054. String[] pn2 = new String[] { null, "ChainContextPos" };
  1055. String[][] pnx = new String[][] { pn1, pn2 };
  1056. if (isParent(pnx)) {
  1057. String index = attrs.getValue("index");
  1058. int ci = -1;
  1059. if (index == null) {
  1060. missingRequiredAttribute(en, "index");
  1061. } else {
  1062. ci = Integer.parseInt(index);
  1063. }
  1064. String format = attrs.getValue("Format");
  1065. int cf = -1;
  1066. if (format == null) {
  1067. missingRequiredAttribute(en, "Format");
  1068. } else {
  1069. cf = Integer.parseInt(format);
  1070. switch (cf) {
  1071. case 1:
  1072. case 2:
  1073. break;
  1074. default:
  1075. unsupportedFormat(en, cf);
  1076. break;
  1077. }
  1078. }
  1079. assertCoverageClear();
  1080. ctIndex = ci;
  1081. ctFormat = cf;
  1082. } else {
  1083. notPermittedInElementContext(en, getParent(), pnx);
  1084. }
  1085. } else if (en[1].equals("Lookup")) {
  1086. String[] pn = new String[] { null, "LookupList" };
  1087. if (isParent(pn)) {
  1088. String index = attrs.getValue("index");
  1089. int li = -1;
  1090. if (index == null) {
  1091. missingRequiredAttribute(en, "index");
  1092. } else {
  1093. li = Integer.parseInt(index);
  1094. }
  1095. assertLookupClear();
  1096. assert ltIndex == -1;
  1097. ltIndex = li;
  1098. } else {
  1099. notPermittedInElementContext(en, getParent(), pn);
  1100. }
  1101. } else if (en[1].equals("LookupFlag")) {
  1102. String[] pn = new String[] { null, "Lookup" };
  1103. if (isParent(pn)) {
  1104. String value = attrs.getValue("value");
  1105. int lf = 0;
  1106. if (value == null) {
  1107. missingRequiredAttribute(en, "value");
  1108. } else {
  1109. lf = Integer.parseInt(value);
  1110. }
  1111. assert ltFlags == 0;
  1112. ltFlags = lf;
  1113. } else {
  1114. notPermittedInElementContext(en, getParent(), pn);
  1115. }
  1116. } else if (en[1].equals("LookupList")) {
  1117. String[] pn1 = new String[] { null, "GSUB" };
  1118. String[] pn2 = new String[] { null, "GPOS" };
  1119. String[][] pnx = new String[][] { pn1, pn2 };
  1120. if (!isParent(pnx)) {
  1121. notPermittedInElementContext(en, getParent(), pnx);
  1122. }
  1123. } else if (en[1].equals("LookupListIndex")) {
  1124. String[] pn1 = new String[] { null, "Feature" };
  1125. String[] pn2 = new String[] { null, "SubstLookupRecord" };
  1126. String[] pn3 = new String[] { null, "PosLookupRecord" };
  1127. String[][] pnx = new String[][] { pn1, pn2, pn3 };
  1128. if (isParent(pnx)) {
  1129. String index = attrs.getValue("index");
  1130. String value = attrs.getValue("value");
  1131. int v = -1;
  1132. if (value == null) {
  1133. missingRequiredAttribute(en, "value");
  1134. } else {
  1135. v = Integer.parseInt(value);
  1136. }
  1137. String[][] pny = new String[][] { pn2, pn3 };
  1138. if (isParent(pny)) {
  1139. assert rlLookup == -1;
  1140. assert v != -1;
  1141. rlLookup = v;
  1142. } else {
  1143. featureLookups.add(makeLookupId(v));
  1144. }
  1145. } else {
  1146. notPermittedInElementContext(en, getParent(), pnx);
  1147. }
  1148. } else if (en[1].equals("LookupType")) {
  1149. String[] pn = new String[] { null, "Lookup" };
  1150. if (isParent(pn)) {
  1151. String value = attrs.getValue("value");
  1152. if (value == null) {
  1153. missingRequiredAttribute(en, "value");
  1154. }
  1155. } else {
  1156. notPermittedInElementContext(en, getParent(), pn);
  1157. }
  1158. } else if (en[1].equals("Mark1Array")) {
  1159. String[] pn = new String[] { null, "MarkMarkPos" };
  1160. if (!isParent(pn)) {
  1161. notPermittedInElementContext(en, getParent(), pn);
  1162. }
  1163. } else if (en[1].equals("Mark1Coverage")) {
  1164. String[] pn = new String[] { null, "MarkMarkPos" };
  1165. if (isParent(pn)) {
  1166. String format = attrs.getValue("Format");
  1167. int cf = -1;
  1168. if (format == null) {
  1169. missingRequiredAttribute(en, "Format");
  1170. } else {
  1171. cf = Integer.parseInt(format);
  1172. switch (cf) {
  1173. case 1:
  1174. case 2:
  1175. break;
  1176. default:
  1177. unsupportedFormat(en, cf);
  1178. break;
  1179. }
  1180. }
  1181. assertCoverageClear();
  1182. ctIndex = 0;
  1183. ctFormat = cf;
  1184. } else {
  1185. notPermittedInElementContext(en, getParent(), pn);
  1186. }
  1187. } else if (en[1].equals("Mark2Anchor")) {
  1188. String[] pn = new String[] { null, "Mark2Record" };
  1189. if (isParent(pn)) {
  1190. String format = attrs.getValue("Format");
  1191. if (format == null) {
  1192. missingRequiredAttribute(en, "Format");
  1193. }
  1194. assert xCoord == Integer.MIN_VALUE;
  1195. assert yCoord == Integer.MIN_VALUE;
  1196. } else {
  1197. notPermittedInElementContext(en, getParent(), pn);
  1198. }
  1199. } else if (en[1].equals("Mark2Array")) {
  1200. String[] pn = new String[] { null, "MarkMarkPos" };
  1201. if (!isParent(pn)) {
  1202. notPermittedInElementContext(en, getParent(), pn);
  1203. }
  1204. } else if (en[1].equals("Mark2Coverage")) {
  1205. String[] pn = new String[] { null, "MarkMarkPos" };
  1206. if (isParent(pn)) {
  1207. String format = attrs.getValue("Format");
  1208. int cf = -1;
  1209. if (format == null) {
  1210. missingRequiredAttribute(en, "Format");
  1211. } else {
  1212. cf = Integer.parseInt(format);
  1213. switch (cf) {
  1214. case 1:
  1215. case 2:
  1216. break;
  1217. default:
  1218. unsupportedFormat(en, cf);
  1219. break;
  1220. }
  1221. }
  1222. assertCoverageClear();
  1223. ctIndex = 0;
  1224. ctFormat = cf;
  1225. } else {
  1226. notPermittedInElementContext(en, getParent(), pn);
  1227. }
  1228. } else if (en[1].equals("Mark2Record")) {
  1229. String[] pn = new String[] { null, "Mark2Array" };
  1230. if (isParent(pn)) {
  1231. String index = attrs.getValue("index");
  1232. if (index == null) {
  1233. missingRequiredAttribute(en, "index");
  1234. }
  1235. } else {
  1236. notPermittedInElementContext(en, getParent(), pn);
  1237. }
  1238. } else if (en[1].equals("MarkAnchor")) {
  1239. String[] pn = new String[] { null, "MarkRecord" };
  1240. if (isParent(pn)) {
  1241. String format = attrs.getValue("Format");
  1242. if (format == null) {
  1243. missingRequiredAttribute(en, "Format");
  1244. }
  1245. assert xCoord == Integer.MIN_VALUE;
  1246. assert yCoord == Integer.MIN_VALUE;
  1247. } else {
  1248. notPermittedInElementContext(en, getParent(), pn);
  1249. }
  1250. } else if (en[1].equals("MarkArray")) {
  1251. String[] pn1 = new String[] { null, "MarkBasePos" };
  1252. String[] pn2 = new String[] { null, "MarkLigPos" };
  1253. String[][] pnx = new String[][] { pn1, pn2 };
  1254. if (!isParent(pnx)) {
  1255. notPermittedInElementContext(en, getParent(), pnx);
  1256. }
  1257. } else if (en[1].equals("MarkAttachClassDef")) {
  1258. String[] pn = new String[] { null, "GDEF" };
  1259. if (isParent(pn)) {
  1260. String format = attrs.getValue("Format");
  1261. int sf = -1;
  1262. if (format == null) {
  1263. missingRequiredAttribute(en, "Format");
  1264. } else {
  1265. sf = Integer.parseInt(format);
  1266. switch (sf) {
  1267. case 1:
  1268. case 2:
  1269. break;
  1270. default:
  1271. unsupportedFormat(en, sf);
  1272. break;
  1273. }
  1274. }
  1275. assertSubtableClear();
  1276. assert sf >= 0;
  1277. // force format 1 since TTX always writes entries as non-range entries
  1278. if (sf != 1) {
  1279. sf = 1;
  1280. }
  1281. stFormat = sf;
  1282. assert glyphClasses.isEmpty();
  1283. } else {
  1284. notPermittedInElementContext(en, getParent(), pn);
  1285. }
  1286. } else if (en[1].equals("MarkBasePos")) {
  1287. String[] pn = new String[] { null, "Lookup" };
  1288. if (isParent(pn)) {
  1289. String index = attrs.getValue("index");
  1290. if (index == null) {
  1291. missingRequiredAttribute(en, "index");
  1292. }
  1293. String format = attrs.getValue("Format");
  1294. int sf = -1;
  1295. if (format == null) {
  1296. missingRequiredAttribute(en, "Format");
  1297. } else {
  1298. sf = Integer.parseInt(format);
  1299. switch (sf) {
  1300. case 1:
  1301. break;
  1302. default:
  1303. unsupportedFormat(en, sf);
  1304. break;
  1305. }
  1306. }
  1307. assertSubtableClear();
  1308. assert sf >= 0;
  1309. stFormat = sf;
  1310. assert markAnchors.size() == 0;
  1311. assert baseOrMarkAnchors.size() == 0;
  1312. } else {
  1313. notPermittedInElementContext(en, getParent(), pn);
  1314. }
  1315. } else if (en[1].equals("MarkCoverage")) {
  1316. String[] pn1 = new String[] { null, "MarkBasePos" };
  1317. String[] pn2 = new String[] { null, "MarkLigPos" };
  1318. String[][] pnx = new String[][] { pn1, pn2 };
  1319. if (isParent(pnx)) {
  1320. String format = attrs.getValue("Format");
  1321. int cf = -1;
  1322. if (format == null) {
  1323. missingRequiredAttribute(en, "Format");
  1324. } else {
  1325. cf = Integer.parseInt(format);
  1326. switch (cf) {
  1327. case 1:
  1328. case 2:
  1329. break;
  1330. default:
  1331. unsupportedFormat(en, cf);
  1332. break;
  1333. }
  1334. }
  1335. assertCoverageClear();
  1336. ctIndex = 0;
  1337. ctFormat = cf;
  1338. } else {
  1339. notPermittedInElementContext(en, getParent(), pnx);
  1340. }
  1341. } else if (en[1].equals("MarkLigPos")) {
  1342. String[] pn = new String[] { null, "Lookup" };
  1343. if (isParent(pn)) {
  1344. String index = attrs.getValue("index");
  1345. if (index == null) {
  1346. missingRequiredAttribute(en, "index");
  1347. }
  1348. String format = attrs.getValue("Format");
  1349. int sf = -1;
  1350. if (format == null) {
  1351. missingRequiredAttribute(en, "Format");
  1352. } else {
  1353. sf = Integer.parseInt(format);
  1354. switch (sf) {
  1355. case 1:
  1356. break;
  1357. default:
  1358. unsupportedFormat(en, sf);
  1359. break;
  1360. }
  1361. }
  1362. assertSubtableClear();
  1363. assert sf >= 0;
  1364. stFormat = sf;
  1365. assert markAnchors.size() == 0;
  1366. assert ligatureAnchors.size() == 0;
  1367. } else {
  1368. notPermittedInElementContext(en, getParent(), pn);
  1369. }
  1370. } else if (en[1].equals("MarkMarkPos")) {
  1371. String[] pn = new String[] { null, "Lookup" };
  1372. if (isParent(pn)) {
  1373. String index = attrs.getValue("index");
  1374. if (index == null) {
  1375. missingRequiredAttribute(en, "index");
  1376. }
  1377. String format = attrs.getValue("Format");
  1378. int sf = -1;
  1379. if (format == null) {
  1380. missingRequiredAttribute(en, "Format");
  1381. } else {
  1382. sf = Integer.parseInt(format);
  1383. switch (sf) {
  1384. case 1:
  1385. break;
  1386. default:
  1387. unsupportedFormat(en, sf);
  1388. break;
  1389. }
  1390. }
  1391. assertSubtableClear();
  1392. assert sf >= 0;
  1393. stFormat = sf;
  1394. assert markAnchors.size() == 0;
  1395. assert baseOrMarkAnchors.size() == 0;
  1396. } else {
  1397. notPermittedInElementContext(en, getParent(), pn);
  1398. }
  1399. } else if (en[1].equals("MarkRecord")) {
  1400. String[] pn1 = new String[] { null, "MarkArray" };
  1401. String[] pn2 = new String[] { null, "Mark1Array" };
  1402. String[][] pnx = new String[][] { pn1, pn2 };
  1403. if (isParent(pnx)) {
  1404. String index = attrs.getValue("index");
  1405. if (index == null) {
  1406. missingRequiredAttribute(en, "index");
  1407. }
  1408. } else {
  1409. notPermittedInElementContext(en, getParent(), pnx);
  1410. }
  1411. } else if (en[1].equals("MultipleSubst")) {
  1412. String[] pn = new String[] { null, "Lookup" };
  1413. if (isParent(pn)) {
  1414. String index = attrs.getValue("index");
  1415. if (index == null) {
  1416. missingRequiredAttribute(en, "index");
  1417. }
  1418. String format = attrs.getValue("Format");
  1419. int sf = -1;
  1420. if (format == null) {
  1421. missingRequiredAttribute(en, "Format");
  1422. } else {
  1423. sf = Integer.parseInt(format);
  1424. switch (sf) {
  1425. case 1:
  1426. break;
  1427. default:
  1428. unsupportedFormat(en, sf);
  1429. break;
  1430. }
  1431. }
  1432. assertSubtableClear();
  1433. assert sf >= 0;
  1434. stFormat = sf;
  1435. } else {
  1436. notPermittedInElementContext(en, getParent(), pn);
  1437. }
  1438. } else if (en[1].equals("PairPos")) {
  1439. String[] pn = new String[] { null, "Lookup" };
  1440. if (isParent(pn)) {
  1441. String index = attrs.getValue("index");
  1442. if (index == null) {
  1443. missingRequiredAttribute(en, "index");
  1444. }
  1445. String format = attrs.getValue("Format");
  1446. int sf = -1;
  1447. if (format == null) {
  1448. missingRequiredAttribute(en, "Format");
  1449. } else {
  1450. sf = Integer.parseInt(format);
  1451. switch (sf) {
  1452. case 1:
  1453. case 2:
  1454. break;
  1455. default:
  1456. unsupportedFormat(en, sf);
  1457. break;
  1458. }
  1459. }
  1460. assertSubtableClear();
  1461. assert sf >= 0;
  1462. stFormat = sf;
  1463. } else {
  1464. notPermittedInElementContext(en, getParent(), pn);
  1465. }
  1466. } else if (en[1].equals("PairSet")) {
  1467. String[] pn = new String[] { null, "PairPos" };
  1468. if (isParent(pn)) {
  1469. String index = attrs.getValue("index");
  1470. int psi = -1;
  1471. if (index == null) {
  1472. missingRequiredAttribute(en, "index");
  1473. } else {
  1474. psi = Integer.parseInt(index);
  1475. }
  1476. assert psIndex == -1;
  1477. psIndex = psi;
  1478. } else {
  1479. notPermittedInElementContext(en, getParent(), pn);
  1480. }
  1481. } else if (en[1].equals("PairValueRecord")) {
  1482. String[] pn = new String[] { null, "PairSet" };
  1483. if (isParent(pn)) {
  1484. String index = attrs.getValue("index");
  1485. if (index == null) {
  1486. missingRequiredAttribute(en, "index");
  1487. } else {
  1488. assertPairClear();
  1489. }
  1490. } else {
  1491. notPermittedInElementContext(en, getParent(), pn);
  1492. }
  1493. } else if (en[1].equals("PosLookupRecord")) {
  1494. String[] pn1 = new String[] { null, "ChainContextSubst" };
  1495. String[] pn2 = new String[] { null, "ChainContextPos" };
  1496. String[][] pnx = new String[][] { pn1, pn2 };
  1497. if (isParent(pnx)) {
  1498. String index = attrs.getValue("index");
  1499. if (index == null) {
  1500. missingRequiredAttribute(en, "index");
  1501. }
  1502. } else {
  1503. notPermittedInElementContext(en, getParent(), pnx);
  1504. }
  1505. } else if (en[1].equals("ReqFeatureIndex")) {
  1506. String[] pn1 = new String[] { null, "DefaultLangSys" };
  1507. String[] pn2 = new String[] { null, "LangSys" };
  1508. String[][] pnx = new String[][] { pn1, pn2 };
  1509. if (isParent(pnx)) {
  1510. String value = attrs.getValue("value");
  1511. int v = -1;
  1512. if (value == null) {
  1513. missingRequiredAttribute(en, "value");
  1514. } else {
  1515. v = Integer.parseInt(value);
  1516. }
  1517. String fid;
  1518. if ((v >= 0) && (v < 65535)) {
  1519. fid = makeFeatureId(v);
  1520. } else {
  1521. fid = null;
  1522. }
  1523. assertLanguageFeaturesClear();
  1524. languageFeatures.add(fid);
  1525. } else {
  1526. notPermittedInElementContext(en, getParent(), pnx);
  1527. }
  1528. } else if (en[1].equals("Script")) {
  1529. String[] pn = new String[] { null, "ScriptRecord" };
  1530. if (!isParent(pn)) {
  1531. notPermittedInElementContext(en, getParent(), pn);
  1532. }
  1533. } else if (en[1].equals("ScriptList")) {
  1534. String[] pn1 = new String[] { null, "GSUB" };
  1535. String[] pn2 = new String[] { null, "GPOS" };
  1536. String[][] pnx = new String[][] { pn1, pn2 };
  1537. if (!isParent(pnx)) {
  1538. notPermittedInElementContext(en, getParent(), pnx);
  1539. }
  1540. } else if (en[1].equals("ScriptRecord")) {
  1541. String[] pn = new String[] { null, "ScriptList" };
  1542. if (isParent(pn)) {
  1543. String index = attrs.getValue("index");
  1544. if (index == null) {
  1545. missingRequiredAttribute(en, "index");
  1546. }
  1547. } else {
  1548. notPermittedInElementContext(en, getParent(), pn);
  1549. }
  1550. } else if (en[1].equals("ScriptTag")) {
  1551. String[] pn = new String[] { null, "ScriptRecord" };
  1552. if (isParent(pn)) {
  1553. String value = attrs.getValue("value");
  1554. if (value == null) {
  1555. missingRequiredAttribute(en, "value");
  1556. } else {
  1557. assert scriptTag == null;
  1558. scriptTag = value;
  1559. }
  1560. } else {
  1561. notPermittedInElementContext(en, getParent(), pn);
  1562. }
  1563. } else if (en[1].equals("SecondGlyph")) {
  1564. String[] pn = new String[] { null, "PairValueRecord" };
  1565. if (isParent(pn)) {
  1566. String value = attrs.getValue("value");
  1567. if (value == null) {
  1568. missingRequiredAttribute(en, "value");
  1569. } else {
  1570. int gid = mapGlyphId(value, en);
  1571. assert g2 == -1;
  1572. g2 = gid;
  1573. }
  1574. } else {
  1575. notPermittedInElementContext(en, getParent(), pn);
  1576. }
  1577. } else if (en[1].equals("Sequence")) {
  1578. String[] pn = new String[] { null, "MultipleSubst" };
  1579. if (isParent(pn)) {
  1580. String index = attrs.getValue("index");
  1581. if (index == null) {
  1582. missingRequiredAttribute(en, "index");
  1583. } else {
  1584. int i = Integer.parseInt(index);
  1585. if (i != subtableEntries.size()) {
  1586. invalidIndex(en, i, subtableEntries.size());
  1587. }
  1588. }
  1589. } else {
  1590. notPermittedInElementContext(en, getParent(), pn);
  1591. }
  1592. } else if (en[1].equals("SequenceIndex")) {
  1593. String[] pn1 = new String[] { null, "PosLookupRecord" };
  1594. String[] pn2 = new String[] { null, "SubstLookupRecord" };
  1595. String[][] pnx = new String[][] { pn1, pn2 };
  1596. if (isParent(pnx)) {
  1597. String value = attrs.getValue("value");
  1598. int v = -1;
  1599. if (value == null) {
  1600. missingRequiredAttribute(en, "value");
  1601. } else {
  1602. v = Integer.parseInt(value);
  1603. }
  1604. assert rlSequence == -1;
  1605. assert v != -1;
  1606. rlSequence = v;
  1607. } else {
  1608. notPermittedInElementContext(en, getParent(), pnx);
  1609. }
  1610. } else if (en[1].equals("SinglePos")) {
  1611. String[] pn = new String[] { null, "Lookup" };
  1612. if (isParent(pn)) {
  1613. String index = attrs.getValue("index");
  1614. if (index == null) {
  1615. missingRequiredAttribute(en, "index");
  1616. }
  1617. String format = attrs.getValue("Format");
  1618. int sf = -1;
  1619. if (format == null) {
  1620. missingRequiredAttribute(en, "Format");
  1621. } else {
  1622. sf = Integer.parseInt(format);
  1623. switch (sf) {
  1624. case 1:
  1625. case 2:
  1626. break;
  1627. default:
  1628. unsupportedFormat(en, sf);
  1629. break;
  1630. }
  1631. }
  1632. assertSubtableClear();
  1633. assert sf >= 0;
  1634. stFormat = sf;
  1635. } else {
  1636. notPermittedInElementContext(en, getParent(), pn);
  1637. }
  1638. } else if (en[1].equals("SingleSubst")) {
  1639. String[] pn = new String[] { null, "Lookup" };
  1640. if (isParent(pn)) {
  1641. String index = attrs.getValue("index");
  1642. if (index == null) {
  1643. missingRequiredAttribute(en, "index");
  1644. }
  1645. String format = attrs.getValue("Format");
  1646. int sf = -1;
  1647. if (format == null) {
  1648. missingRequiredAttribute(en, "Format");
  1649. } else {
  1650. sf = Integer.parseInt(format);
  1651. switch (sf) {
  1652. case 1:
  1653. case 2:
  1654. break;
  1655. default:
  1656. unsupportedFormat(en, sf);
  1657. break;
  1658. }
  1659. }
  1660. assertCoverageClear();
  1661. ctIndex = 0;
  1662. ctFormat = 1;
  1663. assertSubtableClear();
  1664. assert sf >= 0;
  1665. stFormat = sf;
  1666. } else {
  1667. notPermittedInElementContext(en, getParent(), pn);
  1668. }
  1669. } else if (en[1].equals("SubstLookupRecord")) {
  1670. String[] pn = new String[] { null, "ChainContextSubst" };
  1671. if (isParent(pn)) {
  1672. String index = attrs.getValue("index");
  1673. if (index == null) {
  1674. missingRequiredAttribute(en, "index");
  1675. }
  1676. } else {
  1677. notPermittedInElementContext(en, getParent(), pn);
  1678. }
  1679. } else if (en[1].equals("Substitute")) {
  1680. String[] pn = new String[] { null, "Sequence" };
  1681. if (isParent(pn)) {
  1682. String index = attrs.getValue("index");
  1683. if (index == null) {
  1684. missingRequiredAttribute(en, "index");
  1685. } else {
  1686. int i = Integer.parseInt(index);
  1687. if (i != substitutes.size()) {
  1688. invalidIndex(en, i, substitutes.size());
  1689. }
  1690. }
  1691. String value = attrs.getValue("value");
  1692. if (value == null) {
  1693. missingRequiredAttribute(en, "value");
  1694. } else {
  1695. int gid = mapGlyphId(value, en);
  1696. substitutes.add(Integer.valueOf(gid));
  1697. }
  1698. } else {
  1699. notPermittedInElementContext(en, getParent(), pn);
  1700. }
  1701. } else if (en[1].equals("Substitution")) {
  1702. String[] pn = new String[] { null, "SingleSubst" };
  1703. if (isParent(pn)) {
  1704. String in = attrs.getValue("in");
  1705. int igid = -1;
  1706. int ogid = -1;
  1707. if (in == null) {
  1708. missingRequiredAttribute(en, "in");
  1709. } else {
  1710. igid = mapGlyphId(in, en);
  1711. }
  1712. String out = attrs.getValue("out");
  1713. if (out == null) {
  1714. missingRequiredAttribute(en, "out");
  1715. } else {
  1716. ogid = mapGlyphId(out, en);
  1717. }
  1718. coverageEntries.add(Integer.valueOf(igid));
  1719. subtableEntries.add(Integer.valueOf(ogid));
  1720. } else {
  1721. notPermittedInElementContext(en, getParent(), pn);
  1722. }
  1723. } else if (en[1].equals("Value")) {
  1724. String[] pn = new String[] { null, "SinglePos" };
  1725. if (isParent(pn)) {
  1726. String index = attrs.getValue("index");
  1727. if (vf1 < 0) {
  1728. missingParameter(en, "value format");
  1729. } else {
  1730. subtableEntries.add(parseValue(en, attrs, vf1));
  1731. }
  1732. } else {
  1733. notPermittedInElementContext(en, getParent(), pn);
  1734. }
  1735. } else if (en[1].equals("Value1")) {
  1736. String[] pn = new String[] { null, "PairValueRecord" };
  1737. if (isParent(pn)) {
  1738. if (vf1 < 0) {
  1739. missingParameter(en, "value format 1");
  1740. } else {
  1741. assert v1 == null;
  1742. v1 = parseValue(en, attrs, vf1);
  1743. }
  1744. } else {
  1745. notPermittedInElementContext(en, getParent(), pn);
  1746. }
  1747. } else if (en[1].equals("Value2")) {
  1748. String[] pn = new String[] { null, "PairValueRecord" };
  1749. if (isParent(pn)) {
  1750. if (vf2 < 0) {
  1751. missingParameter(en, "value format 2");
  1752. } else {
  1753. assert v2 == null;
  1754. v2 = parseValue(en, attrs, vf2);
  1755. }
  1756. } else {
  1757. notPermittedInElementContext(en, getParent(), pn);
  1758. }
  1759. } else if (en[1].equals("ValueFormat")) {
  1760. String[] pn = new String[] { null, "SinglePos" };
  1761. if (isParent(pn)) {
  1762. String value = attrs.getValue("value");
  1763. int vf = -1;
  1764. if (value == null) {
  1765. missingRequiredAttribute(en, "value");
  1766. } else {
  1767. vf = Integer.parseInt(value);
  1768. }
  1769. assert vf1 == -1;
  1770. vf1 = vf;
  1771. } else {
  1772. notPermittedInElementContext(en, getParent(), pn);
  1773. }
  1774. } else if (en[1].equals("ValueFormat1")) {
  1775. String[] pn = new String[] { null, "PairPos" };
  1776. if (isParent(pn)) {
  1777. String value = attrs.getValue("value");
  1778. int vf = -1;
  1779. if (value == null) {
  1780. missingRequiredAttribute(en, "value");
  1781. } else {
  1782. vf = Integer.parseInt(value);
  1783. }
  1784. assert vf1 == -1;
  1785. vf1 = vf;
  1786. } else {
  1787. notPermittedInElementContext(en, getParent(), pn);
  1788. }
  1789. } else if (en[1].equals("ValueFormat2")) {
  1790. String[] pn = new String[] { null, "PairPos" };
  1791. if (isParent(pn)) {
  1792. String value = attrs.getValue("value");
  1793. int vf = -1;
  1794. if (value == null) {
  1795. missingRequiredAttribute(en, "value");
  1796. } else {
  1797. vf = Integer.parseInt(value);
  1798. }
  1799. assert vf2 == -1;
  1800. vf2 = vf;
  1801. } else {
  1802. notPermittedInElementContext(en, getParent(), pn);
  1803. }
  1804. } else if (en[1].equals("Version")) {
  1805. String[] pn1 = new String[] { null, "GDEF" };
  1806. String[] pn2 = new String[] { null, "GPOS" };
  1807. String[] pn3 = new String[] { null, "GSUB" };
  1808. String[][] pnx = new String[][] { pn1, pn2, pn3 };
  1809. if (isParent(pnx)) {
  1810. String value = attrs.getValue("value");
  1811. if (value == null) {
  1812. missingRequiredAttribute(en, "value");
  1813. }
  1814. } else {
  1815. notPermittedInElementContext(en, getParent(), pnx);
  1816. }
  1817. } else if (en[1].equals("XCoordinate")) {
  1818. String[] pn1 = new String[] { null, "BaseAnchor" };
  1819. String[] pn2 = new String[] { null, "EntryAnchor" };
  1820. String[] pn3 = new String[] { null, "ExitAnchor" };
  1821. String[] pn4 = new String[] { null, "LigatureAnchor" };
  1822. String[] pn5 = new String[] { null, "MarkAnchor" };
  1823. String[] pn6 = new String[] { null, "Mark2Anchor" };
  1824. String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5, pn6 };
  1825. if (isParent(pnx)) {
  1826. String value = attrs.getValue("value");
  1827. int x = 0;
  1828. if (value == null) {
  1829. missingRequiredAttribute(en, "value");
  1830. } else {
  1831. x = Integer.parseInt(value);
  1832. }
  1833. assert xCoord == Integer.MIN_VALUE;
  1834. xCoord = x;
  1835. } else {
  1836. notPermittedInElementContext(en, getParent(), pnx);
  1837. }
  1838. } else if (en[1].equals("YCoordinate")) {
  1839. String[] pn1 = new String[] { null, "BaseAnchor" };
  1840. String[] pn2 = new String[] { null, "EntryAnchor" };
  1841. String[] pn3 = new String[] { null, "ExitAnchor" };
  1842. String[] pn4 = new String[] { null, "LigatureAnchor" };
  1843. String[] pn5 = new String[] { null, "MarkAnchor" };
  1844. String[] pn6 = new String[] { null, "Mark2Anchor" };
  1845. String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5, pn6 };
  1846. if (isParent(pnx)) {
  1847. String value = attrs.getValue("value");
  1848. int y = 0;
  1849. if (value == null) {
  1850. missingRequiredAttribute(en, "value");
  1851. } else {
  1852. y = Integer.parseInt(value);
  1853. }
  1854. assert yCoord == Integer.MIN_VALUE;
  1855. yCoord = y;
  1856. } else {
  1857. notPermittedInElementContext(en, getParent(), pnx);
  1858. }
  1859. } else if (en[1].equals("checkSumAdjustment")) {
  1860. String[] pn = new String[] { null, "head" };
  1861. if (isParent(pn)) {
  1862. String value = attrs.getValue("value");
  1863. if (value == null) {
  1864. missingRequiredAttribute(en, "value");
  1865. }
  1866. } else {
  1867. notPermittedInElementContext(en, getParent(), pn);
  1868. }
  1869. } else if (en[1].equals("cmap")) {
  1870. String[] pn = new String[] { null, "ttFont" };
  1871. if (!isParent(pn)) {
  1872. notPermittedInElementContext(en, getParent(), pn);
  1873. }
  1874. } else if (en[1].equals("cmap_format_0")) {
  1875. String[] pn = new String[] { null, "cmap" };
  1876. if (isParent(pn)) {
  1877. String platformID = attrs.getValue("platformID");
  1878. if (platformID == null) {
  1879. missingRequiredAttribute(en, "platformID");
  1880. }
  1881. String platEncID = attrs.getValue("platEncID");
  1882. if (platEncID == null) {
  1883. missingRequiredAttribute(en, "platEncID");
  1884. }
  1885. String language = attrs.getValue("language");
  1886. if (language == null) {
  1887. missingRequiredAttribute(en, "language");
  1888. }
  1889. } else {
  1890. notPermittedInElementContext(en, getParent(), pn);
  1891. }
  1892. } else if (en[1].equals("cmap_format_4")) {
  1893. String[] pn = new String[] { null, "cmap" };
  1894. if (isParent(pn)) {
  1895. String platformID = attrs.getValue("platformID");
  1896. int pid = -1;
  1897. if (platformID == null) {
  1898. missingRequiredAttribute(en, "platformID");
  1899. } else {
  1900. pid = Integer.parseInt(platformID);
  1901. }
  1902. String platEncID = attrs.getValue("platEncID");
  1903. int eid = -1;
  1904. if (platEncID == null) {
  1905. missingRequiredAttribute(en, "platEncID");
  1906. } else {
  1907. eid = Integer.parseInt(platEncID);
  1908. }
  1909. String language = attrs.getValue("language");
  1910. int lid = -1;
  1911. if (language == null) {
  1912. missingRequiredAttribute(en, "language");
  1913. } else {
  1914. lid = Integer.parseInt(language);
  1915. }
  1916. assert cmapEntries.size() == 0;
  1917. assert cmPlatform == -1;
  1918. assert cmEncoding == -1;
  1919. assert cmLanguage == -1;
  1920. cmPlatform = pid;
  1921. cmEncoding = eid;
  1922. cmLanguage = lid;
  1923. } else {
  1924. notPermittedInElementContext(en, getParent(), pn);
  1925. }
  1926. } else if (en[1].equals("created")) {
  1927. String[] pn = new String[] { null, "head" };
  1928. if (isParent(pn)) {
  1929. String value = attrs.getValue("value");
  1930. if (value == null) {
  1931. missingRequiredAttribute(en, "value");
  1932. }
  1933. } else {
  1934. notPermittedInElementContext(en, getParent(), pn);
  1935. }
  1936. } else if (en[1].equals("flags")) {
  1937. String[] pn = new String[] { null, "head" };
  1938. if (isParent(pn)) {
  1939. String value = attrs.getValue("value");
  1940. if (value == null) {
  1941. missingRequiredAttribute(en, "value");
  1942. }
  1943. } else {
  1944. notPermittedInElementContext(en, getParent(), pn);
  1945. }
  1946. } else if (en[1].equals("fontDirectionHint")) {
  1947. String[] pn = new String[] { null, "head" };
  1948. if (isParent(pn)) {
  1949. String value = attrs.getValue("value");
  1950. if (value == null) {
  1951. missingRequiredAttribute(en, "value");
  1952. }
  1953. } else {
  1954. notPermittedInElementContext(en, getParent(), pn);
  1955. }
  1956. } else if (en[1].equals("fontRevision")) {
  1957. String[] pn = new String[] { null, "head" };
  1958. if (isParent(pn)) {
  1959. String value = attrs.getValue("value");
  1960. if (value == null) {
  1961. missingRequiredAttribute(en, "value");
  1962. }
  1963. } else {
  1964. notPermittedInElementContext(en, getParent(), pn);
  1965. }
  1966. } else if (en[1].equals("glyphDataFormat")) {
  1967. String[] pn = new String[] { null, "head" };
  1968. if (isParent(pn)) {
  1969. String value = attrs.getValue("value");
  1970. if (value == null) {
  1971. missingRequiredAttribute(en, "value");
  1972. }
  1973. } else {
  1974. notPermittedInElementContext(en, getParent(), pn);
  1975. }
  1976. } else if (en[1].equals("head")) {
  1977. String[] pn = new String[] { null, "ttFont" };
  1978. if (!isParent(pn)) {
  1979. notPermittedInElementContext(en, getParent(), pn);
  1980. }
  1981. } else if (en[1].equals("hmtx")) {
  1982. String[] pn = new String[] { null, "ttFont" };
  1983. if (!isParent(pn)) {
  1984. notPermittedInElementContext(en, getParent(), pn);
  1985. } else if (glyphIdMax > 0) {
  1986. hmtxEntries.setSize(glyphIdMax + 1);
  1987. }
  1988. } else if (en[1].equals("indexToLocFormat")) {
  1989. String[] pn = new String[] { null, "head" };
  1990. if (isParent(pn)) {
  1991. String value = attrs.getValue("value");
  1992. if (value == null) {
  1993. missingRequiredAttribute(en, "value");
  1994. }
  1995. } else {
  1996. notPermittedInElementContext(en, getParent(), pn);
  1997. }
  1998. } else if (en[1].equals("lowestRecPPEM")) {
  1999. String[] pn = new String[] { null, "head" };
  2000. if (isParent(pn)) {
  2001. String value = attrs.getValue("value");
  2002. if (value == null) {
  2003. missingRequiredAttribute(en, "value");
  2004. }
  2005. } else {
  2006. notPermittedInElementContext(en, getParent(), pn);
  2007. }
  2008. } else if (en[1].equals("macStyle")) {
  2009. String[] pn = new String[] { null, "head" };
  2010. if (isParent(pn)) {
  2011. String value = attrs.getValue("value");
  2012. if (value == null) {
  2013. missingRequiredAttribute(en, "value");
  2014. }
  2015. } else {
  2016. notPermittedInElementContext(en, getParent(), pn);
  2017. }
  2018. } else if (en[1].equals("magicNumber")) {
  2019. String[] pn = new String[] { null, "head" };
  2020. if (isParent(pn)) {
  2021. String value = attrs.getValue("value");
  2022. if (value == null) {
  2023. missingRequiredAttribute(en, "value");
  2024. }
  2025. } else {
  2026. notPermittedInElementContext(en, getParent(), pn);
  2027. }
  2028. } else if (en[1].equals("map")) {
  2029. String[] pn1 = new String[] { null, "cmap_format_0" };
  2030. String[] pn2 = new String[] { null, "cmap_format_4" };
  2031. String[][] pnx = new String[][] { pn1, pn2 };
  2032. if (isParent(pnx)) {
  2033. String code = attrs.getValue("code");
  2034. int cid = -1;
  2035. if (code == null) {
  2036. missingRequiredAttribute(en, "code");
  2037. } else {
  2038. code = code.toLowerCase();
  2039. if (code.startsWith("0x")) {
  2040. cid = Integer.parseInt(code.substring(2), 16);
  2041. } else {
  2042. cid = Integer.parseInt(code, 10);
  2043. }
  2044. }
  2045. String name = attrs.getValue("name");
  2046. int gid = -1;
  2047. if (name == null) {
  2048. missingRequiredAttribute(en, "name");
  2049. } else {
  2050. gid = mapGlyphId(name, en);
  2051. }
  2052. if ((cmPlatform == 3) && (cmEncoding == 1)) {
  2053. cmapEntries.add(new int[] { cid, gid });
  2054. }
  2055. } else {
  2056. notPermittedInElementContext(en, getParent(), pnx);
  2057. }
  2058. } else if (en[1].equals("modified")) {
  2059. String[] pn = new String[] { null, "head" };
  2060. if (isParent(pn)) {
  2061. String value = attrs.getValue("value");
  2062. if (value == null) {
  2063. missingRequiredAttribute(en, "value");
  2064. }
  2065. } else {
  2066. notPermittedInElementContext(en, getParent(), pn);
  2067. }
  2068. } else if (en[1].equals("mtx")) {
  2069. String[] pn = new String[] { null, "hmtx" };
  2070. if (isParent(pn)) {
  2071. String name = attrs.getValue("name");
  2072. int gid = -1;
  2073. if (name == null) {
  2074. missingRequiredAttribute(en, "name");
  2075. } else {
  2076. gid = mapGlyphId(name, en);
  2077. }
  2078. String width = attrs.getValue("width");
  2079. int w = -1;
  2080. if (width == null) {
  2081. missingRequiredAttribute(en, "width");
  2082. } else {
  2083. w = Integer.parseInt(width);
  2084. }
  2085. String lsb = attrs.getValue("lsb");
  2086. int l = -1;
  2087. if (lsb == null) {
  2088. missingRequiredAttribute(en, "lsb");
  2089. } else {
  2090. l = Integer.parseInt(lsb);
  2091. }
  2092. hmtxEntries.set(gid, new int[] { w, l });
  2093. } else {
  2094. notPermittedInElementContext(en, getParent(), pn);
  2095. }
  2096. } else if (en[1].equals("tableVersion")) {
  2097. String[] pn1 = new String[] { null, "cmap" };
  2098. String[] pn2 = new String[] { null, "head" };
  2099. String[][] pnx = new String[][] { pn1, pn2 };
  2100. if (isParent(pn1)) { // child of cmap
  2101. String version = attrs.getValue("version");
  2102. if (version == null) {
  2103. missingRequiredAttribute(en, "version");
  2104. }
  2105. } else if (isParent(pn2)) { // child of head
  2106. String value = attrs.getValue("value");
  2107. if (value == null) {
  2108. missingRequiredAttribute(en, "value");
  2109. }
  2110. } else {
  2111. notPermittedInElementContext(en, getParent(), pnx);
  2112. }
  2113. } else if (en[1].equals("ttFont")) {
  2114. String[] pn = new String[] { null, null };
  2115. if (isParent(pn)) {
  2116. String sfntVersion = attrs.getValue("sfntVersion");
  2117. if (sfntVersion == null) {
  2118. missingRequiredAttribute(en, "sfntVersion");
  2119. }
  2120. String ttLibVersion = attrs.getValue("ttLibVersion");
  2121. if (ttLibVersion == null) {
  2122. missingRequiredAttribute(en, "ttLibVersion");
  2123. }
  2124. } else {
  2125. notPermittedInElementContext(en, getParent(), null);
  2126. }
  2127. } else if (en[1].equals("unitsPerEm")) {
  2128. String[] pn = new String[] { null, "head" };
  2129. if (isParent(pn)) {
  2130. String value = attrs.getValue("value");
  2131. int v = -1;
  2132. if (value == null) {
  2133. missingRequiredAttribute(en, "value");
  2134. } else {
  2135. v = Integer.parseInt(value);
  2136. }
  2137. assert upem == -1;
  2138. upem = v;
  2139. } else {
  2140. notPermittedInElementContext(en, getParent(), pn);
  2141. }
  2142. } else if (en[1].equals("xMax")) {
  2143. String[] pn = new String[] { null, "head" };
  2144. if (isParent(pn)) {
  2145. String value = attrs.getValue("value");
  2146. if (value == null) {
  2147. missingRequiredAttribute(en, "value");
  2148. }
  2149. } else {
  2150. notPermittedInElementContext(en, getParent(), pn);
  2151. }
  2152. } else if (en[1].equals("xMin")) {
  2153. String[] pn = new String[] { null, "head" };
  2154. if (isParent(pn)) {
  2155. String value = attrs.getValue("value");
  2156. if (value == null) {
  2157. missingRequiredAttribute(en, "value");
  2158. }
  2159. } else {
  2160. notPermittedInElementContext(en, getParent(), pn);
  2161. }
  2162. } else if (en[1].equals("yMax")) {
  2163. String[] pn = new String[] { null, "head" };
  2164. if (isParent(pn)) {
  2165. String value = attrs.getValue("value");
  2166. if (value == null) {
  2167. missingRequiredAttribute(en, "value");
  2168. }
  2169. } else {
  2170. notPermittedInElementContext(en, getParent(), pn);
  2171. }
  2172. } else if (en[1].equals("yMin")) {
  2173. String[] pn = new String[] { null, "head" };
  2174. if (isParent(pn)) {
  2175. String value = attrs.getValue("value");
  2176. if (value == null) {
  2177. missingRequiredAttribute(en, "value");
  2178. }
  2179. } else {
  2180. notPermittedInElementContext(en, getParent(), pn);
  2181. }
  2182. } else {
  2183. unsupportedElement(en);
  2184. }
  2185. elements.push(en);
  2186. }
  2187. @Override
  2188. public void endElement(String uri, String localName, String qName) throws SAXException {
  2189. if (elements.empty()) {
  2190. throw new SAXException("element stack is unbalanced, no elements on stack!");
  2191. }
  2192. String[] enParent = elements.peek();
  2193. if (enParent == null) {
  2194. throw new SAXException("element stack is empty, elements are not balanced");
  2195. }
  2196. String[] en = makeExpandedName(uri, localName, qName);
  2197. if (!sameExpandedName(enParent, en)) {
  2198. throw new SAXException("element stack is unbalanced, expanded name mismatch");
  2199. }
  2200. if (en[0] != null) {
  2201. unsupportedElement(en);
  2202. } else if (isAnchorElement(en[1])) {
  2203. if (xCoord == Integer.MIN_VALUE) {
  2204. missingParameter(en, "x coordinate");
  2205. } else if (yCoord == Integer.MIN_VALUE) {
  2206. missingParameter(en, "y coordinate");
  2207. } else {
  2208. if (en[1].equals("EntryAnchor")) {
  2209. if (anchors.size() > 0) {
  2210. duplicateParameter(en, "entry anchor");
  2211. }
  2212. } else if (en[1].equals("ExitAnchor")) {
  2213. if (anchors.size() > 1) {
  2214. duplicateParameter(en, "exit anchor");
  2215. } else if (anchors.size() == 0) {
  2216. anchors.add(null);
  2217. }
  2218. }
  2219. anchors.add(new GlyphPositioningTable.Anchor(xCoord, yCoord));
  2220. xCoord = yCoord = Integer.MIN_VALUE;
  2221. }
  2222. } else if (en[1].equals("AlternateSet")) {
  2223. subtableEntries.add(extractAlternates());
  2224. } else if (en[1].equals("AlternateSubst")) {
  2225. if (!sortEntries(coverageEntries, subtableEntries)) {
  2226. mismatchedEntries(en, coverageEntries.size(), subtableEntries.size());
  2227. }
  2228. addGSUBSubtable(GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE, extractCoverage());
  2229. } else if (en[1].equals("BacktrackCoverage")) {
  2230. String ck = makeCoverageKey("bk", ctIndex);
  2231. if (coverages.containsKey(ck)) {
  2232. duplicateCoverageIndex(en, ctIndex);
  2233. } else {
  2234. coverages.put(ck, extractCoverage());
  2235. }
  2236. } else if (en[1].equals("BaseCoverage")) {
  2237. coverages.put("base", extractCoverage());
  2238. } else if (en[1].equals("BaseRecord")) {
  2239. baseOrMarkAnchors.add(extractAnchors());
  2240. } else if (en[1].equals("ChainContextPos") || en[1].equals("ChainContextSubst")) {
  2241. GlyphCoverageTable coverage = null;
  2242. if (stFormat == 3) {
  2243. GlyphCoverageTable[] igca = getCoveragesWithPrefix("in");
  2244. GlyphCoverageTable[] bgca = getCoveragesWithPrefix("bk");
  2245. GlyphCoverageTable[] lgca = getCoveragesWithPrefix("la");
  2246. if ((igca.length == 0) || hasMissingCoverage(igca)) {
  2247. missingCoverage(en, "input", igca.length);
  2248. } else if (hasMissingCoverage(bgca)) {
  2249. missingCoverage(en, "backtrack", bgca.length);
  2250. } else if (hasMissingCoverage(lgca)) {
  2251. missingCoverage(en, "lookahead", lgca.length);
  2252. } else {
  2253. GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(extractRuleLookups(), igca.length, igca, bgca, lgca);
  2254. GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r});
  2255. GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
  2256. coverage = igca [ 0 ];
  2257. subtableEntries.add(rsa);
  2258. }
  2259. } else {
  2260. unsupportedFormat(en, stFormat);
  2261. }
  2262. if (en[1].equals("ChainContextPos")) {
  2263. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL, coverage);
  2264. } else if (en[1].equals("ChainContextSubst")) {
  2265. addGSUBSubtable(GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL, coverage);
  2266. }
  2267. } else if (en[1].equals("ComponentRecord")) {
  2268. components.add(extractAnchors());
  2269. } else if (en[1].equals("Coverage")) {
  2270. coverages.put("main", extractCoverage());
  2271. } else if (en[1].equals("DefaultLangSys") || en[1].equals("LangSysRecord")) {
  2272. if (languageTag == null) {
  2273. missingTag(en, "language");
  2274. } else if (languages.containsKey(languageTag)) {
  2275. duplicateTag(en, "language", languageTag);
  2276. } else {
  2277. languages.put(languageTag, extractLanguageFeatures());
  2278. languageTag = null;
  2279. }
  2280. } else if (en[1].equals("CursivePos")) {
  2281. GlyphCoverageTable ct = coverages.get("main");
  2282. if (ct == null) {
  2283. missingParameter(en, "coverages");
  2284. } else if (stFormat == 1) {
  2285. subtableEntries.add(extractAttachmentAnchors());
  2286. } else {
  2287. unsupportedFormat(en, stFormat);
  2288. }
  2289. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE, ct);
  2290. } else if (en[1].equals("EntryExitRecord")) {
  2291. int na = anchors.size();
  2292. if (na == 0) {
  2293. missingParameter(en, "entry or exit anchor");
  2294. } else if (na == 1) {
  2295. anchors.add(null);
  2296. } else if (na > 2) {
  2297. duplicateParameter(en, "entry or exit anchor");
  2298. }
  2299. attachmentAnchors.add(extractAnchors());
  2300. } else if (en[1].equals("BaseRecord")) {
  2301. baseOrMarkAnchors.add(extractAnchors());
  2302. } else if (en[1].equals("FeatureRecord")) {
  2303. if (flIndex != flSequence) {
  2304. mismatchedIndex(en, "feature", flIndex, flSequence);
  2305. } else if (featureTag == null) {
  2306. missingTag(en, "feature");
  2307. } else {
  2308. String fid = makeFeatureId(flIndex);
  2309. features.put(fid, extractFeature());
  2310. nextFeature();
  2311. }
  2312. } else if (en[1].equals("GDEF")) {
  2313. if (subtables.size() > 0) {
  2314. gdef = new GlyphDefinitionTable(subtables);
  2315. }
  2316. clearTable();
  2317. } else if (en[1].equals("GPOS")) {
  2318. if (subtables.size() > 0) {
  2319. gpos = new GlyphPositioningTable(gdef, extractLookups(), subtables);
  2320. }
  2321. clearTable();
  2322. } else if (en[1].equals("GSUB")) {
  2323. if (subtables.size() > 0) {
  2324. gsub = new GlyphSubstitutionTable(gdef, extractLookups(), subtables);
  2325. }
  2326. clearTable();
  2327. } else if (en[1].equals("GlyphClassDef")) {
  2328. GlyphMappingTable mapping = extractClassDefMapping(glyphClasses, stFormat, true);
  2329. addGDEFSubtable(GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS, mapping);
  2330. } else if (en[1].equals("InputCoverage")) {
  2331. String ck = makeCoverageKey("in", ctIndex);
  2332. if (coverages.containsKey(ck)) {
  2333. duplicateCoverageIndex(en, ctIndex);
  2334. } else {
  2335. coverages.put(ck, extractCoverage());
  2336. }
  2337. } else if (en[1].equals("LigatureAttach")) {
  2338. ligatureAnchors.add(extractComponents());
  2339. } else if (en[1].equals("LigatureCoverage")) {
  2340. coverages.put("liga", extractCoverage());
  2341. } else if (en[1].equals("LigatureSet")) {
  2342. subtableEntries.add(extractLigatures());
  2343. } else if (en[1].equals("LigatureSubst")) {
  2344. if (!sortEntries(coverageEntries, subtableEntries)) {
  2345. mismatchedEntries(en, coverageEntries.size(), subtableEntries.size());
  2346. }
  2347. GlyphCoverageTable coverage = extractCoverage();
  2348. addGSUBSubtable(GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE, coverage);
  2349. } else if (en[1].equals("LookAheadCoverage")) {
  2350. String ck = makeCoverageKey("la", ctIndex);
  2351. if (coverages.containsKey(ck)) {
  2352. duplicateCoverageIndex(en, ctIndex);
  2353. } else {
  2354. coverages.put(ck, extractCoverage());
  2355. }
  2356. } else if (en[1].equals("Lookup")) {
  2357. if (ltIndex != ltSequence) {
  2358. mismatchedIndex(en, "lookup", ltIndex, ltSequence);
  2359. } else {
  2360. nextLookup();
  2361. }
  2362. } else if (en[1].equals("MarkAttachClassDef")) {
  2363. GlyphMappingTable mapping = extractClassDefMapping(glyphClasses, stFormat, true);
  2364. addGDEFSubtable(GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT, mapping);
  2365. } else if (en[1].equals("MarkCoverage")) {
  2366. coverages.put("mark", extractCoverage());
  2367. } else if (en[1].equals("Mark1Coverage")) {
  2368. coverages.put("mrk1", extractCoverage());
  2369. } else if (en[1].equals("Mark2Coverage")) {
  2370. coverages.put("mrk2", extractCoverage());
  2371. } else if (en[1].equals("MarkBasePos")) {
  2372. GlyphCoverageTable mct = coverages.get("mark");
  2373. GlyphCoverageTable bct = coverages.get("base");
  2374. if (mct == null) {
  2375. missingParameter(en, "mark coverages");
  2376. } else if (bct == null) {
  2377. missingParameter(en, "base coverages");
  2378. } else if (stFormat == 1) {
  2379. MarkAnchor[] maa = extractMarkAnchors();
  2380. Anchor[][] bam = extractBaseOrMarkAnchors();
  2381. subtableEntries.add(bct);
  2382. subtableEntries.add(computeClassCount(bam));
  2383. subtableEntries.add(maa);
  2384. subtableEntries.add(bam);
  2385. } else {
  2386. unsupportedFormat(en, stFormat);
  2387. }
  2388. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE, mct);
  2389. } else if (en[1].equals("MarkLigPos")) {
  2390. GlyphCoverageTable mct = coverages.get("mark");
  2391. GlyphCoverageTable lct = coverages.get("liga");
  2392. if (mct == null) {
  2393. missingParameter(en, "mark coverages");
  2394. } else if (lct == null) {
  2395. missingParameter(en, "ligature coverages");
  2396. } else if (stFormat == 1) {
  2397. MarkAnchor[] maa = extractMarkAnchors();
  2398. Anchor[][][] lam = extractLigatureAnchors();
  2399. subtableEntries.add(lct);
  2400. subtableEntries.add(computeLigaturesClassCount(lam));
  2401. subtableEntries.add(computeLigaturesComponentCount(lam));
  2402. subtableEntries.add(maa);
  2403. subtableEntries.add(lam);
  2404. } else {
  2405. unsupportedFormat(en, stFormat);
  2406. }
  2407. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE, mct);
  2408. } else if (en[1].equals("MarkMarkPos")) {
  2409. GlyphCoverageTable mct1 = coverages.get("mrk1");
  2410. GlyphCoverageTable mct2 = coverages.get("mrk2");
  2411. if (mct1 == null) {
  2412. missingParameter(en, "mark coverages 1");
  2413. } else if (mct2 == null) {
  2414. missingParameter(en, "mark coverages 2");
  2415. } else if (stFormat == 1) {
  2416. MarkAnchor[] maa = extractMarkAnchors();
  2417. Anchor[][] mam = extractBaseOrMarkAnchors();
  2418. subtableEntries.add(mct2);
  2419. subtableEntries.add(computeClassCount(mam));
  2420. subtableEntries.add(maa);
  2421. subtableEntries.add(mam);
  2422. } else {
  2423. unsupportedFormat(en, stFormat);
  2424. }
  2425. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK, mct1);
  2426. } else if (en[1].equals("MarkRecord")) {
  2427. if (markClass == -1) {
  2428. missingParameter(en, "mark class");
  2429. } else if (anchors.size() == 0) {
  2430. missingParameter(en, "mark anchor");
  2431. } else if (anchors.size() > 1) {
  2432. duplicateParameter(en, "mark anchor");
  2433. } else {
  2434. markAnchors.add(new GlyphPositioningTable.MarkAnchor(markClass, anchors.get(0)));
  2435. markClass = -1;
  2436. anchors.clear();
  2437. }
  2438. } else if (en[1].equals("Mark2Record")) {
  2439. baseOrMarkAnchors.add(extractAnchors());
  2440. } else if (en[1].equals("MultipleSubst")) {
  2441. GlyphCoverageTable coverage = coverages.get("main");
  2442. addGSUBSubtable(GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE, coverage, extractSequenceEntries());
  2443. } else if (en[1].equals("PairPos")) {
  2444. assertSubtableEntriesClear();
  2445. if (stFormat == 1) {
  2446. if (pairSets.size() == 0) {
  2447. missingParameter(en, "pair set");
  2448. } else {
  2449. subtableEntries.add(extractPairSets());
  2450. }
  2451. } else if (stFormat == 2) {
  2452. unsupportedFormat(en, stFormat);
  2453. }
  2454. GlyphCoverageTable coverage = coverages.get("main");
  2455. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR, coverage);
  2456. vf1 = vf2 = -1;
  2457. psIndex = -1;
  2458. } else if (en[1].equals("PairSet")) {
  2459. if (psIndex != pairSets.size()) {
  2460. invalidIndex(en, psIndex, pairSets.size());
  2461. } else {
  2462. pairSets.add(extractPairs());
  2463. }
  2464. } else if (en[1].equals("PairValueRecord")) {
  2465. if (g2 == -1) {
  2466. missingParameter(en, "second glyph");
  2467. } else if ((v1 == null) && (v2 == null)) {
  2468. missingParameter(en, "first or second value");
  2469. } else {
  2470. pairs.add(new PairValues(g2, v1, v2));
  2471. clearPair();
  2472. }
  2473. } else if (en[1].equals("PosLookupRecord") || en[1].equals("SubstLookupRecord")) {
  2474. if (rlSequence < 0) {
  2475. missingParameter(en, "sequence index");
  2476. } else if (rlLookup < 0) {
  2477. missingParameter(en, "lookup index");
  2478. } else {
  2479. ruleLookups.add(new GlyphTable.RuleLookup(rlSequence, rlLookup));
  2480. rlSequence = rlLookup = -1;
  2481. }
  2482. } else if (en[1].equals("Script")) {
  2483. if (scriptTag == null) {
  2484. missingTag(en, "script");
  2485. } else if (scripts.containsKey(scriptTag)) {
  2486. duplicateTag(en, "script", scriptTag);
  2487. } else {
  2488. scripts.put(scriptTag, extractLanguages());
  2489. scriptTag = null;
  2490. }
  2491. } else if (en[1].equals("Sequence")) {
  2492. subtableEntries.add(extractSubstitutes());
  2493. } else if (en[1].equals("SinglePos")) {
  2494. int nv = subtableEntries.size();
  2495. if (stFormat == 1) {
  2496. if (nv < 0) {
  2497. missingParameter(en, "value");
  2498. } else if (nv > 1) {
  2499. duplicateParameter(en, "value");
  2500. }
  2501. } else if (stFormat == 2) {
  2502. GlyphPositioningTable.Value[] pva = (GlyphPositioningTable.Value[]) subtableEntries.toArray(new GlyphPositioningTable.Value [ nv ]);
  2503. subtableEntries.clear();
  2504. subtableEntries.add(pva);
  2505. }
  2506. GlyphCoverageTable coverage = coverages.get("main");
  2507. addGPOSSubtable(GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE, coverage);
  2508. vf1 = -1;
  2509. } else if (en[1].equals("SingleSubst")) {
  2510. if (!sortEntries(coverageEntries, subtableEntries)) {
  2511. mismatchedEntries(en, coverageEntries.size(), subtableEntries.size());
  2512. }
  2513. GlyphCoverageTable coverage = extractCoverage();
  2514. addGSUBSubtable(GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE, coverage);
  2515. } else if (en[1].equals("cmap")) {
  2516. cmap = getCMAP();
  2517. gmap = getGMAP();
  2518. cmapEntries.clear();
  2519. } else if (en[1].equals("cmap_format_4")) {
  2520. cmPlatform = cmEncoding = cmLanguage = -1;
  2521. } else if (en[1].equals("hmtx")) {
  2522. hmtx = getHMTX();
  2523. hmtxEntries.clear();
  2524. } else if (en[1].equals("ttFont")) {
  2525. if (cmap == null) {
  2526. missingParameter(en, "cmap");
  2527. }
  2528. if (hmtx == null) {
  2529. missingParameter(en, "hmtx");
  2530. }
  2531. }
  2532. elements.pop();
  2533. }
  2534. @Override
  2535. public void characters(char[] chars, int start, int length) {
  2536. }
  2537. private String[] getParent() {
  2538. if (!elements.empty()) {
  2539. return elements.peek();
  2540. } else {
  2541. return new String[] { null, null };
  2542. }
  2543. }
  2544. private boolean isParent(Object enx) {
  2545. if (enx instanceof String[][]) {
  2546. for (String[] en : (String[][]) enx) {
  2547. if (isParent(en)) {
  2548. return true;
  2549. }
  2550. }
  2551. return false;
  2552. } else if (enx instanceof String[]) {
  2553. String[] en = (String[]) enx;
  2554. if (!elements.empty()) {
  2555. String[] pn = elements.peek();
  2556. return (pn != null) && sameExpandedName(en, pn);
  2557. } else {
  2558. return ((en[0] == null) && (en[1] == null));
  2559. }
  2560. } else {
  2561. return false;
  2562. }
  2563. }
  2564. private boolean isAnchorElement(String ln) {
  2565. if (ln.equals("BaseAnchor")) {
  2566. return true;
  2567. } else if (ln.equals("EntryAnchor")) {
  2568. return true;
  2569. } else if (ln.equals("ExitAnchor")) {
  2570. return true;
  2571. } else if (ln.equals("LigatureAnchor")) {
  2572. return true;
  2573. } else if (ln.equals("MarkAnchor")) {
  2574. return true;
  2575. } else {
  2576. return ln.equals("Mark2Anchor");
  2577. }
  2578. }
  2579. private Map<Integer, Integer> getCMAP() {
  2580. Map<Integer, Integer> cmap = new TreeMap();
  2581. for (int[] cme : cmapEntries) {
  2582. Integer c = Integer.valueOf(cme[0]);
  2583. Integer g = Integer.valueOf(cme[1]);
  2584. cmap.put(c, g);
  2585. }
  2586. return cmap;
  2587. }
  2588. private Map<Integer, Integer> getGMAP() {
  2589. Map<Integer, Integer> gmap = new TreeMap();
  2590. for (int[] cme : cmapEntries) {
  2591. Integer c = Integer.valueOf(cme[0]);
  2592. Integer g = Integer.valueOf(cme[1]);
  2593. gmap.put(g, c);
  2594. }
  2595. return gmap;
  2596. }
  2597. private int[][] getHMTX() {
  2598. int ne = hmtxEntries.size();
  2599. int[][] hmtx = new int [ ne ] [ 2 ];
  2600. for (int i = 0; i < ne; i++) {
  2601. int[] ea = hmtxEntries.get(i);
  2602. if (ea != null) {
  2603. hmtx [ i ] [ 0 ] = ea[0];
  2604. hmtx [ i ] [ 1 ] = ea[1];
  2605. }
  2606. }
  2607. return hmtx;
  2608. }
  2609. private GlyphClassTable extractClassDefMapping(Map<String, Integer> glyphClasses, int format, boolean clearSourceMap) {
  2610. GlyphClassTable ct;
  2611. if (format == 1) {
  2612. ct = extractClassDefMapping1(extractClassMappings(glyphClasses, clearSourceMap));
  2613. } else if (format == 2) {
  2614. ct = extractClassDefMapping2(extractClassMappings(glyphClasses, clearSourceMap));
  2615. } else {
  2616. ct = null;
  2617. }
  2618. return ct;
  2619. }
  2620. private GlyphClassTable extractClassDefMapping1(int[][] cma) {
  2621. List entries = new ArrayList<Integer>();
  2622. int s = -1;
  2623. int l = -1;
  2624. Integer zero = Integer.valueOf(0);
  2625. for (int[] m : cma) {
  2626. int g = m[0];
  2627. int c = m[1];
  2628. if (s < 0) {
  2629. s = g;
  2630. l = g - 1;
  2631. entries.add(Integer.valueOf(s));
  2632. }
  2633. while (g > (l + 1)) {
  2634. entries.add(zero);
  2635. l++;
  2636. }
  2637. assert l == (g - 1);
  2638. entries.add(Integer.valueOf(c));
  2639. l = g;
  2640. }
  2641. return GlyphClassTable.createClassTable(entries);
  2642. }
  2643. private GlyphClassTable extractClassDefMapping2(int[][] cma) {
  2644. List entries = new ArrayList<Integer>();
  2645. int s = -1;
  2646. int e = s;
  2647. int l = -1;
  2648. for (int[] m : cma) {
  2649. int g = m[0];
  2650. int c = m[1];
  2651. if (c != l) {
  2652. if (s >= 0) {
  2653. entries.add(new GlyphClassTable.MappingRange(s, e, l));
  2654. }
  2655. s = e = g;
  2656. } else {
  2657. e = g;
  2658. }
  2659. l = c;
  2660. }
  2661. return GlyphClassTable.createClassTable(entries);
  2662. }
  2663. private int[][] extractClassMappings(Map<String, Integer> glyphClasses, boolean clearSourceMap) {
  2664. int nc = glyphClasses.size();
  2665. int i = 0;
  2666. int[][] cma = new int [ nc ] [ 2 ];
  2667. for (Map.Entry<String, Integer> e : glyphClasses.entrySet()) {
  2668. Integer gid = glyphIds.get(e.getKey());
  2669. assert gid != null;
  2670. int[] m = cma [ i ];
  2671. m [ 0 ] = (int) gid;
  2672. m [ 1 ] = (int) e.getValue();
  2673. i++;
  2674. }
  2675. if (clearSourceMap) {
  2676. glyphClasses.clear();
  2677. }
  2678. return sortClassMappings(cma);
  2679. }
  2680. private int[][] sortClassMappings(int[][] cma) {
  2681. Arrays.sort(cma, new Comparator<int[]>() {
  2682. public int compare(int[] m1, int[] m2) {
  2683. assert m1.length > 0;
  2684. assert m2.length > 0;
  2685. if (m1[0] < m2[0]) {
  2686. return -1;
  2687. } else if (m1[0] > m2[0]) {
  2688. return 1;
  2689. } else {
  2690. return 0;
  2691. }
  2692. }
  2693. }
  2694. );
  2695. return cma;
  2696. }
  2697. // sort coverage entries and subtable entries together
  2698. private boolean sortEntries(List cel, List sel) {
  2699. assert cel != null;
  2700. assert sel != null;
  2701. if (cel.size() == sel.size()) {
  2702. int np = cel.size();
  2703. Object[][] pa = new Object [ np ] [ 2 ];
  2704. for (int i = 0; i < np; i++) {
  2705. pa [ i ] [ 0 ] = cel.get(i);
  2706. pa [ i ] [ 1 ] = sel.get(i);
  2707. }
  2708. Arrays.sort(pa, new Comparator<Object[]>() {
  2709. public int compare(Object[] p1, Object[] p2) {
  2710. assert p1.length == 2;
  2711. assert p2.length == 2;
  2712. int c1 = (Integer) p1[0];
  2713. int c2 = (Integer) p2[0];
  2714. if (c1 < c2) {
  2715. return -1;
  2716. } else if (c1 > c2) {
  2717. return 1;
  2718. } else {
  2719. return 0;
  2720. }
  2721. }
  2722. }
  2723. );
  2724. cel.clear();
  2725. sel.clear();
  2726. for (int i = 0; i < np; i++) {
  2727. cel.add(pa [ i ] [ 0 ]);
  2728. sel.add(pa [ i ] [ 1 ]);
  2729. }
  2730. assert cel.size() == sel.size();
  2731. return true;
  2732. } else {
  2733. return false;
  2734. }
  2735. }
  2736. private String makeCoverageKey(String prefix, int index) {
  2737. assert prefix != null;
  2738. assert prefix.length() == 2;
  2739. assert index < 100;
  2740. return prefix + CharUtilities.padLeft(Integer.toString(index, 10), 2, '0');
  2741. }
  2742. private List extractCoverageEntries() {
  2743. List entries = new ArrayList<Integer>(coverageEntries);
  2744. clearCoverage();
  2745. return entries;
  2746. }
  2747. private void clearCoverageEntries() {
  2748. coverageEntries.clear();
  2749. ctFormat = -1;
  2750. ctIndex = -1;
  2751. }
  2752. private void assertCoverageEntriesClear() {
  2753. assert coverageEntries.size() == 0;
  2754. }
  2755. private GlyphCoverageTable extractCoverage() {
  2756. assert (ctFormat == 1) || (ctFormat == 2);
  2757. assert ctIndex >= 0;
  2758. GlyphCoverageTable coverage = GlyphCoverageTable.createCoverageTable(extractCoverageEntries());
  2759. clearCoverage();
  2760. return coverage;
  2761. }
  2762. private void clearCoverages() {
  2763. coverages.clear();
  2764. }
  2765. private void assertCoverageClear() {
  2766. assert ctFormat == -1;
  2767. assert ctIndex == -1;
  2768. assertCoverageEntriesClear();
  2769. }
  2770. private void clearCoverage() {
  2771. ctFormat = -1;
  2772. ctIndex = -1;
  2773. clearCoverageEntries();
  2774. }
  2775. private void assertCoveragesClear() {
  2776. assert coverages.size() == 0;
  2777. }
  2778. private GlyphCoverageTable[] getCoveragesWithPrefix(String prefix) {
  2779. assert prefix != null;
  2780. int prefixLength = prefix.length();
  2781. Set<String> keys = coverages.keySet();
  2782. int mi = -1; // maximum coverage table index
  2783. for (String k : keys) {
  2784. if (k.startsWith(prefix)) {
  2785. int i = Integer.parseInt(k.substring(prefixLength));
  2786. if (i > mi) {
  2787. mi = i;
  2788. }
  2789. }
  2790. }
  2791. GlyphCoverageTable[] gca = new GlyphCoverageTable [ mi + 1 ];
  2792. for (String k : keys) {
  2793. if (k.startsWith(prefix)) {
  2794. int i = Integer.parseInt(k.substring(prefixLength));
  2795. if (i >= 0) {
  2796. gca [ i ] = coverages.get(k);
  2797. }
  2798. }
  2799. }
  2800. return gca;
  2801. }
  2802. private boolean hasMissingCoverage(GlyphCoverageTable[] gca) {
  2803. assert gca != null;
  2804. int nc = 0;
  2805. for (int i = 0, n = gca.length; i < n; i++) {
  2806. if (gca [ i ] != null) {
  2807. nc++;
  2808. }
  2809. }
  2810. return nc != gca.length;
  2811. }
  2812. private String makeFeatureId(int fid) {
  2813. assert fid >= 0;
  2814. return "f" + fid;
  2815. }
  2816. private String makeLookupId(int lid) {
  2817. assert lid >= 0;
  2818. return "lu" + lid;
  2819. }
  2820. private void clearScripts() {
  2821. scripts.clear();
  2822. }
  2823. private List<String> extractLanguageFeatures() {
  2824. List<String> lfl = new ArrayList<String>(languageFeatures);
  2825. clearLanguageFeatures();
  2826. return lfl;
  2827. }
  2828. private void assertLanguageFeaturesClear() {
  2829. assert languageFeatures.size() == 0;
  2830. }
  2831. private void clearLanguageFeatures() {
  2832. languageFeatures.clear();
  2833. }
  2834. private Map<String, List<String>> extractLanguages() {
  2835. Map<String, List<String>> lm = new HashMap(languages);
  2836. clearLanguages();
  2837. return lm;
  2838. }
  2839. private void clearLanguages() {
  2840. languages.clear();
  2841. }
  2842. private void assertFeatureLookupsClear() {
  2843. assert featureLookups.size() == 0;
  2844. }
  2845. private List extractFeatureLookups() {
  2846. List lookups = new ArrayList<String>(featureLookups);
  2847. clearFeatureLookups();
  2848. return lookups;
  2849. }
  2850. private void clearFeatureLookups() {
  2851. featureLookups.clear();
  2852. }
  2853. private void assertFeatureClear() {
  2854. assert flIndex == -1;
  2855. assert featureTag == null;
  2856. assertFeatureLookupsClear();
  2857. }
  2858. private Object[] extractFeature() {
  2859. Object[] fa = new Object [ 2 ];
  2860. fa[0] = featureTag;
  2861. fa[1] = extractFeatureLookups();
  2862. clearFeature();
  2863. return fa;
  2864. }
  2865. private void clearFeature() {
  2866. flIndex = -1;
  2867. featureTag = null;
  2868. clearFeatureLookups();
  2869. }
  2870. private void nextFeature() {
  2871. flSequence++;
  2872. }
  2873. private void clearFeatures() {
  2874. features.clear();
  2875. }
  2876. private void clearSubtableInLookup() {
  2877. stFormat = 0;
  2878. clearCoverages();
  2879. }
  2880. private void clearSubtablesInLookup() {
  2881. clearSubtableInLookup();
  2882. stSequence = 0;
  2883. }
  2884. private void clearSubtablesInTable() {
  2885. clearSubtablesInLookup();
  2886. subtables.clear();
  2887. }
  2888. private void nextSubtableInLookup() {
  2889. stSequence++;
  2890. clearSubtableInLookup();
  2891. }
  2892. private void assertLookupClear() {
  2893. assert ltIndex == -1;
  2894. assert ltFlags == 0;
  2895. }
  2896. private void clearLookup() {
  2897. ltIndex = -1;
  2898. ltFlags = 0;
  2899. clearSubtablesInLookup();
  2900. }
  2901. private Map<GlyphTable.LookupSpec, List<String>> extractLookups() {
  2902. Map<GlyphTable.LookupSpec, List<String>> lookups = new LinkedHashMap<GlyphTable.LookupSpec, List<String>>();
  2903. for (String st : scripts.keySet()) {
  2904. Map<String, List<String>> lm = scripts.get(st);
  2905. if (lm != null) {
  2906. for (String lt : lm.keySet()) {
  2907. List<String> fids = lm.get(lt);
  2908. if (fids != null) {
  2909. for (String fid : fids) {
  2910. if (fid != null) {
  2911. Object[] fa = features.get(fid);
  2912. if (fa != null) {
  2913. assert fa.length == 2;
  2914. String ft = (String) fa[0];
  2915. List<String> lids = (List<String>) fa[1];
  2916. if ((lids != null) && (lids.size() > 0)) {
  2917. GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec(st, lt, ft);
  2918. lookups.put(ls, lids);
  2919. }
  2920. }
  2921. }
  2922. }
  2923. }
  2924. }
  2925. }
  2926. }
  2927. clearScripts();
  2928. clearLanguages();
  2929. clearFeatures();
  2930. return lookups;
  2931. }
  2932. private void clearLookups() {
  2933. clearLookup();
  2934. clearSubtablesInTable();
  2935. ltSequence = 0;
  2936. flSequence = 0;
  2937. }
  2938. private void nextLookup() {
  2939. ltSequence++;
  2940. clearLookup();
  2941. }
  2942. private void clearTable() {
  2943. clearLookups();
  2944. }
  2945. private void assertSubtableClear() {
  2946. assert stFormat == 0;
  2947. assertCoverageEntriesClear();
  2948. }
  2949. private void assertSubtablesClear() {
  2950. assertSubtableClear();
  2951. assert subtables.size() == 0;
  2952. }
  2953. private void clearSubtableEntries() {
  2954. subtableEntries.clear();
  2955. }
  2956. private void assertSubtableEntriesClear() {
  2957. assert subtableEntries.size() == 0;
  2958. }
  2959. private List extractSubtableEntries() {
  2960. List entries = new ArrayList(subtableEntries);
  2961. clearSubtableEntries();
  2962. return entries;
  2963. }
  2964. private int[] extractAlternates() {
  2965. int[] aa = new int [ alternates.size() ];
  2966. int i = 0;
  2967. for (Integer a : alternates) {
  2968. aa[i++] = (int) a;
  2969. }
  2970. clearAlternates();
  2971. return aa;
  2972. }
  2973. private void clearAlternates() {
  2974. alternates.clear();
  2975. }
  2976. private LigatureSet extractLigatures() {
  2977. LigatureSet ls = new LigatureSet(ligatures);
  2978. clearLigatures();
  2979. return ls;
  2980. }
  2981. private void clearLigatures() {
  2982. ligatures.clear();
  2983. }
  2984. private int[] extractSubstitutes() {
  2985. int[] aa = new int [ substitutes.size() ];
  2986. int i = 0;
  2987. for (Integer a : substitutes) {
  2988. aa[i++] = (int) a;
  2989. }
  2990. clearSubstitutes();
  2991. return aa;
  2992. }
  2993. private void clearSubstitutes() {
  2994. substitutes.clear();
  2995. }
  2996. private List extractSequenceEntries() {
  2997. List sequences = extractSubtableEntries();
  2998. int[][] sa = new int [ sequences.size() ] [];
  2999. int i = 0;
  3000. for (Object s : sequences) {
  3001. if (s instanceof int[]) {
  3002. sa[i++] = (int[]) s;
  3003. }
  3004. }
  3005. List entries = new ArrayList();
  3006. entries.add(sa);
  3007. return entries;
  3008. }
  3009. private RuleLookup[] extractRuleLookups() {
  3010. RuleLookup[] lookups = (RuleLookup[]) ruleLookups.toArray(new RuleLookup [ ruleLookups.size() ]);
  3011. clearRuleLookups();
  3012. return lookups;
  3013. }
  3014. private void clearRuleLookups() {
  3015. ruleLookups.clear();
  3016. }
  3017. private GlyphPositioningTable.Value parseValue(String[] en, Attributes attrs, int format) throws SAXException {
  3018. String xPlacement = attrs.getValue("XPlacement");
  3019. int xp = 0;
  3020. if (xPlacement != null) {
  3021. xp = Integer.parseInt(xPlacement);
  3022. } else if ((format & GlyphPositioningTable.Value.X_PLACEMENT) != 0) {
  3023. missingParameter(en, "xPlacement");
  3024. }
  3025. String yPlacement = attrs.getValue("YPlacement");
  3026. int yp = 0;
  3027. if (yPlacement != null) {
  3028. yp = Integer.parseInt(yPlacement);
  3029. } else if ((format & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) {
  3030. missingParameter(en, "yPlacement");
  3031. }
  3032. String xAdvance = attrs.getValue("XAdvance");
  3033. int xa = 0;
  3034. if (xAdvance != null) {
  3035. xa = Integer.parseInt(xAdvance);
  3036. } else if ((format & GlyphPositioningTable.Value.X_ADVANCE) != 0) {
  3037. missingParameter(en, "xAdvance");
  3038. }
  3039. String yAdvance = attrs.getValue("YAdvance");
  3040. int ya = 0;
  3041. if (yAdvance != null) {
  3042. ya = Integer.parseInt(yAdvance);
  3043. } else if ((format & GlyphPositioningTable.Value.Y_ADVANCE) != 0) {
  3044. missingParameter(en, "yAdvance");
  3045. }
  3046. return new GlyphPositioningTable.Value(xp, yp, xa, ya, null, null, null, null);
  3047. }
  3048. private void assertPairClear() {
  3049. assert g2 == -1;
  3050. assert v1 == null;
  3051. assert v2 == null;
  3052. }
  3053. private void clearPair() {
  3054. g2 = -1;
  3055. v1 = null;
  3056. v2 = null;
  3057. }
  3058. private void assertPairsClear() {
  3059. assert pairs.size() == 0;
  3060. }
  3061. private void clearPairs() {
  3062. pairs.clear();
  3063. psIndex = -1;
  3064. }
  3065. private PairValues[] extractPairs() {
  3066. PairValues[] pva = (PairValues[]) pairs.toArray(new PairValues [ pairs.size() ]);
  3067. clearPairs();
  3068. return pva;
  3069. }
  3070. private void assertPairSetsClear() {
  3071. assert pairSets.size() == 0;
  3072. }
  3073. private void clearPairSets() {
  3074. pairSets.clear();
  3075. }
  3076. private PairValues[][] extractPairSets() {
  3077. PairValues[][] pvm = (PairValues[][]) pairSets.toArray(new PairValues [ pairSets.size() ][]);
  3078. clearPairSets();
  3079. return pvm;
  3080. }
  3081. private Anchor[] extractAnchors() {
  3082. Anchor[] aa = (Anchor[]) anchors.toArray(new Anchor [ anchors.size() ]);
  3083. anchors.clear();
  3084. return aa;
  3085. }
  3086. private MarkAnchor[] extractMarkAnchors() {
  3087. MarkAnchor[] maa = new MarkAnchor [ markAnchors.size() ];
  3088. maa = (MarkAnchor[]) markAnchors.toArray(new MarkAnchor [ maa.length ]);
  3089. markAnchors.clear();
  3090. return maa;
  3091. }
  3092. private Anchor[][] extractBaseOrMarkAnchors() {
  3093. int na = baseOrMarkAnchors.size();
  3094. int ncMax = 0;
  3095. for (Anchor[] aa : baseOrMarkAnchors) {
  3096. if (aa != null) {
  3097. int nc = aa.length;
  3098. if (nc > ncMax) {
  3099. ncMax = nc;
  3100. }
  3101. }
  3102. }
  3103. Anchor[][] am = new Anchor [ na ][ ncMax ];
  3104. for (int i = 0; i < na; i++) {
  3105. Anchor[] aa = baseOrMarkAnchors.get(i);
  3106. if (aa != null) {
  3107. for (int j = 0; j < ncMax; j++) {
  3108. if (j < aa.length) {
  3109. am [ i ] [ j ] = aa [ j ];
  3110. }
  3111. }
  3112. }
  3113. }
  3114. baseOrMarkAnchors.clear();
  3115. return am;
  3116. }
  3117. private Integer computeClassCount(Anchor[][] am) {
  3118. int ncMax = 0;
  3119. for (int i = 0, n = am.length; i < n; i++) {
  3120. Anchor[] aa = am [ i ];
  3121. if (aa != null) {
  3122. int nc = aa.length;
  3123. if (nc > ncMax) {
  3124. ncMax = nc;
  3125. }
  3126. }
  3127. }
  3128. return Integer.valueOf(ncMax);
  3129. }
  3130. private Anchor[][] extractComponents() {
  3131. Anchor[][] cam = new Anchor [ components.size() ][];
  3132. cam = (Anchor[][]) components.toArray(new Anchor [ cam.length ][]);
  3133. components.clear();
  3134. return cam;
  3135. }
  3136. private Anchor[][][] extractLigatureAnchors() {
  3137. int na = ligatureAnchors.size();
  3138. int ncMax = 0;
  3139. int nxMax = 0;
  3140. for (Anchor[][] cm : ligatureAnchors) {
  3141. if (cm != null) {
  3142. int nx = cm.length;
  3143. if (nx > nxMax) {
  3144. nxMax = nx;
  3145. }
  3146. for (Anchor[] aa : cm) {
  3147. if (aa != null) {
  3148. int nc = aa.length;
  3149. if (nc > ncMax) {
  3150. ncMax = nc;
  3151. }
  3152. }
  3153. }
  3154. }
  3155. }
  3156. Anchor[][][] lam = new Anchor [ na ] [ nxMax ] [ ncMax ];
  3157. for (int i = 0; i < na; i++) {
  3158. Anchor[][] cm = ligatureAnchors.get(i);
  3159. if (cm != null) {
  3160. for (int j = 0; j < nxMax; j++) {
  3161. if (j < cm.length) {
  3162. Anchor[] aa = cm [ j ];
  3163. if (aa != null) {
  3164. for (int k = 0; k < ncMax; k++) {
  3165. if (k < aa.length) {
  3166. lam [ i ] [ j ] [ k ] = aa [ k ];
  3167. }
  3168. }
  3169. }
  3170. }
  3171. }
  3172. }
  3173. }
  3174. ligatureAnchors.clear();
  3175. return lam;
  3176. }
  3177. private Integer computeLigaturesClassCount(Anchor[][][] lam) {
  3178. int ncMax = 0;
  3179. if (lam != null) {
  3180. for (Anchor[][] cm : lam) {
  3181. if (cm != null) {
  3182. for (Anchor[] aa : cm) {
  3183. if (aa != null) {
  3184. int nc = aa.length;
  3185. if (nc > ncMax) {
  3186. ncMax = nc;
  3187. }
  3188. }
  3189. }
  3190. }
  3191. }
  3192. }
  3193. return Integer.valueOf(ncMax);
  3194. }
  3195. private Integer computeLigaturesComponentCount(Anchor[][][] lam) {
  3196. int nxMax = 0;
  3197. if (lam != null) {
  3198. for (Anchor[][] cm : lam) {
  3199. if (cm != null) {
  3200. int nx = cm.length;
  3201. if (nx > nxMax) {
  3202. nxMax = nx;
  3203. }
  3204. }
  3205. }
  3206. }
  3207. return Integer.valueOf(nxMax);
  3208. }
  3209. private Anchor[] extractAttachmentAnchors() {
  3210. int na = attachmentAnchors.size();
  3211. Anchor[] aa = new Anchor [ na * 2 ];
  3212. for (int i = 0; i < na; i++) {
  3213. Anchor[] ea = attachmentAnchors.get(i);
  3214. int ne = ea.length;
  3215. if (ne > 0) {
  3216. aa [ (i * 2) + 0 ] = ea[0];
  3217. }
  3218. if (ne > 1) {
  3219. aa [ (i * 2) + 1 ] = ea[1];
  3220. }
  3221. }
  3222. attachmentAnchors.clear();
  3223. return aa;
  3224. }
  3225. private void addGDEFSubtable(int stType, GlyphMappingTable mapping) {
  3226. subtables.add(GlyphDefinitionTable.createSubtable(stType, makeLookupId(ltSequence), stSequence, ltFlags, stFormat, mapping, extractSubtableEntries()));
  3227. nextSubtableInLookup();
  3228. }
  3229. private void addGSUBSubtable(int stType, GlyphCoverageTable coverage, List entries) {
  3230. subtables.add(GlyphSubstitutionTable.createSubtable(stType, makeLookupId(ltSequence), stSequence, ltFlags, stFormat, coverage, entries));
  3231. nextSubtableInLookup();
  3232. }
  3233. private void addGSUBSubtable(int stType, GlyphCoverageTable coverage) {
  3234. addGSUBSubtable(stType, coverage, extractSubtableEntries());
  3235. }
  3236. private void addGPOSSubtable(int stType, GlyphCoverageTable coverage, List entries) {
  3237. subtables.add(GlyphPositioningTable.createSubtable(stType, makeLookupId(ltSequence), stSequence, ltFlags, stFormat, coverage, entries));
  3238. nextSubtableInLookup();
  3239. }
  3240. private void addGPOSSubtable(int stType, GlyphCoverageTable coverage) {
  3241. addGPOSSubtable(stType, coverage, extractSubtableEntries());
  3242. }
  3243. }
  3244. private int mapGlyphId0(String glyph) {
  3245. assert glyphIds != null;
  3246. Integer gid = glyphIds.get(glyph);
  3247. if (gid != null) {
  3248. return (int) gid;
  3249. } else {
  3250. return -1;
  3251. }
  3252. }
  3253. private int mapGlyphId(String glyph, String[] currentElement) throws SAXException {
  3254. int g = mapGlyphId0(glyph);
  3255. if (g < 0) {
  3256. unsupportedGlyph(currentElement, glyph);
  3257. return -1;
  3258. } else {
  3259. return g;
  3260. }
  3261. }
  3262. private int[] mapGlyphIds(String glyphs, String[] currentElement) throws SAXException {
  3263. String[] ga = glyphs.split(",");
  3264. int[] gids = new int [ ga.length ];
  3265. int i = 0;
  3266. for (String glyph : ga) {
  3267. gids[i++] = mapGlyphId(glyph, currentElement);
  3268. }
  3269. return gids;
  3270. }
  3271. private int mapGlyphIdToChar(String glyph) {
  3272. assert glyphIds != null;
  3273. Integer gid = glyphIds.get(glyph);
  3274. if (gid != null) {
  3275. if (gmap != null) {
  3276. Integer cid = gmap.get(gid);
  3277. if (cid != null) {
  3278. return cid.intValue();
  3279. }
  3280. }
  3281. }
  3282. return -1;
  3283. }
  3284. private String formatLocator() {
  3285. if (locator == null) {
  3286. return "{null}";
  3287. } else {
  3288. return "{" + locator.getSystemId() + ":" + locator.getLineNumber() + ":" + locator.getColumnNumber() + "}";
  3289. }
  3290. }
  3291. private void unsupportedElement(String[] en) throws SAXException {
  3292. throw new SAXException(formatLocator() + ": unsupported element " + formatExpandedName(en));
  3293. }
  3294. private void notPermittedInElementContext(String[] en, String[] cn, Object xns) throws SAXException {
  3295. assert en != null;
  3296. assert cn != null;
  3297. String s = "element " + formatExpandedName(en) + " not permitted in current element context " + formatExpandedName(cn);
  3298. if (xns == null) {
  3299. s += ", expected root context";
  3300. } else if (xns instanceof String[][]) {
  3301. int nxn = 0;
  3302. s += ", expected one of { ";
  3303. for (String[] xn : (String[][]) xns) {
  3304. if (nxn++ > 0) {
  3305. s += ", ";
  3306. }
  3307. s += formatExpandedName(xn);
  3308. }
  3309. s += " }";
  3310. } else if (xns instanceof String[]) {
  3311. s += ", expected " + formatExpandedName((String[]) xns);
  3312. }
  3313. throw new SAXException(formatLocator() + ": " + s);
  3314. }
  3315. private void missingRequiredAttribute(String[] en, String name) throws SAXException {
  3316. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " missing required attribute " + name);
  3317. }
  3318. private void duplicateGlyph(String[] en, String name, int gid) throws SAXException {
  3319. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate name \"" + name + "\", with identifier value " + gid);
  3320. }
  3321. private void unsupportedGlyph(String[] en, String name) throws SAXException {
  3322. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " refers to unsupported glyph id \"" + name + "\"");
  3323. }
  3324. private void duplicateCMAPCharacter(String[] en, int cid) throws SAXException {
  3325. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate cmap character code: " + CharUtilities.format(cid));
  3326. }
  3327. private void duplicateCMAPGlyph(String[] en, int gid) throws SAXException {
  3328. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate cmap glyph code: " + gid);
  3329. }
  3330. private void duplicateGlyphClass(String[] en, String name, String glyphClass) throws SAXException {
  3331. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate glyph class for \"" + name + "\", with class value " + glyphClass);
  3332. }
  3333. private void unsupportedFormat(String[] en, int format) throws SAXException {
  3334. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " refers to unsupported table format \"" + format + "\"");
  3335. }
  3336. private void invalidIndex(String[] en, int actual, int expected) throws SAXException {
  3337. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " specifies invalid index " + actual + ", expected " + expected);
  3338. }
  3339. private void mismatchedIndex(String[] en, String label, int actual, int expected) throws SAXException {
  3340. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " mismatched " + label + " index: got " + actual + ", expected " + expected);
  3341. }
  3342. private void mismatchedEntries(String[] en, int nce, int nse) throws SAXException {
  3343. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " mismatched coverage and subtable entry counts, # coverages " + nce + ", # entries " + nse);
  3344. }
  3345. private void missingParameter(String[] en, String label) throws SAXException {
  3346. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " missing " + label + " parameter");
  3347. }
  3348. private void duplicateParameter(String[] en, String label) throws SAXException {
  3349. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " duplicate " + label + " parameter");
  3350. }
  3351. private void duplicateCoverageIndex(String[] en, int index) throws SAXException {
  3352. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " duplicate coverage table index " + index);
  3353. }
  3354. private void missingCoverage(String[] en, String type, int expected) throws SAXException {
  3355. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " missing " + type + " coverage table, expected " + ((expected > 0) ? expected : 1) + " table(s)");
  3356. }
  3357. private void missingTag(String[] en, String label) throws SAXException {
  3358. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " missing " + label + " tag");
  3359. }
  3360. private void duplicateTag(String[] en, String label, String tag) throws SAXException {
  3361. throw new SAXException(formatLocator() + ": element " + formatExpandedName(en) + " duplicate " + label + " tag: " + tag);
  3362. }
  3363. private static String[] makeExpandedName(String uri, String localName, String qName) {
  3364. if ((uri != null) && (uri.length() == 0)) {
  3365. uri = null;
  3366. }
  3367. if ((localName != null) && (localName.length() == 0)) {
  3368. localName = null;
  3369. }
  3370. if ((uri == null) && (localName == null)) {
  3371. uri = extractPrefix(qName);
  3372. localName = extractLocalName(qName);
  3373. }
  3374. return new String[] { uri, localName };
  3375. }
  3376. private static String extractPrefix(String qName) {
  3377. String[] sa = qName.split(":");
  3378. if (sa.length == 2) {
  3379. return sa[0];
  3380. } else {
  3381. return null;
  3382. }
  3383. }
  3384. private static String extractLocalName(String qName) {
  3385. String[] sa = qName.split(":");
  3386. if (sa.length == 2) {
  3387. return sa[1];
  3388. } else if (sa.length == 1) {
  3389. return sa[0];
  3390. } else {
  3391. return null;
  3392. }
  3393. }
  3394. private static boolean sameExpandedName(String[] n1, String[] n2) {
  3395. String u1 = n1[0];
  3396. String u2 = n2[0];
  3397. if ((u1 == null) ^ (u2 == null)) {
  3398. return false;
  3399. }
  3400. if ((u1 != null) && (u2 != null)) {
  3401. if (!u1.equals(u2)) {
  3402. return false;
  3403. }
  3404. }
  3405. String l1 = n1[1];
  3406. String l2 = n2[1];
  3407. if ((l1 == null) ^ (l2 == null)) {
  3408. return false;
  3409. }
  3410. if ((l1 != null) && (l2 != null)) {
  3411. if (!l1.equals(l2)) {
  3412. return false;
  3413. }
  3414. }
  3415. return true;
  3416. }
  3417. private static String formatExpandedName(String[] n) {
  3418. String u = (n[0] != null) ? n[0] : "null";
  3419. String l = (n[1] != null) ? n[1] : "null";
  3420. return "{" + u + "}" + l;
  3421. }
  3422. }