Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions scripts/ExportFonts.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package;

import sys.io.File;
import sys.FileSystem;
import haxe.io.Path;
import swf.SWF;
import openfl.utils.ByteArray;
import swf.exporters.NativeFontExporter;
import swf.exporters.SWFVectorFont;

class ExportFonts
{
public static function main()
{
var args = Sys.args();
if (args.length < 2)
{
Sys.println("ExportFonts - Native SWF Vector Extractor\n");
Sys.println("Usage (Recommended - Neko):");
Sys.println(" 1. Build: haxe -cp src -cp scripts -lib openfl -lib lime -lib format -D optional-cffi -main ExportFonts -neko export_fonts.n");
Sys.println(" 2. Run: neko export_fonts.n <input.swf> <output_folder>\n");
Sys.println("Usage (Direct/Eval - May cause memory issues on large SWFs):");
Sys.println(" haxe --run ExportFonts <input.swf> <output_folder>");
return;
}

var swfPath = args[0];
var outputDir = args[1];

if (!FileSystem.exists(swfPath))
{
Sys.println("Error: Input SWF file not found at path: " + swfPath);
return;
}

Sys.println("Reading SWF: " + swfPath);
var rawBytes = File.getBytes(swfPath);
var bytes = ByteArray.fromBytes(rawBytes);

bytes.position = 0;
bytes.endian = openfl.utils.Endian.LITTLE_ENDIAN;

var swf = new SWF(bytes);

Sys.println("Extracting Vector Fonts natively...");
var fonts = NativeFontExporter.extract(swf);

if (!FileSystem.exists(outputDir))
{
FileSystem.createDirectory(outputDir);
}

var nameCount = new Map<String, Int>();

for (font in fonts)
{
var baseName = font.name.split(" ").join("_");
var safeName = baseName;

if (nameCount.exists(baseName))
{
var count = nameCount.get(baseName) + 1;
nameCount.set(baseName, count);
safeName = baseName + "_" + count;
}
else
{
nameCount.set(baseName, 1);
}

var xmlContent = generateXML(font);

var filePath = Path.join([outputDir, safeName + ".xml"]);
File.saveContent(filePath, xmlContent);
Sys.println("Saved: " + filePath);
}

Sys.println("Done! Extracted " + fonts.length + " individual XML fonts.");
}

private static function generateXML(font:SWFVectorFont):String
{
var buf = new StringBuf();

buf.add('<?xml version="1.0" encoding="utf-8"?>\n');
buf.add('<font name="${font.name}" ascent="${font.ascent}" descent="${font.descent}" leading="${font.leading}">\n');

for (charCode in font.glyphs.keys())
{
var glyph = font.glyphs.get(charCode);
buf.add('\t<glyph charCode="$charCode" advance="${glyph.advance}">\n');

if (glyph.pathData != "")
{
buf.add('\t\t<path d="');
buf.add(glyph.pathData);
buf.add('"/>\n');
}

buf.add('\t</glyph>\n');
}

buf.add('</font>');
return buf.toString();
}
}
150 changes: 150 additions & 0 deletions src/swf/exporters/NativeFontExporter.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package swf.exporters;

import swf.SWF;
import swf.tags.TagDefineFont2;
import swf.tags.TagDefineFont3;
import swf.exporters.ShapeCommandExporter;
import swf.exporters.core.ShapeCommand;
import swf.exporters.SWFVectorFont;
import sys.thread.Thread;
import sys.thread.Mutex;
import sys.thread.Lock;

class NativeFontExporter
{
private static inline var TWIPS:Float = 20.0;

public static function extract(swf:SWF):Array<SWFVectorFont>
{
var exportedFonts = new Array<SWFVectorFont>();
var fontTagsToProcess = new Array<TagDefineFont2>();

for (tag in swf.data.tags)
{
if (Std.isOfType(tag, TagDefineFont2))
{
fontTagsToProcess.push(cast tag);
}
}

if (fontTagsToProcess.length == 0) return exportedFonts;

var mutex = new Mutex();
var lock = new Lock();

for (fontTag in fontTagsToProcess)
{
Thread.create(function()
{
var parsedFont = processFontTag(fontTag);

mutex.acquire();
exportedFonts.push(parsedFont);
mutex.release();
lock.release();
});
}

for (i in 0...fontTagsToProcess.length)
{
lock.wait();
}

return exportedFonts;
}

private static function processFontTag(fontTag:TagDefineFont2):SWFVectorFont
{
var isFont3 = Std.isOfType(fontTag, TagDefineFont3);
var scale = isFont3 ? (TWIPS * 20.0) : TWIPS;

var font:SWFVectorFont = {
name: fontTag.fontName != null ? fontTag.fontName : "Unknown_" + fontTag.characterId,
ascent: 0,
descent: 0,
leading: 0,
glyphs: new Map<Int, GlyphData>()
};

if (fontTag.hasLayout)
{
font.ascent = roundFloat(fontTag.ascent / scale);
font.descent = roundFloat(fontTag.descent / scale);
font.leading = roundFloat(fontTag.leading / scale);
}

var shapeTable = fontTag.glyphShapeTable;
var codeTable = fontTag.codeTable;

if (shapeTable != null && codeTable != null)
{
for (i in 0...shapeTable.length)
{
if (i >= codeTable.length) break;

var charCode = codeTable[i];
var shape = shapeTable[i];

var shapeExporter = new ShapeCommandExporter(null);
shape.export(shapeExporter);

var finalSvgPath = generateSVGPath(shapeExporter.commands, scale);

var charAdvance = 0.0;
if (fontTag.hasLayout && fontTag.fontAdvanceTable != null && i < fontTag.fontAdvanceTable.length)
{
charAdvance = roundFloat(fontTag.fontAdvanceTable[i] / scale);
}

font.glyphs.set(charCode, {
pathData: finalSvgPath,
advance: charAdvance
});
}
}

return font;
}

private static function generateSVGPath(commands:Array<ShapeCommand>, scale:Float):String
{
if (commands == null || commands.length == 0) return "";

var buf = new StringBuf();
for (cmd in commands)
{
switch (cmd)
{
case MoveTo(x, y):
buf.add("M ");
buf.add(roundFloat(x / scale));
buf.add(" ");
buf.add(roundFloat(y / scale));
buf.add(" ");
case LineTo(x, y):
buf.add("L ");
buf.add(roundFloat(x / scale));
buf.add(" ");
buf.add(roundFloat(y / scale));
buf.add(" ");
case CurveTo(cx, cy, ax, ay):
buf.add("Q ");
buf.add(roundFloat(cx / scale));
buf.add(" ");
buf.add(roundFloat(cy / scale));
buf.add(" ");
buf.add(roundFloat(ax / scale));
buf.add(" ");
buf.add(roundFloat(ay / scale));
buf.add(" ");
default:
}
}
return StringTools.trim(buf.toString());
}

private static inline function roundFloat(val:Float):Float
{
return Math.round(val * 10000.0) / 10000.0;
}
}
16 changes: 16 additions & 0 deletions src/swf/exporters/SWFVectorFont.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package swf.exporters;

typedef GlyphData =
{
var pathData:String;
var advance:Float;
}

typedef SWFVectorFont =
{
var name:String;
var ascent:Float;
var descent:Float;
var leading:Float;
var glyphs:Map<Int, GlyphData>;
}