This document provides a comprehensive analysis of the current state of font support in SWFRecomp and outlines the work required to achieve full font and text rendering functionality.
Current Status: Basic glyph shape parsing only
Completion: ~15% of full font support
Critical Gap: No character-to-glyph mapping (DefineFontInfo not implemented)
- Current Implementation
- Missing Components
- SWF Font System Overview
- Implementation Roadmap
- Technical Specifications
- Code Locations
DefineFont (Tag 10) Parsing - src/swf.cpp:688-735
The current implementation successfully:
- ✅ Parses the font ID from the tag
- ✅ Reads the glyph offset table
- ✅ Calculates the number of glyphs (num_entries)
- ✅ Extracts individual glyph shapes
- ✅ Processes each glyph through
interpretShape() - ✅ Renders glyphs as vector shapes with white fill
Glyph Shape Interpretation - src/swf.cpp:1141-1235
When processing font glyphs:
- Sets
is_font = trueflag - Creates a simple white fill style (RGB: 0xFF, 0xFF, 0xFF)
- Uses simplified rendering path (no line styles)
- Stores glyph geometry as triangulated shapes
DefineFontInfo (Tag 13) - src/swf.cpp:756-761
case SWF_TAG_DEFINE_FONT_INFO:
{
cur_pos += tag.length; // Just skips the entire tag!
break;
}This is critically broken because:
- No character code mappings are stored
- No font metadata is preserved
- Cannot determine which glyph represents which character
- Makes text rendering impossible
Font Storage:
- Font ID is parsed but immediately discarded (line 697)
- No data structure exists to store font information
- No registry to look up fonts by ID
- Glyphs are rendered but not associated with any font
Text Rendering:
- No DefineText/DefineText2 support
- No DefineEditText support
- No text layout engine
- No glyph positioning logic
Tag 13 Structure:
UI16 FontID // Which font this info applies to
UI8 FontNameLen // Length of font name
UI8[] FontName // Font name string (NOT null-terminated!)
UI8 FontFlags // Bit flags (see below)
UI16[] CodeTable // Character codes for each glyph (if WideCodes=1)
UI8[] CodeTable // Character codes for each glyph (if WideCodes=0)
Important: The font name is NOT null-terminated and must be parsed byte-by-byte. The existing SWF_FIELD_STRING field type is defined but not implemented, so manual parsing is required.
Font Flags (Byte Layout):
The SWF spec defines FontFlags as a series of UB (unsigned bit) fields read MSB-first. The byte is structured as:
- UB[2]: Reserved (bits 6-7, MSB)
- UB[1]: SmallText - Anti-aliasing hint (bit 5)
- UB[1]: ShiftJIS - Japanese encoding (bit 4)
- UB[1]: ANSI - ANSI encoding (bit 3)
- UB[1]: Italic (bit 2)
- UB[1]: Bold (bit 1)
- UB[1]: WideCodes - If 1, CodeTable is UI16[]; if 0, UI8[] (bit 0, LSB)
When reading the flags byte directly (LSB to MSB, bit 0 to 7):
- Bit 0 (0x01): WideCodes (1 = 16-bit codes, 0 = 8-bit codes)
- Bit 1 (0x02): Bold
- Bit 2 (0x04): Italic
- Bit 3 (0x08): ANSI encoding
- Bit 4 (0x10): ShiftJIS encoding (Japanese)
- Bit 5 (0x20): SmallText (anti-aliasing hint, SWF 7+)
- Bits 6-7 (0xC0): Reserved
Note: The "Has layout info" flag only appears in DefineFont2, not DefineFontInfo.
Why This Is Critical:
Without DefineFontInfo, you have glyphs but don't know:
- Which glyph represents 'A' vs 'B' vs '1'
- Font name for debugging/logging
- Style information (bold, italic)
- Character encoding (Unicode vs ANSI)
Combines DefineFont + DefineFontInfo + additional metrics in one tag:
UI16 FontID
UI8 FontFlags // Has layout, ShiftJIS, small, ANSI, wide codes, italic, bold
UI8 LanguageCode // 1=Latin, 2=Japanese, 3=Korean, etc.
UI8 FontNameLen
UI8[] FontName
UI16 NumGlyphs
UI32[] OffsetTable // Offsets to each glyph shape
UI16 CodeTableOffset // Offset to code table
SHAPE[] GlyphShapeTable // Shape for each glyph
UI16[] CodeTable // Character code for each glyph
// If HasLayout flag is set:
SI16 FontAscent // Ascender height in EM units
SI16 FontDescent // Descender depth
SI16 FontLeading // Line spacing
SI16[] FontAdvanceTable // Advance width for each glyph
RECT[] FontBoundsTable // Bounding box for each glyph
UI16 KerningCount
KERNINGRECORD[] KerningTable
Advantages over DefineFont + DefineFontInfo:
- All data in one place (easier to parse)
- Includes font metrics (ascent, descent, leading)
- Includes advance widths (proper spacing)
- Includes bounding boxes (accurate positioning)
- Includes kerning pairs (better typography)
Same structure as DefineFont2 but with:
- Always uses 32-bit offsets (supports more glyphs)
- Always uses wide (16-bit) character codes
- Better support for Unicode and large character sets
Introduced in: SWF 10 (Flash Player 10+)
Purpose: Supports the Flash Text Engine with OpenType CFF font embedding
UI16 FontID // ID for this font character
UB[5] FontFlagsReserved // Reserved bits
UB[1] FontFlagsHasFontData // Font is embedded (includes SFNT data)
UB[1] FontFlagsItalic // Italic font
UB[1] FontFlagsBold // Bold font
STRING FontName // Name of the font (null-terminated)
FONTDATA FontData // OpenType CFF font data (optional, based on HasFontData flag)
FontData Structure: When present, contains a complete OpenType CFF font as defined in the OpenType specification. Required tables:
- Required: 'CFF ', 'cmap', 'head', 'maxp', 'OS/2', 'post'
- Required (either): ('hhea' and 'hmtx') OR ('vhea', 'vmtx', and 'VORG')
- Optional: 'GSUB', 'GPOS', 'GDEF', 'BASE'
The 'cmap' table must include one of these Unicode subtables:
- (platform 0, encoding 4)
- (platform 0, encoding 3)
- (platform 3, encoding 10)
- (platform 3, encoding 1)
- (platform 3, encoding 0)
Key Differences from DefineFont/DefineFont2/DefineFont3:
- Uses CFF (Compact Font Format) instead of SWF shape definitions
- Embeds complete OpenType font files
- Designed specifically for Flash Text Engine (not classic TextField)
- More efficient for complex fonts with many glyphs
- Supports advanced typography features (ligatures, kerning via GPOS, etc.)
Similar to DefineFontInfo (tag 13) but adds:
- Language code field (0=Latin, 1=Japanese, 2=Korean, 3=Simplified Chinese, 4=Traditional Chinese)
- Otherwise identical structure to DefineFontInfo
UI16 FontID
UI8 FontNameLen
UI8[] FontName
UI8 FontFlags
UI8 LanguageCode // New in DefineFontInfo2
UI16[] CodeTable // or UI8[] depending on WideCodes flag
UI16 CharacterID // ID for this text object
RECT TextBounds // Bounding rectangle
MATRIX TextMatrix // Transformation matrix
UI8 GlyphBits // Bits per glyph index
UI8 AdvanceBits // Bits per advance value
TEXTRECORD[] TextRecords // Array of text runs
TEXTRECORD Structure:
UI8 TextRecordType // Type flags
UI16 FontID // (if font change)
RGB TextColor // (if color change)
SI16 XOffset // (if position change)
SI16 YOffset // (if position change)
UI16 TextHeight // (if font change)
UI8 GlyphCount // Number of glyphs in this record
GLYPHENTRY[] GlyphEntries
GLYPHENTRY:
UB[GlyphBits] GlyphIndex // Index into font's glyph table
SB[AdvanceBits] GlyphAdvance // Horizontal advance for this glyph
Identical to DefineText but uses RGBA instead of RGB for colors.
UI16 CharacterID
RECT Bounds // Text field boundaries
UI8 Flags1 // HasText, WordWrap, Multiline, Password, etc.
UI8 Flags2 // ReadOnly, HTML, UsesOutlines, etc.
UI16 FontID // Which font to use
UI16 FontHeight // Font size in twips
RGBA TextColor // Text color
UI16 MaxLength // Max characters (if HasMaxLength flag)
UI8 Align // 0=left, 1=right, 2=center, 3=justify
UI16 LeftMargin // In twips
UI16 RightMargin
UI16 Indent
SI16 Leading // Line spacing
STRING VariableName // ActionScript variable name
STRING InitialText // (if HasText flag)
Critical for:
- User input fields
- Dynamic text that changes via ActionScript
- Variables like score displays, usernames, etc.
- HTML text rendering
struct KerningRecord
{
u16 left_char; // First character code
u16 right_char; // Second character code
s16 adjustment; // Kerning adjustment in EM units
};
struct Font
{
// Basic info (DefineFont + DefineFontInfo)
u16 font_id;
std::string font_name;
bool is_bold;
bool is_italic;
bool is_unicode; // True if 16-bit codes, false if 8-bit
bool shift_jis;
bool small_text; // Anti-aliasing hint
// Glyph data
std::vector<Shape*> glyphs; // Glyph shapes (already have this)
std::vector<u16> character_codes; // Maps glyph index -> character code
std::unordered_map<u16, size_t> char_to_glyph; // Fast lookup: char -> glyph index
// Layout info (DefineFont2/3 only)
bool has_layout;
s16 ascent; // Distance from baseline to top
s16 descent; // Distance from baseline to bottom (negative)
s16 leading; // Line spacing
std::vector<s16> advance_widths; // Advance width per glyph
std::vector<RECT> glyph_bounds; // Bounding box per glyph
std::vector<KerningRecord> kerning_table;
// Language
u8 language_code; // 0=none, 1=Latin, 2=Japanese, etc.
};
struct TextRecord
{
bool has_font;
u16 font_id;
bool has_color;
u8 r, g, b, a;
bool has_position;
s16 x_offset;
s16 y_offset;
u16 text_height; // Font size in twips
std::vector<u16> glyph_indices;
std::vector<s16> glyph_advances;
};
struct StaticTextField
{
u16 character_id;
RECT bounds;
MATRIX transform;
std::vector<TextRecord> text_records;
};
struct DynamicTextField
{
u16 character_id;
RECT bounds;
u16 font_id;
u16 font_height;
u8 r, g, b, a;
bool word_wrap;
bool multiline;
bool password;
bool read_only;
bool auto_size;
bool no_select;
bool border;
bool html;
u8 align; // 0=left, 1=right, 2=center, 3=justify
u16 left_margin;
u16 right_margin;
u16 indent;
s16 leading;
std::string variable_name;
std::string initial_text;
u16 max_length;
};class SWF
{
public:
// ... existing members ...
// Font storage (follows existing pattern from char_id_to_bitmap_id at line 181)
std::unordered_map<u16, Font*> fonts; // font_id -> Font
std::unordered_map<u16, StaticTextField*> static_text_fields;
std::unordered_map<u16, DynamicTextField*> dynamic_text_fields;
// ... existing methods ...
void parseDefineFont(Context& context, SWFTag& tag);
void parseDefineFontInfo(Context& context, SWFTag& tag);
void parseDefineFont2(Context& context, SWFTag& tag);
void parseDefineFont3(Context& context, SWFTag& tag);
void parseDefineText(Context& context, SWFTag& tag);
void parseDefineText2(Context& context, SWFTag& tag);
void parseDefineEditText(Context& context, SWFTag& tag);
};Note: The SWF class currently has no destructor. One must be added to properly clean up Font objects (see Phase 1 implementation guide).
Core functionality required:
class TextLayoutEngine
{
public:
struct LayoutGlyph
{
u16 glyph_index;
s32 x; // Position in twips
s32 y;
u16 font_id;
u16 font_size;
u8 r, g, b, a;
};
struct LayoutLine
{
std::vector<LayoutGlyph> glyphs;
s32 baseline_y;
s32 width;
s32 height;
};
// Layout a text field
std::vector<LayoutLine> layoutText(
const std::string& text,
Font* font,
u16 font_size,
RECT bounds,
u8 align,
bool word_wrap,
bool multiline
);
// Apply kerning between two glyphs
s16 getKerningAdjustment(Font* font, u16 left_char, u16 right_char);
// Get advance width for a character
s16 getAdvanceWidth(Font* font, u16 char_code, u16 font_size);
// Convert character to glyph index
size_t getGlyphIndex(Font* font, u16 char_code);
// Word wrap calculation
std::vector<std::string> wrapText(
const std::string& text,
Font* font,
u16 font_size,
s32 max_width
);
};Key Algorithms:
-
Character to Glyph Mapping:
size_t glyph_index = font->char_to_glyph[character_code]; -
Glyph Positioning:
current_x += (advance_width * font_size) / 1024; // EM units to twips current_x += getKerningAdjustment(font, prev_char, current_char);
-
Line Breaking:
- Track current line width
- Break at word boundaries if word_wrap enabled
- Break at newlines if multiline enabled
- Apply alignment (left/right/center/justify) per line
-
Vertical Positioning:
baseline_y = bounds.ymin + font->ascent; next_line_y = baseline_y + font_size + font->leading;
Currently, the runtime has zero font-specific code. Needs:
For efficient GPU rendering:
struct FontAtlas
{
GLuint texture_id;
int width;
int height;
std::unordered_map<u16, GlyphUV> glyph_uvs;
};
struct GlyphUV
{
float u0, v0; // Top-left UV
float u1, v1; // Bottom-right UV
float width; // Glyph width in pixels
float height; // Glyph height
};void renderStaticText(u16 character_id);
void renderDynamicText(u16 character_id);
void updateDynamicText(u16 character_id, const char* new_text);
void setTextColor(u16 character_id, u8 r, u8 g, u8 b, u8 a);For DefineEditText with variable names:
void bindTextVariable(u16 character_id, const char* var_name);
const char* getTextVariable(const char* var_name);
void setTextVariable(const char* var_name, const char* value);-
Font Definition Phase:
DefineFontorDefineFont2/3declares a font and its glyphsDefineFontInfo(if separate) provides metadata and character mappings- Font is registered by ID for later use
-
Text Placement Phase:
DefineText/DefineText2creates static text display objectsDefineEditTextcreates dynamic/input text fields- Both reference fonts by ID
-
Runtime Phase:
- Text objects are placed on stage via
PlaceObject2 - Dynamic text can be updated via ActionScript
- Text fields can be edited by user input (if configured)
- Text objects are placed on stage via
- EM Square: Glyphs are defined in a 1024×1024 EM square
- Baseline: Y=0 is the baseline (not the bottom of the glyph)
- Ascent: Distance from baseline to top of capital letters
- Descent: Distance from baseline to bottom of descenders (negative)
- Advance Width: Horizontal distance to next glyph position
Scaling to Twips:
glyph_size_in_twips = (em_units * font_size_in_twips) / 1024
SWF supports multiple encodings:
- 8-bit ANSI: Characters 0-255 (Western languages)
- 16-bit Unicode: Full Unicode support
- Shift-JIS: Japanese character encoding
The wide_codes flag in FontFlags determines 8-bit vs 16-bit.
Priority: URGENT
Estimated Effort: 2-3 days
Tasks:
-
Implement DefineFontInfo Parsing
- Parse font ID, name, flags
- Parse character code array (8-bit or 16-bit)
- Create Font data structure
- Store in fonts registry
-
Add Font Data Structures
- Add
Fontstruct toswf.hpp - Add
fontsmap toSWFclass - Modify
DefineFontparsing to store glyphs in Font object
- Add
-
Create Character-to-Glyph Mapping
- Build
char_to_glyphmap when parsing DefineFontInfo - Validate glyph count matches character code count
- Build
-
Testing
- Create test SWF with DefineFont + DefineFontInfo
- Verify correct parsing of character codes
- Verify font metadata is stored correctly
Success Criteria:
- DefineFontInfo data is parsed and stored
- Can look up glyph index by character code
- Font name and style flags are accessible
Priority: HIGH
Estimated Effort: 1 week
Tasks:
-
Implement DefineText/DefineText2 Parsing
- Parse text bounds and matrix
- Parse text records with glyph entries
- Store in
static_text_fieldsmap
-
Build Text Layout Engine (Basic)
- Implement glyph positioning from TextRecords
- Calculate absolute positions based on advances
- No word wrapping yet (DefineText is pre-laid-out)
-
Generate Rendering Code
- Output glyph positions and transformations
- Reference font glyph shapes
- Generate draw calls in
tagMain.c
-
Runtime Rendering
- Implement
renderStaticText()in runtime - Render glyphs using existing shape rendering
- Apply text colors and transformations
- Implement
-
Testing
- Create test SWF with static text
- Verify text renders correctly
- Test multiple fonts and colors
Success Criteria:
- Static text fields render correctly
- Text color and positioning is accurate
- Multiple text objects work simultaneously
Priority: MEDIUM
Estimated Effort: 1-2 weeks
Tasks:
-
Implement DefineFont2/3 Parsing
- Parse complete font definition
- Parse layout information (ascent, descent, leading)
- Parse advance widths
- Parse bounding boxes
- Parse kerning table
-
Enhanced Text Layout
- Use proper advance widths instead of fixed spacing
- Apply kerning adjustments
- Use ascent/descent for vertical positioning
- Calculate accurate line heights
-
Font Metrics Validation
- Add debugging output for font metrics
- Verify advance widths match visual spacing
- Test kerning with character pairs like "AV", "To"
-
Testing
- Create test SWFs with DefineFont2
- Test fonts with kerning
- Verify metrics match Flash Player output
Success Criteria:
- DefineFont2/3 fonts parse correctly
- Text spacing matches Flash Player exactly
- Kerning is applied correctly
- Line spacing uses proper metrics
Priority: MEDIUM-HIGH
Estimated Effort: 2-3 weeks
Tasks:
-
Implement DefineEditText Parsing
- Parse all text field properties
- Store variable names
- Store initial text
-
Text Layout Engine (Advanced)
- Implement word wrapping
- Implement text alignment (left/center/right/justify)
- Implement multiline layout
- Implement scrolling regions
-
Runtime Dynamic Text
- Implement
updateDynamicText() - Re-layout text when content changes
- Implement text field bounds clipping
- Add scrolling support
- Implement
-
Variable Binding
- Create variable registry
- Link text fields to ActionScript variables
- Update text when variables change
- Update variables when text is edited
-
Input Handling (if needed)
- Text cursor positioning
- Character insertion/deletion
- Selection handling
- Keyboard event handling
-
Testing
- Test dynamic text updates
- Test variable binding
- Test word wrap and alignment
- Test multiline text fields
Success Criteria:
- Dynamic text fields render correctly
- Text updates when ActionScript changes variables
- Word wrap and alignment work correctly
- Input fields accept user input (if implemented)
Priority: LOW
Estimated Effort: Ongoing
Tasks:
- DefineFont4 (Tag 91) - Embedded OpenType CFF fonts
- Requires CFF font parser
- Requires OpenType table parsing
- Only needed for Flash Text Engine (FTE) support
- Most Flash content uses DefineFont2/3, not DefineFont4
- Recommendation: Defer until DefineFont/DefineFont2/DefineFont3 are fully working
- HTML text rendering
- Advanced typography (ligatures via OpenType GSUB, etc.)
- Font subsetting/optimization
- Font fallback chains
- Emoji support
- Vertical text (Asian languages)
- Right-to-left text (Arabic, Hebrew)
Font Storage:
- Each glyph shape: ~100-500 bytes (depends on complexity)
- Font with 256 glyphs: ~25-125 KB
- Character code array: 256-512 bytes
- Total per font: ~30-150 KB
Text Fields:
- Static text: ~50-200 bytes per field
- Dynamic text: ~100-500 bytes per field
- Layout data: ~20 bytes per glyph
Font Atlas Benefits:
- Single texture bind per font
- Efficient GPU rendering
- Cache-friendly memory access
Without Font Atlas:
- One draw call per glyph
- Slower for large amounts of text
- More CPU overhead
Recommendation: Implement font atlas for any project with more than 10 text fields.
-
Missing Glyphs:
- Character code in text but not in font
- Solution: Use fallback glyph (usually space or '?')
-
Zero-Width Glyphs:
- Some fonts have glyphs with zero advance width
- Solution: Use minimum advance or skip rendering
-
Overlapping Glyphs:
- Kerning can cause glyphs to overlap
- Solution: This is intentional, allow overlap
-
Empty Text Fields:
- Text field with no text
- Solution: Render bounds only (if border enabled)
-
Unicode Normalization:
- Same character, different encodings (é vs e + ´)
- Solution: Normalize to NFC before lookup
-
Right-to-Left Text:
- Arabic, Hebrew require reversed layout
- Solution: Detect RTL script, reverse glyph order
Complete list of font and text-related SWF tags with their tag numbers:
| Tag Name | Number | SWF Version | Description |
|---|---|---|---|
| DefineFont | 10 | 1 | Basic font with glyph shapes only |
| DefineText | 11 | 1 | Static text field |
| DefineFontInfo | 13 | 1 | Font metadata and character mapping |
| DefineText2 | 33 | 3 | Static text with RGBA colors |
| DefineEditText | 37 | 4 | Dynamic/input text field |
| DefineFont2 | 48 | 3 | Complete font with metrics and layout |
| DefineFontInfo2 | 62 | 6 | Font metadata with language code |
| DefineFont3 | 75 | 8 | DefineFont2 with 32-bit offsets |
| DefineFont4 | 91 | 10 | Embedded OpenType CFF fonts for Flash Text Engine |
Notes:
- DefineFont (10) requires DefineFontInfo (13) or DefineFontInfo2 (62) for character mapping
- DefineFont2 (48) and DefineFont3 (75) include all metadata in one tag
- DefineText2 (33) is identical to DefineText (11) except uses RGBA instead of RGB
- DefineFont4 (91) uses OpenType CFF format instead of SWF shapes, designed for Flash Text Engine
- Tag numbers verified against official SWF specification v19
SWFRecomp:
include/tag.hpp:16-18- Font tag enum definitions (DefineFont=10, DefineFontInfo=13)src/swf.cpp:688-735- DefineFont parsingsrc/swf.cpp:756-761- DefineFontInfo (currently skipped)src/swf.cpp:1141-1235- Font glyph shape interpretation (is_font flag, white fill)
Note: Text tags (11, 33, 37) and additional font tags (48, 62, 75) need to be added to tag.hpp enum
SWFModernRuntime:
- No font-specific code exists
Font Data Structures:
include/swf.hpp- Add Font, TextRecord, TextField structs
Font Parsing:
src/swf.cpp- Expand existing DefineFont handlersrc/swf.cpp- Implement DefineFontInfo handlersrc/swf.cpp- Add DefineFont2/3 handlerssrc/swf.cpp- Add DefineText/DefineText2 handlerssrc/swf.cpp- Add DefineEditText handler
Text Layout:
- New file:
src/text_layout.cpp - New header:
include/text_layout.hpp
Runtime Rendering:
SWFModernRuntime/src/text_render.c(new file)SWFModernRuntime/include/text_render.h(new file)
- Adobe SWF File Format Specification v19
- Section 8: Fonts and Text (pages 135-167)
- SWF File Format Specification
- OpenType Font Specification
- Unicode Text Segmentation
- HarfBuzz Text Shaping Library
stb_truetype.hinlib/stb/- Can be used as reference for font metrics- FreeType library - Industry standard for font rendering (could be integrated)
Full font support in SWFRecomp requires significant additional work beyond the current basic glyph parsing. The most critical immediate need is implementing DefineFontInfo parsing to establish character-to-glyph mappings, without which text rendering is impossible.
The roadmap above provides a phased approach that delivers incremental value:
- Phase 1 enables basic font support
- Phase 2 enables static text rendering
- Phase 3 improves text quality
- Phase 4 enables dynamic text and interactivity
Estimated total effort: 6-8 weeks for full implementation through Phase 4.
Document Version: 1.0
Last Updated: 2025-10-30
Author: Analysis by Claude Code
Status: Draft