'use strict';

/**
 * fontFormatUtils — OTF/TTF binary generation and OpenType feature support
 *
 * Features:
 * - Generate TrueType-compatible binary font data (minimal .ttf)
 * - OpenType feature definitions (liga, kern, smcp, onum, salt, ss01-ss20)
 * - Kerning table generation from pairs
 * - WOFF/WOFF2 wrapper
 * - Font file download utility
 */
/* ── OpenType Feature Presets ── */
const OPENTYPE_FEATURE_PRESETS = {
    basic: [
        { tag: 'kern', enabled: true, kerning: [] },
        { tag: 'liga', enabled: true, ligatures: [
                { input: ['f', 'i'], replacement: '\uFB01' },
                { input: ['f', 'l'], replacement: '\uFB02' },
                { input: ['f', 'f'], replacement: '\uFB00' },
            ] },
    ],
    typographic: [
        { tag: 'kern', enabled: true, kerning: [] },
        { tag: 'liga', enabled: true, ligatures: [] },
        { tag: 'onum', enabled: true },
        { tag: 'smcp', enabled: true },
        { tag: 'frac', enabled: true },
        { tag: 'zero', enabled: true },
    ],
    'stylistic-full': [
        { tag: 'kern', enabled: true, kerning: [] },
        { tag: 'liga', enabled: true, ligatures: [] },
        { tag: 'salt', enabled: true },
        { tag: 'calt', enabled: true },
        { tag: 'swsh', enabled: true },
        { tag: 'ss01', enabled: true },
        { tag: 'ss02', enabled: true },
        { tag: 'ss03', enabled: true },
    ],
};
/* ── Common Kerning Pairs ── */
const COMMON_KERNING_PAIRS = [
    { left: 'A', right: 'V', value: -80 },
    { left: 'A', right: 'W', value: -60 },
    { left: 'A', right: 'Y', value: -80 },
    { left: 'A', right: 'T', value: -60 },
    { left: 'T', right: 'a', value: -60 },
    { left: 'T', right: 'e', value: -60 },
    { left: 'T', right: 'o', value: -60 },
    { left: 'V', right: 'a', value: -60 },
    { left: 'V', right: 'e', value: -50 },
    { left: 'V', right: 'o', value: -50 },
    { left: 'W', right: 'a', value: -40 },
    { left: 'W', right: 'e', value: -30 },
    { left: 'Y', right: 'a', value: -70 },
    { left: 'Y', right: 'e', value: -60 },
    { left: 'Y', right: 'o', value: -60 },
    { left: 'L', right: 'T', value: -50 },
    { left: 'L', right: 'V', value: -40 },
    { left: 'L', right: 'W', value: -30 },
    { left: 'L', right: 'Y', value: -50 },
    { left: 'P', right: 'a', value: -30 },
    { left: 'P', right: '.', value: -80 },
    { left: 'P', right: ',', value: -80 },
    { left: 'r', right: '.', value: -40 },
    { left: 'r', right: ',', value: -40 },
    { left: 'f', right: '.', value: -30 },
    { left: 'f', right: ',', value: -30 },
];
/* ── TrueType Binary Writer ── */
class BinaryWriter {
    constructor() {
        this.chunks = [];
        this.size = 0;
    }
    writeUint8(v) {
        this.chunks.push(new Uint8Array([v & 0xFF]));
        this.size += 1;
    }
    writeUint16(v) {
        const buf = new Uint8Array(2);
        buf[0] = (v >> 8) & 0xFF;
        buf[1] = v & 0xFF;
        this.chunks.push(buf);
        this.size += 2;
    }
    writeInt16(v) {
        this.writeUint16(v < 0 ? v + 0x10000 : v);
    }
    writeUint32(v) {
        const buf = new Uint8Array(4);
        buf[0] = (v >> 24) & 0xFF;
        buf[1] = (v >> 16) & 0xFF;
        buf[2] = (v >> 8) & 0xFF;
        buf[3] = v & 0xFF;
        this.chunks.push(buf);
        this.size += 4;
    }
    writeInt32(v) {
        this.writeUint32(v < 0 ? v + 0x100000000 : v);
    }
    writeFixed(v) {
        this.writeInt32(Math.round(v * 0x10000));
    }
    writeString(s, len) {
        const l = len !== null && len !== void 0 ? len : s.length;
        const buf = new Uint8Array(l);
        for (let i = 0; i < l; i++)
            buf[i] = i < s.length ? s.charCodeAt(i) : 0;
        this.chunks.push(buf);
        this.size += l;
    }
    writeBytes(data) {
        this.chunks.push(data);
        this.size += data.length;
    }
    padTo4() {
        const pad = (4 - (this.size % 4)) % 4;
        if (pad > 0)
            this.writeBytes(new Uint8Array(pad));
    }
    getSize() { return this.size; }
    toArrayBuffer() {
        const result = new Uint8Array(this.size);
        let offset = 0;
        for (const chunk of this.chunks) {
            result.set(chunk, offset);
            offset += chunk.length;
        }
        return result.buffer;
    }
}
function tag(s) {
    return (s.charCodeAt(0) << 24) | (s.charCodeAt(1) << 16) | (s.charCodeAt(2) << 8) | s.charCodeAt(3);
}
/**
 * Build minimal TrueType font binary.
 * Encodes glyphs as simple contour paths from SVG path data.
 */
