blob: 4fbeba5c26131064f66fc85fc068dba678c1decb [file] [log] [blame] [edit]
// Copyright 2018 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "src/pdf/SkPDFSubsetFont.h"
#if defined(SK_PDF_USE_HARFBUZZ_SUBSET)
#include "include/core/SkStream.h"
#include "include/core/SkTypeface.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMalloc.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "src/pdf/SkPDFGlyphUse.h"
#include "hb.h" // NO_G3_REWRITE
#include "hb-subset.h" // NO_G3_REWRITE
#include <cstddef>
#include <memory>
#include <utility>
namespace {
using HBBlob = std::unique_ptr<hb_blob_t, SkFunctionObject<hb_blob_destroy>>;
using HBFace = std::unique_ptr<hb_face_t, SkFunctionObject<hb_face_destroy>>;
using HBSubsetInput = std::unique_ptr<hb_subset_input_t, SkFunctionObject<hb_subset_input_destroy>>;
using HBSet = std::unique_ptr<hb_set_t, SkFunctionObject<hb_set_destroy>>;
#if HB_VERSION_ATLEAST(10, 0, 0)
unsigned int skhb_get_table_tags_func(const hb_face_t *face,
unsigned int start_offset,
unsigned int *table_count, /* IN/OUT */
hb_tag_t *table_tags /* OUT */,
void *user_data)
{
SkTypeface& typeface = *reinterpret_cast<SkTypeface*>(user_data);
int numTables = typeface.countTables();
if (numTables <= 0 || static_cast<unsigned int>(numTables) <= start_offset) {
*table_count = 0;
return 0;
}
if (!table_count || *table_count == 0) {
return numTables;
}
std::unique_ptr<SkFontTableTag[]> allTableTags = std::make_unique<SkFontTableTag[]>(numTables);
int numTableTags = typeface.readTableTags({allTableTags.get(), numTables});
if (numTableTags != numTables) {
*table_count = 0;
return 0;
}
static_assert(sizeof(*table_tags) == sizeof(allTableTags[0]), "");
*table_count = std::min(*table_count, static_cast<unsigned int>(numTableTags) - start_offset);
memcpy(table_tags, allTableTags.get() + start_offset, *table_count * sizeof(*table_tags));
return numTableTags;
}
#endif
hb_blob_t* skhb_get_table(hb_face_t* face, hb_tag_t tag, void* user_data) {
SkTypeface& typeface = *reinterpret_cast<SkTypeface*>(user_data);
auto data = typeface.copyTableData(tag);
if (!data) {
return nullptr;
}
SkData* rawData = data.release();
return hb_blob_create(reinterpret_cast<char*>(rawData->writable_data()), rawData->size(),
HB_MEMORY_MODE_READONLY, rawData,
[](void* p) { SkSafeUnref(((SkData*)p)); });
}
HBBlob stream_to_blob(std::unique_ptr<SkStreamAsset> asset) {
size_t size = asset->getLength();
HBBlob blob;
if (const void* base = asset->getMemoryBase()) {
blob.reset(hb_blob_create(const_cast<char*>(static_cast<const char*>(base)), SkToUInt(size),
HB_MEMORY_MODE_READONLY, asset.release(),
[](void* p) { delete (SkStreamAsset*)p; }));
} else {
void* ptr = size ? sk_malloc_throw(size) : nullptr;
asset->read(ptr, size);
blob.reset(hb_blob_create((char*)ptr, SkToUInt(size),
HB_MEMORY_MODE_READONLY, ptr, sk_free));
}
SkASSERT(blob);
hb_blob_make_immutable(blob.get());
return blob;
}
HBFace stream_to_face(std::unique_ptr<SkStreamAsset> asset, int index) {
HBFace face;
HBBlob blob(stream_to_blob(std::move(asset)));
// hb_face_create always succeeds. Check that the format is minimally recognized first.
// See https://github.com/harfbuzz/harfbuzz/issues/248
unsigned int num_hb_faces = hb_face_count(blob.get());
if (0 < num_hb_faces && (unsigned)index < num_hb_faces) {
face.reset(hb_face_create(blob.get(), (unsigned)index));
// Check the number of glyphs as a basic sanitization step.
if (face && hb_face_get_glyph_count(face.get()) == 0) {
face.reset();
}
}
return face;
}
sk_sp<SkData> to_data(HBBlob blob) {
if (!blob) {
return nullptr;
}
unsigned int length;
const char* data = hb_blob_get_data(blob.get(), &length);
if (!data || !length) {
return nullptr;
}
return SkData::MakeWithProc(data, SkToSizeT(length),
[](const void*, void* ctx) { hb_blob_destroy((hb_blob_t*)ctx); },
blob.release());
}
HBFace make_subset(hb_subset_input_t* input, hb_face_t* face, bool retainZeroGlyph) {
// TODO: When possible, check if a font is 'tricky' with FT_IS_TRICKY.
// If it isn't known if a font is 'tricky', retain the hints.
unsigned int flags = HB_SUBSET_FLAGS_RETAIN_GIDS;
if (retainZeroGlyph) {
flags |= HB_SUBSET_FLAGS_NOTDEF_OUTLINE;
}
hb_subset_input_set_flags(input, flags);
return HBFace(hb_subset_or_fail(face, input));
}
sk_sp<SkData> subset_harfbuzz(const SkTypeface& typeface, const SkPDFGlyphUse& glyphUsage) {
HBFace face;
if (!SkPDFCanSubsetTableBasedFonts()) {
int index = 0;
std::unique_ptr<SkStreamAsset> typefaceAsset = typeface.openStream(&index);
if (typefaceAsset) {
face = stream_to_face(std::move(typefaceAsset), index);
}
} else {
int index = 0;
std::unique_ptr<SkStreamAsset> typefaceAsset = typeface.openExistingStream(&index);
if (typefaceAsset && typefaceAsset->getMemoryBase()) {
face = stream_to_face(std::move(typefaceAsset), index);
}
if (!face) {
// Use hb_face_create_for_tables to try creating a working hb_face.
// Using this creates empty subsets in HarfBuzz until 4.4.0.
face.reset(hb_face_create_for_tables(
skhb_get_table,
const_cast<SkTypeface*>(SkRef(&typeface)),
[](void* user_data){ SkSafeUnref(reinterpret_cast<SkTypeface*>(user_data)); }));
hb_face_set_index(face.get(), (unsigned)index);
#if HB_VERSION_ATLEAST(10, 0, 0)
hb_face_set_get_table_tags_func(
face.get(), skhb_get_table_tags_func,
const_cast<SkTypeface*>(SkRef(&typeface)),
[](void* user_data){ SkSafeUnref(reinterpret_cast<SkTypeface*>(user_data)); });
#endif
// Check the number of glyphs as a basic sanitization step.
if (face && hb_face_get_glyph_count(face.get()) == 0) {
face.reset();
}
}
}
HBSubsetInput input(hb_subset_input_create_or_fail());
SkASSERT(input);
if (!face || !input) {
return nullptr;
}
hb_set_t* glyphs = hb_subset_input_glyph_set(input.get());
glyphUsage.getSetValues([&glyphs](unsigned gid) { hb_set_add(glyphs, gid);});
HBFace subset = make_subset(input.get(), face.get(), glyphUsage.has(0));
if (!subset) {
return nullptr;
}
HBBlob result(hb_face_reference_blob(subset.get()));
return to_data(std::move(result));
}
} // namespace
sk_sp<SkData> SkPDFSubsetFont(const SkTypeface& typeface, const SkPDFGlyphUse& glyphUsage) {
return subset_harfbuzz(typeface, glyphUsage);
}
bool SkPDFCanSubsetTableBasedFonts() {
// See https://github.com/harfbuzz/harfbuzz/issues/3609
// See https://github.com/harfbuzz/harfbuzz/issues/1697
// This file requires HarfBuzz 3.0 or later (when the public subsetter API shipped).
// The default tables to search for subsetting were added in HarfBuzz 4.4.0.
// The full fix (hb_face_set_get_table_tags_func) is in HarfBuzz 10.0.0.
return hb_version_atleast(4, 4, 0);
}
#else
sk_sp<SkData> SkPDFSubsetFont(const SkTypeface&, const SkPDFGlyphUse&) {
return nullptr;
}
bool SkPDFCanSubsetTableBasedFonts() {
return false;
}
#endif // defined(SK_PDF_USE_HARFBUZZ_SUBSET)