// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

/**
 * @file font_parser.hpp
 * @brief TrueType (.ttf) / OpenType CFF (.otf) / TTC (.ttc) フォントパーサー
 *
 * TTF: 2次ベジェ曲線によるグリフアウトライン (glyf テーブル)
 * OTF: 3次ベジェ曲線 (CFF テーブル — Type 2 Charstring)
 * TTC: 複数フォントコレクション
 *
 * 自前実装 — 外部ライブラリ依存なし
 */

#ifndef CALX_IMAGE_TEXT_FONT_PARSER_HPP
#define CALX_IMAGE_TEXT_FONT_PARSER_HPP

#include <cstdint>
#include <string>
#include <vector>
#include <array>
#include <fstream>
#include <stdexcept>
#include <unordered_map>
#include <cmath>
#include <algorithm>

namespace calx::image::text {

// ========================================================================
// バイナリ読み込みヘルパー (Big-Endian)
// ========================================================================

namespace detail {
    inline uint16_t readU16(const uint8_t* p) { return (uint16_t(p[0]) << 8) | p[1]; }
    inline int16_t  readI16(const uint8_t* p) { return int16_t(readU16(p)); }
    inline uint32_t readU32(const uint8_t* p) {
        return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) | (uint32_t(p[2]) << 8) | p[3];
    }
    inline int32_t  readI32(const uint8_t* p) { return int32_t(readU32(p)); }
}

// ========================================================================
// グリフアウトライン
// ========================================================================

/// アウトラインの制御点
struct GlyphPoint {
    float x, y;
    bool onCurve;  // true=端点, false=制御点
};

/// 1つの輪郭 (contour)
struct GlyphContour {
    std::vector<GlyphPoint> points;
};

/// グリフのアウトライン全体
struct GlyphOutline {
    std::vector<GlyphContour> contours;
    float advanceWidth = 0;   // 次の文字までの水平距離
    float leftBearing = 0;    // 原点からグリフ左端まで
    float xMin = 0, yMin = 0, xMax = 0, yMax = 0;
};

// ========================================================================
// フォントテーブル
// ========================================================================

struct FontTable {
    uint32_t tag;
    uint32_t offset;
    uint32_t length;
};

// ========================================================================
// FontData — フォントバイナリ + テーブルアクセス
// ========================================================================

class FontData {
public:
    /// ファイルからロード (.ttf, .otf, .ttc)
    bool load(const std::string& path, int faceIndex = 0) {
        std::ifstream ifs(path, std::ios::binary | std::ios::ate);
        if (!ifs) return false;
        auto size = ifs.tellg();
        data_.resize(static_cast<std::size_t>(size));
        ifs.seekg(0);
        ifs.read(reinterpret_cast<char*>(data_.data()), size);
        if (!ifs) return false;
        return parse(faceIndex);
    }

    /// メモリからロード
    bool load(const uint8_t* data, std::size_t size, int faceIndex = 0) {
        data_.assign(data, data + size);
        return parse(faceIndex);
    }

    /// Unicode コードポイント → グリフ ID
    int getGlyphIndex(uint32_t codepoint) const {
        auto it = cmapCache_.find(codepoint);
        return (it != cmapCache_.end()) ? it->second : 0;
    }

    /// グリフ ID → アウトライン抽出
    GlyphOutline getGlyphOutline(int glyphId, float scale = 1.0f) const {
        if (isCFF_)
            return getCFFOutline(glyphId, scale);
        else
            return getTTFOutline(glyphId, scale);
    }

    /// コードポイント → アウトライン (便利関数)
    GlyphOutline getOutline(uint32_t codepoint, float fontSize) const {
        float scale = fontSize / float(unitsPerEm_);
        int gid = getGlyphIndex(codepoint);
        return getGlyphOutline(gid, scale);
    }

    bool isLoaded() const { return !data_.empty() && unitsPerEm_ > 0; }
    int unitsPerEm() const { return unitsPerEm_; }
    bool isCFF() const { return isCFF_; }

private:
    std::vector<uint8_t> data_;
    std::unordered_map<uint32_t, int> cmapCache_;
    std::vector<FontTable> tables_;
    int unitsPerEm_ = 0;
    int numGlyphs_ = 0;
    bool isCFF_ = false;

