/**
* Copyright 2011 JogAmp Community. 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 JogAmp Community ``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 JogAmp Community OR
* 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.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package jogamp.graph.font.typecast;
import jogamp.graph.font.typecast.ot.OTFont;
import jogamp.graph.font.typecast.ot.OTFontCollection;
import jogamp.graph.font.typecast.ot.table.CmapFormat;
import jogamp.graph.font.typecast.ot.table.CmapIndexEntry;
import jogamp.graph.font.typecast.ot.table.CmapTable;
import jogamp.graph.font.typecast.ot.table.HdmxTable;
import jogamp.graph.font.typecast.ot.table.ID;
import jogamp.graph.geom.plane.AffineTransform;
import com.jogamp.common.util.IntObjectHashMap;
import com.jogamp.graph.curve.OutlineShape;
import com.jogamp.graph.font.Font;
import com.jogamp.graph.font.FontFactory;
import com.jogamp.graph.geom.SVertex;
import com.jogamp.graph.geom.Vertex;
import com.jogamp.opengl.math.geom.AABBox;
class TypecastFont implements Font {
static final boolean DEBUG = false;
private static final Vertex.Factory<SVertex> vertexFactory = SVertex.factory();
// private final OTFontCollection fontset;
/* pp */ final OTFont font;
private final CmapFormat cmapFormat;
private final int cmapentries;
private final IntObjectHashMap char2Glyph;
private final TypecastHMetrics metrics;
private final float[] tmpV3 = new float[3];
// FIXME: Add cache size to limit memory usage ??
public TypecastFont(final OTFontCollection fontset) {
// this.fontset = fontset;
this.font = fontset.getFont(0);
// FIXME: Generic attempt to find the best CmapTable,
// which is assumed to be the one with the most entries (stupid 'eh?)
final CmapTable cmapTable = font.getCmapTable();
final CmapFormat[] _cmapFormatP = { null, null, null, null };
int platform = -1;
int platformLength = -1;
int encoding = -1;
for(int i=0; i<cmapTable.getNumTables(); i++) {
final CmapIndexEntry cmapIdxEntry = cmapTable.getCmapIndexEntry(i);
final int pidx = cmapIdxEntry.getPlatformId();
final CmapFormat cf = cmapIdxEntry.getFormat();
if(DEBUG) {
System.err.println("CmapFormat["+i+"]: platform " + pidx +
", encoding "+cmapIdxEntry.getEncodingId() + ": "+cf);
}
if( _cmapFormatP[pidx] == null ||
_cmapFormatP[pidx].getLength() < cf.getLength() ) {
_cmapFormatP[pidx] = cf;
if( cf.getLength() > platformLength ) {
platformLength = cf.getLength() ;
platform = pidx;
encoding = cmapIdxEntry.getEncodingId();
}
}
}
if(0 <= platform) {
cmapFormat = _cmapFormatP[platform];
if(DEBUG) {
System.err.println("Selected CmapFormat: platform " + platform +
", encoding "+encoding + ": "+cmapFormat);
}
} else {
CmapFormat _cmapFormat = null;
/*if(null == _cmapFormat) {
platform = ID.platformMacintosh;
encoding = ID.encodingASCII;
_cmapFormat = cmapTable.getCmapFormat(platform, encoding);
} */
if(null == _cmapFormat) {
// default unicode
platform = ID.platformMicrosoft;
encoding = ID.encodingUnicode;
_cmapFormat = cmapTable.getCmapFormat((short)platform, (short)encoding);
}
if(null == _cmapFormat) {
// maybe a symbol font ?
platform = ID.platformMicrosoft;
encoding = ID.encodingSymbol;
_cmapFormat = cmapTable.getCmapFormat((short)platform, (short)encoding);
}
if(null == _cmapFormat) {
throw new RuntimeException("Cannot find a suitable cmap table for font "+font);
}
cmapFormat = _cmapFormat;
if(DEBUG) {
System.err.println("Selected CmapFormat (2): platform " + platform + ", encoding "+encoding + ": "+cmapFormat);
}
}
{
int _cmapentries = 0;
for (int i = 0; i < cmapFormat.getRangeCount(); ++i) {
final CmapFormat.Range range = cmapFormat.getRange(i);
_cmapentries += range.getEndCode() - range.getStartCode() + 1; // end included
}
cmapentries = _cmapentries;
}
if(DEBUG) {
System.err.println("font direction hint: "+font.getHeadTable().getFontDirectionHint());
System.err.println("num glyphs: "+font.getNumGlyphs());
System.err.println("num cmap entries: "+cmapentries);
System.err.println("num cmap ranges: "+cmapFormat.getRangeCount());
for (int i = 0; i < cmapFormat.getRangeCount(); ++i) {
final CmapFormat.Range range = cmapFormat.getRange(i);
for (int j = range.getStartCode(); j <= range.getEndCode(); ++j) {
final int code = cmapFormat.mapCharCode(j);
if(code < 15) {
System.err.println(" char: " + j + " ( " + (char)j +" ) -> " + code);
}
}
}
}
char2Glyph = new IntObjectHashMap(cmapentries + cmapentries/4);
metrics = new TypecastHMetrics(this);
}
@Override
public StringBuilder getName(final StringBuilder sb, final int nameIndex) {
return font.getName(nameIndex, sb);
}
@Override
public String getName(final int nameIndex) {
return getName(null, nameIndex).toString();
}
@Override
public StringBuilder getAllNames(final StringBuilder sb, final String separator) {
return font.getAllNames(sb, separator);
}
@Override
public StringBuilder getFullFamilyName(StringBuilder sb) {
sb = getName(sb, Font.NAME_FAMILY).append("-");
getName(sb, Font.NAME_SUBFAMILY);
return sb;
}
@Override
public float getAdvanceWidth(final int glyphID, final float pixelSize) {
return font.getHmtxTable().getAdvanceWidth(glyphID) * metrics.getScale(pixelSize);
}
@Override
public final Metrics getMetrics() {
return metrics;
}
@Override
public Glyph getGlyph(final char symbol) {
TypecastGlyph result = (TypecastGlyph) char2Glyph.get(symbol);
if (null == result) {
// final short code = (short) char2Code.get(symbol);
short code = (short) cmapFormat.mapCharCode(symbol);
if(0 == code && 0 != symbol) {
// reserved special glyph IDs by convention
switch(symbol) {
case ' ': code = Glyph.ID_SPACE; break;
case '\n': code = Glyph.ID_CR; break;
default: code = Glyph.ID_UNKNOWN;
}
}
jogamp.graph.font.typecast.ot.OTGlyph glyph = font.getGlyph(code);
if(null == glyph) {
glyph = font.getGlyph(Glyph.ID_UNKNOWN);
}
if(null == glyph) {
throw new RuntimeException("Could not retrieve glyph for symbol: <"+symbol+"> "+(int)symbol+" -> glyph id "+code);
}
final OutlineShape shape = TypecastRenderer.buildShape(symbol, glyph, vertexFactory);
result = new TypecastGlyph(this, symbol, code, glyph.getBBox(), glyph.getAdvanceWidth(), shape);
if(DEBUG) {
System.err.println("New glyph: " + (int)symbol + " ( " + symbol +" ) -> " + code + ", contours " + glyph.getPointCount() + ": " + shape);
}
glyph.clearPointData();
final HdmxTable hdmx = font.getHdmxTable();
if (null!= result && null != hdmx) {
/*if(DEBUG) {
System.err.println("hdmx "+hdmx);
}*/
for (int i=0; i<hdmx.getNumberOfRecords(); i++)
{
final HdmxTable.DeviceRecord dr = hdmx.getRecord(i);
result.addAdvance(dr.getWidth(code), dr.getPixelSize());
/* if(DEBUG) {
System.err.println("hdmx advance : pixelsize = "+dr.getWidth(code)+" : "+ dr.getPixelSize());
} */
}
}
char2Glyph.put(symbol, result);
}
return result;
}
@Override
public final float getPixelSize(final float fontSize /* points per inch */, final float resolution) {
return fontSize * resolution / ( 72f /* points per inch */ );
}
@Override
public float getLineHeight(final float pixelSize) {
final Metrics metrics = getMetrics();
final float lineGap = metrics.getLineGap(pixelSize) ; // negative value!
final float ascent = metrics.getAscent(pixelSize) ; // negative value!
final float descent = metrics.getDescent(pixelSize) ; // positive value!
final float advanceY = lineGap - descent + ascent; // negative value!
return -advanceY;
}
@Override
public float getMetricWidth(final CharSequence string, final float pixelSize) {
float width = 0;
final int len = string.length();
for (int i=0; i< len; i++) {
final char character = string.charAt(i);
if (character == '\n') {
width = 0;
} else {
final Glyph glyph = getGlyph(character);
width += glyph.getAdvance(pixelSize, false);
}
}
return (int)(width + 0.5f);
}
@Override
public float getMetricHeight(final CharSequence string, final float pixelSize, final AABBox tmp) {
int height = 0;
for (int i=0; i<string.length(); i++) {
final char character = string.charAt(i);
if (character != ' ') {
final Glyph glyph = getGlyph(character);
final AABBox bbox = glyph.getBBox(tmp, pixelSize, tmpV3);
height = (int)Math.ceil(Math.max(bbox.getHeight(), height));
}
}
return height;
}
@Override
public AABBox getMetricBounds(final CharSequence string, final float pixelSize) {
if (string == null) {
return new AABBox();
}
final int charCount = string.length();
final float lineHeight = getLineHeight(pixelSize);
float totalHeight = 0;
float totalWidth = 0;
float curLineWidth = 0;
for (int i=0; i<charCount; i++) {
final char character = string.charAt(i);
if (character == '\n') {
totalWidth = Math.max(curLineWidth, totalWidth);
curLineWidth = 0;
totalHeight += lineHeight;
continue;
}
final Glyph glyph = getGlyph(character);
curLineWidth += glyph.getAdvance(pixelSize, true);
}
if (curLineWidth > 0) {
totalHeight += lineHeight;
totalWidth = Math.max(curLineWidth, totalWidth);
}
return new AABBox(0, 0, 0, totalWidth, totalHeight,0);
}
@Override
public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string, final float pixelSize,
final AffineTransform temp1, final AffineTransform temp2) {
if (string == null) {
return new AABBox();
}
final int charCount = string.length();
final float lineHeight = getLineHeight(pixelSize);
final float scale = getMetrics().getScale(pixelSize);
final AABBox tbox = new AABBox();
final AABBox res = new AABBox();
float y = 0;
float advanceTotal = 0;
for(int i=0; i< charCount; i++) {
final char character = string.charAt(i);
if( '\n' == character ) {
y -= lineHeight;
advanceTotal = 0;
} else if (character == ' ') {
advanceTotal += getAdvanceWidth(Glyph.ID_SPACE, pixelSize);
} else {
// reset transform
if( null != transform ) {
temp1.setTransform(transform);
} else {
temp1.setToIdentity();
}
temp1.translate(advanceTotal, y, temp2);
temp1.scale(scale, scale, temp2);
tbox.reset();
final Font.Glyph glyph = getGlyph(character);
res.resize(temp1.transform(glyph.getBBox(), tbox));
final OutlineShape glyphShape = glyph.getShape();
if( null == glyphShape ) {
continue;
}
advanceTotal += glyph.getAdvance(pixelSize, true);
}
}
return res;
}
@Override
final public int getNumGlyphs() {
return font.getNumGlyphs();
}
@Override
public boolean isPrintableChar( final char c ) {
return FontFactory.isPrintableChar(c);
}
@Override
public String toString() {
return getFullFamilyName(null).toString();
}
}