/**
* The MIT License (MIT)
*
* Copyright (c) 2014 momokan (http://lwjgfont.chocolapod.net)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.chocolapod.lwjgfont.packager;
import static net.chocolapod.lwjgfont.packager.LwjgFontFactory.DEFAULT_TEMP_DIR;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.imageio.ImageIO;
import net.chocolapod.lwjgfont.FontMap;
import net.chocolapod.lwjgfont.MappedCharacter;
public class FontMapPainter {
private static final int IMAGE_HEIGHT_DEFAULT_EXPONENT = 6;
private static final int IMAGE_MAX_LENGTH = 4096;
public static final int DEFAULT_PADDING = 5;
public static final String DEFAULT_CHARACTERS_DIR = "characters";
public static final boolean DEFAULT_WRITE_IMAGE = true;
public static final boolean DEFAULT_WRITE_IMAGE_FRAME = false;
private int padding = DEFAULT_PADDING;
private String charactersDir = DEFAULT_CHARACTERS_DIR;
private String resourceDir = DEFAULT_TEMP_DIR;
private String packageDirs = null;
private int defaultImageHeightExponent = IMAGE_HEIGHT_DEFAULT_EXPONENT;
private boolean isWriteImage = DEFAULT_WRITE_IMAGE;
private boolean isWriteImageFrame = DEFAULT_WRITE_IMAGE_FRAME;
// paint 処理用。paint() の開始から終了までの間の一時データ
private CharacterFile file;
private List<CharacterFile> files;
private Font font;
private int imageHeightExponent;
private int height;
private int width;
private BufferedImage bufferedImage;
private Graphics2D g;
public FontMap paint(FontSetting fontSetting) throws IOException, FontFormatException {
FontMap fontMap = new FontMap();
int x = 0;
int y = 0;
int srcX;
int srcY;
Character c = null;
int stringWidthOnMap;
int stringHeightOnMap;
int maxAscent;
int maxDescent;
int advance;
int imageIndex = 0;
Color frameColor = new Color(0.8f, 0.8f, 1f);
/**
* font.getSize() … ベースラインから上辺までの高さ。この内部は重なることはない。
* g.getFontMetrics().stringWidth() … 左辺から右辺までの高さ。この内部は重なることはない。
* 実際には、字によってこの範囲からはみでることがある。
* だが、次の字を描く座標については、この範囲を元に計算してよい。
* 参考: http://docs.oracle.com/javase/tutorial/2d/text/measuringtext.html
*/
files = CharacterFile.listStreams(charactersDir);
files.add(new BuiltinCharacterFile());
imageHeightExponent = defaultImageHeightExponent;
bufferedImage = null;
g = null;
try {
if (fontSetting.isSystemFont()) {
// @ で始まるフォントはシステムフォントとして扱う
font = new Font(fontSetting.getFontPath(), Font.PLAIN, fontSetting.getFontSize());
} else {
// 指定のパスのファイルをフォントとして読み込む
font = loadFont(fontSetting.getFontPath(), fontSetting.getFontSize());
}
fontSetting.configure(font.getName());
/*
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
g = bufferedImage.createGraphics();
g.setColor(new Color(0f, 0f, 0f, 0f));
g.fillRect(0, 0, width, height);
g.setFont(font);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
*/
prepareImageBuffer();
maxAscent = g.getFontMetrics(font).getMaxAscent();
maxDescent = g.getFontMetrics(font).getMaxDescent();
stringHeightOnMap = maxAscent + maxDescent;
fontMap.setLineHeight(stringHeightOnMap);
y = maxAscent;
while ((c = nextCharacter()) != null) {
// stringWidth = g.getFontMetrics().stringWidth(c.toString());
advance = g.getFontMetrics(font).charWidth(c);
stringWidthOnMap = advance + (padding * 2);
if (width < x + stringWidthOnMap) {
x = 0;
if (height < y + stringHeightOnMap) {
// extent image height
if (!extentImageBuffer()) {
break;
}
}
y += stringHeightOnMap;
}
if (isWriteImageFrame) {
g.setColor(frameColor);
g.drawRect(x, y - maxAscent, stringWidthOnMap, stringHeightOnMap);
}
srcX = x + padding;
srcY = y;
g.setColor(Color.WHITE);
g.drawString(c.toString(), srcX, srcY);
// System.out.println("(" + x + ", " + y + ") :" + c.toString());
fontMap.addCharacter(new MappedCharacter(c, imageIndex, srcX, srcY, maxAscent, maxDescent, advance, imageIndex));
x += stringWidthOnMap;
}
fontMap.addImageFile(imageIndex, writeFontMapImage(bufferedImage, fontSetting, imageIndex));
imageIndex++;
} finally {
disposeImageBuffer(g, bufferedImage);
if (file != null) {
file.close();
}
}
return fontMap;
}
private String writeFontMapImage(BufferedImage bufferedImage, FontSetting fontSettings, int imageIndex) throws IOException {
String fileName = fontSettings.getImageFileName(imageIndex);
File dir = LwjgFontUtil.prepareDirectory(resourceDir, packageDirs);
if (isWriteImage) {
ImageIO.write(bufferedImage, "png", new File(dir.getPath() + File.separator + fileName));
}
return fileName;
}
private Character nextCharacter() throws IOException {
Character c = null;
if (file == null) {
if (files.size() <= 0) {
// 次のファイルがなければ null を返す
return null;
}
// 次のファイルを開く
file = files.remove(0);
file.open();
}
c = file.next();
if (c == null) {
// 現在のファイルからはもう読めない
file.close();
file = null;
return nextCharacter();
}
return c;
}
/**
* フォントを読み込む
*/
private Font loadFont(String fontPath, int fontSize) throws IOException, FontFormatException {
InputStream in = null;
try {
in = new FileInputStream(fontPath);
return Font.createFont(Font.TRUETYPE_FONT, in).deriveFont((float)fontSize);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {}
}
}
}
private void prepareImageBuffer() {
height = (int)Math.pow(2, imageHeightExponent);
width = IMAGE_MAX_LENGTH;
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
g = bufferedImage.createGraphics();
g.setColor(new Color(0f, 0f, 0f, 0f));
g.fillRect(0, 0, width, height);
g.setFont(font);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// System.out.println("create @" + bufferedImage.hashCode() + " " + width + "x" + height);
}
private void disposeImageBuffer(Graphics2D g, BufferedImage bufferedImage) {
if (g != null) {
g.dispose();
}
if (bufferedImage != null) {
bufferedImage.flush();
// System.out.println("dispose @" + bufferedImage.hashCode() );
}
}
private boolean extentImageBuffer() {
imageHeightExponent++;
if (IMAGE_MAX_LENGTH <= (int)Math.pow(2, imageHeightExponent)) {
return false;
}
Graphics2D oldG = g;
BufferedImage oldBufferedImage = bufferedImage;
prepareImageBuffer();
g.drawImage(oldBufferedImage, 0, 0, null);
disposeImageBuffer(oldG, oldBufferedImage);
return true;
}
public void setPadding(int padding) {
this.padding = padding;
}
public void setImageHeightExponent(int imageHeightExponent) {
this.imageHeightExponent = imageHeightExponent;
}
public void setResourceDir(String resourceDir) {
this.resourceDir = resourceDir;
}
void setPackageDirs(String packageDirs) {
this.packageDirs = packageDirs;
}
public void setCharactersDir(String charactersDir) {
this.charactersDir = charactersDir;
}
public void setWriteImage(boolean isWriteImage) {
this.isWriteImage = isWriteImage;
}
public void setWriteImageFrame(boolean isWriteImageFrame) {
this.isWriteImageFrame = isWriteImageFrame;
}
}