1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/common/subset_font.h"
// clang-format off
#include <hb.h>
#include <hb-subset.h>
#include <hb-cplusplus.hh>
// clang-format on
#include <memory>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/numerics/safe_conversions.h"
#include "components/crash/core/common/crash_key.h"
#include "skia/ext/font_utils.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace paint_preview {
namespace {
// Converts and SkStream to an SkData object without copy if possible or
// falls back to a copy.
sk_sp<SkData> StreamToData(std::unique_ptr<SkStreamAsset> stream) {
DCHECK(stream);
bool rewind = stream->rewind();
DCHECK(rewind);
DCHECK(stream->hasLength());
size_t size = stream->getLength();
// TODO: Replace with const SkData* = SkStreamAsset::getData() when Skia
// adds such a method.
if (const void* base = stream->getMemoryBase()) {
SkData::ReleaseProc proc = [](const void*, void* ctx) {
delete static_cast<SkStreamAsset*>(ctx);
};
return SkData::MakeWithProc(base, size, proc, stream.release());
}
return SkData::MakeFromStream(stream.get(), size);
}
// Converts SkData to a hb_blob_t.
hb::unique_ptr<hb_blob_t> MakeBlob(sk_sp<SkData> data) {
if (!data ||
!base::IsValueInRangeForNumericType<unsigned int, size_t>(data->size()))
return hb::unique_ptr<hb_blob_t>(nullptr);
return hb::unique_ptr<hb_blob_t>(
hb_blob_create(static_cast<const char*>(data->data()),
static_cast<unsigned int>(data->size()),
HB_MEMORY_MODE_READONLY, nullptr, nullptr));
}
// Adds |glyph_id| to the set of glyphs to be retained.
void AddGlyphs(hb_set_t* glyph_id_set, uint16_t glyph_id) {
hb_set_add(glyph_id_set, glyph_id);
}
} // namespace
// Implementation based on SkPDFSubsetFont() using harfbuzz.
sk_sp<SkData> SubsetFont(SkTypeface* typeface, const GlyphUsage& usage) {
static crash_reporter::CrashKeyString<128> crash_key(
"PaintPreview-SubsetFont");
SkString family_name;
typeface->getFamilyName(&family_name);
crash_reporter::ScopedCrashKeyString auto_clear(&crash_key,
family_name.c_str());
int ttc_index = 0;
sk_sp<SkData> data = StreamToData(typeface->openStream(&ttc_index));
hb::unique_ptr<hb_face_t> face(
hb_face_create(MakeBlob(data).get(), ttc_index));
hb::unique_ptr<hb_subset_input_t> input(hb_subset_input_create_or_fail());
if (!face || !input) {
return nullptr;
}
hb_set_t* glyphs =
hb_subset_input_glyph_set(input.get()); // Owned by |input|.
usage.ForEach(base::BindRepeating(&AddGlyphs, base::Unretained(glyphs)));
hb_subset_input_set_flags(input.get(), HB_SUBSET_FLAGS_RETAIN_GIDS);
// Retain all variation information for OpenType variation fonts. See:
// https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
hb_set_t* skip_subset =
hb_subset_input_set(input.get(), HB_SUBSET_SETS_NO_SUBSET_TABLE_TAG);
hb_set_add(skip_subset, HB_TAG('a', 'v', 'a', 'r'));
hb_set_add(skip_subset, HB_TAG('c', 'v', 'a', 'r'));
hb_set_add(skip_subset, HB_TAG('f', 'v', 'a', 'r'));
hb_set_add(skip_subset, HB_TAG('M', 'V', 'A', 'R'));
// Normally harfbuzz would subset these variation tables, but they are needed
// for variation fonts.
hb_set_add(skip_subset, HB_TAG('g', 'v', 'a', 'r'));
hb_set_add(skip_subset, HB_TAG('H', 'V', 'A', 'R'));
hb_set_add(skip_subset, HB_TAG('V', 'V', 'A', 'R'));
// Also retain layout information which is important for non-latin characters.
hb_set_add(skip_subset, HB_TAG('G', 'D', 'E', 'F'));
hb_set_add(skip_subset, HB_TAG('G', 'S', 'U', 'B'));
hb_set_add(skip_subset, HB_TAG('G', 'P', 'O', 'S'));
hb::unique_ptr<hb_face_t> subset_face(
hb_subset_or_fail(face.get(), input.get()));
if (!subset_face) {
return nullptr;
}
// Store the correct collection index for the subsetted font.
const int final_ttc_index = hb_face_get_index(subset_face.get());
hb::unique_ptr<hb_blob_t> subset_blob(
hb_face_reference_blob(subset_face.get()));
if (!subset_blob) {
return nullptr;
}
unsigned int length = 0;
const char* subset_data = hb_blob_get_data(subset_blob.get(), &length);
if (!subset_data || !length) {
return nullptr;
}
auto sk_data = SkData::MakeWithProc(
subset_data, static_cast<size_t>(length),
[](const void*, void* ctx) { hb_blob_destroy((hb_blob_t*)ctx); },
subset_blob.release());
if (!sk_data) {
return nullptr;
}
// Ensure the data is in SkTypeface format so it will deserialize when
// embedded in an SkPicture. This is *not* a validation/sanitation and the
// inner workings may vary by platform.
sk_sp<SkFontMgr> mgr = skia::DefaultFontMgr();
sk_sp<SkTypeface> sk_subset_typeface =
mgr->makeFromData(sk_data, final_ttc_index);
if (!sk_subset_typeface) {
return nullptr;
}
// For fonts with variations we need to force the right variant of SkTypeface
// post subset.
const int axis_count = typeface->getVariationDesignPosition({});
if (axis_count > 0) {
std::vector<SkFontArguments::VariationPosition::Coordinate> typeface_axis;
typeface_axis.resize(axis_count);
if (typeface->getVariationDesignPosition(typeface_axis) > 0) {
SkFontArguments::VariationPosition variation;
variation.coordinates = typeface_axis.data();
variation.coordinateCount = typeface_axis.size();
SkFontArguments args;
args.setVariationDesignPosition(variation);
args.setCollectionIndex(final_ttc_index);
sk_subset_typeface = sk_subset_typeface->makeClone(args);
}
}
// TODO(crbug.com/40197502): Even after forcing the right variation,
// `sk_subset_typeface` may have the wrong SkFontStyle as there is no way to
// manipulate the style while loading the font from data.
return sk_subset_typeface->serialize(
SkTypeface::SerializeBehavior::kDoIncludeData);
}
} // namespace paint_preview
|