blob: 108f26c829067633c30a1434d92180db4000b910 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.complexscripts.fonts;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import org.apache.fop.complexscripts.util.GlyphSequence;
import org.apache.fop.complexscripts.util.ScriptContextTester;
// CSOFF: LineLengthCheck
/**
* <p>The <code>GlyphSubstitutionState</code> implements an state object used during glyph substitution
* processing.</p>
*
* <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
*/
public class GlyphSubstitutionState extends GlyphProcessingState {
/** alternates index */
private int[] alternatesIndex;
/** current output glyph sequence */
private IntBuffer ogb;
/** current output glyph to character associations */
private List oal;
/** character association predications */
private boolean predications;
/**
* Construct default (reset) glyph substitution state.
*/
public GlyphSubstitutionState() {
}
/**
* Construct glyph substitution state.
* @param gs input glyph sequence
* @param script script identifier
* @param language language identifier
* @param feature feature identifier
* @param sct script context tester (or null)
*/
public GlyphSubstitutionState(GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct) {
super(gs, script, language, feature, sct);
this.ogb = IntBuffer.allocate(gs.getGlyphCount());
this.oal = new ArrayList(gs.getGlyphCount());
this.predications = gs.getPredications();
}
/**
* Construct glyph substitution state using an existing state object using shallow copy
* except as follows: input glyph sequence is copied deep except for its characters array.
* @param ss existing positioning state to copy from
*/
public GlyphSubstitutionState(GlyphSubstitutionState ss) {
super(ss);
this.ogb = IntBuffer.allocate(indexLast);
this.oal = new ArrayList(indexLast);
}
/**
* Reset glyph substitution state.
* @param gs input glyph sequence
* @param script script identifier
* @param language language identifier
* @param feature feature identifier
* @param sct script context tester (or null)
*/
public GlyphSubstitutionState reset(GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct) {
super.reset(gs, script, language, feature, sct);
this.alternatesIndex = null;
this.ogb = IntBuffer.allocate(gs.getGlyphCount());
this.oal = new ArrayList(gs.getGlyphCount());
this.predications = gs.getPredications();
return this;
}
/**
* Set alternates indices.
* @param alternates array of alternates indices ordered by coverage index
*/
public void setAlternates(int[] alternates) {
this.alternatesIndex = alternates;
}
/**
* Obtain alternates index associated with specified coverage index. An alternates
* index is used to select among stylistic alternates of a glyph at a particular
* coverage index. This information must be provided by the document itself (in the
* form of an extension attribute value), since a font has no way to determine which
* alternate the user desires.
* @param ci coverage index
* @return an alternates index
*/
public int getAlternatesIndex(int ci) {
if (alternatesIndex == null) {
return 0;
} else if ((ci < 0) || (ci > alternatesIndex.length)) {
return 0;
} else {
return alternatesIndex [ ci ];
}
}
/**
* Put (write) glyph into glyph output buffer.
* @param glyph to write
* @param a character association that applies to glyph
* @param predication a predication value to add to association A if predications enabled
*/
public void putGlyph(int glyph, GlyphSequence.CharAssociation a, Object predication) {
if (!ogb.hasRemaining()) {
ogb = growBuffer(ogb);
}
ogb.put(glyph);
if (predications && (predication != null)) {
a.setPredication(feature, predication);
}
oal.add(a);
}
/**
* Put (write) array of glyphs into glyph output buffer.
* @param glyphs to write
* @param associations array of character associations that apply to glyphs
* @param predication optional predicaion object to be associated with glyphs' associations
*/
public void putGlyphs(int[] glyphs, GlyphSequence.CharAssociation[] associations, Object predication) {
assert glyphs != null;
assert associations != null;
assert associations.length >= glyphs.length;
for (int i = 0, n = glyphs.length; i < n; i++) {
putGlyph(glyphs [ i ], associations [ i ], predication);
}
}
/**
* Obtain output glyph sequence.
* @return newly constructed glyph sequence comprised of original
* characters, output glyphs, and output associations
*/
public GlyphSequence getOutput() {
int position = ogb.position();
if (position > 0) {
ogb.limit(position);
ogb.rewind();
return new GlyphSequence(igs.getCharacters(), ogb, oal);
} else {
return igs;
}
}
/**
* Apply substitution subtable to current state at current position (only),
* resulting in the consumption of zero or more input glyphs, and possibly
* replacing the current input glyphs starting at the current position, in
* which case it is possible that indexLast is altered to be either less than
* or greater than its value prior to this application.
* @param st the glyph substitution subtable to apply
* @return true if subtable applied, or false if it did not (e.g., its
* input coverage table did not match current input context)
*/
public boolean apply(GlyphSubstitutionSubtable st) {
assert st != null;
updateSubtableState(st);
boolean applied = st.substitute(this);
return applied;
}
/**
* Apply a sequence of matched rule lookups to the <code>nig</code> input glyphs
* starting at the current position. If lookups are non-null and non-empty, then
* all input glyphs specified by <code>nig</code> are consumed irregardless of
* whether any specified lookup applied.
* @param lookups array of matched lookups (or null)
* @param nig number of glyphs in input sequence, starting at current position, to which
* the lookups are to apply, and to be consumed once the application has finished
* @return true if lookups are non-null and non-empty; otherwise, false
*/
public boolean apply(GlyphTable.RuleLookup[] lookups, int nig) {
// int nbg = index;
int nlg = indexLast - (index + nig);
int nog = 0;
if ((lookups != null) && (lookups.length > 0)) {
// apply each rule lookup to extracted input glyph array
for (int i = 0, n = lookups.length; i < n; i++) {
GlyphTable.RuleLookup l = lookups [ i ];
if (l != null) {
GlyphTable.LookupTable lt = l.getLookup();
if (lt != null) {
// perform substitution on a copy of previous state
GlyphSubstitutionState ss = new GlyphSubstitutionState(this);
// apply lookup table substitutions
GlyphSequence gs = lt.substitute(ss, l.getSequenceIndex());
// replace current input sequence starting at current position with result
if (replaceInput(0, -1, gs)) {
nog = gs.getGlyphCount() - nlg;
}
}
}
}
// output glyphs and associations
putGlyphs(getGlyphs(0, nog, false, null, null, null), getAssociations(0, nog, false, null, null, null), null);
// consume replaced input glyphs
consume(nog);
return true;
} else {
return false;
}
}
/**
* Apply default application semantices; namely, consume one input glyph,
* writing that glyph (and its association) to the output glyphs (and associations).
*/
public void applyDefault() {
super.applyDefault();
int gi = getGlyph();
if (gi != 65535) {
putGlyph(gi, getAssociation(), null);
}
}
private static IntBuffer growBuffer(IntBuffer ib) {
int capacity = ib.capacity();
int capacityNew = capacity * 2;
IntBuffer ibNew = IntBuffer.allocate(capacityNew);
ib.rewind();
return ibNew.put(ib);
}
}