//========================================================================
//
// SplashFTFont.cc
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005, 2007-2011, 2014, 2018, 2020, 2025, 2026 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2006 Kristian Høgsberg <krh@bitplanet.net>
// Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
// Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
// Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2018 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>

#include <ft2build.h>
#include FT_OUTLINE_H
#include FT_SIZES_H
#include FT_GLYPH_H
#include "goo/gmem.h"
#include "SplashMath.h"
#include "SplashGlyphBitmap.h"
#include "SplashPath.h"
#include "SplashFTFontEngine.h"
#include "SplashFTFontFile.h"
#include "SplashFTFont.h"

#include "goo/GooLikely.h"

//------------------------------------------------------------------------

static int glyphPathMoveTo(const FT_Vector *pt, void *path);
static int glyphPathLineTo(const FT_Vector *pt, void *path);
static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt, void *path);
static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2, const FT_Vector *pt, void *path);

//------------------------------------------------------------------------
// SplashFTFont
//------------------------------------------------------------------------

SplashFTFont::SplashFTFont(const std::shared_ptr<SplashFTFontFile> &fontFileA, const std::array<double, 4> &matA, const std::array<double, 4> &textMatA)
    : SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa), enableFreeTypeHinting(fontFileA->engine->enableFreeTypeHinting), enableSlightHinting(fontFileA->engine->enableSlightHinting)
{
    FT_Face face;
    int div;
    int x, y;

    face = fontFileA->face;
    if (FT_New_Size(face, &sizeObj)) {
        return;
    }
    face->size = sizeObj;
    size = splashRound(splashDist(0, 0, mat[2], mat[3]));
    if (size < 1) {
        size = 1;
    }
    if (FT_Set_Pixel_Sizes(face, 0, size)) {
        return;
    }
    // if the textMat values are too small, FreeType's fixed point
    // arithmetic doesn't work so well
    textScale = splashDist(0, 0, textMat[2], textMat[3]) / size;

    if (unlikely(textScale == 0 || face->units_per_EM == 0)) {
        return;
    }

    div = face->bbox.xMax > 20000 ? 65536 : 1;

    // transform the four corners of the font bounding box -- the min
    // and max values form the bounding box of the transformed font
    x = static_cast<int>((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMin) / (div * face->units_per_EM));
    xMin = xMax = x;
    y = static_cast<int>((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMin) / (div * face->units_per_EM));
    yMin = yMax = y;
    x = static_cast<int>((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMax) / (div * face->units_per_EM));
    if (x < xMin) {
        xMin = x;
    } else if (x > xMax) {
        xMax = x;
    }
    y = static_cast<int>((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMax) / (div * face->units_per_EM));
    if (y < yMin) {
        yMin = y;
    } else if (y > yMax) {
        yMax = y;
    }
    x = static_cast<int>((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMin) / (div * face->units_per_EM));
    if (x < xMin) {
        xMin = x;
    } else if (x > xMax) {
        xMax = x;
    }
    y = static_cast<int>((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMin) / (div * face->units_per_EM));
    if (y < yMin) {
        yMin = y;
    } else if (y > yMax) {
        yMax = y;
    }
    x = static_cast<int>((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMax) / (div * face->units_per_EM));
    if (x < xMin) {
        xMin = x;
    } else if (x > xMax) {
        xMax = x;
    }
    y = static_cast<int>((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMax) / (div * face->units_per_EM));
    if (y < yMin) {
        yMin = y;
    } else if (y > yMax) {
        yMax = y;
    }
    // This is a kludge: some buggy PDF generators embed fonts with
    // zero bounding boxes.
    if (xMax == xMin) {
        xMin = 0;
        xMax = size;
    }
    if (yMax == yMin) {
        yMin = 0;
        yMax = static_cast<int>(1.2 * size);
    }

    // compute the transform matrix
    matrix.xx = static_cast<FT_Fixed>((mat[0] / size) * 65536);
    matrix.yx = static_cast<FT_Fixed>((mat[1] / size) * 65536);
    matrix.xy = static_cast<FT_Fixed>((mat[2] / size) * 65536);
    matrix.yy = static_cast<FT_Fixed>((mat[3] / size) * 65536);
    textMatrix.xx = static_cast<FT_Fixed>((textMat[0] / (textScale * size)) * 65536);
    textMatrix.yx = static_cast<FT_Fixed>((textMat[1] / (textScale * size)) * 65536);
    textMatrix.xy = static_cast<FT_Fixed>((textMat[2] / (textScale * size)) * 65536);
    textMatrix.yy = static_cast<FT_Fixed>((textMat[3] / (textScale * size)) * 65536);

    isOk = true;
}

