fontist 1.4.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,3 +4,5 @@
4
4
  - Overpass Mono
5
5
  - Lato
6
6
  - Open Sans
7
+ - Work Sans
8
+ - Fira Code
@@ -4,7 +4,10 @@ module Fontist
4
4
  module SystemHelper
5
5
  class << self
6
6
  def run(command)
7
- puts "Run `#{command}`" unless ENV.fetch("TEST_ENV", "") === "CI"
7
+ unless ENV.fetch("TEST_ENV", "") === "CI"
8
+ Fontist.ui.say("Run `#{command}`")
9
+ end
10
+
8
11
  result = `#{command}`
9
12
  unless $CHILD_STATUS.to_i.zero?
10
13
  raise Errors::BinaryCallError,
@@ -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)
@@ -0,0 +1,2 @@
1
+ require_relative "manifest/locations"
2
+ require_relative "manifest/install"
@@ -0,0 +1,32 @@
1
+ require_relative "locations"
2
+
3
+ module Fontist
4
+ module Manifest
5
+ class Install < Locations
6
+ def initialize(manifest, confirmation: "no")
7
+ @manifest = manifest
8
+ @confirmation = confirmation
9
+ end
10
+
11
+ def self.call(manifest, confirmation: "no")
12
+ new(manifest, confirmation: confirmation).call
13
+ end
14
+
15
+ private
16
+
17
+ def file_paths(font, style)
18
+ paths = super
19
+ return paths unless paths["paths"].empty?
20
+
21
+ install_font(font)
22
+ super
23
+ end
24
+
25
+ def install_font(font)
26
+ Fontist::Font.try_install(font, confirmation: @confirmation)
27
+ rescue Fontist::Errors::LicensingError
28
+ [] # try to install other fonts
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ module Fontist
2
+ module Manifest
3
+ class Locations
4
+ def initialize(manifest)
5
+ @manifest = manifest
6
+ end
7
+
8
+ def self.call(manifest)
9
+ new(manifest).call
10
+ end
11
+
12
+ def call
13
+ font_names.zip(font_paths).to_h
14
+ end
15
+
16
+ private
17
+
18
+ def font_names
19
+ fonts.keys
20
+ end
21
+
22
+ def fonts
23
+ @fonts ||= begin
24
+ unless File.exist?(@manifest)
25
+ raise Fontist::Errors::ManifestCouldNotBeFoundError
26
+ end
27
+
28
+ fonts = YAML.load_file(@manifest)
29
+ unless fonts.is_a?(Hash)
30
+ raise Fontist::Errors::ManifestCouldNotBeReadError
31
+ end
32
+
33
+ fonts
34
+ end
35
+ end
36
+
37
+ def font_paths
38
+ fonts.map do |font, styles|
39
+ styles_to_ary = [styles].flatten
40
+ style_paths_map(font, styles_to_ary)
41
+ end
42
+ end
43
+
44
+ def style_paths_map(font, names)
45
+ paths = style_paths(font, names)
46
+ names.zip(paths).to_h
47
+ end
48
+
49
+ def style_paths(font, names)
50
+ names.map do |style|
51
+ file_paths(font, style)
52
+ end
53
+ end
54
+
55
+ def file_paths(font, style)
56
+ Fontist::SystemFont.find_with_name(font, style).transform_keys(&:to_s)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,7 +1,10 @@
1
+ require_relative "system_index"
2
+
1
3
  module Fontist
2
4
  class SystemFont
3
- def initialize(font:, sources: nil)
5
+ def initialize(font:, style: nil, sources: nil)
4
6
  @font = font
7
+ @style = style
5
8
  @user_sources = sources || []
6
9
  end
7
10
 
@@ -9,6 +12,10 @@ module Fontist
9
12
  new(font: font, sources: sources).find
10
13
  end
11
14
 
15
+ def self.find_with_name(font, style)
16
+ new(font: font, style: style).find_with_name
17
+ end
18
+
12
19
  def find
13
20
  paths = grep_font_paths(font)
14
21
  paths = lookup_using_font_name || [] if paths.empty?
@@ -16,9 +23,17 @@ module Fontist
16
23
  paths.empty? ? nil : paths
17
24
  end
18
25
 
26
+ def find_with_name
27
+ styles = find_styles
28
+ return { full_name: nil, paths: [] } unless styles
29
+
30
+ { full_name: styles.first[:full_name],
31
+ paths: styles.map { |x| x[:path] } }
32
+ end
33
+
19
34
  private
20
35
 
21
- attr_reader :font, :user_sources
36
+ attr_reader :font, :style, :user_sources
22
37
 
23
38
  def normalize_default_paths
24
39
  @normalize_default_paths ||= default_sources["paths"].map do |path|
@@ -30,15 +45,27 @@ module Fontist
30
45
  end
31
46
  end
32
47
 
33
- def grep_font_paths(font)
48
+ def grep_font_paths(font, style = nil)
49
+ pattern = prepare_pattern(font, style)
50
+
34
51
  paths = font_paths.map { |path| [File.basename(path), path] }.to_h
35
52
  files = paths.keys
36
- matched = files.grep(/#{font}/i)
53
+ matched = files.grep(pattern)
37
54
  paths.values_at(*matched).compact
38
55
  end
39
56
 
57
+ def prepare_pattern(font, style = nil)
58
+ style = nil if style&.casecmp?("regular")
59
+
60
+ s = [font, style].compact.map { |x| Regexp.quote(x) }
61
+ .join(".*")
62
+ .gsub("\\ ", "\s?") # space independent
63
+
64
+ Regexp.new(s, Regexp::IGNORECASE)
65
+ end
66
+
40
67
  def font_paths
41
- Dir.glob((
68
+ @font_paths ||= Dir.glob((
42
69
  user_sources +
43
70
  normalize_default_paths +
44
71
  [fontist_fonts_path.join("**")]
@@ -54,7 +81,6 @@ module Fontist
54
81
  @fontist_fonts_path ||= Fontist.fonts_path
55
82
  end
56
83
 
57
-
58
84
  def user_os
59
85
  Fontist::Utils::System.user_os
60
86
  end
@@ -71,5 +97,45 @@ module Fontist
71
97
  def default_sources
72
98
  @default_sources ||= YAML.load(system_path_file)["system"][user_os.to_s]
73
99
  end
100
+
101
+ def find_styles
102
+ find_by_index || find_by_formulas
103
+ end
104
+
105
+ def find_by_index
106
+ SystemIndex.new(font_paths).find(font, style)
107
+ end
108
+
109
+ def find_by_formulas
110
+ styles = find_styles_by_formulas(font, style)
111
+ return if styles.empty?
112
+
113
+ fonts = styles.uniq { |s| s["font"] }.flat_map do |s|
114
+ paths = search_font_paths(s["font"])
115
+ paths.map do |path|
116
+ { full_name: s["full_name"],
117
+ path: path }
118
+ end
119
+ end
120
+
121
+ fonts.empty? ? nil : fonts
122
+ end
123
+
124
+ def find_styles_by_formulas(font, style)
125
+ if style
126
+ Formula.find_styles(font, style)
127
+ else
128
+ fonts = Formula.find_fonts(font)
129
+ return [] unless fonts
130
+
131
+ fonts.flat_map(&:styles)
132
+ end
133
+ end
134
+
135
+ def search_font_paths(filename)
136
+ font_paths.select do |path|
137
+ File.basename(path) == filename
138
+ end
139
+ end
74
140
  end
75
141
  end