blob: a366f7126fbc199ccf7d29155088a5f1856e122c [file] [log] [blame]
//========================================================================
//
// 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 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>
//
// 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(SplashFTFontFile *fontFileA, SplashCoord *matA, const SplashCoord *textMatA)
: SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa), textScale(0), enableFreeTypeHinting(fontFileA->engine->enableFreeTypeHinting), enableSlightHinting(fontFileA->engine->enableSlightHinting), isOk(false)
{
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 = (int)((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMin) / (div * face->units_per_EM));
xMin = xMax = x;
y = (int)((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMin) / (div * face->units_per_EM));
yMin = yMax = y;
x = (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 = (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 = (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 = (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 = (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 = (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 = (int)((SplashCoord)1.2 * size);
}
// compute the transform matrix
matrix.xx = (FT_Fixed)((mat[0] / size) * 65536);
matrix.yx = (FT_Fixed)((mat[1] / size) * 65536);
matrix.xy = (FT_Fixed)((mat[2] / size) * 65536);
matrix.yy = (FT_Fixed)((mat[3] / size) * 65536);
textMatrix.xx = (FT_Fixed)((textMat[0] / (textScale * size)) * 65536);
textMatrix.yx = (FT_Fixed)((textMat[1] / (textScale * size)) * 65536);
textMatrix.xy = (FT_Fixed)((textMat[2] / (textScale * size)) * 65536);
textMatrix.yy = (FT_Fixed)((textMat[3] / (textScale * size)) * 65536);
isOk = true;
}
SplashFTFont::~SplashFTFont() { }
bool SplashFTFont::getGlyph(int c, int xFrac, int yFrac, SplashGlyphBitmap *bitmap, int x0, int y0, 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, 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 = (SplashFTFontFile *)fontFile;
ff->face->size = sizeObj;
offset.x = (FT_Pos)(int)((SplashCoord)xFrac * splashFontFractionMul * 64);
offset.y = 0;
FT_Set_Transform(ff->face, &matrix, &offset);
slot = ff->face->glyph;
if (ff->codeToGID && c < ff->codeToGIDLen && c >= 0) {
gid = (FT_UInt)ff->codeToGID[c];
} else {
gid = (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;
*clipRes = clip->testRect(x0 - bitmap->x, y0 - bitmap->y, x0 - bitmap->x + bitmap->w, y0 - bitmap->y + 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 = (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 = (SplashFTFontFile *)fontFile;
// 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 (ff->codeToGID && c < ff->codeToGIDLen) {
gid = (FT_UInt)ff->codeToGID[c];
} else {
gid = (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;
SplashCoord 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
&glyphPathMoveTo,
&glyphPathLineTo,
&glyphPathConicTo,
&glyphPathCubicTo,
#endif
0,
0
};
SplashFTFontFile *ff;
SplashFTFontPath path;
FT_GlyphSlot slot;
FT_UInt gid;
FT_Glyph glyph;
if (unlikely(textScale == 0)) {
return nullptr;
}
ff = (SplashFTFontFile *)fontFile;
ff->face->size = sizeObj;
FT_Set_Transform(ff->face, &textMatrix, nullptr);
slot = ff->face->glyph;
if (ff->codeToGID && c < ff->codeToGIDLen && c >= 0) {
gid = ff->codeToGID[c];
} else {
gid = (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(&((FT_OutlineGlyph)glyph)->outline)) {
return nullptr;
}
path.path = new SplashPath();
path.textScale = textScale;
path.needClose = false;
FT_Outline_Decompose(&((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)
{
SplashFTFontPath *p = (SplashFTFontPath *)path;
if (p->needClose) {
p->path->close();
p->needClose = false;
}
p->path->moveTo((SplashCoord)pt->x * p->textScale / 64.0, (SplashCoord)pt->y * p->textScale / 64.0);
return 0;
}
static int glyphPathLineTo(const FT_Vector *pt, void *path)
{
SplashFTFontPath *p = (SplashFTFontPath *)path;
p->path->lineTo((SplashCoord)pt->x * p->textScale / 64.0, (SplashCoord)pt->y * p->textScale / 64.0);
p->needClose = true;
return 0;
}
static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt, void *path)
{
SplashFTFontPath *p = (SplashFTFontPath *)path;
SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xc, yc;
if (!p->path->getCurPt(&x0, &y0)) {
return 0;
}
xc = (SplashCoord)ctrl->x * p->textScale / 64.0;
yc = (SplashCoord)ctrl->y * p->textScale / 64.0;
x3 = (SplashCoord)pt->x * p->textScale / 64.0;
y3 = (SplashCoord)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 = (SplashCoord)(1.0 / 3.0) * (x0 + (SplashCoord)2 * xc);
y1 = (SplashCoord)(1.0 / 3.0) * (y0 + (SplashCoord)2 * yc);
x2 = (SplashCoord)(1.0 / 3.0) * ((SplashCoord)2 * xc + x3);
y2 = (SplashCoord)(1.0 / 3.0) * ((SplashCoord)2 * 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)
{
SplashFTFontPath *p = (SplashFTFontPath *)path;
p->path->curveTo((SplashCoord)ctrl1->x * p->textScale / 64.0, (SplashCoord)ctrl1->y * p->textScale / 64.0, (SplashCoord)ctrl2->x * p->textScale / 64.0, (SplashCoord)ctrl2->y * p->textScale / 64.0,
(SplashCoord)pt->x * p->textScale / 64.0, (SplashCoord)pt->y * p->textScale / 64.0);
p->needClose = true;
return 0;
}