fontisan 0.2.4 → 0.2.5
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +150 -30
- data/README.adoc +497 -242
- data/lib/fontisan/cli.rb +67 -6
- data/lib/fontisan/commands/validate_command.rb +107 -151
- data/lib/fontisan/converters/woff2_encoder.rb +7 -29
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
- data/lib/fontisan/tables/cmap.rb +82 -2
- data/lib/fontisan/tables/glyf.rb +118 -0
- data/lib/fontisan/tables/head.rb +60 -0
- data/lib/fontisan/tables/hhea.rb +74 -0
- data/lib/fontisan/tables/maxp.rb +60 -0
- data/lib/fontisan/tables/name.rb +76 -0
- data/lib/fontisan/tables/os2.rb +113 -0
- data/lib/fontisan/tables/post.rb +57 -0
- data/lib/fontisan/validators/basic_validator.rb +85 -0
- data/lib/fontisan/validators/font_book_validator.rb +130 -0
- data/lib/fontisan/validators/opentype_validator.rb +112 -0
- data/lib/fontisan/validators/profile_loader.rb +139 -0
- data/lib/fontisan/validators/validator.rb +484 -0
- data/lib/fontisan/validators/web_font_validator.rb +102 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +78 -6
- metadata +7 -11
- data/lib/fontisan/config/validation_rules.yml +0 -149
- data/lib/fontisan/validation/checksum_validator.rb +0 -170
- data/lib/fontisan/validation/consistency_validator.rb +0 -197
- data/lib/fontisan/validation/structure_validator.rb +0 -198
- data/lib/fontisan/validation/table_validator.rb +0 -158
- data/lib/fontisan/validation/validator.rb +0 -152
- data/lib/fontisan/validation/variable_font_validator.rb +0 -218
- data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
- data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
- data/lib/fontisan/validation/woff2_validator.rb +0 -248
data/lib/fontisan/tables/maxp.rb
CHANGED
|
@@ -157,6 +157,66 @@ module Fontisan
|
|
|
157
157
|
true
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
# Validation helper: Check if version is valid (0.5 or 1.0)
|
|
161
|
+
#
|
|
162
|
+
# @return [Boolean] True if version is 0.5 or 1.0
|
|
163
|
+
def valid_version?
|
|
164
|
+
version_0_5? || version_1_0?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Validation helper: Check if number of glyphs is valid
|
|
168
|
+
#
|
|
169
|
+
# Must be at least 1 (.notdef glyph must exist)
|
|
170
|
+
#
|
|
171
|
+
# @return [Boolean] True if num_glyphs >= 1
|
|
172
|
+
def valid_num_glyphs?
|
|
173
|
+
num_glyphs && num_glyphs >= 1
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Validation helper: Check if maxZones is valid (version 1.0 only)
|
|
177
|
+
#
|
|
178
|
+
# For TrueType fonts, maxZones must be 1 or 2
|
|
179
|
+
#
|
|
180
|
+
# @return [Boolean] True if maxZones is valid or not applicable
|
|
181
|
+
def valid_max_zones?
|
|
182
|
+
return true if version_0_5? # Not applicable for CFF
|
|
183
|
+
|
|
184
|
+
max_zones && max_zones.between?(1, 2)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Validation helper: Check if all TrueType metrics are present
|
|
188
|
+
#
|
|
189
|
+
# For version 1.0, all max* fields should be present
|
|
190
|
+
#
|
|
191
|
+
# @return [Boolean] True if all required fields are present
|
|
192
|
+
def has_truetype_metrics?
|
|
193
|
+
version_1_0? &&
|
|
194
|
+
!max_points.nil? &&
|
|
195
|
+
!max_contours.nil? &&
|
|
196
|
+
!max_composite_points.nil? &&
|
|
197
|
+
!max_composite_contours.nil?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Validation helper: Check if metrics are reasonable
|
|
201
|
+
#
|
|
202
|
+
# Checks that values don't exceed reasonable limits
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] True if metrics are within reasonable bounds
|
|
205
|
+
def reasonable_metrics?
|
|
206
|
+
# num_glyphs should not exceed 65535
|
|
207
|
+
return false if num_glyphs > 65535
|
|
208
|
+
|
|
209
|
+
if version_1_0?
|
|
210
|
+
# Check reasonable limits for TrueType metrics
|
|
211
|
+
# These are generous limits to allow for complex fonts
|
|
212
|
+
return false if max_points && max_points > 50000
|
|
213
|
+
return false if max_contours && max_contours > 10000
|
|
214
|
+
return false if max_stack_elements && max_stack_elements > 1000
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
true
|
|
218
|
+
end
|
|
219
|
+
|
|
160
220
|
# Validate the table and raise error if invalid
|
|
161
221
|
#
|
|
162
222
|
# @raise [Fontisan::CorruptedTableError] If table is invalid
|
data/lib/fontisan/tables/name.rb
CHANGED
|
@@ -199,6 +199,82 @@ module Fontisan
|
|
|
199
199
|
false
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
+
# Validation helper: Check if version is valid (0 or 1)
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] True if version is 0 or 1
|
|
205
|
+
def valid_version?
|
|
206
|
+
format == 0 || format == 1
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Validation helper: Check if encoding combinations are valid
|
|
210
|
+
#
|
|
211
|
+
# According to OpenType spec, certain platform/encoding combinations are valid:
|
|
212
|
+
# - Platform 0 (Unicode): encoding 0-6
|
|
213
|
+
# - Platform 1 (Mac): encoding 0-32
|
|
214
|
+
# - Platform 3 (Windows): encoding 0-10
|
|
215
|
+
#
|
|
216
|
+
# @return [Boolean] True if all encoding heuristics are valid
|
|
217
|
+
def valid_encoding_heuristics?
|
|
218
|
+
name_records.all? do |rec|
|
|
219
|
+
case rec.platform_id
|
|
220
|
+
when PLATFORM_UNICODE
|
|
221
|
+
rec.encoding_id.between?(0, 6)
|
|
222
|
+
when PLATFORM_MACINTOSH
|
|
223
|
+
rec.encoding_id.between?(0, 32)
|
|
224
|
+
when PLATFORM_WINDOWS
|
|
225
|
+
rec.encoding_id.between?(0, 10)
|
|
226
|
+
else
|
|
227
|
+
# Unknown platform - consider invalid
|
|
228
|
+
false
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Validation helper: Check if required platform combinations exist
|
|
234
|
+
#
|
|
235
|
+
# @param combos [Array<Array<Integer>>] Array of [platform_id, encoding_id, language_id] arrays
|
|
236
|
+
# @return [Boolean] True if all required combinations are present
|
|
237
|
+
#
|
|
238
|
+
# @example Check for Windows English name
|
|
239
|
+
# name.has_valid_platform_combos?([3, 1, 0x0409])
|
|
240
|
+
def has_valid_platform_combos?(*combos)
|
|
241
|
+
combos.all? do |platform_id, encoding_id, language_id|
|
|
242
|
+
name_records.any? do |rec|
|
|
243
|
+
rec.platform_id == platform_id &&
|
|
244
|
+
rec.encoding_id == encoding_id &&
|
|
245
|
+
rec.language_id == language_id
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Validation helper: Check if family name is present and non-empty
|
|
251
|
+
#
|
|
252
|
+
# @return [Boolean] True if family name exists and is not empty
|
|
253
|
+
def family_name_present?
|
|
254
|
+
name = english_name(FAMILY)
|
|
255
|
+
!name.nil? && !name.empty?
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Validation helper: Check if PostScript name is present and non-empty
|
|
259
|
+
#
|
|
260
|
+
# @return [Boolean] True if PostScript name exists and is not empty
|
|
261
|
+
def postscript_name_present?
|
|
262
|
+
name = english_name(POSTSCRIPT_NAME)
|
|
263
|
+
!name.nil? && !name.empty?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Validation helper: Check if PostScript name is valid
|
|
267
|
+
#
|
|
268
|
+
# PostScript names must contain only ASCII alphanumerics and hyphens
|
|
269
|
+
#
|
|
270
|
+
# @return [Boolean] True if PostScript name matches the required pattern
|
|
271
|
+
def postscript_name_valid?
|
|
272
|
+
name = english_name(POSTSCRIPT_NAME)
|
|
273
|
+
return false if name.nil? || name.empty?
|
|
274
|
+
|
|
275
|
+
name.match?(/^[A-Za-z0-9-]+$/)
|
|
276
|
+
end
|
|
277
|
+
|
|
202
278
|
private
|
|
203
279
|
|
|
204
280
|
# Find a name record matching the criteria
|
data/lib/fontisan/tables/os2.rb
CHANGED
|
@@ -170,6 +170,119 @@ module Fontisan
|
|
|
170
170
|
|
|
171
171
|
us_upper_optical_point_size / 20.0
|
|
172
172
|
end
|
|
173
|
+
|
|
174
|
+
# Validation helper: Check if version is valid
|
|
175
|
+
#
|
|
176
|
+
# Valid versions are 0 through 5
|
|
177
|
+
#
|
|
178
|
+
# @return [Boolean] True if version is 0-5
|
|
179
|
+
def valid_version?
|
|
180
|
+
version && version.between?(0, 5)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Validation helper: Check if weight class is valid
|
|
184
|
+
#
|
|
185
|
+
# Valid values are 1-1000, common values are multiples of 100
|
|
186
|
+
#
|
|
187
|
+
# @return [Boolean] True if weight class is valid
|
|
188
|
+
def valid_weight_class?
|
|
189
|
+
us_weight_class && us_weight_class.between?(1, 1000)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Validation helper: Check if width class is valid
|
|
193
|
+
#
|
|
194
|
+
# Valid values are 1-9
|
|
195
|
+
#
|
|
196
|
+
# @return [Boolean] True if width class is 1-9
|
|
197
|
+
def valid_width_class?
|
|
198
|
+
us_width_class && us_width_class.between?(1, 9)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Validation helper: Check if vendor ID is present
|
|
202
|
+
#
|
|
203
|
+
# Vendor ID should be a 4-character code
|
|
204
|
+
#
|
|
205
|
+
# @return [Boolean] True if vendor ID exists and is non-empty
|
|
206
|
+
def has_vendor_id?
|
|
207
|
+
!vendor_id.empty?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Validation helper: Check if typo metrics are reasonable
|
|
211
|
+
#
|
|
212
|
+
# Ascent should be positive, descender negative, line gap non-negative
|
|
213
|
+
#
|
|
214
|
+
# @return [Boolean] True if typo metrics have correct signs
|
|
215
|
+
def valid_typo_metrics?
|
|
216
|
+
s_typo_ascender > 0 && s_typo_descender < 0 && s_typo_line_gap >= 0
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Validation helper: Check if Win metrics are valid
|
|
220
|
+
#
|
|
221
|
+
# Both should be positive (unsigned in spec)
|
|
222
|
+
#
|
|
223
|
+
# @return [Boolean] True if Win ascent and descent are positive
|
|
224
|
+
def valid_win_metrics?
|
|
225
|
+
us_win_ascent > 0 && us_win_descent > 0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Validation helper: Check if Unicode ranges are set
|
|
229
|
+
#
|
|
230
|
+
# At least one Unicode range bit should be set
|
|
231
|
+
#
|
|
232
|
+
# @return [Boolean] True if any Unicode range bits are set
|
|
233
|
+
def has_unicode_ranges?
|
|
234
|
+
(ul_unicode_range1 | ul_unicode_range2 | ul_unicode_range3 | ul_unicode_range4) != 0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Validation helper: Check if PANOSE data is present
|
|
238
|
+
#
|
|
239
|
+
# All PANOSE values should not be zero
|
|
240
|
+
#
|
|
241
|
+
# @return [Boolean] True if PANOSE seems to be set
|
|
242
|
+
def has_panose?
|
|
243
|
+
panose && panose.any? { |val| val != 0 }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Validation helper: Check if embedding permissions are set
|
|
247
|
+
#
|
|
248
|
+
# fs_type indicates embedding and subsetting permissions
|
|
249
|
+
#
|
|
250
|
+
# @return [Boolean] True if embedding permissions are defined
|
|
251
|
+
def has_embedding_permissions?
|
|
252
|
+
!fs_type.nil?
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Validation helper: Check if selection flags are valid
|
|
256
|
+
#
|
|
257
|
+
# Checks for valid combinations of selection flags
|
|
258
|
+
#
|
|
259
|
+
# @return [Boolean] True if fs_selection has valid flags
|
|
260
|
+
def valid_selection_flags?
|
|
261
|
+
return false if fs_selection.nil?
|
|
262
|
+
|
|
263
|
+
# Bits 0-9 are defined, others should be zero
|
|
264
|
+
(fs_selection & 0xFC00).zero?
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Validation helper: Check if x_height and cap_height are present (v2+)
|
|
268
|
+
#
|
|
269
|
+
# For version 2+, these should be set
|
|
270
|
+
#
|
|
271
|
+
# @return [Boolean] True if metrics are present (or not required)
|
|
272
|
+
def has_x_height_cap_height?
|
|
273
|
+
return true if version < 2 # Not required for v0-1
|
|
274
|
+
|
|
275
|
+
!sx_height.nil? && !s_cap_height.nil? && sx_height > 0 && s_cap_height > 0
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Validation helper: Check if first/last char indices are reasonable
|
|
279
|
+
#
|
|
280
|
+
# first should be <= last
|
|
281
|
+
#
|
|
282
|
+
# @return [Boolean] True if character range is valid
|
|
283
|
+
def valid_char_range?
|
|
284
|
+
us_first_char_index <= us_last_char_index
|
|
285
|
+
end
|
|
173
286
|
end
|
|
174
287
|
end
|
|
175
288
|
end
|
data/lib/fontisan/tables/post.rb
CHANGED
|
@@ -143,6 +143,63 @@ module Fontisan
|
|
|
143
143
|
end
|
|
144
144
|
end
|
|
145
145
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
146
|
+
|
|
147
|
+
public
|
|
148
|
+
|
|
149
|
+
# Validation helper: Check if version is valid
|
|
150
|
+
#
|
|
151
|
+
# Common versions: 1.0, 2.0, 2.5, 3.0, 4.0
|
|
152
|
+
#
|
|
153
|
+
# @return [Boolean] True if version is recognized
|
|
154
|
+
def valid_version?
|
|
155
|
+
[1.0, 2.0, 2.5, 3.0, 4.0].include?(version)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Validation helper: Check if italic angle is reasonable
|
|
159
|
+
#
|
|
160
|
+
# Italic angle should be between -60 and 60 degrees
|
|
161
|
+
#
|
|
162
|
+
# @return [Boolean] True if italic angle is within reasonable bounds
|
|
163
|
+
def valid_italic_angle?
|
|
164
|
+
italic_angle.abs <= 60.0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Validation helper: Check if underline values are present
|
|
168
|
+
#
|
|
169
|
+
# Both position and thickness should be non-zero for valid underline
|
|
170
|
+
#
|
|
171
|
+
# @return [Boolean] True if underline metrics exist
|
|
172
|
+
def has_underline_metrics?
|
|
173
|
+
underline_position != 0 && underline_thickness != 0
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Validation helper: Check if fixed pitch flag is consistent
|
|
177
|
+
#
|
|
178
|
+
# @return [Boolean] True if is_fixed_pitch is 0 or 1
|
|
179
|
+
def valid_fixed_pitch_flag?
|
|
180
|
+
is_fixed_pitch == 0 || is_fixed_pitch == 1
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Validation helper: Check if glyph names are available
|
|
184
|
+
#
|
|
185
|
+
# For versions 1.0 and 2.0, glyph names should be accessible
|
|
186
|
+
#
|
|
187
|
+
# @return [Boolean] True if glyph names can be retrieved
|
|
188
|
+
def has_glyph_names?
|
|
189
|
+
names = glyph_names
|
|
190
|
+
!names.nil? && !names.empty?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Validation helper: Check if version 2.0 data is complete
|
|
194
|
+
#
|
|
195
|
+
# For version 2.0, we should have glyph count and name data
|
|
196
|
+
#
|
|
197
|
+
# @return [Boolean] True if version 2.0 data is present and complete
|
|
198
|
+
def complete_version_2_data?
|
|
199
|
+
return true unless version == 2.0
|
|
200
|
+
|
|
201
|
+
!num_glyphs_v2.nil? && num_glyphs_v2 > 0 && !remaining_data.empty?
|
|
202
|
+
end
|
|
146
203
|
end
|
|
147
204
|
end
|
|
148
205
|
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "validator"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validators
|
|
7
|
+
# BasicValidator provides minimal validation for fast font indexing
|
|
8
|
+
#
|
|
9
|
+
# This validator implements only essential checks needed for font discovery
|
|
10
|
+
# and indexing systems (e.g., Fontist). It is optimized for speed with a
|
|
11
|
+
# target performance of < 50ms per font.
|
|
12
|
+
#
|
|
13
|
+
# The validator checks only critical font identification and structural
|
|
14
|
+
# integrity, making it suitable for:
|
|
15
|
+
# - Font discovery and indexing
|
|
16
|
+
# - Quick font database updates
|
|
17
|
+
# - Large-scale font scanning
|
|
18
|
+
#
|
|
19
|
+
# @example Using BasicValidator
|
|
20
|
+
# validator = BasicValidator.new
|
|
21
|
+
# report = validator.validate(font)
|
|
22
|
+
# puts "Font is valid for indexing" if report.valid?
|
|
23
|
+
#
|
|
24
|
+
# @example Target performance
|
|
25
|
+
# # Should complete in < 50ms
|
|
26
|
+
# start_time = Time.now
|
|
27
|
+
# report = BasicValidator.new.validate(font)
|
|
28
|
+
# elapsed = Time.now - start_time
|
|
29
|
+
# puts "Validated in #{elapsed * 1000}ms"
|
|
30
|
+
class BasicValidator < Validator
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Define essential validation checks
|
|
34
|
+
#
|
|
35
|
+
# This validator implements 8 checks covering:
|
|
36
|
+
# - Required tables presence
|
|
37
|
+
# - Name table identification
|
|
38
|
+
# - Head table integrity
|
|
39
|
+
# - Maxp table glyph count
|
|
40
|
+
#
|
|
41
|
+
# All checks use helpers from Week 1 table implementations.
|
|
42
|
+
def define_checks
|
|
43
|
+
# Check 1: Required tables must be present
|
|
44
|
+
check_structure :required_tables, severity: :error do |font|
|
|
45
|
+
%w[name head maxp hhea].all? { |tag| font.table(tag) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check 2: Name table version must be valid (0 or 1)
|
|
49
|
+
check_table :name_version, "name", severity: :error do |table|
|
|
50
|
+
table.valid_version?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check 3: Family name must be present and non-empty
|
|
54
|
+
check_table :family_name, "name", severity: :error do |table|
|
|
55
|
+
table.family_name_present?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check 4: PostScript name must be valid (alphanumeric + hyphens)
|
|
59
|
+
check_table :postscript_name, "name", severity: :error do |table|
|
|
60
|
+
table.postscript_name_valid?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check 5: Head table magic number must be correct
|
|
64
|
+
check_table :head_magic, "head", severity: :error do |table|
|
|
65
|
+
table.valid_magic?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check 6: Units per em must be valid (16-16384)
|
|
69
|
+
check_table :units_per_em, "head", severity: :error do |table|
|
|
70
|
+
table.valid_units_per_em?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check 7: Number of glyphs must be at least 1 (.notdef)
|
|
74
|
+
check_table :num_glyphs, "maxp", severity: :error do |table|
|
|
75
|
+
table.valid_num_glyphs?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check 8: Maxp metrics should be reasonable (not absurd values)
|
|
79
|
+
check_table :reasonable_metrics, "maxp", severity: :warning do |table|
|
|
80
|
+
table.reasonable_metrics?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "basic_validator"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validators
|
|
7
|
+
# FontBookValidator provides macOS Font Book installation compatibility checks
|
|
8
|
+
#
|
|
9
|
+
# This validator extends BasicValidator with additional checks needed for
|
|
10
|
+
# fonts to be successfully installed and used in macOS Font Book. It ensures
|
|
11
|
+
# proper encoding combinations, OS/2 metrics, and other macOS-specific
|
|
12
|
+
# requirements.
|
|
13
|
+
#
|
|
14
|
+
# The validator inherits all 8 checks from BasicValidator and adds 12 new
|
|
15
|
+
# checks focusing on:
|
|
16
|
+
# - Name table encoding combinations (Windows and Mac)
|
|
17
|
+
# - OS/2 table metrics and metadata
|
|
18
|
+
# - Head table bounding box
|
|
19
|
+
# - Hhea table metrics
|
|
20
|
+
# - Post table metadata
|
|
21
|
+
# - Cmap subtables
|
|
22
|
+
#
|
|
23
|
+
# @example Using FontBookValidator
|
|
24
|
+
# validator = FontBookValidator.new
|
|
25
|
+
# report = validator.validate(font)
|
|
26
|
+
# puts "Font is Font Book compatible" if report.valid?
|
|
27
|
+
class FontBookValidator < BasicValidator
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Define Font Book compatibility checks
|
|
31
|
+
#
|
|
32
|
+
# Calls super to inherit BasicValidator's 8 checks, then adds 12 new checks.
|
|
33
|
+
# All checks use helpers from Week 1 table implementations.
|
|
34
|
+
def define_checks
|
|
35
|
+
# Inherit BasicValidator checks (8 checks)
|
|
36
|
+
super
|
|
37
|
+
|
|
38
|
+
# Check 9: Name table Windows Unicode English encoding
|
|
39
|
+
check_table :name_windows_encoding, 'name', severity: :error do |table|
|
|
40
|
+
table.has_valid_platform_combos?([3, 1, 0x0409]) # Windows Unicode English
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check 10: Name table Mac Roman English encoding
|
|
44
|
+
check_table :name_mac_encoding, 'name', severity: :warning do |table|
|
|
45
|
+
table.has_valid_platform_combos?([1, 0, 0]) # Mac Roman English
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check 11: OS/2 table version must be valid
|
|
49
|
+
check_table :os2_version, 'OS/2', severity: :error do |table|
|
|
50
|
+
table.valid_version?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check 12: OS/2 weight class must be valid (1-1000)
|
|
54
|
+
check_table :os2_weight_class, 'OS/2', severity: :error do |table|
|
|
55
|
+
table.valid_weight_class?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check 13: OS/2 width class must be valid (1-9)
|
|
59
|
+
check_table :os2_width_class, 'OS/2', severity: :error do |table|
|
|
60
|
+
table.valid_width_class?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check 14: OS/2 vendor ID should be present
|
|
64
|
+
check_table :os2_vendor_id, 'OS/2', severity: :warning do |table|
|
|
65
|
+
table.has_vendor_id?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check 15: OS/2 PANOSE classification should be present
|
|
69
|
+
check_table :os2_panose, 'OS/2', severity: :info do |table|
|
|
70
|
+
table.has_panose?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check 16: OS/2 typographic metrics must be valid
|
|
74
|
+
check_table :os2_typo_metrics, 'OS/2', severity: :error do |table|
|
|
75
|
+
table.valid_typo_metrics?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check 17: OS/2 Windows metrics must be valid
|
|
79
|
+
check_table :os2_win_metrics, 'OS/2', severity: :error do |table|
|
|
80
|
+
table.valid_win_metrics?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Check 18: OS/2 Unicode ranges should be present
|
|
84
|
+
check_table :os2_unicode_ranges, 'OS/2', severity: :warning do |table|
|
|
85
|
+
table.has_unicode_ranges?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check 19: Head table bounding box must be valid
|
|
89
|
+
check_table :head_bounding_box, 'head', severity: :error do |table|
|
|
90
|
+
table.valid_bounding_box?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check 20: Hhea ascent/descent must be valid
|
|
94
|
+
check_table :hhea_ascent_descent, 'hhea', severity: :error do |table|
|
|
95
|
+
table.valid_ascent_descent?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check 21: Hhea line gap should be valid
|
|
99
|
+
check_table :hhea_line_gap, 'hhea', severity: :warning do |table|
|
|
100
|
+
table.valid_line_gap?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Check 22: Hhea horizontal metrics count must be valid
|
|
104
|
+
check_table :hhea_metrics_count, 'hhea', severity: :error do |table|
|
|
105
|
+
table.valid_number_of_h_metrics?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check 23: Post table version must be valid
|
|
109
|
+
check_table :post_version, 'post', severity: :error do |table|
|
|
110
|
+
table.valid_version?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Check 24: Post table italic angle should be valid
|
|
114
|
+
check_table :post_italic_angle, 'post', severity: :warning do |table|
|
|
115
|
+
table.valid_italic_angle?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Check 25: Post table underline metrics should be present
|
|
119
|
+
check_table :post_underline, 'post', severity: :info do |table|
|
|
120
|
+
table.has_underline_metrics?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check 26: Cmap table must have subtables
|
|
124
|
+
check_table :cmap_subtables, 'cmap', severity: :error do |table|
|
|
125
|
+
table.has_subtables?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "font_book_validator"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validators
|
|
7
|
+
# OpenTypeValidator provides comprehensive OpenType specification compliance checks
|
|
8
|
+
#
|
|
9
|
+
# This validator extends FontBookValidator with additional checks ensuring full
|
|
10
|
+
# OpenType specification compliance. It validates glyph data, character mappings,
|
|
11
|
+
# and cross-table consistency.
|
|
12
|
+
#
|
|
13
|
+
# The validator inherits all checks from FontBookValidator (18 checks from
|
|
14
|
+
# FontBookValidator + 8 from BasicValidator = 26 total) and adds 10 new checks:
|
|
15
|
+
# - Maxp TrueType metrics validation
|
|
16
|
+
# - Glyf table structure and accessibility
|
|
17
|
+
# - Cmap Unicode mapping and coverage
|
|
18
|
+
# - Cross-table consistency checks
|
|
19
|
+
#
|
|
20
|
+
# @example Using OpenTypeValidator
|
|
21
|
+
# validator = OpenTypeValidator.new
|
|
22
|
+
# report = validator.validate(font)
|
|
23
|
+
# puts "Font is OpenType compliant" if report.valid?
|
|
24
|
+
class OpenTypeValidator < FontBookValidator
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Define OpenType specification compliance checks
|
|
28
|
+
#
|
|
29
|
+
# Calls super to inherit FontBookValidator's checks, then adds 10 new checks.
|
|
30
|
+
# All checks use helpers from Week 1 table implementations.
|
|
31
|
+
def define_checks
|
|
32
|
+
# Inherit FontBookValidator checks (26 checks total)
|
|
33
|
+
super
|
|
34
|
+
|
|
35
|
+
# Check 27: Maxp TrueType metrics (only for version 1.0)
|
|
36
|
+
check_table :maxp_truetype_metrics, 'maxp', severity: :warning do |table|
|
|
37
|
+
!table.version_1_0? || table.has_truetype_metrics?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check 28: Maxp max zones must be valid
|
|
41
|
+
check_table :maxp_zones, 'maxp', severity: :error do |table|
|
|
42
|
+
table.valid_max_zones?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check 29: Glyf glyphs must be accessible (TrueType fonts only)
|
|
46
|
+
check_glyphs :glyf_accessible, severity: :error do |font|
|
|
47
|
+
glyf = font.table('glyf')
|
|
48
|
+
next true unless glyf # Skip if CFF font
|
|
49
|
+
|
|
50
|
+
loca = font.table('loca')
|
|
51
|
+
head = font.table('head')
|
|
52
|
+
maxp = font.table('maxp')
|
|
53
|
+
glyf.all_glyphs_accessible?(loca, head, maxp.num_glyphs)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check 30: Glyf glyphs should not be clipped
|
|
57
|
+
check_glyphs :glyf_no_clipping, severity: :warning do |font|
|
|
58
|
+
glyf = font.table('glyf')
|
|
59
|
+
next true unless glyf
|
|
60
|
+
|
|
61
|
+
loca = font.table('loca')
|
|
62
|
+
head = font.table('head')
|
|
63
|
+
maxp = font.table('maxp')
|
|
64
|
+
glyf.no_clipped_glyphs?(loca, head, maxp.num_glyphs)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check 31: Glyf contour counts must be valid
|
|
68
|
+
check_glyphs :glyf_valid_contours, severity: :error do |font|
|
|
69
|
+
glyf = font.table('glyf')
|
|
70
|
+
next true unless glyf
|
|
71
|
+
|
|
72
|
+
loca = font.table('loca')
|
|
73
|
+
head = font.table('head')
|
|
74
|
+
maxp = font.table('maxp')
|
|
75
|
+
|
|
76
|
+
(0...maxp.num_glyphs).all? do |glyph_id|
|
|
77
|
+
glyf.valid_contour_count?(glyph_id, loca, head)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check 32: Cmap must have Unicode mapping
|
|
82
|
+
check_table :cmap_unicode_mapping, 'cmap', severity: :error do |table|
|
|
83
|
+
table.has_unicode_mapping?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check 33: Cmap should have BMP coverage
|
|
87
|
+
check_table :cmap_bmp_coverage, 'cmap', severity: :warning do |table|
|
|
88
|
+
table.has_bmp_coverage?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check 34: Cmap must have format 4 subtable
|
|
92
|
+
check_table :cmap_format4, 'cmap', severity: :error do |table|
|
|
93
|
+
table.has_format_4_subtable?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Check 35: Cmap glyph indices must be valid
|
|
97
|
+
check_structure :cmap_glyph_indices, severity: :error do |font|
|
|
98
|
+
cmap = font.table('cmap')
|
|
99
|
+
maxp = font.table('maxp')
|
|
100
|
+
cmap.valid_glyph_indices?(maxp.num_glyphs)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Check 36: Table checksums (info level - many fonts have mismatches)
|
|
104
|
+
check_structure :checksum_valid, severity: :info do |font|
|
|
105
|
+
# Table checksum validation (info level - for reference)
|
|
106
|
+
# Most fonts have checksum mismatches, so we make it info not error
|
|
107
|
+
true # Placeholder - actual checksum validation if desired
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|