fontist 1.20.0 → 1.21.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ require "fontist/import"
2
+ require "fontist/import/formula_builder"
3
+ require "fontist/import/otf/font_file"
4
+
5
+ module Fontist
6
+ module Import
7
+ module Google
8
+ class CreateGoogleFormula
9
+ REPO_PATH = Fontist.fontist_path.join("google", "fonts")
10
+ POSSIBLE_LICENSE_FILES = ["LICENSE.txt",
11
+ "LICENCE.txt",
12
+ "OFL.txt",
13
+ "UFL.txt"].freeze
14
+
15
+ def initialize(item, options = {})
16
+ @item = item
17
+ @options = options
18
+ end
19
+
20
+ def call
21
+ builder = FormulaBuilder.new
22
+ builder.options = options
23
+ builder.resources = resources
24
+ builder.font_files = font_files
25
+ builder.license_text = license_text
26
+ builder.save
27
+ end
28
+
29
+ private
30
+
31
+ def options
32
+ @options.merge(name: formula_name, open_license: true)
33
+ end
34
+
35
+ def formula_name
36
+ @item["family"]
37
+ end
38
+
39
+ def resources
40
+ {
41
+ @item["family"] => {
42
+ source: "google",
43
+ family: @item["family"],
44
+ files: @item["files"].values,
45
+ },
46
+ }
47
+ end
48
+
49
+ def font_files
50
+ @font_files ||= @item["files"].map do |_key, url|
51
+ font_file(url)
52
+ end
53
+ end
54
+
55
+ def license_text
56
+ @license_text ||= find_license_text
57
+ end
58
+
59
+ def font_file(url)
60
+ path = Utils::Downloader.download(url, use_content_length: false).path
61
+ Otf::FontFile.new(path)
62
+ end
63
+
64
+ def find_license_text
65
+ file = license_file
66
+ return unless file
67
+
68
+ File.read(file)
69
+ end
70
+
71
+ def license_file
72
+ dir = @item["family"].gsub(" ", "").downcase
73
+ path = repo_paths(dir).first
74
+ return unless path
75
+
76
+ full_paths = POSSIBLE_LICENSE_FILES.map { |f| File.join(path, f) }
77
+
78
+ Dir[*full_paths].first
79
+ end
80
+
81
+ def repo_paths(dir)
82
+ Dir[File.join(REPO_PATH, "apache", dir),
83
+ File.join(REPO_PATH, "ofl", dir),
84
+ File.join(REPO_PATH, "ufl", dir)]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,54 +1,92 @@
1
- require "erb"
2
1
  require_relative "google"
3
- require_relative "google/new_fonts_fetcher"
4
- require_relative "create_formula"
2
+ require_relative "google/api"
3
+ require_relative "google/create_google_formula"
5
4
 
6
5
  module Fontist
7
6
  module Import
8
7
  class GoogleImport
8
+ REPO_PATH = Fontist.fontist_path.join("google", "fonts")
9
+ REPO_URL = "https://github.com/google/fonts.git".freeze
10
+
9
11
  def initialize(options)
10
12
  @max_count = options[:max_count] || Google::DEFAULT_MAX_COUNT
11
13
  end
12
14
 
13
15
  def call
14
- fonts = new_fonts
15
- create_formulas(fonts)
16
- rebuild_index unless fonts.empty?
16
+ update_repo
17
+ count = update_formulas
18
+ rebuild_index if count.positive?
17
19
  end
18
20
 
19
21
  private
20
22
 
21
- def new_fonts
22
- Fontist::Import::Google::NewFontsFetcher.new(logging: true,
23
- limit: @max_count).call
23
+ def update_repo
24
+ if Dir.exist?(REPO_PATH)
25
+ `cd #{REPO_PATH} && git pull`
26
+ else
27
+ FileUtils.mkdir_p(File.dirname(REPO_PATH))
28
+ `git clone --depth 1 #{REPO_URL} #{REPO_PATH}`
29
+ end
24
30
  end
25
31
 
26
- def create_formulas(fonts)
27
- return puts("Nothing to update") if fonts.empty?
32
+ def update_formulas
33
+ Fontist.ui.say "Updating formulas..."
34
+
35
+ items = api_items
28
36
 
