fontist 1.6.0 → 1.8.1

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.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/release.yml +38 -0
  3. data/.github/workflows/rspec.yml +58 -0
  4. data/README.md +109 -32
  5. data/{bin → exe}/fontist +0 -0
  6. data/fontist.gemspec +4 -2
  7. data/lib/fontist.rb +10 -3
  8. data/lib/fontist/cli.rb +63 -42
  9. data/lib/fontist/errors.rb +14 -11
  10. data/lib/fontist/font.rb +25 -27
  11. data/lib/fontist/font_installer.rb +114 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +89 -63
  14. data/lib/fontist/formula_paths.rb +43 -0
  15. data/lib/fontist/helpers.rb +7 -0
  16. data/lib/fontist/import/create_formula.rb +15 -30
  17. data/lib/fontist/import/files/collection_file.rb +6 -1
  18. data/lib/fontist/import/files/file_requirement.rb +17 -0
  19. data/lib/fontist/import/files/font_detector.rb +48 -0
  20. data/lib/fontist/import/formula_builder.rb +7 -3
  21. data/lib/fontist/import/google_check.rb +1 -1
  22. data/lib/fontist/import/google_import.rb +3 -4
  23. data/lib/fontist/import/otf/font_file.rb +17 -3
  24. data/lib/fontist/import/otfinfo_generate.rb +1 -1
  25. data/lib/fontist/import/recursive_extraction.rb +74 -13
  26. data/lib/fontist/index.rb +72 -0
  27. data/lib/fontist/index_formula.rb +30 -0
  28. data/lib/fontist/manifest/install.rb +6 -15
  29. data/lib/fontist/manifest/locations.rb +59 -4
  30. data/lib/fontist/system_font.rb +22 -49
  31. data/lib/fontist/system_index.rb +92 -0
  32. data/lib/fontist/utils.rb +1 -0
  33. data/lib/fontist/utils/dsl.rb +4 -0
  34. data/lib/fontist/utils/dsl/collection_font.rb +36 -0
  35. data/lib/fontist/utils/dsl/font.rb +2 -1
  36. data/lib/fontist/utils/exe_extractor.rb +6 -5
  37. data/lib/fontist/utils/zip_extractor.rb +20 -12
  38. data/lib/fontist/version.rb +1 -1
  39. metadata +45 -20
  40. data/.github/workflows/macosx.yml +0 -33
  41. data/.github/workflows/ubuntu.yml +0 -30
  42. data/.github/workflows/windows.yml +0 -32
  43. data/bin/check_google +0 -8
  44. data/bin/console +0 -11
  45. data/bin/convert_formulas +0 -8
  46. data/bin/generate_otfinfo +0 -8
  47. data/bin/import_google +0 -8
  48. data/bin/rspec +0 -29
  49. data/bin/setup +0 -7
  50. data/lib/fontist/font_formula.rb +0 -130
  51. data/lib/fontist/formula_template.rb +0 -108
  52. data/lib/fontist/formulas.rb +0 -56
  53. data/lib/fontist/manifest/common.rb +0 -60
  54. data/lib/fontist/registry.rb +0 -43
@@ -0,0 +1,43 @@
1
+ module Fontist
2
+ class FormulaPaths
3
+ attr_reader :font_paths
4
+
5
+ def initialize(font_paths)
6
+ @font_paths = font_paths
7
+ end
8
+
9
+ def find(font, style = nil)
10
+ styles = find_styles_by_formulas(font, style)
11
+ return if styles.empty?
12
+
13
+ fonts = styles.uniq { |s| s["font"] }.flat_map do |s|
14
+ paths = search_font_paths(s["font"])
15
+ paths.map do |path|
16
+ { full_name: s["full_name"],
17
+ path: path }
18
+ end
19
+ end
20
+
21
+ fonts.empty? ? nil : fonts
22
+ end
23
+
24
+ private
25
+
26
+ def find_styles_by_formulas(font, style)
27
+ if style
28
+ Formula.find_styles(font, style)
29
+ else
30
+ fonts = Formula.find_fonts(font)
31
+ return [] unless fonts
32
+
33
+ fonts.flat_map(&:styles)
34
+ end
35
+ end
36
+
37
+ def search_font_paths(filename)
38
+ font_paths.select do |path|
39
+ File.basename(path) == filename
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module Fontist
2
+ module Helpers
3
+ def self.parse_to_object(data)
4
+ JSON.parse(data.to_json, object_class: OpenStruct)
5
+ end
6
+ end
7
+ end
@@ -8,10 +8,6 @@ require_relative "formula_builder"
8
8
  module Fontist
