├── .gitignore ├── InvalidWoffException.java ├── LICENSE ├── README.md └── WoffConverter.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | -------------------------------------------------------------------------------- /InvalidWoffException.java: -------------------------------------------------------------------------------- 1 | @SuppressWarnings("serial") 2 | public class InvalidWoffException extends RuntimeException { 3 | 4 | public InvalidWoffException() { 5 | super(); 6 | } 7 | 8 | public InvalidWoffException(String message) { 9 | super(message); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 anupthegit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #WoffConverter 2 | 3 | This is a Java library to convert WOFF font files to TTF format. There are currently two library functions. Both accept the TTF InputStream as input. The `convertToTTFByteArray` method returns a byte array whereas the `convertToTTFOutputStream` method returns a ByteArrayOutputStream. 4 | 5 | ##Installation 6 | 7 | Just download the [jar file](https://github.com/anupthegit/WOFFToTTFJava/releases/download/v1.0/WoffConverter.jar) and add it to your classpath 8 | 9 | ##TODOs 10 | 11 | * Add library functions for other input and output methods 12 | * Add font validation 13 | -------------------------------------------------------------------------------- /WoffConverter.java: -------------------------------------------------------------------------------- 1 | import java.io.ByteArrayOutputStream; 2 | import java.io.DataInputStream; 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.ByteBuffer; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.LinkedHashMap; 10 | import java.util.zip.DataFormatException; 11 | import java.util.zip.Inflater; 12 | 13 | public class WoffConverter { 14 | 15 | @SuppressWarnings("serial") 16 | private static final LinkedHashMap woffHeaderFormat = new LinkedHashMap() { 17 | { 18 | put("signature", 4); 19 | put("flavor", 4); 20 | put("length", 4); 21 | put("numTables", 2); 22 | put("reserved", 2); 23 | put("totalSfntSize", 4); 24 | put("majorVersion", 2); 25 | put("minorVersion", 2); 26 | put("metaOffset", 4); 27 | put("metaLength", 4); 28 | put("metaOrigLength", 4); 29 | put("privOffset", 4); 30 | put("privOrigLength", 4); 31 | } 32 | }; 33 | 34 | @SuppressWarnings("serial") 35 | private static final LinkedHashMap tableRecordEntryFormat = new LinkedHashMap() { 36 | { 37 | put("tag", 4); 38 | put("offset", 4); 39 | put("compLength", 4); 40 | put("origLength", 4); 41 | put("origChecksum", 4); 42 | } 43 | }; 44 | 45 | private HashMap woffHeaders = new HashMap(); 46 | 47 | private ArrayList> tableRecordEntries = new ArrayList>(); 48 | 49 | private int offset = 0; 50 | 51 | private int readOffset = 0; 52 | 53 | public byte[] convertToTTFByteArray(InputStream woffFileStream) 54 | throws InvalidWoffException, IOException, DataFormatException { 55 | ByteArrayOutputStream ttfOutputStream = convertToTTFOutputStream(woffFileStream); 56 | byte[] ttfByteArray = ttfOutputStream.toByteArray(); 57 | return ttfByteArray; 58 | } 59 | 60 | public ByteArrayOutputStream convertToTTFOutputStream(InputStream woffFileStream) 61 | throws InvalidWoffException, IOException, DataFormatException { 62 | getHeaders(new DataInputStream(woffFileStream)); 63 | if ((Integer) woffHeaders.get("signature") != 0x774F4646) { 64 | throw new InvalidWoffException("Invalid woff file"); 65 | } 66 | ByteArrayOutputStream ttfOutputStream = new ByteArrayOutputStream(); 67 | writeOffsetTable(ttfOutputStream); 68 | getTableRecordEntries(new DataInputStream(woffFileStream)); 69 | writeTableRecordEntries(ttfOutputStream); 70 | writeFontData(woffFileStream, ttfOutputStream); 71 | return ttfOutputStream; 72 | } 73 | 74 | private void getHeaders(DataInputStream woffFileStream) throws IOException { 75 | readTableData(woffFileStream, woffHeaderFormat, woffHeaders); 76 | } 77 | 78 | private void writeOffsetTable(ByteArrayOutputStream ttfOutputStream) 79 | throws IOException { 80 | ttfOutputStream.write(getBytes((Integer) woffHeaders.get("flavor"))); 81 | int numTables = (Integer) woffHeaders.get("numTables"); 82 | ttfOutputStream.write(getBytes((short)numTables)); 83 | int temp = numTables; 84 | int searchRange = 16; 85 | short entrySelector = 0; 86 | while (temp > 1) { 87 | temp = temp >> 1; 88 | entrySelector++; 89 | searchRange = (searchRange << 1); 90 | } 91 | short rangeShift = (short) (numTables * 16 - searchRange); 92 | ttfOutputStream.write(getBytes((short) searchRange)); 93 | ttfOutputStream.write(getBytes(entrySelector)); 94 | ttfOutputStream.write(getBytes(rangeShift)); 95 | offset += 12; 96 | } 97 | 98 | private void getTableRecordEntries(DataInputStream woffFileStream) 99 | throws IOException { 100 | int numTables = (Integer) woffHeaders.get("numTables"); 101 | for (int i = 0; i < numTables; i++) { 102 | HashMap tableDirectory = new HashMap(); 103 | readTableData(woffFileStream, tableRecordEntryFormat, 104 | tableDirectory); 105 | offset += 16; 106 | tableRecordEntries.add(tableDirectory); 107 | } 108 | } 109 | 110 | private void writeTableRecordEntries(ByteArrayOutputStream ttfOutputStream) 111 | throws IOException { 112 | for (HashMap tableRecordEntry : tableRecordEntries) { 113 | ttfOutputStream.write(getBytes((Integer) tableRecordEntry 114 | .get("tag"))); 115 | ttfOutputStream.write(getBytes((Integer) tableRecordEntry 116 | .get("origChecksum"))); 117 | ttfOutputStream.write(getBytes(offset)); 118 | ttfOutputStream.write(getBytes((Integer) tableRecordEntry 119 | .get("origLength"))); 120 | tableRecordEntry.put("outOffset", offset); 121 | offset += (Integer) tableRecordEntry.get("origLength"); 122 | if (offset % 4 != 0) { 123 | offset += 4 - (offset % 4); 124 | } 125 | } 126 | } 127 | 128 | private void writeFontData(InputStream woffFileStream, 129 | ByteArrayOutputStream ttfOutputStream) throws IOException, 130 | DataFormatException { 131 | for (HashMap tableRecordEntry : tableRecordEntries) { 132 | int tableRecordEntryOffset = (Integer) tableRecordEntry 133 | .get("offset"); 134 | int skipBytes = tableRecordEntryOffset - readOffset; 135 | if (skipBytes > 0) 136 | woffFileStream.skip(skipBytes); 137 | readOffset += skipBytes; 138 | int compressedLength = (Integer) tableRecordEntry.get("compLength"); 139 | int origLength = (Integer) tableRecordEntry.get("origLength"); 140 | byte[] fontData = new byte[compressedLength]; 141 | byte[] inflatedFontData = new byte[origLength]; 142 | int readBytes = 0; 143 | while (readBytes < compressedLength) { 144 | readBytes += woffFileStream.read(fontData, readBytes, 145 | compressedLength - readBytes); 146 | } 147 | readOffset += compressedLength; 148 | inflatedFontData = inflateFontData(compressedLength, 149 | origLength, fontData, inflatedFontData); 150 | ttfOutputStream.write(inflatedFontData); 151 | offset = (Integer) tableRecordEntry.get("outOffset") 152 | + (Integer) tableRecordEntry.get("origLength"); 153 | int padding = 0; 154 | if (offset % 4 != 0) 155 | padding = 4 - (offset % 4); 156 | ttfOutputStream.write(getBytes(0), 0, padding); 157 | } 158 | } 159 | 160 | private byte[] inflateFontData(int compressedLength, int origLength, 161 | byte[] fontData, byte[] inflatedFontData) { 162 | if (compressedLength != origLength) { 163 | Inflater decompressor = new Inflater(); 164 | decompressor.setInput(fontData, 0, compressedLength); 165 | try { 166 | decompressor.inflate(inflatedFontData, 0, origLength); 167 | } catch (DataFormatException e) { 168 | throw new InvalidWoffException("Malformed woff file"); 169 | } 170 | } else 171 | inflatedFontData = fontData; 172 | return inflatedFontData; 173 | } 174 | 175 | private byte[] getBytes(int i) { 176 | return ByteBuffer.allocate(4).putInt(i).array(); 177 | } 178 | 179 | private byte[] getBytes(short h) { 180 | return ByteBuffer.allocate(2).putShort(h).array(); 181 | } 182 | 183 | private void readTableData(DataInputStream woffFileStream, 184 | LinkedHashMap formatTable, 185 | HashMap table) throws IOException { 186 | Iterator headerKeys = formatTable.keySet().iterator(); 187 | while (headerKeys.hasNext()) { 188 | String key = headerKeys.next(); 189 | int size = formatTable.get(key); 190 | if (size == 2) { 191 | table.put(key, woffFileStream.readUnsignedShort()); 192 | } else if (size == 4) { 193 | table.put(key, woffFileStream.readInt()); 194 | } 195 | readOffset += size; 196 | } 197 | } 198 | } --------------------------------------------------------------------------------