    // TTF 用
    uint32_t locaOffset_ = 0, glyfOffset_ = 0;
    bool locaIsLong_ = false;
    uint32_t hmtxOffset_ = 0;
    int numHMetrics_ = 0;

    // CFF 用
    uint32_t cffOffset_ = 0;
    uint32_t charstringsOffset_ = 0;
    int charstringsCount_ = 0;
    std::vector<uint32_t> charstringsOffsets_;
    // CFF サブルーチン (簡易版では省略)

    // ── パーサー ──

    bool parse(int faceIndex) {
        if (data_.size() < 12) return false;
        const uint8_t* d = data_.data();
        uint32_t tag = detail::readU32(d);

        uint32_t fontOffset = 0;

        // TTC (コレクション)
        if (tag == 0x74746366) { // 'ttcf'
            uint32_t numFonts = detail::readU32(d + 8);
            if (faceIndex < 0 || faceIndex >= static_cast<int>(numFonts)) return false;
            fontOffset = detail::readU32(d + 12 + 4 * faceIndex);
        }

        // テーブルディレクトリ読み込み
        const uint8_t* hdr = d + fontOffset;
        uint32_t sfVersion = detail::readU32(hdr);
        int numTables = detail::readU16(hdr + 4);

        isCFF_ = (sfVersion == 0x4F54544F); // 'OTTO' = CFF

        tables_.clear();
        for (int i = 0; i < numTables; ++i) {
            const uint8_t* entry = hdr + 12 + i * 16;
            FontTable t;
            t.tag = detail::readU32(entry);
            t.offset = detail::readU32(entry + 8);
            t.length = detail::readU32(entry + 12);
            tables_.push_back(t);
        }

        // head テーブル
        auto headT = findTable(0x68656164); // 'head'
        if (!headT) return false;
        unitsPerEm_ = detail::readU16(d + headT->offset + 18);
        locaIsLong_ = (detail::readI16(d + headT->offset + 50) == 1);

        // maxp テーブル
        auto maxpT = findTable(0x6D617870); // 'maxp'
        if (maxpT) numGlyphs_ = detail::readU16(d + maxpT->offset + 4);

        // hhea + hmtx
        auto hheaT = findTable(0x68686561); // 'hhea'
        if (hheaT) numHMetrics_ = detail::readU16(d + hheaT->offset + 34);
        auto hmtxT = findTable(0x686D7478); // 'hmtx'
        if (hmtxT) hmtxOffset_ = hmtxT->offset;

        // cmap テーブル
        parseCmap();

        // カーニング
        parseKern();

        if (isCFF_) {
            parseCFF();
        } else {
            auto locaT = findTable(0x6C6F6361); // 'loca'
            auto glyfT = findTable(0x676C7966); // 'glyf'
            if (locaT) locaOffset_ = locaT->offset;
            if (glyfT) glyfOffset_ = glyfT->offset;
        }

        return true;
    }

    const FontTable* findTable(uint32_t tag) const {
        for (auto& t : tables_) if (t.tag == tag) return &t;
        return nullptr;
    }

    // ── cmap 解析 (Format 4 + Format 12) ──

    void parseCmap() {
        auto cmapT = findTable(0x636D6170); // 'cmap'
        if (!cmapT) return;
        const uint8_t* d = data_.data() + cmapT->offset;
        int numSubtables = detail::readU16(d + 2);

        for (int i = 0; i < numSubtables; ++i) {
            const uint8_t* sub = d + 4 + i * 8;
            int platformId = detail::readU16(sub);
            int encodingId = detail::readU16(sub + 2);
            uint32_t subOffset = detail::readU32(sub + 4);

            // Unicode (platform 0 or 3+1)
            bool isUnicode = (platformId == 0) ||
                             (platformId == 3 && (encodingId == 1 || encodingId == 10));
            if (!isUnicode) continue;

            const uint8_t* fmt = d + subOffset;
            uint16_t format = detail::readU16(fmt);

            if (format == 4) {
                parseCmapFormat4(fmt);
                return;
            } else if (format == 12) {
                parseCmapFormat12(fmt);
                return;
            }
        }
    }

