fontist 1.6.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/release.yml +38 -0
  3. data/.github/workflows/rspec.yml +58 -0
  4. data/README.md +109 -32
  5. data/{bin → exe}/fontist +0 -0
  6. data/fontist.gemspec +4 -2
  7. data/lib/fontist.rb +10 -3
  8. data/lib/fontist/cli.rb +63 -42
  9. data/lib/fontist/errors.rb +14 -11
  10. data/lib/fontist/font.rb +25 -27
  11. data/lib/fontist/font_installer.rb +114 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +89 -63
  14. data/lib/fontist/formula_paths.rb +43 -0
  15. data/lib/fontist/helpers.rb +7 -0
  16. data/lib/fontist/import/create_formula.rb +15 -30
  17. data/lib/fontist/import/files/collection_file.rb +6 -1
  18. data/lib/fontist/import/files/file_requirement.rb +17 -0
  19. data/lib/fontist/import/files/font_detector.rb +48 -0
  20. data/lib/fontist/import/formula_builder.rb +7 -3
  21. data/lib/fontist/import/google_check.rb +1 -1
  22. data/lib/fontist/import/google_import.rb +3 -4
  23. data/lib/fontist/import/otf/font_file.rb +17 -3
  24. data/lib/fontist/import/otfinfo_generate.rb +1 -1
  25. data/lib/fontist/import/recursive_extraction.rb +74 -13
  26. data/lib/fontist/index.rb +72 -0
  27. data/lib/fontist/index_formula.rb +30 -0
  28. data/lib/fontist/manifest/install.rb +6 -15
  29. data/lib/fontist/manifest/locations.rb +59 -4
  30. data/lib/fontist/system_font.rb +22 -49
  31. data/lib/fontist/system_index.rb +92 -0
  32. data/lib/fontist/utils.rb +1 -0
  33. data/lib/fontist/utils/dsl.rb +4 -0
  34. data/lib/fontist/utils/dsl/collection_font.rb +36 -0
  35. data/lib/fontist/utils/dsl/font.rb +2 -1
  36. data/lib/fontist/utils/exe_extractor.rb +6 -5
  37. data/lib/fontist/utils/zip_extractor.rb +20 -12
  38. data/lib/fontist/version.rb +1 -1
  39. metadata +45 -20
  40. data/.github/workflows/macosx.yml +0 -33
  41. data/.github/workflows/ubuntu.yml +0 -30
  42. data/.github/workflows/windows.yml +0 -32
  43. data/bin/check_google +0 -8
  44. data/bin/console +0 -11
  45. data/bin/convert_formulas +0 -8
  46. data/bin/generate_otfinfo +0 -8
  47. data/bin/import_google +0 -8
  48. data/bin/rspec +0 -29
  49. data/bin/setup +0 -7
  50. data/lib/fontist/font_formula.rb +0 -130
  51. data/lib/fontist/formula_template.rb +0 -108
  52. data/lib/fontist/formulas.rb +0 -56
  53. data/lib/fontist/manifest/common.rb +0 -60
  54. data/lib/fontist/registry.rb +0 -43
@@ -0,0 +1,30 @@
1
+ module Fontist
2
+ class IndexFormula
3
+ def initialize(path)
4
+ @path = path
5
+ end
6
+
7
+ def to_s
8
+ normalized
9
+ end
10
+
11
+ def to_full
12
+ Formula.new_from_file(full_path)
13
+ end
14
+
15
+ def ==(other)
16
+ to_s == other.to_s
17
+ end
18
+
19
+ private
20
+
21
+ def normalized
22
+ escaped = Regexp.escape(Fontist.formulas_path.to_s + "/")
23
+ @path.sub(Regexp.new("^" + escaped), "")
24
+ end
25
+
26
+ def full_path
27
+ Fontist.formulas_path.join(normalized).to_s
28
+ end
29
+ end
30
+ end
@@ -1,35 +1,26 @@
1
- require_relative "common"
1
+ require_relative "locations"
2
2
 
