fontist 1.4.0 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +176 -6
- data/bin/fontist +1 -2
- data/fontist.gemspec +2 -0
- data/lib/fontist.rb +5 -0
- data/lib/fontist/cli.rb +59 -9
- data/lib/fontist/errors.rb +3 -0
- data/lib/fontist/font.rb +29 -6
- data/lib/fontist/font_formula.rb +35 -4
- data/lib/fontist/formula.rb +16 -1
- data/lib/fontist/formula_template.rb +41 -22
- data/lib/fontist/import/create_formula.rb +15 -30
- data/lib/fontist/import/files/collection_file.rb +11 -7
- data/lib/fontist/import/files/file_requirement.rb +17 -0
- data/lib/fontist/import/files/font_detector.rb +48 -0
- data/lib/fontist/import/formula_builder.rb +57 -6
- data/lib/fontist/import/google/skiplist.yml +2 -0
- data/lib/fontist/import/helpers/system_helper.rb +4 -1
- data/lib/fontist/import/otf/font_file.rb +20 -4
- data/lib/fontist/import/recursive_extraction.rb +99 -15
- data/lib/fontist/manifest.rb +2 -0
- data/lib/fontist/manifest/install.rb +32 -0
- data/lib/fontist/manifest/locations.rb +60 -0
- data/lib/fontist/system_font.rb +72 -6
- data/lib/fontist/system_index.rb +92 -0
- data/lib/fontist/utils.rb +1 -0
- data/lib/fontist/utils/cache.rb +27 -8
- data/lib/fontist/utils/downloader.rb +54 -10
- data/lib/fontist/utils/dsl.rb +4 -0
- data/lib/fontist/utils/dsl/collection_font.rb +36 -0
- data/lib/fontist/utils/dsl/font.rb +2 -1
- data/lib/fontist/utils/exe_extractor.rb +19 -9
- data/lib/fontist/utils/ui.rb +4 -0
- data/lib/fontist/utils/zip_extractor.rb +20 -11
- data/lib/fontist/version.rb +1 -1
- metadata +37 -3
- data/bin/stripttc +0 -0
@@ -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
|
data/lib/fontist/utils.rb
CHANGED
@@ -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"
|
data/lib/fontist/utils/cache.rb
CHANGED
@@ -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
|
-
|
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
|
13
|
+
path = save_cache(generated_file, key)
|
10
14
|
|
11
|
-
|
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
|
25
|
-
File.new(
|
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?(
|
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
|
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
|
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"
|
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
|
-
|
64
|
+
@progress_bar.total = content_length if content_length
|
61
65
|
},
|
62
66
|
progress_proc: -> (progress) {
|
63
|
-
|
67
|
+
@progress_bar.increment(progress)
|
64
68
|
}
|
65
69
|
)
|
66
70
|
|
67
|
-
|
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
|
data/lib/fontist/utils/dsl.rb
CHANGED
@@ -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
|
@@ -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
|
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
|
-
|
18
|
-
block_given? ? yield(
|
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
|
32
|
+
def grep_fonts(file)
|
31
33
|
Array.new.tap do |fonts|
|
32
34
|
while file
|
33
|
-
fonts.push(file) if file.filename
|
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
|
-
|
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
|
53
|
+
def extract_subarchive(file, subarchive = nil)
|
51
54
|
while file
|
52
55
|
filename = file.filename
|
53
|
-
|
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
|
data/lib/fontist/utils/ui.rb
CHANGED
@@ -4,40 +4,49 @@ require "pathname"
|
|
4
4
|
module Fontist
|
5
5
|
module Utils
|
6
6
|
module ZipExtractor
|
7
|
-
|
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
|
-
|
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.
|
26
|
+
zip_file.each do |entry|
|
29
27
|
if entry.name
|
30
|
-
filename = Pathname.new(entry.name).basename
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|