    void parseCmapFormat4(const uint8_t* fmt) {
        int segCount = detail::readU16(fmt + 6) / 2;
        const uint8_t* endCodes   = fmt + 14;
        const uint8_t* startCodes = endCodes + segCount * 2 + 2;
        const uint8_t* idDeltas   = startCodes + segCount * 2;
        const uint8_t* idRangeOffsets = idDeltas + segCount * 2;

        for (int i = 0; i < segCount; ++i) {
            uint16_t endCode   = detail::readU16(endCodes + i * 2);
            uint16_t startCode = detail::readU16(startCodes + i * 2);
            int16_t  idDelta   = detail::readI16(idDeltas + i * 2);
            uint16_t rangeOff  = detail::readU16(idRangeOffsets + i * 2);

            if (startCode == 0xFFFF) break;

            for (uint32_t c = startCode; c <= endCode; ++c) {
                int gid;
                if (rangeOff == 0) {
                    gid = (c + idDelta) & 0xFFFF;
                } else {
                    const uint8_t* ptr = idRangeOffsets + i * 2 + rangeOff + (c - startCode) * 2;
                    gid = detail::readU16(ptr);
                    if (gid != 0) gid = (gid + idDelta) & 0xFFFF;
                }
                if (gid != 0) cmapCache_[c] = gid;
            }
        }
    }

    void parseCmapFormat12(const uint8_t* fmt) {
        uint32_t numGroups = detail::readU32(fmt + 12);
        const uint8_t* groups = fmt + 16;
        for (uint32_t i = 0; i < numGroups; ++i) {
            uint32_t startCode = detail::readU32(groups + i * 12);
            uint32_t endCode   = detail::readU32(groups + i * 12 + 4);
            uint32_t startGid  = detail::readU32(groups + i * 12 + 8);
            for (uint32_t c = startCode; c <= endCode; ++c)
                cmapCache_[c] = startGid + (c - startCode);
        }
    }

    // ── TTF グリフアウトライン (glyf テーブル) ──

    uint32_t getGlyfOffset(int glyphId) const {
        const uint8_t* d = data_.data() + locaOffset_;
        if (locaIsLong_)
            return detail::readU32(d + glyphId * 4);
        else
            return uint32_t(detail::readU16(d + glyphId * 2)) * 2;
    }

    GlyphOutline getTTFOutline(int glyphId, float scale) const {
        GlyphOutline outline;
        if (glyphId <= 0 || glyphId >= numGlyphs_) return outline;

        // メトリクス
        if (hmtxOffset_ && glyphId < numHMetrics_) {
            const uint8_t* hm = data_.data() + hmtxOffset_ + glyphId * 4;
            outline.advanceWidth = detail::readU16(hm) * scale;
            outline.leftBearing = detail::readI16(hm + 2) * scale;
        }

        uint32_t off0 = getGlyfOffset(glyphId);
        uint32_t off1 = getGlyfOffset(glyphId + 1);
        if (off0 == off1) return outline;  // 空グリフ

        const uint8_t* g = data_.data() + glyfOffset_ + off0;
        int16_t numContours = detail::readI16(g);
        outline.xMin = detail::readI16(g + 2) * scale;
        outline.yMin = detail::readI16(g + 4) * scale;
        outline.xMax = detail::readI16(g + 6) * scale;
        outline.yMax = detail::readI16(g + 8) * scale;

        if (numContours < 0) {
            // 複合グリフ
            parseCompoundGlyph(g + 10, outline, scale);
        } else {
            parseSimpleGlyph(g + 10, numContours, outline, scale);
        }
        return outline;
    }