9
9
  module Import
10
10
  class CreateFormula
11
- FONT_PATTERN = /(\.ttf|\.otf)/i.freeze
12
- FONT_COLLECTION_PATTERN = /\.ttc/i.freeze
13
- LICENSE_PATTERN = /(OFL\.txt|UFL\.txt|LICENSE\.txt|COPYING)$/i.freeze
14
-
15
11
  def initialize(url, options = {})
16
12
  @url = url
17
13
  @options = options
@@ -26,41 +22,30 @@ module Fontist
26
22
  def formula
27
23
  builder = FormulaBuilder.new
28
24
  builder.url = @url
29
- builder.archive = download(@url)
30
- builder.extractor = extractor(builder.archive)
25
+ builder.archive = archive
26
+ builder.extractor = extractor
31
27
  builder.options = @options
32
- builder.font_files = font_files(builder.extractor)
33
- builder.font_collection_files = font_collection_files(builder.extractor)
34
- builder.license_text = license_texts(builder.extractor).first
28
+ builder.font_files = extractor.font_files
29
+ builder.font_collection_files = extractor.font_collection_files
30
+ builder.license_text = extractor.license_text
35
31
  builder.formula
36
32
  end
37
33
 
38
- def download(url)
39
- return url if File.exist?(url)
40
-
41
- Fontist::Utils::Downloader.download(url, progress_bar: true).path
34
+ def extractor
35
+ @extractor ||=
36
+ RecursiveExtraction.new(archive,
37
+ subarchive: @options[:subarchive],
38
+ subdir: @options[:subdir])
42
39
  end
43
40
 
44
- def extractor(archive)
45
- RecursiveExtraction.new(archive, subarchive: @options[:subarchive])
41
+ def archive
42
+ @archive ||= download(@url)
46
43
  end
47
44
 
48
- def font_files(extractor)
49
- extractor.extract(FONT_PATTERN) do |path|
50
- Otf::FontFile.new(path)
51
- end
52
- end
53
-
54
- def font_collection_files(extractor)
55
- extractor.extract(FONT_COLLECTION_PATTERN) do |path|
56
- Files::CollectionFile.new(path)
57
- end
58
- end
45
+ def download(url)
46
+ return url if File.exist?(url)
59
47
 
60
- def license_texts(extractor)
61
- extractor.extract(LICENSE_PATTERN) do |path|
62
- File.read(path)
63
- end
48
+ Fontist::Utils::Downloader.download(url, progress_bar: true).path
64
49
  end
65
50
 
66
51
  def save(hash)
@@ -11,10 +11,15 @@ module Fontist
11
11
  def initialize(path)
12
12
  @path = path
13
13
  @fonts = read
14
+ @extension = "ttc"
14
15
  end
15
16
 
16
17
  def filename
17
- File.basename(@path)
18
+ File.basename(@path, ".*") + "." + @extension
19
+ end
20
+
21
+ def source_filename
22
+ File.basename(@path) unless filename == File.basename(@path)
18
23
  end
19
24
 
20
25
  private
