| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.complexscripts.scripts; |
| |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; |
| import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; |
| import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable; |
| import org.apache.fop.complexscripts.fonts.GlyphTable; |
| import org.apache.fop.complexscripts.util.CharScript; |
| import org.apache.fop.complexscripts.util.GlyphSequence; |
| import org.apache.fop.complexscripts.util.ScriptContextTester; |
| import org.apache.fop.fonts.MultiByteFont; |
| |
| // CSOFF: LineLengthCheck |
| |
| /** |
| * <p>Abstract script processor base class for which an implementation of the substitution and positioning methods |
| * must be supplied.</p> |
| * |
| * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> |
| */ |
| public abstract class ScriptProcessor { |
| |
| private final String script; |
| |
| private final Map<AssembledLookupsKey, GlyphTable.UseSpec[]> assembledLookups; |
| |
| /** |
| * Instantiate a script processor. |
| * @param script a script identifier |
| */ |
| protected ScriptProcessor(String script) { |
| if ((script == null) || (script.length() == 0)) { |
| throw new IllegalArgumentException("script must be non-empty string"); |
| } else { |
| this.script = script; |
| this.assembledLookups = new HashMap<AssembledLookupsKey, GlyphTable.UseSpec[]>(); |
| } |
| } |
| |
| /** @return script identifier */ |
| public final String getScript() { |
| return script; |
| } |
| |
| /** |
| * Obtain script specific required substitution features. |
| * @return array of suppported substitution features or null |
| */ |
| public abstract String[] getSubstitutionFeatures(); |
| |
| /** |
| * Obtain script specific optional substitution features. |
| * @return array of suppported substitution features or null |
| */ |
| public String[] getOptionalSubstitutionFeatures() { |
| return new String[0]; |
| } |
| |
| /** |
| * Obtain script specific substitution context tester. |
| * @return substitution context tester or null |
| */ |
| public abstract ScriptContextTester getSubstitutionContextTester(); |
| |
| /** |
| * Perform substitution processing using a specific set of lookup tables. |
| * @param gsub the glyph substitution table that applies |
| * @param gs an input glyph sequence |
| * @param script a script identifier |
| * @param language a language identifier |
| * @param lookups a mapping from lookup specifications to glyph subtables to use for substitution processing |
| * @return the substituted (output) glyph sequence |
| */ |
| public final GlyphSequence substitute(GlyphSubstitutionTable gsub, GlyphSequence gs, String script, String language, |
| Map<GlyphTable.LookupSpec, List<GlyphTable.LookupTable>> lookups) { |
| return substitute(gs, script, language, assembleLookups(gsub, getSubstitutionFeatures(), lookups), getSubstitutionContextTester()); |
| } |
| |
| /** |
| * Perform substitution processing using a specific set of ordered glyph table use specifications. |
| * @param gs an input glyph sequence |
| * @param script a script identifier |
| * @param language a language identifier |
| * @param usa an ordered array of glyph table use specs |
| * @param sct a script specific context tester (or null) |
| * @return the substituted (output) glyph sequence |
| */ |
| public GlyphSequence substitute(GlyphSequence gs, String script, String language, GlyphTable.UseSpec[] usa, ScriptContextTester sct) { |
| assert usa != null; |
| for (GlyphTable.UseSpec us : usa) { |
| gs = us.substitute(gs, script, language, sct); |
| } |
| return gs; |
| } |
| |
| /** |
| * Reorder combining marks in glyph sequence so that they precede (within the sequence) the base |
| * character to which they are applied. N.B. In the case of RTL segments, marks are not reordered by this, |
| * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede |
| * their base glyph. |
| * @param gdef the glyph definition table that applies |
| * @param gs an input glyph sequence |
| * @param unscaledWidths associated unscaled advance widths (also reordered) |
| * @param gpa associated glyph position adjustments (also reordered) |
| * @param script a script identifier |
| * @param language a language identifier |
| * @return the reordered (output) glyph sequence |
| */ |
| public GlyphSequence reorderCombiningMarks(GlyphDefinitionTable gdef, GlyphSequence gs, int[] unscaledWidths, int[][] gpa, String script, String language) { |
| return gs; |
| } |
| |
| /** |
| * Obtain script specific required positioning features. |
| * @return array of suppported positioning features or null |
| */ |
| public abstract String[] getPositioningFeatures(); |
| |
| /** |
| * Obtain script specific optional positioning features. |
| * @return array of suppported positioning features or null |
| */ |
| public String[] getOptionalPositioningFeatures() { |
| return new String[0]; |
| } |
| |
| /** |
| * Obtain script specific positioning context tester. |
| * @return positioning context tester or null |
| */ |
| public abstract ScriptContextTester getPositioningContextTester(); |
| |
| /** |
| * Perform positioning processing using a specific set of lookup tables. |
| * @param gpos the glyph positioning table that applies |
| * @param gs an input glyph sequence |
| * @param script a script identifier |
| * @param language a language identifier |
| * @param fontSize size in device units |
| * @param lookups a mapping from lookup specifications to glyph subtables to use for positioning processing |
| * @param widths array of default advancements for each glyph |
| * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order, |
| * with one 4-tuple for each element of glyph sequence |
| * @return true if some adjustment is not zero; otherwise, false |
| */ |
| public final boolean position(GlyphPositioningTable gpos, GlyphSequence gs, String script, String language, int fontSize, |
| Map<GlyphTable.LookupSpec, List<GlyphTable.LookupTable>> lookups, int[] widths, int[][] adjustments) { |
| return position(gs, script, language, fontSize, assembleLookups(gpos, getPositioningFeatures(), lookups), widths, adjustments, getPositioningContextTester()); |
| } |
| |
| /** |
| * Perform positioning processing using a specific set of ordered glyph table use specifications. |
| * @param gs an input glyph sequence |
| * @param script a script identifier |
| * @param language a language identifier |
| * @param fontSize size in device units |
| * @param usa an ordered array of glyph table use specs |
| * @param widths array of default advancements for each glyph in font |
| * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order, |
| * with one 4-tuple for each element of glyph sequence |
| * @param sct a script specific context tester (or null) |
| * @return true if some adjustment is not zero; otherwise, false |
| */ |
| public boolean position(GlyphSequence gs, String script, String language, int fontSize, GlyphTable.UseSpec[] usa, int[] widths, int[][] adjustments, ScriptContextTester sct) { |
| assert usa != null; |
| boolean adjusted = false; |
| for (GlyphTable.UseSpec us : usa) { |
| if (us.position(gs, script, language, fontSize, widths, adjustments, sct)) { |
| adjusted = true; |
| } |
| } |
| return adjusted; |
| } |
| |
| /** |
| * Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups, |
| * where the order of the array is in accordance to the order of the applicable lookup list. |
| * @param table the governing glyph table |
| * @param features array of feature identifiers to apply |
| * @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features |
| * @return ordered array of assembled lookup table use specifications |
| */ |
| public final GlyphTable.UseSpec[] assembleLookups(GlyphTable table, String[] features, |
| Map<GlyphTable.LookupSpec, List<GlyphTable.LookupTable>> lookups) { |
| AssembledLookupsKey key = new AssembledLookupsKey(table, features, lookups); |
| GlyphTable.UseSpec[] usa; |
| if ((usa = assembledLookupsGet(key)) != null) { |
| return usa; |
| } else { |
| return assembledLookupsPut(key, table.assembleLookups(features, lookups)); |
| } |
| } |
| |
| private GlyphTable.UseSpec[] assembledLookupsGet(AssembledLookupsKey key) { |
| return assembledLookups.get(key); |
| } |
| |
| private GlyphTable.UseSpec[] assembledLookupsPut(AssembledLookupsKey key, GlyphTable.UseSpec[] usa) { |
| assembledLookups.put(key, usa); |
| return usa; |
| } |
| |
| /** |
| * Obtain script processor instance associated with specified script. |
| * @param script a script identifier |
| * @return a script processor instance or null if none found |
| */ |
| public static synchronized ScriptProcessor getInstance(String script, Map<String, ScriptProcessor> processors) { |
| ScriptProcessor sp = null; |
| assert processors != null; |
| if ((sp = processors.get(script)) == null) { |
| processors.put(script, sp = createProcessor(script)); |
| } |
| return sp; |
| } |
| |
| // [TBD] - rework to provide more configurable binding between script name and script processor constructor |
| private static ScriptProcessor createProcessor(String script) { |
| ScriptProcessor sp = null; |
| int sc = CharScript.scriptCodeFromTag(script); |
| if (sc == CharScript.SCRIPT_ARABIC) { |
| sp = new ArabicScriptProcessor(script); |
| } else if (CharScript.isIndicScript(sc)) { |
| sp = IndicScriptProcessor.makeProcessor(script); |
| } else if (sc == CharScript.SCRIPT_THAI) { |
| sp = new ThaiScriptProcessor(script); |
| } else { |
| sp = new DefaultScriptProcessor(script); |
| } |
| return sp; |
| } |
| |
| private static class AssembledLookupsKey { |
| |
| private final GlyphTable table; |
| private final String[] features; |
| private final Map<GlyphTable.LookupSpec, List<GlyphTable.LookupTable>> lookups; |
| |
| AssembledLookupsKey(GlyphTable table, String[] features, Map<GlyphTable.LookupSpec, List<GlyphTable.LookupTable>> lookups) { |
| this.table = table; |
| this.features = features; |
| this.lookups = lookups; |
| } |
| |
| /** {@inheritDoc} */ |
| public int hashCode() { |
| int hc = 0; |
| hc = 7 * hc + (hc ^ table.hashCode()); |
| hc = 11 * hc + (hc ^ Arrays.hashCode(features)); |
| hc = 17 * hc + (hc ^ lookups.hashCode()); |
| return hc; |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean equals(Object o) { |
| if (o instanceof AssembledLookupsKey) { |
| AssembledLookupsKey k = (AssembledLookupsKey) o; |
| if (!table.equals(k.table)) { |
| return false; |
| } else if (!Arrays.equals(features, k.features)) { |
| return false; |
| } else { |
| return lookups.equals(k.lookups); |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| } |
| |
| public CharSequence preProcess(CharSequence charSequence, MultiByteFont font, List associations) { |
| return charSequence; |
| } |
| } |