diff options
author | Sven Gothel <[email protected]> | 2023-02-12 00:08:39 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-02-12 00:22:44 +0100 |
commit | 990c613770dbc0ef81bdc448f159eb1dd66c87ec (patch) | |
tree | 1f08a7de45b13e163f3364d1381a6d224e671f96 | |
parent | 093b4b2bc90595dc65c32e65bba1e1909e8d5b18 (diff) |
*Font*: Bring back loading fonts and glyph directly via input stream w/o font data array copies and re-wrapped input-stream
Adjusted TTFontTest and SVGExporterTest using a direct URLConnection's input-stream for a single TTFont.
6 files changed, 225 insertions, 95 deletions
diff --git a/src/main/java/net/java/dev/typecast/ot/OTFont.java b/src/main/java/net/java/dev/typecast/ot/OTFont.java index 086db52..645ddcb 100644 --- a/src/main/java/net/java/dev/typecast/ot/OTFont.java +++ b/src/main/java/net/java/dev/typecast/ot/OTFont.java @@ -22,7 +22,23 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; -import net.java.dev.typecast.ot.table.*; +import net.java.dev.typecast.ot.table.CmapTable; +import net.java.dev.typecast.ot.table.GsubTable; +import net.java.dev.typecast.ot.table.HdmxTable; +import net.java.dev.typecast.ot.table.HeadTable; +import net.java.dev.typecast.ot.table.HheaTable; +import net.java.dev.typecast.ot.table.HmtxTable; +import net.java.dev.typecast.ot.table.KernTable; +import net.java.dev.typecast.ot.table.LocaTable; +import net.java.dev.typecast.ot.table.MaxpTable; +import net.java.dev.typecast.ot.table.NameRecord; +import net.java.dev.typecast.ot.table.NameTable; +import net.java.dev.typecast.ot.table.Os2Table; +import net.java.dev.typecast.ot.table.PostTable; +import net.java.dev.typecast.ot.table.Table; +import net.java.dev.typecast.ot.table.TableDirectory; +import net.java.dev.typecast.ot.table.VheaTable; + /** * The TrueType font. @@ -30,29 +46,6 @@ import net.java.dev.typecast.ot.table.*; */ public abstract class OTFont { - - /** - * @param fontData OpenType/TrueType font file data. - * @param directoryOffset The Table Directory offset within the file. For a - * regular TTF/OTF file this will be zero, but for a TTC (Font Collection) - * the offset is retrieved from the TTC header. For a Mac font resource, - * offset is retrieved from the resource headers. - * @param tablesOrigin The point the table offsets are calculated from. - * Once again, in a regular TTF file, this will be zero. In a TTC is is - * also zero, but within a Mac resource, it is the beginning of the - * individual font resource data. - * @throws java.io.IOException - */ - OTFont(byte[] fontData, int tablesOrigin) throws IOException { - - // Load the table directory -// dis.skip(directoryOffset); - TableDirectory tableDirectory = new TableDirectory(fontData); - - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(fontData)); - dis.mark(fontData.length); - dis.reset(); - private final Os2Table _os2; private final CmapTable _cmap; private final HeadTable _head; @@ -63,6 +56,15 @@ public abstract class OTFont { private final PostTable _post; private final VheaTable _vhea; private final GsubTable _gsub; + + /** + * + * @param dis input stream marked at start with read-ahead set to known stream length + * @param tableDirectory + * @param tablesOrigin + * @throws IOException + */ + OTFont(final DataInputStream dis, TableDirectory tableDirectory, final int tablesOrigin) throws IOException { // Load some prerequisite tables // (These are tables that are referenced by other tables, so we need to load // them first) @@ -81,6 +83,8 @@ public abstract class OTFont { int length = seekTable(tableDirectory, dis, tablesOrigin, Table.vhea); if (length > 0) { _vhea = new VheaTable(dis); + } else { + _vhea = null; } // 'post' is required by 'glyf' @@ -96,24 +100,26 @@ public abstract class OTFont { _name = new NameTable(dis, length); seekTable(tableDirectory, dis, tablesOrigin, Table.OS_2); _os2 = new Os2Table(dis); + + _gsub = null; // FIXME: delete? } public Os2Table getOS2Table() { return _os2; } - + public CmapTable getCmapTable() { return _cmap; } - + public HeadTable getHeadTable() { return _head; } - + public HheaTable getHheaTable() { return _hhea; } - + public HmtxTable getHmtxTable() { return _hmtx; } @@ -153,12 +159,12 @@ public abstract class OTFont { public abstract Glyph getGlyph(int i); int seekTable( - TableDirectory tableDirectory, - DataInputStream dis, - int tablesOrigin, - int tag) throws IOException { + final TableDirectory tableDirectory, + final DataInputStream dis, + final int tablesOrigin, + final int tag) throws IOException { dis.reset(); - TableDirectory.Entry entry = tableDirectory.getEntryByTag(tag); + final TableDirectory.Entry entry = tableDirectory.getEntryByTag(tag); if (entry == null) { return 0; } diff --git a/src/main/java/net/java/dev/typecast/ot/OTFontCollection.java b/src/main/java/net/java/dev/typecast/ot/OTFontCollection.java index bee9b70..f61dd33 100644 --- a/src/main/java/net/java/dev/typecast/ot/OTFontCollection.java +++ b/src/main/java/net/java/dev/typecast/ot/OTFontCollection.java @@ -23,7 +23,7 @@ import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.nio.file.Files; +import java.io.InputStream; import net.java.dev.typecast.ot.mac.ResourceHeader; import net.java.dev.typecast.ot.mac.ResourceMap; import net.java.dev.typecast.ot.mac.ResourceReference; @@ -40,16 +40,26 @@ class OTFontCollection { private final boolean DEBUG = false; private TTCHeader _ttcHeader; private OTFont[] _fonts; + private String _pathName; + private String _fileName; + private boolean _resourceFork = false; + public String getPathName() { + return _pathName; + } + + public String getFileName() { + return _fileName; + } - public OTFont getFont(int i) { + public OTFont getFont(final int i) { return _fonts[i]; } - + public int getFontCount() { return _fonts.length; } - + public TTCHeader getTtcHeader() { return _ttcHeader; } @@ -57,36 +67,83 @@ class OTFontCollection { /** * @param file The OpenType font file */ - public OTFontCollection(File file) throws IOException { + public OTFontCollection(final File file) throws IOException { + read(file); + } + + /** + * @param istream The OpenType font input stream + * @param streamLen the length of the OpenType font segment in the stream + */ + public OTFontCollection(final InputStream istream, final int streamLen) throws IOException { + read(istream, streamLen); + } + + /** + * @param file The OpenType font file + */ + protected void read(File file) throws IOException { + _pathName = file.getPath(); + _fileName = file.getName(); + if (!file.exists()) { - throw new IOException(); + throw new IOException("File <"+file.getName()+"> doesn't exist."); } // Do we need to modify the path name to deal with font resources // in a Mac resource fork? - boolean resourceFork = false; if (file.length() == 0) { file = new File(file, "..namedfork/rsrc"); if (!file.exists()) { - throw new IOException(); + throw new IOException("File <"+file.getName()+"> doesn't exist."); } - resourceFork = true; + _resourceFork = true; + } + + final int streamLen = (int) file.length(); + final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), streamLen); + try { + readImpl(bis, streamLen); + } finally { + bis.close(); } + } - DataInputStream dis = new DataInputStream( - new BufferedInputStream( - new FileInputStream(file), (int) file.length())); - dis.mark((int) file.length()); + /** + * @param is The OpenType font stream + * @param streamLen the length of the OpenType font segment in the stream + */ + protected void read(final InputStream is, final int streamLen) throws IOException { + _pathName = ""; + _fileName = ""; + final InputStream bis; + if( is.markSupported() ) { + bis = is; + } else { + bis = new BufferedInputStream(is, streamLen); + } + readImpl(bis, streamLen); + } + + /** + * @param is The OpenType font stream, must {@link InputStream#markSupported() support mark}! + */ + private void readImpl(final InputStream bis, final int streamLen) throws IOException { + if( !bis.markSupported() ) { + throw new IllegalArgumentException("stream of type "+bis.getClass().getName()+" doesn't support mark"); + } + bis.mark(streamLen); + final DataInputStream dis = new DataInputStream(bis); - if (resourceFork || file.getPath().endsWith(".dfont")) { + if (_resourceFork || _pathName.endsWith(".dfont")) { // This is a Macintosh font suitcase resource - ResourceHeader resourceHeader = new ResourceHeader(dis); + final ResourceHeader resourceHeader = new ResourceHeader(dis); // Seek to the map offset and read the map dis.reset(); dis.skip(resourceHeader.getMapOffset()); - ResourceMap map = new ResourceMap(dis); + final ResourceMap map = new ResourceMap(dis); if( DEBUG ) { // Dump some info about the font suitcase @@ -102,16 +159,15 @@ class OTFontCollection { } // Get the 'sfnt' resources - ResourceType resourceType = map.getResourceType("sfnt"); + final ResourceType resourceType = map.getResourceType("sfnt"); // Load the font data _fonts = new OTFont[resourceType.getCount()]; for (int i = 0; i < resourceType.getCount(); i++) { - ResourceReference resourceReference = resourceType.getReference(i); - int offset = resourceHeader.getDataOffset() + - resourceReference.getDataOffset() + 4; - byte[] fontData = Files.readAllBytes(file.toPath()); - _fonts[i] = new TTFont(fontData, offset /*, offset*/); + final ResourceReference resourceReference = resourceType.getReference(i); + final int offset = resourceHeader.getDataOffset() + + resourceReference.getDataOffset() + 4; + _fonts[i] = new TTFont(dis, offset, offset); } } else if (TTCHeader.isTTC(dis)) { @@ -119,17 +175,15 @@ class OTFontCollection { // This is a TrueType font collection dis.reset(); _ttcHeader = new TTCHeader(dis); - _fonts = new OTFont[_ttcHeader.getDirectoryCount()]; + _fonts = new TTFont[_ttcHeader.getDirectoryCount()]; for (int i = 0; i < _ttcHeader.getDirectoryCount(); i++) { - byte[] fontData = Files.readAllBytes(file.toPath()); - _fonts[i] = new TTFont(fontData, _ttcHeader.getTableDirectory(i)); + _fonts[i] = new TTFont(dis, _ttcHeader.getTableDirectory(i), 0); } } else { // This is a standalone font file - _fonts = new OTFont[1]; - byte[] fontData = Files.readAllBytes(file.toPath()); - _fonts[0] = new TTFont(fontData, 0); + _fonts = new TTFont[1]; + _fonts[0] = new TTFont(dis, 0, 0); // TODO T2Fonts } diff --git a/src/main/java/net/java/dev/typecast/ot/TTFont.java b/src/main/java/net/java/dev/typecast/ot/TTFont.java index 55f333c..4505f1b 100644 --- a/src/main/java/net/java/dev/typecast/ot/TTFont.java +++ b/src/main/java/net/java/dev/typecast/ot/TTFont.java @@ -18,40 +18,105 @@ package net.java.dev.typecast.ot; -import net.java.dev.typecast.ot.table.*; - -import java.io.ByteArrayInputStream; +import java.io.BufferedInputStream; import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; + +import net.java.dev.typecast.ot.table.GaspTable; +import net.java.dev.typecast.ot.table.GlyfDescript; +import net.java.dev.typecast.ot.table.GlyfTable; +import net.java.dev.typecast.ot.table.HdmxTable; +import net.java.dev.typecast.ot.table.KernTable; +import net.java.dev.typecast.ot.table.LocaTable; +import net.java.dev.typecast.ot.table.Table; +import net.java.dev.typecast.ot.table.TableDirectory; +import net.java.dev.typecast.ot.table.VdmxTable; public class TTFont extends OTFont { - private GlyfTable _glyf; + private final GlyfTable _glyf; private GaspTable _gasp; private KernTable _kern; private HdmxTable _hdmx; private VdmxTable _vdmx; + private static TableDirectory readTableDir(final DataInputStream dis, final int directoryOffset) throws IOException { + // Load the table directory + dis.reset(); // throws if not marked or mark not supported + dis.skip(directoryOffset); + return new TableDirectory(dis); + } + + private static DataInputStream openStream(final File file) throws IOException { + if (!file.exists()) { + throw new IOException("File <"+file.getName()+"> doesn't exist."); + } + final int streamLen = (int) file.length(); + final BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), streamLen); + if( !bis.markSupported() ) { + throw new IllegalArgumentException("stream of type "+bis.getClass().getName()+" doesn't support mark"); + } + bis.mark(streamLen); + return new DataInputStream(bis); + } + + private static DataInputStream openStream(final InputStream is, final int streamLen) throws IOException { + final BufferedInputStream bis = new BufferedInputStream(is, streamLen); + if( !bis.markSupported() ) { + throw new IllegalArgumentException("stream of type "+is.getClass().getName()+" doesn't support mark"); + } + bis.mark(streamLen); + return new DataInputStream(bis); + } + /** * Constructor - * - * @param fontData + * @param file standalone font file * @param tablesOrigin + * @throws IOException */ - public TTFont(byte[] fontData, int tablesOrigin) throws IOException { - super(fontData, tablesOrigin); + public TTFont(final File file) throws IOException { + this(openStream(file), 0, 0); + } - // Load the table directory -// dis.skip(directoryOffset); - TableDirectory tableDirectory = new TableDirectory(fontData); + /** + * Constructor + * @param is standalone font input stream + * @param streamLen length of input stream to rewind across whole font data set + * @throws IOException + */ + public TTFont(final InputStream is, final int streamLen) throws IOException { + this(openStream(is, streamLen), 0, 0); + } + + /** + * Constructor + * @param dis input stream marked at start with read-ahead set to known stream length + * @param directoryOffset + * @param tablesOrigin + * @return + * @throws IOException + */ + public TTFont(final DataInputStream dis, final int directoryOffset, final int tablesOrigin) throws IOException { + this(dis, readTableDir(dis, directoryOffset), tablesOrigin); + } - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(fontData)); - dis.mark(fontData.length); - dis.reset(); + /** + * + * @param dis input stream marked at start with read-ahead set to known stream length + * @param tableDirectory + * @param tablesOrigin + * @throws IOException + */ + TTFont(final DataInputStream dis, final TableDirectory tableDirectory, final int tablesOrigin) throws IOException { + super(dis, tableDirectory, tablesOrigin); // 'loca' is required by 'glyf' int length = seekTable(tableDirectory, dis, tablesOrigin, Table.loca); - LocaTable loca = new LocaTable(dis, length, this.getHeadTable(), this.getMaxpTable()); + final LocaTable loca = new LocaTable(dis, length, this.getHeadTable(), this.getMaxpTable()); // If this is a TrueType outline, then we'll have at least the // 'glyf' table (along with the 'loca' table) diff --git a/src/main/java/net/java/dev/typecast/ot/table/TableDirectory.java b/src/main/java/net/java/dev/typecast/ot/table/TableDirectory.java index 733a7e1..dbdf8fb 100644 --- a/src/main/java/net/java/dev/typecast/ot/table/TableDirectory.java +++ b/src/main/java/net/java/dev/typecast/ot/table/TableDirectory.java @@ -50,9 +50,7 @@ package net.java.dev.typecast.ot.table; -import java.io.ByteArrayInputStream; import java.io.DataInput; -import java.io.DataInputStream; import java.io.IOException; import net.java.dev.typecast.ot.Fixed; @@ -114,16 +112,15 @@ public class TableDirectory { private final short _rangeShift; private final Entry[] _entries; - public TableDirectory(byte[] fontData) throws IOException { - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(fontData)); - _version = dis.readInt(); - _numTables = dis.readShort(); - _searchRange = dis.readShort(); - _entrySelector = dis.readShort(); - _rangeShift = dis.readShort(); + public TableDirectory(final DataInput di) throws IOException { + _version = di.readInt(); + _numTables = di.readShort(); + _searchRange = di.readShort(); + _entrySelector = di.readShort(); + _rangeShift = di.readShort(); _entries = new Entry[_numTables]; for (int i = 0; i < _numTables; i++) { - _entries[i] = new Entry(dis); + _entries[i] = new Entry(di); } } diff --git a/src/test/java/net/java/dev/typecast/exchange/SVGExporterTest.java b/src/test/java/net/java/dev/typecast/exchange/SVGExporterTest.java index 11c8d4a..e9c58c5 100644 --- a/src/test/java/net/java/dev/typecast/exchange/SVGExporterTest.java +++ b/src/test/java/net/java/dev/typecast/exchange/SVGExporterTest.java @@ -27,8 +27,10 @@ import net.java.dev.typecast.ot.table.TableException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; import java.nio.file.Files; public class SVGExporterTest extends TestCase { @@ -49,10 +51,13 @@ public class SVGExporterTest extends TestCase { } public void testExportFont() throws URISyntaxException, IOException, TableException { - URL url = ClassLoader.getSystemResource("Lato-Regular.ttf"); - File file = new File(url.toURI()); - byte[] fontData = Files.readAllBytes(file.toPath()); - TTFont font = new TTFont(fontData, 0); + final URL url = ClassLoader.getSystemResource("Lato-Regular.ttf"); + final URLConnection con = url.openConnection(); + final int len = con.getContentLength(); + TTFont font; + try(InputStream is = con.getInputStream()) { + font = new TTFont(is, len); + } SVGExporter exporter = new SVGExporter(font, 32, 32, "abc", true, false); ByteArrayOutputStream baos = new ByteArrayOutputStream(); exporter.export(baos); diff --git a/src/test/java/net/java/dev/typecast/ot/TTFontTest.java b/src/test/java/net/java/dev/typecast/ot/TTFontTest.java index 03076b9..1eb72ba 100644 --- a/src/test/java/net/java/dev/typecast/ot/TTFontTest.java +++ b/src/test/java/net/java/dev/typecast/ot/TTFontTest.java @@ -21,6 +21,7 @@ package net.java.dev.typecast.ot; import java.io.*; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; import java.nio.file.Files; import junit.framework.Test; @@ -46,10 +47,12 @@ public class TTFontTest extends TestCase { } public void testLoadFont() throws URISyntaxException, IOException { - URL url = ClassLoader.getSystemResource("Lato-Regular.ttf"); - File file = new File(url.toURI()); - byte[] fontData = Files.readAllBytes(file.toPath()); - TTFont font = new TTFont(fontData, 0); - assertEquals(HeadTable.class, font.getHeadTable().getClass()); + final URL url = ClassLoader.getSystemResource("Lato-Regular.ttf"); + final URLConnection con = url.openConnection(); + final int len = con.getContentLength(); + try(InputStream is = con.getInputStream()) { + TTFont font = new TTFont(is, len); + assertEquals(HeadTable.class, font.getHeadTable().getClass()); + } } } |