    void parseSimpleGlyph(const uint8_t* p, int numContours,
                          GlyphOutline& outline, float scale) const {
        // endPtsOfContours
        std::vector<uint16_t> endPts(numContours);
        for (int i = 0; i < numContours; ++i)
            endPts[i] = detail::readU16(p + i * 2);
        int numPts = (numContours > 0) ? endPts.back() + 1 : 0;

        // instructionLength + skip
        uint16_t instrLen = detail::readU16(p + numContours * 2);
        const uint8_t* flags = p + numContours * 2 + 2 + instrLen;

        // フラグを展開
        std::vector<uint8_t> flagVec;
        flagVec.reserve(numPts);
        const uint8_t* fp = flags;
        while (static_cast<int>(flagVec.size()) < numPts) {
            uint8_t flag = *fp++;
            flagVec.push_back(flag);
            if (flag & 0x08) { // repeat
                int repeat = *fp++;
                for (int r = 0; r < repeat; ++r) flagVec.push_back(flag);
            }
        }

        // X 座標
        std::vector<int16_t> xCoords(numPts);
        const uint8_t* xp = fp;
        int16_t xVal = 0;
        for (int i = 0; i < numPts; ++i) {
            uint8_t f = flagVec[i];
            if (f & 0x02) {
                int16_t dx = *xp++;
                xVal += (f & 0x10) ? dx : -dx;
            } else if (!(f & 0x10)) {
                xVal += detail::readI16(xp); xp += 2;
            }
            xCoords[i] = xVal;
        }

        // Y 座標
        std::vector<int16_t> yCoords(numPts);
        const uint8_t* yp = xp;
        int16_t yVal = 0;
        for (int i = 0; i < numPts; ++i) {
            uint8_t f = flagVec[i];
            if (f & 0x04) {
                int16_t dy = *yp++;
                yVal += (f & 0x20) ? dy : -dy;
            } else if (!(f & 0x20)) {
                yVal += detail::readI16(yp); yp += 2;
            }
            yCoords[i] = yVal;
        }

        // 輪郭に変換
        int startPt = 0;
        for (int c = 0; c < numContours; ++c) {
            GlyphContour contour;
            for (int i = startPt; i <= endPts[c]; ++i) {
                contour.points.push_back({
                    xCoords[i] * scale,
                    yCoords[i] * scale,
                    bool(flagVec[i] & 0x01)
                });
            }
            outline.contours.push_back(std::move(contour));
            startPt = endPts[c] + 1;
        }
    }

    void parseCompoundGlyph(const uint8_t* p, GlyphOutline& outline, float scale) const {
        const uint8_t* ptr = p;
        uint16_t flags;
        do {
            flags = detail::readU16(ptr); ptr += 2;
            uint16_t glyphIdx = detail::readU16(ptr); ptr += 2;

            float dx = 0, dy = 0;
            if (flags & 0x0001) { // ARG_1_AND_2_ARE_WORDS
                dx = detail::readI16(ptr) * scale; ptr += 2;
                dy = detail::readI16(ptr) * scale; ptr += 2;
            } else {
                dx = int8_t(ptr[0]) * scale;
                dy = int8_t(ptr[1]) * scale;
                ptr += 2;
            }

            // 変換行列 (簡易: 平行移動のみ)
            if (flags & 0x0008) ptr += 2;       // SCALE
            else if (flags & 0x0040) ptr += 4;  // XY_SCALE
            else if (flags & 0x0080) ptr += 8;  // 2x2 matrix

            auto sub = getTTFOutline(glyphIdx, scale);
            for (auto& contour : sub.contours) {
                for (auto& pt : contour.points) {
                    pt.x += dx;
                    pt.y += dy;
                }
                outline.contours.push_back(std::move(contour));
            }
        } while (flags & 0x0020); // MORE_COMPONENTS
    }

    // ── CFF パーサー ──

    // CFF INDEX 構造の読み込み
    struct CFFIndex {
        int count = 0;
        std::vector<uint32_t> offsets;  // count+1 個
        const uint8_t* dataStart = nullptr;
    };

    CFFIndex readCFFIndex(const uint8_t* p) const {
        CFFIndex idx;
        idx.count = detail::readU16(p);
        if (idx.count == 0) return idx;
        int offSize = p[2];
        const uint8_t* offP = p + 3;
        idx.offsets.resize(idx.count + 1);
        for (int i = 0; i <= idx.count; ++i) {
            uint32_t off = 0;
            for (int b = 0; b < offSize; ++b)
                off = (off << 8) | offP[i * offSize + b];
            idx.offsets[i] = off;
        }
        idx.dataStart = offP + (idx.count + 1) * offSize - 1; // -1 because offsets are 1-based
        return idx;
    }

