fontist 1.13.2 → 1.14.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/metanorma.yml +12 -2
- data/README.adoc +245 -201
- data/fontist.gemspec +1 -0
- data/lib/fontist/cli.rb +14 -14
- data/lib/fontist/errors.rb +12 -0
- data/lib/fontist/font.rb +13 -1
- data/lib/fontist/fontconfig.rb +87 -0
- data/lib/fontist/fontconfig_cli.rb +29 -0
- data/lib/fontist/helpers.rb +21 -0
- data/lib/fontist/import/create_formula.rb +22 -2
- data/lib/fontist/import/files/collection_file.rb +7 -3
- data/lib/fontist/import/formula_builder.rb +23 -7
- data/lib/fontist/import/google/new_fonts_fetcher.rb +4 -0
- data/lib/fontist/import/helpers/system_helper.rb +1 -9
- data/lib/fontist/import/macos/macos_license.txt +596 -0
- data/lib/fontist/import/macos.rb +25 -109
- data/lib/fontist/import/recursive_extraction.rb +11 -1
- data/lib/fontist/import/text_helper.rb +1 -1
- data/lib/fontist/import_cli.rb +17 -5
- data/lib/fontist/utils/cache.rb +11 -2
- data/lib/fontist/utils/system.rb +8 -0
- data/lib/fontist/utils.rb +0 -3
- data/lib/fontist/version.rb +1 -1
- metadata +19 -6
- data/lib/fontist/google_cli.rb +0 -21
- data/lib/fontist/utils/dsl/collection_font.rb +0 -36
- data/lib/fontist/utils/dsl/font.rb +0 -38
- data/lib/fontist/utils/dsl.rb +0 -85
data/lib/fontist/cli.rb
CHANGED
@@ -2,7 +2,7 @@ require "thor"
|
|
2
2
|
require "fontist/cli/class_options"
|
3
3
|
require "fontist/repo_cli"
|
4
4
|
require "fontist/import_cli"
|
5
|
-
require "fontist/
|
5
|
+
require "fontist/fontconfig_cli"
|
6
6
|
|
7
7
|
module Fontist
|
8
8
|
class CLI < Thor
|
@@ -22,6 +22,8 @@ module Fontist
|
|
22
22
|
STATUS_MANUAL_FONT_ERROR = 11
|
23
23
|
STATUS_SIZE_LIMIT_ERROR = 12
|
24
24
|
STATUS_FORMULA_NOT_FOUND = 13
|
25
|
+
STATUS_FONTCONFIG_NOT_FOUND = 14
|
26
|
+
STATUS_FONTCONFIG_FILE_NOT_FOUND = 15
|
25
27
|
|
26
28
|
ERROR_TO_STATUS = {
|
27
29
|
Fontist::Errors::UnsupportedFontError => [STATUS_NON_SUPPORTED_FONT_ERROR],
|
@@ -44,6 +46,9 @@ module Fontist
|
|
44
46
|
Fontist::Errors::RepoNotFoundError => [STATUS_REPO_NOT_FOUND],
|
45
47
|
Fontist::Errors::MainRepoNotFoundError => [STATUS_MAIN_REPO_NOT_FOUND],
|
46
48
|
Fontist::Errors::FormulaNotFoundError => [STATUS_FORMULA_NOT_FOUND],
|
49
|
+
Fontist::Errors::FontconfigNotFoundError => [STATUS_FONTCONFIG_NOT_FOUND],
|
50
|
+
Fontist::Errors::FontconfigFileNotFoundError =>
|
51
|
+
[STATUS_FONTCONFIG_FILE_NOT_FOUND],
|
47
52
|
}.freeze
|
48
53
|
|
49
54
|
def self.exit_on_failure?
|
@@ -63,9 +68,9 @@ module Fontist
|
|
63
68
|
|
64
69
|
desc "install FONT", "Install font"
|
65
70
|
option :force, type: :boolean, aliases: :f,
|
66
|
-
desc: "Install even if
|
71
|
+
desc: "Install even if already installed in system"
|
67
72
|
option :formula, type: :boolean, aliases: :F,
|
68
|
-
desc: "Install
|
73
|
+
desc: "Install whole formula instead of a font"
|
69
74
|
option :accept_all_licenses, type: :boolean,
|
70
75
|
aliases: ["--confirm-license", :a],
|
71
76
|
desc: "Accept all license agreements"
|
@@ -76,13 +81,15 @@ module Fontist
|
|
76
81
|
option :version, type: :string, aliases: :V,
|
77
82
|
desc: "Specify particular version of a font"
|
78
83
|
option :smallest, type: :boolean, aliases: :s,
|
79
|
-
desc: "Install the smallest
|
84
|
+
desc: "Install the smallest font by file size if several"
|
80
85
|
option :newest, type: :boolean, aliases: :n,
|
81
86
|
desc: "Install the newest version of a font if several"
|
82
87
|
option :size_limit,
|
83
88
|
type: :numeric, aliases: :S,
|
84
|
-
desc: "Specify
|
89
|
+
desc: "Specify upper limit for file size of a formula to be installed" \
|
85
90
|
"(default is #{Fontist.formula_size_limit_in_megabytes} MB)"
|
91
|
+
option :update_fontconfig, type: :boolean, aliases: :u,
|
92
|
+
desc: "Update fontconfig"
|
86
93
|
def install(font)
|
87
94
|
handle_class_options(options)
|
88
95
|
confirmation = options[:accept_all_licenses] ? "yes" : "no"
|
@@ -196,21 +203,14 @@ module Fontist
|
|
196
203
|
STATUS_SUCCESS
|
197
204
|
end
|
198
205
|
|
199
|
-
desc "import-sil", "Import formulas from SIL"
|
200
|
-
def import_sil
|
201
|
-
handle_class_options(options)
|
202
|
-
require "fontist/import/sil_import"
|
203
|
-
Fontist::Import::SilImport.new.call
|
204
|
-
end
|
205
|
-
|
206
206
|
desc "repo SUBCOMMAND ...ARGS", "Manage custom repositories"
|
207
207
|
subcommand "repo", Fontist::RepoCLI
|
208
208
|
|
209
209
|
desc "import SUBCOMMAND ...ARGS", "Manage imports"
|
210
210
|
subcommand "import", Fontist::ImportCLI
|
211
211
|
|
212
|
-
desc "
|
213
|
-
subcommand "
|
212
|
+
desc "fontconfig SUBCOMMAND ...ARGS", "Manage fontconfig"
|
213
|
+
subcommand "fontconfig", Fontist::FontconfigCLI
|
214
214
|
|
215
215
|
private
|
216
216
|
|
data/lib/fontist/errors.rb
CHANGED
@@ -4,6 +4,18 @@ module Fontist
|
|
4
4
|
|
5
5
|
class BinaryCallError < GeneralError; end
|
6
6
|
|
7
|
+
class FontconfigNotFoundError < GeneralError
|
8
|
+
def initialize
|
9
|
+
super("Could not find fontconfig.")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class FontconfigFileNotFoundError < GeneralError
|
14
|
+
def initialize
|
15
|
+
super("Fontist file could not be found in fontconfig configuration.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
7
19
|
class FontIndexCorrupted < GeneralError; end
|
8
20
|
|
9
21
|
class FontNotFoundError < GeneralError; end
|
data/lib/fontist/font.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "fontist/font_installer"
|
2
2
|
require "fontist/font_path"
|
3
3
|
require "fontist/formula_picker"
|
4
|
+
require "fontist/fontconfig"
|
4
5
|
|
5
6
|
module Fontist
|
6
7
|
class Font
|
@@ -15,6 +16,7 @@ module Fontist
|
|
15
16
|
@newest = options[:newest]
|
16
17
|
@size_limit = options[:size_limit]
|
17
18
|
@by_formula = options[:formula]
|
19
|
+
@update_fontconfig = options[:update_fontconfig]
|
18
20
|
|
19
21
|
check_or_create_fontist_path!
|
20
22
|
end
|
@@ -166,9 +168,13 @@ module Fontist
|
|
166
168
|
def download_font
|
167
169
|
return if sufficient_formulas.empty?
|
168
170
|
|
169
|
-
sufficient_formulas.flat_map do |formula|
|
171
|
+
paths = sufficient_formulas.flat_map do |formula|
|
170
172
|
request_formula_installation(formula)
|
171
173
|
end
|
174
|
+
|
175
|
+
update_fontconfig
|
176
|
+
|
177
|
+
paths
|
172
178
|
end
|
173
179
|
|
174
180
|
def request_formula_installation(formula)
|
@@ -220,6 +226,12 @@ module Fontist
|
|
220
226
|
MSG
|
221
227
|
end
|
222
228
|
|
229
|
+
def update_fontconfig
|
230
|
+
return unless @update_fontconfig
|
231
|
+
|
232
|
+
Fontconfig.update
|
233
|
+
end
|
234
|
+
|
223
235
|
def manual_font
|
224
236
|
return if manual_formulas.empty?
|
225
237
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Fontist
|
2
|
+
class Fontconfig
|
3
|
+
def self.update
|
4
|
+
new.update
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.remove(options = {})
|
8
|
+
new(options).remove
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
ensure_fontconfig_installed
|
17
|
+
create_config
|
18
|
+
regenerate_fontconfig_cache
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove
|
22
|
+
return handle_file_not_found unless config_exists?
|
23
|
+
|
24
|
+
regenerate_fontconfig_cache if fontconfig_installed?
|
25
|
+
remove_config
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def ensure_fontconfig_installed
|
31
|
+
raise Errors::FontconfigNotFoundError unless fontconfig_installed?
|
32
|
+
end
|
33
|
+
|
34
|
+
def fontconfig_installed?
|
35
|
+
Utils::System.fontconfig_installed?
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_config
|
39
|
+
return if File.exist?(config_path)
|
40
|
+
|
41
|
+
FileUtils.mkdir_p(File.dirname(config_path))
|
42
|
+
File.write(config_path, config_content)
|
43
|
+
end
|
44
|
+
|
45
|
+
def config_path
|
46
|
+
File.join(xdg_config_home, "fontconfig", "conf.d", "10-fontist.conf")
|
47
|
+
end
|
48
|
+
|
49
|
+
def xdg_config_home
|
50
|
+
ENV["XDG_CONFIG_HOME"] || File.join(Dir.home, ".config")
|
51
|
+
end
|
52
|
+
|
53
|
+
def config_content
|
54
|
+
<<~CONTENT
|
55
|
+
<?xml version='1.0'?>
|
56
|
+
<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
|
57
|
+
<fontconfig>
|
58
|
+
<dir>#{Fontist.fonts_path}</dir>
|
59
|
+
</fontconfig>
|
60
|
+
CONTENT
|
61
|
+
end
|
62
|
+
|
63
|
+
def regenerate_fontconfig_cache
|
64
|
+
Helpers.run("fc-cache -f")
|
65
|
+
end
|
66
|
+
|
67
|
+
def ensure_file_exists
|
68
|
+
return if @options[:force]
|
69
|
+
|
70
|
+
raise Errors::FontconfigFileNotFoundError unless File.exist?(config_path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def config_exists?
|
74
|
+
File.exist?(config_path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_file_not_found
|
78
|
+
return if @options[:force]
|
79
|
+
|
80
|
+
raise Errors::FontconfigFileNotFoundError
|
81
|
+
end
|
82
|
+
|
83
|
+
def remove_config
|
84
|
+
FileUtils.rm(config_path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Fontist
|
2
|
+
class FontconfigCLI < Thor
|
3
|
+
include CLI::ClassOptions
|
4
|
+
|
5
|
+
desc "update", "Update fontconfig configuration to use fontist fonts"
|
6
|
+
def update
|
7
|
+
handle_class_options(options)
|
8
|
+
Fontconfig.update
|
9
|
+
Fontist.ui.success("Fontconfig file has been successfully updated.")
|
10
|
+
CLI::STATUS_SUCCESS
|
11
|
+
rescue Errors::FontconfigNotFoundError => e
|
12
|
+
Fontist.ui.error(e.message)
|
13
|
+
CLI::STATUS_FONTCONFIG_NOT_FOUND
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "remove", "Remove fontist file in fontconfig configuration"
|
17
|
+
option :force, type: :boolean, aliases: :f,
|
18
|
+
desc: "Proceed even if does not exist"
|
19
|
+
def remove
|
20
|
+
handle_class_options(options)
|
21
|
+
Fontconfig.remove(options)
|
22
|
+
Fontist.ui.success("Fontconfig file has been successfully removed.")
|
23
|
+
CLI::STATUS_SUCCESS
|
24
|
+
rescue Errors::FontconfigFileNotFoundError => e
|
25
|
+
Fontist.ui.error(e.message)
|
26
|
+
CLI::STATUS_FONTCONFIG_FILE_NOT_FOUND
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/fontist/helpers.rb
CHANGED
@@ -3,5 +3,26 @@ module Fontist
|
|
3
3
|
def self.parse_to_object(data)
|
4
4
|
JSON.parse(data.to_json, object_class: OpenStruct)
|
5
5
|
end
|
6
|
+
|
7
|
+
def self.run(command)
|
8
|
+
Fontist.ui.debug("Run `#{command}`")
|
9
|
+
|
10
|
+
result = `#{command}`
|
11
|
+
unless $CHILD_STATUS.to_i.zero?
|
12
|
+
raise Errors::BinaryCallError,
|
13
|
+
"Failed to run #{command}, status: #{$CHILD_STATUS}"
|
14
|
+
end
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.silence_stream(stream)
|
20
|
+
old_stream = stream.dup
|
21
|
+
stream.reopen(RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ ? "NUL:" : "/dev/null") # rubocop:disable Performance/RegexpMatch, Metrics/LineLength
|
22
|
+
stream.sync = true
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
stream.reopen(old_stream)
|
26
|
+
end
|
6
27
|
end
|
7
28
|
end
|
@@ -55,12 +55,32 @@ module Fontist
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def save(builder)
|
58
|
-
|
59
|
-
path = @options[:formula_dir] ? File.join(@options[:formula_dir], filename) : filename
|
58
|
+
path = vacant_path
|
60
59
|
yaml = YAML.dump(Helpers::HashHelper.stringify_keys(builder.formula))
|
61
60
|
File.write(path, yaml)
|
62
61
|
path
|
63
62
|
end
|
63
|
+
|
64
|
+
def vacant_path
|
65
|
+
path = path_from_name
|
66
|
+
return path unless @options[:keep_existing] && File.exist?(path)
|
67
|
+
|
68
|
+
2.upto(9) do |i|
|
69
|
+
candidate = path.sub(/\.yml$/, "#{i}.yml")
|
70
|
+
return candidate unless File.exist?(candidate)
|
71
|
+
end
|
72
|
+
|
73
|
+
raise Errors::GeneralError, "Formula #{path} already exists."
|
74
|
+
end
|
75
|
+
|
76
|
+
def path_from_name
|
77
|
+
filename = Import.name_to_filename(builder.name)
|
78
|
+
if @options[:formula_dir]
|
79
|
+
File.join(@options[:formula_dir], filename)
|
80
|
+
else
|
81
|
+
filename
|
82
|
+
end
|
83
|
+
end
|
64
84
|
end
|
65
85
|
end
|
66
86
|
end
|
@@ -26,9 +26,9 @@ module Fontist
|
|
26
26
|
|
27
27
|
def read
|
28
28
|
switch_to_temp_dir do |tmp_dir|
|
29
|
-
extract_ttfs(tmp_dir)
|
30
|
-
Otf::FontFile.new(path)
|
31
|
-
|
29
|
+
extract_ttfs(tmp_dir)
|
30
|
+
.map { |path| Otf::FontFile.new(path) }
|
31
|
+
.reject { |font_file| hidden_or_pua_encoded?(font_file) }
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -47,6 +47,10 @@ module Fontist
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
def hidden_or_pua_encoded?(font_file)
|
51
|
+
font_file.family_name.start_with?(".")
|
52
|
+
end
|
53
|
+
|
50
54
|
def detect_extension
|
51
55
|
base_extension = "ttc"
|
52
56
|
|
@@ -4,9 +4,10 @@ require_relative "text_helper"
|
|
4
4
|
module Fontist
|
5
5
|
module Import
|
6
6
|
class FormulaBuilder
|
7
|
-
FORMULA_ATTRIBUTES = %i[description homepage resources
|
7
|
+
FORMULA_ATTRIBUTES = %i[platforms description homepage resources
|
8
8
|
font_collections fonts extract copyright
|
9
|
-
license_url
|
9
|
+
license_url requires_license_agreement
|
10
|
+
open_license digest command].freeze
|
10
11
|
|
11
12
|
attr_writer :archive,
|
12
13
|
:url,
|
@@ -28,9 +29,15 @@ module Fontist
|
|
28
29
|
def name
|
29
30
|
return @options[:name] if @options[:name]
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
common = %i[family_name type]
|
33
|
+
.map { |attr| both_fonts.map(&attr).uniq }
|
34
|
+
.map { |names| TextHelper.longest_common_prefix(names) }
|
35
|
+
.map { |prefix| prefix unless prefix == "Regular" }
|
36
|
+
.compact
|
37
|
+
.join(" ")
|
38
|
+
return common unless common.empty?
|
39
|
+
|
40
|
+
both_fonts.map(&:family_name).first
|
34
41
|
end
|
35
42
|
|
36
43
|
private
|
@@ -50,6 +57,10 @@ module Fontist
|
|
50
57
|
files
|
51
58
|
end
|
52
59
|
|
60
|
+
def platforms
|
61
|
+
@options[:platforms]
|
62
|
+
end
|
63
|
+
|
53
64
|
def description
|
54
65
|
name
|
55
66
|
end
|
@@ -175,12 +186,17 @@ module Fontist
|
|
175
186
|
both_fonts.map(&:license_url).compact.first
|
176
187
|
end
|
177
188
|
|
189
|
+
def requires_license_agreement
|
190
|
+
@options[:requires_license_agreement]
|
191
|
+
end
|
192
|
+
|
178
193
|
def open_license
|
179
|
-
unless @license_text
|
194
|
+
unless @license_text || requires_license_agreement
|
180
195
|
Fontist.ui.error("WARN: please add license manually")
|
181
|
-
return
|
182
196
|
end
|
183
197
|
|
198
|
+
return unless @license_text
|
199
|
+
|
184
200
|
Fontist.ui.error("WARN: ensure it's an open license, otherwise " \
|
185
201
|
"change the 'open_license' attribute to " \
|
186
202
|
"'requires_license_agreement'")
|
@@ -121,6 +121,10 @@ module Fontist
|
|
121
121
|
Down.open("https://fonts.google.com/download?family=#{name}")
|
122
122
|
true
|
123
123
|
rescue Down::NotFound
|
124
|
+
false
|
125
|
+
rescue Down::ClientError => e
|
126
|
+
raise unless e.message == "403 Forbidden"
|
127
|
+
|
124
128
|
false
|
125
129
|
rescue Down::TimeoutError
|
126
130
|
retry unless retries >= 3
|
@@ -4,15 +4,7 @@ module Fontist
|
|
4
4
|
module SystemHelper
|
5
5
|
class << self
|
6
6
|
def run(command)
|
7
|
-
Fontist.
|
8
|
-
|
9
|
-
result = `#{command}`
|
10
|
-
unless $CHILD_STATUS.to_i.zero?
|
11
|
-
raise Errors::BinaryCallError,
|
12
|
-
"Failed to run #{command}, status: #{$CHILD_STATUS}"
|
13
|
-
end
|
14
|
-
|
15
|
-
result
|
7
|
+
Fontist::Helpers.run(command)
|
16
8
|
end
|
17
9
|
end
|
18
10
|
end
|