Commit fd1ade74 authored by ckitagawa's avatar ckitagawa Committed by Commit Bot

[Paint Preview] Add basic subsetting support

This CL adds basic subsetting support for paint previews. This depends
on Harfbuzz's subsetter and is based on the implementation in
SkPDFSubsetFont.

Part of landing:
https://chromium-review.googlesource.com/c/chromium/src/+/1786583

Bug: 1006242
Change-Id: I757543ab9a575dc829a6f2b7fc1bb9b89fef5c97
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1816730Reviewed-by: default avatarDominik Röttsches <drott@chromium.org>
Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#700264}
parent e782af8c
...@@ -118,7 +118,6 @@ test("components_unittests") { ...@@ -118,7 +118,6 @@ test("components_unittests") {
"//components/omnibox/browser:unit_tests", "//components/omnibox/browser:unit_tests",
"//components/open_from_clipboard:unit_tests", "//components/open_from_clipboard:unit_tests",
"//components/os_crypt:unit_tests", "//components/os_crypt:unit_tests",
"//components/paint_preview/common:unit_tests",
"//components/password_manager/core/browser:unit_tests", "//components/password_manager/core/browser:unit_tests",
"//components/password_manager/core/common:unit_tests", "//components/password_manager/core/common:unit_tests",
"//components/payments/core:unit_tests", "//components/payments/core:unit_tests",
...@@ -241,6 +240,7 @@ test("components_unittests") { ...@@ -241,6 +240,7 @@ test("components_unittests") {
"//components/page_image_annotation/core:unit_tests", "//components/page_image_annotation/core:unit_tests",
"//components/page_load_metrics/browser:unit_tests", "//components/page_load_metrics/browser:unit_tests",
"//components/page_load_metrics/renderer:unit_tests", "//components/page_load_metrics/renderer:unit_tests",
"//components/paint_preview/common:unit_tests",
"//components/password_manager/content/browser:unit_tests", "//components/password_manager/content/browser:unit_tests",
"//components/payments/content:unit_tests", "//components/payments/content:unit_tests",
"//components/payments/content/utility:unit_tests", "//components/payments/content/utility:unit_tests",
......
include_rules = [ include_rules = [
"+third_party/harfbuzz-ng/src/src",
"+third_party/skia/include/core", "+third_party/skia/include/core",
] ]
...@@ -19,6 +19,9 @@ centralized to this directory. Parts of the code are consumed in; ...@@ -19,6 +19,9 @@ centralized to this directory. Parts of the code are consumed in;
* `//content` * `//content`
* `//third_party/blink` * `//third_party/blink`
NOTE: This feature depends on working with `//content` and `//third_party/blink`
so it is incompatible with iOS.
## Directory Structure (WIP) ## Directory Structure (WIP)
* `common/` - Shared code; mojo, protos, and C++ code. * `common/` - Shared code; mojo, protos, and C++ code.
...@@ -4,43 +4,50 @@ ...@@ -4,43 +4,50 @@
import("//testing/test.gni") import("//testing/test.gni")
static_library("common") { if (!is_ios) {
sources = [ static_library("common") {
"file_stream.cc", sources = [
"file_stream.h", "file_stream.cc",
"glyph_usage.cc", "file_stream.h",
"glyph_usage.h", "glyph_usage.cc",
] "glyph_usage.h",
"subset_font.cc",
"subset_font.h",
]
deps = [ deps = [
"//base", "//base",
"//skia", "//skia",
] "//third_party:freetype_harfbuzz",
} ]
}
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"file_stream_unittest.cc", "file_stream_unittest.cc",
"glyph_usage_unittest.cc", "glyph_usage_unittest.cc",
] "subset_font_unittest.cc",
]
deps = [ deps = [
":common", ":common",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//skia", "//skia",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
] "//third_party:freetype_harfbuzz",
} ]
}
test("paint_preview_common_unit_tests") { test("paint_preview_common_unit_tests") {
deps = [ deps = [
":unit_tests", ":unit_tests",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/test:run_all_unittests", "//components/test:run_all_unittests",
] ]
}
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "third_party/harfbuzz-ng/src/src/hb-subset.h"
#include "third_party/harfbuzz-ng/src/src/hb.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace paint_preview {
namespace {
// Handles auto-deletion of harfbuzz objects.
template <typename T, T* P>
struct HbDeleter {
template <typename... Args>
auto operator()(Args&&... args) const
-> decltype(P(std::forward<Args>(args)...)) {
return P(std::forward<Args>(args)...);
}
};
using HbBlob =
std::unique_ptr<hb_blob_t,
HbDeleter<decltype(hb_blob_destroy), &hb_blob_destroy>>;
using HbFace =
std::unique_ptr<hb_face_t,
HbDeleter<decltype(hb_face_destroy), &hb_face_destroy>>;
using HbSubsetInput = std::unique_ptr<
hb_subset_input_t,
HbDeleter<decltype(hb_subset_input_destroy), &hb_subset_input_destroy>>;
// 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.
HbBlob MakeBlob(sk_sp<SkData> data) {
if (!data ||
!base::IsValueInRangeForNumericType<unsigned int, size_t>(data->size()))
return nullptr;
return HbBlob(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) {
int ttc_index = 0;
sk_sp<SkData> data = StreamToData(typeface->openStream(&ttc_index));
HbFace face(hb_face_create(MakeBlob(data).get(), ttc_index));
HbSubsetInput 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_retain_gids(input.get(), true);
hb_subset_input_set_drop_hints(input.get(), true);
HbFace subset_face(hb_subset(face.get(), input.get()));
HbBlob 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.
auto sk_subset_typeface = SkTypeface::MakeFromData(sk_data);
if (!sk_subset_typeface)
return nullptr;
return sk_subset_typeface->serialize(
SkTypeface::SerializeBehavior::kDoIncludeData);
}
} // namespace paint_preview
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PAINT_PREVIEW_COMMON_SUBSET_FONT_H_
#define COMPONENTS_PAINT_PREVIEW_COMMON_SUBSET_FONT_H_
#include "components/paint_preview/common/glyph_usage.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkRefCnt.h"
class SkTypeface;
namespace paint_preview {
// Subsets |typeface| to only contain the glyphs in |usage|. Returns nullptr on
// failure.
sk_sp<SkData> SubsetFont(SkTypeface* typeface, const GlyphUsage& usage);
} // namespace paint_preview
#endif // COMPONENTS_PAINT_PREVIEW_COMMON_SUBSET_FONT_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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"
#include "components/paint_preview/common/glyph_usage.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace paint_preview {
TEST(PaintPreviewSubsetFontTest, TestBasicSubset) {
auto typeface = SkTypeface::MakeDefault();
ASSERT_NE(typeface, nullptr);
SparseGlyphUsage sparse(typeface->countGlyphs());
sparse.Set(0);
uint16_t glyph_a = typeface->unicharToGlyph('a');
sparse.Set(glyph_a);
uint16_t glyph_t = typeface->unicharToGlyph('t');
sparse.Set(glyph_t);
auto subset_data = SubsetFont(typeface.get(), sparse);
ASSERT_NE(subset_data, nullptr);
SkMemoryStream stream(subset_data);
auto subset_typeface = SkTypeface::MakeDeserialize(&stream);
ASSERT_NE(subset_typeface, nullptr);
// Subsetting doesn't guarantee all glyphs are removed, so just check that the
// size is smaller and that the requested glyphs still exist and have the same
// glyphs ids.
auto origin_data =
typeface->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
EXPECT_LE(subset_data->size(), origin_data->size());
EXPECT_LE(subset_typeface->countTables(), typeface->countTables());
EXPECT_LE(subset_typeface->countGlyphs(), typeface->countGlyphs());
// TODO(ckitagawa): Find a reliable way to check that |glyph_{a, t}| are in
// the subset_typeface. This is non-trivial.
}
} // namespace paint_preview
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment