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.
@@ -1,8 +1,8 @@
1
- require_relative "common"
1
+ require_relative "locations"
2
2
 
3
3
  module Fontist
4
4
  module Manifest
5
- class Install < Common
5
+ class Install < Locations
6
6
  def initialize(manifest, confirmation: "no")
7
7
  @manifest = manifest
8
8
  @confirmation = confirmation
@@ -15,15 +15,11 @@ module Fontist
15
15
  private
16
16
 
17
17
  def file_paths(font, style)
18
- paths = find_installed_font(font, style)
19
- return paths unless paths.empty?
18
+ paths = super
19
+ return paths unless paths["paths"].empty?
20
20
 
21
21
  install_font(font)
22
- find_installed_font(font, style)
23
- end
24
-
25
- def find_installed_font(font, style)
26
- Fontist::SystemFont.find_with_style(font, style)
22
+ super
27
23
  end
28
24
 
29
25
  def install_font(font)
@@ -1,12 +1,59 @@
1
- require_relative "common"
2
-
3
1
  module Fontist
4
2
  module Manifest
5
- class Locations < Common
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
+
6
16
  private
7
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
+
8
55
  def file_paths(font, style)
9
- Fontist::SystemFont.find_with_style(font, style)
56
+ Fontist::SystemFont.find_with_name(font, style).transform_keys(&:to_s)
10
57
  end
11
58
  end
12
59
  end
@@ -1,3 +1,5 @@
1
+ require_relative "system_index"
2
+
1
3
  module Fontist
2
4
  class SystemFont
3
5
  def initialize(font:, style: nil, sources: nil)
@@ -10,22 +12,23 @@ module Fontist
10
12
  new(font: font, sources: sources).find
11
13
  end
12
14
 
13
- def self.find_with_style(font, style)
14
- new(font: font, style: style).find_with_style
15
+ def self.find_with_name(font, style)
16
+ new(font: font, style: style).find_with_name
15
17
  end
16
18
 
17
19
  def find
18
- paths = grep_font_paths(font)
19
- paths = lookup_using_font_name || [] if paths.empty?
20
+ styles = find_styles
21
+ return unless styles
20
22
 
21
- paths.empty? ? nil : paths
23
+ styles.map { |x| x[:path] }
22
24
  end
23
25
 
24
- def find_with_style
25
- paths = lookup_using_font_and_style
26
- return paths unless paths.empty?
26
+ def find_with_name
27
+ styles = find_styles
28
+ return { full_name: nil, paths: [] } unless styles
27
29
 
28
- grep_font_paths(font, style)
30
+ { full_name: styles.first[:full_name],
31
+ paths: styles.map { |x| x[:path] } }
29
32
  end
30
33
 
31
34
  private
@@ -42,25 +45,6 @@ module Fontist
42
45
  end
43
46
  end
44
47
 
45
- def grep_font_paths(font, style = nil)
46
- pattern = prepare_pattern(font, style)
47
-
48
- paths = font_paths.map { |path| [File.basename(path), path] }.to_h
49
- files = paths.keys
50
- matched = files.grep(pattern)
51
- paths.values_at(*matched).compact
52
- end
53
-
54
- def prepare_pattern(font, style = nil)
55
- style = nil if style&.casecmp?("regular")
56
-
57
- s = [font, style].compact.map { |x| Regexp.quote(x) }
58
- .join(".*")
59
- .gsub("\\ ", "\s?") # space independent
60
-
61
- Regexp.new(s, Regexp::IGNORECASE)
62
- end
63
-
64
48
  def font_paths
65
49
  @font_paths ||= Dir.glob((
66
50
  user_sources +
@@ -69,11 +53,6 @@ module Fontist
69
53
  ).flatten.uniq)
70
54
  end
71
55
 
