fontist 1.5.0 → 1.7.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.
@@ -23,10 +23,23 @@ module Fontist
23
23
  formula.font_collections.each do |collection|
24
24
  provides_font_collection do
25
25
  filename collection.filename
26
+ source_filename collection.source_filename
26
27
 
27
28
  collection.fonts.each do |font|
28
- styles = font.styles.map { |s| [s.type, s.full_name] }.to_h
29
- provides_font font.name, extract_styles_from_collection: styles
29
+ provides_font(
30
+ font.name,
31
+ extract_styles_from_collection: font.styles.map do |style|
32
+ {
33
+ family_name: style.family_name,
34
+ style: style.type,
35
+ full_name: style.full_name,
36
+ post_script_name: style.post_script_name,
37
+ version: style.version,
38
+ description: style.description,
39
+ copyright: style.copyright,
40
+ }
41
+ end
42
+ )
30
43
  end
31
44
  end
32
45
  end
@@ -45,6 +58,7 @@ module Fontist
45
58
  version: style.version,
46
59
  description: style.description,
47
60
  filename: style.font,
61
+ source_filename: style.source_font,
48
62
  copyright: style.copyright,
49
63
  }
50
64
  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)$/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)
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
@@ -89,7 +89,7 @@ module Fontist
89
89
  def download(url)
90
90
  Fontist::Utils::Downloader.download(url, progress_bar: true).path
91
91
  rescue Errors::InvalidResourceError
92
- Fontist.ui.say("WARN: a mirror is not found '#{url}'")
92
+ Fontist.ui.error("WARN: a mirror is not found '#{url}'")
93
93
  nil
94
94
  end
95
95
 
@@ -98,7 +98,7 @@ module Fontist
98
98
  return output.first if output.size == 1
99
99
 
100
100
  checksums = output.join(", ")
101
- Fontist.ui.say("WARN: SHA256 differs (#{checksums})")
101
+ Fontist.ui.error("WARN: SHA256 differs (#{checksums})")
102
102
  output
103
103
  end
104
104
 
@@ -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|
@@ -147,7 +150,14 @@ module Fontist
147
150
  end
148
151
 
149
152
  def open_license
150
- return unless @license_text
153
+ unless @license_text
154
+ Fontist.ui.error("WARN: please add license manually")
155
+ return
156
+ end
157
+
158
+ Fontist.ui.error("WARN: ensure it's an open license, otherwise " \
159
+ "change the 'open_license' attribute to " \
160
+ "'requires_license_agreement'")
151
161
 
152
162
  TextHelper.cleanup(@license_text)
153
163
  end
@@ -4,3 +4,5 @@
4
4
  - Overpass Mono
5
5
  - Lato
6
6
  - Open Sans
7
+ - Work Sans
8
+ - Fira Code
@@ -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
@@ -77,12 +87,18 @@ module Fontist
77
87
  def read
78
88
  text = REQUIREMENTS[:otfinfo].call(@path)
79
89
 
80
- text.split("\n")
90
+ text
91
+ .encode("UTF-8", invalid: :replace, replace: "")
92
+ .split("\n")
81
93
  .select { |x| x.include?(":") }
82
94
  .map { |x| x.split(":", 2) }
83
95
  .map { |x| x.map { |y| Fontist::Import::TextHelper.cleanup(y) } }
84
96
  .to_h
85
97
  end
98
+
99
+ def detect_extension
100
+ Files::FontDetector.standard_extension(@path)
101
+ end
86
102
  end
87
103
  end
88
104
  end
@@ -1,27 +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)
12
+ def initialize(archive, subarchive: nil, subdir: nil)
11
13
  @archive = archive
14
+ @subarchive = subarchive
15
+ @subdir = subdir
12
16
  @operations = []
17
+ @font_files = []
18
+ @collection_files = []
13
19
  end
14
20
 
15
21
  def extension
16
22
  File.extname(filename(@archive)).sub(/^\./, "")
17
23
  end
18
24
 
19
- def extract(pattern)
20
- Array.new.tap do |results|
21
- Find.find(extracted_path) do |path| # rubocop:disable Style/CollectionMethods, Metrics/LineLength
22
- results << yield(path) if path.match(pattern)
23
- end
24
- 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
25
38
  end
26
39
 
27
40
  def operations
@@ -49,7 +62,11 @@ module Fontist
49
62
 
50
63
  def extract_recursively(archive)
51
64
  path = operate_on_archive(archive)
52
- return path if fonts_exist?(path)
65
+ match_files(path)
66
+ if matched?
67
+ save_operation_subdir
68
+ return path
69
+ end
53
70
 
54
71
  next_archive = find_archive(path)
55
72
  extract_recursively(next_archive)
@@ -57,6 +74,8 @@ module Fontist
57
74
 
58
75
  def operate_on_archive(archive)
59
76
  extractor = choose_extractor(archive)
77
+ Fontist.ui.say("Extracting #{archive} with #{extractor.class.name}")
78
+
60
79
  save_operation(extractor)
61
80
  extractor.extract
62
81
  end
@@ -81,15 +100,80 @@ module Fontist
81
100
  @operations << { format: extractor.format }
82
101
  end
83
102
 
84
- def fonts_exist?(path)
85
- fonts = Dir.glob(File.join(path, BOTH_FONTS_PATTERN))
86
- !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
+ relative_path = Pathname.new(path).relative_path_from(base_path).to_s
122
+ dirname = File.dirname(relative_path)
123
+ normalized_pattern = @subdir.chomp("/")
124
+ File.fnmatch?(normalized_pattern, dirname)
125
+ end
126
+
127
+ def match_font(path)
128
+ case Files::FontDetector.detect(path)
129
+ when :font
130
+ @font_files << Otf::FontFile.new(path)
131
+ when :collection
132
+ @collection_files << Files::CollectionFile.new(path)
133
+ end
134
+ end
135
+
136
+ def matched?
137
+ [@font_files, @collection_files].any? do |files|
138
+ files.size.positive?
139
+ end
140
+ end
141
+
142
+ def save_operation_subdir
143
+ return unless @subdir
144
+
145
+ @operations.last[:options] ||= {}
146
+ @operations.last[:options][:fonts_sub_dir] = @subdir
87
147
  end
88
148
 
89
149
  def find_archive(path)
90
- Dir.children(path)
91
- .map { |file_name| File.join(path, file_name) }
92
- .max_by { |file_path| [file_type(file_path), File.size(file_path)] }
150
+ paths = Dir.children(path).map { |file| File.join(path, file) }
151
+ by_subarchive(paths) || by_size(paths)
152
+ end
153
+
154
+ def by_subarchive(paths)
155
+ return unless @subarchive
156
+
157
+ path_found = paths.detect do |path|
158
+ @subarchive == File.basename(path)
159
+ end
160
+
161
+ return unless path_found
162
+
163
+ save_operation_subarchive(path_found)
164
+
165
+ path_found
166
+ end
167
+
168
+ def save_operation_subarchive(path)
169
+ @operations.last[:options] ||= {}
170
+ @operations.last[:options][:subarchive] = File.basename(path)
171
+ end
172
+
173
+ def by_size(paths)
174
+ paths.max_by do |path|
175
+ [file_type(path), File.size(path)]
176
+ end
93
177
  end
94
178
 
95
179
  def file_type(file_path)