Skip to content

Commit a663522

Browse files
committed
When brotli quality is set to 0 estimate compressed size instead of doing the compression.
1 parent 027e3e3 commit a663522

File tree

10 files changed

+210
-24
lines changed

10 files changed

+210
-24
lines changed

common/font_helper.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
#include "common/hb_set_unique_ptr.h"
1111
#include "common/indexed_data_reader.h"
1212
#include "common/int_set.h"
13+
#include "common/try.h"
1314
#include "hb-ot.h"
1415
#include "hb-subset.h"
1516
#include "hb.h"
16-
#include "common/try.h"
1717

1818
using absl::btree_set;
1919
using absl::flat_hash_map;
20+
using absl::flat_hash_set;
2021
using absl::Status;
2122
using absl::StatusOr;
2223
using absl::StrCat;
@@ -48,7 +49,6 @@ bool FontHelper::HasWideGvar(const hb_face_t* face) {
4849
return (((uint8_t)gvar.str()[gvar_flags_offset]) & 0x01);
4950
}
5051

51-
5252
absl::StatusOr<string_view> FontHelper::GlyfData(const hb_face_t* face,
5353
uint32_t gid) {
5454
auto loca = Loca(face);
@@ -126,8 +126,9 @@ FontData FontHelper::Cff2Data(hb_face_t* face, uint32_t gid) {
126126
return data;
127127
}
128128

129-
StatusOr<uint32_t> FontHelper::TotalGlyphData(hb_face_t* face, const GlyphSet& gids) {
130-
auto tags = FontHelper::GetTags(face);
129+
StatusOr<uint32_t> FontHelper::TotalGlyphData(hb_face_t* face,
130+
const GlyphSet& gids) {
131+
flat_hash_set<hb_tag_t> tags = FontHelper::GetTags(face);
131132

132133
uint32_t total = 0;
133134
for (uint32_t gid : gids) {

common/font_helper.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ class FontHelper {
147147

148148
// Counts up the total size of all glyph data (gvar, glyf, cff, cff2)
149149
// for the provided set of gids.
150-
static absl::StatusOr<uint32_t> TotalGlyphData(hb_face_t* face, const GlyphSet& gids);
150+
static absl::StatusOr<uint32_t> TotalGlyphData(hb_face_t* face,
151+
const GlyphSet& gids);
151152

152153
static absl::Status Cff2GetCharstrings(hb_face_t* face,
153154
FontData& non_charstrings,

common/font_helper_test.cc

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -619,42 +619,42 @@ TEST_F(FontHelperTest, GlyfData_ShortOverflowSynthetic) {
619619
}
620620

621621
TEST_F(FontHelperTest, TotalGlyphData_GlyfGvar) {
622-
auto size = FontHelper::TotalGlyphData(roboto_vf.get(), GlyphSet {78, 83, 95});
622+
auto size = FontHelper::TotalGlyphData(roboto_vf.get(), GlyphSet{78, 83, 95});
623623
ASSERT_TRUE(size.ok()) << size.status();
624624

625-
uint32_t expected =
626-
FontHelper::GlyfData(roboto_vf.get(), 78)->size() +
627-
FontHelper::GlyfData(roboto_vf.get(), 83)->size() +
628-
FontHelper::GlyfData(roboto_vf.get(), 95)->size() +
629-
FontHelper::GvarData(roboto_vf.get(), 78)->size() +
630-
FontHelper::GvarData(roboto_vf.get(), 83)->size() +
631-
FontHelper::GvarData(roboto_vf.get(), 95)->size();
625+
uint32_t expected = FontHelper::GlyfData(roboto_vf.get(), 78)->size() +
626+
FontHelper::GlyfData(roboto_vf.get(), 83)->size() +
627+
FontHelper::GlyfData(roboto_vf.get(), 95)->size() +
628+
FontHelper::GvarData(roboto_vf.get(), 78)->size() +
629+
FontHelper::GvarData(roboto_vf.get(), 83)->size() +
630+
FontHelper::GvarData(roboto_vf.get(), 95)->size();
632631

633632
ASSERT_GT(*size, 0);
634633
ASSERT_EQ(*size, expected);
635634
}
636635

637636
TEST_F(FontHelperTest, TotalGlyphData_Cff) {
638-
auto size = FontHelper::TotalGlyphData(noto_sans_jp_otf.get(), GlyphSet {78, 83, 95});
637+
auto size =
638+
FontHelper::TotalGlyphData(noto_sans_jp_otf.get(), GlyphSet{78, 83, 95});
639639
ASSERT_TRUE(size.ok()) << size.status();
640640

641-
uint32_t expected =
642-
FontHelper::CffData(noto_sans_jp_otf.get(), 78).size() +
643-
FontHelper::CffData(noto_sans_jp_otf.get(), 83).size() +
644-
FontHelper::CffData(noto_sans_jp_otf.get(), 95).size();
641+
uint32_t expected = FontHelper::CffData(noto_sans_jp_otf.get(), 78).size() +
642+
FontHelper::CffData(noto_sans_jp_otf.get(), 83).size() +
643+
FontHelper::CffData(noto_sans_jp_otf.get(), 95).size();
645644

646645
ASSERT_GT(*size, 0);
647646
ASSERT_EQ(*size, expected);
648647
}
649648

650649
TEST_F(FontHelperTest, TotalGlyphData_Cff2) {
651-
auto size = FontHelper::TotalGlyphData(noto_sans_vf_jp_otf.get(), GlyphSet {34, 35, 46});
650+
auto size = FontHelper::TotalGlyphData(noto_sans_vf_jp_otf.get(),
651+
GlyphSet{34, 35, 46});
652652
ASSERT_TRUE(size.ok()) << size.status();
653653

654654
uint32_t expected =
655-
FontHelper::Cff2Data(noto_sans_vf_jp_otf.get(), 34).size() +
656-
FontHelper::Cff2Data(noto_sans_vf_jp_otf.get(), 35).size() +
657-
FontHelper::Cff2Data(noto_sans_vf_jp_otf.get(), 46).size();
655+
FontHelper::Cff2Data(noto_sans_vf_jp_otf.get(), 34).size() +
656+
FontHelper::Cff2Data(noto_sans_vf_jp_otf.get(), 35).size() +
657+
FontHelper::Cff2Data(noto_sans_vf_jp_otf.get(), 46).size();
658658

659659
ASSERT_GT(*size, 0);
660660
ASSERT_EQ(*size, expected);

ift/encoder/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ cc_library(
6363
"candidate_merge.cc",
6464
"candidate_merge.h",
6565
"patch_size_cache.h",
66+
"estimated_patch_size_cache.h",
67+
"estimated_patch_size_cache.cc",
6668
"merge_strategy.h",
6769
"merge_strategy.cc",
6870
"merger.cc",
@@ -253,4 +255,17 @@ cc_test(
253255
"@abseil-cpp//absl/types:span",
254256
"@googletest//:gtest_main",
255257
],
258+
)
259+
260+
cc_test(
261+
name = "estimated_patch_size_cache_test",
262+
srcs = ["estimated_patch_size_cache_test.cc"],
263+
data = [
264+
"//common:testdata",
265+
],
266+
deps = [
267+
":segmentation_context",
268+
"//common",
269+
"@googletest//:gtest_main",
270+
],
256271
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "ift/encoder/estimated_patch_size_cache.h"
2+
3+
#include "common/int_set.h"
4+
#include "common/try.h"
5+
6+
using absl::StatusOr;
7+
using absl::flat_hash_set;
8+
using common::GlyphSet;
9+
10+
namespace ift::encoder {
11+
12+
StatusOr<uint32_t> EstimatedPatchSizeCache::GetPatchSize(const GlyphSet& gids) {
13+
auto it = cache_.find(gids);
14+
if (it != cache_.end()) {
15+
return it->second;
16+
}
17+
18+
flat_hash_set<hb_tag_t> tags = common::FontHelper::GetTags(face_.get());
19+
uint32_t table_count = (tags.contains(common::FontHelper::kCFF) ? 1 : 0) +
20+
(tags.contains(common::FontHelper::kCFF2) ? 1 : 0) +
21+
(tags.contains(common::FontHelper::kGlyf) ? 1 : 0) +
22+
(tags.contains(common::FontHelper::kGvar) ? 1 : 0);
23+
24+
uint32_t gid_width = (gids.size() > 255) ? 3 : 2;
25+
26+
uint32_t header_size = 1 + 7 * 4;
27+
uint32_t uncompressed_stream_size =
28+
5 + gids.size() * gid_width + // glyph ids
29+
4 * table_count + // table tags
30+
4 * (gids.size() * table_count + 1); // data offsets
31+
32+
uncompressed_stream_size +=
33+
TRY(common::FontHelper::TotalGlyphData(face_.get(), gids));
34+
35+
uint32_t size = header_size + (uint32_t)((double)uncompressed_stream_size *
36+
compression_ratio_);
37+
cache_[gids] = size;
38+
return size;
39+
}
40+
41+
StatusOr<double> EstimatedPatchSizeCache::EstimateCompressionRatio(
42+
hb_face_t* original_face) {
43+
PatchSizeCacheImpl patch_sizes(original_face, 11);
44+
45+
uint32_t glyph_count = hb_face_get_glyph_count(original_face);
46+
if (glyph_count == 0) {
47+
return 0.0;
48+
}
49+
50+
common::GlyphSet gids;
51+
gids.insert_range(0, glyph_count - 1);
52+
53+
double uncompressed_size =
54+
TRY(common::FontHelper::TotalGlyphData(original_face, gids));
55+
double compressed_size = TRY(patch_sizes.GetPatchSize(gids));
56+
57+
return compressed_size / uncompressed_size;
58+
}
59+
60+
} // namespace ift::encoder
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#ifndef IFT_ENCODER_ESTIMATED_PATCH_SIZE_CACHE_H_
2+
#define IFT_ENCODER_ESTIMATED_PATCH_SIZE_CACHE_H_
3+
4+
#include <memory>
5+
#include "absl/status/statusor.h"
6+
#include "common/font_data.h"
7+
#include "common/int_set.h"
8+
#include "ift/encoder/patch_size_cache.h"
9+
10+
namespace ift::encoder {
11+
12+
// Estimates the size of a glyph keyed patch using a fixed compression ratio.
13+
// Does not actually run the brotli compression.
14+
//
15+
// The fixed compression ratio is determined by looking at the compression ratio
16+
// of glyph data in the provided original_face.
17+
class EstimatedPatchSizeCache : public PatchSizeCache {
18+
public:
19+
static absl::StatusOr<std::unique_ptr<PatchSizeCache>> New(hb_face_t* face) {
20+
double compression_ratio = TRY(EstimateCompressionRatio(face));
21+
return std::unique_ptr<PatchSizeCache>(new EstimatedPatchSizeCache(face, compression_ratio));
22+
}
23+
24+
absl::StatusOr<uint32_t> GetPatchSize(const common::GlyphSet& gids) override;
25+
26+
double CompressionRatio() const {
27+
return compression_ratio_;
28+
}
29+
30+
private:
31+
explicit EstimatedPatchSizeCache(hb_face_t* original_face,
32+
double compression_ratio)
33+
: face_(common::make_hb_face(hb_face_reference(original_face))),
34+
compression_ratio_(compression_ratio),
35+
cache_() {}
36+
37+
static absl::StatusOr<double> EstimateCompressionRatio(
38+
hb_face_t* original_face);
39+
40+
common::hb_face_unique_ptr face_;
41+
double compression_ratio_;
42+
absl::flat_hash_map<common::GlyphSet, uint32_t> cache_;
43+
};
44+
45+
} // namespace ift::encoder
46+
47+
#endif // IFT_ENCODER_ESTIMATED_PATCH_SIZE_CACHE_H_
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "ift/encoder/estimated_patch_size_cache.h"
2+
3+
#include "common/font_helper.h"
4+
#include "common/int_set.h"
5+
#include "gtest/gtest.h"
6+
7+
#include "common/font_data.h"
8+
9+
using common::hb_face_unique_ptr;
10+
using common::make_hb_face;
11+
using common::hb_blob_unique_ptr;
12+
using common::make_hb_blob;
13+
using common::FontHelper;
14+
using common::GlyphSet;
15+
16+
namespace ift::encoder {
17+
18+
class EstimatedPatchSizeCacheTest : public ::testing::Test {
19+
protected:
20+
EstimatedPatchSizeCacheTest() : roboto(make_hb_face(nullptr)) {
21+
hb_blob_unique_ptr blob = make_hb_blob(
22+
hb_blob_create_from_file("common/testdata/Roboto-Regular.ttf"));
23+
roboto = make_hb_face(hb_face_create(blob.get(), 0));
24+
}
25+
26+
27+
double CompressionRatio(GlyphSet gids, double expected_compression_ratio) {
28+
uint32_t raw_outline_size =
29+
*FontHelper::TotalGlyphData(roboto.get(), gids);
30+
double fixed_size = 1 + 7 * 4; // header
31+
fixed_size += (double) (5 + gids.size() * 2 + 4 + (gids.size() + 1)*4) * expected_compression_ratio; // glyph patches header
32+
auto estimated = *EstimatedPatchSizeCache::New(roboto.get());
33+
uint32_t compressed_size = *estimated->GetPatchSize(gids);
34+
return (double) (compressed_size - fixed_size) / (double) raw_outline_size;
35+
}
36+
37+
hb_face_unique_ptr roboto;
38+
};
39+
40+
TEST_F(EstimatedPatchSizeCacheTest, PatchSize) {
41+
// There should be a consistent compression ratio between patches.
42+
ASSERT_NEAR(this->CompressionRatio(GlyphSet {44, 47, 49}, 0.457), 0.46, 0.01);
43+
ASSERT_NEAR(CompressionRatio(GlyphSet {45, 48, 50, 51, 52, 53}, 0.457), 0.46, 0.01);
44+
}
45+
46+
} // namespace ift::encoder

ift/encoder/patch_size_cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define IFT_ENCODER_PATCH_SIZE_CACHE_H_
33

44
#include <cstdint>
5+
#include <memory>
56

67
#include "absl/container/flat_hash_map.h"
78
#include "absl/status/statusor.h"

ift/encoder/segmentation_context.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "common/font_data.h"
99
#include "common/int_set.h"
1010
#include "common/try.h"
11+
#include "ift/encoder/estimated_patch_size_cache.h"
1112
#include "ift/encoder/glyph_closure_cache.h"
1213
#include "ift/encoder/glyph_condition_set.h"
1314
#include "ift/encoder/glyph_groupings.h"
@@ -43,9 +44,9 @@ class SegmentationContext {
4344
const std::vector<Segment>& segments,
4445
uint32_t brotli_quality,
4546
uint32_t init_font_brotli_quality)
46-
: patch_size_cache(new PatchSizeCacheImpl(face, brotli_quality)),
47+
: patch_size_cache(NewPatchSizeCache(face, brotli_quality)),
4748
patch_size_cache_for_init_font(
48-
new PatchSizeCacheImpl(face, init_font_brotli_quality)),
49+
NewPatchSizeCache(face, init_font_brotli_quality)),
4950
glyph_closure_cache(face),
5051
original_face(common::make_hb_face(hb_face_reference(face))),
5152
segmentation_info_(segments, initial_segment, glyph_closure_cache),
@@ -175,6 +176,16 @@ class SegmentationContext {
175176
// too small to be worthwhile.
176177
absl::StatusOr<segment_index_t> ComputeSegmentCutoff() const;
177178

179+
static std::unique_ptr<PatchSizeCache> NewPatchSizeCache(hb_face_t* face, uint32_t brotli_quality) {
180+
if (brotli_quality == 0) {
181+
auto cache = EstimatedPatchSizeCache::New(face);
182+
if (cache.ok()) {
183+
return std::move(*cache);
184+
}
185+
}
186+
return std::unique_ptr<PatchSizeCache>(new PatchSizeCacheImpl(face, brotli_quality));
187+
}
188+
178189
public:
179190
// Caches and logging
180191
std::unique_ptr<PatchSizeCache> patch_size_cache;

util/segmenter_config.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ message SegmenterConfig {
2929
// When generating compressed patches (to evaluate their size) this is the brotli quality
3030
// level used. Segmentation is typically bottle necked on brotli compression so higher values
3131
// increase segmentation times, but yield more accurate results.
32+
//
33+
// If quality is set to '0' this disables brotli compression and instead estimates the
34+
// affect of compression using a fixed compression ratio calculated based on how well
35+
// the glyph data in the input font compresses.
3236
uint32 brotli_quality = 5 [default = 8];
3337

3438
// During processing to determine which segments to move into the initial font this is

0 commit comments

Comments
 (0)