    // CFF DICT からキーに対応する整数値を取得 (簡易版)
    int32_t readCFFDictInt(const uint8_t* data, int len, int key) const {
        const uint8_t* p = data;
        const uint8_t* end = data + len;
        std::vector<int32_t> stack;
        while (p < end) {
            uint8_t b = *p;
            if (b >= 32 && b <= 246) {
                stack.push_back(int32_t(b) - 139); ++p;
            } else if (b >= 247 && b <= 250) {
                stack.push_back((int32_t(b) - 247) * 256 + p[1] + 108); p += 2;
            } else if (b >= 251 && b <= 254) {
                stack.push_back(-(int32_t(b) - 251) * 256 - p[1] - 108); p += 2;
            } else if (b == 28) {
                stack.push_back(detail::readI16(p + 1)); p += 3;
            } else if (b == 29) {
                stack.push_back(detail::readI32(p + 1)); p += 5;
            } else if (b == 30) {
                // 実数 (スキップ)
                ++p;
                while (p < end) {
                    uint8_t nb = *p++;
                    if ((nb & 0x0F) == 0x0F || (nb >> 4) == 0x0F) break;
                }
                stack.push_back(0);
            } else if (b == 12) {
                int op = 0x0C00 | p[1]; p += 2;
                if (op == key && !stack.empty()) return stack.back();
                stack.clear();
            } else {
                // operator
                if (b == key && !stack.empty()) return stack.back();
                stack.clear();
                ++p;
            }
        }
        return -1;
    }

    void parseCFF() {
        auto cffT = findTable(0x43464620); // 'CFF '
        if (!cffT) return;
        cffOffset_ = cffT->offset;
        const uint8_t* cff = data_.data() + cffOffset_;

        // CFF Header
        int hdrSize = cff[2]; // header size

        // Name INDEX (skip)
        auto nameIdx = readCFFIndex(cff + hdrSize);
        const uint8_t* afterName = cff + hdrSize;
        {
            // Skip Name INDEX
            int offSize = afterName[2];
            int cnt = detail::readU16(afterName);
            if (cnt > 0) {
                const uint8_t* offP = afterName + 3;
                uint32_t lastOff = 0;
                for (int b = 0; b < offSize; ++b)
                    lastOff = (lastOff << 8) | offP[cnt * offSize + b];
                afterName = offP + (cnt + 1) * offSize - 1 + lastOff;
            } else {
                afterName += 2;
            }
        }

        // Top DICT INDEX
        auto topDictIdx = readCFFIndex(afterName);
        if (topDictIdx.count == 0) return;

        // Top DICT のデータ
        const uint8_t* topDictData = topDictIdx.dataStart + topDictIdx.offsets[0];
        int topDictLen = topDictIdx.offsets[1] - topDictIdx.offsets[0];

        // CharStrings オフセット (operator 17)
        int32_t charStringsOff = readCFFDictInt(topDictData, topDictLen, 17);
        if (charStringsOff < 0) return;

        // CharStrings INDEX
        const uint8_t* csStart = cff + charStringsOff;
        cffCharStrings_ = readCFFIndex(csStart);
    }

    CFFIndex cffCharStrings_;

    GlyphOutline getCFFOutline(int glyphId, float scale) const {
        GlyphOutline outline;
        if (glyphId < 0 || glyphId >= cffCharStrings_.count) return outline;

        // メトリクス
        if (hmtxOffset_ && glyphId < numHMetrics_) {
            const uint8_t* hm = data_.data() + hmtxOffset_ + glyphId * 4;
            outline.advanceWidth = detail::readU16(hm) * scale;
            outline.leftBearing = detail::readI16(hm + 2) * scale;
        }

        // Type 2 Charstring 解釈
        const uint8_t* cs = cffCharStrings_.dataStart + cffCharStrings_.offsets[glyphId];
        int csLen = cffCharStrings_.offsets[glyphId + 1] - cffCharStrings_.offsets[glyphId];

        interpretType2(cs, csLen, outline, scale);
        return outline;
    }

