fontist 1.3.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check_google.yml +28 -0
  3. data/.github/workflows/macosx.yml +3 -0
  4. data/.github/workflows/ubuntu.yml +1 -1
  5. data/.github/workflows/windows.yml +1 -1
  6. data/.gitignore +6 -0
  7. data/.hound.yml +2 -0
  8. data/.rubocop.yml +22 -0
  9. data/README.md +224 -0
  10. data/bin/check_google +8 -0
  11. data/bin/convert_formulas +8 -0
  12. data/bin/fontist +7 -0
  13. data/bin/generate_otfinfo +8 -0
  14. data/bin/import_google +8 -0
  15. data/fontist.gemspec +15 -1
  16. data/lib/fontist.rb +19 -13
  17. data/lib/fontist/cli.rb +168 -0
  18. data/lib/fontist/errors.rb +6 -0
  19. data/lib/fontist/font.rb +145 -7
  20. data/lib/fontist/font_formula.rb +42 -7
  21. data/lib/fontist/fontist_font.rb +70 -0
  22. data/lib/fontist/formula.rb +31 -9
  23. data/lib/fontist/formula_template.rb +122 -0
  24. data/lib/fontist/formulas.rb +41 -0
  25. data/lib/fontist/import.rb +9 -0
  26. data/lib/fontist/import/convert_formulas.rb +65 -0
  27. data/lib/fontist/import/create_formula.rb +59 -0
  28. data/lib/fontist/import/extractors.rb +5 -0
  29. data/lib/fontist/import/extractors/cab_extractor.rb +37 -0
  30. data/lib/fontist/import/extractors/extractor.rb +19 -0
  31. data/lib/fontist/import/extractors/ole_extractor.rb +41 -0
  32. data/lib/fontist/import/extractors/seven_zip_extractor.rb +44 -0
  33. data/lib/fontist/import/extractors/zip_extractor.rb +31 -0
  34. data/lib/fontist/import/files/collection_file.rb +52 -0
  35. data/lib/fontist/import/files/file_requirement.rb +17 -0
  36. data/lib/fontist/import/files/font_detector.rb +48 -0
  37. data/lib/fontist/import/formula_builder.rb +166 -0
  38. data/lib/fontist/import/formula_serializer.rb +133 -0
  39. data/lib/fontist/import/google.rb +16 -0
  40. data/lib/fontist/import/google/fonts_public.md +10 -0
  41. data/lib/fontist/import/google/fonts_public.pb.rb +71 -0
  42. data/lib/fontist/import/google/fonts_public.proto +46 -0
  43. data/lib/fontist/import/google/new_fonts_fetcher.rb +121 -0
  44. data/lib/fontist/import/google/skiplist.yml +8 -0
  45. data/lib/fontist/import/google_check.rb +34 -0
  46. data/lib/fontist/import/google_import.rb +180 -0
  47. data/lib/fontist/import/helpers/hash_helper.rb +13 -0
  48. data/lib/fontist/import/helpers/system_helper.rb +23 -0
  49. data/lib/fontist/import/otf/font_file.rb +105 -0
  50. data/lib/fontist/import/otf_parser.rb +25 -0
  51. data/lib/fontist/import/otf_style.rb +29 -0
  52. data/lib/fontist/import/otfinfo/otfinfo_requirement.rb +22 -0
  53. data/lib/fontist/import/otfinfo/template.erb +20 -0
  54. data/lib/fontist/import/otfinfo_generate.rb +45 -0
  55. data/lib/fontist/import/recursive_extraction.rb +185 -0
  56. data/lib/fontist/import/template_helper.rb +19 -0
  57. data/lib/fontist/import/text_helper.rb +30 -0
  58. data/lib/fontist/manifest.rb +2 -0
  59. data/lib/fontist/manifest/install.rb +32 -0
  60. data/lib/fontist/manifest/locations.rb +60 -0
  61. data/lib/fontist/system_font.rb +66 -22
  62. data/lib/fontist/utils.rb +5 -0
  63. data/lib/fontist/utils/cache.rb +88 -0
  64. data/lib/fontist/utils/downloader.rb +66 -9
  65. data/lib/fontist/utils/dsl.rb +8 -0
  66. data/lib/fontist/utils/dsl/collection_font.rb +36 -0
  67. data/lib/fontist/utils/dsl/font.rb +38 -0
  68. data/lib/fontist/utils/exe_extractor.rb +25 -22
  69. data/lib/fontist/utils/msi_extractor.rb +31 -0
  70. data/lib/fontist/utils/seven_zip_extractor.rb +41 -0
  71. data/lib/fontist/utils/system.rb +23 -0
  72. data/lib/fontist/utils/ui.rb +12 -0
  73. data/lib/fontist/utils/zip_extractor.rb +22 -8
  74. data/lib/fontist/version.rb +1 -1
  75. metadata +173 -49
  76. data/lib/fontist/formulas/andale_font.rb +0 -80
  77. data/lib/fontist/formulas/arial_black_font.rb +0 -79
  78. data/lib/fontist/formulas/cleartype_fonts.rb +0 -227
  79. data/lib/fontist/formulas/comic_font.rb +0 -78
  80. data/lib/fontist/formulas/courier_font.rb +0 -81
  81. data/lib/fontist/formulas/euphemia_font.rb +0 -85
  82. data/lib/fontist/formulas/georgia_font.rb +0 -80
  83. data/lib/fontist/formulas/impact_font.rb +0 -78
  84. data/lib/fontist/formulas/montserrat_font.rb +0 -132
  85. data/lib/fontist/formulas/ms_truetype_fonts.rb +0 -125
  86. data/lib/fontist/formulas/open_sans_fonts.rb +0 -263
  87. data/lib/fontist/formulas/overpass_font.rb +0 -73
  88. data/lib/fontist/formulas/source_fonts.rb +0 -109
  89. data/lib/fontist/formulas/stix_fonts.rb +0 -108
  90. data/lib/fontist/formulas/tahoma_font.rb +0 -147
  91. data/lib/fontist/formulas/webding_font.rb +0 -78
  92. data/spec/fontist/font_formula_spec.rb +0 -67
  93. data/spec/fontist/font_spec.rb +0 -113
  94. data/spec/fontist/formula_spec.rb +0 -67
  95. data/spec/fontist/formulas/andale_font_spec.rb +0 -29
  96. data/spec/fontist/formulas/arial_black_font_spec.rb +0 -29
  97. data/spec/fontist/formulas/cleartype_fonts_spec.rb +0 -38
  98. data/spec/fontist/formulas/comic_font_spec.rb +0 -29
  99. data/spec/fontist/formulas/courier_font_spec.rb +0 -29
  100. data/spec/fontist/formulas/euphemia_font_spec.rb +0 -29
  101. data/spec/fontist/formulas/georgia_font_spec.rb +0 -29
  102. data/spec/fontist/formulas/impact_font_spec.rb +0 -29
  103. data/spec/fontist/formulas/montserrat_font_spec.rb +0 -29
  104. data/spec/fontist/formulas/ms_truetype_fonts_spec.rb +0 -29
  105. data/spec/fontist/formulas/open_sans_fonts_spec.rb +0 -29
  106. data/spec/fontist/formulas/overpass_font_spec.rb +0 -29
  107. data/spec/fontist/formulas/source_fonts_spec.rb +0 -31
  108. data/spec/fontist/formulas/stix_fonts_spec.rb +0 -29
  109. data/spec/fontist/formulas/tahoma_font_spec.rb +0 -29
  110. data/spec/fontist/formulas/webding_font_spec.rb +0 -29
  111. data/spec/fontist/registry_spec.rb +0 -47
  112. data/spec/fontist/system_font_spec.rb +0 -44
  113. data/spec/fontist/utils/downloader_spec.rb +0 -35
  114. data/spec/fontist_spec.rb +0 -5
  115. data/spec/spec_helper.rb +0 -22
  116. data/spec/support/fontist_helper.rb +0 -10
@@ -0,0 +1,185 @@
1
+ require "find"
2
+ require_relative "extractors"
3
+ require_relative "files/font_detector"
4
+
5
+ module Fontist
6
+ module Import
7
+ class RecursiveExtraction
8
+ FONTS_PATTERN = "**/*.{ttf,otf,ttc}".freeze
9
+ ARCHIVE_EXTENSIONS = %w[zip msi exe cab].freeze
10
+ LICENSE_PATTERN = /(OFL\.txt|UFL\.txt|LICENSE\.txt|COPYING)$/i.freeze
11
+
12
+ def initialize(archive, subarchive: nil, subdir: nil)
13
+ @archive = archive
14
+ @subarchive = subarchive
15
+ @subdir = subdir
16
+ @operations = []
17
+ @font_files = []
18
+ @collection_files = []
19
+ end
20
+
21
+ def extension
22
+ File.extname(filename(@archive)).sub(/^\./, "")
23
+ end
24
+
25
+ def font_files
26
+ ensure_extracted
27
+ @font_files
28
+ end
29
+
30
+ def font_collection_files
31
+ ensure_extracted
32
+ @collection_files
33
+ end
34
+
35
+ def license_text
36
+ ensure_extracted
37
+ @license_text
38
+ end
39
+
40
+ def operations
41
+ ensure_extracted
42
+ @operations.size == 1 ? @operations.first : @operations
43
+ end
44
+
45
+ private
46
+
47
+ def filename(file)
48
+ if file.respond_to?(:original_filename)
49
+ file.original_filename
50
+ else
51
+ File.basename(file)
52
+ end
53
+ end
54
+
55
+ def ensure_extracted
56
+ extracted_path
57
+ end
58
+
59
+ def extracted_path
60
+ @extracted_path ||= extract_recursively(@archive)
61
+ end
62
+
63
+ def extract_recursively(archive)
64
+ path = operate_on_archive(archive)
65
+ match_files(path)
66
+ if matched?
67
+ save_operation_subdir
68
+ return path
69
+ end
70
+
71
+ next_archive = find_archive(path)
72
+ extract_recursively(next_archive)
73
+ end
74
+
75
+ def operate_on_archive(archive)
76
+ extractor = choose_extractor(archive)
77
+ Fontist.ui.say("Extracting #{archive} with #{extractor.class.name}")
78
+
79
+ save_operation(extractor)
80
+ extractor.extract
81
+ end
82
+
83
+ # rubocop:disable Metrics/MethodLength
84
+ def choose_extractor(archive)
85
+ case filename(archive)
86
+ when /\.msi$/i
87
+ Extractors::OleExtractor.new(archive)
88
+ when /\.cab$/i
89
+ Extractors::CabExtractor.new(archive)
90
+ when /\.exe$/i
91
+ extractor = Extractors::SevenZipExtractor.new(archive)
92
+ extractor.try ? extractor : Extractors::CabExtractor.new(archive)
93
+ else
94
+ Extractors::ZipExtractor.new(archive)
95
+ end
96
+ end
97
+ # rubocop:enable Metrics/MethodLength
98
+
99
+ def save_operation(extractor)
100
+ @operations << { format: extractor.format }
101
+ end
102
+
103
+ def match_files(dir_path)
104
+ Find.find(dir_path) do |entry_path| # rubocop:disable Style/CollectionMethods
105
+ match_license(entry_path)
106
+ match_font(entry_path) if font_directory?(entry_path, dir_path)
107
+ end
108
+ end
109
+
110
+ def match_license(path)
111
+ @license_text ||= File.read(path) if license?(path)
112
+ end
113
+
114
+ def license?(file)
115
+ file.match?(LICENSE_PATTERN)
116
+ end
117
+
118
+ def font_directory?(path, base_path)
119
+ return true unless @subdir
120
+
121
+ relative_path = Pathname.new(path).relative_path_from(base_path).to_s
122
+ dirname = File.dirname(relative_path)
123
+ normalized_pattern = @subdir.chomp("/")
124
+ File.fnmatch?(normalized_pattern, dirname)
125
+ end
126
+
127
+ def match_font(path)
128
+ case Files::FontDetector.detect(path)
129
+ when :font
130
+ @font_files << Otf::FontFile.new(path)
131
+ when :collection
132
+ @collection_files << Files::CollectionFile.new(path)
133
+ end
134
+ end
135
+
136
+ def matched?
137
+ [@font_files, @collection_files].any? do |files|
138
+ files.size.positive?
139
+ end
140
+ end
141
+
142
+ def save_operation_subdir
143
+ return unless @subdir
144
+
145
+ @operations.last[:options] ||= {}
146
+ @operations.last[:options][:fonts_sub_dir] = @subdir
147
+ end
148
+
149
+ def find_archive(path)
150
+ paths = Dir.children(path).map { |file| File.join(path, file) }
151
+ by_subarchive(paths) || by_size(paths)
152
+ end
153
+
154
+ def by_subarchive(paths)
155
+ return unless @subarchive
156
+
157
+ path_found = paths.detect do |path|
158
+ @subarchive == File.basename(path)
159
+ end
160
+
161
+ return unless path_found
162
+
163
+ save_operation_subarchive(path_found)
164
+
165
+ path_found
166
+ end
167
+
168
+ def save_operation_subarchive(path)
169
+ @operations.last[:options] ||= {}
170
+ @operations.last[:options][:subarchive] = File.basename(path)
171
+ end
172
+
173
+ def by_size(paths)
174
+ paths.max_by do |path|
175
+ [file_type(path), File.size(path)]
176
+ end
177
+ end
178
+
179
+ def file_type(file_path)
180
+ extension = File.extname(file_path).delete(".")
181
+ ARCHIVE_EXTENSIONS.include?(extension) ? 1 : 0
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,19 @@
1
+ module Fontist
2
+ module Import
3
+ module TemplateHelper
4
+ class << self
5
+ def bind(resource, name = nil)
6
+ b = binding
7
+ b.local_variable_set(name, resource) if name
8
+ b
9
+ end
10
+
11
+ def escape_double_quotes(text)
12
+ text.gsub('"', '\"')
13
+ end
14
+
15
+ alias esc escape_double_quotes
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Fontist
2
+ module Import
3
+ module TextHelper
4
+ class << self
5
+ def cleanup(text)
6
+ return unless text
7
+
8
+ text.gsub("\r\n", "\n")
9
+ .gsub("\r", "\n")
10
+ .strip
11
+ .lines
12
+ .map(&:rstrip)
13
+ .drop_while(&:empty?)
14
+ .join("\n")
15
+ end
16
+
17
+ def longest_common_prefix(strs)
18
+ return if strs.empty?
19
+
20
+ min, max = strs.minmax
21
+ idx = min.size.times { |i| break i if min[i] != max[i] }
22
+ prefix = min[0...idx].strip
23
+ return if prefix.empty?
24
+
25
+ prefix
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "manifest/locations"
2
+ require_relative "manifest/install"
@@ -0,0 +1,32 @@
1
+ require_relative "locations"
2
+
3
+ module Fontist
4
+ module Manifest
5
+ class Install < Locations
6
+ def initialize(manifest, confirmation: "no")
7
+ @manifest = manifest
8
+ @confirmation = confirmation
9
+ end
10
+
11
+ def self.call(manifest, confirmation: "no")
12
+ new(manifest, confirmation: confirmation).call
13
+ end
14
+
15
+ private
16
+
17
+ def file_paths(font, style)
18
+ paths = super
19
+ return paths unless paths["paths"].empty?
20
+
21
+ install_font(font)
22
+ super
23
+ end
24
+
25
+ def install_font(font)
26
+ Fontist::Font.try_install(font, confirmation: @confirmation)
27
+ rescue Fontist::Errors::LicensingError
28
+ [] # try to install other fonts
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ module Fontist
2
+ module Manifest
3
+ class Locations
4
+ def initialize(manifest)
5
+ @manifest = manifest
6
+ end
7
+
8
+ def self.call(manifest)
9
+ new(manifest).call
10
+ end
11
+
12
+ def call
13
+ font_names.zip(font_paths).to_h
14
+ end
15
+
16
+ private
17
+
18
+ def font_names
19
+ fonts.keys
20
+ end
21
+
22
+ def fonts
23
+ @fonts ||= begin
24
+ unless File.exist?(@manifest)
25
+ raise Fontist::Errors::ManifestCouldNotBeFoundError
26
+ end
27
+
28
+ fonts = YAML.load_file(@manifest)
29
+ unless fonts.is_a?(Hash)
30
+ raise Fontist::Errors::ManifestCouldNotBeReadError
31
+ end
32
+
33
+ fonts
34
+ end
35
+ end
36
+
37
+ def font_paths
38
+ fonts.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
+
55
+ def file_paths(font, style)
56
+ Fontist::SystemFont.find_with_style(font, style).transform_keys(&:to_s)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,7 +1,8 @@
1
1
  module Fontist
