fontist 1.5.0 → 1.7.2

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