/*********************************************************************** * mt4j Copyright (c) 2008 - 2010 Christopher Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package processing.core; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; import org.mt4j.MTApplication; import org.mt4j.components.visibleComponents.font.BitmapFont; import org.mt4j.components.visibleComponents.font.BitmapFontCharacter; import org.mt4j.components.visibleComponents.font.IFont; import org.mt4j.components.visibleComponents.font.fontFactories.IFontFactory; import org.mt4j.util.MT4jSettings; import org.mt4j.util.MTColor; import processing.core.PFont.Glyph; /** * A factory for creating BitmapFont objects. * @author Christopher Ruff */ public class BitmapFontFactoryProxy implements IFontFactory { /** The Constant logger. */ private static final Logger logger = Logger.getLogger(BitmapFontFactoryProxy.class.getName()); static{ // logger.setLevel(Level.ERROR); // logger.setLevel(Level.WARN); logger.setLevel(Level.DEBUG); SimpleLayout l = new SimpleLayout(); ConsoleAppender ca = new ConsoleAppender(l); logger.addAppender(ca); } public static String defaultCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöü<>|,;.:-_#'+*!\"§$%&/()=?´{[]}\\@"; // static{ // FontManager.getInstance().registerFontFactory("", new BitmapFontFactory()); // } /* (non-Javadoc) * @see org.mt4j.components.visibleComponents.font.fontFactories.IFontFactory#createFont(processing.core.PApplet, java.lang.String, int, org.mt4j.util.MTColor, org.mt4j.util.MTColor) */ public IFont createFont( PApplet pa, String fontFileName, int fontSize, MTColor fillColor, MTColor strokeColor ) { return this.createFont(pa, fontFileName, fontSize, fillColor, strokeColor, true); } /* (non-Javadoc) * @see org.mt4j.components.visibleComponents.font.fontFactories.IFontFactory#createFont(processing.core.PApplet, java.lang.String, int, org.mt4j.util.MTColor, org.mt4j.util.MTColor) */ public IFont createFont( PApplet pa, String fontFileName, int fontSize, MTColor fillColor, MTColor strokeColor, boolean antiAliased ) { PFont p5Font = null; try { p5Font = this.getProcessingFont(pa, fontFileName, fontSize, antiAliased); } catch (FileNotFoundException e1) { e1.printStackTrace(); } List<BitmapFontCharacter> bitMapCharacters = this.createCharacters(pa, p5Font, defaultCharacters, fillColor, strokeColor); //font is null sometimes (vlw) /* Font f = p5Font.getFont(); FontMetrics fm = pa.getFontMetrics(f); Map<TextAttribute, ?> atts = f.getAttributes(); Set<TextAttribute> attKeys = atts.keySet(); for (Iterator iterator = attKeys.iterator(); iterator.hasNext();) { TextAttribute textAttribute = (TextAttribute) iterator.next(); Object value = atts.get(textAttribute); logger.debug("Key: " + textAttribute + " Value: " + value); } // FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(f); */ int defaultHorizontalAdvX = (!bitMapCharacters.isEmpty())? bitMapCharacters.get(0).getHorizontalDist() : Math.round(p5Font.descent() * fontSize); //FIXME HACK! String fontFamily = p5Font.getPostScriptName(); // String fontFamily = f.getFamily(); //FIXME ascent() and descent() return to small values! wheres the difference?? int fontMaxAscent = Math.round(p5Font.ascent()* (fontSize)); fontMaxAscent +=(float)fontSize/5.5f; //FIXME HACK! because the same ttf fonts seem to have bigger ascents // int fontMaxAscent = p5Font.lazyMetrics.getAscent(); int fontMaxDescent = Math.round(p5Font.descent() * fontSize); /* //TODO INFO: because in vector font this is a negative value, too Font f = p5Font.getFont(); if (f != null){ FontMetrics fm = pa.getFontMetrics(f); fontMaxDescent = fm.getDescent(); } */ fontMaxDescent *= -1; //We use negative descent values //logger.debug("Bitmapfont max descent: " + fontMaxDescent); // int fontMaxAscent = Math.round(p5Font.ascent()*fontSize); // int fontMaxDescent = Math.round(p5Font.descent()*fontSize); // int fontMaxAscent = fm.getMaxAscent(); // int fontMaxDescent = fm.getMaxDescent(); int unitsPerEm = 1000; //FIXME HACK! int originalFontSize = fontSize; //important for font cache PImage dummy = new PImage(1,1); // /* //Manually add a newLine character to the font BitmapFontCharacter newLine = new BitmapFontCharacter(dummy, pa, "\n", 0, 0, 0); newLine.setPickable(false); newLine.setVisible(false); newLine.setNoFill(true); newLine.setNoStroke(true); newLine.setName("newline"); bitMapCharacters.add(newLine); //Manually add a SPACE character to the font // int spaceAdvancex = defaultHorizontalAdvX; // int spaceAdvancex = fm.charWidth(' '); //TODO hack, we use the dash character's width for the space width, because dont know how to get it // int spaceIndex = p5Font.index('-'); // int spaceAdvancex = p5Font.width[spaceIndex]; // int spaceAdvancex = p5Font.getGlyph('-').width; int spaceAdvancex = Math.round(((float) p5Font.width('i') * (float) fontSize)); // int spaceAdvancex = Math.round(pa.textWidth(' ')); // int spaceAdvancex = Math.round(p5Font.width(' ') * p5Font.size); BitmapFontCharacter space = new BitmapFontCharacter(dummy, pa, " ", 0, 0, spaceAdvancex); space.setPickable(false); space.setVisible(false); space.setNoFill(true); space.setNoStroke(true); space.setName("space"); bitMapCharacters.add(space); //Manually add a TAB character to the font int defaultTabWidth = spaceAdvancex*4; BitmapFontCharacter tab = new BitmapFontCharacter(dummy, pa, "\t", 0, 0, defaultTabWidth); try { int tabWidth = 4 * space.getHorizontalDist(); tab.setHorizontalDist(tabWidth); } catch (Exception e) { tab.setHorizontalDist(defaultTabWidth); } tab.setPickable(false); tab.setName("tab"); tab.setVisible(false); tab.setNoFill(true); tab.setNoStroke(true); bitMapCharacters.add(tab); // */ //TODO bitmap font size seems different to same size vector font, we must have check descent -> textarea -> res*em*etc //TODO eureka font - numbers baseline wrong? //Create the bitmap font BitmapFontCharacter[] characters = bitMapCharacters.toArray(new BitmapFontCharacter[bitMapCharacters.size()]); BitmapFont bitmapFont = new BitmapFont(characters, defaultHorizontalAdvX, fontFamily, fontMaxAscent, fontMaxDescent, unitsPerEm, originalFontSize, fillColor, strokeColor, antiAliased ); bitmapFont.setFontFileName(fontFileName); return bitmapFont; } // /** // * Gets the processing font. // * // * @param pa the pa // * @param fontFileName the font file name // * @param fontSize the font size // * @return the processing font // */ // private PFont getProcessingFont(PApplet pa, String fontFileName, int fontSize){ // return this.getProcessingFont(pa, fontFileName, fontSize, true); // } /** * Gets the processing font. * * @param pa the pa * @param fontFileName the font file name * @param fontSize the font size * @param antiAliased the anti aliased * @return the processing font * @throws FileNotFoundException */ private PFont getProcessingFont(PApplet pa, String fontFileName, int fontSize, boolean antiAliased) throws FileNotFoundException{ PFont p5Font; //When loading the vlw font the font size and anti aliasing is already determined with the file //and our parameter isnt honored if (fontFileName.endsWith(".vlw")){ p5Font = pa.loadFont(fontFileName); //If not found try to load from the "/data" directory if (p5Font == null){ int lastDirFileSeparator = fontFileName.lastIndexOf(java.io.File.separator); int lastDirSeparator = fontFileName.lastIndexOf(MTApplication.separator); if (lastDirFileSeparator != -1){ p5Font = pa.loadFont(fontFileName.substring(lastDirFileSeparator+1, fontFileName.length())); }else if (lastDirSeparator != -1){ p5Font = pa.loadFont(fontFileName.substring(lastDirSeparator+1, fontFileName.length())); } } } else if (fontFileName.endsWith(".ttf") || fontFileName.endsWith(".otf")){ p5Font = pa.createFont(fontFileName, fontSize, antiAliased); //If not found try to load from the "/data" directory if (p5Font == null){ int lastDirFileSeparator = fontFileName.lastIndexOf(java.io.File.separator); int lastDirSeparator = fontFileName.lastIndexOf(MTApplication.separator); if (lastDirFileSeparator != -1){ p5Font = pa.createFont(fontFileName.substring(lastDirFileSeparator+1, fontFileName.length()), fontSize, antiAliased); }else if (lastDirSeparator != -1){ p5Font = pa.createFont(fontFileName.substring(lastDirSeparator+1, fontFileName.length()), fontSize, antiAliased); }else{ p5Font = pa.loadFont(fontFileName); } } } else{ //No file suffix -> Create font from a java/system font int lastDirFileSeparator = fontFileName.lastIndexOf(java.io.File.separator); int lastDirSeparator = fontFileName.lastIndexOf(MTApplication.separator); if (lastDirFileSeparator != -1){ p5Font = pa.createFont(fontFileName.substring(lastDirFileSeparator+1, fontFileName.length()), fontSize, antiAliased); //Creats the font } else if (lastDirSeparator != -1){ p5Font = pa.createFont(fontFileName.substring(lastDirSeparator+1, fontFileName.length()), fontSize, antiAliased); //Creats the font } else{ p5Font = pa.loadFont(fontFileName); } } if (p5Font == null){ throw new NullPointerException("Couldnt load the font: " + fontFileName); } return p5Font; } public List<BitmapFontCharacter> getCharacters(PApplet pa, String chars, MTColor fillColor, MTColor strokeColor, String fontFileName, int fontSize ){ return this.getCharacters(pa, chars, fillColor, strokeColor, fontFileName, fontSize, true); } /** * Creates the specified characters. * * @param pa the pa * @param chars the chars * @param fillColor the fill color * @param strokeColor the stroke color * @param fontFileName the font file name * @param fontSize the font size * @param antiAliased the anti aliased * @return the characters */ public List<BitmapFontCharacter> getCharacters(PApplet pa, String chars, MTColor fillColor, MTColor strokeColor, String fontFileName, int fontSize, boolean antiAliased ){ PFont p5Font = null; try { p5Font = this.getProcessingFont(pa, fontFileName, fontSize, antiAliased); } catch (FileNotFoundException e) { e.printStackTrace(); } return createCharacters(pa, p5Font, chars, fillColor, strokeColor); } private List<BitmapFontCharacter> createCharacters(PApplet pa, PFont p5Font, String chars, MTColor fillColor, MTColor strokeColor){ List<BitmapFontCharacter> bitMapCharacters = new ArrayList<BitmapFontCharacter>(); for (int i = 0; i < chars.length(); i++) { char c = chars.charAt(i); // int charIndex = p5Font.index(c); Glyph glyph = p5Font.getGlyph(c); if (glyph != null){ PImage charImage = glyph.image; int charWidth = glyph.width; int charHeight = glyph.height; int topExtend = glyph.topExtent; int leftExtend = glyph.leftExtent; int widthDisplacement = glyph.setWidth; //int topOffset = p5Font.descent + (-charHeight - (topExtend-charHeight)); //ORIGINAL int topOffset = (-charHeight - (topExtend-charHeight)); //Copy the actual font data on the image from the upper left corner 1 pixel //into the middle of the image to avoid anti aliasing artefacts at the corners // PImage copy = new PImage(charImage.width, charImage.height, PImage.ARGB); //ORG // for (int j = 0; j < charImage.pixels.length; j++) { //ORG // int d = charImage.pixels[j]; // /* // int a = d >> 24 & 0xFF; // int r = d >> 16 & 0xFF; // int g = d >> 8 & 0xFF; // int b = d & 0xFF; // logger.debug("R: " + r + " G:" + g + " B:" + " A:" + a); // */ // charImage.pixels[j] = (d << 24) | 0x00FFFFFF; //ORIGINAL! //make it white //// charImage.pixels[j] = (d << 24) | pa.color(fillColor.getR(), fillColor.getG(), fillColor.getB(), 0); //// charImage.pixels[j] = (charImage.pixels[j] << 24) | 0x00FFFFFF; // //charImage.format = PConstants.ARGB; // // //Clear the copy image in the same loop // copy.pixels[j] = (copy.pixels[j] << 24) | 0x00FFFFFF; //Original! //make it white //// copy.pixels[j] = (d << 24) | 0x00FFFFFF; //Original! //make it white // } for (int j = 0; j < charImage.pixels.length; j++) { //ORG charImage.pixels[j] = (charImage.pixels[j] << 24) | 0x00FFFFFF; //ORIGINAL! //make it white } //Shift character image data down and right in the image because of aliasing artifacts at the border //we need to compensate for this when displaying the char //FIXME this creates far to big images..but because of artefacts needed..? int topShiftAmount = 4; int leftShiftAmount = 4; // PImage copy = new PImage(ToolsMath.nearestPowerOfTwo(charWidth + shiftAmount), ToolsMath.nearestPowerOfTwo(charHeight + shiftAmount), PImage.ARGB); // PImage copy = new PImage(nextPowerOfTwo(charImage.width + leftShiftAmount + 1), nextPowerOfTwo(charImage.height + topShiftAmount), PImage.ARGB); // PImage copy = new PImage(charImage.width + leftShiftAmount + 1, charImage.height + topShiftAmount, PImage.ARGB); copy.copy(charImage, 0, 0, charWidth, charHeight, leftShiftAmount, topShiftAmount, charWidth, charHeight); // copy.copy(charImage, 0, 0, charImage.width, charImage.height, leftShiftAmount, topShiftAmount, charImage.width, charImage.height); // copy.copy(charImage, 0, 0, charWidth, charHeight, shiftAmount, shiftAmount, charWidth, charHeight); // copy.copy(charImage, 0, 0, charImage.width, charImage.height, shiftAmount, shiftAmount, charImage.width, charImage.height); // copy.copy(charImage, 0, 0, charImage.width, charImage.height, shiftAmount, shiftAmount, charImage.width, charImage.height); // copy.copy(charImage, 0, 0, charWidth, charHeight, shiftAmount, shiftAmount, charWidth, charHeight); charImage = copy; //FIXME the topoffset is smaller than with the vector font! check that! //FIXME anti aliasing artefacts may also stem from using a perspective and not ortho camera!! //FIXME space character too wide.. //Move the character to compensate for the shifting of the image topOffset -= topShiftAmount; //org shiftamount leftExtend -= leftShiftAmount; //FIXME TEST // if (c == 'i'){ // copy.save(MT4jSettings.DEFAULT_IMAGES_PATH + "i.png"); // } //Create bitmap font character String StringChar = new Character(c).toString(); BitmapFontCharacter character = new BitmapFontCharacter(charImage, pa, StringChar, leftExtend, topOffset, widthDisplacement); character.setName(StringChar); character.setFillColor(new MTColor(fillColor)); if (MT4jSettings.getInstance().isOpenGlMode()){ character.generateAndUseDisplayLists(); } bitMapCharacters.add(character); //logger.debug("Char: " + c + " charWidth: " + charWidth + " leftExtend: " + leftExtend + " widthDisplacement: " + widthDisplacement + " imageHeight: " + charImage.height + " charHeight: " + charHeight + " topExtent: " + topExtend); }else{ logger.warn("Couldnt create bitmap character : " + c + " -> not found!"); } } return bitMapCharacters; } private int nextPowerOfTwo(int val) { int ret = 1; while (ret < val) { ret <<= 1; } return ret; } // // /** // * Create a .vlw font on the fly from either a font name that's // * installed on the system, or from a .ttf or .otf that's inside // * the data folder of this sketch. // * <P/> // * Many .otf fonts don't seem to be supported by Java, perhaps because // * they're CFF based? // * <P/> // * Font names are inconsistent across platforms and Java versions. // * On Mac OS X, Java 1.3 uses the font menu name of the font, // * whereas Java 1.4 uses the PostScript name of the font. Java 1.4 // * on OS X will also accept the font menu name as well. On Windows, // * it appears that only the menu names are used, no matter what // * Java version is in use. Naming system unknown/untested for 1.5. // * <P/> // * Use 'null' for the charset if you want to dynamically create // * character bitmaps only as they're needed. (Version 1.0.9 and // * earlier would interpret null as all unicode characters.) // */ // public PFont createFont(PApplet app, String name, float size, // boolean smooth, char charset[]) { // String lowerName = name.toLowerCase(); // Font baseFont = null; // // try { // InputStream stream = null; // if (lowerName.endsWith(".otf") || lowerName.endsWith(".ttf")) { // stream = app.createInput(name); // if (stream == null) { // System.err.println("The font \"" + name + "\" " + // "is missing or inaccessible, make sure " + // "the URL is valid or that the file has been " + // "added to your sketch and is readable."); // return null; // } // baseFont = Font.createFont(Font.TRUETYPE_FONT, app.createInput(name)); // // } else { // baseFont = PFont.findFont(name); // } // return new PFont(baseFont.deriveFont(size), smooth, charset, // stream != null); // // } catch (Exception e) { // System.err.println("Problem createFont(" + name + ")"); // e.printStackTrace(); // return null; // } // } // // private class MYPFont extends PFont{ // // public void getGlyphImage(){ // getGlyph('a'); // } // // public class bla extends PFont.Glyph{ // // } // // } }