3
3
  module Fontist
4
4
  module Manifest
5
- class Install < Common
5
+ class Install < Locations
6
6
  def initialize(manifest, confirmation: "no")
7
7
  @manifest = manifest
8
8
  @confirmation = confirmation
9
9
  end
10
10
 
11
- def self.call(manifest, confirmation: "no")
12
- new(manifest, confirmation: confirmation).call
13
- end
14
-
15
11
  private
16
12
 
17
13
  def file_paths(font, style)
18
- paths = find_installed_font(font, style)
19
- return paths unless paths.empty?
14
+ paths = find_font_with_name(font, style)
15
+ return paths unless paths["paths"].empty?
20
16
 
21
17
  install_font(font)
22
- find_installed_font(font, style)
23
- end
24
18
 
25
- def find_installed_font(font, style)
26
- Fontist::SystemFont.find_with_style(font, style)
19
+ find_font_with_name(font, style)
27
20
  end
28
21
 
29
22
  def install_font(font)
30
- Fontist::Font.try_install(font, confirmation: @confirmation)
31
- rescue Fontist::Errors::LicensingError
32
- [] # try to install other fonts
23
+ Fontist::Font.install(font, force: true, confirmation: @confirmation)
33
24
  end
34
25
  end
35
26
  end
@@ -1,12 +1,67 @@
1
- require_relative "common"
2
-
3
1
  module Fontist
4
2
  module Manifest
5
- class Locations < Common
3
+ class Locations
4
+ def initialize(manifest)
5
+ @manifest = manifest
6
+ end
7
+
8
+ def self.from_file(file, **keywords)
9
+ raise Fontist::Errors::ManifestCouldNotBeFoundError unless File.exist?(file)
10
+
11
+ manifest = YAML.load_file(file)
12
+ raise Fontist::Errors::ManifestCouldNotBeReadError unless manifest.is_a?(Hash)
13
+
14
+ from_hash(manifest, **keywords)
15
+ end
16
+
17
+ def self.from_hash(manifest, **keywords)
18
+ if keywords.empty?
19
+ new(manifest).call
20
+ else
21
+ new(manifest, **keywords).call
22
+ end
23
+ end
24
+
25
+ def call
26
+ font_names.zip(font_paths).to_h
27
+ end
28
+
6
29
  private
7
30
 
31
+ attr_reader :manifest
32
+
33
+ def font_names
34
+ manifest.keys
35
+ end
36
+
37
+ def font_paths
38
+ manifest.map do |font, styles|
39
+ styles_to_ary = [styles].flatten
40
+ style_paths_map(font, styles_to_ary)
41
+ end
42
+ end
43
+
44
+ def style_paths_map(font, names)
45
+ paths = style_paths(font, names)
46
+ names.zip(paths).to_h
47
+ end
48
+
49
+ def style_paths(font, names)
50
+ names.map do |style|
51
+ file_paths(font, style)
52
+ end
53
+ end
54
+
8
55
  def file_paths(font, style)
9
- Fontist::SystemFont.find_with_style(font, style)
56
+ find_font_with_name(font, style).tap do |x|
57
+ if x["paths"].empty?
58
+ raise Errors::MissingFontError.new("Could not find font #{font} #{style}.")
59
+ end
60
+ end
61
+ end
62
+
63
+ def find_font_with_name(font, style)
64
+ Fontist::SystemFont.find_with_name(font, style).map { |k, v| [k.to_s, v] }.to_h
10
65
  end
11
66
  end
12
67
  end
@@ -1,3 +1,6 @@
1
+ require_relative "system_index"
2
+ require_relative "formula_paths"
3
+
1
4
  module Fontist
2
5
  class SystemFont
3
6
  def initialize(font:, style: nil, sources: nil)
@@ -10,22 +13,23 @@ module Fontist
10
13
  new(font: font, sources: sources).find
11
14
  end
12
15
 
