fontist 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/discover-fonts.yml +76 -0
- data/.github/workflows/rake.yml +103 -8
- data/TODO.audit-docs.md +164 -0
- data/TODO.improve-docs.md +114 -0
- data/TODO.upgrade-excavate.md +107 -0
- data/docs/guide/formulas.md +37 -1
- data/docs/guide/how-it-works.md +13 -0
- data/docs/guide/platforms/windows.md +67 -0
- data/lib/fontist/errors.rb +21 -0
- data/lib/fontist/font_installer.rb +2 -0
- data/lib/fontist/formula.rb +11 -0
- data/lib/fontist/import/create_formula.rb +5 -0
- data/lib/fontist/import/formula_builder.rb +9 -0
- data/lib/fontist/import/sil_importer.rb +2 -2
- data/lib/fontist/import/windows/fod_capabilities.yml +654 -0
- data/lib/fontist/import/windows/windows_license.txt +4 -0
- data/lib/fontist/import/windows.rb +162 -0
- data/lib/fontist/import.rb +1 -0
- data/lib/fontist/import_source.rb +1 -0
- data/lib/fontist/macos/catalog/catalog_manager.rb +1 -1
- data/lib/fontist/resource.rb +5 -1
- data/lib/fontist/resources/windows_fod_resource.rb +51 -0
- data/lib/fontist/resources.rb +1 -0
- data/lib/fontist/utils/downloader.rb +4 -3
- data/lib/fontist/utils/system.rb +17 -0
- data/lib/fontist/utils/user_agent.rb +95 -0
- data/lib/fontist/utils.rb +1 -0
- data/lib/fontist/version.rb +1 -1
- data/lib/fontist/windows_fod_metadata.rb +83 -0
- data/lib/fontist/windows_import_source.rb +54 -0
- data/lib/fontist.rb +2 -0
- data/script/generate_windows_formulas.rb +24 -0
- data/script/validate_windows_fod_ci.rb +175 -0
- metadata +14 -2
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Fontist
|
|
5
|
+
module Import
|
|
6
|
+
class Windows
|
|
7
|
+
HOMEPAGE = "https://learn.microsoft.com/en-us/typography/fonts/windows_11_font_list".freeze
|
|
8
|
+
|
|
9
|
+
def initialize(formulas_dir: nil)
|
|
10
|
+
@custom_formulas_dir = formulas_dir
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
capabilities = WindowsFodMetadata.all_capabilities
|
|
15
|
+
|
|
16
|
+
Fontist.ui.say(
|
|
17
|
+
"Generating #{capabilities.size} Windows FOD formula files...",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
capabilities.each do |cap_name|
|
|
21
|
+
generate_formula(cap_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Fontist.ui.say("Done. #{capabilities.size} formulas generated.")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def generate_formula(cap_name)
|
|
30
|
+
description = WindowsFodMetadata.description_for_capability(cap_name)
|
|
31
|
+
fonts_data = WindowsFodMetadata.fonts_for_capability(cap_name)
|
|
32
|
+
formula = build_formula(cap_name, description, fonts_data)
|
|
33
|
+
write_formula(description, formula, fonts_data.size)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def write_formula(description, formula, font_count)
|
|
37
|
+
path = formula_path(description)
|
|
38
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
39
|
+
File.write(path, YAML.dump(stringify_keys(formula)))
|
|
40
|
+
Fontist.ui.say(
|
|
41
|
+
" Created: #{File.basename(path)} (#{font_count} fonts)",
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_formula(cap_name, description, fonts_data)
|
|
46
|
+
base_attrs(description).merge(
|
|
47
|
+
resources: build_resources(cap_name, description, fonts_data),
|
|
48
|
+
fonts: build_fonts(fonts_data),
|
|
49
|
+
**import_source_attrs(cap_name),
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def base_attrs(description)
|
|
54
|
+
{
|
|
55
|
+
schema_version: 5,
|
|
56
|
+
name: description,
|
|
57
|
+
description: "#{description} for Windows",
|
|
58
|
+
homepage: HOMEPAGE,
|
|
59
|
+
platforms: ["windows"],
|
|
60
|
+
open_license: license_text,
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def import_source_attrs(cap_name)
|
|
65
|
+
{
|
|
66
|
+
import_source: {
|
|
67
|
+
type: "windows",
|
|
68
|
+
capability_name: cap_name,
|
|
69
|
+
min_windows_version: "10.0",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_resources(cap_name, description, fonts_data)
|
|
75
|
+
all_fonts = collect_font_filenames(fonts_data)
|
|
76
|
+
formats = all_fonts.map { |f| detect_format(f) }.uniq
|
|
77
|
+
|
|
78
|
+
resource = {
|
|
79
|
+
source: "windows_fod",
|
|
80
|
+
capability_name: cap_name,
|
|
81
|
+
}
|
|
82
|
+
resource[:format] = formats.first if formats.size == 1
|
|
83
|
+
|
|
84
|
+
{ normalize_key(description) => resource }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def collect_font_filenames(fonts_data)
|
|
88
|
+
fonts_data.flat_map do |_, data|
|
|
89
|
+
data["styles"].map { |s| s["font"] }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_fonts(fonts_data)
|
|
94
|
+
fonts_data.map do |family_name, data|
|
|
95
|
+
{
|
|
96
|
+
name: family_name,
|
|
97
|
+
styles: build_styles(family_name, data["styles"]),
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_styles(family_name, styles)
|
|
103
|
+
styles.map do |style|
|
|
104
|
+
fmt = detect_format(style["font"])
|
|
105
|
+
{
|
|
106
|
+
family_name: family_name,
|
|
107
|
+
type: style["type"],
|
|
108
|
+
font: style["font"],
|
|
109
|
+
formats: [fmt],
|
|
110
|
+
variable_font: false,
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def detect_format(filename)
|
|
116
|
+
File.extname(filename).downcase.delete(".").then do |ext|
|
|
117
|
+
%w[ttf ttc otf otc].include?(ext) ? ext : "ttf"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def formula_path(description)
|
|
122
|
+
filename = "#{normalize_key(description)}.yml"
|
|
123
|
+
formula_dir.join(filename)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def formula_dir
|
|
127
|
+
@formula_dir ||= if @custom_formulas_dir
|
|
128
|
+
Pathname.new(@custom_formulas_dir).tap do |path|
|
|
129
|
+
FileUtils.mkdir_p(path)
|
|
130
|
+
end
|
|
131
|
+
else
|
|
132
|
+
Fontist.formulas_path.join("windows").tap do |path|
|
|
133
|
+
FileUtils.mkdir_p(path)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def normalize_key(name)
|
|
139
|
+
name.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/^_|_$/, "")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def license_text
|
|
143
|
+
@license_text ||= File.read(
|
|
144
|
+
File.expand_path("windows/windows_license.txt", __dir__),
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def stringify_keys(obj)
|
|
149
|
+
case obj
|
|
150
|
+
when Hash
|
|
151
|
+
obj.each_with_object({}) do |(k, v), result|
|
|
152
|
+
result[k.to_s] = stringify_keys(v)
|
|
153
|
+
end
|
|
154
|
+
when Array
|
|
155
|
+
obj.map { |item| stringify_keys(item) }
|
|
156
|
+
else
|
|
157
|
+
obj
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/lib/fontist/import.rb
CHANGED
|
@@ -23,6 +23,7 @@ module Fontist
|
|
|
23
23
|
autoload :TemplateHelper, "#{__dir__}/import/template_helper"
|
|
24
24
|
autoload :TextHelper, "#{__dir__}/import/text_helper"
|
|
25
25
|
autoload :UpgradeFormulas, "#{__dir__}/import/upgrade_formulas"
|
|
26
|
+
autoload :Windows, "#{__dir__}/import/windows"
|
|
26
27
|
|
|
27
28
|
class << self
|
|
28
29
|
def name_to_filename(name)
|
|
@@ -55,7 +55,7 @@ module Fontist
|
|
|
55
55
|
Fontist.ui.say("Downloading Font#{version} catalog from #{url}...") if Fontist.ui.respond_to?(:say)
|
|
56
56
|
|
|
57
57
|
URI(url).open(
|
|
58
|
-
"User-Agent" =>
|
|
58
|
+
"User-Agent" => Fontist::Utils::UserAgent.random_user_agent,
|
|
59
59
|
) do |response|
|
|
60
60
|
File.write(catalog_file, response.read)
|
|
61
61
|
end
|
data/lib/fontist/resource.rb
CHANGED
|
@@ -15,6 +15,9 @@ module Fontist
|
|
|
15
15
|
attribute :format, :string # ttf, otf, woff2, ttc, otc
|
|
16
16
|
attribute :variable_axes, :string, collection: true # [wght], [ital,wght], etc.
|
|
17
17
|
|
|
18
|
+
# Windows FOD support
|
|
19
|
+
attribute :capability_name, :string
|
|
20
|
+
|
|
18
21
|
key_value do
|
|
19
22
|
map "name", to: :name
|
|
20
23
|
map "source", to: :source
|
|
@@ -25,10 +28,11 @@ module Fontist
|
|
|
25
28
|
map "files", to: :files
|
|
26
29
|
map "format", to: :format
|
|
27
30
|
map "variable_axes", to: :variable_axes
|
|
31
|
+
map "capability_name", to: :capability_name
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def empty?
|
|
31
|
-
Array(urls).empty? && Array(files).empty?
|
|
35
|
+
Array(urls).empty? && Array(files).empty? && capability_name.nil?
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def variable_font?
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Fontist
|
|
2
|
+
module Resources
|
|
3
|
+
class WindowsFodResource
|
|
4
|
+
def initialize(resource, options = {})
|
|
5
|
+
@resource = resource
|
|
6
|
+
@options = options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def files(source_names, &block)
|
|
10
|
+
install_capability!
|
|
11
|
+
|
|
12
|
+
source_names.each do |filename|
|
|
13
|
+
path = File.join(system_fonts_dir, filename)
|
|
14
|
+
yield path if File.exist?(path)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def install_capability!
|
|
21
|
+
cap_name = @resource.capability_name
|
|
22
|
+
return if capability_installed?(cap_name)
|
|
23
|
+
|
|
24
|
+
Fontist.ui.say("Installing Windows font capability: #{cap_name}")
|
|
25
|
+
result = Utils::System.run_powershell(
|
|
26
|
+
"Add-WindowsCapability -Online -Name '#{ps_escape(cap_name)}'",
|
|
27
|
+
)
|
|
28
|
+
unless result.success?
|
|
29
|
+
raise Errors::WindowsFodInstallError.new(cap_name, result.stderr)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def capability_installed?(name)
|
|
34
|
+
result = Utils::System.run_powershell(
|
|
35
|
+
"(Get-WindowsCapability -Online -Name '#{ps_escape(name)}').State",
|
|
36
|
+
)
|
|
37
|
+
result.stdout.strip == "Installed"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Escape single quotes for PowerShell single-quoted strings
|
|
41
|
+
def ps_escape(str)
|
|
42
|
+
str.gsub("'", "''")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def system_fonts_dir
|
|
46
|
+
windir = ENV["windir"] || ENV["SystemRoot"] || "C:/Windows"
|
|
47
|
+
File.join(windir, "Fonts")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/fontist/resources.rb
CHANGED
|
@@ -3,5 +3,6 @@ module Fontist
|
|
|
3
3
|
autoload :AppleCDNResource, "#{__dir__}/resources/apple_cdn_resource"
|
|
4
4
|
autoload :ArchiveResource, "#{__dir__}/resources/archive_resource"
|
|
5
5
|
autoload :GoogleResource, "#{__dir__}/resources/google_resource"
|
|
6
|
+
autoload :WindowsFodResource, "#{__dir__}/resources/windows_fod_resource"
|
|
6
7
|
end
|
|
7
8
|
end
|
|
@@ -135,10 +135,11 @@ module Fontist
|
|
|
135
135
|
|
|
136
136
|
def headers
|
|
137
137
|
obj = Helpers.url_object(@file)
|
|
138
|
-
(obj.respond_to?(:headers) &&
|
|
138
|
+
formula_headers = (obj.respond_to?(:headers) &&
|
|
139
139
|
obj.headers &&
|
|
140
|
-
obj.headers.to_h.to_h { |k, v| [k.to_s, v] }) || # rubocop:disable Style/HashTransformKeys, Metrics/LineLength
|
|
141
|
-
|
|
140
|
+
obj.headers.to_h.to_h { |k, v| [k.to_s, v] }) || {} # rubocop:disable Style/HashTransformKeys, Metrics/LineLength
|
|
141
|
+
|
|
142
|
+
Utils::UserAgent.browser_headers.merge(formula_headers)
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
def extract_raw_url
|
data/lib/fontist/utils/system.rb
CHANGED
|
@@ -179,6 +179,23 @@ module Fontist
|
|
|
179
179
|
true
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
+
PowerShellResult = Struct.new(:stdout, :stderr, :success,
|
|
183
|
+
keyword_init: true) do
|
|
184
|
+
alias_method :success?, :success
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def self.run_powershell(command)
|
|
188
|
+
require "open3"
|
|
189
|
+
stdout, stderr, status = Open3.capture3(
|
|
190
|
+
"powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command
|
|
191
|
+
)
|
|
192
|
+
PowerShellResult.new(stdout: stdout, stderr: stderr,
|
|
193
|
+
success: status.success?)
|
|
194
|
+
rescue Errno::ENOENT
|
|
195
|
+
PowerShellResult.new(stdout: "", stderr: "powershell.exe not found",
|
|
196
|
+
success: false)
|
|
197
|
+
end
|
|
198
|
+
|
|
182
199
|
def self.catalog_version_for_macos
|
|
183
200
|
# Check for platform override first
|
|
184
201
|
if platform_override?
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontist
|
|
4
|
+
module Utils
|
|
5
|
+
module UserAgent
|
|
6
|
+
PROFILES = [
|
|
7
|
+
{
|
|
8
|
+
user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \
|
|
9
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) " \
|
|
10
|
+
"Chrome/131.0.0.0 Safari/537.36",
|
|
11
|
+
platform: '"macOS"',
|
|
12
|
+
chrome_version: "131",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " \
|
|
16
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) " \
|
|
17
|
+
"Chrome/130.0.0.0 Safari/537.36",
|
|
18
|
+
platform: '"Windows"',
|
|
19
|
+
chrome_version: "130",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
user_agent: "Mozilla/5.0 (X11; Linux x86_64) " \
|
|
23
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) " \
|
|
24
|
+
"Chrome/132.0.0.0 Safari/537.36",
|
|
25
|
+
platform: '"Linux"',
|
|
26
|
+
chrome_version: "132",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \
|
|
30
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) " \
|
|
31
|
+
"Chrome/130.0.0.0 Safari/537.36",
|
|
32
|
+
platform: '"macOS"',
|
|
33
|
+
chrome_version: "130",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " \
|
|
37
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) " \
|
|
38
|
+
"Chrome/132.0.0.0 Safari/537.36",
|
|
39
|
+
platform: '"Windows"',
|
|
40
|
+
chrome_version: "132",
|
|
41
|
+
},
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," \
|
|
45
|
+
"image/avif,image/webp,image/apng,*/*;q=0.8," \
|
|
46
|
+
"application/signed-exchange;v=b3;q=0.7"
|
|
47
|
+
|
|
48
|
+
ACCEPT_LANGUAGE = "en-US,en;q=0.9"
|
|
49
|
+
|
|
50
|
+
STATIC_HEADERS = {
|
|
51
|
+
"Accept" => ACCEPT,
|
|
52
|
+
"Accept-Language" => ACCEPT_LANGUAGE,
|
|
53
|
+
"Cache-Control" => "no-cache",
|
|
54
|
+
"Pragma" => "no-cache",
|
|
55
|
+
"Sec-Ch-Ua-Mobile" => "?0",
|
|
56
|
+
"Sec-Fetch-Dest" => "document",
|
|
57
|
+
"Sec-Fetch-Mode" => "navigate",
|
|
58
|
+
"Sec-Fetch-Site" => "cross-site",
|
|
59
|
+
"Sec-Fetch-User" => "?1",
|
|
60
|
+
"Upgrade-Insecure-Requests" => "1",
|
|
61
|
+
}.freeze
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
def random_profile
|
|
65
|
+
PROFILES.sample
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def browser_headers
|
|
69
|
+
profile = random_profile
|
|
70
|
+
build_headers(profile)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def random_user_agent
|
|
74
|
+
random_profile[:user_agent]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def build_headers(profile)
|
|
80
|
+
STATIC_HEADERS.merge(
|
|
81
|
+
"User-Agent" => profile[:user_agent],
|
|
82
|
+
"Sec-Ch-Ua" => build_sec_ch_ua(profile[:chrome_version]),
|
|
83
|
+
"Sec-Ch-Ua-Platform" => profile[:platform],
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_sec_ch_ua(chrome_version)
|
|
88
|
+
"\"Google Chrome\";v=\"#{chrome_version}\", " \
|
|
89
|
+
"\"Chromium\";v=\"#{chrome_version}\", " \
|
|
90
|
+
"\"Not_A Brand\";v=\"24\""
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/fontist/utils.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Fontist
|
|
|
9
9
|
autoload :Locking, "#{__dir__}/utils/locking"
|
|
10
10
|
autoload :System, "#{__dir__}/utils/system"
|
|
11
11
|
autoload :UI, "#{__dir__}/utils/ui"
|
|
12
|
+
autoload :UserAgent, "#{__dir__}/utils/user_agent"
|
|
12
13
|
|
|
13
14
|
# Converts a glob pattern to case-insensitive by replacing each
|
|
14
15
|
# alphabetic character with a character class [aA]
|
data/lib/fontist/version.rb
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module Fontist
|
|
4
|
+
# Metadata for Windows Features on Demand (FOD) font capabilities
|
|
5
|
+
#
|
|
6
|
+
# Provides lookup methods to map between FOD capability names and
|
|
7
|
+
# their associated font families/filenames.
|
|
8
|
+
class WindowsFodMetadata
|
|
9
|
+
DATA_PATH = File.expand_path("import/windows/fod_capabilities.yml", __dir__)
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
# Reverse lookup: font family name -> capability name
|
|
13
|
+
#
|
|
14
|
+
# @param font_name [String] The font family name (e.g., "Meiryo")
|
|
15
|
+
# @return [String, nil] The capability name or nil if not found
|
|
16
|
+
def capability_for_font(font_name)
|
|
17
|
+
reverse_map[font_name.downcase]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get all font families for a capability
|
|
21
|
+
#
|
|
22
|
+
# @param cap_name [String] The capability name
|
|
23
|
+
# @return [Hash, nil] Hash of font family names to their metadata
|
|
24
|
+
def fonts_for_capability(cap_name)
|
|
25
|
+
cap = metadata.dig("capabilities", cap_name)
|
|
26
|
+
cap&.fetch("fonts", nil)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get the description for a capability
|
|
30
|
+
#
|
|
31
|
+
# @param cap_name [String] The capability name
|
|
32
|
+
# @return [String, nil] The description
|
|
33
|
+
def description_for_capability(cap_name)
|
|
34
|
+
metadata.dig("capabilities", cap_name, "description")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Flat list of all FOD font family names
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<String>] All font family names
|
|
40
|
+
def all_font_names
|
|
41
|
+
metadata["capabilities"].flat_map do |_cap, data|
|
|
42
|
+
data["fonts"].keys
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# List of all capability names
|
|
47
|
+
#
|
|
48
|
+
# @return [Array<String>] All capability names
|
|
49
|
+
def all_capabilities
|
|
50
|
+
metadata["capabilities"].keys
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Raw parsed YAML metadata
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash] The parsed YAML data
|
|
56
|
+
def metadata
|
|
57
|
+
@metadata ||= YAML.safe_load(File.read(DATA_PATH))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Reset cached metadata (for testing)
|
|
61
|
+
# @api private
|
|
62
|
+
def reset_cache
|
|
63
|
+
@metadata = nil
|
|
64
|
+
@reverse_map = nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Build reverse lookup map: lowercase font name -> capability name
|
|
70
|
+
def reverse_map
|
|
71
|
+
@reverse_map ||= begin
|
|
72
|
+
map = {}
|
|
73
|
+
metadata["capabilities"].each do |cap_name, data|
|
|
74
|
+
data["fonts"].each_key do |font_name|
|
|
75
|
+
map[font_name.downcase] = cap_name
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
map
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Fontist
|
|
2
|
+
# Import source for Windows Features on Demand (FOD) supplementary fonts
|
|
3
|
+
#
|
|
4
|
+
# Tracks the capability name and minimum Windows version for fonts
|
|
5
|
+
# installed via Add-WindowsCapability.
|
|
6
|
+
class WindowsImportSource < ImportSource
|
|
7
|
+
attribute :capability_name, :string
|
|
8
|
+
attribute :min_windows_version, :string
|
|
9
|
+
|
|
10
|
+
key_value do
|
|
11
|
+
map "capability_name", to: :capability_name
|
|
12
|
+
map "min_windows_version", to: :min_windows_version
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns the capability name for differentiation
|
|
16
|
+
#
|
|
17
|
+
# @return [String, nil] Capability name or nil
|
|
18
|
+
def differentiation_key
|
|
19
|
+
capability_name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Checks if this import source is older than the provided new source
|
|
23
|
+
#
|
|
24
|
+
# @param new_source [WindowsImportSource] The new source to compare against
|
|
25
|
+
# @return [Boolean] true if this source is outdated
|
|
26
|
+
def outdated?(new_source)
|
|
27
|
+
return false unless new_source.is_a?(WindowsImportSource)
|
|
28
|
+
|
|
29
|
+
# Windows FOD capabilities don't have versioned updates in the same way;
|
|
30
|
+
# they are either present or not. Always return false.
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns a human-readable string representation
|
|
35
|
+
#
|
|
36
|
+
# @return [String] String representation for debugging/logging
|
|
37
|
+
def to_s
|
|
38
|
+
"Windows FOD (capability: #{capability_name}, " \
|
|
39
|
+
"min_version: #{min_windows_version})"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Equality check based on capability name
|
|
43
|
+
#
|
|
44
|
+
# @param other [Object] The object to compare
|
|
45
|
+
# @return [Boolean] true if objects are equal
|
|
46
|
+
def ==(other)
|
|
47
|
+
return false unless other.is_a?(WindowsImportSource)
|
|
48
|
+
|
|
49
|
+
capability_name == other.capability_name
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
alias eql? ==
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/fontist.rb
CHANGED
|
@@ -54,6 +54,8 @@ module Fontist
|
|
|
54
54
|
autoload :SilImportSource, "#{__dir__}/fontist/sil_import_source"
|
|
55
55
|
autoload :MacosFrameworkMetadata,
|
|
56
56
|
"#{__dir__}/fontist/macos_framework_metadata"
|
|
57
|
+
autoload :WindowsImportSource, "#{__dir__}/fontist/windows_import_source"
|
|
58
|
+
autoload :WindowsFodMetadata, "#{__dir__}/fontist/windows_fod_metadata"
|
|
57
59
|
|
|
58
60
|
# Manifest classes
|
|
59
61
|
autoload :Manifest, "#{__dir__}/fontist/manifest"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Generate Windows FOD (Features on Demand) formula files
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bundle exec ruby script/generate_windows_formulas.rb [output_dir]
|
|
8
|
+
#
|
|
9
|
+
# Generates formula YAML files for all 25 Windows FOD font capabilities.
|
|
10
|
+
# Output goes to Formulas/windows/ by default, or to the specified directory.
|
|
11
|
+
|
|
12
|
+
require "bundler/setup"
|
|
13
|
+
require "fontist"
|
|
14
|
+
require_relative "../lib/fontist/import/windows"
|
|
15
|
+
|
|
16
|
+
output_dir = ARGV[0]
|
|
17
|
+
|
|
18
|
+
importer = if output_dir
|
|
19
|
+
Fontist::Import::Windows.new(formulas_dir: output_dir)
|
|
20
|
+
else
|
|
21
|
+
Fontist::Import::Windows.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
importer.call
|