polargraph_processing/libraries/geomerative/src/geomerative/RFont.java

584 lines
20 KiB
Java

/**
Copyright 2004-2008 Ricard Marxer <email@ricardmarxer.com>
This file is part of Geomerative.
Geomerative is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Geomerative is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Geomerative. If not, see <http://www.gnu.org/licenses/>.
*/
package geomerative;
import processing.core.*;
import org.apache.batik.svggen.font.*;
import org.apache.batik.svggen.font.table.*;
/**
* RShape is a reduced interface for creating, holding and drawing text from TrueType Font files. It's a basic interpreter of TrueType fonts enabling to access any String in the form of a group of shapes. Enabling us in this way to access their geometry.
* @eexample RFont
* @usage Geometry
* @related RGroup
*
* @extended
*/
public class RFont implements PConstants{
Font f;
float scaleFactor = 0.2F;
//int scaleFactorFixed = 1;
/**
* The point size of the font.
* @eexample size
* @related setSize ( )
* @related RFont
*/
public int size = DEFAULT_SIZE;
/**
* The alignment of the font. This property can take the following values: RFont.LEFT, RFont.CENTER and RFont.RIGHT
* @eexample align
* @related setAlign ( )
* @related RFont
*/
public int align = DEFAULT_ALIGN;
final static int DEFAULT_SIZE = 48;
final static int DEFAULT_RESOLUTION = 72;
final static int DEFAULT_ALIGN = RFont.LEFT;
/**
* Should we try to use ASCII, rather than Unicode?
*/
public boolean forceAscii = false;
/**
* The constructor of the RFont object. Use this in order to create a font with which we will be able to draw and obtain outlines of text.
* @eexample RFont
* @param fontPath String, the name of the TrueType Font file which should be situated in the data folder of the sketch.
* @param size int, the point size of the font in points.
* @param align int, this can only take the following values: RFont.LEFT, RFont.CENTER and RFont.RIGHT.
* @related toGroup ( )
* @related toShape ( )
* @related toPolygon ( )
* @related toMesh ( )
* @related draw ( )
*/
public RFont(String fontPath, int size, int align) throws RuntimeException{
// Try to find the font as font path
byte[] bs = RG.parent().loadBytes(fontPath);
f = Font.create(bs);
setSize(size);
setAlign(align);
}
public RFont(String fontPath, int size) throws RuntimeException{
this(fontPath, size, DEFAULT_ALIGN);
}
public RFont(String fontPath) throws RuntimeException{
this(fontPath, DEFAULT_SIZE, DEFAULT_ALIGN);
}
/**
* Use this method to reset the point size of the font.
* @eexample setSize
* @param size int, the point size of the font in points.
* @related size
* @related RFont
*/
public void setSize(int size){
short unitsPerEm = f.getHeadTable().getUnitsPerEm();
int resolution = RG.dpi();
this.scaleFactor = ((float)size * (float)resolution) / (72F * (float)unitsPerEm);
//this.scaleFactorFixed = (int)(this.scaleFactor * 65536F);
//System.out.println(scaleFactor);
//System.out.println(scaleFactorFixed);
}
public float getLineSpacing() {
// More info at:
// http://fontforge.sourceforge.net/faq.html#linespace
// http://typophile.com/node/13081
short unitsPerEm = f.getHeadTable().getUnitsPerEm();
System.out.println("UnitsPerEm (emsize): " + unitsPerEm);
// HHEA table method:
float hheaLineGap = (f.getHheaTable().getAscender() - f.getHheaTable().getDescender() + f.getHheaTable().getLineGap()) * this.scaleFactor;
System.out.println("HHEA lineGap: " + hheaLineGap);
// OS2 table typographic line gap method:
float os2TypoLineGap = (f.getOS2Table().getTypoAscender() - f.getOS2Table().getTypoDescender() + f.getOS2Table().getTypoLineGap()) * this.scaleFactor;
System.out.println("Os2 Typo lineGap: " + os2TypoLineGap);
// OS2 table win line gap method:
float os2WinLineGap = (f.getOS2Table().getWinAscent() + f.getOS2Table().getWinDescent()) * this.scaleFactor;
System.out.println("Os2 Win lineGap: " + os2WinLineGap);
// Automatic calculation
float autoLineGap = f.getHeadTable().getUnitsPerEm() * 1.25f * this.scaleFactor;
System.out.println("Automatic lineGap: " + autoLineGap);
return hheaLineGap;
}
/**
* Use this method to reset the alignment of the font. This property can take the following values: RFont.LEFT, RFont.CENTER and RFont.RIGHT
* @eexample setAlign
* @param align int, this can only take the following values: RFont.LEFT, RFont.CENTER and RFont.RIGHT.
* @related align
* @related RFont
*/
public void setAlign(int align) throws RuntimeException{
if(align!=LEFT && align!=CENTER && align!=RIGHT){
throw new RuntimeException("Alignment unknown. The only accepted values are: RFont.LEFT, RFont.CENTER and RFont.RIGHT");
}
this.align = align;
}
/**
* @invisible
**/
public String getFamily(){
return f.getNameTable().getRecord(org.apache.batik.svggen.font.table.Table.nameFontFamilyName);
}
/**
* Use this method to get the outlines of a character in the form of an RShape.
* @eexample RFont_toShape
* @param character char, the character we want the outline from.
* @return RShape, the outline of the character.
* @related toGroup ( )
* @related toPolygon ( )
* @related draw ( )
*/
public RShape toShape(char character){
RGroup grp = toGroup(Character.toString(character));
if(grp.countElements()>0) return (RShape)(grp.elements[0]);
return new RShape();
}
/**
* Use this method to get the outlines of a character in the form of an RPolygon.
* @eexample RFont_toPolygon
* @param character char, the character we want the outline from.
* @return RPolygon, the outline of the character.
* @related toGroup ( )
* @related toShape ( )
* @related draw ( )
*/
public RPolygon toPolygon(char character) {
return toShape(character).toPolygon();
}
private CmapFormat getCmapFormat() {
if (forceAscii) {
// We've been asked to use the ASCII/Macintosh cmap format
return f.getCmapTable().getCmapFormat(
org.apache.batik.svggen.font.table.Table.platformMacintosh,
org.apache.batik.svggen.font.table.Table.encodingRoman );
} else {
short[] platforms = new short[] {
org.apache.batik.svggen.font.table.Table.platformMicrosoft,
org.apache.batik.svggen.font.table.Table.platformAppleUnicode,
org.apache.batik.svggen.font.table.Table.platformMacintosh
};
short[] encodings = new short[] {
org.apache.batik.svggen.font.table.Table.encodingUGL,
org.apache.batik.svggen.font.table.Table.encodingKorean,
org.apache.batik.svggen.font.table.Table.encodingHebrew,
org.apache.batik.svggen.font.table.Table.encodingUndefined
};
CmapFormat cmapFmt;
for(int i = 0; i < encodings.length; i++) {
for(int j = 0; j < platforms.length; j++) {
cmapFmt = f.getCmapTable().getCmapFormat(platforms[j], encodings[i]);
if (cmapFmt != null) {
return cmapFmt;
}
}
}
return null;
}
}
/**
* Use this method to get the outlines of a string in the form of an RGroup. All the elements of the group will be RShapes.
* @eexample RFont_toGroup
* @param text String, the string we want the outlines from.
* @return RGroup, the group of outlines of the character. All the elements are RShapes.
* @related toShape ( )
* @related draw ( )
*/
public RGroup toGroup(String text) throws RuntimeException{
RGroup result = new RGroup();
// Decide upon a cmap table to use for our character to glyph look-up
CmapFormat cmapFmt = getCmapFormat();
if (cmapFmt == null) {
throw new RuntimeException("Cannot find a suitable cmap table");
}
// If this font includes arabic script, we want to specify
// substitutions for initial, medial, terminal & isolated
// cases.
/*
GsubTable gsub = (GsubTable) f.getTable(Table.GSUB);
SingleSubst initialSubst = null;
SingleSubst medialSubst = null;
SingleSubst terminalSubst = null;
if (gsub != null) {
Script s = gsub.getScriptList().findScript(ScriptTags.SCRIPT_TAG_ARAB);
if (s != null) {
LangSys ls = s.getDefaultLangSys();
if (ls != null) {
Feature init = gsub.getFeatureList().findFeature(ls, FeatureTags.FEATURE_TAG_INIT);
Feature medi = gsub.getFeatureList().findFeature(ls, FeatureTags.FEATURE_TAG_MEDI);
Feature fina = gsub.getFeatureList().findFeature(ls, FeatureTags.FEATURE_TAG_FINA);
initialSubst = (SingleSubst)
gsub.getLookupList().getLookup(init, 0).getSubtable(0);
medialSubst = (SingleSubst)
gsub.getLookupList().getLookup(medi, 0).getSubtable(0);
terminalSubst = (SingleSubst)
gsub.getLookupList().getLookup(fina, 0).getSubtable(0);
}
}
}*/
int x = 0;
for (short i = 0; i < text.length(); i++) {
int glyphIndex = cmapFmt.mapCharCode(text.charAt(i));
Glyph glyph = f.getGlyph(glyphIndex);
int default_advance_x = f.getHmtxTable().getAdvanceWidth(glyphIndex);
if (glyph != null) {
glyph.scale(scaleFactor);
// Add the Glyph to the Shape with an horizontal offset of x
result.addElement(getGlyphAsShape(f,glyph, glyphIndex,x));
x += glyph.getAdvanceWidth();
}else{
x += (int)((float)default_advance_x*scaleFactor);
}
}
if(align!=LEFT && align!=CENTER && align!=RIGHT){
throw new RuntimeException("Alignment unknown. The only accepted values are: RFont.LEFT, RFont.CENTER and RFont.RIGHT");
}
RRectangle r;
RMatrix mattrans;
switch(this.align){
case RFont.CENTER:
r = result.getBounds();
mattrans = new RMatrix();
mattrans.translate((r.getMinX()-r.getMaxX())/2,0);
result.transform(mattrans);
break;
case RFont.RIGHT:
r = result.getBounds();
mattrans = new RMatrix();
mattrans.translate((r.getMinX()-r.getMaxX()),0);
result.transform(mattrans);
break;
case RFont.LEFT:
break;
}
return result;
}
public RShape toShape(String text) throws RuntimeException{
RShape result = new RShape();
// Decide upon a cmap table to use for our character to glyph look-up
CmapFormat cmapFmt = getCmapFormat();
if (cmapFmt == null) {
throw new RuntimeException("Cannot find a suitable cmap table");
}
// If this font includes arabic script, we want to specify
// substitutions for initial, medial, terminal & isolated
// cases.
/*
GsubTable gsub = (GsubTable) f.getTable(Table.GSUB);
SingleSubst initialSubst = null;
SingleSubst medialSubst = null;
SingleSubst terminalSubst = null;
if (gsub != null) {
Script s = gsub.getScriptList().findScript(ScriptTags.SCRIPT_TAG_ARAB);
if (s != null) {
LangSys ls = s.getDefaultLangSys();
if (ls != null) {
Feature init = gsub.getFeatureList().findFeature(ls, FeatureTags.FEATURE_TAG_INIT);
Feature medi = gsub.getFeatureList().findFeature(ls, FeatureTags.FEATURE_TAG_MEDI);
Feature fina = gsub.getFeatureList().findFeature(ls, FeatureTags.FEATURE_TAG_FINA);
initialSubst = (SingleSubst)
gsub.getLookupList().getLookup(init, 0).getSubtable(0);
medialSubst = (SingleSubst)
gsub.getLookupList().getLookup(medi, 0).getSubtable(0);
terminalSubst = (SingleSubst)
gsub.getLookupList().getLookup(fina, 0).getSubtable(0);
}
}
}*/
int x = 0;
for (short i = 0; i < text.length(); i++) {
int glyphIndex = cmapFmt.mapCharCode(text.charAt(i));
Glyph glyph = f.getGlyph(glyphIndex);
int default_advance_x = f.getHmtxTable().getAdvanceWidth(glyphIndex);
if (glyph != null) {
glyph.scale(scaleFactor);
// Add the Glyph to the Shape with an horizontal offset of x
result.addChild(getGlyphAsShape(f,glyph, glyphIndex,x));
x += glyph.getAdvanceWidth();
}else{
x += (int)((float)default_advance_x*scaleFactor);
}
}
if(align!=LEFT && align!=CENTER && align!=RIGHT){
throw new RuntimeException("Alignment unknown. The only accepted values are: RFont.LEFT, RFont.CENTER and RFont.RIGHT");
}
RRectangle r;
RMatrix mattrans;
switch(this.align){
case RFont.CENTER:
r = result.getBounds();
mattrans = new RMatrix();
mattrans.translate((r.getMinX()-r.getMaxX())/2,0);
result.transform(mattrans);
break;
case RFont.RIGHT:
r = result.getBounds();
mattrans = new RMatrix();
mattrans.translate((r.getMinX()-r.getMaxX()),0);
result.transform(mattrans);
break;
case RFont.LEFT:
break;
}
return result;
}
/**
* Use this method to draw a character on a certain canvas.
* @eexample RFont_draw
* @param character the character to be drawn
* @param g the canvas where to draw
* @related toShape ( )
* @related toGroup ( )
*/
public void draw(char character, PGraphics g) throws RuntimeException{
this.toShape(character).draw(g);
}
/**
* Use this method to draw a character on a certain canvas.
* @eexample RFont_draw
* @param text the string to be drawn
* @param g the canvas where to draw
* @related toShape ( )
* @related toGroup ( )
*/
public void draw(String text, PGraphics g) throws RuntimeException{
this.toGroup(text).draw(g);
}
/**
* Use this method to draw a character on a certain canvas.
* @eexample RFont_draw
* @param character char, the character to be drawn
* @param g the canvas where to draw
* @related toShape ( )
* @related toGroup ( )
*/
public void draw(char character, PApplet g) throws RuntimeException{
this.toShape(character).draw(g);
}
/**
* Use this method to draw a character on a certain canvas.
* @eexample RFont_draw
* @param text the string to be drawn
* @param g the canvas where to draw
* @related toShape ( )
* @related toGroup ( )
*/
public void draw(String text, PApplet g) throws RuntimeException{
this.toGroup(text).draw(g);
}
public void draw(String text) throws RuntimeException{
this.toGroup(text).draw();
}
public void draw(char character) throws RuntimeException{
this.toShape(character).draw();
}
private static float midValue(float a, float b) {
return a + (b - a)/2;
}
protected static RShape getContourAsShape(Glyph glyph, int startIndex, int count) {
return getContourAsShape(glyph, startIndex, count, 0);
}
protected static RShape getContourAsShape(Glyph glyph, int startIndex, int count, float xadv) {
// If this is a single point on it's own, weSystem.out.println("Value of pointx: "+pointx); can't do anything with it
if (glyph.getPoint(startIndex).endOfContour) {
return new RShape();
}
RShape result = new RShape();
int offset = 0;
//float originx = 0F,originy = 0F;
while (offset < count) {
Point point = glyph.getPoint(startIndex + offset%count);
Point point_plus1 = glyph.getPoint(startIndex + (offset+1)%count);
Point point_plus2 = glyph.getPoint(startIndex + (offset+2)%count);
float pointx = ((float)point.x + xadv);
float pointy = ((float)point.y);
float point_plus1x = ((float)point_plus1.x + xadv);
float point_plus1y = ((float)point_plus1.y);
float point_plus2x = ((float)point_plus2.x + xadv);
float point_plus2y = ((float)point_plus2.y);
if (offset == 0) {
// move command
result.addMoveTo(pointx,pointy);
}
if (point.onCurve && point_plus1.onCurve) {
// line command
result.addLineTo(point_plus1x,point_plus1y);
offset++;
} else if (point.onCurve && !point_plus1.onCurve && point_plus2.onCurve) {
// This is a curve with no implied points
// quadratic bezier command
result.addQuadTo(point_plus1x, point_plus1y, point_plus2x, point_plus2y);
offset+=2;
} else if (point.onCurve && !point_plus1.onCurve && !point_plus2.onCurve) {
// This is a curve with one implied point
// quadratic bezier command avec le endPoint implicit
result.addQuadTo(point_plus1x, point_plus1y, midValue(point_plus1x, point_plus2x), midValue(point_plus1y, point_plus2y));
offset+=2;
} else if (!point.onCurve && !point_plus1.onCurve) {
// This is a curve with two implied points
// quadratic bezier with
result.addQuadTo(pointx, pointy, midValue(pointx, point_plus1x), midValue(pointy, point_plus1y));
offset++;
} else if (!point.onCurve && point_plus1.onCurve) {
// This is a curve with no implied points
result.addQuadTo(pointx, pointy, point_plus1x, point_plus1y);
offset++;
} else {
System.out.println("drawGlyph case not catered for!!");
break;
}
}
result.addClose();
return result;
}
protected static RShape getGlyphAsShape(Font font, Glyph glyph, int glyphIndex) {
return getGlyphAsShape(font,glyph,glyphIndex,0);
}
protected static RShape getGlyphAsShape(Font font, Glyph glyph, int glyphIndex,float xadv) {
RShape result = new RShape();
int firstIndex = 0;
int count = 0;
int i;
if (glyph != null) {
for (i = 0; i < glyph.getPointCount(); i++) {
count++;
if (glyph.getPoint(i).endOfContour) {
result.addShape(getContourAsShape(glyph, firstIndex, count, xadv));
firstIndex = i + 1;
count = 0;
}
}
}
return result;
}
protected static RShape getGlyphAsShape(Font font, Glyph glyph, int glyphIndex, SingleSubst arabInitSubst, SingleSubst arabMediSubst, SingleSubst arabTermSubst) {
return getGlyphAsShape(font, glyph, glyphIndex, arabInitSubst, arabMediSubst, arabTermSubst, 0);
}
protected static RShape getGlyphAsShape(Font font, Glyph glyph, int glyphIndex, SingleSubst arabInitSubst, SingleSubst arabMediSubst, SingleSubst arabTermSubst, float xadv) {
RShape result = new RShape();
boolean substituted = false;
// arabic = "initial | medial | terminal | isolated"
int arabInitGlyphIndex = glyphIndex;
int arabMediGlyphIndex = glyphIndex;
int arabTermGlyphIndex = glyphIndex;
if (arabInitSubst != null) {
arabInitGlyphIndex = arabInitSubst.substitute(glyphIndex);
}
if (arabMediSubst != null) {
arabMediGlyphIndex = arabMediSubst.substitute(glyphIndex);
}
if (arabTermSubst != null) {
arabTermGlyphIndex = arabTermSubst.substitute(glyphIndex);
}
if (arabInitGlyphIndex != glyphIndex) {
result.addShape(getGlyphAsShape(font,font.getGlyph(arabInitGlyphIndex),arabInitGlyphIndex));
substituted = true;
}
if (arabMediGlyphIndex != glyphIndex) {
result.addShape(getGlyphAsShape(font,font.getGlyph(arabMediGlyphIndex),arabMediGlyphIndex));
substituted = true;
}
if (arabTermGlyphIndex != glyphIndex) {
result.addShape(getGlyphAsShape(font,font.getGlyph(arabTermGlyphIndex),arabTermGlyphIndex));
substituted = true;
}
if (substituted) {
result.addShape(getGlyphAsShape(font,glyph,glyphIndex));
} else {
result.addShape(getGlyphAsShape(font,glyph,glyphIndex));
}
return result;
}
}