@@ -0,0 +1,17 @@
1
+ module Fontist
2
+ module Import
3
+ module Files
4
+ class FileRequirement
5
+ def initialize
6
+ `file -v`
7
+ rescue Errno::ENOENT
8
+ abort "`file` is not available. (Or is PATH not setup properly?)"
9
+ end
10
+
11
+ def call(path)
12
+ Helpers::SystemHelper.run("file --brief '#{path}'")
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ require_relative "file_requirement"
2
+
3
+ module Fontist
4
+ module Import
5
+ module Files
6
+ class FontDetector
7
+ REQUIREMENTS = { file: FileRequirement.new }.freeze
8
+
9
+ FONT_LABELS = ["OpenType font data",
10
+ "TrueType Font data"].freeze
11
+
12
+ COLLECTION_LABEL = "TrueType font collection data".freeze
13
+
14
+ FONT_EXTENSIONS = {
15
+ "OpenType font data" => "otf",
16
+ "TrueType Font data" => "ttf",
17
+ "TrueType font collection data" => "ttc",
18
+ }.freeze
19
+
20
+ def self.detect(path)
21
+ brief = file_brief(path)
22
+
23
+ if brief.start_with?(*FONT_LABELS)
24
+ :font
25
+ elsif brief.start_with?(COLLECTION_LABEL)
26
+ :collection
27
+ else
28
+ :other
29
+ end
30
+ end
31
+
32
+ def self.standard_extension(path)
33
+ brief = file_brief(path)
34
+
35
+ FONT_EXTENSIONS.each do |label, extension|
36
+ return extension if brief.start_with?(label)
37
+ end
38
+
39
+ raise Errors::UnknownFontTypeError.new(path)
40
+ end
41
+
42
+ def self.file_brief(path)
43
+ REQUIREMENTS[:file].call(path)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -107,7 +107,10 @@ module Fontist
107
107
 
108
108
  collections = @font_collection_files.map do |file|
109
109
  fonts = fonts_from_files(file.fonts, :to_collection_style)
110
- { filename: file.filename, fonts: fonts }
110
+
111
+ { filename: file.filename,
112
+ source_filename: file.source_filename,
113
+ fonts: fonts }.compact
111
114
  end
112
115
 
113
116
  collections.sort_by do |x|
@@ -152,8 +155,9 @@ module Fontist
152
155
  return
153
156
  end
154
157
 
155
- Fontist.ui.error("WARN: ensure it's an open license, otherwise change \
156
- to 'requires_license_agreement'")
158
+ Fontist.ui.error("WARN: ensure it's an open license, otherwise " \
159
+ "change the 'open_license' attribute to " \
160
+ "'requires_license_agreement'")
157
161
 
158
162
  TextHelper.cleanup(@license_text)
159
163
  end
@@ -12,7 +12,7 @@ module Fontist
12
12
  private
13
13
 
14
14
  def fetch_formulas
15
- Formulas.fetch_formulas
15
+ Formula.update_formulas_repo
16
16
  end
17
17
 
18
18
  def new_fonts
@@ -156,10 +156,9 @@ module Fontist
156
156
  h.merge!(family_name: style.family_name,
157
157
  type: style.style,
158
158
  full_name: style.full_name)
159
- h.merge!(style.to_h.slice(:post_script_name,
160
- :version,
161
- :description,
162
- :copyright).compact)
159
+ h.merge!(style.to_h.select do |k, _|
160
+ %i(post_script_name version description copyright).include?(k)
161
+ end.compact)
163
162
  h.merge!(font: fix_variable_filename(style.filename))
164
163
  end
165
164
  end
@@ -1,5 +1,6 @@
1
1
  require_relative "../otfinfo/otfinfo_requirement"
2
2
  require_relative "../text_helper"
3
+ require_relative "../files/font_detector"
3
4
 
4
5
  module Fontist
5
6
  module Import
@@ -10,14 +11,19 @@ module Fontist
10
11
  }.freeze
11
12
 
12
13
  STYLE_ATTRIBUTES = %i[family_name type full_name post_script_name
13
- version description copyright font].freeze
14
- COLLECTION_ATTRIBUTES = STYLE_ATTRIBUTES.reject { |a| a == :font }
14
+ version description copyright font
15
+ source_font].freeze
16
+
17
+ COLLECTION_ATTRIBUTES = STYLE_ATTRIBUTES.reject do |a|
18
+ %i[font source_font].include?(a)
19
+ end
15
20
 