    // Type 2 Charstring インタプリタ
    void interpretType2(const uint8_t* cs, int len,
                        GlyphOutline& outline, float scale) const {
        std::vector<float> stack;
        float curX = 0, curY = 0;
        bool hasWidth = false;
        GlyphContour* contour = nullptr;

        auto moveTo = [&](float x, float y) {
            // 前の輪郭を閉じる
            outline.contours.push_back({});
            contour = &outline.contours.back();
            curX = x; curY = y;
            contour->points.push_back({curX * scale, curY * scale, true});
        };
        auto lineTo = [&](float x, float y) {
            if (!contour) moveTo(0, 0);
            curX = x; curY = y;
            contour->points.push_back({curX * scale, curY * scale, true});
        };
        auto curveTo = [&](float x1, float y1, float x2, float y2, float x3, float y3) {
            if (!contour) moveTo(0, 0);
            // 3次ベジェ → フラット化して on-curve 点を追加
            float px = curX, py = curY;
            constexpr int N = 8;
            for (int i = 1; i <= N; ++i) {
                float t = float(i) / N, u = 1 - t;
                float x = u*u*u*px + 3*u*u*t*x1 + 3*u*t*t*x2 + t*t*t*x3;
                float y = u*u*u*py + 3*u*u*t*y1 + 3*u*t*t*y2 + t*t*t*y3;
                contour->points.push_back({x * scale, y * scale, true});
            }
            curX = x3; curY = y3;
        };

        const uint8_t* p = cs;
        const uint8_t* end = cs + len;

        while (p < end) {
            uint8_t b = *p;

            // 数値オペランド
            if (b >= 32 && b <= 246) {
                stack.push_back(float(int(b) - 139)); ++p; continue;
            } else if (b >= 247 && b <= 250) {
                stack.push_back(float((int(b) - 247) * 256 + p[1] + 108)); p += 2; continue;
            } else if (b >= 251 && b <= 254) {
                stack.push_back(float(-(int(b) - 251) * 256 - p[1] - 108)); p += 2; continue;
            } else if (b == 28) {
                stack.push_back(float(detail::readI16(p + 1))); p += 3; continue;
            } else if (b == 255) {
                // 16.16 固定小数点
                int32_t v = detail::readI32(p + 1);
                stack.push_back(float(v) / 65536.0f); p += 5; continue;
            }

            // オペレータ
            ++p;
            int n = static_cast<int>(stack.size());

            if (b == 14) { // endchar
                break;
            } else if (b == 21) { // rmoveto
                if (!hasWidth && n > 2) { hasWidth = true; }
                float dy = stack[n-1], dx = stack[n-2];
                moveTo(curX + dx, curY + dy);
                stack.clear();
            } else if (b == 22) { // hmoveto
                if (!hasWidth && n > 1) { hasWidth = true; }
                moveTo(curX + stack[n-1], curY);
                stack.clear();
            } else if (b == 4) { // vmoveto
                if (!hasWidth && n > 1) { hasWidth = true; }
                moveTo(curX, curY + stack[n-1]);
                stack.clear();
            } else if (b == 5) { // rlineto
                for (int i = 0; i + 1 < n; i += 2)
                    lineTo(curX + stack[i], curY + stack[i+1]);
                stack.clear();
            } else if (b == 6) { // hlineto
                for (int i = 0; i < n; ++i) {
                    if (i % 2 == 0) lineTo(curX + stack[i], curY);
                    else lineTo(curX, curY + stack[i]);
                }
                stack.clear();
            } else if (b == 7) { // vlineto
                for (int i = 0; i < n; ++i) {
                    if (i % 2 == 0) lineTo(curX, curY + stack[i]);
                    else lineTo(curX + stack[i], curY);
                }
                stack.clear();
            } else if (b == 8) { // rrcurveto
                for (int i = 0; i + 5 < n; i += 6)
                    curveTo(curX+stack[i], curY+stack[i+1],
                            curX+stack[i]+stack[i+2], curY+stack[i+1]+stack[i+3],
                            curX+stack[i]+stack[i+2]+stack[i+4],
                            curY+stack[i+1]+stack[i+3]+stack[i+5]);
                stack.clear();
            } else if (b == 31) { // hvcurveto
                int i = 0;
                while (i + 3 < n) {
                    if ((i / 4) % 2 == 0) {
                        float dx1=stack[i],dy1=0, dx2=stack[i+1],dy2=stack[i+2], dx3=0,dy3=stack[i+3];
                        if (i+4==n-1) dx3=stack[i+4];
                        curveTo(curX+dx1,curY+dy1, curX+dx1+dx2,curY+dy1+dy2,
                                curX+dx1+dx2+dx3,curY+dy1+dy2+dy3);
                    } else {
                        float dx1=0,dy1=stack[i], dx2=stack[i+1],dy2=stack[i+2], dx3=stack[i+3],dy3=0;
                        if (i+4==n-1) dy3=stack[i+4];
                        curveTo(curX+dx1,curY+dy1, curX+dx1+dx2,curY+dy1+dy2,
                                curX+dx1+dx2+dx3,curY+dy1+dy2+dy3);
                    }
                    i += 4;
                }
                stack.clear();
            } else if (b == 30) { // vhcurveto
                int i = 0;
                while (i + 3 < n) {
                    if ((i / 4) % 2 == 0) {
                        float dx1=0,dy1=stack[i], dx2=stack[i+1],dy2=stack[i+2], dx3=stack[i+3],dy3=0;
                        if (i+4==n-1) dy3=stack[i+4];
                        curveTo(curX+dx1,curY+dy1, curX+dx1+dx2,curY+dy1+dy2,
                                curX+dx1+dx2+dx3,curY+dy1+dy2+dy3);
                    } else {
                        float dx1=stack[i],dy1=0, dx2=stack[i+1],dy2=stack[i+2], dx3=0,dy3=stack[i+3];
                        if (i+4==n-1) dx3=stack[i+4];
                        curveTo(curX+dx1,curY+dy1, curX+dx1+dx2,curY+dy1+dy2,
                                curX+dx1+dx2+dx3,curY+dy1+dy2+dy3);
                    }
                    i += 4;
                }
                stack.clear();
            } else if (b == 12) {
                // 2-byte operators (skip for now)
                if (p < end) ++p;
                stack.clear();
            } else {
                // 未知のオペレータ → スタッククリア
                stack.clear();
            }
        }
    }