72
- def lookup_using_font_name
73
- font_names = map_name_to_valid_font_names || []
74
- font_paths.grep(/#{font_names.join("|")}/i) unless font_names.empty?
75
- end
76
-
77
56
  def fontist_fonts_path
78
57
  @fontist_fonts_path ||= Fontist.fonts_path
79
58
  end
@@ -82,24 +61,45 @@ module Fontist
82
61
  Fontist::Utils::System.user_os
83
62
  end
84
63
 
85
- def map_name_to_valid_font_names
86
- fonts = Formula.find_fonts(font)
87
- fonts.map { |font| font.styles.map(&:font) }.flatten if fonts
88
- end
89
-
90
64
  def system_path_file
91
65
  File.open(Fontist.system_file_path)
92
66
  end
93
67
 
94
68
  def default_sources
95
- @default_sources ||= YAML.load(system_path_file)["system"][user_os.to_s]
69
+ @default_sources ||= YAML.safe_load(system_path_file)["system"][user_os.to_s]
70
+ end
71
+
72
+ def find_styles
73
+ find_by_index || find_by_formulas
96
74
  end
97
75
 
98
- def lookup_using_font_and_style
99
- styles = Formula.find_styles(font, style)
100
- filenames = styles.map(&:font)
101
- filenames.flat_map do |filename|
102
- search_font_paths(filename)
76
+ def find_by_index
77
+ SystemIndex.new(font_paths).find(font, style)
78
+ end
79
+
80
+ def find_by_formulas
81
+ styles = find_styles_by_formulas(font, style)
82
+ return if styles.empty?
83
+
84
+ fonts = styles.uniq { |s| s["font"] }.flat_map do |s|
85
+ paths = search_font_paths(s["font"])
86
+ paths.map do |path|
87
+ { full_name: s["full_name"],
88
+ path: path }
89
+ end
90
+ end
91
+
92
+ fonts.empty? ? nil : fonts
93
+ end
94
+
95
+ def find_styles_by_formulas(font, style)
96
+ if style
97
+ Formula.find_styles(font, style)
98
+ else
99
+ fonts = Formula.find_fonts(font)
100
+ return [] unless fonts
101
+
102
+ fonts.flat_map(&:styles)
103
103
  end
104
104
  end
105
105
 
@@ -0,0 +1,92 @@
1
+ require "ttfunk"
2
+
3
+ module Fontist
4
+ class SystemIndex
5
+ attr_reader :font_paths
6
+
7
+ def initialize(font_paths)
8
+ @font_paths = font_paths
9
+ end
10
+
11
+ def find(font, style)
12
+ fonts = system_index.select do |file|
13
+ file[:family_name].casecmp?(font) &&
14
+ (style.nil? || file[:type].casecmp?(style))
15
+ end
16
+
17
+ fonts.empty? ? nil : fonts
18
+ end
19
+
20
+ private
21
+
22
+ def system_index
23
+ @system_index ||= build_system_index
24
+ end
25
+
26
+ def build_system_index
27
+ previous_index = load_system_index
28
+ updated_index = detect_paths(font_paths, previous_index)
29
+ updated_index.tap do |index|
30
+ save_index(index)
31
+ end
32
+ end
33
+
34
+ def load_system_index
35
+ index = File.exist?(Fontist.system_index_path) ? YAML.load_file(Fontist.system_index_path) : []
36
+ index.group_by { |x| x[:path] }
37
+ end
38
+
39
+ def detect_paths(paths, indexed)
40
+ paths.flat_map do |path|
41
+ next indexed[path] if indexed[path]
42
+
43
+ detect_fonts(path)
44
+ end
45
+ end
46
+
47
+ def detect_fonts(path)
48
+ case File.extname(path).delete_prefix(".").downcase
49
+ when "ttf", "otf"
50
+ detect_file_font(path)
51
+ when "ttc"
52
+ detect_collection_fonts(path)
53
+ else
54
+ raise Errors::UnknownFontTypeError.new(path)
55
+ end
56
+ end
57
+
58
+ def detect_file_font(path)
59
+ file = TTFunk::File.open(path)
60
+ parse_font(file, path)
61
+ end
62
+
63
+ def detect_collection_fonts(path)
64
+ TTFunk::Collection.open(path) do |collection|
65
+ collection.map do |file|
66
+ parse_font(file, path)
67
+ end
68
+ end
69
+ end
70
+
71
+ def parse_font(file, path)
72
+ x = file.name
73
+
74
+ {
75
+ path: path,
76
+ full_name: parse_text(x.font_name.first),
77
+ family_name: parse_text(x.preferred_family.first || x.font_family.first),
78
+ type: parse_text(x.preferred_subfamily.first || x.font_subfamily.first),
79
+ }
80
+ end
81
+
82
+ def parse_text(text)
83
+ text.gsub(/[^[:print:]]/, "").to_s
84
+ end
85
+
86
+ def save_index(index)
87
+ dir = File.dirname(Fontist.system_index_path)
88
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
89
+ File.write(Fontist.system_index_path, YAML.dump(index))
90
+ end
91
+ end
92
+ end
@@ -2,6 +2,7 @@ require "fontist/utils/ui"
2
2
  require "fontist/utils/system"
3
3
  require "fontist/utils/dsl"
4
4
  require "fontist/utils/dsl/font"
5
+ require "fontist/utils/dsl/collection_font"
5
6
  require "fontist/utils/downloader"
6
7
  require "fontist/utils/zip_extractor"
7
8
  require "fontist/utils/exe_extractor"
@@ -1,14 +1,18 @@
1
1
  module Fontist
2
2
  module Utils
3
3
  class Cache
4
- def fetch(key)
4
+ def fetch(key, bar: nil)
5
5
  map = load_cache
6
- return downloaded_path(map[key]) if cache_exist?(map[key])
6
+ if cache_exist?(map[key])
7
+ print_bar(bar, map[key]) if bar
8
+
9
+ return downloaded_file(map[key])
10
+ end
7
11
 
8
12
  generated_file = yield
9
- path = save_cache(generated_file, key, map)
13
+ path = save_cache(generated_file, key)
10
14
 
11
- downloaded_path(path)
15
+ downloaded_file(path)
12
16
  end
13
17
 
14
18
  private
@@ -21,18 +25,33 @@ module Fontist
21
25
  cache_map_path.exist? ? YAML.load_file(cache_map_path) : {}
22
26
  end
23
27
 
24
- def downloaded_path(path)
25
- File.new(Fontist.downloads_path.join(path))
28
+ def downloaded_file(path)
29
+ File.new(downloaded_path(path))
26
30
  end
27
31
 
28
32
  def cache_exist?(path)
29
- path && File.exist?(Fontist.downloads_path.join(path))
33
+ path && File.exist?(downloaded_path(path))
34
+ end
35
+
36
+ def downloaded_path(path)
37
+ Fontist.downloads_path.join(path)
30
38
  end
31
39
 
32
- def save_cache(generated_file, key, map)
40
+ def print_bar(bar, path)
41
+ File.size(downloaded_path(path)).tap do |size|
42
+ bar.total = size
43
+ bar.increment(size)
44
+ bar.finish("cache")
45
+ end
46
+ end
47
+
48
+ def save_cache(generated_file, key)
33
49
  path = move_to_downloads(generated_file)
50
+
51
+ map = load_cache
34
52
  map[key] = path
35
53
  File.write(cache_map_path, YAML.dump(map))
54
+
36
55
  path
37
56
  end
38
57
 
@@ -7,13 +7,15 @@ module Fontist
7
7
  # TODO: If the first mirror fails, try the second one
8
8
  @file = file
9
9
  @sha = [sha].flatten.compact
10
- @progress_bar = set_progress_bar(progress_bar)
11
10
  @file_size = (file_size || default_file_size).to_i
11
+ @progress_bar = set_progress_bar(progress_bar)
12
12
  @cache = Cache.new
13
13
  end
14
14
 
15
15
  def download
16
- file = @cache.fetch(@file) { download_file }
16
+ file = @cache.fetch(@file, bar: @progress_bar) do
17
+ download_file
18
+ end
17
19
 
18
20
  if !sha.empty? && !sha.include?(Digest::SHA256.file(file).to_s)
19
21
  raise(Fontist::Errors::TamperedFileError.new(
@@ -46,25 +48,27 @@ module Fontist
46
48
  end
47
49
 
48
50
  def set_progress_bar(progress_bar)
49
- ENV.fetch("TEST_ENV", "") === "CI" ? false : progress_bar
51
+ if ENV.fetch("TEST_ENV", "") === "CI" || progress_bar
52
+ ProgressBar.new(@file_size)
53
+ else
54
+ NullProgressBar.new
55
+ end
50
56
  end
51
57
 
52
58
  def download_file
53
- bar = ProgressBar.new(file_size / byte_to_megabyte)
54
-
55
59
  file = Down.download(
56
60
  @file,
57
61
  open_timeout: 10,
58
62
  read_timeout: 10,
59
63
  content_length_proc: ->(content_length) {
60
- bar.total = content_length / byte_to_megabyte if content_length
64
+ @progress_bar.total = content_length if content_length
61
65
  },
62
66
  progress_proc: -> (progress) {
63
- bar.increment(progress / byte_to_megabyte) if @progress_bar === true
67
+ @progress_bar.increment(progress)
64
68
  }
65
69
  )
66
70
 
67
- puts if @progress_bar === true
71
+ @progress_bar.finish
68
72
 
69
73
  file
70
74
  rescue Down::NotFound
@@ -72,6 +76,20 @@ module Fontist
72
76
  end
73
77
  end
74
78
 
79
+ class NullProgressBar
80
+ def total=(_)
81
+ # do nothing
82
+ end
83
+
84
+ def increment(_)
85
+ # do nothing
86
+ end
87
+
88
+ def finish(_ = nil)
89
+ # do nothing
90
+ end
91
+ end
92
+
75
93
  class ProgressBar
76
94
  def initialize(total)
77
95
  @counter = 1
@@ -83,9 +101,35 @@ module Fontist
83
101
  end
84
102
 
85
103
  def increment(progress)
86
- complete = sprintf("%#.2f%%", ((@counter.to_f / @total.to_f) * 100))
87
- print "\r\e[0KDownloads: #{@counter}MB/#{@total}MB (#{complete})"
88
104
  @counter = progress
105
+ Fontist.ui.print "\r\e[0KDownloads: #{counter_mb}MB/#{total_mb}MB " \
106
+ "(#{completeness})"
107
+ end
108
+
109
+ def finish(message = nil)
110
+ if message
111
+ Fontist.ui.print " (#{message})\n"
112
+ else
113
+ Fontist.ui.print "\n"
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def completeness
120
+ sprintf("%#.2f%%", (@counter.fdiv(@total) * 100)) # rubocop:disable Style/FormatStringToken, Metrics/LineLength
121
+ end
122
+
123
+ def counter_mb
124
+ @counter / byte_to_megabyte
125
+ end
126
+
127
+ def total_mb
128
+ @total / byte_to_megabyte
129
+ end
130
+
131
+ def byte_to_megabyte
132
+ @byte_to_megabyte ||= 1024 * 1024
89
133
  end
90
134
  end
91
135
  end