SplashFTFont::~SplashFTFont() = default;

bool SplashFTFont::getGlyph(int c, int xFrac, int /*yFrac*/, SplashGlyphBitmap *bitmap, int x0, int y0, const SplashClip &clip, SplashClipResult *clipRes)
{
    return SplashFont::getGlyph(c, xFrac, 0, bitmap, x0, y0, clip, clipRes);
}

static FT_Int32 getFTLoadFlags(bool type1, bool trueType, bool aa, bool enableFreeTypeHinting, bool enableSlightHinting)
{
    int ret = FT_LOAD_DEFAULT;
    if (aa) {
        ret |= FT_LOAD_NO_BITMAP;
    }

    if (enableFreeTypeHinting) {
        if (enableSlightHinting) {
            ret |= FT_LOAD_TARGET_LIGHT;
        } else {
            if (trueType) {
                // FT2's autohinting doesn't always work very well (especially with
                // font subsets), so turn it off if anti-aliasing is enabled; if
                // anti-aliasing is disabled, this seems to be a tossup - some fonts
                // look better with hinting, some without, so leave hinting on
                if (aa) {
                    ret |= FT_LOAD_NO_AUTOHINT;
                }
            } else if (type1) {
                // Type 1 fonts seem to look better with 'light' hinting mode
                ret |= FT_LOAD_TARGET_LIGHT;
            }
        }
    } else {
        ret |= FT_LOAD_NO_HINTING;
    }
    return ret;
}

bool SplashFTFont::makeGlyph(int c, int xFrac, int /*yFrac*/, SplashGlyphBitmap *bitmap, int x0, int y0, const SplashClip &clip, SplashClipResult *clipRes)
{
    SplashFTFontFile *ff;
    FT_Vector offset;
    FT_GlyphSlot slot;
    FT_UInt gid;
    int rowSize;
    unsigned char *p, *q;
    int i;

    if (unlikely(!isOk)) {
        return false;
    }

    ff = static_cast<SplashFTFontFile *>(fontFile.get());

    ff->face->size = sizeObj;
    offset.x = static_cast<FT_Pos>(static_cast<int>(static_cast<double>(xFrac) * splashFontFractionMul * 64));
    offset.y = 0;
    FT_Set_Transform(ff->face, &matrix, &offset);
    slot = ff->face->glyph;

    if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) {
        gid = static_cast<FT_UInt>(ff->codeToGID[c]);
    } else {
        gid = static_cast<FT_UInt>(c);
    }

    if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) {
        return false;
    }

    // prelimirary values based on FT_Outline_Get_CBox
    // we add two pixels to each side to be in the safe side
    FT_BBox cbox;
    FT_Outline_Get_CBox(&ff->face->glyph->outline, &cbox);
    bitmap->x = -(cbox.xMin / 64) + 2;
    bitmap->y = (cbox.yMax / 64) + 2;
    bitmap->w = ((cbox.xMax - cbox.xMin) / 64) + 4;
    bitmap->h = ((cbox.yMax - cbox.yMin) / 64) + 4;

    int rectXMin, rectYMin;
    if (checkedSubtraction(x0, bitmap->x, &rectXMin)) {
        return false;
    }
    if (checkedSubtraction(y0, bitmap->y, &rectYMin)) {
        return false;
    }
    *clipRes = clip.testRect(rectXMin, rectYMin, rectXMin + bitmap->w, rectYMin + bitmap->h);
    if (*clipRes == splashClipAllOutside) {
        bitmap->freeData = false;
        return true;
    }

    if (FT_Render_Glyph(slot, aa ? ft_render_mode_normal : ft_render_mode_mono)) {
        return false;
    }

    if (slot->bitmap.width == 0 || slot->bitmap.rows == 0) {
        // this can happen if (a) the glyph is really tiny or (b) the
        // metrics in the TrueType file are broken
        return false;
    }

    bitmap->x = -slot->bitmap_left;
    bitmap->y = slot->bitmap_top;
    bitmap->w = slot->bitmap.width;
    bitmap->h = slot->bitmap.rows;
    bitmap->aa = aa;
    if (aa) {
        rowSize = bitmap->w;
    } else {
        rowSize = (bitmap->w + 7) >> 3;
    }
    bitmap->data = static_cast<unsigned char *>(gmallocn_checkoverflow(rowSize, bitmap->h));
    if (!bitmap->data) {
        return false;
    }
    bitmap->freeData = true;
    for (i = 0, p = bitmap->data, q = slot->bitmap.buffer; i < bitmap->h; ++i, p += rowSize, q += slot->bitmap.pitch) {
        memcpy(p, q, rowSize);
    }

    return true;
}

