fontist 1.13.2 → 1.14.2

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.
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/google_cli"
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 it's already installed in system"
71
+ desc: "Install even if already installed in system"
67
72
  option :formula, type: :boolean, aliases: :F,
68
- desc: "Install by formula instead of font"
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 formula if several"
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 size limit for formula " \
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 "google SUBCOMMAND ...ARGS", "Manage Google formulas"
213
- subcommand "google", Fontist::GoogleCLI
212
+ desc "fontconfig SUBCOMMAND ...ARGS", "Manage fontconfig"
213
+ subcommand "fontconfig", Fontist::FontconfigCLI
214
214
 
215
215
  private
216
216
 
@@ -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
@@ -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
- filename = Import.name_to_filename(builder.name)
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).map do |path|
30
- Otf::FontFile.new(path)
31
- end
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 open_license digest command].freeze
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
- unique_names = both_fonts.map(&:family_name).uniq
32
- TextHelper.longest_common_prefix(unique_names) ||
33
- both_fonts.first.family_name
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.ui.debug("Run `#{command}`")
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