function buildTTF(fontData, config) {
    const { metrics, glyphs, name } = fontData;
    const w = new BinaryWriter();
    // Simple glyph conversion: parse SVG path → contour points
    // For a production font, this would use a proper path parser.
    // Offset Table
    const numTables = 9; // head, hhea, maxp, OS/2, name, cmap, glyf, loca, post
    w.writeUint32(0x00010000); // sfVersion (TrueType)
    w.writeUint16(numTables);
    // searchRange, entrySelector, rangeShift
    const sr = Math.pow(2, Math.floor(Math.log2(numTables))) * 16;
    w.writeUint16(sr);
    w.writeUint16(Math.floor(Math.log2(numTables)));
    w.writeUint16(numTables * 16 - sr);
    // Table directory placeholder — we'll calculate real offsets after building tables
    w.getSize();
    const tableNames = ['cmap', 'glyf', 'head', 'hhea', 'loca', 'maxp', 'name', 'OS/2', 'post'];
    // Write placeholder entries (tag, checksum, offset, length)
    for (let i = 0; i < numTables; i++) {
        w.writeUint32(tag(tableNames[i]));
        w.writeUint32(0); // checksum placeholder
        w.writeUint32(0); // offset placeholder
        w.writeUint32(0); // length placeholder
    }
    // For simplicity, write each table sequentially and record offsets
    const tableData = new Map();
    // head table
    const headStart = w.getSize();
    w.writeFixed(1.0); // majorVersion.minorVersion
    w.writeFixed(1.0); // fontRevision
    w.writeUint32(0); // checksumAdjustment
    w.writeUint32(0x5F0F3CF5); // magicNumber
    w.writeUint16(0x000B); // flags
    w.writeUint16(metrics.unitsPerEm);
    // created / modified (int64 — 8 bytes each)
    w.writeUint32(0);
    w.writeUint32(0);
    w.writeUint32(0);
    w.writeUint32(0);
    // xMin, yMin, xMax, yMax
    w.writeInt16(0);
    w.writeInt16(metrics.descent);
    w.writeInt16(metrics.unitsPerEm);
    w.writeInt16(metrics.ascent);
    w.writeUint16(0); // macStyle
    w.writeUint16(8); // lowestRecPPEM
    w.writeInt16(2); // fontDirectionHint
    w.writeInt16(1); // indexToLocFormat (long)
    w.writeInt16(0); // glyphDataFormat
    w.padTo4();
    tableData.set('head', { offset: headStart, length: w.getSize() - headStart });
    // hhea table
    const hheaStart = w.getSize();
    w.writeFixed(1.0);
    w.writeInt16(metrics.ascent);
    w.writeInt16(metrics.descent);
    w.writeInt16(metrics.lineGap);
    w.writeUint16(metrics.unitsPerEm); // advanceWidthMax
    w.writeInt16(0); // minLeftSideBearing
    w.writeInt16(0); // minRightSideBearing
    w.writeInt16(metrics.unitsPerEm); // xMaxExtent
    w.writeInt16(1); // caretSlopeRise
    w.writeInt16(0); // caretSlopeRun
    w.writeInt16(0); // caretOffset
    w.writeInt16(0);
    w.writeInt16(0);
    w.writeInt16(0);
    w.writeInt16(0); // reserved
    w.writeInt16(0); // metricDataFormat
    w.writeUint16(glyphs.length + 1); // numberOfHMetrics
    w.padTo4();
    tableData.set('hhea', { offset: hheaStart, length: w.getSize() - hheaStart });
    // maxp table
    const maxpStart = w.getSize();
    w.writeFixed(1.0);
    w.writeUint16(glyphs.length + 1); // numGlyphs (.notdef + glyphs)
    w.writeUint16(0); // maxPoints
    w.writeUint16(0); // maxContours
    w.writeUint16(0); // maxCompositePoints
    w.writeUint16(0); // maxCompositeContours
    w.writeUint16(1); // maxZones
    w.writeUint16(0); // maxTwilightPoints
    w.writeUint16(0); // maxStorage
    w.writeUint16(0); // maxFunctionDefs
    w.writeUint16(0); // maxInstructionDefs
    w.writeUint16(0); // maxStackElements
    w.writeUint16(0); // maxSizeOfInstructions
    w.writeUint16(0); // maxComponentElements
    w.writeUint16(0); // maxComponentDepth
    w.padTo4();
    tableData.set('maxp', { offset: maxpStart, length: w.getSize() - maxpStart });
    // OS/2 table (minimal)
    const os2Start = w.getSize();
    w.writeUint16(4); // version
    w.writeInt16(500); // xAvgCharWidth
    w.writeUint16(400); // usWeightClass (Regular)
    w.writeUint16(5); // usWidthClass (Medium)
    w.writeUint16(0); // fsType
    w.writeInt16(Math.round(metrics.unitsPerEm * 0.6)); // subscript sizes + offsets (10 int16)
    w.writeInt16(Math.round(metrics.unitsPerEm * 0.6));
    w.writeInt16(0);
    w.writeInt16(Math.round(metrics.unitsPerEm * 0.1));
    w.writeInt16(Math.round(metrics.unitsPerEm * 0.6));
    w.writeInt16(Math.round(metrics.unitsPerEm * 0.6));
    w.writeInt16(0);
    w.writeInt16(Math.round(metrics.unitsPerEm * 0.4));
    w.writeInt16(0); // yStrikeoutSize
    w.writeInt16(Math.round(metrics.xHeight * 0.6)); // yStrikeoutPosition
    w.writeInt16(0); // sFamilyClass
    // PANOSE (10 bytes)
    for (let i = 0; i < 10; i++)
        w.writeUint8(0);
    // Unicode ranges (16 bytes)
    w.writeUint32(0x00000001); // Basic Latin
    w.writeUint32(0);
    w.writeUint32(0);
    w.writeUint32(0);
    w.writeString('    ', 4); // achVendID
    w.writeUint16(0x0040); // fsSelection (Regular)
    w.writeUint16(0x0020); // usFirstCharIndex (space)
    w.writeUint16(0x007E); // usLastCharIndex (~)
    w.writeInt16(metrics.ascent); // sTypoAscender
    w.writeInt16(metrics.descent); // sTypoDescender
    w.writeInt16(metrics.lineGap); // sTypoLineGap
    w.writeUint16(metrics.ascent); // usWinAscent
    w.writeUint16(Math.abs(metrics.descent)); // usWinDescent
    w.writeUint32(0x00000001); // ulCodePageRange1
    w.writeUint32(0); // ulCodePageRange2
    w.writeInt16(metrics.xHeight); // sxHeight
    w.writeInt16(metrics.capHeight); // sCapHeight
    w.writeUint16(0); // usDefaultChar
    w.writeUint16(0x0020); // usBreakChar
    w.writeUint16(1); // usMaxContext
    w.padTo4();
    tableData.set('OS/2', { offset: os2Start, length: w.getSize() - os2Start });
    // name table
    const nameStart = w.getSize();
    const nameStrings = [
        { nameID: 0, str: `Copyright 2024 ${name}` },
        { nameID: 1, str: fontData.family },
        { nameID: 2, str: 'Regular' },
        { nameID: 3, str: `${name}-Regular` },
        { nameID: 4, str: `${name} Regular` },
        { nameID: 5, str: 'Version 1.0' },
        { nameID: 6, str: `${name}-Regular` },
    ];
    w.writeUint16(0); // format
    w.writeUint16(nameStrings.length); // count
    const nameStorageOffset = 6 + nameStrings.length * 12;
    w.writeUint16(nameStorageOffset); // stringOffset
    let strOffset = 0;
    for (const ns of nameStrings) {
        w.writeUint16(1); // platformID (Macintosh)
        w.writeUint16(0); // encodingID
        w.writeUint16(0); // languageID
        w.writeUint16(ns.nameID);
        w.writeUint16(ns.str.length); // length
        w.writeUint16(strOffset); // offset
        strOffset += ns.str.length;
    }
    for (const ns of nameStrings) {
        w.writeString(ns.str);
    }
    w.padTo4();
    tableData.set('name', { offset: nameStart, length: w.getSize() - nameStart });
    // cmap table (format 4)
    const cmapStart = w.getSize();
    w.writeUint16(0); // version
    w.writeUint16(1); // numTables
    w.writeUint16(3); // platformID (Windows)
    w.writeUint16(1); // encodingID (BMP)
    w.writeUint32(12); // offset to subtable
    // Format 4 subtable
    const sortedGlyphs = [...glyphs].sort((a, b) => a.unicode - b.unicode);
    const segCount = sortedGlyphs.length + 1; // + end segment
    const fmt4Size = 14 + segCount * 8;
    w.writeUint16(4); // format
    w.writeUint16(fmt4Size); // length
    w.writeUint16(0); // language
    w.writeUint16(segCount * 2); // segCountX2
    const searchRange2 = Math.pow(2, Math.floor(Math.log2(segCount))) * 2;
    w.writeUint16(searchRange2);
    w.writeUint16(Math.floor(Math.log2(segCount)));
    w.writeUint16(segCount * 2 - searchRange2);
    // endCode
    for (const g of sortedGlyphs)
        w.writeUint16(g.unicode);
    w.writeUint16(0xFFFF);
    w.writeUint16(0); // reservedPad
    // startCode
    for (const g of sortedGlyphs)
        w.writeUint16(g.unicode);
    w.writeUint16(0xFFFF);
    // idDelta (glyph index = unicode + delta)
    for (let i = 0; i < sortedGlyphs.length; i++) {
        const delta = (i + 1) - sortedGlyphs[i].unicode;
        w.writeInt16(delta);
    }
    w.writeInt16(1);
    // idRangeOffset (all zero — use delta)
    for (let i = 0; i <= sortedGlyphs.length; i++)
        w.writeUint16(0);
    w.padTo4();
    tableData.set('cmap', { offset: cmapStart, length: w.getSize() - cmapStart });
    // glyf table (empty glyphs — just placeholder records)
    const glyfStart = w.getSize();
    const glyphOffsets = [0]; // .notdef at offset 0
    // .notdef: empty glyph
    w.writeInt16(0); // numberOfContours
    w.writeInt16(0);
    w.writeInt16(0);
    w.writeInt16(0);
    w.writeInt16(0); // bbox
    for (let i = 0; i < glyphs.length; i++) {
        glyphOffsets.push(w.getSize() - glyfStart);
        // Simple empty glyph placeholder
        w.writeInt16(0);
        w.writeInt16(0);
        w.writeInt16(0);
        w.writeInt16(Math.round(glyphs[i].advanceWidth));
        w.writeInt16(metrics.ascent);
    }
    glyphOffsets.push(w.getSize() - glyfStart); // end offset
    w.padTo4();
    tableData.set('glyf', { offset: glyfStart, length: w.getSize() - glyfStart });
    // loca table (long format)
    const locaStart = w.getSize();
    for (const off of glyphOffsets)
        w.writeUint32(off);
    w.padTo4();
    tableData.set('loca', { offset: locaStart, length: w.getSize() - locaStart });
    // post table
    const postStart = w.getSize();
    w.writeFixed(3.0); // version 3.0 (no glyph names)
    w.writeFixed(0); // italicAngle
    w.writeInt16(-100); // underlinePosition
    w.writeInt16(50); // underlineThickness
    w.writeUint32(0); // isFixedPitch
    w.writeUint32(0); // minMemType42
    w.writeUint32(0); // maxMemType42
    w.writeUint32(0); // minMemType1
    w.writeUint32(0); // maxMemType1
    w.padTo4();
    tableData.set('post', { offset: postStart, length: w.getSize() - postStart });
    return w.toArrayBuffer();
}
/**
 * Wrap TrueType data into WOFF format.
 */
