fontist 1.13.0 → 1.14.0

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.
@@ -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
@@ -12,6 +24,16 @@ module Fontist
12
24
  # it depends on this exception to automatically download formulas
13
25
  class FormulaIndexNotFoundError < GeneralError; end
14
26
 
27
+ class FormulaNotFoundError < GeneralError
28
+ def initialize(formula)
29
+ super(<<~MSG.chomp)
30
+ Formula '#{formula}' not found locally nor available in the Fontist formula repository.
31
+ Perhaps it is available at the latest Fontist formula repository.
32
+ You can update the formula repository using the command `fontist update` and try again.
33
+ MSG
34
+ end
35
+ end
36
+
15
37
  class MainRepoNotFoundError < FormulaIndexNotFoundError; end
16
38
 
17
39
  class InvalidResourceError < 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
@@ -14,6 +15,8 @@ module Fontist
14
15
  @smallest = options[:smallest]
15
16
  @newest = options[:newest]
16
17
  @size_limit = options[:size_limit]
18
+ @by_formula = options[:formula]
19
+ @update_fontconfig = options[:update_fontconfig]
17
20
 
18
21
  check_or_create_fontist_path!
19
22
  end
@@ -48,6 +51,8 @@ module Fontist
48
51
  end
49
52
 
50
53
  def install
54
+ return install_formula if @by_formula
55
+
51
56
  (find_system_font unless @force) || download_font || manual_font ||
52
57
  raise_non_supported_font
53
58
  end
@@ -103,6 +108,22 @@ module Fontist
103
108
  end
104
109
  end
105
110
 
111
+ def install_formula
112
+ download_formula || raise_formula_not_found
113
+ end
114
+
115
+ def download_formula
116
+ formula = Formula.find_by_key(@name)
117
+ return unless formula
118
+ return unless formula.downloadable?
119
+
120
+ request_formula_installation(formula)
121
+ end
122
+
123
+ def raise_formula_not_found
124
+ raise Errors::FormulaNotFoundError.new(@name)
125
+ end
126
+
106
127
  def font_installer(formula)
107
128
  FontInstaller.new(formula, no_progress: @no_progress)
108
129
  end
@@ -147,14 +168,22 @@ module Fontist
147
168
  def download_font
148
169
  return if sufficient_formulas.empty?
149
170
 
150
- sufficient_formulas.flat_map do |formula|
151
- confirmation = check_and_confirm_required_license(formula)
152
- paths = font_installer(formula).install(confirmation: confirmation)
171
+ paths = sufficient_formulas.flat_map do |formula|
172
+ request_formula_installation(formula)
173
+ end
153
174
 
154
- Fontist.ui.say("Fonts installed at:")
155
- paths.each do |path|
156
- Fontist.ui.say("- #{path}")
157
- end
175
+ update_fontconfig
176
+
177
+ paths
178
+ end
179
+
180
+ def request_formula_installation(formula)
181
+ confirmation = check_and_confirm_required_license(formula)
182
+ paths = font_installer(formula).install(confirmation: confirmation)
183
+
184
+ Fontist.ui.say("Fonts installed at:")
185
+ paths.each do |path|
186
+ Fontist.ui.say("- #{path}")
158
187
  end
159
188
  end
160
189
 
@@ -197,6 +226,12 @@ module Fontist
197
226
  MSG
198
227
  end
199
228
 
229
+ def update_fontconfig
230
+ return unless @update_fontconfig
231
+
232
+ Fontconfig.update
233
+ end
234
+
200
235
  def manual_font
201
236
  return if manual_formulas.empty?
202
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
@@ -45,6 +45,13 @@ module Fontist
45
45
  end.flatten
46
46
  end
47
47
 
48
+ def self.find_by_key(key)
49
+ path = Fontist.formulas_path.join("#{key}.yml")
50
+ return unless File.exist?(path)
51
+
52
+ new_from_file(path)
53
+ end
54
+
48
55
  def self.new_from_file(path)
49
56
  data = YAML.load_file(path)
50
57
  new(data, path)
@@ -72,7 +79,7 @@ module Fontist
72
79
  end
73
80
 
74
81
  def key
75
- @data["key"] || default_key
82
+ key_from_path
76
83
  end
77
84
 
78
85
  def description
@@ -137,7 +144,7 @@ module Fontist
137
144
 
138
145
  private
139
146
 
140
- def default_key
147
+ def key_from_path
141
148
  escaped = Regexp.escape(Fontist.formulas_path.to_s + "/")
142
149
  @path.sub(Regexp.new("^" + escaped), "").sub(/\.yml$/, "")
143
150
  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'")
@@ -9,3 +9,4 @@
9
9
  - Andika
10
10
  - Harmattan
11
11
  - Padauk
12
+ - STIX Two Math
@@ -4,15 +4,7 @@ module Fontist
4
4
  module SystemHelper
5
5
  class << self
6
6
  def run(command)
7
- Fontist.ui.say("Run `#{command}`") if Fontist.debug?
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