    // ── カーニング (kern テーブル) ──

    std::unordered_map<uint64_t, int16_t> kernPairs_;

    void parseKern() {
        auto kernT = findTable(0x6B65726E); // 'kern'
        if (!kernT) return;
        const uint8_t* d = data_.data() + kernT->offset;
        uint16_t version = detail::readU16(d);
        uint16_t nTables = detail::readU16(d + 2);
        const uint8_t* sub = d + 4;

        for (int t = 0; t < nTables; ++t) {
            uint16_t subVersion = detail::readU16(sub);
            uint16_t subLen = detail::readU16(sub + 2);
            uint16_t coverage = detail::readU16(sub + 4);

            // Format 0, horizontal kerning のみ
            if ((coverage & 0x00FF) == 0 && (coverage & 0x0001)) {
                uint16_t nPairs = detail::readU16(sub + 6);
                const uint8_t* pairs = sub + 14;
                for (int i = 0; i < nPairs; ++i) {
                    uint16_t left = detail::readU16(pairs + i * 6);
                    uint16_t right = detail::readU16(pairs + i * 6 + 2);
                    int16_t value = detail::readI16(pairs + i * 6 + 4);
                    uint64_t key = (uint64_t(left) << 32) | right;
                    kernPairs_[key] = value;
                }
            }
            sub += subLen;
        }
    }

public:
    /// カーニング値を取得 (font units)
    int16_t getKerning(int leftGlyph, int rightGlyph) const {
        uint64_t key = (uint64_t(leftGlyph) << 32) | uint32_t(rightGlyph);
        auto it = kernPairs_.find(key);
        return (it != kernPairs_.end()) ? it->second : 0;
    }
};

} // namespace calx::image::text

#endif // CALX_IMAGE_TEXT_FONT_PARSER_HPP
