/* * $Copyright: copyright (c) 2003-2008, e.e d3si9n $ * $License: * This source code is part of DoubleType. * DoubleType is a graphical typeface designer. * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as a special exception, e.e d3si9n gives permission to * link the code of this program with any Java Platform that is available * to public with free of charge, including but not limited to * Sun Microsystem's JAVA(TM) 2 RUNTIME ENVIRONMENT (J2RE), * and distribute linked combinations including the two. * You must obey the GNU General Public License in all respects for all * of the code used other than Java Platform. If you modify this file, * you may extend this exception to your version of the file, but you are not * obligated to do so. If you do not wish to do so, delete this exception * statement from your version. * $ */ package org.doubletype.ossa.module; import java.awt.Font; import java.awt.Point; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.doubletype.ossa.OutOfRangeException; import org.doubletype.ossa.truetype.FontFileWriter; import org.doubletype.ossa.truetype.TTCodePage; import org.doubletype.ossa.truetype.TTGlyph; import org.doubletype.ossa.truetype.TTUnicodeRange; /** * @author e.e */ public class TypefaceFile extends GlyphFile { private final double k_defaultTopSideBearing = 170; // 2 px private final double k_defaultAscender = 683; // 8 px private final double k_defaultXHeight = 424; // 5 px private final double k_defaultDescender = 171; // 2 px private final double k_defaultBottomSideBearing = 0; // 0 px private final double k_em = 1024; private final int k_defaultAdvanceWidth = 512; private final String k_dotTtf = ".ttf"; private File m_dir; private File m_ttfFile; private Font m_font = null; private List<GlyphFile> m_glyphFiles = new ArrayList<>(); private List<String> m_unicodeRanges = new ArrayList<>(); private List<String> m_codePages = new ArrayList<>(); private String m_fontFamilyName; private String m_version; private Double m_topSideBearing = null; private Double m_ascender = null; private Double m_xHeight = null; private Double m_descender = null; private Double m_bottomSideBearing = null; private String m_name; public TypefaceFile(String a_name, File a_dir) throws FileNotFoundException { super(a_dir); m_dir = a_dir; m_name = a_name; initFileName(); } public TypefaceFile(File a_file) { super(a_file); m_dir = a_file.getParentFile(); m_name = a_file.getName(); initFileName(); } private void initFileName() { String fileName = m_name + k_dotTtf; m_ttfFile = new File(m_dir, fileName); } public GlyphFile createGlyph(long a_unicode) { return new GlyphFile(getGlyphPath(), a_unicode); } private GlyphFile getGlyphFileByUnicode(long code) { for (GlyphFile glyphFile : m_glyphFiles) { if (glyphFile.getUnicode() == code) { return glyphFile; } } return null; } public boolean addRequiredGlyphs() { boolean retval = false; if (getGlyphFileByUnicode(TTUnicodeRange.k_notDef) == null) { GlyphFile glyph = new GlyphFile(getGlyphPath(), TTUnicodeRange.k_notDef); glyph.initNotDef(k_defaultAdvanceWidth); addGlyph(0, glyph); retval = true; } if (getGlyphFileByUnicode(TTUnicodeRange.k_null) == null) { GlyphFile glyph = new GlyphFile(getGlyphPath(), TTUnicodeRange.k_null); glyph.initNullGlyph(); addGlyph(1, glyph); retval = true; } if (getGlyphFileByUnicode(TTUnicodeRange.k_cr) == null) { GlyphFile glyph = new GlyphFile(getGlyphPath(), TTUnicodeRange.k_cr); glyph.initSpace(k_defaultAdvanceWidth); addGlyph(2, glyph); retval = true; } if (getGlyphFileByUnicode(TTUnicodeRange.k_space) == null) { GlyphFile glyph = new GlyphFile(getGlyphPath(), TTUnicodeRange.k_space); glyph.initSpace(k_defaultAdvanceWidth); addGlyph(3, glyph); retval = true; } return retval; } public void addBasicLatinGlyphs() { String basicLatin = Character.UnicodeBlock.BASIC_LATIN.toString(); TTUnicodeRange.find(basicLatin); TTUnicodeRange range = TTUnicodeRange.getLastFound(); addUnicodeRange(basicLatin); for (long i = range.getStartCode(); i <= range.getEndCode(); i++) { if (i != 0x0020) { addGlyph(createGlyph(i)); } } } public File getGlyphPath() { return m_dir; } /** * change glyph's unicode mapping. * * @param a_glyphFile * @param a_unicode */ public void setGlyphUnicode(GlyphFile a_glyphFile, long a_unicode) { a_glyphFile.setUnicode(a_unicode); } public void addGlyph(GlyphFile a_file) { m_glyphFiles.add(a_file); } public void addGlyph(int a_index, GlyphFile a_file) { m_glyphFiles.add(a_index, a_file); } public Object[] getCodePages() { int i; Object[] retval; retval = new Object[0]; return retval; } public boolean containsUnicodeRange(String a_unicodeRange) { return m_unicodeRanges.contains(a_unicodeRange); } public void addUnicodeRange(String a_unicodeRange) { if (containsUnicodeRange(a_unicodeRange)) { return; } m_unicodeRanges.add(a_unicodeRange); } public boolean containsCodePage(String a_codePage) { return m_codePages.contains(a_codePage); } public void addCodePage(String a_codePage) { if (containsCodePage(a_codePage)) { return; } m_codePages.add(a_codePage); } public void removeCodePage(String a_codePage) { if (!containsCodePage(a_codePage)) { return; } m_codePages.remove(a_codePage); } public void setFontFamilyName(String a_value) { m_fontFamilyName = a_value; } public String getFontFamilyName() { return m_fontFamilyName; } public String getVersion() { return m_version == null ? "0.1" : m_version; } public void setVersion(String a_value) { m_version = a_value; } public void setDefaultMetrics() { m_topSideBearing = k_defaultTopSideBearing; m_ascender = k_defaultAscender; m_xHeight = k_defaultXHeight; m_descender = k_defaultDescender; m_bottomSideBearing = k_defaultBottomSideBearing; } public double getEm() { return k_em; } public double getBaseline() { return getBottomSideBearing() + getDescender(); } public double getMeanline() { return getBottomSideBearing() + getDescender() + getXHeight(); } public double getBodyBottom() { return getBottomSideBearing(); } public double getBodyTop() { return getEm() - getTopSideBearing(); } public double getTopSideBearing() { if (m_topSideBearing == null) { setDefaultMetrics(); } return m_topSideBearing; } public double getAscender() { if (m_ascender == null) { setDefaultMetrics(); } return m_ascender; } public double getXHeight() { if (m_xHeight == null) { setDefaultMetrics(); } return m_xHeight; } public double getDescender() { if (m_descender == null) { setDefaultMetrics(); } return m_descender; } public double getBottomSideBearing() { if (m_bottomSideBearing == null) { setDefaultMetrics(); } return m_bottomSideBearing; } public void setTopSideBearing(double a_value) throws OutOfRangeException { checkBoundary(a_value); m_topSideBearing = a_value; } private void checkBoundary(double a_value) throws OutOfRangeException { if (a_value > k_em || a_value < 0) { throw new OutOfRangeException(a_value); } } public void setAscender(double a_value) throws OutOfRangeException { checkBoundary(a_value); m_ascender = a_value; } public void setXHeight(double a_value) throws OutOfRangeException { checkBoundary(a_value); if (a_value > getAscender()) { throw new OutOfRangeException(a_value); } m_xHeight = a_value; } public void setDescender(double a_value) throws OutOfRangeException { checkBoundary(a_value); m_descender = a_value; } public void setBottomSideBearing(double a_value) throws OutOfRangeException { checkBoundary(a_value); m_bottomSideBearing = a_value; } public double getBodyHeight() { return k_em - getTopSideBearing() - getBottomSideBearing(); } // -------------------------------------------------------------------- /** * Calls FontFileWriter to produce TrueType font file. */ public void buildTTF() throws Exception { String randomString = UUID.randomUUID().toString().substring(0, 4); File tempFile = new File(m_dir, m_name + "_" + randomString + k_dotTtf); File target; String fontFamilyName; target = m_ttfFile; fontFamilyName = getFontFamilyName(); target.delete(); FontFileWriter writer; try (RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw")) { writer = new FontFileWriter(randomAccessFile); writer.setFontFamilyName(fontFamilyName); writer.setCopyrightYear(getCopyrightYear()); writer.setCreationDate(getCreationDate()); writer.setModificationDate(getModificationDate()); writer.setFontVersion(getVersion()); writer.setManufacturer(getAuthor()); writer.setAscent((int) getAscender()); writer.setXHeight((int) getXHeight()); writer.setDescent((int) getDescender()); writer.setLineGap((int) (getTopSideBearing() + getBottomSideBearing())); loadCodePages(writer); loadUnicodeRanges(writer); loadGlyphs(writer); writer.write(); } if (target.exists()) { copyFile(target, tempFile); } FileInputStream in = new FileInputStream(tempFile); m_font = Font.createFont(Font.TRUETYPE_FONT, (InputStream) in); in.close(); } private void copyFile(File a_in, File a_out) throws Exception { FileInputStream in = new FileInputStream(a_in); FileOutputStream out = new FileOutputStream(a_out); byte[] buffer = new byte[1024]; int i = 0; while ((i = in.read(buffer)) != -1) { out.write(buffer, 0, i); } // while in.close(); out.close(); } public Font getFont() { return m_font; } private void loadCodePages(FontFileWriter a_writer) { for (String codePageName : m_codePages) { TTCodePage codePage = TTCodePage.forName(codePageName); if (codePage == null) { continue; } a_writer.setCodeRangeFlag(codePage.getOsTwoFlag()); } // for codePageName } private void loadUnicodeRanges(FontFileWriter a_writer) { for (String unicodeRange : m_unicodeRanges) { if (!TTUnicodeRange.find(unicodeRange)) { continue; } a_writer.addUnicodeRange(TTUnicodeRange.getLastFound()); } } private void loadGlyphs(FontFileWriter a_writer) throws Exception { for (GlyphFile glyphFile : m_glyphFiles) { loadGlyph(glyphFile, a_writer); } } /** * load the glyph into FontFileWriter. * * @param a_fileName * @param a_writer * @throws Exception */ private void loadGlyph(GlyphFile a_glyphFile, FontFileWriter a_writer) throws Exception { TTGlyph glyph = null; if (a_glyphFile.isSimple()) { // glyph will be null if it is empty glyph = a_glyphFile.toSimpleGlyph(); } else { glyph = createCompoundGlyph(a_glyphFile, a_writer); } if (glyph == null && a_glyphFile.isWhiteSpace()) { glyph = new TTGlyph(); } if (glyph == null) { return; } int glyphIndex = a_writer.addGlyph(glyph); long unicode = a_glyphFile.getUnicode(); if (unicode != -1) { long existingIndex = a_writer.getCharacterMapping(unicode); if (existingIndex != 0) { throw new Exception(Long.toHexString(unicode) + " is mapped already."); } a_writer.addCharacterMapping(unicode, glyphIndex); } } private TTGlyph createCompoundGlyph(GlyphFile a_glyphFile, FontFileWriter a_writer) throws Exception { TTGlyph retval = new TTGlyph(); ArrayList<Point> locs = new ArrayList<>(); ArrayList<Integer> indeces = new ArrayList<>(); retval.setSimple(false); retval.setAdvanceWidth(a_glyphFile.getAdvanceWidth()); TTGlyph simple = a_glyphFile.toSimpleGlyph(); if (simple != null) { int glyphIndex = a_writer.addGlyph(simple); locs.add(new Point(0, 0)); indeces.add(glyphIndex); } int i = 0; int flag = TTGlyph.ARG_1_AND_2_ARE_WORDS | TTGlyph.ARGS_ARE_XY_VALUES | TTGlyph.ROUND_XY_TO_GRID; int numOfCompositePoints = 0; int numOfCompositeContours = 0; int componentDepth = 0; for (int glyfIndex : indeces) { TTGlyph glyph = a_writer.getGlyph(glyfIndex); numOfCompositePoints += glyph.getNumOfCompositePoints(); numOfCompositeContours += glyph.getNumOfCompositeContours(); if (glyph.getComponentDepth() > componentDepth) { componentDepth = glyph.getComponentDepth(); } retval.addGlyfIndex(glyfIndex); if (i < indeces.size() - 1) { retval.addFlag(flag | TTGlyph.MORE_COMPONENTS); } else { retval.addFlag(flag); } Point loc = locs.get(i); retval.addArg1(loc.x); retval.addArg2(loc.y); } // for retval.setNumOfCompositePoints(numOfCompositePoints); retval.setNumOfCompositeContours(numOfCompositeContours); retval.setComponentDepth(componentDepth + 1); return retval; } }