16
21
  attr_reader :path
17
22
 
18
23
  def initialize(path)
19
24
  @path = path
20
25
  @info = read
26
+ @extension = detect_extension
21
27
  end
22
28
 
23
29
  def to_style
@@ -55,7 +61,11 @@ module Fontist
55
61
  end
56
62
 
57
63
  def font
58
- File.basename(@path)
64
+ File.basename(@path, ".*") + "." + @extension
65
+ end
66
+
67
+ def source_font
68
+ File.basename(@path) unless font == File.basename(@path)
59
69
  end
60
70
 
61
71
  def copyright
@@ -85,6 +95,10 @@ module Fontist
85
95
  .map { |x| x.map { |y| Fontist::Import::TextHelper.cleanup(y) } }
86
96
  .to_h
87
97
  end
98
+
99
+ def detect_extension
100
+ Files::FontDetector.standard_extension(@path)
101
+ end
88
102
  end
89
103
  end
90
104
  end
@@ -25,7 +25,7 @@ module Fontist
25
25
  def font_paths(font)
26
26
  formula = Fontist::Formula.find(font)
27
27
  font_formula = Object.const_get(formula.installer)
28
- font_formula.fetch_font(nil, confirmation: "yes")
28
+ font_formula.install(confirmation: "yes")
29
29
  end
30
30
 
31
31
  def generate_styles(paths)
@@ -1,28 +1,40 @@
1
1
  require "find"
2
2
  require_relative "extractors"
3
+ require_relative "files/font_detector"
3
4
 
4
5
  module Fontist
5
6
  module Import
6
7
  class RecursiveExtraction
7
- BOTH_FONTS_PATTERN = "**/*.{ttf,otf,ttc}*".freeze
8
+ FONTS_PATTERN = "**/*.{ttf,otf,ttc}".freeze
8
9
  ARCHIVE_EXTENSIONS = %w[zip msi exe cab].freeze
10
+ LICENSE_PATTERN = /(OFL\.txt|UFL\.txt|LICENSE\.txt|COPYING)$/i.freeze
9
11
 
10
- def initialize(archive, subarchive: nil)
12
+ def initialize(archive, subarchive: nil, subdir: nil)
11
13
  @archive = archive
12
14
  @subarchive = subarchive
15
+ @subdir = subdir
13
16
  @operations = []
17
+ @font_files = []
18
+ @collection_files = []
14
19
  end
15
20
 
16
21
  def extension
17
22
  File.extname(filename(@archive)).sub(/^\./, "")
18
23
  end
19
24
 
20
- def extract(pattern)
21
- Array.new.tap do |results|
22
- Find.find(extracted_path) do |path| # rubocop:disable Style/CollectionMethods, Metrics/LineLength
23
- results << yield(path) if path.match(pattern)
24
- end
25
- end
25
+ def font_files
26
+ ensure_extracted
27
+ @font_files
28
+ end
29
+
30
+ def font_collection_files
31
+ ensure_extracted
32
+ @collection_files
33
+ end
34
+
35
+ def license_text
36
+ ensure_extracted
37
+ @license_text
26
38
  end
27
39
 
28
40
  def operations
@@ -50,7 +62,11 @@ module Fontist
50
62
 
51
63
  def extract_recursively(archive)
52
64
  path = operate_on_archive(archive)
53
- return path if fonts_exist?(path)
65
+ match_files(path)
66
+ if matched?
67
+ save_operation_subdir
68
+ return path
69
+ end
54
70
 
55
71
  next_archive = find_archive(path)
56
72
  extract_recursively(next_archive)
@@ -84,13 +100,58 @@ module Fontist
84
100
  @operations << { format: extractor.format }
85
101
  end
86
102
 
