| // 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/SkAssert.h" | |
| #include "include/private/SkMalloc.h" | |
| #include "include/private/SkTemplates.h" | |
| #include "include/private/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) |