fontist 1.4.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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