From fe83a17aaed78749e7b6a1abc6853c480294b8d8 Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Sat, 28 Mar 2015 19:18:52 +0100 Subject: [PATCH] faster font export (removed undo support, do not save dozens of temp files during export), to be continued... --- lib/ttf.jar | Bin 819956 -> 814960 bytes .../flash/exporters/FontExporter.java | 1 + libsrc/ttf/src/fontastic/Fontastic.java | 5 +- .../ttf/src/org/doubletype/ossa/Engine.java | 1459 ++++++----- .../src/org/doubletype/ossa/HistoryList.java | 104 - .../org/doubletype/ossa/module/GlyphFile.java | 2218 ++++++++--------- .../doubletype/ossa/module/TypefaceFile.java | 1332 +++++----- .../doubletype/ossa/truetype/GlyfWriter.java | 407 +-- 8 files changed, 2639 insertions(+), 2887 deletions(-) delete mode 100644 libsrc/ttf/src/org/doubletype/ossa/HistoryList.java diff --git a/lib/ttf.jar b/lib/ttf.jar index 70bde0bb798757911776e18d9685322a85228886..4cae30b2976eb1a65937eba572fbd1f56271c3bb 100644 GIT binary patch delta 46240 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 delta 49837 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 bZ8~-0sJv_&Iy=!r_@9LlZume*7193!nFYt1 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java index 5be9e3e76..37bc23c6d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FontExporter.java @@ -263,6 +263,7 @@ public class FontExporter { WritableFontData woffData = w.convert(font); woffData.copyTo(fos); } + ttfFile.delete(); } } diff --git a/libsrc/ttf/src/fontastic/Fontastic.java b/libsrc/ttf/src/fontastic/Fontastic.java index 7dcbc7c11..bf07e4c77 100644 --- a/libsrc/ttf/src/fontastic/Fontastic.java +++ b/libsrc/ttf/src/fontastic/Fontastic.java @@ -165,6 +165,7 @@ public class Fontastic { //m_engine.fireAction(); //m_engine.addDefaultGlyphs(); + List glyphFiles = new ArrayList<>(glyphs.size()); for (FGlyph glyph : glyphs) { m_engine.checkUnicodeBlock(glyph.getGlyphChar()); @@ -199,8 +200,10 @@ public class Fontastic { glyphFile.addContour(econtour); } - glyphFile.saveGlyphFile(); + + glyphFiles.add(glyphFile); } + m_engine.getTypeface().addRequiredGlyphs(); m_engine.buildTrueType(false); diff --git a/libsrc/ttf/src/org/doubletype/ossa/Engine.java b/libsrc/ttf/src/org/doubletype/ossa/Engine.java index 94c92bd19..16e459f68 100644 --- a/libsrc/ttf/src/org/doubletype/ossa/Engine.java +++ b/libsrc/ttf/src/org/doubletype/ossa/Engine.java @@ -1,762 +1,697 @@ -/* - * $Id: Engine.java,v 1.84 2004/12/27 04:56:03 eed3si9n Exp $ - * - * $Copyright: copyright (c) 2003, 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; - -import java.awt.*; -import java.awt.datatransfer.*; -import java.awt.event.*; -import java.awt.geom.*; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; -import java.io.*; -import java.net.MalformedURLException; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.*; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.*; -import javax.swing.event.*; - -import org.doubletype.ossa.adapter.*; -import org.doubletype.ossa.module.*; -import org.doubletype.ossa.truetype.*; - -/** - * @author e.e - */ -public class Engine { - // -------------------------------------------------------------- - - // used by findFile - public static final int USER_CANCELLED = -1; - public static final int FILE_NOT_FOUND = 0; - public static final int FILE_FOUND = 1; - - // used by Find - public static final int SEARCH_BY_EXAMPLE = 0; - public static final int SEARCH_UNICODE = 1; - public static final int SEARCH_JIS_CODE = 2; - - // public static final double k_fontSizes [] = {9, 10, 11, 12, 14, 18, 24, 36, 72}; - public static final int k_defaultPixelSize = 16; - public static final int k_resolutions [] = {96, 72, 75, 100}; - public static final int k_defaultResolution = 96; - public static final int k_zooms [] = {25, 50, 100}; - public static final int k_defaultZoom = 100; - - // -------------------------------------------------------------- - - private static int s_em = 1024; - private static Engine s_singleton = null; - - public static Engine getSingletonInstance() { - if (s_singleton == null) - s_singleton = new Engine(); - return s_singleton; - } - - public static int getEm() { - return TTPixelSize.getEm(); - } - - // -------------------------------------------------------------- - - private UiBridge m_ui; - - private TypefaceFile m_typeface; - private GlyphFile m_root; - private ActiveList m_actives; - private ArrayList m_listeners = new ArrayList<>(); - - private String m_foundFileName; - private Clipboard m_clipboard; - private JFileChooser m_gifChooser; - private JFileChooser m_chooser; - - private Action m_deleteAction; - private Action m_addPointAction; - private Action m_toggleAction; - private Action m_hintAction; - private Action m_contourAction; - private Action m_moduleAction; - private Action m_includeAction; - private Action m_selectNextAction; - private Action m_roundAction; - private Action m_propertyAction; - private Action m_convertControlPointAction; - private Action m_convertContourAction; - - private String m_msgAlreadyExists = " already exists!"; - private String m_msgNoTypeface = "no typeface"; - private String m_msgEmptyGlyphTitle = "glyph title is empty"; - private String m_msgGlyphName = "glyph name?"; - private String m_msgCircular = "circular include!"; - private String m_msgNoJis = "Charset ISO-2022-JP is not supported.\n" - + "Please include charsets.jar in classpath."; - - // -------------------------------------------------------------- - - private Engine() { - //GlyphFactory.setFactory(new EGlyphFactory()); - - - - m_typeface = null; - m_root = null; - - m_clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); - m_actives = ActiveList.getSingletonInstance(); - - initActions(); - } - - private void initActions() { - - } - - public Action [] buildCommands() { - ArrayList actions = buildCommandsArrayList(); - Action [] retval = new Action[actions.size()]; - int i; - for (i = 0; i < actions.size(); i++) { - retval[i] = (Action) actions.get(i); - } // for i - - return retval; - } - - private ArrayList buildCommandsArrayList() { - ArrayList retval = new ArrayList<>(); - - if (m_root == null) { - return retval; - } // if - - if (m_actives.hasActiveContour()) { - retval.add(m_convertContourAction); - } - - if (m_actives.hasActiveControlPoint()) { - EControlPoint controlPoint = m_actives.getActiveControlPoint(); - retval.add(m_convertControlPointAction); - } - - if (m_actives.hasActivePoint()) { - EContourPoint point = m_actives.getActivePoint(); - - retval.add(m_toggleAction); - - if (!point.isRounded()) { - retval.add(m_hintAction); - } // if - - if (!point.hasHintForCurrentPpem()) { - retval.add(m_roundAction); - } // if - } // if - - if (m_actives.size() > 0) { - // retval.add(m_propertyAction); - retval.add(m_deleteAction); - } // if - - if (m_actives.hasActivePoint()) { - retval.add(m_addPointAction); - } // if - - /*if (!GlyphAction.isPointVisible()) { - retval.add(m_moduleAction); - retval.add(m_contourAction); - retval.add(m_includeAction); - } // if*/ - - return retval; - } - - public void localize(ResourceBundle a_bundle) { - m_msgAlreadyExists = a_bundle.getString("msgAlreadyExists"); - m_msgNoTypeface = a_bundle.getString("msgNoTypeface"); - m_msgEmptyGlyphTitle = a_bundle.getString("msgEmptyGlyphTitle"); - m_msgGlyphName = a_bundle.getString("msgGlyphName"); - m_msgCircular = a_bundle.getString("msgCircular"); - } - - private void showPropertyDialog() { - if (m_actives.size() != 1) { - return; - } // if - - m_ui.showPropertyDialog(m_actives.get(0)); - fireAction(); - } - - public void setUi(UiBridge a_ui) { - m_ui = a_ui; - } - - - - public void selectNext() { - if (m_root == null) { - return; - } // if - - m_root.selectNext(); - fireAction(); - } - - public void delete() { - if (m_root == null) - return; - if (!m_actives.hasSelected()) { - return; - } // if - - m_root.remove(); - - fireAction(); - } - - public void cutToClipboard() { - if (m_root == null) - return; - if (!m_actives.hasSelected()) { - return; - } // if - - copyToClipboard(); - delete(); - - fireAction(); - } - - public void copyToClipboard() { - if (m_root == null) - return; - if (!m_actives.hasSelected()) { - return; - } // if - - String s = m_actives.getSelectedAsString(); - if (s.equals("")) { - return; - } // if - - StringSelection selection = new StringSelection(s); - - try { - m_clipboard.setContents(selection, selection); - } catch (Exception e) { - e.printStackTrace(); - } // try-catch - - fireAction(); - } - - public void pasteFromClipboard() { - if (m_root == null) - return; - Transferable content = null; - String s = ""; - - try { - content = m_clipboard.getContents(this); - if (content == null) - return; - s = (String) content.getTransferData(DataFlavor.stringFlavor); - if (s.equals("")) { - return; - } // if - } catch (Exception e) { - e.printStackTrace(); - return; - } // try-catch - - try { - m_root.addObjectFromClipboard(s); - } catch (GlyphFile.CircularIncludeException e) { - Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, m_msgCircular, e); - } - - fireAction(); - } - - public void undo() { - if (m_root == null) - return; - - m_root.undo(); - fireAction(); - } - - public void redo() { - if (m_root == null) - return; - - m_root.redo(); - fireAction(); - } - - public void setAdvanceWidth(int a_value) { - if (m_root == null) - return; - - m_root.setAdvanceWidth(a_value); - fireAction(); - } - - public void moveLeft() { - move(new Point2D.Double(-1, 0)); - } - - public void moveUp() { - move(new Point2D.Double(0, 1)); - } - - public void moveDown() { - move(new Point2D.Double(0, -1)); - } - - public void moveRight() { - move(new Point2D.Double(1, 0)); - } - - private void move(Point2D a_delta) { - if (m_root == null) { - return; - } // if - - m_root.move(a_delta); - } - - public void buildNewTypeface(String a_name, File a_dir) throws FileNotFoundException { - if (a_name == null || a_name.equals("")) { - return; - } // if - - TypefaceFile typeface = new TypefaceFile(a_name, a_dir); - typeface.setAuthor("no body"); - DateFormat format = new SimpleDateFormat("yyyy"); - typeface.setCopyrightYear(format.format(new Date())); - typeface.setFontFamilyName(a_name); - typeface.setSubFamily("Regular"); - typeface.addCodePage(TTCodePage.US_ASCII.toString()); - typeface.addCodePage(TTCodePage.Latin_1.toString()); - - setTypeface(typeface); - } - - public void addDefaultGlyphs() throws FileNotFoundException { - m_typeface.addRequiredGlyphs(); - m_typeface.addBasicLatinGlyphs(); - - fireAction(); - } - - public void openTypeface() throws FileNotFoundException { - if (m_chooser == null) { - m_chooser = new JFileChooser(new File(AppSettings.getLastTypefaceDir())); - } // if - - m_chooser.setFileFilter(new TypefaceFileFilter()); - int returnVal = m_chooser.showOpenDialog(null); - - if (returnVal != JFileChooser.APPROVE_OPTION) { - return; - } // if - - AppSettings.setLastTypefaceDir(m_chooser.getSelectedFile().toString()); - openTypeface(m_chooser.getSelectedFile()); - } - - private void openTypeface(File a_file) throws FileNotFoundException { - setTypeface(new TypefaceFile(a_file)); - - if (m_typeface.addRequiredGlyphs()) { - fireAction(); - } // if - } - - public void setTypeface(TypefaceFile a_typeface) { - m_typeface = a_typeface; - fireAction(); - } - - public void changeUnicode(long a_unicode) throws FileNotFoundException { - if (m_typeface == null || m_root == null) { - return; - } // if - - m_typeface.setGlyphUnicode(m_root, a_unicode); - fireAction(); - } - - public int findFile(long a_unicode) { - if (m_typeface == null) - return USER_CANCELLED; - - m_foundFileName = m_typeface.unicodeToFileName(a_unicode); - if (m_foundFileName != null) { - return FILE_FOUND; - } // if - - return FILE_NOT_FOUND; - } - - public String getFoundFileName() { - return m_foundFileName; - } - - - /** - * Create glyph out of given unicode, and add it to the typeface. - * @param a_unicode - */ - public GlyphFile addNewGlyph(long a_unicode) throws FileNotFoundException { - GlyphFile retval; - - retval = m_typeface.createGlyph(a_unicode); - addGlyphToTypeface(retval); - - return retval; - } - - public void checkUnicodeBlock(long a_unicode) throws FileNotFoundException { - TTUnicodeRange range = TTUnicodeRange.of(a_unicode); - if (range == null){ - return; - } - - if (m_typeface.containsUnicodeRange(range.toString())){ - return; - } - m_typeface.addUnicodeRange(range.toString()); - } - - private void addGlyphToTypeface(GlyphFile a_file) throws FileNotFoundException { - m_typeface.addGlyph(a_file); - m_typeface.saveGlyphFile(); - - setRoot(a_file); - } - - public GlyphFile openGlyphFile(String a_fileName) { - ModuleManager manager = ModuleManager.getSingletonInstance(); - GlyphFile retval = manager.getReloadedGlyphFile(a_fileName); - setRoot(retval); - - return retval; - } - - public void removeGlyphFromTypeface(String a_fileName) { - if (m_typeface == null) - return; - - m_typeface.removeGlyph(a_fileName); - fireAction(); - } - - public Font buildTrueType(boolean a_isDebug) { - Font retval = null; - - if (m_typeface == null) - return retval; - - - - try { - m_typeface.buildTTF(a_isDebug); - retval = m_typeface.getFont(); - } catch (Exception e) { - Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, null,e); - } // try-catch - - return retval; - } - - public void saveGlyph() throws FileNotFoundException { - if (m_root == null) - return; - - if (m_root.getGlyphTitle().equals("")) { - Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, m_msgEmptyGlyphTitle); - return; - } // if - - m_root.saveGlyphFile(); - fireAction(); - } - - public TypefaceFile getTypeface() { - return m_typeface; - } - - public File getGlyphPath() { - return m_typeface.getGlyphPath(); - } - - - public GlyphFile getRoot() { - return m_root; - } - - public void setRoot(GlyphFile a_file) { - m_root = a_file; - fireAction(); - } - - public void addActionListener(ActionListener a_listener) { - m_listeners.add(a_listener); - } - - public void fireAction() { - ActionEvent e = new ActionEvent(this, Event.ACTION_EVENT, "foo"); - - for (ActionListener listener: m_listeners) { - listener.actionPerformed(e); - } // for listener - } - - public String includeFileName() { - String retval = ""; - - JFileChooser chooser = new JFileChooser(getGlyphPath()); - chooser.setFileFilter(new GlyphFileFilter()); - - int returnVal = chooser.showOpenDialog(null); - - if (returnVal == JFileChooser.APPROVE_OPTION) { - retval = chooser.getSelectedFile().getName().toString(); - } // if - - return retval; - } - - public void addCodePage(String a_codePage) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - m_typeface.addCodePage(a_codePage); - fireAction(); - } - - public void removeCodePage(String a_codePage) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - m_typeface.removeCodePage(a_codePage); - fireAction(); - } - - public void setAuthor(String a_value) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - m_typeface.setAuthor(a_value); - m_typeface.saveGlyphFile(); - fireAction(); - } - - public void setCopyrightYear(String a_value) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - m_typeface.setCopyrightYear(a_value); - m_typeface.saveGlyphFile(); - fireAction(); - } - - public void setFontFamilyName(String a_value) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - m_typeface.setFontFamilyName(a_value); - fireAction(); - } - - public void setTypefaceLicense(String a_value) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - m_typeface.setLicense(a_value); - m_typeface.saveGlyphFile(); - fireAction(); - } - - public void setBaseline(double a_value) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - double min = m_typeface.getBottomSideBearing(); - double max = m_typeface.getMeanline(); - - if (a_value < min) { - a_value = min; - } // if - - if (a_value > max) { - a_value = max; - } // if - - try { - m_typeface.setDescender(a_value - min); - m_typeface.setAscender(m_typeface.getEm() - - m_typeface.getTopSideBearing() - a_value); - m_typeface.setXHeight(max - a_value); - } - catch (OutOfRangeException e) { - e.printStackTrace(); - } // try-catch - - fireAction(); - } - - public void setMeanline(double a_value) throws FileNotFoundException { - if (m_typeface == null) { - return; - } // if - - double min = m_typeface.getBaseline(); - double max = m_typeface.getEm() - - m_typeface.getTopSideBearing(); - - if (a_value < min) { - a_value = min; - } // if - - if (a_value > max) { - a_value = max; - } // if - - try { - m_typeface.setXHeight(a_value - min); - } - catch (OutOfRangeException e) { - e.printStackTrace(); - } // try-catch - - fireAction(); - } - - public void mousePressed(MouseEvent a_event) { - - } - - - public void mouseDragged(MouseEvent a_event) { - - } - - public void mouseReleased(MouseEvent a_event) { - - } - - public void setAction(String a_key) { - - } - - public void keyPressed(KeyEvent a_event) { - if (a_event.getModifiers() == 0) { - if (a_event.getKeyCode() == KeyEvent.VK_TAB) { - - m_selectNextAction.actionPerformed(null); - } // if - } else if (a_event.getModifiers() == KeyEvent.SHIFT_MASK) { - - } // if - - fireAction(); - } - - private JFileChooser createImageChooser() { - JFileChooser retval; - - retval = new JFileChooser(new File(AppSettings.getLastTypefaceDir())); - retval.setFileFilter(new javax.swing.filechooser.FileFilter() { - public boolean accept(File a_file) { - if (a_file.isDirectory()) - return true; - - String s = a_file.toString().toLowerCase(); - if (s.endsWith(".gif") - || s.endsWith(".jpg") - || s.endsWith(".jpeg") - || s.endsWith(".png")) - return true; - - return false; - } - - //The description of this filter - public String getDescription() { - return "Image Files"; - } - }); - - return retval; - } - - - - - public ArrayList getPixelSizes() { - return TTPixelSize.getList(); - } - - class MyTreeListener implements TreeSelectionListener { - public void valueChanged(TreeSelectionEvent a_event) { - TreePath path = a_event.getPath(); - Object obj = path.getLastPathComponent(); - String s = obj.toString(); - - TreePath parent = path.getParentPath(); - if (parent != null) { - obj = parent.getLastPathComponent(); - s = obj.toString() + "->" + s; - } // if - - } - } - -} +/* + * $Id: Engine.java,v 1.84 2004/12/27 04:56:03 eed3si9n Exp $ + * + * $Copyright: copyright (c) 2003, 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; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.*; +import java.awt.geom.*; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.io.*; +import java.net.MalformedURLException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.*; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.*; +import javax.swing.event.*; + +import org.doubletype.ossa.adapter.*; +import org.doubletype.ossa.module.*; +import org.doubletype.ossa.truetype.*; + +/** + * @author e.e + */ +public class Engine { + // -------------------------------------------------------------- + + // used by findFile + public static final int USER_CANCELLED = -1; + public static final int FILE_NOT_FOUND = 0; + public static final int FILE_FOUND = 1; + + // used by Find + public static final int SEARCH_BY_EXAMPLE = 0; + public static final int SEARCH_UNICODE = 1; + public static final int SEARCH_JIS_CODE = 2; + + // public static final double k_fontSizes [] = {9, 10, 11, 12, 14, 18, 24, 36, 72}; + public static final int k_defaultPixelSize = 16; + public static final int k_resolutions [] = {96, 72, 75, 100}; + public static final int k_defaultResolution = 96; + public static final int k_zooms [] = {25, 50, 100}; + public static final int k_defaultZoom = 100; + + // -------------------------------------------------------------- + + private static int s_em = 1024; + private static Engine s_singleton = null; + + public static Engine getSingletonInstance() { + if (s_singleton == null) + s_singleton = new Engine(); + return s_singleton; + } + + public static int getEm() { + return TTPixelSize.getEm(); + } + + // -------------------------------------------------------------- + + private UiBridge m_ui; + + private TypefaceFile m_typeface; + private GlyphFile m_root; + private ActiveList m_actives; + private ArrayList m_listeners = new ArrayList<>(); + + private String m_foundFileName; + private Clipboard m_clipboard; + private JFileChooser m_gifChooser; + private JFileChooser m_chooser; + + private Action m_deleteAction; + private Action m_addPointAction; + private Action m_toggleAction; + private Action m_hintAction; + private Action m_contourAction; + private Action m_moduleAction; + private Action m_includeAction; + private Action m_selectNextAction; + private Action m_roundAction; + private Action m_propertyAction; + private Action m_convertControlPointAction; + private Action m_convertContourAction; + + private String m_msgAlreadyExists = " already exists!"; + private String m_msgNoTypeface = "no typeface"; + private String m_msgEmptyGlyphTitle = "glyph title is empty"; + private String m_msgGlyphName = "glyph name?"; + private String m_msgCircular = "circular include!"; + private String m_msgNoJis = "Charset ISO-2022-JP is not supported.\n" + + "Please include charsets.jar in classpath."; + + // -------------------------------------------------------------- + + private Engine() { + //GlyphFactory.setFactory(new EGlyphFactory()); + + + + m_typeface = null; + m_root = null; + + m_clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + m_actives = ActiveList.getSingletonInstance(); + + initActions(); + } + + private void initActions() { + + } + + public Action [] buildCommands() { + ArrayList actions = buildCommandsArrayList(); + Action [] retval = new Action[actions.size()]; + int i; + for (i = 0; i < actions.size(); i++) { + retval[i] = (Action) actions.get(i); + } // for i + + return retval; + } + + private ArrayList buildCommandsArrayList() { + ArrayList retval = new ArrayList<>(); + + if (m_root == null) { + return retval; + } // if + + if (m_actives.hasActiveContour()) { + retval.add(m_convertContourAction); + } + + if (m_actives.hasActiveControlPoint()) { + EControlPoint controlPoint = m_actives.getActiveControlPoint(); + retval.add(m_convertControlPointAction); + } + + if (m_actives.hasActivePoint()) { + EContourPoint point = m_actives.getActivePoint(); + + retval.add(m_toggleAction); + + if (!point.isRounded()) { + retval.add(m_hintAction); + } // if + + if (!point.hasHintForCurrentPpem()) { + retval.add(m_roundAction); + } // if + } // if + + if (m_actives.size() > 0) { + // retval.add(m_propertyAction); + retval.add(m_deleteAction); + } // if + + if (m_actives.hasActivePoint()) { + retval.add(m_addPointAction); + } // if + + /*if (!GlyphAction.isPointVisible()) { + retval.add(m_moduleAction); + retval.add(m_contourAction); + retval.add(m_includeAction); + } // if*/ + + return retval; + } + + public void localize(ResourceBundle a_bundle) { + m_msgAlreadyExists = a_bundle.getString("msgAlreadyExists"); + m_msgNoTypeface = a_bundle.getString("msgNoTypeface"); + m_msgEmptyGlyphTitle = a_bundle.getString("msgEmptyGlyphTitle"); + m_msgGlyphName = a_bundle.getString("msgGlyphName"); + m_msgCircular = a_bundle.getString("msgCircular"); + } + + private void showPropertyDialog() { + if (m_actives.size() != 1) { + return; + } // if + + m_ui.showPropertyDialog(m_actives.get(0)); + } + + public void setUi(UiBridge a_ui) { + m_ui = a_ui; + } + + + + public void selectNext() { + if (m_root == null) { + return; + } // if + + m_root.selectNext(); + } + + public void delete() { + if (m_root == null) + return; + if (!m_actives.hasSelected()) { + return; + } // if + + m_root.remove(); + } + + public void cutToClipboard() { + if (m_root == null) + return; + if (!m_actives.hasSelected()) { + return; + } // if + + copyToClipboard(); + delete(); + } + + public void copyToClipboard() { + if (m_root == null) + return; + if (!m_actives.hasSelected()) { + return; + } // if + + String s = m_actives.getSelectedAsString(); + if (s.equals("")) { + return; + } // if + + StringSelection selection = new StringSelection(s); + + try { + m_clipboard.setContents(selection, selection); + } catch (Exception e) { + e.printStackTrace(); + } // try-catch + } + + public void pasteFromClipboard() { + if (m_root == null) + return; + Transferable content = null; + String s = ""; + + try { + content = m_clipboard.getContents(this); + if (content == null) + return; + s = (String) content.getTransferData(DataFlavor.stringFlavor); + if (s.equals("")) { + return; + } // if + } catch (Exception e) { + e.printStackTrace(); + return; + } // try-catch + + try { + m_root.addObjectFromClipboard(s); + } catch (GlyphFile.CircularIncludeException e) { + Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, m_msgCircular, e); + } + } + + public void setAdvanceWidth(int a_value) { + if (m_root == null) + return; + + m_root.setAdvanceWidth(a_value); + } + + public void moveLeft() { + move(new Point2D.Double(-1, 0)); + } + + public void moveUp() { + move(new Point2D.Double(0, 1)); + } + + public void moveDown() { + move(new Point2D.Double(0, -1)); + } + + public void moveRight() { + move(new Point2D.Double(1, 0)); + } + + private void move(Point2D a_delta) { + if (m_root == null) { + return; + } // if + + m_root.move(a_delta); + } + + public void buildNewTypeface(String a_name, File a_dir) throws FileNotFoundException { + if (a_name == null || a_name.equals("")) { + return; + } // if + + TypefaceFile typeface = new TypefaceFile(a_name, a_dir); + typeface.setAuthor("no body"); + DateFormat format = new SimpleDateFormat("yyyy"); + typeface.setCopyrightYear(format.format(new Date())); + typeface.setFontFamilyName(a_name); + typeface.setSubFamily("Regular"); + typeface.addCodePage(TTCodePage.US_ASCII.toString()); + typeface.addCodePage(TTCodePage.Latin_1.toString()); + + setTypeface(typeface); + } + + public void addDefaultGlyphs() throws FileNotFoundException { + m_typeface.addRequiredGlyphs(); + m_typeface.addBasicLatinGlyphs(); + } + + public void openTypeface() throws FileNotFoundException { + if (m_chooser == null) { + m_chooser = new JFileChooser(new File(AppSettings.getLastTypefaceDir())); + } // if + + m_chooser.setFileFilter(new TypefaceFileFilter()); + int returnVal = m_chooser.showOpenDialog(null); + + if (returnVal != JFileChooser.APPROVE_OPTION) { + return; + } // if + + AppSettings.setLastTypefaceDir(m_chooser.getSelectedFile().toString()); + openTypeface(m_chooser.getSelectedFile()); + } + + private void openTypeface(File a_file) throws FileNotFoundException { + setTypeface(new TypefaceFile(a_file)); + + if (m_typeface.addRequiredGlyphs()) { + } // if + } + + public void setTypeface(TypefaceFile a_typeface) { + m_typeface = a_typeface; + } + + public void changeUnicode(long a_unicode) throws FileNotFoundException { + if (m_typeface == null || m_root == null) { + return; + } // if + + m_typeface.setGlyphUnicode(m_root, a_unicode); + } + + public int findFile(long a_unicode) { + if (m_typeface == null) + return USER_CANCELLED; + + m_foundFileName = m_typeface.unicodeToFileName(a_unicode); + if (m_foundFileName != null) { + return FILE_FOUND; + } // if + + return FILE_NOT_FOUND; + } + + public String getFoundFileName() { + return m_foundFileName; + } + + + /** + * Create glyph out of given unicode, and add it to the typeface. + * @param a_unicode + */ + public GlyphFile addNewGlyph(long a_unicode) throws FileNotFoundException { + GlyphFile retval; + + retval = m_typeface.createGlyph(a_unicode); + addGlyphToTypeface(retval); + + return retval; + } + + public void checkUnicodeBlock(long a_unicode) throws FileNotFoundException { + TTUnicodeRange range = TTUnicodeRange.of(a_unicode); + if (range == null){ + return; + } + + if (m_typeface.containsUnicodeRange(range.toString())){ + return; + } + m_typeface.addUnicodeRange(range.toString()); + } + + private void addGlyphToTypeface(GlyphFile a_file) throws FileNotFoundException { + m_typeface.addGlyph(a_file); + + setRoot(a_file); + } + + public GlyphFile openGlyphFile(String a_fileName) { + ModuleManager manager = ModuleManager.getSingletonInstance(); + GlyphFile retval = manager.getReloadedGlyphFile(a_fileName); + setRoot(retval); + + return retval; + } + + public void removeGlyphFromTypeface(String a_fileName) { + if (m_typeface == null) + return; + + m_typeface.removeGlyph(a_fileName); + } + + public Font buildTrueType(boolean a_isDebug) { + Font retval = null; + + if (m_typeface == null) + return retval; + + + + try { + m_typeface.buildTTF(a_isDebug); + retval = m_typeface.getFont(); + } catch (Exception e) { + Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, null,e); + } // try-catch + + return retval; + } + + public void saveGlyph() throws FileNotFoundException { + if (m_root == null) + return; + + if (m_root.getGlyphTitle().equals("")) { + Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, m_msgEmptyGlyphTitle); + return; + } // if + } + + public TypefaceFile getTypeface() { + return m_typeface; + } + + public File getGlyphPath() { + return m_typeface.getGlyphPath(); + } + + + public GlyphFile getRoot() { + return m_root; + } + + public void setRoot(GlyphFile a_file) { + m_root = a_file; + } + + public String includeFileName() { + String retval = ""; + + JFileChooser chooser = new JFileChooser(getGlyphPath()); + chooser.setFileFilter(new GlyphFileFilter()); + + int returnVal = chooser.showOpenDialog(null); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + retval = chooser.getSelectedFile().getName().toString(); + } // if + + return retval; + } + + public void addCodePage(String a_codePage) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + m_typeface.addCodePage(a_codePage); + } + + public void removeCodePage(String a_codePage) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + m_typeface.removeCodePage(a_codePage); + } + + public void setAuthor(String a_value) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + m_typeface.setAuthor(a_value); + } + + public void setCopyrightYear(String a_value) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + m_typeface.setCopyrightYear(a_value); + } + + public void setFontFamilyName(String a_value) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + m_typeface.setFontFamilyName(a_value); + } + + public void setTypefaceLicense(String a_value) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + m_typeface.setLicense(a_value); + } + + public void setBaseline(double a_value) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + double min = m_typeface.getBottomSideBearing(); + double max = m_typeface.getMeanline(); + + if (a_value < min) { + a_value = min; + } // if + + if (a_value > max) { + a_value = max; + } // if + + try { + m_typeface.setDescender(a_value - min); + m_typeface.setAscender(m_typeface.getEm() + - m_typeface.getTopSideBearing() - a_value); + m_typeface.setXHeight(max - a_value); + } + catch (OutOfRangeException e) { + e.printStackTrace(); + } // try-catch + } + + public void setMeanline(double a_value) throws FileNotFoundException { + if (m_typeface == null) { + return; + } // if + + double min = m_typeface.getBaseline(); + double max = m_typeface.getEm() + - m_typeface.getTopSideBearing(); + + if (a_value < min) { + a_value = min; + } // if + + if (a_value > max) { + a_value = max; + } // if + + try { + m_typeface.setXHeight(a_value - min); + } + catch (OutOfRangeException e) { + e.printStackTrace(); + } // try-catch + } + + public void mousePressed(MouseEvent a_event) { + + } + + + public void mouseDragged(MouseEvent a_event) { + + } + + public void mouseReleased(MouseEvent a_event) { + + } + + public void setAction(String a_key) { + + } + + public void keyPressed(KeyEvent a_event) { + if (a_event.getModifiers() == 0) { + if (a_event.getKeyCode() == KeyEvent.VK_TAB) { + + m_selectNextAction.actionPerformed(null); + } // if + } else if (a_event.getModifiers() == KeyEvent.SHIFT_MASK) { + + } // if + } + + private JFileChooser createImageChooser() { + JFileChooser retval; + + retval = new JFileChooser(new File(AppSettings.getLastTypefaceDir())); + retval.setFileFilter(new javax.swing.filechooser.FileFilter() { + public boolean accept(File a_file) { + if (a_file.isDirectory()) + return true; + + String s = a_file.toString().toLowerCase(); + if (s.endsWith(".gif") + || s.endsWith(".jpg") + || s.endsWith(".jpeg") + || s.endsWith(".png")) + return true; + + return false; + } + + //The description of this filter + public String getDescription() { + return "Image Files"; + } + }); + + return retval; + } + + + + + public ArrayList getPixelSizes() { + return TTPixelSize.getList(); + } + + class MyTreeListener implements TreeSelectionListener { + public void valueChanged(TreeSelectionEvent a_event) { + TreePath path = a_event.getPath(); + Object obj = path.getLastPathComponent(); + String s = obj.toString(); + + TreePath parent = path.getParentPath(); + if (parent != null) { + obj = parent.getLastPathComponent(); + s = obj.toString() + "->" + s; + } // if + + } + } + +} diff --git a/libsrc/ttf/src/org/doubletype/ossa/HistoryList.java b/libsrc/ttf/src/org/doubletype/ossa/HistoryList.java deleted file mode 100644 index 53fe5e867..000000000 --- a/libsrc/ttf/src/org/doubletype/ossa/HistoryList.java +++ /dev/null @@ -1,104 +0,0 @@ - /* - * $Id: HistoryList.java,v 1.1 2004/09/05 17:08:03 eed3si9n Exp $ - * - * $Copyright: copyright (c) 2004, 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; - -import java.util.*; -import org.doubletype.ossa.module.GlyphFile; - -/** Caretaker of MementoPattern. - * @author e.e - */ -public class HistoryList { - private GlyphFile m_file; - private ArrayList m_list = new ArrayList<>(); - private int m_index = -1; - - - public HistoryList(GlyphFile a_file) { - m_file = a_file; - } - - private void printHistoryArray() { - int i; - for (i = 0; i <= m_index; i++) { - Memento memento = m_list.get(i); - System.out.println(memento.toString()); - } // for i - } - - /** - * Records snapshot into history queue. - * Takes place after each action. - * Updates modified time with the current system time. - * @param a_description - */ - public void record(String a_description) { - add(m_file.createMemento(a_description)); - } - - private void add(Memento a_memento) { - while (m_list.size() - 1 > m_index) { - m_list.remove(m_list.size() - 1); - } // if - - m_list.add(a_memento); - m_index = m_list.size() - 1; - } - - public void undo() { - if (m_list.size() == 0 - || m_index <= 0) { - return; - } // if - - m_index--; - m_file.restore(get(m_index)); - } - - public void redo() { - if (m_list.size() == 0 - || m_index >= m_list.size() - 1) { - return; - } // if - - m_index++; - m_file.restore(get(m_index)); - } - - private Memento get(int a_index) { - return m_list.get(a_index); - } -} diff --git a/libsrc/ttf/src/org/doubletype/ossa/module/GlyphFile.java b/libsrc/ttf/src/org/doubletype/ossa/module/GlyphFile.java index fd57a24d3..bdd5d618b 100644 --- a/libsrc/ttf/src/org/doubletype/ossa/module/GlyphFile.java +++ b/libsrc/ttf/src/org/doubletype/ossa/module/GlyphFile.java @@ -1,1141 +1,1079 @@ - /* - * $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 org.doubletype.ossa.*; -import org.doubletype.ossa.xml.*; -import org.doubletype.ossa.adapter.*; -import org.doubletype.ossa.truetype.*; - -import java.io.*; -import java.awt.*; -import java.awt.geom.*; - -import javax.xml.transform.*; -import javax.xml.transform.stream.*; -import javax.xml.transform.dom.*; -import java.net.*; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.w3c.dom.*; -import javax.xml.parsers.*; -import org.xml.sax.*; - -/** - * @author e.e - */ -public class GlyphFile extends GlyphModule { - protected static String s_emptyFileName = "empty.glyph"; - private static final String k_dotGlyph = ".glyph"; - private static Transformer s_transformer = null; - - static { - GlyphFactory.setFactory(EGlyphFactory.getFactory()); - } - - public static File createFileName(File a_dir, String a_name) { - return new File(a_dir, a_name + k_dotGlyph); - } - - /** - * http://www.atmarkit.co.jp/fxml/rensai2/xmltool04/02.html - * @return - */ - private static Transformer getTransformer() { - if (s_transformer == null) { - TransformerFactory transFactory - = TransformerFactory.newInstance(); - try { - s_transformer = transFactory.newTransformer(); - } - catch (TransformerConfigurationException e) { - } - - s_transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - s_transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - } // if - - return s_transformer; - } - - private static XStartGlyphElement loadGlyphElement(URL a_url) { - IGlyphFactory factory = GlyphFactory.getFactory(); - - XStartGlyphElement retval = null; - - try { - retval = factory.createXStartGlyphElement(a_url); - } catch( SAXException | ParserConfigurationException | IOException e) { - e.printStackTrace(); - } - - return retval; - } - - private static XStartGlyphElement loadGlyphElement(InputStream a_in) { - IGlyphFactory factory = GlyphFactory.getFactory(); - XStartGlyphElement retval = null; - - try { - retval = factory.createXStartGlyphElement(a_in); - } catch( SAXException | ParserConfigurationException | IOException e) { - e.printStackTrace(); - } - - return retval; - } - // -------------------------------------------------------------- - - protected XStartGlyphElement m_glyph; - protected File m_fileName; - protected long m_modifiedTime = 0; - protected long m_savedTime = 0; - protected HistoryList m_history; - - private final int k_halfWidth = 512; - private final int k_fullWidth = 1024; - private String m_selectedNodeName = ""; - private boolean m_isMoving = false; - private PointAggregate m_pointHost; - - - // -------------------------------------------------------------- - - /** - * creates new file - */ - public GlyphFile(File a_dir, String a_name, long a_unicode) throws FileNotFoundException { - super(); - - m_fileName = createFileName(a_dir, a_name); - - init(getClass().getResource(s_emptyFileName)); - setGlyphTitle(a_name); - setUnicode(Long.toHexString(a_unicode)); - - /*int eastAsianWidth = UCharacter.getIntPropertyValue( - (int) a_unicode, - 0x1004); //UProperty.EAST_ASIAN_WIDTH); - */ - int eastAsianWidth = 5; //?? - if (eastAsianWidth == 5 || eastAsianWidth == 1) { - setAdvanceWidth(k_fullWidth); - } // if - - saveGlyphFile(); - } - - /** - * creates new file - * @param a_dir parent dir - * @param a_name glyph name - */ - public GlyphFile(File a_dir, String a_name) throws FileNotFoundException { - super(); - init(getClass().getResource(s_emptyFileName)); - setGlyphTitle(a_name); - m_fileName = createFileName(a_dir, a_name); - saveGlyphFile(); - } - - /** - * opens existing file - * @param a_file - */ - public GlyphFile(File a_file) { - super(); - - m_fileName = a_file; - - URL url = null; - - try { - url = a_file.toURI().toURL(); - } catch(MalformedURLException e) { - e.printStackTrace(); - } - - init(url); - } - - protected GlyphFile(URL a_url) { - super(); - init(a_url); - } - - private void init(URL a_url) { - m_glyph = loadGlyphElement(a_url); - ModuleManager.getSingletonInstance().clear(); - - //m_display = Engine.getSingletonInstance().getDisplay(); - m_history = new HistoryList(this); - - m_history.record("loadFile"); - m_savedTime = m_modifiedTime; - } - - /** - * initialize .notdef - */ - public void initNotDef(int a_advanceWidth) throws FileNotFoundException { - setAdvanceWidth(a_advanceWidth); - - EContour contour = new EContour(); - contour.setType(EContour.k_cubic); - contour.addContourPoint(new EContourPoint(0.0, 0.0, true)); - contour.addContourPoint(new EContourPoint(438.0, 0.0, true)); - contour.addContourPoint(new EContourPoint(438.0, 683.0, true)); - contour.addContourPoint(new EContourPoint(0.0, 683.0, true)); - addContour(contour); - - contour = new EContour(); - contour.setType(EContour.k_cubic); - contour.addContourPoint(new EContourPoint(365.0, 73.0, true)); - contour.addContourPoint(new EContourPoint(73.0, 73.0, true)); - contour.addContourPoint(new EContourPoint(73.0, 610.0, true)); - contour.addContourPoint(new EContourPoint(365.0, 610.0, true)); - addContour(contour); - - saveGlyphFile(); - } - - public void initNullGlyph() throws FileNotFoundException { - setAdvanceWidth(0); - saveGlyphFile(); - } - - public void initSpace(int a_advanceWidth) throws FileNotFoundException { - setAdvanceWidth(a_advanceWidth); - saveGlyphFile(); - } - - public void beforePush() { - loadVar(); - } - - public void undo() { - m_history.undo(); - } - - public void redo() { - m_history.redo(); - } - - public void restore(Memento a_memento) { - m_glyph = loadGlyphElement(a_memento.getData()); - ModuleManager.getSingletonInstance().clear(); - } - - // -------------------------------------------------------------- - - public XStartGlyphElement getGlyph() { - return m_glyph; - } - - // -------------------------------------------------------------- - - public void saveGlyphFile() throws FileNotFoundException { - saveGlyphFile(m_fileName); - } - - protected void saveGlyphFile(File a_file) throws FileNotFoundException { - FileOutputStream output = new FileOutputStream(a_file); - saveGlyphFile(output); - try { - output.close(); //JPEXS - } catch (IOException ex) { - Logger.getLogger(GlyphFile.class.getName()).log(Level.SEVERE, null, ex); - } - } - - public void saveGlyphFile(OutputStream a_output) { - try { - Transformer transformer = getTransformer(); - Document document = m_glyph.makeDocument(); - DOMSource source = new DOMSource(document); - StreamResult result = new StreamResult(a_output); - transformer.transform(source, result); - } catch (ParserConfigurationException | TransformerException e) { - e.printStackTrace(); - } - - m_savedTime = System.currentTimeMillis(); - m_modifiedTime = m_savedTime; - } - - public Memento createMemento(String a_description) { - m_modifiedTime = System.currentTimeMillis(); - byte [] bytes = m_glyph.makeTextDocument().getBytes(); - return new Memento(a_description, bytes); - } - - public boolean hasUnsavedChange() { - return (m_savedTime != m_modifiedTime); - } - - public void setAuthor(String a_value) { - m_glyph.getHead().setAuthor(a_value); - m_history.record("setAuthor"); - } - - public String getAuthor() { - return m_glyph.getHead().getAuthor(); - } - - public void setCopyrightYear(String a_value) { - m_glyph.getHead().setCopyright(a_value); - m_history.record("setCopyrightYear"); - } - - public String getCopyrightYear() { - return m_glyph.getHead().getCopyright(); - } - - public void setAdvanceWidth(int a_width) { - m_glyph.getHead().setAdvanceWidth(a_width); - m_history.record("setAdvanceWidth"); - } - - public int getAdvanceWidth() { - if (!m_glyph.getHead().checkAdvanceWidth()) { - setAdvanceWidth(k_halfWidth); - } // if - - return (int) m_glyph.getHead().getAdvanceWidth(); - } - - public Iterator createIterator() { - return new GlyphIterator(this); - } - - - // -------------------------------------------------------------- - - public void display(Graphics2D g, AffineTransform a_trans) - { - Iterator i = createIterator(); - while (i.hasNext()) { - GlyphObject object = (GlyphObject) i.next(); - object.display(g, a_trans); - } // while - } - - // -------------------------------------------------------------- - - public static final int k_defaultPixelSize = 16; //PPM - - /** converts this glyph into Shape. - * It could be called for root's preview mode or by include invoke. - * Pushing either this GlyphFile or DIncludeInvoke should be - * handled before this. - */ - public Shape toShape(AffineTransform a_trans) { - - - int ppem = k_defaultPixelSize; - - GeneralPath retval = new GeneralPath(); - Iterator i = createIterator(); - while (i.hasNext()) { - GlyphObject object = (GlyphObject) i.next(); - - if (object instanceof EContourPoint - || object instanceof EHint) { - continue; - } // if - - retval.append(object.toShape(a_trans, ppem), false); - } // if - - return retval; - } - - // -------------------------------------------------------------- - - /** - * Generates array of XContour from local contours and modules. - * Used for TTF building. - */ - private XContour [] toContours() { - XContour [] retval; - ArrayList list = new ArrayList<>(); - XContour [] contours = m_glyph.getBody().getContour(); - for (int i = 0; i < contours.length; i++) { - EContour contour = (EContour) contours[i]; - list.add(contour.toQuadratic()); - } // for i - - XModule [] modules = m_glyph.getBody().getModule(); - for (int i = 0; i < modules.length; i++) { - EModuleInvoke module = (EModuleInvoke) modules[i]; - - // push and pop happens inside toContour - list.add(module.toContour(new AffineTransform())); - } // for i - - if (list.size() == 0) - return null; - - retval = new XContour[list.size()]; - for (int i = 0; i < list.size(); i++) { - retval[i] = list.get(i); - } // for i - - return retval; - } - - // -------------------------------------------------------------- - - public boolean isMoving() { - return m_isMoving; - } - - // -------------------------------------------------------------- - - public void beginMove() { - m_isMoving = true; - } - - // -------------------------------------------------------------- - - public void endMove() { - if (!m_isMoving) { - return; - } // if - - m_isMoving = false; - - m_history.record("move"); - } - - // -------------------------------------------------------------- - - public void move(Point2D a_delta) { - int i; - for (i = 0; i < m_actives.size(); i++) { - GlyphObject active = m_actives.get(i); - active.move(a_delta); - } // for i - } - - - // -------------------------------------------------------------- - - public void fitMove() { - if (m_actives.hasActiveControlPoint()) { - EControlPoint controlPoint = m_actives.getActiveControlPoint(); - controlPoint.rotateTo45(); - } // if - } - - // -------------------------------------------------------------- - - public boolean isHittingSelected(Point2D a_point) { - ActiveList oldActiveObjects = new ActiveList(); - oldActiveObjects.setActives(m_actives); - - m_actives.unselectAll(); - hit(a_point, false, false); - - int i, j; - for (i = 0; i < m_actives.size(); i++) { - GlyphObject selected = m_actives.get(i); - for (j = 0; j < oldActiveObjects.size(); j++) { - GlyphObject old = oldActiveObjects.get(j); - - if (selected == old) { - m_actives.setActives(oldActiveObjects); - return true; - } // if - } // for j - } // for i - - m_actives.setActives(oldActiveObjects); - - return false; - } - - // -------------------------------------------------------------- - - /** hits a point to add them in active object queue. - * @param a_single when it is true, the method returns after - * finding the first hit. - */ - public boolean hit(Point2D a_point, - boolean a_isAppend, - boolean a_isSelectOnlyOne) { - if (a_point == null) { - return false; - } // if - - Rectangle2D rect = new Rectangle2D.Double( - a_point.getX(), a_point.getY(), - 1.0, 1.0); - return hit(rect, a_isAppend, a_isSelectOnlyOne); - } - - public void selectNext() { - Iterator i = new SelectionIterator(this); - - if (m_actives.size() == 1) { - boolean isFound = false; - - // loop the iterator in reverse order of drawing - while (i.hasNext()) { - GlyphObject object = (GlyphObject) i.next(); - - if (m_actives.isSelected(object)) { - isFound = true; - continue; - } // if - - if (!isFound) { - continue; - } // if - - m_actives.unselectAll(); - m_actives.addActive(object); - return; - } // while i - } // if - - i = new SelectionIterator(this); - if (i.hasNext()) { - GlyphObject object = (GlyphObject) i.next(); - m_actives.unselectAll(); - m_actives.addActive(object); - return; - } // if - } - - // -------------------------------------------------------------- - - public boolean hit(Rectangle2D a_rect, - boolean a_isAppend, - boolean a_single) { - - - if (!a_isAppend) { - m_actives.unselectAll(); - } // if - - return hitObjects(a_rect, a_single); - } - - // -------------------------------------------------------------- - - private boolean hitObjects(Rectangle2D a_rect, boolean a_isSelectOnlyOne) { - boolean retval = false; - - Iterator i = new SelectionIterator(this); - - // loop the iterator in reverse order of drawing - while (i.hasNext()) { - GlyphObject object = (GlyphObject) i.next(); - - if (!object.hit(a_rect, new AffineTransform())) { - continue; - } // if - - retval = true; - - if (a_isSelectOnlyOne) { - return true; - } // if - } // while i - - return retval; - } - - // -------------------------------------------------------------- - - /** used for cut and paste. - */ - public void addObjectFromClipboard(String a_value) throws CircularIncludeException { - Reader reader = new StringReader(a_value); - Document document = null; - try { - document = UJAXP.getDocument(reader); - } catch (Exception e) { - e.printStackTrace(); - return; - } // try-catch - - Element root = document.getDocumentElement(); - if (!root.getNodeName().equals("clipboard")) { - return; - } // if - - Node child; - for (child = root.getFirstChild(); child != null; - child = child.getNextSibling()) { - if (!(child instanceof Element)) { - continue; - } // if - Element element = (Element) child; - - IGlyphFactory factory = GlyphFactory.getFactory(); - - - if (XModule.isMatch(element)) { - EModuleInvoke module = (EModuleInvoke) factory.createXModule(element); - addModule(module); - continue; - } // if - - if (XContour.isMatch(element)) { - EContour contour = (EContour) factory.createXContour(element); - addContour(contour); - continue; - } // if - - if (XInclude.isMatch(element)) { - EIncludeInvoke include = (EIncludeInvoke) factory.createXInclude(element); - addInclude(include); - continue; - } // if - } // while - } - - // -------------------------------------------------------------- - - private boolean isCircularInclude(String a_href) { - if (getShortFileName().equals(a_href)) { - return true; - } // if - - ModuleManager manager = ModuleManager.getSingletonInstance(); - XInclude [] includes = m_glyph.getBody().getInclude(); - int i; - for (i = 0; i < includes.length; i++) { - XInclude include = includes[i]; - GlyphFile child = manager.getGlyphFile(include.getHref()); - - if (child.isCircularInclude(a_href)) { - return true; - } // if - } // for i - - return false; - } - - // -------------------------------------------------------------- - - public void addInclude(String a_fileName) throws CircularIncludeException { - addInclude(EIncludeInvoke.create(a_fileName)); - } - - public void addInclude(XInclude a_include) throws CircularIncludeException { - if (isCircularInclude(a_include.getHref())) { - throw new CircularIncludeException(); - } // if - - m_actives.unselectAll(); - m_actives.addActive((GlyphObject) a_include); - m_glyph.getBody().addInclude(a_include); - m_history.record("addInclude"); - } - - // -------------------------------------------------------------- - - /** - * pasted module from clipboard or ModuleInvokeAction - * @param a_module - */ - public XModule addModule(EModuleInvoke a_module) { - m_actives.unselectAll(); - m_actives.addActive(a_module); - m_glyph.getBody().addModule(a_module); - m_history.record("addModule"); - - return a_module; - } - - // -------------------------------------------------------------- - - /** add contour from clipboard or ContourAction - * @param a_contour - */ - public void addContour(EContour a_contour) { - m_actives.unselectAll(); - m_actives.addActive(a_contour); - m_glyph.getBody().addContour(a_contour); - m_history.record("addContour"); - } - - - // -------------------------------------------------------------- - - - public void addPoint() { - if (!m_actives.hasActivePoint()) { - return; - } // if - - EContourPoint contourPoint = m_actives.getActivePoint(); - m_actives.unselectAll(); - m_actives.addActive(contourPoint.add()); - - m_history.record("addPoint"); - } - - // -------------------------------------------------------------- - - public void addHint(int a_ppem) { - if (!m_actives.hasActivePoint()) - return; - - EContourPoint contourPoint = m_actives.getActivePoint(); - m_actives.unselectAll(); - m_actives.addActive(contourPoint.addHint(a_ppem)); - - if (contourPoint.hasControlPoint1()) { - EContourPoint p = (EContourPoint) contourPoint.getControlPoint1().getContourPoint(); - m_actives.addActive(p.addHint(a_ppem)); - } // if - - if (contourPoint.hasControlPoint2()) { - EContourPoint p = (EContourPoint) contourPoint.getControlPoint2().getContourPoint(); - m_actives.addActive(p.addHint(a_ppem)); - } // if - - - m_history.record("addHint"); - } - - public void toggleRounded() { - if (!m_actives.hasActivePoint()) - return; - - EContourPoint contourPoint = m_actives.getActivePoint(); - contourPoint.toggleRounded(); - - m_history.record("toggleGridfit"); - } - - public void convertControlPoint() { - if (!m_actives.hasActiveControlPoint()) { - return; - } // if - - EControlPoint controlPoint = m_actives.getActiveControlPoint(); - controlPoint.convert(); - - m_history.record("convertControlPoint"); - } - - public void convertContour() { - if (!m_actives.hasActiveContour()) { - return; - } // if - - EContour contour = m_actives.getActiveContour(); - contour.convert(); - - m_history.record("convertContour"); - } - - // -------------------------------------------------------------- - - public void remove() { - if (!m_actives.hasSelected()) - return; - - int i; - for (i = 0; i < m_actives.size(); i++) { - GlyphObject active = m_actives.get(i); - active.remove(); - } // for i - - m_history.record("remove"); - - m_actives.unselectAll(); - } - - // -------------------------------------------------------------- - - public void toggleOnOff() { - if (!m_actives.hasSelected()) - return; - - int i; - for (i = 0; i < m_actives.size(); i++) { - GlyphObject active = m_actives.get(i); - if (!(active instanceof EContourPoint)) { - continue; - } // if - - EContourPoint point = (EContourPoint) active; - point.toggleOnCurve(); - } // for i - - m_history.record("toggleOnOff"); - } - - private void loadVar() { - XParamListParam [] params = m_glyph.getHead().getHeadGlobal().getParamListParam(); - int i; - for (i = 0; i < params.length; i++) { - XParamListParam param = params[i]; - addVar(param.getName(), param.getContent()); - } // for i - } - - public String getSelectedNodeName() { - return m_selectedNodeName; - } - - public void addFileVar() { - XParamListParam param = new XParamListParam(); - param.setName("New parameter"); - param.setContent(0.0); - m_history.record("addFileVar"); - - m_glyph.getHead().getHeadGlobal().addParamListParam(param); - } - - public void removeFileVar(int a_index) { - m_history.record("removeFileVar"); - - m_glyph.getHead().getHeadGlobal().removeParamListParam(a_index); - } - - - public void addInvokeArg(int a_type) { - - } - - public void removeInvokeArg(int a_type, int a_index) { - - } - - public String getIncludeName() { - String retval = ""; - - if (!m_actives.hasActiveInclude()) - return retval; - retval = m_actives.getActiveInclude().getHref(); - - return retval; - } - - public void setIncludeName(String a_name) { - if (!m_actives.hasActiveInclude()) - return; - - m_actives.getActiveInclude().setHref(a_name); - m_history.record("setIncludeName"); - } - - public void setGlyphTitle(String a_title) { - m_glyph.getHead().setTitle(a_title); - m_history.record("setGlyphTitle"); - } - - public String getGlyphTitle() { - String retval = ""; - - retval = m_glyph.getHead().getTitle(); - - if (retval.equals("empty")) { - retval = ""; - } // if - - return retval; - } - - public String getModuleName() { - String retval = ""; - - if (!m_actives.hasActiveModule()) - return retval; - retval = m_actives.getActiveModule().getName(); - - return retval; - } - - public String getUnicode() { - return getUnicodeAsString(); - } - - public String getUnicodeAsString() { - return m_glyph.getHead().getUnicode(); - } - - public long getUnicodeAsLong() { - long retval = -1; - - String s = getUnicodeAsString(); - if (s.equals("")) { - return retval; - } // if - - try { - retval = Long.parseLong(s, 16); - } catch (NumberFormatException e) { - retval = -1; - } // try-catch - - return retval; - } - - protected void setUnicode(String a_unicode) { - m_glyph.getHead().setUnicode(a_unicode); - m_history.record("setUnicode"); - } - - public boolean isSimple() { - return (m_glyph.getBody().getInclude().length == 0); - } - - public boolean isWhiteSpace() { - long unicode = getUnicodeAsLong(); - - if (unicode == 0x0020 - || unicode == 0x00a0 - || unicode == 0x200b - || unicode == 0x2060 - || unicode == 0x3000 - || unicode == 0xfeff) - { - return true; - } // if - - return false; - } - - public String getShortFileName() { - return m_fileName.getName(); - } - - public void setLicense(String a_value) { - m_glyph.getHead().setLicense(a_value); - m_history.record("setLicense"); - } - - public String getLicense() { - return m_glyph.getHead().getLicense(); - } - - public PointAggregate getPointHost() { - return m_pointHost; - } - - public void buildPointHost() { - m_pointHost = null; - - /*if (!GlyphAction.isPointVisible()) { - return; - } // if*/ - - int i; - for (i = 0; i < m_actives.size(); i++) { - GlyphObject object = (GlyphObject) m_actives.get(i); - PointAggregate host = null; - - if (object instanceof EContourPoint) { - EContourPoint point = (EContourPoint) object; - host = point.getParent(); - - if (host instanceof EContourPoint) { - EContourPoint hostPoint = (EContourPoint) host; - host = hostPoint.getParent(); - } // if - } else if (object instanceof EHint) { - EHint hint = (EHint) object; - host = hint.getPointHost(); - } // if-else - - if (host == null) { - continue; - } // if - - if (m_pointHost == null) { - m_pointHost = host; - } else { - if (m_pointHost != host) { - m_pointHost = null; - return; - } // if - } // if - } // while - } - - public boolean isRequiredGlyph() { - long unicode = getUnicodeAsLong(); - - return (unicode == TTUnicodeRange.k_notDef - || unicode == TTUnicodeRange.k_null - || unicode == TTUnicodeRange.k_cr - || unicode == TTUnicodeRange.k_space); - } - - public TTGlyph toSimpleGlyph() { - // convert the file into array of contours - XContour [] contours = toContours(); - if ((contours == null) && (!isRequiredGlyph())) { - return null; - } // if - - TTGlyph retval = new TTGlyph(); - retval.setSimple(true); - retval.setAdvanceWidth(getAdvanceWidth()); - - if (contours == null) { - return retval; - } // if - - ArrayList points = new ArrayList<>(); - for (int i = 0; i < contours.length; i++) { - XContour contour = contours[i]; - XContourPoint [] contourPoints = contour.getContourPoint(); - for (int j = 0; j < contourPoints.length; j++) { - points.add((EContourPoint) contourPoints[j]); - } // for j - retval.addEndPoint(points.size() - 1); - } // for i - - for (EContourPoint point: points) { - loadContourPoint(retval, point); - } // for point - - boolean hasGridfit = false; - // I need int i here. - for (int i = 0; i < points.size(); i++) { - EContourPoint point = points.get(i); - - if (!point.isRounded()) { - continue; - } // if - - hasGridfit = true; - loadGridfit(retval, point, i); - } // for i - - if (hasGridfit) { - retval.addInstruction(TTGlyph.IUP1); - retval.addInstruction(TTGlyph.IUP0); - } // if - - // I need int i here. - for (int i = 0; i < points.size(); i++) { - EContourPoint point = points.get(i); - if (point.getHint().length == 0) { - continue; - } // if - - loadHint(retval, point, i); - } // for i - - return retval; - } - - private void loadContourPoint(TTGlyph a_glyph, EContourPoint a_point) { - double x = a_point.getX(); - double y = a_point.getY(); - Point p = new Point((int) x, (int) y); - int flag = 0; - if (a_point.isOn()) { - flag = TTGlyph.k_onCurve; - } // if - - a_glyph.addPoint(p); - a_glyph.addFlag(flag); - } - - private void loadGridfit(TTGlyph a_glyph, EContourPoint a_point, int a_index) { - if (!a_point.isRounded()) { - return; - } // if - - a_glyph.addInstruction(TTGlyph.PUSHB000); - a_glyph.addInstruction(a_index); - a_glyph.addInstruction(TTGlyph.MDAP1); - } - - - private void loadHint(TTGlyph a_glyph, EContourPoint a_point, int a_index) { - double x = a_point.getX(); - double y = a_point.getY(); - - XHint [] hints = a_point.getHint(); - - for (int i = 0; i < hints.length; i++) { - EHint hint = (EHint) hints[i]; - double xHint = hint.getX(); - double yHint = hint.getY(); - - if (x == xHint && y == yHint) { - continue; - } // if - - double xDelta = xHint - x; - double yDelta = yHint - y; - int instruction = TTGlyph.DELTAP1; - double deltaStep = ((double) Engine.getEm()) / hint.getPpem() / 8; - int xShift = (int) Math.round(xDelta / deltaStep); - int yShift = (int) Math.round(yDelta / deltaStep); - - if (xShift == 0 && yShift == 0) { - continue; - } // if - - a_glyph.addInstruction(TTGlyph.PUSHB000); - a_glyph.addInstruction((int) hint.getPpem()); - a_glyph.addInstruction(TTGlyph.SDB); - - if (xShift != 0) { - a_glyph.addInstruction(TTGlyph.SVTCA1); - a_glyph.addInstruction(TTGlyph.PUSHB010); - a_glyph.addInstruction(TTGlyph.toDeltaArg(0, xShift)); - a_glyph.addInstruction(a_index); - a_glyph.addInstruction(1); - a_glyph.addInstruction(TTGlyph.DELTAP1); - } // if - - if (yShift != 0) { - a_glyph.addInstruction(TTGlyph.SVTCA0); - a_glyph.addInstruction(TTGlyph.PUSHB010); - a_glyph.addInstruction(TTGlyph.toDeltaArg(0, yShift)); - a_glyph.addInstruction(a_index); - a_glyph.addInstruction(1); - a_glyph.addInstruction(TTGlyph.DELTAP1); - } // if - } // for i - } - - public class CircularIncludeException extends Exception {} + /* + * $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 org.doubletype.ossa.*; +import org.doubletype.ossa.xml.*; +import org.doubletype.ossa.adapter.*; +import org.doubletype.ossa.truetype.*; + +import java.io.*; +import java.awt.*; +import java.awt.geom.*; + +import javax.xml.transform.*; +import javax.xml.transform.stream.*; +import javax.xml.transform.dom.*; +import java.net.*; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.*; +import javax.xml.parsers.*; +import org.xml.sax.*; + +/** + * @author e.e + */ +public class GlyphFile extends GlyphModule { + protected static String s_emptyFileName = "empty.glyph"; + private static final String k_dotGlyph = ".glyph"; + private static Transformer s_transformer = null; + + static { + GlyphFactory.setFactory(EGlyphFactory.getFactory()); + } + + public static File createFileName(File a_dir, String a_name) { + return new File(a_dir, a_name + k_dotGlyph); + } + + /** + * http://www.atmarkit.co.jp/fxml/rensai2/xmltool04/02.html + * @return + */ + private static Transformer getTransformer() { + if (s_transformer == null) { + TransformerFactory transFactory + = TransformerFactory.newInstance(); + try { + s_transformer = transFactory.newTransformer(); + } + catch (TransformerConfigurationException e) { + } + + s_transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + s_transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + } // if + + return s_transformer; + } + + private static XStartGlyphElement loadGlyphElement(URL a_url) { + IGlyphFactory factory = GlyphFactory.getFactory(); + + XStartGlyphElement retval = null; + + try { + retval = factory.createXStartGlyphElement(a_url); + } catch( SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + } + + return retval; + } + + private static XStartGlyphElement loadGlyphElement(InputStream a_in) { + IGlyphFactory factory = GlyphFactory.getFactory(); + XStartGlyphElement retval = null; + + try { + retval = factory.createXStartGlyphElement(a_in); + } catch( SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + } + + return retval; + } + // -------------------------------------------------------------- + + protected XStartGlyphElement m_glyph; + protected File m_fileName; + protected long m_modifiedTime = 0; + protected long m_savedTime = 0; + + private final int k_halfWidth = 512; + private final int k_fullWidth = 1024; + private String m_selectedNodeName = ""; + private boolean m_isMoving = false; + private PointAggregate m_pointHost; + + + // -------------------------------------------------------------- + + /** + * creates new file + */ + public GlyphFile(File a_dir, String a_name, long a_unicode) { + super(); + + m_fileName = createFileName(a_dir, a_name); + + init(getClass().getResource(s_emptyFileName)); + setGlyphTitle(a_name); + setUnicode(Long.toHexString(a_unicode)); + + /*int eastAsianWidth = UCharacter.getIntPropertyValue( + (int) a_unicode, + 0x1004); //UProperty.EAST_ASIAN_WIDTH); + */ + int eastAsianWidth = 5; //?? + if (eastAsianWidth == 5 || eastAsianWidth == 1) { + setAdvanceWidth(k_fullWidth); + } // if + } + + /** + * creates new file + * @param a_dir parent dir + * @param a_name glyph name + */ + public GlyphFile(File a_dir, String a_name) throws FileNotFoundException { + super(); + init(getClass().getResource(s_emptyFileName)); + setGlyphTitle(a_name); + m_fileName = createFileName(a_dir, a_name); + } + + /** + * opens existing file + * @param a_file + */ + public GlyphFile(File a_file) { + super(); + + m_fileName = a_file; + + URL url = null; + + try { + url = a_file.toURI().toURL(); + } catch(MalformedURLException e) { + e.printStackTrace(); + } + + init(url); + } + + protected GlyphFile(URL a_url) { + super(); + init(a_url); + } + + private void init(URL a_url) { + m_glyph = loadGlyphElement(a_url); + ModuleManager.getSingletonInstance().clear(); + + //m_display = Engine.getSingletonInstance().getDisplay(); + + m_savedTime = m_modifiedTime; + } + + /** + * initialize .notdef + */ + public void initNotDef(int a_advanceWidth) { + setAdvanceWidth(a_advanceWidth); + + EContour contour = new EContour(); + contour.setType(EContour.k_cubic); + contour.addContourPoint(new EContourPoint(0.0, 0.0, true)); + contour.addContourPoint(new EContourPoint(438.0, 0.0, true)); + contour.addContourPoint(new EContourPoint(438.0, 683.0, true)); + contour.addContourPoint(new EContourPoint(0.0, 683.0, true)); + addContour(contour); + + contour = new EContour(); + contour.setType(EContour.k_cubic); + contour.addContourPoint(new EContourPoint(365.0, 73.0, true)); + contour.addContourPoint(new EContourPoint(73.0, 73.0, true)); + contour.addContourPoint(new EContourPoint(73.0, 610.0, true)); + contour.addContourPoint(new EContourPoint(365.0, 610.0, true)); + addContour(contour); + } + + public void initNullGlyph() { + setAdvanceWidth(0); + } + + public void initSpace(int a_advanceWidth) { + setAdvanceWidth(a_advanceWidth); + } + + public void beforePush() { + loadVar(); + } + + public void restore(Memento a_memento) { + m_glyph = loadGlyphElement(a_memento.getData()); + ModuleManager.getSingletonInstance().clear(); + } + + // -------------------------------------------------------------- + + public XStartGlyphElement getGlyph() { + return m_glyph; + } + + // -------------------------------------------------------------- + + public void saveGlyphFile(OutputStream a_output) { + try { + Transformer transformer = getTransformer(); + Document document = m_glyph.makeDocument(); + DOMSource source = new DOMSource(document); + StreamResult result = new StreamResult(a_output); + transformer.transform(source, result); + } catch (ParserConfigurationException | TransformerException e) { + e.printStackTrace(); + } + + m_savedTime = System.currentTimeMillis(); + m_modifiedTime = m_savedTime; + } + + public Memento createMemento(String a_description) { + m_modifiedTime = System.currentTimeMillis(); + byte [] bytes = m_glyph.makeTextDocument().getBytes(); + return new Memento(a_description, bytes); + } + + public boolean hasUnsavedChange() { + return (m_savedTime != m_modifiedTime); + } + + public void setAuthor(String a_value) { + m_glyph.getHead().setAuthor(a_value); + } + + public String getAuthor() { + return m_glyph.getHead().getAuthor(); + } + + public void setCopyrightYear(String a_value) { + m_glyph.getHead().setCopyright(a_value); + } + + public String getCopyrightYear() { + return m_glyph.getHead().getCopyright(); + } + + public void setAdvanceWidth(int a_width) { + m_glyph.getHead().setAdvanceWidth(a_width); + } + + public int getAdvanceWidth() { + if (!m_glyph.getHead().checkAdvanceWidth()) { + setAdvanceWidth(k_halfWidth); + } // if + + return (int) m_glyph.getHead().getAdvanceWidth(); + } + + public Iterator createIterator() { + return new GlyphIterator(this); + } + + + // -------------------------------------------------------------- + + public void display(Graphics2D g, AffineTransform a_trans) + { + Iterator i = createIterator(); + while (i.hasNext()) { + GlyphObject object = (GlyphObject) i.next(); + object.display(g, a_trans); + } // while + } + + // -------------------------------------------------------------- + + public static final int k_defaultPixelSize = 16; //PPM + + /** converts this glyph into Shape. + * It could be called for root's preview mode or by include invoke. + * Pushing either this GlyphFile or DIncludeInvoke should be + * handled before this. + */ + public Shape toShape(AffineTransform a_trans) { + + + int ppem = k_defaultPixelSize; + + GeneralPath retval = new GeneralPath(); + Iterator i = createIterator(); + while (i.hasNext()) { + GlyphObject object = (GlyphObject) i.next(); + + if (object instanceof EContourPoint + || object instanceof EHint) { + continue; + } // if + + retval.append(object.toShape(a_trans, ppem), false); + } // if + + return retval; + } + + // -------------------------------------------------------------- + + /** + * Generates array of XContour from local contours and modules. + * Used for TTF building. + */ + private XContour [] toContours() { + XContour [] retval; + ArrayList list = new ArrayList<>(); + XContour [] contours = m_glyph.getBody().getContour(); + for (int i = 0; i < contours.length; i++) { + EContour contour = (EContour) contours[i]; + list.add(contour.toQuadratic()); + } // for i + + XModule [] modules = m_glyph.getBody().getModule(); + for (int i = 0; i < modules.length; i++) { + EModuleInvoke module = (EModuleInvoke) modules[i]; + + // push and pop happens inside toContour + list.add(module.toContour(new AffineTransform())); + } // for i + + if (list.size() == 0) + return null; + + retval = new XContour[list.size()]; + for (int i = 0; i < list.size(); i++) { + retval[i] = list.get(i); + } // for i + + return retval; + } + + // -------------------------------------------------------------- + + public boolean isMoving() { + return m_isMoving; + } + + // -------------------------------------------------------------- + + public void beginMove() { + m_isMoving = true; + } + + // -------------------------------------------------------------- + + public void endMove() { + if (!m_isMoving) { + return; + } // if + + m_isMoving = false; + } + + // -------------------------------------------------------------- + + public void move(Point2D a_delta) { + int i; + for (i = 0; i < m_actives.size(); i++) { + GlyphObject active = m_actives.get(i); + active.move(a_delta); + } // for i + } + + + // -------------------------------------------------------------- + + public void fitMove() { + if (m_actives.hasActiveControlPoint()) { + EControlPoint controlPoint = m_actives.getActiveControlPoint(); + controlPoint.rotateTo45(); + } // if + } + + // -------------------------------------------------------------- + + public boolean isHittingSelected(Point2D a_point) { + ActiveList oldActiveObjects = new ActiveList(); + oldActiveObjects.setActives(m_actives); + + m_actives.unselectAll(); + hit(a_point, false, false); + + int i, j; + for (i = 0; i < m_actives.size(); i++) { + GlyphObject selected = m_actives.get(i); + for (j = 0; j < oldActiveObjects.size(); j++) { + GlyphObject old = oldActiveObjects.get(j); + + if (selected == old) { + m_actives.setActives(oldActiveObjects); + return true; + } // if + } // for j + } // for i + + m_actives.setActives(oldActiveObjects); + + return false; + } + + // -------------------------------------------------------------- + + /** hits a point to add them in active object queue. + * @param a_single when it is true, the method returns after + * finding the first hit. + */ + public boolean hit(Point2D a_point, + boolean a_isAppend, + boolean a_isSelectOnlyOne) { + if (a_point == null) { + return false; + } // if + + Rectangle2D rect = new Rectangle2D.Double( + a_point.getX(), a_point.getY(), + 1.0, 1.0); + return hit(rect, a_isAppend, a_isSelectOnlyOne); + } + + public void selectNext() { + Iterator i = new SelectionIterator(this); + + if (m_actives.size() == 1) { + boolean isFound = false; + + // loop the iterator in reverse order of drawing + while (i.hasNext()) { + GlyphObject object = (GlyphObject) i.next(); + + if (m_actives.isSelected(object)) { + isFound = true; + continue; + } // if + + if (!isFound) { + continue; + } // if + + m_actives.unselectAll(); + m_actives.addActive(object); + return; + } // while i + } // if + + i = new SelectionIterator(this); + if (i.hasNext()) { + GlyphObject object = (GlyphObject) i.next(); + m_actives.unselectAll(); + m_actives.addActive(object); + return; + } // if + } + + // -------------------------------------------------------------- + + public boolean hit(Rectangle2D a_rect, + boolean a_isAppend, + boolean a_single) { + + + if (!a_isAppend) { + m_actives.unselectAll(); + } // if + + return hitObjects(a_rect, a_single); + } + + // -------------------------------------------------------------- + + private boolean hitObjects(Rectangle2D a_rect, boolean a_isSelectOnlyOne) { + boolean retval = false; + + Iterator i = new SelectionIterator(this); + + // loop the iterator in reverse order of drawing + while (i.hasNext()) { + GlyphObject object = (GlyphObject) i.next(); + + if (!object.hit(a_rect, new AffineTransform())) { + continue; + } // if + + retval = true; + + if (a_isSelectOnlyOne) { + return true; + } // if + } // while i + + return retval; + } + + // -------------------------------------------------------------- + + /** used for cut and paste. + */ + public void addObjectFromClipboard(String a_value) throws CircularIncludeException { + Reader reader = new StringReader(a_value); + Document document = null; + try { + document = UJAXP.getDocument(reader); + } catch (Exception e) { + e.printStackTrace(); + return; + } // try-catch + + Element root = document.getDocumentElement(); + if (!root.getNodeName().equals("clipboard")) { + return; + } // if + + Node child; + for (child = root.getFirstChild(); child != null; + child = child.getNextSibling()) { + if (!(child instanceof Element)) { + continue; + } // if + Element element = (Element) child; + + IGlyphFactory factory = GlyphFactory.getFactory(); + + + if (XModule.isMatch(element)) { + EModuleInvoke module = (EModuleInvoke) factory.createXModule(element); + addModule(module); + continue; + } // if + + if (XContour.isMatch(element)) { + EContour contour = (EContour) factory.createXContour(element); + addContour(contour); + continue; + } // if + + if (XInclude.isMatch(element)) { + EIncludeInvoke include = (EIncludeInvoke) factory.createXInclude(element); + addInclude(include); + continue; + } // if + } // while + } + + // -------------------------------------------------------------- + + private boolean isCircularInclude(String a_href) { + if (getShortFileName().equals(a_href)) { + return true; + } // if + + ModuleManager manager = ModuleManager.getSingletonInstance(); + XInclude [] includes = m_glyph.getBody().getInclude(); + int i; + for (i = 0; i < includes.length; i++) { + XInclude include = includes[i]; + GlyphFile child = manager.getGlyphFile(include.getHref()); + + if (child.isCircularInclude(a_href)) { + return true; + } // if + } // for i + + return false; + } + + // -------------------------------------------------------------- + + public void addInclude(String a_fileName) throws CircularIncludeException { + addInclude(EIncludeInvoke.create(a_fileName)); + } + + public void addInclude(XInclude a_include) throws CircularIncludeException { + if (isCircularInclude(a_include.getHref())) { + throw new CircularIncludeException(); + } // if + + m_actives.unselectAll(); + m_actives.addActive((GlyphObject) a_include); + m_glyph.getBody().addInclude(a_include); + } + + // -------------------------------------------------------------- + + /** + * pasted module from clipboard or ModuleInvokeAction + * @param a_module + */ + public XModule addModule(EModuleInvoke a_module) { + m_actives.unselectAll(); + m_actives.addActive(a_module); + m_glyph.getBody().addModule(a_module); + + return a_module; + } + + // -------------------------------------------------------------- + + /** add contour from clipboard or ContourAction + * @param a_contour + */ + public void addContour(EContour a_contour) { + m_actives.unselectAll(); + m_actives.addActive(a_contour); + m_glyph.getBody().addContour(a_contour); + } + + + // -------------------------------------------------------------- + + + public void addPoint() { + if (!m_actives.hasActivePoint()) { + return; + } // if + + EContourPoint contourPoint = m_actives.getActivePoint(); + m_actives.unselectAll(); + m_actives.addActive(contourPoint.add()); + } + + // -------------------------------------------------------------- + + public void addHint(int a_ppem) { + if (!m_actives.hasActivePoint()) + return; + + EContourPoint contourPoint = m_actives.getActivePoint(); + m_actives.unselectAll(); + m_actives.addActive(contourPoint.addHint(a_ppem)); + + if (contourPoint.hasControlPoint1()) { + EContourPoint p = (EContourPoint) contourPoint.getControlPoint1().getContourPoint(); + m_actives.addActive(p.addHint(a_ppem)); + } // if + + if (contourPoint.hasControlPoint2()) { + EContourPoint p = (EContourPoint) contourPoint.getControlPoint2().getContourPoint(); + m_actives.addActive(p.addHint(a_ppem)); + } // if + } + + public void toggleRounded() { + if (!m_actives.hasActivePoint()) + return; + + EContourPoint contourPoint = m_actives.getActivePoint(); + contourPoint.toggleRounded(); + } + + public void convertControlPoint() { + if (!m_actives.hasActiveControlPoint()) { + return; + } // if + + EControlPoint controlPoint = m_actives.getActiveControlPoint(); + controlPoint.convert(); + } + + public void convertContour() { + if (!m_actives.hasActiveContour()) { + return; + } // if + + EContour contour = m_actives.getActiveContour(); + contour.convert(); + } + + // -------------------------------------------------------------- + + public void remove() { + if (!m_actives.hasSelected()) + return; + + int i; + for (i = 0; i < m_actives.size(); i++) { + GlyphObject active = m_actives.get(i); + active.remove(); + } // for i + + m_actives.unselectAll(); + } + + // -------------------------------------------------------------- + + public void toggleOnOff() { + if (!m_actives.hasSelected()) + return; + + int i; + for (i = 0; i < m_actives.size(); i++) { + GlyphObject active = m_actives.get(i); + if (!(active instanceof EContourPoint)) { + continue; + } // if + + EContourPoint point = (EContourPoint) active; + point.toggleOnCurve(); + } // for i + } + + private void loadVar() { + XParamListParam [] params = m_glyph.getHead().getHeadGlobal().getParamListParam(); + int i; + for (i = 0; i < params.length; i++) { + XParamListParam param = params[i]; + addVar(param.getName(), param.getContent()); + } // for i + } + + public String getSelectedNodeName() { + return m_selectedNodeName; + } + + public void addFileVar() { + XParamListParam param = new XParamListParam(); + param.setName("New parameter"); + param.setContent(0.0); + + m_glyph.getHead().getHeadGlobal().addParamListParam(param); + } + + public void removeFileVar(int a_index) { + m_glyph.getHead().getHeadGlobal().removeParamListParam(a_index); + } + + + public void addInvokeArg(int a_type) { + + } + + public void removeInvokeArg(int a_type, int a_index) { + + } + + public String getIncludeName() { + String retval = ""; + + if (!m_actives.hasActiveInclude()) + return retval; + retval = m_actives.getActiveInclude().getHref(); + + return retval; + } + + public void setIncludeName(String a_name) { + if (!m_actives.hasActiveInclude()) + return; + + m_actives.getActiveInclude().setHref(a_name); + } + + public void setGlyphTitle(String a_title) { + m_glyph.getHead().setTitle(a_title); + } + + public String getGlyphTitle() { + String retval = ""; + + retval = m_glyph.getHead().getTitle(); + + if (retval.equals("empty")) { + retval = ""; + } // if + + return retval; + } + + public String getModuleName() { + String retval = ""; + + if (!m_actives.hasActiveModule()) + return retval; + retval = m_actives.getActiveModule().getName(); + + return retval; + } + + public String getUnicode() { + return getUnicodeAsString(); + } + + public String getUnicodeAsString() { + return m_glyph.getHead().getUnicode(); + } + + public long getUnicodeAsLong() { + long retval = -1; + + String s = getUnicodeAsString(); + if (s.equals("")) { + return retval; + } // if + + try { + retval = Long.parseLong(s, 16); + } catch (NumberFormatException e) { + retval = -1; + } // try-catch + + return retval; + } + + protected void setUnicode(String a_unicode) { + m_glyph.getHead().setUnicode(a_unicode); + } + + public boolean isSimple() { + return (m_glyph.getBody().getInclude().length == 0); + } + + public boolean isWhiteSpace() { + long unicode = getUnicodeAsLong(); + + if (unicode == 0x0020 + || unicode == 0x00a0 + || unicode == 0x200b + || unicode == 0x2060 + || unicode == 0x3000 + || unicode == 0xfeff) + { + return true; + } // if + + return false; + } + + public String getShortFileName() { + return m_fileName.getName(); + } + + public void setLicense(String a_value) { + m_glyph.getHead().setLicense(a_value); + } + + public String getLicense() { + return m_glyph.getHead().getLicense(); + } + + public PointAggregate getPointHost() { + return m_pointHost; + } + + public void buildPointHost() { + m_pointHost = null; + + /*if (!GlyphAction.isPointVisible()) { + return; + } // if*/ + + int i; + for (i = 0; i < m_actives.size(); i++) { + GlyphObject object = (GlyphObject) m_actives.get(i); + PointAggregate host = null; + + if (object instanceof EContourPoint) { + EContourPoint point = (EContourPoint) object; + host = point.getParent(); + + if (host instanceof EContourPoint) { + EContourPoint hostPoint = (EContourPoint) host; + host = hostPoint.getParent(); + } // if + } else if (object instanceof EHint) { + EHint hint = (EHint) object; + host = hint.getPointHost(); + } // if-else + + if (host == null) { + continue; + } // if + + if (m_pointHost == null) { + m_pointHost = host; + } else { + if (m_pointHost != host) { + m_pointHost = null; + return; + } // if + } // if + } // while + } + + public boolean isRequiredGlyph() { + long unicode = getUnicodeAsLong(); + + return (unicode == TTUnicodeRange.k_notDef + || unicode == TTUnicodeRange.k_null + || unicode == TTUnicodeRange.k_cr + || unicode == TTUnicodeRange.k_space); + } + + public TTGlyph toSimpleGlyph() { + // convert the file into array of contours + XContour [] contours = toContours(); + if ((contours == null) && (!isRequiredGlyph())) { + return null; + } // if + + TTGlyph retval = new TTGlyph(); + retval.setSimple(true); + retval.setAdvanceWidth(getAdvanceWidth()); + + if (contours == null) { + return retval; + } // if + + ArrayList points = new ArrayList<>(); + for (int i = 0; i < contours.length; i++) { + XContour contour = contours[i]; + XContourPoint [] contourPoints = contour.getContourPoint(); + for (int j = 0; j < contourPoints.length; j++) { + points.add((EContourPoint) contourPoints[j]); + } // for j + retval.addEndPoint(points.size() - 1); + } // for i + + for (EContourPoint point: points) { + loadContourPoint(retval, point); + } // for point + + boolean hasGridfit = false; + // I need int i here. + for (int i = 0; i < points.size(); i++) { + EContourPoint point = points.get(i); + + if (!point.isRounded()) { + continue; + } // if + + hasGridfit = true; + loadGridfit(retval, point, i); + } // for i + + if (hasGridfit) { + retval.addInstruction(TTGlyph.IUP1); + retval.addInstruction(TTGlyph.IUP0); + } // if + + // I need int i here. + for (int i = 0; i < points.size(); i++) { + EContourPoint point = points.get(i); + if (point.getHint().length == 0) { + continue; + } // if + + loadHint(retval, point, i); + } // for i + + return retval; + } + + private void loadContourPoint(TTGlyph a_glyph, EContourPoint a_point) { + double x = a_point.getX(); + double y = a_point.getY(); + Point p = new Point((int) x, (int) y); + int flag = 0; + if (a_point.isOn()) { + flag = TTGlyph.k_onCurve; + } // if + + a_glyph.addPoint(p); + a_glyph.addFlag(flag); + } + + private void loadGridfit(TTGlyph a_glyph, EContourPoint a_point, int a_index) { + if (!a_point.isRounded()) { + return; + } // if + + a_glyph.addInstruction(TTGlyph.PUSHB000); + a_glyph.addInstruction(a_index); + a_glyph.addInstruction(TTGlyph.MDAP1); + } + + + private void loadHint(TTGlyph a_glyph, EContourPoint a_point, int a_index) { + double x = a_point.getX(); + double y = a_point.getY(); + + XHint [] hints = a_point.getHint(); + + for (int i = 0; i < hints.length; i++) { + EHint hint = (EHint) hints[i]; + double xHint = hint.getX(); + double yHint = hint.getY(); + + if (x == xHint && y == yHint) { + continue; + } // if + + double xDelta = xHint - x; + double yDelta = yHint - y; + int instruction = TTGlyph.DELTAP1; + double deltaStep = ((double) Engine.getEm()) / hint.getPpem() / 8; + int xShift = (int) Math.round(xDelta / deltaStep); + int yShift = (int) Math.round(yDelta / deltaStep); + + if (xShift == 0 && yShift == 0) { + continue; + } // if + + a_glyph.addInstruction(TTGlyph.PUSHB000); + a_glyph.addInstruction((int) hint.getPpem()); + a_glyph.addInstruction(TTGlyph.SDB); + + if (xShift != 0) { + a_glyph.addInstruction(TTGlyph.SVTCA1); + a_glyph.addInstruction(TTGlyph.PUSHB010); + a_glyph.addInstruction(TTGlyph.toDeltaArg(0, xShift)); + a_glyph.addInstruction(a_index); + a_glyph.addInstruction(1); + a_glyph.addInstruction(TTGlyph.DELTAP1); + } // if + + if (yShift != 0) { + a_glyph.addInstruction(TTGlyph.SVTCA0); + a_glyph.addInstruction(TTGlyph.PUSHB010); + a_glyph.addInstruction(TTGlyph.toDeltaArg(0, yShift)); + a_glyph.addInstruction(a_index); + a_glyph.addInstruction(1); + a_glyph.addInstruction(TTGlyph.DELTAP1); + } // if + } // for i + } + + public class CircularIncludeException extends Exception {} } \ No newline at end of file diff --git a/libsrc/ttf/src/org/doubletype/ossa/module/TypefaceFile.java b/libsrc/ttf/src/org/doubletype/ossa/module/TypefaceFile.java index 9133ea84f..b08e580e3 100644 --- a/libsrc/ttf/src/org/doubletype/ossa/module/TypefaceFile.java +++ b/libsrc/ttf/src/org/doubletype/ossa/module/TypefaceFile.java @@ -1,677 +1,655 @@ -/* - * $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.io.*; -import java.util.*; - -import org.doubletype.ossa.*; -import org.doubletype.ossa.xml.*; -import org.doubletype.ossa.adapter.*; -import org.doubletype.ossa.truetype.*; -import java.awt.*; - -/** - * @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_dotDtyp = ".dtyp"; - private final String k_dotTtf = ".ttf"; - - private File m_dir; - private Hashtable m_nameToIndeces = new Hashtable<>(); - private File m_ttfFile; - private Font m_font = null; - private File m_binFolder; - - public TypefaceFile(String a_name, File a_dir) throws FileNotFoundException { - super(TypefaceFile.class.getResource(s_emptyFileName)); - - m_dir = a_dir; - setGlyphTitle(a_name); - - m_fileName = new File(m_dir, a_name + k_dotDtyp); - initFileName(); - - saveGlyphFile(); - } - - public TypefaceFile(File a_file) { - super(a_file); - - m_dir = a_file.getParentFile(); - m_fileName = a_file; - - initFileName(); - } - - private void initFileName() { - m_binFolder = new File(m_dir, "bin"); - if (!m_binFolder.exists()) { - m_binFolder.mkdir(); - } // if - - String fileName = getGlyphTitle() + k_dotTtf; - m_ttfFile = new File(m_binFolder, fileName); - } - - public GlyphFile createGlyph(long a_unicode) throws FileNotFoundException { - String name = Character.getName((int) a_unicode); - if (name == null) { - name = "NAC_" + Long.toHexString(a_unicode); - } // if - - name = name.replace(' ', '_'); - return new GlyphFile( - getGlyphPath(), name, a_unicode); - } - - public boolean addRequiredGlyphs() throws FileNotFoundException { - boolean retval = false; - - if (unicodeToFileName(TTUnicodeRange.k_notDef) == null) { - GlyphFile glyph = new GlyphFile(getGlyphPath(), "NOTDEF", TTUnicodeRange.k_notDef); - glyph.initNotDef(k_defaultAdvanceWidth); - addGlyph(0, glyph); - retval = true; - } // if - - if (unicodeToFileName(TTUnicodeRange.k_null) == null) { - GlyphFile glyph = new GlyphFile(getGlyphPath(), "NULL", TTUnicodeRange.k_null); - glyph.initNullGlyph(); - addGlyph(1, glyph); - retval = true; - } // if - - if (unicodeToFileName(TTUnicodeRange.k_cr) == null) { - GlyphFile glyph = new GlyphFile(getGlyphPath(), "CR", TTUnicodeRange.k_cr); - glyph.initSpace(k_defaultAdvanceWidth); - addGlyph(2, glyph); - retval = true; - } // if - - if (unicodeToFileName(TTUnicodeRange.k_space) == null) { - GlyphFile glyph = new GlyphFile(getGlyphPath(), "SPACE", TTUnicodeRange.k_space); - glyph.initSpace(k_defaultAdvanceWidth); - addGlyph(3, glyph); - retval = true; - } // if - - if (retval) { - saveGlyphFile(); - } // if - - return retval; - } - - public void addBasicLatinGlyphs() throws FileNotFoundException { - 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)); - } // if - } // for i - - saveGlyphFile(); - } - - public File getGlyphPath() { - return m_dir; - } - - public String unicodeToFileName(long a_unicode) { - for (XGlyphFile glyphFile: m_glyph.getBody().getGlyphFile()) { - if (glyphFile.getUnicode() == a_unicode) { - return glyphFile.getHref(); - } // if - } // for i - - return null; - } - - /** - * change glyph's unicode mapping. - * @param a_glyphFile - * @param a_unicode - */ - public void setGlyphUnicode(GlyphFile a_glyphFile, long a_unicode) throws FileNotFoundException { - int i; - XGlyphFile [] glyphFiles = m_glyph.getBody().getGlyphFile(); - for (i = 0; i < glyphFiles.length; i++) { - XGlyphFile glyphFile = glyphFiles[i]; - - if (glyphFile.getHref().equals( - a_glyphFile.getShortFileName())) { - continue; - } // if - - glyphFile.setUnicode(a_unicode); - a_glyphFile.setUnicode(Long.toHexString(a_unicode)); - a_glyphFile.saveGlyphFile(); - saveGlyphFile(); - - return; - } // for i - } - - public void addGlyph(GlyphFile a_file) { - XGlyphFile xglyphFile = new XGlyphFile(); - xglyphFile.setHref(a_file.getShortFileName()); - xglyphFile.setUnicode(a_file.getUnicodeAsLong()); - m_glyph.getBody().addGlyphFile(xglyphFile); - } - - public void addGlyph(int a_index, GlyphFile a_file) { - XGlyphFile xglyphFile = new XGlyphFile(); - xglyphFile.setHref(a_file.getShortFileName()); - xglyphFile.setUnicode(a_file.getUnicodeAsLong()); - m_glyph.getBody().addGlyphFile(a_index, xglyphFile); - } - - public void removeGlyph(String a_fileName) { - for (XGlyphFile file: m_glyph.getBody().getGlyphFile()) { - if (file.getHref().equals(a_fileName)) { - m_glyph.getBody().removeGlyphFile(file); - return; - } // if - } - } - - public ArrayList getChildFileNames() { - ArrayList retval = new ArrayList<>(); - XGlyphFile [] files = m_glyph.getBody().getGlyphFile(); - - for (int i = 0; i < files.length; i++) { - XGlyphFile file = files[i]; - retval.add(file.getHref()); - } // for i - - return retval; - } - - public Object [] getCodePages() { - int i; - Object [] retval; - String [] codePages = m_glyph.getHead().getCodePage(); - retval = new Object[codePages.length]; - for (i = 0; i < codePages.length; i++) { - retval[i] = codePages[i]; - } // for i - - return retval; - } - - public boolean containsUnicodeRange(String a_unicodeRange) { - int i; - String [] unicodeRanges = m_glyph.getHead().getUnicodeRange(); - - for (i = 0; i < unicodeRanges.length; i++) { - if (unicodeRanges[i].equals(a_unicodeRange)) { - return true; - } // if - } // for i - - return false; - } - - public void addUnicodeRange(String a_unicodeRange) throws FileNotFoundException { - if (containsUnicodeRange(a_unicodeRange)) { - return; - } // if - - m_glyph.getHead().addUnicodeRange(a_unicodeRange); - saveGlyphFile(); - } - - public boolean containsCodePage(String a_codePage) { - int i; - String [] codePages = m_glyph.getHead().getCodePage(); - - for (i = 0; i < codePages.length; i++) { - if (codePages[i].equals(a_codePage)) { - return true; - } // if - } // for i - - return false; - } - - public void addCodePage(String a_codePage) throws FileNotFoundException { - if (containsCodePage(a_codePage)) { - return; - } // if - - m_glyph.getHead().addCodePage(a_codePage); - saveGlyphFile(); - } - - public void removeCodePage(String a_codePage) throws FileNotFoundException { - if (!containsCodePage(a_codePage)) { - return; - } // if - - m_glyph.getHead().removeCodePage(a_codePage); - saveGlyphFile(); - } - - public void setFontFamilyName(String a_value) throws FileNotFoundException { - m_glyph.getHead().setFontFamily(a_value); - saveGlyphFile(); - } - - public String getFontFamilyName() { - return m_glyph.getHead().getFontFamily(); - } - - public String getVersion() throws FileNotFoundException { - if (m_glyph.getHead().getVersion() == null) { - m_glyph.getHead().setVersion("0.1"); - saveGlyphFile(); - } // if - - return m_glyph.getHead().getVersion(); - } - - public void setSubFamily(String a_value) throws FileNotFoundException { - m_glyph.getHead().setFontSubFamily(a_value); - saveGlyphFile(); - } - - public void setDefaultMetrics() throws FileNotFoundException { - XHead head = m_glyph.getHead(); - head.setTopSideBearing(k_defaultTopSideBearing); - head.setAscender(k_defaultAscender); - head.setXHeight(k_defaultXHeight); - head.setDescender(k_defaultDescender); - head.setBottomSideBearing(k_defaultBottomSideBearing); - - saveGlyphFile(); - } - - public double getEm() { - return k_em; - } - - public double getBaseline() throws FileNotFoundException { - return getBottomSideBearing() + getDescender(); - } - - public double getMeanline() throws FileNotFoundException { - return getBottomSideBearing() - + getDescender() + getXHeight(); - } - - public double getBodyBottom() throws FileNotFoundException { - return getBottomSideBearing(); - } - - public double getBodyTop() throws FileNotFoundException { - return getEm() - getTopSideBearing(); - } - - public double getTopSideBearing() throws FileNotFoundException { - if (!m_glyph.getHead().checkTopSideBearing()) { - setDefaultMetrics(); - } // if - - return m_glyph.getHead().getTopSideBearing(); - } - - public double getAscender() throws FileNotFoundException { - if (!m_glyph.getHead().checkAscender()) { - setDefaultMetrics(); - } // if - - return m_glyph.getHead().getAscender(); - } - - public double getXHeight() throws FileNotFoundException { - if (!m_glyph.getHead().checkXHeight()) { - setDefaultMetrics(); - } // if - - return m_glyph.getHead().getXHeight(); - } - - public double getDescender() throws FileNotFoundException { - if (!m_glyph.getHead().checkDescender()) { - setDefaultMetrics(); - } // if - - return m_glyph.getHead().getDescender(); - } - - public double getBottomSideBearing() throws FileNotFoundException { - if (!m_glyph.getHead().checkBottomSideBearing()) { - setDefaultMetrics(); - } // if - - return m_glyph.getHead().getBottomSideBearing(); - } - - public void setTopSideBearing(double a_value) throws OutOfRangeException, FileNotFoundException { - checkBoundary(a_value); - m_glyph.getHead().setTopSideBearing(a_value); - saveGlyphFile(); - } - - private void checkBoundary(double a_value) throws OutOfRangeException { - if (a_value > k_em || a_value < 0) { - throw new OutOfRangeException(a_value); - } // if - } - - public void setAscender(double a_value) throws OutOfRangeException, FileNotFoundException { - checkBoundary(a_value); - m_glyph.getHead().setAscender(a_value); - saveGlyphFile(); - } - - public void setXHeight(double a_value) throws OutOfRangeException, FileNotFoundException { - checkBoundary(a_value); - if (a_value > getAscender()) { - throw new OutOfRangeException(a_value); - } // if - - m_glyph.getHead().setXHeight(a_value); - - m_history.record("setXHeight"); - saveGlyphFile(); - } - - public void setDescender(double a_value) throws OutOfRangeException, FileNotFoundException { - checkBoundary(a_value); - m_glyph.getHead().setDescender(a_value); - saveGlyphFile(); - } - - public void setBottomSideBearing(double a_value) throws OutOfRangeException, FileNotFoundException { - checkBoundary(a_value); - m_glyph.getHead().setBottomSideBearing(a_value); - saveGlyphFile(); - } - - public double getBodyHeight() throws FileNotFoundException { - return k_em - getTopSideBearing() - getBottomSideBearing(); - } - -// -------------------------------------------------------------------- - - /** - * Calls FontFileWriter to produce TrueType font file. - * @throws FileNotFoundException - */ - public void buildTTF(boolean a_isDebug) throws Exception { - String randomString = UUID.randomUUID().toString().substring(0, 4); - - File tempFile = new File(m_binFolder, - getGlyphTitle() + "_" + randomString + k_dotTtf); - File target; - String fontFamilyName; - - if (a_isDebug) { - target = tempFile; - fontFamilyName = getGlyphTitle() + " " + randomString; - } else { - target = m_ttfFile; - fontFamilyName = getFontFamilyName(); - } // if-else - - target.delete(); - FontFileWriter writer; - ModuleManager.getSingletonInstance().clear(); - m_stack.clear(); - m_stack.push(this); - try (RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw")) { - writer = new FontFileWriter(randomAccessFile); - - writer.setFontFamilyName(fontFamilyName); - writer.setCopyrightYear(getCopyrightYear()); - 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 (!a_isDebug && target.exists()) { - copyFile(target, tempFile); - } // if - - FileInputStream in = new FileInputStream(tempFile); - m_font = Font.createFont(Font.TRUETYPE_FONT, - (InputStream) in); - in.close(); - - ModuleManager.getSingletonInstance().clear(); - m_stack.pop(); // pop this - } - - 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_glyph.getHead().getCodePage()) { - TTCodePage codePage = TTCodePage.forName(codePageName); - if (codePage == null) { - continue; - } // if - - a_writer.setCodeRangeFlag(codePage.getOsTwoFlag()); - } // for codePageName - } - - private void loadUnicodeRanges(FontFileWriter a_writer) { - String [] unicodeRanges = m_glyph.getHead().getUnicodeRange(); - int i; - for (i = 0; i < unicodeRanges.length; i++) { - if (!TTUnicodeRange.find(unicodeRanges[i])) { - continue; - } // if - - a_writer.addUnicodeRange(TTUnicodeRange.getLastFound()); - } // for i - } - - private void loadGlyphs(FontFileWriter a_writer) throws Exception { - m_nameToIndeces.clear(); - - for (String fileName: getChildFileNames()) { - GlyphFile glyphFile = nameToGlyphFile(fileName); - loadGlyph(glyphFile, glyphFile, a_writer); - } // for - } - - private GlyphFile nameToGlyphFile(String a_fileName) throws FileNotFoundException { - File file = new File(m_dir, a_fileName); - - if (!file.exists()) { - throw new FileNotFoundException(a_fileName); - } // if - - GlyphFile retval = new GlyphFile(file); - - return retval; - } - - /** - * load the glyph into FontFileWriter. - * @param a_fileName - * @param a_writer - * @throws Exception - */ - private void loadGlyph(GlyphFile a_glyphFile, VarStackFrame a_frame, FontFileWriter a_writer) throws Exception { - if (m_nameToIndeces.containsKey(a_glyphFile.getShortFileName())) { - return; - } // if - - /* - if (a_glyphFile.getUnicodeAsLong() == TTUnicodeRange.k_null) { - return; - } // if - */ - - TTGlyph glyph = null; - m_stack.push(a_frame); - - if (a_glyphFile.isSimple()) { - // glyph will be null if it is empty - glyph = a_glyphFile.toSimpleGlyph(); - } else { - glyph = createCompoundGlyph(a_glyphFile, a_writer); - } // if - - m_stack.pop(); - - if (glyph == null && a_glyphFile.isWhiteSpace()) { - glyph = new TTGlyph(); - } // if - - if (glyph == null) { - return; - } // if - - int glyphIndex = a_writer.addGlyph(glyph); - m_nameToIndeces.put(a_glyphFile.getShortFileName(), glyphIndex); - long unicode = a_glyphFile.getUnicodeAsLong(); - - if (unicode != -1 && glyph != null) { - long existingIndex = a_writer.getCharacterMapping(unicode); - if (existingIndex != 0) { - throw new Exception(Long.toHexString(unicode) + " is mapped already."); - } // if - - a_writer.addCharacterMapping(unicode, glyphIndex); - } // if - } - - private TTGlyph createCompoundGlyph(GlyphFile a_glyphFile, - FontFileWriter a_writer) throws Exception - { - TTGlyph retval = new TTGlyph(); - ArrayList locs = new ArrayList<>(); - ArrayList 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); - } // if - - XInclude [] includes = a_glyphFile.m_glyph.getBody().getInclude(); - int i; - for (i = 0; i < includes.length; i++) { - EIncludeInvoke include = (EIncludeInvoke) includes[i]; - GlyphFile glyphFile = nameToGlyphFile(include.getHref()); - - // load the glyph included in this one - loadGlyph(glyphFile, include, a_writer); - Integer n = m_nameToIndeces.get(glyphFile.getShortFileName()); - if (n == null) { - continue; - } // if - - indeces.add(n); - XPoint2d pos = include.getInvoke().getInvokePos().getPoint2d(); - locs.add(new Point((int) pos.getX(), (int) pos.getY())); - } // for i - - 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(); - } // if - - retval.addGlyfIndex(glyfIndex); - if (i < indeces.size() - 1) { - retval.addFlag(flag | TTGlyph.MORE_COMPONENTS); - } else { - retval.addFlag(flag); - } // if-else - - 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; - } -} +/* + * $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.io.*; +import java.util.*; + +import org.doubletype.ossa.*; +import org.doubletype.ossa.xml.*; +import org.doubletype.ossa.adapter.*; +import org.doubletype.ossa.truetype.*; +import java.awt.*; +import java.util.List; + +/** + * @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_dotDtyp = ".dtyp"; + private final String k_dotTtf = ".ttf"; + + private File m_dir; + private Hashtable m_nameToIndeces = new Hashtable<>(); + private File m_ttfFile; + private Font m_font = null; + private File m_binFolder; + private Map m_glyphFiles = new HashMap<>(); + + public TypefaceFile(String a_name, File a_dir) throws FileNotFoundException { + super(TypefaceFile.class.getResource(s_emptyFileName)); + + m_dir = a_dir; + setGlyphTitle(a_name); + + m_fileName = new File(m_dir, a_name + k_dotDtyp); + initFileName(); + } + + public TypefaceFile(File a_file) { + super(a_file); + + m_dir = a_file.getParentFile(); + m_fileName = a_file; + + initFileName(); + } + + private void initFileName() { + m_binFolder = new File(m_dir, "bin"); + if (!m_binFolder.exists()) { + m_binFolder.mkdir(); + } // if + + String fileName = getGlyphTitle() + k_dotTtf; + m_ttfFile = new File(m_binFolder, fileName); + } + + public GlyphFile createGlyph(long a_unicode) { + String name = Character.getName((int) a_unicode); + if (name == null) { + name = "NAC_" + Long.toHexString(a_unicode); + } // if + + name = name.replace(' ', '_'); + return new GlyphFile( + getGlyphPath(), name, a_unicode); + } + + public boolean addRequiredGlyphs() { + boolean retval = false; + + if (unicodeToFileName(TTUnicodeRange.k_notDef) == null) { + GlyphFile glyph = new GlyphFile(getGlyphPath(), "NOTDEF", TTUnicodeRange.k_notDef); + glyph.initNotDef(k_defaultAdvanceWidth); + addGlyph(0, glyph); + retval = true; + } // if + + if (unicodeToFileName(TTUnicodeRange.k_null) == null) { + GlyphFile glyph = new GlyphFile(getGlyphPath(), "NULL", TTUnicodeRange.k_null); + glyph.initNullGlyph(); + addGlyph(1, glyph); + retval = true; + } // if + + if (unicodeToFileName(TTUnicodeRange.k_cr) == null) { + GlyphFile glyph = new GlyphFile(getGlyphPath(), "CR", TTUnicodeRange.k_cr); + glyph.initSpace(k_defaultAdvanceWidth); + addGlyph(2, glyph); + retval = true; + } // if + + if (unicodeToFileName(TTUnicodeRange.k_space) == null) { + GlyphFile glyph = new GlyphFile(getGlyphPath(), "SPACE", TTUnicodeRange.k_space); + glyph.initSpace(k_defaultAdvanceWidth); + addGlyph(3, glyph); + retval = true; + } // if + + 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)); + } // if + } // for i + } + + public File getGlyphPath() { + return m_dir; + } + + public String unicodeToFileName(long a_unicode) { + for (XGlyphFile glyphFile: m_glyph.getBody().getGlyphFile()) { + if (glyphFile.getUnicode() == a_unicode) { + return glyphFile.getHref(); + } // if + } // for i + + return null; + } + + /** + * change glyph's unicode mapping. + * @param a_glyphFile + * @param a_unicode + */ + public void setGlyphUnicode(GlyphFile a_glyphFile, long a_unicode) { + int i; + XGlyphFile [] glyphFiles = m_glyph.getBody().getGlyphFile(); + for (i = 0; i < glyphFiles.length; i++) { + XGlyphFile glyphFile = glyphFiles[i]; + + if (glyphFile.getHref().equals( + a_glyphFile.getShortFileName())) { + continue; + } // if + + glyphFile.setUnicode(a_unicode); + a_glyphFile.setUnicode(Long.toHexString(a_unicode)); + + return; + } // for i + } + + public void addGlyph(GlyphFile a_file) { + String shortFileName = a_file.getShortFileName(); + m_glyphFiles.put(shortFileName, a_file); + XGlyphFile xglyphFile = new XGlyphFile(); + xglyphFile.setHref(shortFileName); + xglyphFile.setUnicode(a_file.getUnicodeAsLong()); + m_glyph.getBody().addGlyphFile(xglyphFile); + } + + public void addGlyph(int a_index, GlyphFile a_file) { + String shortFileName = a_file.getShortFileName(); + m_glyphFiles.put(shortFileName, a_file); + XGlyphFile xglyphFile = new XGlyphFile(); + xglyphFile.setHref(shortFileName); + xglyphFile.setUnicode(a_file.getUnicodeAsLong()); + m_glyph.getBody().addGlyphFile(a_index, xglyphFile); + } + + public void removeGlyph(String a_fileName) { + for (XGlyphFile file: m_glyph.getBody().getGlyphFile()) { + if (file.getHref().equals(a_fileName)) { + m_glyph.getBody().removeGlyphFile(file); + return; + } // if + } + } + + public ArrayList getChildFileNames() { + ArrayList retval = new ArrayList<>(); + XGlyphFile [] files = m_glyph.getBody().getGlyphFile(); + + for (int i = 0; i < files.length; i++) { + XGlyphFile file = files[i]; + retval.add(file.getHref()); + } // for i + + return retval; + } + + public Object [] getCodePages() { + int i; + Object [] retval; + String [] codePages = m_glyph.getHead().getCodePage(); + retval = new Object[codePages.length]; + for (i = 0; i < codePages.length; i++) { + retval[i] = codePages[i]; + } // for i + + return retval; + } + + public boolean containsUnicodeRange(String a_unicodeRange) { + int i; + String [] unicodeRanges = m_glyph.getHead().getUnicodeRange(); + + for (i = 0; i < unicodeRanges.length; i++) { + if (unicodeRanges[i].equals(a_unicodeRange)) { + return true; + } // if + } // for i + + return false; + } + + public void addUnicodeRange(String a_unicodeRange) { + if (containsUnicodeRange(a_unicodeRange)) { + return; + } // if + + m_glyph.getHead().addUnicodeRange(a_unicodeRange); + } + + public boolean containsCodePage(String a_codePage) { + int i; + String [] codePages = m_glyph.getHead().getCodePage(); + + for (i = 0; i < codePages.length; i++) { + if (codePages[i].equals(a_codePage)) { + return true; + } // if + } // for i + + return false; + } + + public void addCodePage(String a_codePage) { + if (containsCodePage(a_codePage)) { + return; + } // if + + m_glyph.getHead().addCodePage(a_codePage); + } + + public void removeCodePage(String a_codePage) { + if (!containsCodePage(a_codePage)) { + return; + } // if + + m_glyph.getHead().removeCodePage(a_codePage); + } + + public void setFontFamilyName(String a_value) { + m_glyph.getHead().setFontFamily(a_value); + } + + public String getFontFamilyName() { + return m_glyph.getHead().getFontFamily(); + } + + public String getVersion() { + if (m_glyph.getHead().getVersion() == null) { + m_glyph.getHead().setVersion("0.1"); + } // if + + return m_glyph.getHead().getVersion(); + } + + public void setSubFamily(String a_value) { + m_glyph.getHead().setFontSubFamily(a_value); + } + + public void setDefaultMetrics() { + XHead head = m_glyph.getHead(); + head.setTopSideBearing(k_defaultTopSideBearing); + head.setAscender(k_defaultAscender); + head.setXHeight(k_defaultXHeight); + head.setDescender(k_defaultDescender); + head.setBottomSideBearing(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_glyph.getHead().checkTopSideBearing()) { + setDefaultMetrics(); + } // if + + return m_glyph.getHead().getTopSideBearing(); + } + + public double getAscender() { + if (!m_glyph.getHead().checkAscender()) { + setDefaultMetrics(); + } // if + + return m_glyph.getHead().getAscender(); + } + + public double getXHeight() { + if (!m_glyph.getHead().checkXHeight()) { + setDefaultMetrics(); + } // if + + return m_glyph.getHead().getXHeight(); + } + + public double getDescender() { + if (!m_glyph.getHead().checkDescender()) { + setDefaultMetrics(); + } // if + + return m_glyph.getHead().getDescender(); + } + + public double getBottomSideBearing() { + if (!m_glyph.getHead().checkBottomSideBearing()) { + setDefaultMetrics(); + } // if + + return m_glyph.getHead().getBottomSideBearing(); + } + + public void setTopSideBearing(double a_value) throws OutOfRangeException { + checkBoundary(a_value); + m_glyph.getHead().setTopSideBearing(a_value); + } + + private void checkBoundary(double a_value) throws OutOfRangeException { + if (a_value > k_em || a_value < 0) { + throw new OutOfRangeException(a_value); + } // if + } + + public void setAscender(double a_value) throws OutOfRangeException { + checkBoundary(a_value); + m_glyph.getHead().setAscender(a_value); + } + + public void setXHeight(double a_value) throws OutOfRangeException { + checkBoundary(a_value); + if (a_value > getAscender()) { + throw new OutOfRangeException(a_value); + } // if + + m_glyph.getHead().setXHeight(a_value); + } + + public void setDescender(double a_value) throws OutOfRangeException { + checkBoundary(a_value); + m_glyph.getHead().setDescender(a_value); + } + + public void setBottomSideBearing(double a_value) throws OutOfRangeException { + checkBoundary(a_value); + m_glyph.getHead().setBottomSideBearing(a_value); + } + + public double getBodyHeight() { + return k_em - getTopSideBearing() - getBottomSideBearing(); + } + +// -------------------------------------------------------------------- + + /** + * Calls FontFileWriter to produce TrueType font file. + */ + public void buildTTF(boolean a_isDebug) throws Exception { + String randomString = UUID.randomUUID().toString().substring(0, 4); + + File tempFile = new File(m_binFolder, + getGlyphTitle() + "_" + randomString + k_dotTtf); + File target; + String fontFamilyName; + + if (a_isDebug) { + target = tempFile; + fontFamilyName = getGlyphTitle() + " " + randomString; + } else { + target = m_ttfFile; + fontFamilyName = getFontFamilyName(); + } // if-else + + target.delete(); + FontFileWriter writer; + ModuleManager.getSingletonInstance().clear(); + m_stack.clear(); + m_stack.push(this); + try (RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw")) { + writer = new FontFileWriter(randomAccessFile); + + writer.setFontFamilyName(fontFamilyName); + writer.setCopyrightYear(getCopyrightYear()); + 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 (!a_isDebug && target.exists()) { + copyFile(target, tempFile); + } // if + + FileInputStream in = new FileInputStream(tempFile); + m_font = Font.createFont(Font.TRUETYPE_FONT, + (InputStream) in); + in.close(); + + ModuleManager.getSingletonInstance().clear(); + m_stack.pop(); // pop this + } + + 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_glyph.getHead().getCodePage()) { + TTCodePage codePage = TTCodePage.forName(codePageName); + if (codePage == null) { + continue; + } // if + + a_writer.setCodeRangeFlag(codePage.getOsTwoFlag()); + } // for codePageName + } + + private void loadUnicodeRanges(FontFileWriter a_writer) { + String [] unicodeRanges = m_glyph.getHead().getUnicodeRange(); + int i; + for (i = 0; i < unicodeRanges.length; i++) { + if (!TTUnicodeRange.find(unicodeRanges[i])) { + continue; + } // if + + a_writer.addUnicodeRange(TTUnicodeRange.getLastFound()); + } // for i + } + + private void loadGlyphs(FontFileWriter a_writer) throws Exception { + m_nameToIndeces.clear(); + + for (String fileName: getChildFileNames()) { + GlyphFile glyphFile = nameToGlyphFile(fileName); + loadGlyph(glyphFile, glyphFile, a_writer); + } // for + } + + private GlyphFile nameToGlyphFile(String a_fileName) throws FileNotFoundException { + if (!m_glyphFiles.containsKey(a_fileName)) { + throw new FileNotFoundException(a_fileName); + } // if + + GlyphFile retval = m_glyphFiles.get(a_fileName); + + return retval; + } + + /** + * load the glyph into FontFileWriter. + * @param a_fileName + * @param a_writer + * @throws Exception + */ + private void loadGlyph(GlyphFile a_glyphFile, VarStackFrame a_frame, FontFileWriter a_writer) throws Exception { + if (m_nameToIndeces.containsKey(a_glyphFile.getShortFileName())) { + return; + } // if + + /* + if (a_glyphFile.getUnicodeAsLong() == TTUnicodeRange.k_null) { + return; + } // if + */ + + TTGlyph glyph = null; + m_stack.push(a_frame); + + if (a_glyphFile.isSimple()) { + // glyph will be null if it is empty + glyph = a_glyphFile.toSimpleGlyph(); + } else { + glyph = createCompoundGlyph(a_glyphFile, a_writer); + } // if + + m_stack.pop(); + + if (glyph == null && a_glyphFile.isWhiteSpace()) { + glyph = new TTGlyph(); + } // if + + if (glyph == null) { + return; + } // if + + int glyphIndex = a_writer.addGlyph(glyph); + m_nameToIndeces.put(a_glyphFile.getShortFileName(), glyphIndex); + long unicode = a_glyphFile.getUnicodeAsLong(); + + if (unicode != -1 && glyph != null) { + long existingIndex = a_writer.getCharacterMapping(unicode); + if (existingIndex != 0) { + throw new Exception(Long.toHexString(unicode) + " is mapped already."); + } // if + + a_writer.addCharacterMapping(unicode, glyphIndex); + } // if + } + + private TTGlyph createCompoundGlyph(GlyphFile a_glyphFile, + FontFileWriter a_writer) throws Exception + { + TTGlyph retval = new TTGlyph(); + ArrayList locs = new ArrayList<>(); + ArrayList 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); + } // if + + XInclude [] includes = a_glyphFile.m_glyph.getBody().getInclude(); + int i; + for (i = 0; i < includes.length; i++) { + EIncludeInvoke include = (EIncludeInvoke) includes[i]; + GlyphFile glyphFile = nameToGlyphFile(include.getHref()); + + // load the glyph included in this one + loadGlyph(glyphFile, include, a_writer); + Integer n = m_nameToIndeces.get(glyphFile.getShortFileName()); + if (n == null) { + continue; + } // if + + indeces.add(n); + XPoint2d pos = include.getInvoke().getInvokePos().getPoint2d(); + locs.add(new Point((int) pos.getX(), (int) pos.getY())); + } // for i + + 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(); + } // if + + retval.addGlyfIndex(glyfIndex); + if (i < indeces.size() - 1) { + retval.addFlag(flag | TTGlyph.MORE_COMPONENTS); + } else { + retval.addFlag(flag); + } // if-else + + 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; + } +} diff --git a/libsrc/ttf/src/org/doubletype/ossa/truetype/GlyfWriter.java b/libsrc/ttf/src/org/doubletype/ossa/truetype/GlyfWriter.java index 8a89e92b1..a0e650029 100644 --- a/libsrc/ttf/src/org/doubletype/ossa/truetype/GlyfWriter.java +++ b/libsrc/ttf/src/org/doubletype/ossa/truetype/GlyfWriter.java @@ -1,203 +1,204 @@ -/* - * $Id: GlyfWriter.java,v 1.17 2004/09/23 07:47:39 eed3si9n Exp $ - * - * $Copyright: copyright (c) 2003, 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.truetype; - -import java.io.*; -import java.awt.*; -import java.util.*; - -/** - * @author e.e - */ -public class GlyfWriter extends FontFormatWriter { - private ArrayList m_glyphs; - - private LocaWriter m_loca; - private MaxpWriter m_maxp; - private HeadWriter m_head; - private HdmxWriter m_hdmx; - - public GlyfWriter(LocaWriter a_loca, MaxpWriter a_maxp, - HeadWriter a_head, HdmxWriter a_hdmx) { - super(); - - m_loca = a_loca; - m_maxp = a_maxp; - m_head = a_head; - m_hdmx = a_hdmx; - m_glyphs = new ArrayList<>(); - } - - public void write() throws IOException { - m_hdmx.setNumGlyphs(numOfGlyph()); - m_maxp.setNumGlyphs(numOfGlyph()); - m_loca.m_offsets.clear(); - - for (int i = 0; i < m_glyphs.size(); i++) { - writeGlyph(m_glyphs.get(i)); - m_hdmx.updatePixelWidth(i, m_glyphs.get(i)); - } // for i - - m_loca.m_offsets.add(size()); - } - - public int add(TTGlyph a_glyph) { - m_head.updateMax(a_glyph.getMax()); - m_head.updateMin(a_glyph.getMin()); - - m_glyphs.add(a_glyph); - return m_glyphs.size() - 1; - } - - public int numOfGlyph() { - return m_glyphs.size(); - } - - public TTGlyph getGlyph(int a_index) { - return m_glyphs.get(a_index); - } - - private void writeGlyph(TTGlyph a_glyph) throws IOException { - m_loca.m_offsets.add(size()); - - if (a_glyph == null) { - return; - } // if - - if (a_glyph.isSimple()) { - writeSimpleGlyph(a_glyph); - } else { - writeCompoundGlyph(a_glyph); - } // if-else - - pad(); - } - - /** - * @param a_glyph - * @throws IOException - */ - private void writeSimpleGlyph(TTGlyph a_glyph) throws IOException { - if (a_glyph.getNumOfContours() == 0) { - return; - } // if - - m_maxp.updateNumOfContours(a_glyph.getNumOfContours()); - writeInt16(a_glyph.getNumOfContours()); - writeMinMax(a_glyph); - - int i; - for (i = 0; i < a_glyph.getNumOfContours(); i++) { - writeUInt16(a_glyph.getEndPoint(i)); - } // for i - - int numOfInst = a_glyph.getNumOfInstructions(); - m_maxp.updateSizeOfInstructions(numOfInst); - - writeUInt16(numOfInst); - for (i = 0; i < numOfInst; i++) { - writeUInt8(a_glyph.getInstruction(i)); - } // for i - - for (i = 0; i < a_glyph.getNumOfFlags(); i++) { - int flag = a_glyph.getFlag(i); - writeUInt8(flag); - } // for i - - // update num of points - m_maxp.updateNumOfPoints(a_glyph.getNumOfPoints()); - - int lastX = 0; - for (i = 0; i < a_glyph.getNumOfPoints(); i++) { - Point point = a_glyph.getPoint(i); - - writeInt16(point.x - lastX); - lastX = point.x; - } // for i - - int lastY = 0; - for (i = 0; i < a_glyph.getNumOfPoints(); i++) { - Point point = a_glyph.getPoint(i); - - writeInt16(point.y - lastY); - lastY = point.y; - } // for i - } - - /** - * @param a_glyph - * @throws IOException - */ - private void writeCompoundGlyph(TTGlyph a_glyph) throws IOException { - int i; - - m_maxp.updateNumOfCompositePoints(a_glyph.getNumOfCompositePoints()); - m_maxp.updateNumOfCompositeContours(a_glyph.getNumOfCompositeContours()); - - writeInt16(-1); - writeMinMax(a_glyph); - - int numOfGlyphs = a_glyph.getNumOfFlags(); - m_maxp.updateNumOfComponentElements(numOfGlyphs); - m_maxp.updateComponentDepth(a_glyph.getComponentDepth()); - - for (i = 0; i < numOfGlyphs; i++) { - writeUInt16(a_glyph.getFlag(i)); - writeUInt16(a_glyph.getGlyfIndex(i)); - writeInt16(a_glyph.getArg1(i)); - writeInt16(a_glyph.getArg2(i)); - } // for i - } - - /** - * @param a_glyph - * @throws IOException - */ - private void writeMinMax(TTGlyph a_glyph) throws IOException { - Point min = a_glyph.getMin(); - Point max = a_glyph.getMax(); - - writeFWord(min.x); - writeFWord(min.y); - writeFWord(max.x); - writeFWord(max.y); - } - - protected String getTag() { - return "glyf"; - } -} +/* + * $Id: GlyfWriter.java,v 1.17 2004/09/23 07:47:39 eed3si9n Exp $ + * + * $Copyright: copyright (c) 2003, 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.truetype; + +import java.io.*; +import java.awt.*; +import java.util.*; + +/** + * @author e.e + */ +public class GlyfWriter extends FontFormatWriter { + private ArrayList m_glyphs; + + private LocaWriter m_loca; + private MaxpWriter m_maxp; + private HeadWriter m_head; + private HdmxWriter m_hdmx; + + public GlyfWriter(LocaWriter a_loca, MaxpWriter a_maxp, + HeadWriter a_head, HdmxWriter a_hdmx) { + super(); + + m_loca = a_loca; + m_maxp = a_maxp; + m_head = a_head; + m_hdmx = a_hdmx; + m_glyphs = new ArrayList<>(); + } + + public void write() throws IOException { + m_hdmx.setNumGlyphs(numOfGlyph()); + m_maxp.setNumGlyphs(numOfGlyph()); + m_loca.m_offsets.clear(); + + for (int i = 0; i < m_glyphs.size(); i++) { + TTGlyph glyph = m_glyphs.get(i); + writeGlyph(glyph); + m_hdmx.updatePixelWidth(i, glyph); + } // for i + + m_loca.m_offsets.add(size()); + } + + public int add(TTGlyph a_glyph) { + m_head.updateMax(a_glyph.getMax()); + m_head.updateMin(a_glyph.getMin()); + + m_glyphs.add(a_glyph); + return m_glyphs.size() - 1; + } + + public int numOfGlyph() { + return m_glyphs.size(); + } + + public TTGlyph getGlyph(int a_index) { + return m_glyphs.get(a_index); + } + + private void writeGlyph(TTGlyph a_glyph) throws IOException { + m_loca.m_offsets.add(size()); + + if (a_glyph == null) { + return; + } // if + + if (a_glyph.isSimple()) { + writeSimpleGlyph(a_glyph); + } else { + writeCompoundGlyph(a_glyph); + } // if-else + + pad(); + } + + /** + * @param a_glyph + * @throws IOException + */ + private void writeSimpleGlyph(TTGlyph a_glyph) throws IOException { + if (a_glyph.getNumOfContours() == 0) { + return; + } // if + + m_maxp.updateNumOfContours(a_glyph.getNumOfContours()); + writeInt16(a_glyph.getNumOfContours()); + writeMinMax(a_glyph); + + int i; + for (i = 0; i < a_glyph.getNumOfContours(); i++) { + writeUInt16(a_glyph.getEndPoint(i)); + } // for i + + int numOfInst = a_glyph.getNumOfInstructions(); + m_maxp.updateSizeOfInstructions(numOfInst); + + writeUInt16(numOfInst); + for (i = 0; i < numOfInst; i++) { + writeUInt8(a_glyph.getInstruction(i)); + } // for i + + for (i = 0; i < a_glyph.getNumOfFlags(); i++) { + int flag = a_glyph.getFlag(i); + writeUInt8(flag); + } // for i + + // update num of points + m_maxp.updateNumOfPoints(a_glyph.getNumOfPoints()); + + int lastX = 0; + for (i = 0; i < a_glyph.getNumOfPoints(); i++) { + Point point = a_glyph.getPoint(i); + + writeInt16(point.x - lastX); + lastX = point.x; + } // for i + + int lastY = 0; + for (i = 0; i < a_glyph.getNumOfPoints(); i++) { + Point point = a_glyph.getPoint(i); + + writeInt16(point.y - lastY); + lastY = point.y; + } // for i + } + + /** + * @param a_glyph + * @throws IOException + */ + private void writeCompoundGlyph(TTGlyph a_glyph) throws IOException { + int i; + + m_maxp.updateNumOfCompositePoints(a_glyph.getNumOfCompositePoints()); + m_maxp.updateNumOfCompositeContours(a_glyph.getNumOfCompositeContours()); + + writeInt16(-1); + writeMinMax(a_glyph); + + int numOfGlyphs = a_glyph.getNumOfFlags(); + m_maxp.updateNumOfComponentElements(numOfGlyphs); + m_maxp.updateComponentDepth(a_glyph.getComponentDepth()); + + for (i = 0; i < numOfGlyphs; i++) { + writeUInt16(a_glyph.getFlag(i)); + writeUInt16(a_glyph.getGlyfIndex(i)); + writeInt16(a_glyph.getArg1(i)); + writeInt16(a_glyph.getArg2(i)); + } // for i + } + + /** + * @param a_glyph + * @throws IOException + */ + private void writeMinMax(TTGlyph a_glyph) throws IOException { + Point min = a_glyph.getMin(); + Point max = a_glyph.getMax(); + + writeFWord(min.x); + writeFWord(min.y); + writeFWord(max.x); + writeFWord(max.y); + } + + protected String getTag() { + return "glyf"; + } +}