0

Add FontIndexer tool to enumerate locally installed fonts

This will aid in constructing a catalog of known font digests for the
identifiability study.

Change-Id: Ic0d4b603513aed6c5f1fec7d3606a371119e2707
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2399446
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@google.com>
Reviewed-by: Asanka Herath <asanka@chromium.org>
Commit-Queue: Alex Turner <alexmt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824015}
This commit is contained in:
Alex Turner
2020-11-04 16:51:26 +00:00
committed by Commit Bot
parent 046f5c7965
commit 21735e20a8
9 changed files with 505 additions and 1 deletions
BUILD.gn
third_party/blink/renderer/platform
tools/privacy_budget

@ -154,6 +154,7 @@ group("gn_all") {
"//third_party/pdfium/samples:pdfium_test",
"//tools/perf/clear_system_cache",
"//tools/polymer:polymer_tools_python_unittests",
"//tools/privacy_budget:privacy_budget_tools",
"//ui/accessibility:accessibility_perftests",
"//ui/accessibility:accessibility_unittests",
"//ui/accessibility/extensions:extension_tests",

@ -253,6 +253,7 @@ component("platform") {
"//services/device/public/mojom:mojom_blink",
"//services/media_session/public/mojom:mojom_blink",
"//third_party/blink/*",
"//tools/privacy_budget/font_indexer:*",
"//url/mojom:url_mojom_gurl_blink",
"//url/mojom:url_mojom_origin_blink",
]
@ -1773,7 +1774,10 @@ component("platform") {
}
static_library("test_support") {
visibility += [ "//third_party/blink/*" ]
visibility += [
"//third_party/blink/*",
"//tools/privacy_budget/font_indexer:*",
]
testonly = true
sources = [

@ -0,0 +1,8 @@
# Copyright 2020 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.
group("privacy_budget_tools") {
testonly = true
deps = [ "font_indexer:font_indexer" ]
}

@ -0,0 +1,6 @@
include_rules = [
"+base",
"+content/public",
"+mojo/core/embedder",
"+third_party/blink",
]

@ -0,0 +1,4 @@
file://third_party/blink/public/common/privacy_budget/OWNERS
# TEAM: privacy-sandbox-dev@chromium.org
# COMPONENT: Privacy>Fingerprinting

@ -0,0 +1,28 @@
# Copyright 2020 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.
static_library("tools_lib") {
sources = [
"font_indexer.cc",
"font_indexer.h",
]
deps = [
"//base:base",
"//content/public/browser:browser",
"//third_party/blink/renderer/platform:platform",
]
}
executable("font_indexer") {
testonly = true
sources = [ "font_indexer_main.cc" ]
deps = [
":tools_lib",
"//base:base",
"//base/test:test_support",
"//content/public/browser:browser",
"//mojo/core/embedder",
"//third_party/blink/renderer/platform:test_support",
]
}

@ -0,0 +1,264 @@
// Copyright 2020 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 "tools/privacy_budget/font_indexer/font_indexer.h"
#include <iostream>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "content/public/browser/font_list_async.h"
#include "third_party/blink/renderer/platform/fonts/font_global_context.h"
#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
namespace privacy_budget {
const std::pair<blink::FontSelectionValue, std::string> kFontWeights[] = {
{blink::FontSelectionValue(400), ""},
{blink::FontSelectionValue(700), "bold"},
{blink::FontSelectionValue(100), "100w"},
{blink::FontSelectionValue(200), "200w"},
{blink::FontSelectionValue(300), "300w"},
{blink::FontSelectionValue(500), "500w"},
{blink::FontSelectionValue(600), "600w"},
{blink::FontSelectionValue(800), "800w"},
{blink::FontSelectionValue(900), "900w"},
{blink::FontSelectionValue(950), "950w"},
{blink::FontSelectionValue(1), "1w"}, // min
{blink::FontSelectionValue(1000), "1000w"}, // max
};
const std::pair<blink::FontSelectionValue, std::string> kFontWidths[] = {
{blink::FontSelectionValue(100.0f), ""},
{blink::FontSelectionValue(75), "condensed"},
{blink::FontSelectionValue(125), "expanded"},
{blink::FontSelectionValue(62.5f), "extra-condensed"},
{blink::FontSelectionValue(87.5f), "semi-condensed"},
{blink::FontSelectionValue(112.5f), "semi-expanded"},
{blink::FontSelectionValue(150), "extra-expanded"},
{blink::FontSelectionValue(50), "ultra-condensed"}, // min
{blink::FontSelectionValue(200), "ultra-expanded"}, // max
};
const std::pair<blink::FontSelectionValue, std::string> kFontSlopes[] = {
{blink::FontSelectionValue(), ""},
{blink::FontSelectionValue(20), "italic"},
{blink::FontSelectionValue(14), "oblique"},
{blink::FontSelectionValue(1), "1deg"}, // Chosen to search 1 upwards
{blink::FontSelectionValue(-1), "-1deg"},
{blink::FontSelectionValue(21), "21deg"}, // Chosen to search 21 upwards
{blink::FontSelectionValue(-21), "-21deg"},
{blink::FontSelectionValue(90), "90deg"}, // max
{blink::FontSelectionValue(-90), "-90deg"}, // min
};
const std::pair<blink::FontSelectionValue, std::string>
kAdditionalFontSlopes[] = {
{blink::FontSelectionValue(5), "5deg"},
{blink::FontSelectionValue(-5), "-5deg"},
{blink::FontSelectionValue(10), "10deg"},
{blink::FontSelectionValue(-10), "-10deg"},
{blink::FontSelectionValue(19), "19deg"},
{blink::FontSelectionValue(-19), "-19deg"},
{blink::FontSelectionValue(30), "30deg"},
{blink::FontSelectionValue(-30), "-30deg"},
{blink::FontSelectionValue(35), "35deg"},
{blink::FontSelectionValue(-35), "-35deg"},
{blink::FontSelectionValue(40), "40deg"},
{blink::FontSelectionValue(-40), "-40deg"},
{blink::FontSelectionValue(45), "45deg"},
{blink::FontSelectionValue(-45), "-45deg"},
{blink::FontSelectionValue(50), "50deg"},
{blink::FontSelectionValue(-50), "-50deg"},
{blink::FontSelectionValue(60), "60deg"},
{blink::FontSelectionValue(-60), "-60deg"},
{blink::FontSelectionValue(70), "70deg"},
{blink::FontSelectionValue(-70), "-70deg"},
{blink::FontSelectionValue(80), "80deg"},
{blink::FontSelectionValue(-80), "-80deg"},
};
const char kOutputHeader[] =
"Family name\tPostScript name\tweight\twidth\tslope\tdigest "
"(int64_t)\tdigest (uint64_t)";
const char kOutputSeparator[] = "\t";
FontIndexer::FontIndexer() : font_cache_(blink::FontCache::GetFontCache()) {}
FontIndexer::~FontIndexer() = default;
void FontIndexer::PrintAllFonts() {
// Use of base::Unretained is safe as we wait synchronously for the callback.
content::GetFontListAsync(
base::BindOnce(&FontIndexer::FontListHasLoaded, base::Unretained(this)));
WaitForFontListToLoad();
}
void FontIndexer::FontListHasLoaded(std::unique_ptr<base::ListValue> list) {
std::cout << kOutputHeader << std::endl;
for (size_t i = 0; i < list->GetSize(); i++) {
base::ListValue* font;
bool has_font = list->GetList(i, &font);
DCHECK(has_font);
std::string non_localized_name;
bool has_value = font->GetString(0, &non_localized_name);
DCHECK(has_value);
PrintAllFontsWithName(non_localized_name.c_str());
}
has_font_list_loaded_ = true;
if (quit_closure_)
std::move(quit_closure_).Run();
}
bool FontIndexer::DoesFontHaveDigest(WTF::AtomicString name,
blink::FontDescription font_description,
int64_t digest) {
scoped_refptr<blink::SimpleFontData> font_data =
font_cache_->GetFontData(font_description, name);
DCHECK(font_data);
return blink::FontGlobalContext::Get()
->GetOrComputeTypefaceDigest(font_data->PlatformData())
.ToUkmMetricValue() == digest;
}
bool FontIndexer::DoFontsWithNameHaveVaryingWeights(
WTF::AtomicString name,
int64_t default_font_digest) {
blink::FontDescription font_description;
font_description.SetWeight(blink::FontSelectionValue(900)); // max for mac
if (!DoesFontHaveDigest(name, font_description, default_font_digest))
return true;
font_description.SetWeight(blink::FontSelectionValue(100)); // min for mac
return (!DoesFontHaveDigest(name, font_description, default_font_digest));
}
bool FontIndexer::DoFontsWithNameHaveVaryingWidths(
WTF::AtomicString name,
int64_t default_font_digest) {
blink::FontDescription font_description;
font_description.SetStretch(blink::FontSelectionValue(50)); // min
if (!DoesFontHaveDigest(name, font_description, default_font_digest))
return true;
font_description.SetStretch(blink::FontSelectionValue(200)); // max
return (!DoesFontHaveDigest(name, font_description, default_font_digest));
}
bool FontIndexer::DoFontsWithNameHaveVaryingSlopes(
WTF::AtomicString name,
int64_t default_font_digest) {
blink::FontDescription font_description;
font_description.SetStyle(blink::FontSelectionValue(90)); // max
if (!DoesFontHaveDigest(name, font_description, default_font_digest))
return true;
font_description.SetStyle(blink::FontSelectionValue(-90)); // min
return (!DoesFontHaveDigest(name, font_description, default_font_digest));
}
void FontIndexer::PrintAllFontsWithName(WTF::AtomicString name) {
WTF::HashSet<int64_t> set_of_digests;
// First, we load the font with default selection settings to verify any font
// exists and for later comparison.
int64_t default_font_digest;
{
scoped_refptr<blink::SimpleFontData> font_data =
font_cache_->GetFontData(blink::FontDescription(), name);
default_font_digest =
font_data ? blink::FontGlobalContext::Get()
->GetOrComputeTypefaceDigest(font_data->PlatformData())
.ToUkmMetricValue()
: 0;
}
if (!default_font_digest) {
LOG(ERROR) << "No default font loaded for " << name;
return;
}
bool should_vary_weights = true;
bool should_vary_widths = true;
bool should_vary_slopes = true;
if (smart_skipping_) {
// With smart skipping on, we only test different values along an axis if we
// think the font varies along that axis.
should_vary_weights =
DoFontsWithNameHaveVaryingWeights(name, default_font_digest);
should_vary_widths =
DoFontsWithNameHaveVaryingWidths(name, default_font_digest);
should_vary_slopes =
DoFontsWithNameHaveVaryingSlopes(name, default_font_digest);
}
std::vector<std::pair<blink::FontSelectionValue, std::string>> weights;
std::vector<std::pair<blink::FontSelectionValue, std::string>> widths;
std::vector<std::pair<blink::FontSelectionValue, std::string>> slopes;
if (should_vary_weights) {
weights.insert(weights.begin(), std::begin(kFontWeights),
std::end(kFontWeights));
} else {
weights.push_back(kFontWeights[0]);
}
if (should_vary_widths) {
widths.insert(widths.begin(), std::begin(kFontWidths),
std::end(kFontWidths));
} else {
widths.push_back(kFontWidths[0]);
}
if (should_vary_slopes) {
slopes.insert(slopes.begin(), std::begin(kFontSlopes),
std::end(kFontSlopes));
if (more_slope_checks_) {
slopes.insert(slopes.end(), std::begin(kAdditionalFontSlopes),
std::end(kAdditionalFontSlopes));
}
} else {
slopes.push_back(kFontSlopes[0]);
}
blink::FontDescription font_description;
for (auto weight_pair : weights) {
font_description.SetWeight(weight_pair.first);
for (auto width_pair : widths) {
font_description.SetStretch(width_pair.first);
for (auto slope_pair : slopes) {
font_description.SetStyle(slope_pair.first);
if (scoped_refptr<blink::SimpleFontData> font_data =
font_cache_->GetFontData(font_description, name)) {
int64_t digest =
blink::FontGlobalContext::Get()
->GetOrComputeTypefaceDigest(font_data->PlatformData())
.ToUkmMetricValue();
if (set_of_digests.insert(digest).is_new_entry) {
std::cout << name.Ascii() << kOutputSeparator
<< font_data->PlatformData().GetPostScriptName().Ascii()
<< kOutputSeparator << weight_pair.second
<< kOutputSeparator << width_pair.second
<< kOutputSeparator << slope_pair.second
<< kOutputSeparator << digest << kOutputSeparator
<< static_cast<uint64_t>(digest) << std::endl;
}
}
}
}
}
}
void FontIndexer::WaitForFontListToLoad() {
if (has_font_list_loaded_)
return;
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
} // namespace privacy_budget

@ -0,0 +1,92 @@
// Copyright 2020 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 TOOLS_PRIVACY_BUDGET_FONT_INDEXER_FONT_INDEXER_H_
#define TOOLS_PRIVACY_BUDGET_FONT_INDEXER_FONT_INDEXER_H_
#include <string>
#include <utility>
#include "base/callback_forward.h"
#include "base/values.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/font_description.h"
#include "third_party/blink/renderer/platform/fonts/font_selection_types.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace privacy_budget {
// Note that in the following constants, we prioritize the more common values as
// we later associate each unique digest with the first set of settings for
// which that digest is found.
extern const std::pair<blink::FontSelectionValue, std::string> kFontWeights[];
extern const std::pair<blink::FontSelectionValue, std::string> kFontWidths[];
// Not as thorough as above, given its rarity and to reduce the speed impact.
extern const std::pair<blink::FontSelectionValue, std::string> kFontSlopes[];
// Following only used if |more_slope_checks_|
extern const std::pair<blink::FontSelectionValue, std::string>
kAdditionalFontSlopes[];
extern const char kOutputHeader[];
extern const char kOutputSeparator[];
// FontIndexer allows for enumerating all locally installed fonts and computing
// identifiability digests of each typeface.
class FontIndexer {
public:
FontIndexer();
~FontIndexer();
// The main function that enumerates all fonts and prints a tab-separated file
// containing the fonts' details to stdout.
void PrintAllFonts();
// By default, this tool attempts to determine whether the fonts vary along
// each axis (i.e. width, weight and slope), skipping checks along the axes
// with no variation. This call disables this optimization, slowing the tool
// substantially, but possibly being more thorough (if the determinations are
// incorrect).
void SetNoSmartSkipping() { smart_skipping_ = false; }
// By default, the tool only checks a limited number of slope values as
// substantial slope variation is rare and slow to check for. This call adds
// more granularity when slopes are varied. This will slow down the tool, but
// will give more results if a font with many slope variations is available.
void SetMoreSlopeChecks() { more_slope_checks_ = true; }
private:
void FontListHasLoaded(std::unique_ptr<base::ListValue> list);
void WaitForFontListToLoad();
// Determines whether the fonts with |name| appear to vary along the specified
// axis, by comparing font digests at extreme values to |default_font_digest|.
bool DoFontsWithNameHaveVaryingWeights(WTF::AtomicString name,
int64_t default_font_digest);
bool DoFontsWithNameHaveVaryingWidths(WTF::AtomicString name,
int64_t default_font_digest);
bool DoFontsWithNameHaveVaryingSlopes(WTF::AtomicString name,
int64_t default_font_digest);
// Determines whether a font lookup for |name| with |font_description| results
// in a typeface with |digest|.
bool DoesFontHaveDigest(WTF::AtomicString name,
blink::FontDescription font_description,
int64_t digest);
// Enumerates fonts with |name| and prints tab-separated lines with each
// font's details.
void PrintAllFontsWithName(WTF::AtomicString name);
blink::FontCache* font_cache_;
bool has_font_list_loaded_ = false;
bool smart_skipping_ = true;
bool more_slope_checks_ = false;
base::OnceClosure quit_closure_;
};
} // namespace privacy_budget
#endif // TOOLS_PRIVACY_BUDGET_FONT_INDEXER_FONT_INDEXER_H_

@ -0,0 +1,97 @@
// Copyright 2020 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 <iostream>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/task_runner_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_io_thread.h"
#include "base/test/test_suite.h"
#include "base/test/test_timeouts.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/core/embedder/scoped_ipc_support.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#include "tools/privacy_budget/font_indexer/font_indexer.h"
namespace {
const char kHelpMsg[] = R"(
font_indexer [--no-smart-skipping] [--more-slope-checks]
--no-smart-skipping stops the tool from skipping checks along axes of
variation when it appears the font does not varying along those axes. This
will slow down the tool substantially, but may be more thorough if the checks
are incorrect.
--more-slope-checks gives more granular checking of different slopes. This
will slow down the tool, but will give more results if a font with many
slope variations is available.
)";
const char kNoSmartSkippingSwitch[] = "no-smart-skipping";
const char kMoreSlopeChecksSwitch[] = "more-slope-checks";
void PrintHelp() {
printf("%s\n\n", kHelpMsg);
}
bool ShouldPrintHelpAndQuit(const base::CommandLine::StringVector& args,
const base::CommandLine::SwitchMap& switches) {
if (args.size() != 0U || switches.size() > 2) {
return true;
}
for (const auto& switch_entry : switches) {
std::string switch_name = switch_entry.first;
if (switch_name != kNoSmartSkippingSwitch &&
switch_name != kMoreSlopeChecksSwitch) {
return true;
}
}
return false;
}
} // namespace
int main(int argc, char* argv[]) {
base::CommandLine::Init(argc, argv);
base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
base::CommandLine::StringVector args = command_line.GetArgs();
if (ShouldPrintHelpAndQuit(command_line.GetArgs(),
command_line.GetSwitches())) {
PrintHelp();
return 1;
}
// Initialize a test environment to satisfy the expectations of
// content::GetFontListAsync().
blink::ScopedUnittestsEnvironmentSetup testEnvironmentSetup(argc, argv);
base::TestSuite testSuite(argc, argv);
mojo::core::Init();
base::TestIOThread testIoThread(base::TestIOThread::kAutoStart);
mojo::core::ScopedIPCSupport ipcSupport(
testIoThread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
TestTimeouts::Initialize();
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
// Set up and run tool.
privacy_budget::FontIndexer indexer;
if (command_line.HasSwitch(kNoSmartSkippingSwitch)) {
indexer.SetNoSmartSkipping();
}
if (command_line.HasSwitch(kMoreSlopeChecksSwitch)) {
indexer.SetMoreSlopeChecks();
}
indexer.PrintAllFonts();
return 0;
}