/*
* Copyright 2014-15 Skynav, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.skynav.ttpe.fonts;
import java.nio.IntBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import org.apache.fontbox.ttf.advanced.util.CharAssociation;
import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
import com.skynav.ttpe.geometry.Axis;
@SuppressWarnings("rawtypes")
public class GlyphMapping {
private Key key; // key (input text + input features)
private String script; // resolved script
private String language; // resolved language
private int[] glyphs; // mapped glyphs
private String glyphsAsText; // mapped glyphs as text
private List<CharAssociation> associations; // associations between mapped glyphs and original text
private int[] advances; // glyph advances (in IPD)
private int[][] adjustments; // glyph position adjustments
public GlyphMapping(Key key, GlyphSequence gs, int[] advances, int[][] adjustments) {
this(key, null, null, getGlyphs(gs), getText(gs), getAssociations(gs), advances, adjustments);
}
private GlyphMapping(Key key, String script, String language, int[] glyphs, String glyphsAsText, List<CharAssociation> associations, int[] advances, int[][] adjustments) {
this.key = key;
this.script = script;
this.glyphs = glyphs;
this.glyphsAsText = glyphsAsText;
this.associations = associations;
this.advances = advances;
this.adjustments = adjustments;
}
public Key getKey() {
return key;
}
public String getText() {
return key.getText();
}
public Collection<FontFeature> getFeatures() {
return key.getFeatures();
}
public String getScript() {
return key.getScript();
}
public void setResolvedScript(String script) {
this.script = script;
}
public String getResolvedScript() {
return script;
}
public String getLanguage() {
return key.getLanguage();
}
public String getResolvedLanguage() {
return language;
}
public void setResolvedLanguage(String language) {
this.language = language;
}
public Orientation getOrientation() {
return key.getOrientation();
}
public Combination getCombination() {
return key.getCombination();
}
public boolean isReversed() {
return key.isReversed();
}
public boolean isMirrored() {
return key.isMirrored();
}
public int[] getGlyphs() {
return glyphs;
}
public String getGlyphsAsText() {
return glyphsAsText;
}
public List getAssociations() {
return associations;
}
public int[] getAdvances() {
return advances;
}
public int[][] getAdjustments() {
return adjustments;
}
public GlyphMapping reverse(GlyphMapping.Key gmk) {
return new GlyphMapping(gmk, script, language, reverse(glyphs), reverse(glyphsAsText), reverse(associations), reverse(advances), reverse(adjustments));
}
private static int[] reverse(int[] a) {
if ((a != null) && (a.length > 0)) {
int[] aNew = new int[a.length];
System.arraycopy(a, 0, aNew, 0, a.length);
for (int i = 0, n = a.length, m = n / 2; i <= m; ++i) {
int k = n - i - 1;
aNew [ k ] = a [ i ];
aNew [ i ] = a [ k ];
}
return aNew;
} else
return a;
}
private static int[][] reverse(int[][] a) {
if ((a != null) && (a.length > 0)) {
int[][] aNew = new int[a.length][];
System.arraycopy(a, 0, aNew, 0, a.length);
for (int i = 0, n = a.length, m = n / 2; i < m; ++i) {
int k = n - i - 1;
aNew [ k ] = a [ i ];
aNew [ i ] = a [ k ];
}
return aNew;
} else
return a;
}
private static String reverse(String s) {
return new StringBuffer(s).reverse().toString();
}
private static List<CharAssociation> reverse(List<CharAssociation> associations) {
List<CharAssociation> associationsNew = new java.util.ArrayList<CharAssociation>(associations);
Collections.reverse(associationsNew);
return associationsNew;
}
private static String getText(GlyphSequence gs) {
IntBuffer cb = gs.getCharacters();
cb.rewind();
StringBuffer sb = new StringBuffer(cb.limit() - cb.position());
while (cb.position() < cb.limit()) {
int c = cb.get();
if (c < 0x10000)
sb.append((char) c);
else {
c -= 0x10000;
int sh = ((c >> 10) & 0x03FF) + 0xD800;
int sl = ((c >> 0) & 0x03FF) + 0xDC00;
sb.append((char) sh);
sb.append((char) sl);
}
}
return sb.toString();
}
private static int[] getGlyphs(GlyphSequence gs) {
IntBuffer gb = gs.getGlyphs();
gb.rewind();
int[] glyphs = new int[gb.limit() - gb.position()];
for (int i = 0; gb.position() < gb.limit(); ) {
glyphs[i++] = gb.get();
}
return glyphs;
}
private static List<CharAssociation> getAssociations(GlyphSequence gs) {
List associations = gs.getAssociations();
if (associations != null) {
List<CharAssociation> cal = new java.util.ArrayList<CharAssociation>(associations.size());
for (Object a : associations) {
if (a instanceof CharAssociation)
cal.add((CharAssociation) a);
else
cal.add(null);
}
return cal;
} else
return null;
}
public static Key makeKey(String text, SortedSet<FontFeature> features) {
return new Key(text, features);
}
public static Key makeKey(Key key, SortedSet<FontFeature> features) {
return new Key(key, features);
}
static class Key {
private String text; // original text
private Map<String,FontFeature> features; // font feature and pseudo-feature parameters
Key(Key key, SortedSet<FontFeature> features) {
this(key.text, augmentFeatures(new java.util.TreeSet<FontFeature>(key.features.values()), features));
}
Key(String text, SortedSet<FontFeature> features) {
this.text = text;
this.features = new java.util.TreeMap<String,FontFeature>();
populateFeatures(features);
}
@Override
public int hashCode() {
int hc = 23;
hc = hc * 31 + text.hashCode();
hc = hc * 31 + features.hashCode();
return hc;
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
if (!text.equals(other.text))
return false;
else if (!features.equals(other.features))
return false;
else
return true;
} else
return false;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append('[');
sb.append('\'');
sb.append(text);
sb.append('\'');
if (features != null) {
sb.append(',');
sb.append('[');
boolean first = true;
for (FontFeature f : features.values()) {
if (!first)
sb.append(',');
else
first = false;
sb.append(f);
}
sb.append(']');
}
sb.append(']');
return sb.toString();
}
String getText() {
return text;
}
Collection<FontFeature> getFeatures() {
return features.values();
}
String getScript() {
FontFeature f = getFeature(FontFeature.SCPT);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof String))
return (String) a0;
}
return null;
}
String getLanguage() {
FontFeature f = getFeature(FontFeature.LANG);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof String))
return (String) a0;
}
return null;
}
boolean isKerningEnabled() {
return getKerning();
}
Boolean getKerning() {
FontFeature f = getFeature(FontFeature.KERN);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof Boolean))
return (Boolean) a0;
}
return Boolean.FALSE;
}
Orientation getOrientation() {
FontFeature f = getFeature(FontFeature.ORNT);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof Orientation))
return (Orientation) a0;
}
return Orientation.ROTATE000;
}
Combination getCombination() {
FontFeature f = getFeature(FontFeature.COMB);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof Combination))
return (Combination) a0;
}
return Combination.NONE;
}
Axis getAdvanceAxis(FontKey key) {
return (key.axis.cross(!getCombination().isNone()).isVertical() && !getOrientation().isRotated()) ? Axis.VERTICAL : Axis.HORIZONTAL;
}
boolean isReversed() {
return getReversed();
}
Boolean getReversed() {
FontFeature f = getFeature(FontFeature.REVS);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof Boolean))
return (Boolean) a0;
}
return Boolean.FALSE;
}
boolean isMirrored() {
return getMirrored();
}
Boolean getMirrored() {
FontFeature f = getFeature(FontFeature.MIRR);
if (f != null) {
Object a0 = f.getArgument(0);
if ((a0 != null) && (a0 instanceof Boolean))
return (Boolean) a0;
}
return Boolean.FALSE;
}
private void populateFeatures(SortedSet<FontFeature> features) {
for (FontFeature f : features)
putFeature(f);
}
private static SortedSet<FontFeature> augmentFeatures(SortedSet<FontFeature> features, SortedSet<FontFeature> augmentation) {
SortedSet<FontFeature> fs = features;
for (FontFeature f : augmentation)
fs.add(f);
return Collections.unmodifiableSortedSet(fs);
}
private FontFeature getFeature(FontFeature f) {
assert f != null;
return this.features.get(f.getFeature());
}
private void putFeature(FontFeature f) {
assert f != null;
this.features.put(f.getFeature(), f);
}
}
}