87
- def fonts_exist?(path)
88
- fonts = Dir.glob(File.join(path, BOTH_FONTS_PATTERN))
89
- !fonts.empty?
103
+ def match_files(dir_path)
104
+ Find.find(dir_path) do |entry_path| # rubocop:disable Style/CollectionMethods
105
+ match_license(entry_path)
106
+ match_font(entry_path) if font_directory?(entry_path, dir_path)
107
+ end
108
+ end
109
+
110
+ def match_license(path)
111
+ @license_text ||= File.read(path) if license?(path)
112
+ end
113
+
114
+ def license?(file)
115
+ file.match?(LICENSE_PATTERN)
116
+ end
117
+
118
+ def font_directory?(path, base_path)
119
+ return true unless @subdir
120
+
121
+ # https://bugs.ruby-lang.org/issues/10011
122
+ base_path = Pathname.new(base_path)
123
+
124
+ relative_path = Pathname.new(path).relative_path_from(base_path).to_s
125
+ dirname = File.dirname(relative_path)
126
+ normalized_pattern = @subdir.chomp("/")
127
+ File.fnmatch?(normalized_pattern, dirname)
128
+ end
129
+
130
+ def match_font(path)
131
+ case Files::FontDetector.detect(path)
132
+ when :font
133
+ @font_files << Otf::FontFile.new(path)
134
+ when :collection
135
+ @collection_files << Files::CollectionFile.new(path)
136
+ end
137
+ end
138
+
139
+ def matched?
140
+ [@font_files, @collection_files].any? do |files|
141
+ files.size.positive?
142
+ end
143
+ end
144
+
145
+ def save_operation_subdir
146
+ return unless @subdir
147
+
148
+ @operations.last[:options] ||= {}
149
+ @operations.last[:options][:fonts_sub_dir] = @subdir
90
150
  end
91
151
 
92
152
  def find_archive(path)
93
- paths = Dir.children(path).map { |file| File.join(path, file) }
153
+ children = Dir.entries(path) - [".", ".."] # ruby 2.4 compat
154
+ paths = children.map { |file| File.join(path, file) }
94
155
  by_subarchive(paths) || by_size(paths)
95
156
  end
96
157
 
@@ -0,0 +1,72 @@
1
+ require_relative "index_formula"
2
+
3
+ module Fontist
4
+ class Index
5
+ def self.from_yaml
6
+ unless File.exist?(Fontist.formula_index_path)
7
+ raise Errors::FormulaIndexNotFoundError.new("Please fetch index with `fontist update`.")
8
+ end
9
+
10
+ data = YAML.load_file(Fontist.formula_index_path)
11
+ new(data)
12
+ end
13
+
14
+ def self.rebuild
15
+ index = new
16
+ index.build
17
+ index.to_yaml
18
+ end
19
+
20
+ def initialize(data = {})
21
+ @index = {}
22
+
23
+ data.each_pair do |font, paths|
24
+ paths.each do |path|
25
+ add_index_formula(font, IndexFormula.new(path))
26
+ end
27
+ end
28
+ end
29
+
30
+ def build
31
+ Formula.all.each do |formula|
32
+ add_formula(formula)
33
+ end
34
+ end
35
+
36
+ def add_formula(formula)
37
+ formula.fonts.each do |font|
38
+ add_index_formula(font.name, formula.to_index_formula)
39
+ end
40
+ end
41
+
42
+ def add_index_formula(font_raw, index_formula)
43
+ font = normalize_font(font_raw)
44
+ @index[font] ||= []
45
+ @index[font] << index_formula unless @index[font].include?(index_formula)
46
+ end
47
+
48
+ def load_formulas(font)
49
+ index_formulas(font).map(&:to_full)
50
+ end
51
+
52
+ def to_yaml
53
+ File.write(Fontist.formula_index_path, YAML.dump(to_h))
54
+ end
55
+
56
+ def to_h
57
+ @index.map do |font, index_formulas|
58
+ [font, index_formulas.map(&:to_s)]
59
+ end.to_h
60
+ end
61
+
62
+ private
63
+
64
+ def index_formulas(font)
65
+ @index[normalize_font(font)] || []
66
+ end
67
+
68
+ def normalize_font(font)
69
+ font.downcase
70
+ end
71
+ end
72
+ end