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.
@@ -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
@@ -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
@@ -5,17 +5,19 @@ module Fontist
5
5
  download = @downloaded === true ? false : download
6
6
 
7
7
  exe_file = download_file(exe_file).path if download
8
+
9
+ Fontist.ui.say(%(Installing font "#{key}".))
8
10
  cab_file = decompressor.search(exe_file)
9
- cabbed_fonts = grep_fonts(cab_file.files, font_ext) || []
11
+ cabbed_fonts = grep_fonts(cab_file.files) || []
10
12
  fonts_paths = extract_cabbed_fonts_to_assets(cabbed_fonts)
11
13
 
12
14
  block_given? ? yield(fonts_paths) : fonts_paths
13
15
  end
14
16
 
15
- def exe_extract(source)
17
+ def exe_extract(source, subarchive: nil)
16
18
  cab_file = decompressor.search(download_file(source).path)
17
- fonts_paths = build_cab_file_hash(cab_file.files)
18
- block_given? ? yield(fonts_paths) : fonts_paths
19
+ subarchive_path = extract_subarchive(cab_file.files, subarchive)
20
+ block_given? ? yield(subarchive_path) : subarchive_path
19
21
  end
20
22
 
21
23
  private
@@ -27,10 +29,10 @@ module Fontist
27
29
  )
28
30
  end
29
31
 
30
- def grep_fonts(file, font_ext)
32
+ def grep_fonts(file)
31
33
  Array.new.tap do |fonts|
32
34
  while file
33
- fonts.push(file) if file.filename.match(font_ext)
35
+ fonts.push(file) if font_file?(file.filename)
34
36
  file = file.next
35
37
  end
36
38
  end
@@ -39,7 +41,8 @@ module Fontist
39
41
  def extract_cabbed_fonts_to_assets(cabbed_fonts)
40
42
  Array.new.tap do |fonts|
41
43
  cabbed_fonts.each do |font|
42
- font_path = fonts_path.join(font.filename).to_s
44
+ target_filename = target_filename(font.filename)
45
+ font_path = fonts_path.join(target_filename).to_s
43
46
  decompressor.extract(font, font_path)
44
47
 
45
48
  fonts.push(font_path)
@@ -47,10 +50,11 @@ module Fontist
47
50
  end
48
51
  end
49
52
 
50
- def build_cab_file_hash(file)
53
+ def extract_subarchive(file, subarchive = nil)
51
54
  while file
52
55
  filename = file.filename
53
- if filename.include?("cab") || filename.include?("msi")
56
+
57
+ if subarchive_found?(filename, subarchive)
54
58
  file_path = File.join(Dir.mktmpdir, filename)
55
59
  decompressor.extract(file, file_path)
56
60
 
@@ -60,6 +64,12 @@ module Fontist
60
64
  file = file.next
61
65
  end
62
66
  end
67
+
68
+ def subarchive_found?(filename, subarchive)
69
+ return subarchive == filename if subarchive
70
+
71
+ filename.include?("cab") || filename.include?("msi")
72
+ end
63
73
  end
64
74
  end
65
75
  end
@@ -18,6 +18,10 @@ module Fontist
18
18
  def self.ask(message, options = {})
19
19
  new.ask(message, options)
20
20
  end
21
+
22
+ def self.print(message)
23
+ super
24
+ end
21
25
  end
22
26
  end
23
27
  end
@@ -4,40 +4,49 @@ require "pathname"
4
4
  module Fontist
5
5
  module Utils
6
6
  module ZipExtractor
7
- # fonts_sub_dir is unused now, but formulas have this option
8
- # rubocop:disable Lint/UnusedMethodArgument
9
- def zip_extract(resource, download: true, fonts_sub_dir: "")
7
+ def zip_extract(resource, download: true, fonts_sub_dir: nil)
10
8
  zip_file = download_file(resource) if download
11
9
  zip_file ||= resource.urls.first
12
10
 
13
- fonts_paths = unzip_fonts(zip_file)
11
+ Fontist.ui.say(%(Installing font "#{key}".))
12
+ fonts_paths = unzip_fonts(zip_file, fonts_sub_dir)
14
13
  block_given? ? yield(fonts_paths) : fonts_paths
15
14
  end
16
- # rubocop:enable Lint/UnusedMethodArgument
17
15
 
18
16
  alias_method :unzip, :zip_extract
19
17
 
20
18
  private
21
19
 
22
20
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
23
- def unzip_fonts(file)
21
+ def unzip_fonts(file, subdir)
24
22
  Zip.on_exists_proc = true
25
23
 
26
24
  Array.new.tap do |fonts|
27
25
  Zip::File.open(file) do |zip_file|
28
- zip_file.glob("**/*.{ttf,ttc,otf}").each do |entry|
26
+ zip_file.each do |entry|
29
27
  if entry.name
30
- filename = Pathname.new(entry.name).basename
31
- font_path = fonts_path.join(filename.to_s)
32
- fonts.push(font_path.to_s)
28
+ filename = Pathname.new(entry.name).basename.to_s
29
+ if font_directory?(entry.name, subdir) && font_file?(filename)
30
+ target_filename = target_filename(filename)
31
+ font_path = fonts_path.join(target_filename)
32
+ fonts.push(font_path.to_s)
33
33
 
34
- entry.extract(font_path)
34
+ entry.extract(font_path)
35
+ end
35
36
  end
36
37
  end
37
38
  end
38
39
  end
39
40
  end
40
41
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
42
+
43
+ def font_directory?(path, subdir)
44
+ return true unless subdir
45
+
46
+ dirname = File.dirname(path)
47
+ normalized_pattern = subdir.chomp("/")
48
+ File.fnmatch?(normalized_pattern, dirname)
49
+ end
41
50
  end
42
51
  end
43
52
  end