double SplashFTFont::getGlyphAdvance(int c)
{
    SplashFTFontFile *ff;
    FT_Vector offset;
    FT_UInt gid;
    FT_Matrix identityMatrix;

    ff = static_cast<SplashFTFontFile *>(fontFile.get());

    // init the matrix
    identityMatrix.xx = 65536; // 1 in 16.16 format
    identityMatrix.xy = 0;
    identityMatrix.yx = 0;
    identityMatrix.yy = 65536; // 1 in 16.16 format

    // init the offset
    offset.x = 0;
    offset.y = 0;

    ff->face->size = sizeObj;
    FT_Set_Transform(ff->face, &identityMatrix, &offset);

    if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) {
        gid = static_cast<FT_UInt>(ff->codeToGID[c]);
    } else {
        gid = static_cast<FT_UInt>(c);
    }

    if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) {
        return -1;
    }

    // 64.0 is 1 in 26.6 format
    return ff->face->glyph->metrics.horiAdvance / 64.0 / size;
}

struct SplashFTFontPath
{
    SplashPath *path;
    double textScale;
    bool needClose;
};

SplashPath *SplashFTFont::getGlyphPath(int c)
{
    static const FT_Outline_Funcs outlineFuncs = {
#if FREETYPE_MINOR <= 1
        (int (*)(FT_Vector *, void *))&glyphPathMoveTo,
        (int (*)(FT_Vector *, void *))&glyphPathLineTo,
        (int (*)(FT_Vector *, FT_Vector *, void *))&glyphPathConicTo,
        (int (*)(FT_Vector *, FT_Vector *, FT_Vector *, void *))&glyphPathCubicTo,
#else
        .move_to = &glyphPathMoveTo,
        .line_to = &glyphPathLineTo,
        .conic_to = &glyphPathConicTo,
        .cubic_to = &glyphPathCubicTo,
#endif
        .shift = 0,
        .delta = 0
    };
    SplashFTFontFile *ff;
    SplashFTFontPath path;
    FT_GlyphSlot slot;
    FT_UInt gid;
    FT_Glyph glyph;

    if (unlikely(textScale == 0)) {
        return nullptr;
    }

    ff = static_cast<SplashFTFontFile *>(fontFile.get());
    ff->face->size = sizeObj;
    FT_Set_Transform(ff->face, &textMatrix, nullptr);
    slot = ff->face->glyph;
    if (c >= 0 && static_cast<size_t>(c) < ff->codeToGID.size()) {
        gid = ff->codeToGID[c];
    } else {
        gid = static_cast<FT_UInt>(c);
    }
    if (FT_Load_Glyph(ff->face, gid, getFTLoadFlags(ff->type1, ff->trueType, aa, enableFreeTypeHinting, enableSlightHinting))) {
        return nullptr;
    }
    if (FT_Get_Glyph(slot, &glyph)) {
        return nullptr;
    }
    if (FT_Outline_Check(&(reinterpret_cast<FT_OutlineGlyph>(glyph))->outline)) {
        return nullptr;
    }
    path.path = new SplashPath();
    path.textScale = textScale;
    path.needClose = false;
    FT_Outline_Decompose(&(reinterpret_cast<FT_OutlineGlyph>(glyph))->outline, &outlineFuncs, &path);
    if (path.needClose) {
        path.path->close();
    }
    FT_Done_Glyph(glyph);
    return path.path;
}