13
- def self.find_with_style(font, style)
14
- new(font: font, style: style).find_with_style
16
+ def self.find_with_name(font, style)
17
+ new(font: font, style: style).find_with_name
15
18
  end
16
19
 
17
20
  def find
18
- paths = grep_font_paths(font)
19
- paths = lookup_using_font_name || [] if paths.empty?
21
+ styles = find_styles
22
+ return unless styles
20
23
 
21
- paths.empty? ? nil : paths
24
+ styles.map { |x| x[:path] }
22
25
  end
23
26
 
24
- def find_with_style
25
- paths = lookup_using_font_and_style
26
- return paths unless paths.empty?
27
+ def find_with_name
28
+ styles = find_styles
29
+ return { full_name: nil, paths: [] } unless styles
27
30
 
28
- grep_font_paths(font, style)
31
+ { full_name: styles.first[:full_name],
32
+ paths: styles.map { |x| x[:path] } }
29
33
  end
30
34
 
31
35
  private
@@ -42,25 +46,6 @@ module Fontist
42
46
  end
43
47
  end
44
48
 
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
49
  def font_paths
65
50
  @font_paths ||= Dir.glob((
66
51
  user_sources +
@@ -69,11 +54,6 @@ module Fontist
69
54
  ).flatten.uniq)
70
55
  end
71
56
 
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
57
  def fontist_fonts_path
78
58
  @fontist_fonts_path ||= Fontist.fonts_path
79
59
  end
@@ -82,31 +62,24 @@ module Fontist
82
62
  Fontist::Utils::System.user_os
83
63
  end
84
64
 
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
65
  def system_path_file
91
66
  File.open(Fontist.system_file_path)
92
67
  end
93
68
 
94
69
  def default_sources
95
- @default_sources ||= YAML.load(system_path_file)["system"][user_os.to_s]
70
+ @default_sources ||= YAML.safe_load(system_path_file)["system"][user_os.to_s]
96
71
  end
97
72
 
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)
103
- end
73
+ def find_styles
74
+ find_by_index || find_by_formulas
104
75
  end
105
76
 
106
- def search_font_paths(filename)
107
- font_paths.select do |path|
108
- File.basename(path) == filename
109
- end
77
+ def find_by_index
78
+ SystemIndex.new(font_paths).find(font, style)
79
+ end
80
+
81
+ def find_by_formulas
82
+ FormulaPaths.new(font_paths).find(font, style)
110
83
  end
111
84
  end
112
85
  end
@@ -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).gsub(/^\./, "").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"
@@ -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
@@ -6,9 +6,9 @@ module Fontist
6
6
 
7
7
  exe_file = download_file(exe_file).path if download
8
8
 
9
- Fontist.ui.say(%(Installing font "#{key}".))
9
+ Fontist.ui.say(%(Installing font "#{formula.key}".))
10
10
  cab_file = decompressor.search(exe_file)
11
- cabbed_fonts = grep_fonts(cab_file.files, font_ext) || []
11
+ cabbed_fonts = grep_fonts(cab_file.files) || []
12
12
  fonts_paths = extract_cabbed_fonts_to_assets(cabbed_fonts)
13
13
 
14
14
  block_given? ? yield(fonts_paths) : fonts_paths
@@ -29,10 +29,10 @@ module Fontist
29
29
  )
30
30
  end
31
31
 
32
- def grep_fonts(file, font_ext)
32
+ def grep_fonts(file)
33
33
  Array.new.tap do |fonts|
34
34
  while file
35
- fonts.push(file) if file.filename.match(font_ext)
35
+ fonts.push(file) if font_file?(file.filename)
36
36
  file = file.next
37
37
  end
38
38
  end
@@ -41,7 +41,8 @@ module Fontist
41
41
  def extract_cabbed_fonts_to_assets(cabbed_fonts)
42
42
  Array.new.tap do |fonts|
43
43
  cabbed_fonts.each do |font|
44
- 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
45
46
  decompressor.extract(font, font_path)
46
47
 
47
48
  fonts.push(font_path)