29
- puts "Creating formulas..."
30
- fonts.each do |path|
31
- create_formula(path)
37
+ count = 0
38
+ items.each do |item|
39
+ break if count >= @max_count
40
+
41
+ path = update_formula(item)
42
+ count += 1 if path
32
43
  end
44
+
45
+ count
46
+ end
47
+
48
+ def api_items
49
+ Google::Api.items
50
+ end
51
+
52
+ def update_formula(item)
53
+ family = item["family"]
54
+ Fontist.ui.say "Checking #{family}"
55
+ unless new_changes?(item)
56
+ Fontist.ui.say "Skip, no changes"
57
+ return
58
+ end
59
+
60
+ create_formula(item)
33
61
  end
34
62
 
35
- def create_formula(font_path)
36
- puts font_path
63
+ def new_changes?(item)
64
+ formula = formula(item["family"])
65
+ return true unless formula
37
66
 
38
- path = Fontist::Import::CreateFormula.new(
39
- url(font_path),
40
- name: Google.metadata_name(font_path),
67
+ item["files"].values != formula.resources.first.files
68
+ end
69
+
70
+ def formula(font_name)
71
+ path = formula_path(font_name)
72
+ Formula.new_from_file(path) if File.exist?(path)
73
+ end
74
+
75
+ def formula_path(name)
76
+ snake_case = name.downcase.gsub(" ", "_")
77
+ filename = "#{snake_case}.yml"
78
+ Fontist.formulas_path.join("google", filename)
79
+ end
80
+
81
+ def create_formula(item)
82
+ path = Google::CreateGoogleFormula.new(
83
+ item,
41
84
  formula_dir: formula_dir,
42
- skip_sha: variable_style?(font_path),
43
- digest: Google.digest(font_path),
44
85
  ).call
45
86
 
46
87
  Fontist.ui.success("Formula has been successfully created: #{path}")
47
- end
48
88
 
49
- def url(path)
50
- name = Google.metadata_name(path)
51
- "https://fonts.google.com/download?family=#{ERB::Util.url_encode(name)}"
89
+ path
52
90
  end
53
91
 
54
92
  def formula_dir
@@ -57,13 +95,6 @@ module Fontist
57
95
  end
58
96
  end
59
97
 
60
- def variable_style?(path)
61
- fonts = Dir.glob(File.join(path, "*.{ttf,otf}"))
62
- fonts.any? do |font|
63
- File.basename(font).match?(/\[(.+,)?(wght|opsz)\]/)
64
- end
65
- end
66
-
67
98
  def rebuild_index
68
99
  Fontist::Index.rebuild
69
100
  end
@@ -19,10 +19,6 @@ module Fontist
19
19
  save_operation_subdir
20
20
  end
21
21
 
22
- def extension
23
- fetch_extension(@archive)
24
- end
25
-
26
22
  def font_files
27
23
  ensure_extracted
28
24
  @font_files
@@ -52,18 +48,6 @@ module Fontist
52
48
  @operations[:options][:fonts_sub_dir] = @subdir
53
49
  end
54
50
 
55
- def fetch_extension(file)
56
- File.extname(filename(file)).sub(/^\./, "")
57
- end
58
-
59
- def filename(file)
60
- if file.respond_to?(:original_filename)
61
- file.original_filename
62
- else
63
- File.basename(file)
64
- end
65
- end
66
-
67
51
  def ensure_extracted
68
52
  return if @extracted
69
53
 
@@ -2,6 +2,8 @@ module Fontist
2
2
  module Manifest
3
3
  class Locations
4
4
  def initialize(manifest)
5
+ Fontist.ui.debug("Manifest: #{manifest}")
6
+
5
7
  @manifest = manifest
6
8
  end
7
9
 
@@ -0,0 +1,55 @@
1
+ module Fontist
2
+ module Resources
3
+ class ArchiveResource
4
+ def initialize(resource, options = {})
5
+ @resource = resource
6
+ @options = options
7
+ end
8
+
9
+ def files(_source_names, &block)
10
+ excavate.files(recursive_packages: true, &block)
11
+ end
12
+
13
+ private
14
+
15
+ def excavate
16
+ Excavate::Archive.new(archive.path)
17
+ end
18
+
19
+ def archive
20
+ download_file(@resource)
21
+ end
22
+
23
+ def download_file(source)
24
+ errors = []
25
+ source.urls.each do |request|
26
+ result = try_download_file(request, source)
27
+ return result unless result.is_a?(Errors::InvalidResourceError)
28
+
29
+ errors << result
30
+ end
31
+
32
+ raise Errors::InvalidResourceError, errors.join(" ")
33
+ end
34
+
35
+ def try_download_file(request, source)
36
+ info_log(request)
37
+
38
+ Fontist::Utils::Downloader.download(
39
+ request,
40
+ sha: source.sha256,
41
+ file_size: source.file_size,
42
+ progress_bar: !@options[:no_progress],
43
+ )
44
+ rescue Errors::InvalidResourceError => e
45
+ Fontist.ui.say(e.message)
46
+ e
47
+ end
48
+
49
+ def info_log(request)
50
+ url = request.respond_to?(:url) ? request.url : request
51
+ Fontist.ui.say(%(Downloading from #{url}))
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,64 @@
1
+ module Fontist
2
+ module Resources
3
+ class GoogleResource
4
+ def initialize(resource, options = {})
5
+ @resource = resource
6
+ @options = options
7
+ end
8
+
9
+ def files(source_names)
10
+ cached_paths = download_fonts(source_names)
11
+
12
+ cached_paths.map do |path|
13
+ Dir.mktmpdir do |dir|
14
+ FileUtils.cp(path, dir)
15
+
16
+ yield File.join(dir, File.basename(path))
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def download_fonts(source_names)
24
+ urls = font_urls(source_names)
25
+
26
+ urls.map do |url|
27
+ download(url)
28
+ end
29
+ end
30
+
31
+ def font_urls(source_names)
32
+ @resource.files.select do |url|
33
+ source_names.include?(path_to_source_file(url))
34
+ end
35
+ end
36
+
37
+ def path_to_source_file(path)
38
+ format_filename(File.basename(path))
39
+ end
40
+
41
+ # TODO: remove duplication, another in Cache
42
+ def format_filename(filename)
43
+ return filename unless filename.length > 255
44
+
45
+ ext = File.extname(filename)
46
+ target_size = 255 - ext.length
47
+ cut_filename = filename.slice(0, target_size)
48
+ "#{cut_filename}#{ext}"
49
+ end
50
+
51
+ def download(url)
52
+ Fontist.ui.say(%(Downloading from #{url}))
53
+
54
+ file = Utils::Downloader.download(
55
+ url,
56
+ use_content_length: false,
57
+ progress_bar: !@options[:no_progress],
58
+ )
59
+
60
+ file.path
61
+ end
62
+ end
63
+ end
64
+ end
@@ -4,6 +4,10 @@ module Fontist
4
4
  @text = text
5
5
  end
6
6
 
7
+ def to_s
8
+ value.join(" . ")
9
+ end
10
+
7
11
  def value
8
12
  @value ||= numbers || default_value
9
13
  end
@@ -1,6 +1,8 @@
1
1
  module Fontist
2
2
  module Utils
3
3
  class Cache
4
+ MAX_FILENAME_SIZE = 255
5
+
4
6
  include Locking
5
7
 
6
8
  def self.lock_path(path)
@@ -110,6 +112,11 @@ module Fontist
110
112
  end
111
113
 
112
114
  def filename(source)
115
+ filename = response_to_filename(source)
116
+ format_filename(filename)
117
+ end
118
+
119
+ def response_to_filename(source)
113
120
  if File.extname(source.original_filename).empty? && source.content_type
114
121
  require "mime/types"
115
122
  ext = MIME::Types[source.content_type].first&.preferred_extension
@@ -119,6 +126,15 @@ module Fontist
119
126
  source.original_filename
120
127
  end
121
128
 
129
+ def format_filename(filename)
130
+ return filename unless filename.length > MAX_FILENAME_SIZE
131
+
132
+ ext = File.extname(filename)
133
+ target_size = MAX_FILENAME_SIZE - ext.length
134
+ cut_filename = filename.slice(0, target_size)
135
+ "#{cut_filename}#{ext}"
136
+ end
137
+
122
138
  def move(source_file, target_path)
123
139
  # Windows requires file descriptors to be closed before files are moved
124
140
  source_file.close
@@ -10,12 +10,17 @@ module Fontist
10
10
  ruby2_keywords :download if respond_to?(:ruby2_keywords, true)
11
11
  end
12
12
 
13
- def initialize(file, file_size: nil, sha: nil, progress_bar: nil)
13
+ def initialize(file,
14
+ file_size: nil,
15
+ sha: nil,
16
+ progress_bar: nil,
17
+ use_content_length: true)
14
18
  # TODO: If the first mirror fails, try the second one
15
19
  @file = file
16
20
  @sha = [sha].flatten.compact
17
21
  @file_size = file_size.to_i if file_size
18
22
  @progress_bar = progress_bar
23
+ @use_content_length = use_content_length
19
24
  @cache = Cache.new
20
25
  end
21
26
 
@@ -85,7 +90,9 @@ module Fontist
85
90
  max_redirects: 10,
86
91
  headers: headers,
87
92
  content_length_proc: ->(content_length) {
88
- progress_bar.total = content_length if content_length
93
+ if @use_content_length && content_length
94
+ progress_bar.total = content_length
95
+ end
89
96
  },
90
97
  progress_proc: -> (progress) {
91
98
  progress_bar.increment(progress)
@@ -16,7 +16,15 @@ module Fontist
16
16
  end
17
17
 
18
18
  def self.level
19
- @level || default_level
19
+ @level ||= env_level || default_level
20
+ end
21
+
22
+ def self.env_level
23
+ ENV["FONTIST_LOG"]&.to_sym
24
+ end
25
+
26
+ def self.debug?
27
+ log_levels.include?(:debug)
20
28
  end
21
29
 
22
30
  def self.default_level
@@ -50,7 +58,7 @@ module Fontist
50
58
  end
51
59
 
52
60
  def self.debug(message)
53
- new.say(message) if log_levels.include?(:debug)
61
+ new.say(message) if debug?
54
62
  end
55
63
 
56
64
  def self.log_levels
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "1.20.0".freeze
2
+ VERSION = "1.21.1".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -49,7 +49,7 @@ module Fontist
49
49
  end
50
50
 
51
51
  def self.formulas_version
52
- "v3"
52
+ "v4"
53
53
  end
54
54
 
55
55
  def self.formulas_repo_url
@@ -159,4 +159,8 @@ module Fontist
159
159
  def self.interactive=(bool)
160
160
  @interactive = bool
161
161
  end
162
+
163
+ def self.google_fonts_key
164
+ ENV["GOOGLE_FONTS_API_KEY"] || config[:google_fonts_key]
165
+ end
162
166
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.0
4
+ version: 1.21.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-05 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -377,6 +377,7 @@ files:
377
377
  - lib/fontist/cache_cli.rb
378
378
  - lib/fontist/cli.rb
379
379
  - lib/fontist/cli/class_options.rb
380
+ - lib/fontist/cli/thor_ext.rb
380
381
  - lib/fontist/collection_file.rb
381
382
  - lib/fontist/config.rb
382
383
  - lib/fontist/config_cli.rb
@@ -401,9 +402,8 @@ files:
401
402
  - lib/fontist/import/formula_builder.rb
402
403
  - lib/fontist/import/formula_serializer.rb
403
404
  - lib/fontist/import/google.rb
404
- - lib/fontist/import/google/new_fonts_fetcher.rb
405
- - lib/fontist/import/google/skiplist.yml
406
- - lib/fontist/import/google_check.rb
405
+ - lib/fontist/import/google/api.rb
406
+ - lib/fontist/import/google/create_google_formula.rb
407
407
  - lib/fontist/import/google_import.rb
408
408
  - lib/fontist/import/helpers/hash_helper.rb
409
409
  - lib/fontist/import/helpers/system_helper.rb
@@ -433,6 +433,8 @@ files:
433
433
  - lib/fontist/manifest/locations.rb
434
434
  - lib/fontist/repo.rb
435
435
  - lib/fontist/repo_cli.rb
436
+ - lib/fontist/resources/archive_resource.rb
437
+ - lib/fontist/resources/google_resource.rb
436
438
  - lib/fontist/style_version.rb
437
439
  - lib/fontist/system.yml
438
440
  - lib/fontist/system_font.rb
@@ -468,7 +470,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
468
470
  - !ruby/object:Gem::Version
469
471
  version: '0'
470
472
  requirements: []
471
- rubygems_version: 3.3.26
473
+ rubygems_version: 3.3.27
472
474
  signing_key:
473
475
  specification_version: 4
474
476
  summary: Install openly-licensed fonts on Windows, Linux and Mac!