fontist 1.4.0 → 1.7.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.
@@ -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