1// Copyright 2018 Google LLC.
2// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3
4#include "src/pdf/SkPDFSubsetFont.h"
5
6#if defined(SK_PDF_USE_HARFBUZZ_SUBSET)
7
8#include "include/private/SkTemplates.h"
9#include "include/private/SkTo.h"
10#include "src/utils/SkCallableTraits.h"
11
12#include "hb.h"
13#include "hb-subset.h"
14
15template <class T, void(*P)(T*)> using resource =
16    std::unique_ptr<T, SkFunctionWrapper<std::remove_pointer_t<decltype(P)>, P>>;
17using HBBlob = resource<hb_blob_t, &hb_blob_destroy>;
18using HBFace = resource<hb_face_t, &hb_face_destroy>;
19using HBSubsetInput = resource<hb_subset_input_t, &hb_subset_input_destroy>;
20using HBSet = resource<hb_set_t, &hb_set_destroy>;
21
22static HBBlob to_blob(sk_sp<SkData> data) {
23    using blob_size_t = SkCallableTraits<decltype(hb_blob_create)>::argument<1>::type;
24    if (!SkTFitsIn<blob_size_t>(data->size())) {
25        return nullptr;
26    }
27    const char* blobData = static_cast<const char*>(data->data());
28    blob_size_t blobSize = SkTo<blob_size_t>(data->size());
29    return HBBlob(hb_blob_create(blobData, blobSize,
30                                 HB_MEMORY_MODE_READONLY,
31                                 data.release(), [](void* p){ ((SkData*)p)->unref(); }));
32}
33
34static sk_sp<SkData> to_data(HBBlob blob) {
35    if (!blob) {
36        return nullptr;
37    }
38    unsigned int length;
39    const char* data = hb_blob_get_data(blob.get(), &length);
40    if (!data || !length) {
41        return nullptr;
42    }
43    return SkData::MakeWithProc(data, SkToSizeT(length),
44                                [](const void*, void* ctx) { hb_blob_destroy((hb_blob_t*)ctx); },
45                                blob.release());
46}
47
48template<typename...> using void_t = void;
49template<typename T, typename = void>
50struct SkPDFHarfBuzzSubset {
51    // This is the HarfBuzz 3.0 interface.
52    // hb_subset_flags_t does not exist in 2.0. It isn't dependent on T, so inline the value of
53    // HB_SUBSET_FLAGS_RETAIN_GIDS until 2.0 is no longer supported.
54    static HBFace Make(T input, hb_face_t* face) {
55        // TODO: When possible, check if a font is 'tricky' with FT_IS_TRICKY.
56        // If it isn't known if a font is 'tricky', retain the hints.
57        hb_subset_input_set_flags(input, 2/*HB_SUBSET_FLAGS_RETAIN_GIDS*/);
58        return HBFace(hb_subset_or_fail(face, input));
59    }
60};
61template<typename T>
62struct SkPDFHarfBuzzSubset<T, void_t<
63    decltype(hb_subset_input_set_retain_gids(std::declval<T>(), std::declval<bool>())),
64    decltype(hb_subset_input_set_drop_hints(std::declval<T>(), std::declval<bool>())),
65    decltype(hb_subset(std::declval<hb_face_t*>(), std::declval<T>()))
66    >>
67{
68    // This is the HarfBuzz 2.0 (non-public) interface, used if it exists.
69    // This code should be removed as soon as all users are migrated to the newer API.
70    static HBFace Make(T input, hb_face_t* face) {
71        hb_subset_input_set_retain_gids(input, true);
72        // TODO: When possible, check if a font is 'tricky' with FT_IS_TRICKY.
73        // If it isn't known if a font is 'tricky', retain the hints.
74        hb_subset_input_set_drop_hints(input, false);
75        return HBFace(hb_subset(face, input));
76    }
77};
78
79static sk_sp<SkData> subset_harfbuzz(sk_sp<SkData> fontData,
80                                     const SkPDFGlyphUse& glyphUsage,
81                                     int ttcIndex) {
82    if (!fontData) {
83        return nullptr;
84    }
85    HBFace face(hb_face_create(to_blob(std::move(fontData)).get(), ttcIndex));
86    SkASSERT(face);
87
88    HBSubsetInput input(hb_subset_input_create_or_fail());
89    SkASSERT(input);
90    if (!face || !input) {
91        return nullptr;
92    }
93    hb_set_t* glyphs = hb_subset_input_glyph_set(input.get());
94    glyphUsage.getSetValues([&glyphs](unsigned gid) { hb_set_add(glyphs, gid);});
95
96    HBFace subset = SkPDFHarfBuzzSubset<hb_subset_input_t*>::Make(input.get(), face.get());
97    if (!subset) {
98        return nullptr;
99    }
100    HBBlob result(hb_face_reference_blob(subset.get()));
101    return to_data(std::move(result));
102}
103
104#endif  // defined(SK_PDF_USE_HARFBUZZ_SUBSET)
105
106////////////////////////////////////////////////////////////////////////////////
107
108#if defined(SK_PDF_USE_SFNTLY)
109
110#include "sample/chromium/font_subsetter.h"
111#include <vector>
112
113static sk_sp<SkData> subset_sfntly(sk_sp<SkData> fontData,
114                                   const SkPDFGlyphUse& glyphUsage,
115                                   const char* fontName,
116                                   int ttcIndex) {
117#if defined(SK_USING_THIRD_PARTY_ICU)
118    if (!SkLoadICU()) {
119        return nullptr;
120    }
121#endif
122    // Generate glyph id array in format needed by sfntly.
123    // TODO(halcanary): sfntly should take a more compact format.
124    std::vector<unsigned> subset;
125    glyphUsage.getSetValues([&subset](unsigned v) { subset.push_back(v); });
126
127    unsigned char* subsetFont{nullptr};
128#if defined(SK_BUILD_FOR_GOOGLE3)
129    // TODO(halcanary): update SK_BUILD_FOR_GOOGLE3 to newest version of Sfntly.
130    (void)ttcIndex;
131    int subsetFontSize = SfntlyWrapper::SubsetFont(fontName,
132                                                   fontData->bytes(),
133                                                   fontData->size(),
134                                                   subset.data(),
135                                                   subset.size(),
136                                                   &subsetFont);
137#else  // defined(SK_BUILD_FOR_GOOGLE3)
138    (void)fontName;
139    int subsetFontSize = SfntlyWrapper::SubsetFont(ttcIndex,
140                                                   fontData->bytes(),
141                                                   fontData->size(),
142                                                   subset.data(),
143                                                   subset.size(),
144                                                   &subsetFont);
145#endif  // defined(SK_BUILD_FOR_GOOGLE3)
146    SkASSERT(subsetFontSize > 0 || subsetFont == nullptr);
147    if (subsetFontSize < 1 || subsetFont == nullptr) {
148        return nullptr;
149    }
150    return SkData::MakeWithProc(subsetFont, subsetFontSize,
151                                [](const void* p, void*) { delete[] (unsigned char*)p; },
152                                nullptr);
153}
154
155#endif  // defined(SK_PDF_USE_SFNTLY)
156
157////////////////////////////////////////////////////////////////////////////////
158
159#if defined(SK_PDF_USE_SFNTLY) && defined(SK_PDF_USE_HARFBUZZ_SUBSET)
160
161sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
162                              const SkPDFGlyphUse& glyphUsage,
163                              SkPDF::Metadata::Subsetter subsetter,
164                              const char* fontName,
165                              int ttcIndex) {
166    switch (subsetter) {
167        case SkPDF::Metadata::kHarfbuzz_Subsetter:
168            return subset_harfbuzz(std::move(fontData), glyphUsage, ttcIndex);
169        case SkPDF::Metadata::kSfntly_Subsetter:
170            return subset_sfntly(std::move(fontData), glyphUsage, fontName, ttcIndex);
171    }
172    return nullptr;
173}
174
175#elif defined(SK_PDF_USE_SFNTLY)
176
177sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
178                              const SkPDFGlyphUse& glyphUsage,
179                              SkPDF::Metadata::Subsetter,
180                              const char* fontName,
181                              int ttcIndex) {
182    return subset_sfntly(std::move(fontData), glyphUsage, fontName, ttcIndex);
183}
184
185#elif defined(SK_PDF_USE_HARFBUZZ_SUBSET)
186
187sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData> fontData,
188                              const SkPDFGlyphUse& glyphUsage,
189                              SkPDF::Metadata::Subsetter,
190                              const char*,
191                              int ttcIndex) {
192    return subset_harfbuzz(std::move(fontData), glyphUsage, ttcIndex);
193}
194
195#else
196
197sk_sp<SkData> SkPDFSubsetFont(sk_sp<SkData>, const SkPDFGlyphUse&, SkPDF::Metadata::Subsetter,
198                              const char*, int) {
199    return nullptr;
200}
201#endif  // defined(SK_PDF_USE_SFNTLY)
202