diff --git a/scripts/ExportFonts.hx b/scripts/ExportFonts.hx new file mode 100644 index 0000000..8b88295 --- /dev/null +++ b/scripts/ExportFonts.hx @@ -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 \n"); + Sys.println("Usage (Direct/Eval - May cause memory issues on large SWFs):"); + Sys.println(" haxe --run ExportFonts "); + 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(); + + 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('\n'); + buf.add('\n'); + + for (charCode in font.glyphs.keys()) + { + var glyph = font.glyphs.get(charCode); + buf.add('\t\n'); + + if (glyph.pathData != "") + { + buf.add('\t\t\n'); + } + + buf.add('\t\n'); + } + + buf.add(''); + return buf.toString(); + } +} diff --git a/src/swf/exporters/NativeFontExporter.hx b/src/swf/exporters/NativeFontExporter.hx new file mode 100644 index 0000000..1078bcb --- /dev/null +++ b/src/swf/exporters/NativeFontExporter.hx @@ -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 + { + var exportedFonts = new Array(); + var fontTagsToProcess = new Array(); + + 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() + }; + + 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, 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; + } +} diff --git a/src/swf/exporters/SWFVectorFont.hx b/src/swf/exporters/SWFVectorFont.hx new file mode 100644 index 0000000..04366d4 --- /dev/null +++ b/src/swf/exporters/SWFVectorFont.hx @@ -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; +}