2
2
  class SystemFont
3
- def initialize(font:, sources: nil)
3
+ def initialize(font:, style: nil, sources: nil)
4
4
  @font = font
5
+ @style = style
5
6
  @user_sources = sources || []
6
7
  end
7
8
 
@@ -9,28 +10,59 @@ module Fontist
9
10
  new(font: font, sources: sources).find
10
11
  end
11
12
 
13
+ def self.find_with_style(font, style)
14
+ new(font: font, style: style).find_with_style
15
+ end
16
+
12
17
  def find
13
- paths = font_paths.grep(/#{font}/i)
18
+ paths = grep_font_paths(font)
14
19
  paths = lookup_using_font_name || [] if paths.empty?
15
20
 
16
21
  paths.empty? ? nil : paths
17
22
  end
18
23
 
24
+ def find_with_style
25
+ styles = Formula.find_styles_with_fonts(font, style)
26
+
27
+ { full_name: style_full_name(styles),
28
+ paths: style_paths(styles) }
29
+ end
30
+
19
31
  private
20
32
 
21
- attr_reader :font, :user_sources
33
+ attr_reader :font, :style, :user_sources
22
34
 
23
35
  def normalize_default_paths
24
36
  @normalize_default_paths ||= default_sources["paths"].map do |path|
25
37
  require "etc"
26
38
  passwd = Etc.getpwuid
39
+ username = passwd ? passwd.name : Etc.getlogin
27
40
 
28
- passwd ? path.gsub("{username}", passwd.name) : path
41
+ username ? path.gsub("{username}", username) : path
29
42
  end
30
43
  end
31
44
 
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
+
32
64
  def font_paths
33
- Dir.glob((
65
+ @font_paths ||= Dir.glob((
34
66
  user_sources +
35
67
  normalize_default_paths +
36
68
  [fontist_fonts_path.join("**")]
@@ -46,23 +78,8 @@ module Fontist
46
78
  @fontist_fonts_path ||= Fontist.fonts_path
47
79
  end
48
80
 
49
-
50
81
  def user_os
51
- @user_os ||= (
52
- host_os = RbConfig::CONFIG["host_os"]
53
- case host_os
54
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
55
- :windows
56
- when /darwin|mac os/
57
- :macos
58
- when /linux/
59
- :linux
60
- when /solaris|bsd/
61
- :unix
62
- else
63
- raise Fontist::Error, "unknown os: #{host_os.inspect}"
64
- end
65
- )
82
+ Fontist::Utils::System.user_os
66
83
  end
67
84
 
68
85
  def map_name_to_valid_font_names
@@ -71,11 +88,38 @@ module Fontist
71
88
  end
72
89
 
73
90
  def system_path_file
74
- File.open(Fontist.lib_path.join("fontist", "system.yml"))
91
+ File.open(Fontist.system_file_path)
75
92
  end
76
93
 
77
94
  def default_sources
78
95
  @default_sources ||= YAML.load(system_path_file)["system"][user_os.to_s]
79
96
  end
97
+
98
+ def style_full_name(styles)
99
+ return if styles.empty?
100
+
101
+ s = styles.first
102
+ s[:style]["full_name"] || s[:font]["name"]
103
+ end
104
+
105
+ def style_paths(styles)
106
+ filenames = styles.map { |x| x[:style]["font"] }
107
+ paths = lookup_using_filenames(filenames)
108
+ return paths unless paths.empty?
109
+
110
+ grep_font_paths(font, style)
111
+ end
112
+
113
+ def lookup_using_filenames(filenames)
114
+ filenames.flat_map do |filename|
115
+ search_font_paths(filename)
116
+ end
117
+ end
118
+
119
+ def search_font_paths(filename)
120
+ font_paths.select do |path|
121
+ File.basename(path) == filename
122
+ end
123
+ end
80
124
  end
81
125
  end