Skip to content
This repository was archived by the owner on Jan 21, 2024. It is now read-only.

Commit a095992

Browse files
author
Mitch Talmadge
committed
Apple based emoji fonts (with sbix tables) are now extracted VERY
quickly, with full unicode name support. All other fonts extract the same way.
1 parent e555b3e commit a095992

File tree

6 files changed

+672
-48
lines changed

6 files changed

+672
-48
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package me.MitchT.EmojiExtractor;
2+
3+
import java.io.File;
4+
import java.io.FileInputStream;
5+
import java.io.FileNotFoundException;
6+
import java.io.IOException;
7+
import java.io.RandomAccessFile;
8+
import java.nio.ByteBuffer;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
12+
import me.MitchT.EmojiExtractor.Extractors.AppleExtractionThread;
13+
import me.MitchT.EmojiExtractor.Extractors.ExtractionThread;
14+
import me.MitchT.EmojiExtractor.Extractors.StandardExtractionThread;
15+
import me.MitchT.EmojiExtractor.GUI.MainFrame;
16+
import me.MitchT.EmojiExtractor.GUI.ProgressPanel;
17+
18+
public class ExtractionManager
19+
{
20+
21+
private File font;
22+
private MainFrame mainFrame;
23+
24+
private ExtractionThread extractionThread;
25+
26+
27+
public ExtractionManager(File font, MainFrame mainFrame, ProgressPanel progressPanel)
28+
{
29+
this.font = font;
30+
this.mainFrame = mainFrame;
31+
32+
//Determine which Extraction Method to use
33+
try
34+
{
35+
RandomAccessFile inputStream = new RandomAccessFile(font, "r");
36+
37+
byte[] b = new byte[4];
38+
inputStream.readFully(b);
39+
40+
if(!ExtractionUtilites.compareBytes(b, (byte)0x00,(byte)0x01,(byte)0x00,(byte)0x00))
41+
{
42+
showMessagePanel("Selected Font is not a valid True Type Font! (*.ttf)");
43+
inputStream.close();
44+
return;
45+
}
46+
47+
b = new byte[2];
48+
inputStream.readFully(b);
49+
50+
short numTables = ExtractionUtilites.getShortFromBytes(b);
51+
List<String> tableNames = Arrays.asList(new String[numTables]);
52+
List<Integer> tableOffsets = Arrays.asList(new Integer[numTables]);
53+
List<Integer> tableLengths = Arrays.asList(new Integer[numTables]);
54+
55+
inputStream.seek(12);
56+
b = new byte[4];
57+
for(int i = 0; i < numTables; i++)
58+
{
59+
inputStream.readFully(b);
60+
tableNames.set(i, ExtractionUtilites.getStringFromBytes(b));
61+
inputStream.skipBytes(4);
62+
inputStream.readFully(b);
63+
tableOffsets.set(i, ExtractionUtilites.getIntFromBytes(b));
64+
inputStream.readFully(b);
65+
tableLengths.set(i, ExtractionUtilites.getIntFromBytes(b));
66+
}
67+
68+
if(tableNames.contains("sbix"))
69+
extractionThread = new AppleExtractionThread(font, tableNames, tableOffsets, tableLengths, this, progressPanel);
70+
else
71+
extractionThread = new StandardExtractionThread(font, this, progressPanel);
72+
73+
inputStream.close();
74+
}
75+
catch(FileNotFoundException e)
76+
{
77+
e.printStackTrace();
78+
}
79+
catch(IOException e)
80+
{
81+
e.printStackTrace();
82+
}
83+
}
84+
85+
public void startExtraction()
86+
{
87+
extractionThread.start();
88+
}
89+
90+
public void stopExtraction()
91+
{
92+
if(extractionThread != null && extractionThread.isAlive())
93+
{
94+
extractionThread.endExtraction();
95+
}
96+
}
97+
98+
public void showMessagePanel(String message)
99+
{
100+
this.mainFrame.showMessagePanel(message);
101+
}
102+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package me.MitchT.EmojiExtractor;
2+
3+
import java.nio.ByteBuffer;
4+
import java.util.Arrays;
5+
6+
public class ExtractionUtilites
7+
{
8+
9+
public static short getShortFromBytes(byte[] bytes)
10+
{
11+
return ByteBuffer.wrap(bytes).getShort();
12+
}
13+
14+
public static int getIntFromBytes(byte[] bytes)
15+
{
16+
return ByteBuffer.wrap(bytes).getInt();
17+
}
18+
19+
public static String getStringFromBytes(byte[] bytes)
20+
{
21+
return new String(bytes);
22+
}
23+
24+
public static boolean compareBytes(byte[] bytes, byte ... compare)
25+
{
26+
return Arrays.equals(bytes, compare);
27+
}
28+
29+
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package me.MitchT.EmojiExtractor.Extractors;
2+
3+
import java.io.File;
4+
import java.io.FileNotFoundException;
5+
import java.io.FileOutputStream;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.io.RandomAccessFile;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import me.MitchT.EmojiExtractor.EmojiExtractor;
13+
import me.MitchT.EmojiExtractor.ExtractionManager;
14+
import me.MitchT.EmojiExtractor.ExtractionUtilites;
15+
import me.MitchT.EmojiExtractor.GUI.ProgressPanel;
16+
17+
public class AppleExtractionThread extends ExtractionThread
18+
{
19+
private static final File emojisDir = new File(EmojiExtractor.getRootDirectory() + "/ExtractedEmojis");
20+
21+
private long currentBytePos = 0;
22+
23+
private List<String> tableNames;
24+
private List<Integer> tableOffsets;
25+
private List<Integer> tableLengths;
26+
27+
private short numGlyphs = 0;
28+
private String[] glyphNames;
29+
30+
private ExtractionManager extractionManager;
31+
private ProgressPanel progressPanel;
32+
33+
private long startTime = 0;
34+
private long currTime = 0;
35+
36+
public AppleExtractionThread(File font, List<String> tableNames, List<Integer> tableOffsets, List<Integer> tableLengths, ExtractionManager extractionManager, ProgressPanel progressPanel)
37+
{
38+
super(font);
39+
40+
this.tableNames = tableNames;
41+
this.tableOffsets = tableOffsets;
42+
this.tableLengths = tableLengths;
43+
44+
this.extractionManager = extractionManager;
45+
this.progressPanel = progressPanel;
46+
}
47+
48+
@Override
49+
public void run()
50+
{
51+
try
52+
{
53+
RandomAccessFile inputStream = new RandomAccessFile(font, "r");
54+
55+
if(!emojisDir.exists())
56+
{
57+
emojisDir.mkdir();
58+
}
59+
60+
startTime = System.currentTimeMillis();
61+
62+
//Get numGlyphs, ordinal numbers, and glyphNames from post table
63+
int postIndex = tableNames.indexOf("post");
64+
if(postIndex > -1)
65+
{
66+
int offset = tableOffsets.get(postIndex);
67+
int length = tableLengths.get(postIndex);
68+
69+
inputStream.seek(offset);
70+
71+
b = new byte[4];
72+
inputStream.readFully(b);
73+
74+
if(!ExtractionUtilites.compareBytes(b, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00))
75+
{
76+
extractionManager.showMessagePanel("Invalid 'post' table format! Contact developer for help.");
77+
inputStream.close();
78+
return;
79+
}
80+
81+
inputStream.skipBytes(28);
82+
b = new byte[2];
83+
inputStream.readFully(b);
84+
85+
numGlyphs = ExtractionUtilites.getShortFromBytes(b);
86+
short[] ordinalNumbers = new short[numGlyphs];
87+
88+
short numNewGlyphs = 0;
89+
90+
for(int i = 0; i < numGlyphs; i++)
91+
{
92+
inputStream.readFully(b);
93+
ordinalNumbers[i] = ExtractionUtilites.getShortFromBytes(b);
94+
if(ordinalNumbers[i] > 257)
95+
numNewGlyphs++;
96+
}
97+
98+
String[] extraNames = new String[numNewGlyphs];
99+
100+
for(int i = 0; i < numNewGlyphs; i++)
101+
{
102+
103+
short nameLen = (short) inputStream.read();
104+
b = new byte[nameLen];
105+
inputStream.readFully(b);
106+
extraNames[i] = ExtractionUtilites.getStringFromBytes(b);
107+
}
108+
109+
//Build list of names for GlyphIDs
110+
glyphNames = new String[numGlyphs];
111+
for(int i = 0; i < numGlyphs; i++)
112+
{
113+
if(ordinalNumbers[i] <= 257)
114+
{
115+
glyphNames[i] = standardOrderNames[ordinalNumbers[i]];
116+
}
117+
else
118+
{
119+
glyphNames[i] = extraNames[ordinalNumbers[i] - 258];
120+
}
121+
}
122+
}
123+
else
124+
{
125+
extractionManager.showMessagePanel("Could not find 'post' table! Contact developer for help.");
126+
inputStream.close();
127+
return;
128+
}
129+
130+
//Get number of strikes, and scan for PNG files.
131+
int sbixIndex = tableNames.indexOf("sbix");
132+
if(sbixIndex > -1)
133+
{
134+
int offset = tableOffsets.get(sbixIndex);
135+
int length = tableLengths.get(sbixIndex);
136+
137+
inputStream.seek(offset);
138+
139+
inputStream.skipBytes(4);
140+
141+
b = new byte[4];
142+
inputStream.readFully(b);
143+
144+
int numStrikes = ExtractionUtilites.getIntFromBytes(b);
145+
int[] strikeOffsets = new int[numStrikes];
146+
for(int i = 0; i < numStrikes; i++)
147+
{
148+
inputStream.readFully(b);
149+
strikeOffsets[i] = ExtractionUtilites.getIntFromBytes(b);
150+
}
151+
152+
//TODO: Figure out how to convert rgbl to png.. for now, use last strike..
153+
inputStream.seek(offset+strikeOffsets[strikeOffsets.length-1]);
154+
inputStream.skipBytes(4);
155+
156+
int[] glyphOffsets = new int[numGlyphs];
157+
int[] glyphLengths = new int[numGlyphs];
158+
b = new byte[4];
159+
160+
for(int i = 0; i < numGlyphs; i++)
161+
{
162+
inputStream.readFully(b);
163+
glyphOffsets[i] = ExtractionUtilites.getIntFromBytes(b);
164+
}
165+
166+
for(int i = 0; i < numGlyphs; i++)
167+
{
168+
if(i+1 == numGlyphs)
169+
glyphLengths[i] = length - strikeOffsets[strikeOffsets.length-1] - glyphOffsets[i];
170+
else
171+
glyphLengths[i] = glyphOffsets[i+1] - glyphOffsets[i];
172+
}
173+
174+
inputStream.seek(offset+strikeOffsets[strikeOffsets.length-1]);
175+
inputStream.skipBytes(glyphOffsets[0]);
176+
177+
for(int i = 0; i < numGlyphs; i++)
178+
{
179+
if(glyphLengths[i] == 0)
180+
continue;
181+
else
182+
{
183+
inputStream.skipBytes(4);
184+
b = new byte[4];
185+
inputStream.readFully(b);
186+
if(ExtractionUtilites.getStringFromBytes(b).equals("png "))
187+
{
188+
System.out.println("Extracting Glyph #" + i + " to '" + glyphNames[i] + ".png'");
189+
FileOutputStream outputStream = new FileOutputStream(new File(emojisDir, glyphNames[i] + ".png"));
190+
b = new byte[glyphLengths[i] - 8];
191+
inputStream.readFully(b);
192+
outputStream.write(b);
193+
outputStream.close();
194+
}
195+
}
196+
}
197+
}
198+
else
199+
{
200+
extractionManager.showMessagePanel("Could not find 'sbix' table! Contact developer for help.");
201+
inputStream.close();
202+
return;
203+
}
204+
inputStream.close();
205+
206+
System.out.println("No more Emojis to extract! All done! :)");
207+
extractionManager.showMessagePanel("No more Emojis to extract! All done! :)");
208+
return;
209+
}
210+
catch(FileNotFoundException e)
211+
{
212+
System.out.println(this.font.getName() + " not found!");
213+
extractionManager.showMessagePanel(this.font.getName() + " not found!");
214+
}
215+
catch(IOException e)
216+
{
217+
e.printStackTrace();
218+
}
219+
}
220+
221+
private void updateProgress()
222+
{
223+
this.currTime = System.currentTimeMillis();
224+
progressPanel.setProgress(currentBytePos, this.font.length());
225+
progressPanel.setTimeRemaining(currentBytePos, this.font.length(), currTime, startTime);
226+
}
227+
228+
}

0 commit comments

Comments
 (0)