static int glyphPathMoveTo(const FT_Vector *pt, void *path)
{
    auto *p = static_cast<SplashFTFontPath *>(path);

    if (p->needClose) {
        p->path->close();
        p->needClose = false;
    }
    p->path->moveTo(static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0);
    return 0;
}

static int glyphPathLineTo(const FT_Vector *pt, void *path)
{
    auto *p = static_cast<SplashFTFontPath *>(path);

    p->path->lineTo(static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0);
    p->needClose = true;
    return 0;
}

static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt, void *path)
{
    auto *p = static_cast<SplashFTFontPath *>(path);
    double x0, y0, x1, y1, x2, y2, x3, y3, xc, yc;

    if (!p->path->getCurPt(&x0, &y0)) {
        return 0;
    }
    xc = static_cast<double>(ctrl->x) * p->textScale / 64.0;
    yc = static_cast<double>(ctrl->y) * p->textScale / 64.0;
    x3 = static_cast<double>(pt->x) * p->textScale / 64.0;
    y3 = static_cast<double>(pt->y) * p->textScale / 64.0;

    // A second-order Bezier curve is defined by two endpoints, p0 and
    // p3, and one control point, pc:
    //
    //     p(t) = (1-t)^2*p0 + t*(1-t)*pc + t^2*p3
    //
    // A third-order Bezier curve is defined by the same two endpoints,
    // p0 and p3, and two control points, p1 and p2:
    //
    //     p(t) = (1-t)^3*p0 + 3t*(1-t)^2*p1 + 3t^2*(1-t)*p2 + t^3*p3
    //
    // Applying some algebra, we can convert a second-order curve to a
    // third-order curve:
    //
    //     p1 = (1/3) * (p0 + 2pc)
    //     p2 = (1/3) * (2pc + p3)

    x1 = (1.0 / 3.0) * (x0 + 2.0 * xc);
    y1 = (1.0 / 3.0) * (y0 + 2.0 * yc);
    x2 = (1.0 / 3.0) * (2.0 * xc + x3);
    y2 = (1.0 / 3.0) * (2.0 * yc + y3);

    p->path->curveTo(x1, y1, x2, y2, x3, y3);
    p->needClose = true;
    return 0;
}

static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2, const FT_Vector *pt, void *path)
{
    auto *p = static_cast<SplashFTFontPath *>(path);

    p->path->curveTo(static_cast<double>(ctrl1->x) * p->textScale / 64.0, static_cast<double>(ctrl1->y) * p->textScale / 64.0, static_cast<double>(ctrl2->x) * p->textScale / 64.0, static_cast<double>(ctrl2->y) * p->textScale / 64.0,
                     static_cast<double>(pt->x) * p->textScale / 64.0, static_cast<double>(pt->y) * p->textScale / 64.0);
    p->needClose = true;
    return 0;
}
