fontist 1.6.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
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