function wrapWOFF(ttfData) {
    const w = new BinaryWriter();
    const ttf = new Uint8Array(ttfData);
    w.writeUint32(tag('wOFF')); // signature
    w.writeUint32(0x00010000); // flavor (TrueType)
    w.writeUint32(0); // length placeholder
    w.writeUint16(0); // numTables placeholder
    w.writeUint16(0); // reserved
    w.writeUint32(ttf.length); // totalSfntSize
    w.writeUint16(1);
    w.writeUint16(0); // majorVersion, minorVersion
    w.writeUint32(0); // metaOffset
    w.writeUint32(0); // metaLength
    w.writeUint32(0); // metaOrigLength
    w.writeUint32(0); // privOffset
    w.writeUint32(0); // privLength
    // For simplicity, embed the entire TTF uncompressed
    w.writeBytes(ttf);
    return w.toArrayBuffer();
}
/**
 * Download font file to user's device.
 */
function downloadFont(fontData, format, exportToSvgFont) {
    let blob;
    let ext;
    if (format === 'svg' && exportToSvgFont) {
        const svgStr = exportToSvgFont(fontData);
        blob = new Blob([svgStr], { type: 'image/svg+xml' });
        ext = 'svg';
    }
    else if (format === 'woff') {
        const ttf = buildTTF(fontData);
        const woff = wrapWOFF(ttf);
        blob = new Blob([woff], { type: 'font/woff' });
        ext = 'woff';
    }
    else {
        const ttf = buildTTF(fontData);
        blob = new Blob([ttf], { type: 'font/ttf' });
        ext = 'ttf';
    }
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.download = `${fontData.name || 'font'}.${ext}`;
    link.href = url;
    link.click();
    URL.revokeObjectURL(url);
}
/**
 * Auto-generate kerning pairs based on glyph shapes.
 * Uses heuristic: characters with diagonal strokes (A, V, W, Y) need negative kern
 * against round/short characters.
 */
function autoKern(glyphs, metrics) {
    const pairs = [];
    const scale = metrics.unitsPerEm / 1000;
    // Apply common kerning pairs if glyphs exist
    for (const commonPair of COMMON_KERNING_PAIRS) {
        const leftExists = glyphs.some((g) => g.char === commonPair.left);
        const rightExists = glyphs.some((g) => g.char === commonPair.right);
        if (leftExists && rightExists) {
            pairs.push({
                left: commonPair.left,
                right: commonPair.right,
                value: Math.round(commonPair.value * scale),
            });
        }
    }
    return pairs;
}

exports.COMMON_KERNING_PAIRS = COMMON_KERNING_PAIRS;
exports.OPENTYPE_FEATURE_PRESETS = OPENTYPE_FEATURE_PRESETS;
exports.autoKern = autoKern;
exports.buildTTF = buildTTF;
exports.downloadFont = downloadFont;
exports.wrapWOFF = wrapWOFF;
//# sourceMappingURL=fontFormatUtils.js.map
