fontist 1.5.1 → 1.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
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,12 +25,24 @@ 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)
38
+ end
39
+
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
30
46
  end
31
47
 
32
48
  def save_cache(generated_file, key)
@@ -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
@@ -47,6 +47,10 @@ module Fontist
47
47
  instance.temp_resource.merge!(filename: name)
48
48
  end
49
49
 
50
+ def source_filename(name)
51
+ instance.temp_resource.merge!(source_filename: name)
52
+ end
53
+
50
54
  def provides_font(font, options = {})
51
55
  font_styles = instance.extract_font_styles(options)
52
56
  instance.font_list.push(name: font, styles: font_styles)
@@ -0,0 +1,36 @@
1
+ module Fontist
2
+ module Utils
3
+ module Dsl
4
+ class CollectionFont
5
+ REQUIRED_ATTRIBUTES = %i[style].freeze
6
+
7
+ attr_reader :attributes
8
+
9
+ def initialize(attributes)
10
+ REQUIRED_ATTRIBUTES.each do |required_attribute|
11
+ unless attributes[required_attribute]
12
+ raise(Fontist::Errors::MissingAttributeError.new(
13
+ "Missing attribute: #{required_attribute}"
14
+ ))
15
+ end
16
+ end
17
+
18
+ self.attributes = attributes
19
+ end
20
+
21
+ def attributes=(attrs)
22
+ @attributes = { family_name: attrs[:family_name],
23
+ type: attrs[:style],
24
+ collection: attrs[:full_name],
25
+ full_name: attrs[:full_name],
26
+ post_script_name: attrs[:post_script_name],
27
+ version: attrs[:version],
28
+ description: attrs[:description],
29
+ copyright: attrs[:copyright],
30
+ font: attrs[:filename],
31
+ source_font: attrs[:source_filename] }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -29,7 +29,8 @@ module Fontist
29
29
  version: attrs[:version],
30
30
  description: attrs[:description],
31
31
  copyright: attrs[:copyright],
32
- font: attrs[:filename] }
32
+ font: attrs[:filename],
33
+ source_font: attrs[:source_filename] }
33
34
  end
34
35
  end
35
36
  end