fontist 1.6.0 → 1.8.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.
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)