blob: b4575d50759e0d3da096379dce671a1fe853eb02 [file] [log] [blame] [edit]
//========================================================================
//
// CairoFontEngine.cc
//
// Copyright 2003 Glyph & Cog, LLC
// Copyright 2004 Red Hat, Inc
//
//========================================================================
//========================================================================
//
// 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 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2005, 2006 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2005 Martin Kretzschmar <martink@gnome.org>
// Copyright (C) 2005, 2009, 2012, 2013, 2015, 2017-2019, 2021, 2022, 2024 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2006, 2007, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2007 Koji Otani <sho@bbr.jp>
// Copyright (C) 2008, 2009 Chris Wilson <chris@chris-wilson.co.uk>
// Copyright (C) 2008, 2012, 2014, 2016, 2017, 2022-2024 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2009 Darren Kenny <darren.kenny@sun.com>
// Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
// Copyright (C) 2010 Jan Kümmel <jan+freedesktop@snorc.org>
// Copyright (C) 2012 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2015, 2016 Jason Crain <jason@aquaticape.us>
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2019 Christian Persch <chpe@src.gnome.org>
// Copyright (C) 2020 Michal <sudolskym@gmail.com>
// Copyright (C) 2021, 2022 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2022 Marcel Fabian Krüger <tex@2krueger.de>
// Copyright (C) 2023 Pablo Correa Gómez <ablocorrea@hotmail.com>
// Copyright (C) 2023 Frederic Germain <frederic.germain@gmail.com>
// Copyright (C) 2023 Ilia Kats <ilia-kats@gmx.net>
// Copyright (C) 2024 Vincent Lefevre <vincent@vinc17.net>
// Copyright (C) 2024 Dmitry Shubin <dshubin@accusoft.com>
//
// 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 <cstring>
#include <fstream>
#include "CairoFontEngine.h"
#include "CairoOutputDev.h"
#include "GlobalParams.h"
#include <fofi/FoFiTrueType.h>
#include <fofi/FoFiType1C.h>
#include "goo/ft_utils.h"
#include "goo/gfile.h"
#include "Error.h"
#include "XRef.h"
#include "Gfx.h"
#include "Page.h"
//------------------------------------------------------------------------
// CairoFont
//------------------------------------------------------------------------
CairoFont::CairoFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool substituteA, bool printingA) : ref(refA), cairo_font_face(cairo_font_faceA), substitute(substituteA), printing(printingA)
{
codeToGID = std::move(codeToGIDA);
}
CairoFont::~CairoFont()
{
cairo_font_face_destroy(cairo_font_face);
}
bool CairoFont::matches(Ref &other, bool printingA)
{
return (other == ref);
}
cairo_font_face_t *CairoFont::getFontFace()
{
return cairo_font_face;
}
unsigned long CairoFont::getGlyph(CharCode code, const Unicode *u, int uLen)
{
FT_UInt gid;
if (code < codeToGID.size()) {
gid = (FT_UInt)codeToGID[code];
} else {
gid = (FT_UInt)code;
}
return gid;
}
double CairoFont::getSubstitutionCorrection(const std::shared_ptr<GfxFont> &gfxFont)
{
double w1, w2, w3;
CharCode code;
const char *name;
// for substituted fonts: adjust the font matrix -- compare the
// width of 'm' in the original font and the substituted font
if (isSubstitute() && !gfxFont->isCIDFont()) {
for (code = 0; code < 256; ++code) {
if ((name = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getCharName(code)) && name[0] == 'm' && name[1] == '\0') {
break;
}
}
if (code < 256) {
w1 = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getWidth(code);
{
cairo_matrix_t m;
cairo_matrix_init_identity(&m);
cairo_font_options_t *options = cairo_font_options_create();
cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE);
cairo_font_options_set_hint_metrics(options, CAIRO_HINT_METRICS_OFF);
cairo_scaled_font_t *scaled_font = cairo_scaled_font_create(cairo_font_face, &m, &m, options);
cairo_text_extents_t extents;
cairo_scaled_font_text_extents(scaled_font, "m", &extents);
cairo_scaled_font_destroy(scaled_font);
cairo_font_options_destroy(options);
w2 = extents.x_advance;
}
w3 = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getWidth(0);
if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) {
// if real font is substantially narrower than substituted
// font, reduce the font size accordingly
if (w1 > 0.01 && w1 < 0.9 * w2) {
w1 /= w2;
return w1;
}
}
}
}
return 1.0;
}
//------------------------------------------------------------------------
// CairoFreeTypeFont
//------------------------------------------------------------------------
static cairo_user_data_key_t ft_cairo_key;
// Font resources to be freed when cairo_font_face_t is destroyed
struct FreeTypeFontResource
{
FT_Face face;
std::vector<unsigned char> font_data;
};
// cairo callback for when cairo_font_face_t is destroyed
static void _ft_done_face(void *closure)
{
FreeTypeFontResource *resource = (FreeTypeFontResource *)closure;
FT_Done_Face(resource->face);
delete resource;
}
CairoFreeTypeFont::CairoFreeTypeFont(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool substituteA) : CairoFont(refA, cairo_font_faceA, std::move(codeToGIDA), substituteA, true) { }
CairoFreeTypeFont::~CairoFreeTypeFont() { }
// Create a cairo_font_face_t for the given font filename OR font data.
std::optional<FreeTypeFontFace> CairoFreeTypeFont::createFreeTypeFontFace(FT_Library lib, const std::string &filename, std::vector<unsigned char> &&font_data)
{
FreeTypeFontResource *resource = new FreeTypeFontResource;
FreeTypeFontFace font_face;
if (font_data.empty()) {
FT_Error err = ft_new_face_from_file(lib, filename.c_str(), 0, &resource->face);
if (err) {
delete resource;
return {};
}
} else {
resource->font_data = std::move(font_data);
FT_Error err = FT_New_Memory_Face(lib, (FT_Byte *)resource->font_data.data(), resource->font_data.size(), 0, &resource->face);
if (err) {
delete resource;
return {};
}
}
font_face.cairo_font_face = cairo_ft_font_face_create_for_ft_face(resource->face, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
if (cairo_font_face_set_user_data(font_face.cairo_font_face, &ft_cairo_key, resource, _ft_done_face)) {
cairo_font_face_destroy(font_face.cairo_font_face);
_ft_done_face(resource);
return {};
}
font_face.face = resource->face;
return font_face;
}
CairoFreeTypeFont *CairoFreeTypeFont::create(const std::shared_ptr<GfxFont> &gfxFont, XRef *xref, FT_Library lib, CairoFontEngine *fontEngine, bool useCIDs)
{
std::string fileName;
int faceIndex = 0;
std::vector<unsigned char> font_data;
int i, n;
std::optional<GfxFontLoc> fontLoc;
char **enc;
const char *name;
FoFiType1C *ff1c;
std::optional<FreeTypeFontFace> font_face;
std::vector<int> codeToGID;
bool substitute = false;
Ref ref = *gfxFont->getID();
Ref embFontID = Ref::INVALID();
gfxFont->getEmbeddedFontID(&embFontID);
GfxFontType fontType = gfxFont->getType();
if (!(fontLoc = gfxFont->locateFont(xref, nullptr))) {
error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
goto err2;
}
// embedded font
if (fontLoc->locType == gfxFontLocEmbedded) {
auto fd = gfxFont->readEmbFontFile(xref);
if (!fd || fd->empty()) {
goto err2;
}
font_data = std::move(fd.value());
// external font
} else { // gfxFontLocExternal
fileName = fontLoc->path;
fontType = fontLoc->fontType;
faceIndex = fontLoc->fontNum;
substitute = true;
}
switch (fontType) {
case fontType1:
case fontType1C:
case fontType1COT:
font_face = createFreeTypeFontFace(lib, fileName, std::move(font_data));
if (!font_face) {
error(errSyntaxError, -1, "could not create type1 face");
goto err2;
}
enc = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getEncoding();
codeToGID.resize(256);
for (i = 0; i < 256; ++i) {
codeToGID[i] = 0;
if ((name = enc[i])) {
codeToGID[i] = FT_Get_Name_Index(font_face->face, (char *)name);
if (codeToGID[i] == 0) {
Unicode u;
u = globalParams->mapNameToUnicodeText(name);
codeToGID[i] = FT_Get_Char_Index(font_face->face, u);
}
if (codeToGID[i] == 0) {
name = GfxFont::getAlternateName(name);
if (name) {
codeToGID[i] = FT_Get_Name_Index(font_face->face, (char *)name);
}
}
}
}
break;
case fontCIDType2:
case fontCIDType2OT:
if (std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCIDToGID()) {
n = std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCIDToGIDLen();
if (n) {
const int *src = std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCIDToGID();
codeToGID.reserve(n);
codeToGID.insert(codeToGID.begin(), src, src + n);
}
} else {
std::unique_ptr<FoFiTrueType> ff;
if (!font_data.empty()) {
ff = FoFiTrueType::make(font_data.data(), font_data.size(), faceIndex);
} else {
ff = FoFiTrueType::load(fileName.c_str(), faceIndex);
}
if (!ff) {
goto err2;
}
int *src = std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCodeToGIDMap(ff.get(), &n);
codeToGID.reserve(n);
codeToGID.insert(codeToGID.begin(), src, src + n);
gfree(src);
}
/* Fall through */
case fontTrueType:
case fontTrueTypeOT: {
std::unique_ptr<FoFiTrueType> ff;
if (!font_data.empty()) {
ff = FoFiTrueType::make(font_data.data(), font_data.size(), faceIndex);
} else {
ff = FoFiTrueType::load(fileName.c_str(), faceIndex);
}
if (!ff) {
error(errSyntaxError, -1, "failed to load truetype font");
goto err2;
}
/* This might be set already for the CIDType2 case */
if (fontType == fontTrueType || fontType == fontTrueTypeOT) {
int *src = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getCodeToGIDMap(ff.get());
codeToGID.reserve(256);
codeToGID.insert(codeToGID.begin(), src, src + 256);
gfree(src);
}
font_face = createFreeTypeFontFace(lib, fileName, std::move(font_data));
if (!font_face) {
error(errSyntaxError, -1, "could not create truetype face");
goto err2;
}
break;
}
case fontCIDType0:
case fontCIDType0C:
if (!useCIDs) {
if (!font_data.empty()) {
ff1c = FoFiType1C::make(font_data.data(), font_data.size());
} else {
ff1c = FoFiType1C::load(fileName.c_str());
}
if (ff1c) {
int *src = ff1c->getCIDToGIDMap(&n);
codeToGID.reserve(n);
codeToGID.insert(codeToGID.begin(), src, src + n);
gfree(src);
delete ff1c;
}
}
font_face = createFreeTypeFontFace(lib, fileName, std::move(font_data));
if (!font_face) {
error(errSyntaxError, -1, "could not create cid face");
goto err2;
}
break;
case fontCIDType0COT:
if (std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCIDToGID()) {
n = std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCIDToGIDLen();
if (n) {
const int *src = std::static_pointer_cast<GfxCIDFont>(gfxFont)->getCIDToGID();
codeToGID.reserve(n);
codeToGID.insert(codeToGID.begin(), src, src + n);
}
}
if (codeToGID.empty()) {
if (!useCIDs) {
std::unique_ptr<FoFiTrueType> ff;
if (!font_data.empty()) {
ff = FoFiTrueType::make(font_data.data(), font_data.size(), faceIndex);
} else {
ff = FoFiTrueType::load(fileName.c_str(), faceIndex);
}
if (ff) {
if (ff->isOpenTypeCFF()) {
int *src = ff->getCIDToGIDMap(&n);
codeToGID.reserve(n);
codeToGID.insert(codeToGID.begin(), src, src + n);
gfree(src);
}
}
}
}
font_face = createFreeTypeFontFace(lib, fileName, std::move(font_data));
if (!font_face) {
error(errSyntaxError, -1, "could not create cid (OT) face");
goto err2;
}
break;
default:
fprintf(stderr, "font type %d not handled\n", (int)fontType);
goto err2;
break;
}
return new CairoFreeTypeFont(ref, font_face->cairo_font_face, std::move(codeToGID), substitute);
err2:
fprintf(stderr, "some font thing failed\n");
return nullptr;
}
//------------------------------------------------------------------------
// CairoType3Font
//------------------------------------------------------------------------
static const cairo_user_data_key_t type3_font_key = { 0 };
typedef struct _type3_font_info
{
_type3_font_info(const std::shared_ptr<GfxFont> &fontA, PDFDoc *docA, CairoFontEngine *fontEngineA, CairoOutputDev *outputDevA, Gfx *gfxA) : font(fontA), doc(docA), fontEngine(fontEngineA), outputDev(outputDevA), gfx(gfxA) { }
std::shared_ptr<GfxFont> font;
PDFDoc *doc;
CairoFontEngine *fontEngine;
CairoOutputDev *outputDev;
Gfx *gfx;
} type3_font_info_t;
static void _free_type3_font_info(void *closure)
{
type3_font_info_t *info = (type3_font_info_t *)closure;
delete info->gfx;
delete info->outputDev;
delete info;
}
static cairo_status_t _init_type3_glyph(cairo_scaled_font_t *scaled_font, cairo_t *cr, cairo_font_extents_t *extents)
{
type3_font_info_t *info;
info = (type3_font_info_t *)cairo_font_face_get_user_data(cairo_scaled_font_get_font_face(scaled_font), &type3_font_key);
const double *mat = info->font->getFontBBox();
extents->ascent = mat[3]; /* y2 */
extents->descent = -mat[3]; /* -y1 */
extents->height = extents->ascent + extents->descent;
extents->max_x_advance = mat[2] - mat[1]; /* x2 - x1 */
extents->max_y_advance = 0;
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t _render_type3_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics, bool color)
{
Dict *charProcs;
Object charProc;
cairo_matrix_t matrix, invert_y_axis;
const double *mat;
double wx, wy;
type3_font_info_t *info;
Gfx *gfx;
cairo_status_t status;
info = (type3_font_info_t *)cairo_font_face_get_user_data(cairo_scaled_font_get_font_face(scaled_font), &type3_font_key);
charProcs = std::static_pointer_cast<Gfx8BitFont>(info->font)->getCharProcs();
if (!charProcs) {
return CAIRO_STATUS_USER_FONT_ERROR;
}
if ((int)glyph >= charProcs->getLength()) {
return CAIRO_STATUS_USER_FONT_ERROR;
}
mat = info->font->getFontMatrix();
matrix.xx = mat[0];
matrix.yx = mat[1];
matrix.xy = mat[2];
matrix.yy = mat[3];
matrix.x0 = mat[4];
matrix.y0 = mat[5];
cairo_matrix_init_scale(&invert_y_axis, 1, -1);
cairo_matrix_multiply(&matrix, &matrix, &invert_y_axis);
cairo_transform(cr, &matrix);
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0)
cairo_set_source(cr, cairo_user_scaled_font_get_foreground_marker(scaled_font));
#endif
CairoOutputDev *output_dev = info->outputDev;
output_dev->setCairo(cr);
gfx = info->gfx;
gfx->saveState();
output_dev->startDoc(info->doc, info->fontEngine);
output_dev->startType3Render(gfx->getState(), gfx->getXRef());
output_dev->setType3RenderType(color ? CairoOutputDev::Type3RenderColor : CairoOutputDev::Type3RenderMask);
charProc = charProcs->getVal(glyph);
if (!charProc.isStream()) {
return CAIRO_STATUS_USER_FONT_ERROR;
}
Object charProcResObject = charProc.streamGetDict()->lookup("Resources");
if (charProcResObject.isDict()) {
gfx->pushResources(charProcResObject.getDict());
}
gfx->display(&charProc);
if (charProcResObject.isDict()) {
gfx->popResources();
}
output_dev->getType3GlyphWidth(&wx, &wy);
cairo_matrix_transform_distance(&matrix, &wx, &wy);
metrics->x_advance = wx;
metrics->y_advance = wy;
if (output_dev->hasType3GlyphBBox()) {
double *bbox = output_dev->getType3GlyphBBox();
cairo_matrix_transform_point(&matrix, &bbox[0], &bbox[1]);
cairo_matrix_transform_point(&matrix, &bbox[2], &bbox[3]);
metrics->x_bearing = bbox[0];
metrics->y_bearing = bbox[1];
metrics->width = bbox[2] - bbox[0];
metrics->height = bbox[3] - bbox[1];
}
gfx->restoreState();
status = CAIRO_STATUS_SUCCESS;
// If this is a render color glyph callback but the Type 3 glyph
// specified non-color, return NOT_IMPLEMENTED. Cairo will then
// call the render non-color glyph callback.
if (color && !output_dev->type3GlyphHasColor()) {
status = CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED;
}
return status;
}
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0)
static cairo_status_t _render_type3_color_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics)
{
return _render_type3_glyph(scaled_font, glyph, cr, metrics, true);
}
#endif
static cairo_status_t _render_type3_noncolor_glyph(cairo_scaled_font_t *scaled_font, unsigned long glyph, cairo_t *cr, cairo_text_extents_t *metrics)
{
return _render_type3_glyph(scaled_font, glyph, cr, metrics, false);
}
CairoType3Font *CairoType3Font::create(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine, bool printing, XRef *xref)
{
std::vector<int> codeToGID;
char *name;
const double *mat;
Dict *charProcs = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getCharProcs();
Ref ref = *gfxFont->getID();
cairo_font_face_t *font_face = cairo_user_font_face_create();
cairo_user_font_face_set_init_func(font_face, _init_type3_glyph);
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0)
// When both callbacks are set, Cairo will call the color glyph
// callback first. If that returns NOT_IMPLEMENTED, Cairo will
// then call the non-color glyph callback.
cairo_user_font_face_set_render_color_glyph_func(font_face, _render_type3_color_glyph);
#endif
cairo_user_font_face_set_render_glyph_func(font_face, _render_type3_noncolor_glyph);
CairoOutputDev *output_dev = new CairoOutputDev();
output_dev->setPrinting(printing);
Dict *resDict = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getResources();
mat = gfxFont->getFontBBox();
PDFRectangle box;
box.x1 = mat[0];
box.y1 = mat[1];
box.x2 = mat[2];
box.y2 = mat[3];
Gfx *gfx = new Gfx(doc, output_dev, resDict, &box, nullptr);
type3_font_info_t *info = new type3_font_info_t(gfxFont, doc, fontEngine, output_dev, gfx);
cairo_font_face_set_user_data(font_face, &type3_font_key, (void *)info, _free_type3_font_info);
char **enc = std::static_pointer_cast<Gfx8BitFont>(gfxFont)->getEncoding();
codeToGID.resize(256);
for (int i = 0; i < 256; ++i) {
codeToGID[i] = 0;
if (charProcs && (name = enc[i])) {
for (int j = 0; j < charProcs->getLength(); j++) {
if (strcmp(name, charProcs->getKey(j)) == 0) {
codeToGID[i] = j;
}
}
}
}
return new CairoType3Font(ref, font_face, std::move(codeToGID), printing, xref);
}
CairoType3Font::CairoType3Font(Ref refA, cairo_font_face_t *cairo_font_faceA, std::vector<int> &&codeToGIDA, bool printingA, XRef *xref) : CairoFont(refA, cairo_font_faceA, std::move(codeToGIDA), false, printingA) { }
CairoType3Font::~CairoType3Font() { }
bool CairoType3Font::matches(Ref &other, bool printingA)
{
return (other == ref && printing == printingA);
}
//------------------------------------------------------------------------
// CairoFontEngine
//------------------------------------------------------------------------
CairoFontEngine::CairoFontEngine(FT_Library libA)
{
lib = libA;
fontCache.reserve(cairoFontCacheSize);
FT_Int major, minor, patch;
// as of FT 2.1.8, CID fonts are indexed by CID instead of GID
FT_Library_Version(lib, &major, &minor, &patch);
useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7)));
}
CairoFontEngine::~CairoFontEngine() { }
std::shared_ptr<CairoFont> CairoFontEngine::getFont(const std::shared_ptr<GfxFont> &gfxFont, PDFDoc *doc, bool printing, XRef *xref)
{
std::scoped_lock lock(mutex);
Ref ref = *gfxFont->getID();
std::shared_ptr<CairoFont> font;
// Check if font is in the MRU cache, and move it to the end if it is.
for (auto it = fontCache.rbegin(); it != fontCache.rend(); ++it) {
if ((*it)->matches(ref, printing)) {
font = *it;
// move it to the end
if (it != fontCache.rbegin()) {
// https://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator
fontCache.erase(std::next(it).base());
fontCache.push_back(font);
}
return font;
}
}
GfxFontType fontType = gfxFont->getType();
if (fontType == fontType3) {
font = std::shared_ptr<CairoFont>(CairoType3Font::create(gfxFont, doc, this, printing, xref));
} else {
font = std::shared_ptr<CairoFont>(CairoFreeTypeFont::create(gfxFont, xref, lib, this, useCIDs));
}
if (font) {
if (fontCache.size() == cairoFontCacheSize) {
fontCache.erase(fontCache.begin());
}
fontCache.push_back(font);
}
return font;
}