/**
* 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 java.awt.FontFormatException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.chocolapod.lwjgfont.LWJGFont;
import net.chocolapod.lwjgfont.FontMap;
import net.chocolapod.lwjgfont.MappedCharacter;
import net.chocolapod.lwjgfont.cli.Main;
import static net.chocolapod.lwjgfont.cli.CliMessage.LWJGFONT_VERSION_FORMAT;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.ARTIFACT_NAME;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.ARTIFACT_VERSION;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.CHARACTER_FILE_DIR;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.IMAGE_CHARACTER_PADDING;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.IMAGE_DRAW;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.IMAGE_DRAW_FRAME;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.TEMP_DIR;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.DIST_DIR;
import static net.chocolapod.lwjgfont.packager.LwjgFontPropertyKey.LWJGFONT_VERSION;
import static net.chocolapod.lwjgfont.packager.LwjgFontUtil.CHARSET_UTF8;
public class LwjgFontFactory {
public static final String DEFAULT_DIST_DIR = "";
public static final String DEFAULT_TEMP_DIR = "temp/";
private static final String SOURCE_DIR = "src";
public static final String RESOURCE_DIR = "resources";
private static final String COMPILES_DIR = "target";
private static final String VERSION_PROPERTIES = "version.properties";
private LwjgFontProperties properties;
private ProcessLog classMapLog;
private String tempDir;
private String srcDir;
private String resourceDir;
private String targetDir;
private String packageName;
private String pomName;
private int maxCharacterRegistration = 500;
public LwjgFontFactory(String propertiesPath) throws IOException {
properties = LwjgFontProperties.load(propertiesPath);
properties.appendProerties(LWJGFont.class, VERSION_PROPERTIES);
tempDir = properties.getAsString(TEMP_DIR);
LwjgFontUtil.deleteFile(tempDir);
srcDir = LwjgFontUtil.prepareDirectory(tempDir, SOURCE_DIR).getPath();
resourceDir = LwjgFontUtil.prepareDirectory(tempDir, RESOURCE_DIR).getPath();
targetDir = LwjgFontUtil.prepareDirectory(tempDir, COMPILES_DIR).getPath();
String groupId = LWJGFont.class.getPackage().getName();
String artifactId = properties.getAsString(ARTIFACT_NAME);
String version = properties.getAsString(ARTIFACT_VERSION);
String dstDir = LwjgFontUtil.prepareDirectory(properties.getAsString(DIST_DIR)).getPath();
packageName = LwjgFontUtil.toDirectoryPath(dstDir) + artifactId + "-" + version + ".jar";
pomName = LwjgFontUtil.toDirectoryPath(dstDir) + artifactId + "-" + version + ".pom.xml";
classMapLog = new ProcessLog(packageName, pomName, groupId, artifactId, version);
}
public void create(FontSetting fontSetting) throws IOException, FontFormatException {
SourceBuffer sourceBuffer = processClass(fontSetting, resourceDir);
writeJavaSource(sourceBuffer, srcDir);
classMapLog.add(fontSetting.getFontPath(), fontSetting.getFontSize(), fontSetting.getFontAlias(), sourceBuffer.getCannonicalClassName());
}
public void makePackage() throws IOException {
// 生成した Java ソースコードをコンパイルする
SourceCompiler sourceCompiler = new SourceCompiler();
sourceCompiler.setSourceDir(srcDir);
sourceCompiler.setResourceDir(resourceDir);
sourceCompiler.setTargetDir(targetDir);
sourceCompiler.compile(classMapLog.listClasses());
extractStaticResources(resourceDir);
// Jar ファイルにアーカイブする
Packager packager = new Packager();
packager.setResourceDir(resourceDir);
packager.setTargetDir(targetDir);
packager.process(packageName);
// pom.xml を展開する
ResourceExtractor resourceExtractor = prepareResourceExtractor();
resourceExtractor.addResourcePath(
"packagedResources/META-INF/maven/net/chocolapod/lwjgfont/lwjgfont/pom.xml",
pomName);
resourceExtractor.copy();
}
private SourceBuffer processClass(FontSetting fontSetting, String resourceDir) throws IOException, FontFormatException {
FontMapPainter fontMapPainter = new FontMapPainter();
String artifactName = properties.getAsString(ARTIFACT_NAME);
String packageName = LWJGFont.class.getPackage().getName() + "." + artifactName;
String packageDirs = packageName.replace('.', File.separatorChar);
fontMapPainter.setWriteImage(properties.getAsBoolean(IMAGE_DRAW));
fontMapPainter.setWriteImageFrame(properties.getAsBoolean(IMAGE_DRAW_FRAME));
fontMapPainter.setPadding(properties.getAsInt(IMAGE_CHARACTER_PADDING));
fontMapPainter.setCharactersDir(properties.getAsString(CHARACTER_FILE_DIR));
fontMapPainter.setResourceDir(resourceDir);
fontMapPainter.setPackageDirs(packageDirs);
FontMap fontMap = fontMapPainter.paint(fontSetting);
SourceBuffer source = new SourceBuffer(packageName);
source.printJavadocComment(
"The subclass of net.chocolapod.lwjgfont.LWJGFont<br>",
"to render string with font: \"" + fontSetting.getFontPath() + "\", with size: " + fontSetting.getFontSize() + ".<br>",
"<br>",
"This class has utility methods to reder any strings with the above font.<br>",
"@see net.chocolapod.lwjgfont.LWJGFont"
);
source.openClass(fontSetting.getFontClassName(), null, true, LWJGFont.class);
printStaticFieldFontMap(source);
printPrepareFontMap(source, fontMap);
printMethodGetFontMap(source);
printMethodGetDefaultLineHieght(source, fontMap.getLineHeight());
source.closeClass();
return source;
}
private void printStaticFieldFontMap(SourceBuffer source) {
source.println("private static final FontMap\tmap = new FontMap();");
}
private void printPrepareFontMap(SourceBuffer source, FontMap fontMap) {
source.println("static ");
source.openBrace();
// ImageIndex と画像ファイルの対応を書き出す
for (int imageIndex: fontMap.listImageIndexes()) {
String imageFile = fontMap.getImageFile(imageIndex);
source.println("map.addImageFile(%d, \"%s\");", imageIndex, imageFile);
}
source.println();
// 文字とフォント情報を書き出す
List<SourceBuffer> configMethodSources = new ArrayList<>();
SourceBuffer configMethodSource = null;
Map<String, Class> configMethodArguments = new HashMap<String, Class>();
int configMethodIndex = 0;
int count = 0;
configMethodArguments.put("map", FontMap.class);
source.importClass(MappedCharacter.class);
for (char character: fontMap.listCharacters()) {
if ((configMethodSource == null) || (maxCharacterRegistration <= count)) {
if (configMethodSource != null) {
configMethodSource.closeMethod();
configMethodSources.add(configMethodSource);
}
configMethodSource = new SourceBuffer();
configMethodSource.openMethod("configMap" + configMethodIndex, null, configMethodArguments, "private", true);
source.println("configMap%d(map);", configMethodIndex);
configMethodIndex++;
count = 0;
}
MappedCharacter mappedCharacter = fontMap.getMappedCharacter(character);
String escapedCharacter = String.valueOf(mappedCharacter.getCharacter());
if (escapedCharacter.equals("'")) {
escapedCharacter = "\\'";
} else if (escapedCharacter.equals("\\")) {
escapedCharacter = "\\\\";
}
configMethodSource.println(
"map.addCharacter(new %s('%s', %d, %d, %d, %d, %d, %d, %d));",
MappedCharacter.class.getSimpleName(),
escapedCharacter,
mappedCharacter.getImageIndex(),
mappedCharacter.getSrcX(),
mappedCharacter.getSrcY(),
mappedCharacter.getAscent(),
mappedCharacter.getDescent(),
mappedCharacter.getAdvance(),
mappedCharacter.getPadding()
);
count++;
}
if (configMethodSource != null) {
configMethodSource.closeMethod();
configMethodSources.add(configMethodSource);
}
source.closeBrace();
source.println();
for (SourceBuffer toMerge: configMethodSources) {
source.merge(toMerge, 1);
source.println();
}
}
private void printMethodGetFontMap(SourceBuffer source) {
source.printJavadocComment("@see net.chocolapod.lwjgfont.LWJGFont#getFontMap()");
source.importClass(FontMap.class);
source.openMethod("getFontMap", FontMap.class.getSimpleName(), new HashMap<String, Class>(), "protected", false);
source.println("return map;");
source.closeMethod();
}
private void printMethodGetDefaultLineHieght(SourceBuffer source, int lineHeight) {
source.println("@Override");
source.openMethod("getDefaultLineHeight", "int", new HashMap<String, Class>(), false);
source.println("return " + lineHeight + ";");
source.closeMethod();
}
private void writeJavaSource(SourceBuffer sourceBuffer, String baseDir) throws IOException {
PrintWriter pw = null;
File sourceFile = sourceBuffer.getFile(baseDir);
LwjgFontUtil.prepareDirectory(sourceFile.getParent());
try {
pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(sourceFile), CHARSET_UTF8));
pw.print(sourceBuffer.toString());
} finally {
if (pw != null) {
pw.close();
}
}
}
private void extractStaticResources(String resourceDir) throws IOException {
ResourceExtractor resourceExtractor = prepareResourceExtractor();
String artifactName = properties.getAsString(ARTIFACT_NAME);
resourceExtractor.addResourcePath(
"packagedResources/META-INF/maven/net/chocolapod/lwjgfont/lwjgfont/pom.properties",
"META-INF/maven/net/chocolapod/lwjgfont/" + artifactName + "/pom.properties");
resourceExtractor.addResourcePath(
"packagedResources/META-INF/maven/net/chocolapod/lwjgfont/lwjgfont/pom.xml",
"META-INF/maven/net/chocolapod/lwjgfont/" + artifactName + "/pom.xml");
resourceExtractor.addResourcePath(
"packagedResources/META-INF/MANIFEST.MF",
"META-INF/MANIFEST.MF");
// resourceExtractor.addReplacePatterns(properties, ARTIFACT_NAME, ARTIFACT_VERSION);
resourceExtractor.setResourcesDir(resourceDir);
resourceExtractor.copy();
}
private ResourceExtractor prepareResourceExtractor() {
ResourceExtractor resourceExtractor = new ResourceExtractor();
resourceExtractor.addReplacePatterns(properties, ARTIFACT_NAME, ARTIFACT_VERSION, LWJGFONT_VERSION);
return resourceExtractor;
}
public void writeProcessLog() throws IOException {
classMapLog.write();
System.out.println(classMapLog);
}
// TODO リファクタ
public void extractCharacterFiles() throws IOException, URISyntaxException {
String charactersDir = LwjgFontUtil.prepareDirectory("characters").getPath();
URL urlCharacters = this.getClass().getClassLoader().getResource(this.getClass().getPackage().getName().replaceAll("\\.", "/") + "/characters/");
if (urlCharacters.toURI().getScheme().equals("file")) {
extractCharacterFilesFromDir(urlCharacters);
} else {
extractCharacterFilesFromJar(urlCharacters);
}
}
// TODO リファクタ
private void extractCharacterFilesFromDir(URL urlCharacters) throws URISyntaxException, IOException {
File dir = new File(urlCharacters.toURI());
String pathMask = urlCharacters.getPath();
ResourceExtractor resourceExtractor = new ResourceExtractor();
// Windows 環境では絶対パスの先頭に / がつかないので、取り除く
if ((File.separator.equals("\\")) && (pathMask.startsWith("/"))) {
pathMask = pathMask.substring(1);
}
for (File nextFile: dir.listFiles()) {
String filePath = nextFile.getPath();
filePath = filePath.substring(pathMask.length());
filePath = "characters/" + filePath;
resourceExtractor.addResourcePath(filePath, nextFile.getName());
}
resourceExtractor.setResourcesDir("characters");
resourceExtractor.copy();
}
// TODO リファクタ
private void extractCharacterFilesFromJar(URL urlCharacters) throws IOException {
JarURLConnection connection = (JarURLConnection)urlCharacters.openConnection();
ZipInputStream in = null;
ZipEntry zipEntry = null;
String basePath = connection.getJarEntry().getName();
byte[] buff = new byte[1024 * 1024];
int size;
try {
in = new ZipInputStream(new FileInputStream(connection.getJarFile().getName()));
while ((zipEntry = in.getNextEntry()) != null) {
if ((!zipEntry.getName().startsWith(basePath)) || (zipEntry.getName().length() <= basePath.length())) {
continue;
}
FileOutputStream out = null;
String fileName = zipEntry.getName().substring(basePath.length());
try {
out = new FileOutputStream("characters/" + fileName);
while (0 < (size = in.read(buff))) {
out.write(buff, 0, size);
}
} catch (Exception e) {
continue;
} finally {
try {
in.closeEntry();
} catch (IOException e2) {}
if (out != null) {
try {
out.close();
} catch (IOException e2) {}
}
}
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {}
}
}
}
public void printVersion() {
System.out.println(LWJGFONT_VERSION_FORMAT.format(properties.getAsString(LWJGFONT_VERSION)));
}
}