fontisan 0.2.6 → 0.2.8
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.yml +103 -0
- data/.rubocop_todo.yml +107 -318
- data/Gemfile +1 -1
- data/README.adoc +127 -17
- data/Rakefile +12 -7
- data/benchmark/variation_quick_bench.rb +1 -1
- data/lib/fontisan/base_collection.rb +5 -33
- data/lib/fontisan/cli.rb +45 -13
- data/lib/fontisan/collection/dfont_builder.rb +2 -1
- data/lib/fontisan/collection/shared_logic.rb +54 -0
- data/lib/fontisan/commands/convert_command.rb +2 -4
- data/lib/fontisan/commands/info_command.rb +3 -3
- data/lib/fontisan/commands/pack_command.rb +2 -1
- data/lib/fontisan/commands/validate_command.rb +157 -6
- data/lib/fontisan/converters/collection_converter.rb +22 -13
- data/lib/fontisan/converters/svg_generator.rb +2 -1
- data/lib/fontisan/converters/woff2_encoder.rb +6 -6
- data/lib/fontisan/converters/woff_writer.rb +3 -1
- data/lib/fontisan/dfont_collection.rb +84 -0
- data/lib/fontisan/font_loader.rb +9 -9
- data/lib/fontisan/formatters/text_formatter.rb +18 -14
- data/lib/fontisan/hints/hint_converter.rb +1 -1
- data/lib/fontisan/hints/hint_validator.rb +13 -10
- data/lib/fontisan/hints/truetype_instruction_analyzer.rb +15 -8
- data/lib/fontisan/hints/truetype_instruction_generator.rb +1 -1
- data/lib/fontisan/models/collection_validation_report.rb +104 -0
- data/lib/fontisan/models/font_report.rb +24 -0
- data/lib/fontisan/models/validation_report.rb +7 -2
- data/lib/fontisan/open_type_font.rb +2 -3
- data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +6 -2
- data/lib/fontisan/subset/glyph_mapping.rb +2 -0
- data/lib/fontisan/subset/table_subsetter.rb +2 -2
- data/lib/fontisan/tables/cblc.rb +8 -4
- data/lib/fontisan/tables/cff/index.rb +2 -0
- data/lib/fontisan/tables/cff.rb +6 -3
- data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +1 -1
- data/lib/fontisan/tables/cff2.rb +1 -1
- data/lib/fontisan/tables/cmap.rb +5 -5
- data/lib/fontisan/tables/glyf.rb +8 -10
- data/lib/fontisan/tables/head.rb +3 -3
- data/lib/fontisan/tables/hhea.rb +4 -4
- data/lib/fontisan/tables/maxp.rb +2 -2
- data/lib/fontisan/tables/name.rb +1 -1
- data/lib/fontisan/tables/os2.rb +8 -8
- data/lib/fontisan/tables/post.rb +2 -2
- data/lib/fontisan/tables/sbix.rb +5 -4
- data/lib/fontisan/true_type_font.rb +2 -3
- data/lib/fontisan/utilities/checksum_calculator.rb +0 -44
- data/lib/fontisan/validation/collection_validator.rb +4 -2
- data/lib/fontisan/validators/basic_validator.rb +11 -21
- data/lib/fontisan/validators/font_book_validator.rb +29 -50
- data/lib/fontisan/validators/opentype_validator.rb +24 -28
- data/lib/fontisan/validators/validator.rb +87 -66
- data/lib/fontisan/validators/web_font_validator.rb +16 -21
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/glyf_transformer.rb +31 -8
- data/lib/fontisan/woff2/hmtx_transformer.rb +2 -1
- data/lib/fontisan/woff2/table_transformer.rb +4 -2
- data/lib/fontisan/woff2_font.rb +4 -2
- data/lib/fontisan/woff_font.rb +2 -2
- data/lib/fontisan.rb +2 -2
- data/scripts/compare_stack_aware.rb +1 -1
- data/scripts/measure_optimization.rb +1 -2
- metadata +5 -2
data/lib/fontisan/tables/os2.rb
CHANGED
|
@@ -177,7 +177,7 @@ module Fontisan
|
|
|
177
177
|
#
|
|
178
178
|
# @return [Boolean] True if version is 0-5
|
|
179
179
|
def valid_version?
|
|
180
|
-
version
|
|
180
|
+
version&.between?(0, 5)
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
# Validation helper: Check if weight class is valid
|
|
@@ -186,7 +186,7 @@ module Fontisan
|
|
|
186
186
|
#
|
|
187
187
|
# @return [Boolean] True if weight class is valid
|
|
188
188
|
def valid_weight_class?
|
|
189
|
-
us_weight_class
|
|
189
|
+
us_weight_class&.between?(1, 1000)
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
# Validation helper: Check if width class is valid
|
|
@@ -195,7 +195,7 @@ module Fontisan
|
|
|
195
195
|
#
|
|
196
196
|
# @return [Boolean] True if width class is 1-9
|
|
197
197
|
def valid_width_class?
|
|
198
|
-
us_width_class
|
|
198
|
+
us_width_class&.between?(1, 9)
|
|
199
199
|
end
|
|
200
200
|
|
|
201
201
|
# Validation helper: Check if vendor ID is present
|
|
@@ -213,7 +213,7 @@ module Fontisan
|
|
|
213
213
|
#
|
|
214
214
|
# @return [Boolean] True if typo metrics have correct signs
|
|
215
215
|
def valid_typo_metrics?
|
|
216
|
-
s_typo_ascender
|
|
216
|
+
s_typo_ascender.positive? && s_typo_descender.negative? && s_typo_line_gap >= 0
|
|
217
217
|
end
|
|
218
218
|
|
|
219
219
|
# Validation helper: Check if Win metrics are valid
|
|
@@ -222,7 +222,7 @@ module Fontisan
|
|
|
222
222
|
#
|
|
223
223
|
# @return [Boolean] True if Win ascent and descent are positive
|
|
224
224
|
def valid_win_metrics?
|
|
225
|
-
us_win_ascent
|
|
225
|
+
us_win_ascent.positive? && us_win_descent.positive?
|
|
226
226
|
end
|
|
227
227
|
|
|
228
228
|
# Validation helper: Check if Unicode ranges are set
|
|
@@ -240,7 +240,7 @@ module Fontisan
|
|
|
240
240
|
#
|
|
241
241
|
# @return [Boolean] True if PANOSE seems to be set
|
|
242
242
|
def has_panose?
|
|
243
|
-
panose
|
|
243
|
+
panose&.any? { |val| val != 0 }
|
|
244
244
|
end
|
|
245
245
|
|
|
246
246
|
# Validation helper: Check if embedding permissions are set
|
|
@@ -270,9 +270,9 @@ module Fontisan
|
|
|
270
270
|
#
|
|
271
271
|
# @return [Boolean] True if metrics are present (or not required)
|
|
272
272
|
def has_x_height_cap_height?
|
|
273
|
-
return true if version < 2
|
|
273
|
+
return true if version < 2 # Not required for v0-1
|
|
274
274
|
|
|
275
|
-
!sx_height.nil? && !s_cap_height.nil? && sx_height
|
|
275
|
+
!sx_height.nil? && !s_cap_height.nil? && sx_height.positive? && s_cap_height.positive?
|
|
276
276
|
end
|
|
277
277
|
|
|
278
278
|
# Validation helper: Check if first/last char indices are reasonable
|
data/lib/fontisan/tables/post.rb
CHANGED
|
@@ -177,7 +177,7 @@ module Fontisan
|
|
|
177
177
|
#
|
|
178
178
|
# @return [Boolean] True if is_fixed_pitch is 0 or 1
|
|
179
179
|
def valid_fixed_pitch_flag?
|
|
180
|
-
|
|
180
|
+
[0, 1].include?(is_fixed_pitch)
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
# Validation helper: Check if glyph names are available
|
|
@@ -198,7 +198,7 @@ module Fontisan
|
|
|
198
198
|
def complete_version_2_data?
|
|
199
199
|
return true unless version == 2.0
|
|
200
200
|
|
|
201
|
-
!num_glyphs_v2.nil? && num_glyphs_v2
|
|
201
|
+
!num_glyphs_v2.nil? && num_glyphs_v2.positive? && !remaining_data.empty?
|
|
202
202
|
end
|
|
203
203
|
end
|
|
204
204
|
end
|
data/lib/fontisan/tables/sbix.rb
CHANGED
|
@@ -166,7 +166,8 @@ module Fontisan
|
|
|
166
166
|
# Sample first few glyphs to detect formats
|
|
167
167
|
strike[:graphic_types]&.each do |type|
|
|
168
168
|
format_name = GRAPHIC_TYPE_NAMES[type]
|
|
169
|
-
formats << format_name if format_name && !["dupe",
|
|
169
|
+
formats << format_name if format_name && !["dupe",
|
|
170
|
+
"mask"].include?(format_name)
|
|
170
171
|
end
|
|
171
172
|
end
|
|
172
173
|
formats.uniq.compact
|
|
@@ -312,12 +313,12 @@ module Fontisan
|
|
|
312
313
|
|
|
313
314
|
# Check if offsets are valid
|
|
314
315
|
next if glyph_offset >= strike_size || next_glyph_offset > strike_size
|
|
315
|
-
next if next_glyph_offset <= glyph_offset
|
|
316
|
+
next if next_glyph_offset <= glyph_offset # Empty glyph
|
|
316
317
|
|
|
317
318
|
# Calculate absolute offset in table
|
|
318
319
|
# glyph_offset is relative to strike start, so add strike_offset
|
|
319
320
|
absolute_offset = strike_offset + glyph_offset
|
|
320
|
-
next if absolute_offset + 8 > raw_data.length
|
|
321
|
+
next if absolute_offset + 8 > raw_data.length # Need at least header
|
|
321
322
|
|
|
322
323
|
# Read graphic type (skip originOffsetX and originOffsetY = 4 bytes)
|
|
323
324
|
io = StringIO.new(raw_data)
|
|
@@ -345,7 +346,7 @@ module Fontisan
|
|
|
345
346
|
next_offset = strike[:glyph_offsets][glyph_id + 1]
|
|
346
347
|
|
|
347
348
|
return nil unless offset && next_offset
|
|
348
|
-
return nil if next_offset <= offset
|
|
349
|
+
return nil if next_offset <= offset # Empty glyph
|
|
349
350
|
|
|
350
351
|
# Calculate absolute position in table
|
|
351
352
|
absolute_offset = strike[:base_offset] + offset
|
|
@@ -563,10 +563,9 @@ module Fontisan
|
|
|
563
563
|
# @param path [String] Path to the TTF file
|
|
564
564
|
# @return [void]
|
|
565
565
|
def update_checksum_adjustment_in_file(path)
|
|
566
|
-
# Use tempfile-based checksum calculation for Windows compatibility
|
|
567
|
-
# This keeps the tempfile alive until we're done with the checksum
|
|
568
566
|
File.open(path, "r+b") do |io|
|
|
569
|
-
checksum
|
|
567
|
+
# Calculate checksum directly from IO to avoid Windows Tempfile issues
|
|
568
|
+
checksum = Utilities::ChecksumCalculator.calculate_checksum_from_io(io)
|
|
570
569
|
|
|
571
570
|
# Calculate adjustment
|
|
572
571
|
adjustment = Utilities::ChecksumCalculator.calculate_adjustment(checksum)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "stringio"
|
|
4
|
-
require "tempfile"
|
|
5
4
|
require_relative "../constants"
|
|
6
5
|
|
|
7
6
|
module Fontisan
|
|
@@ -101,49 +100,6 @@ module Fontisan
|
|
|
101
100
|
|
|
102
101
|
sum
|
|
103
102
|
end
|
|
104
|
-
|
|
105
|
-
# Calculate checksum from an IO object using a tempfile for Windows compatibility.
|
|
106
|
-
#
|
|
107
|
-
# This method creates a temporary file from the IO content to ensure proper
|
|
108
|
-
# file handle semantics on Windows, where file handles must remain open
|
|
109
|
-
# for checksum calculation. The tempfile reference is returned alongside
|
|
110
|
-
# the checksum to prevent premature garbage collection on Windows.
|
|
111
|
-
#
|
|
112
|
-
# @param io [IO] the IO object to read from (must be rewindable)
|
|
113
|
-
# @return [Array<Integer, Tempfile>] array containing [checksum, tempfile]
|
|
114
|
-
# The checksum value and the tempfile that must be kept alive until
|
|
115
|
-
# the caller is done with the checksum.
|
|
116
|
-
#
|
|
117
|
-
# @example
|
|
118
|
-
# checksum, tmpfile = ChecksumCalculator.calculate_checksum_from_io_with_tempfile(io)
|
|
119
|
-
# # Use checksum...
|
|
120
|
-
# # tmpfile will be GC'd when it goes out of scope, which is safe
|
|
121
|
-
#
|
|
122
|
-
# @note On Windows, Ruby's Tempfile automatically deletes the temp file when
|
|
123
|
-
# the Tempfile object is garbage collected. In multi-threaded environments,
|
|
124
|
-
# this can cause PermissionDenied errors if the file is deleted while
|
|
125
|
-
# another thread is still using it. By returning the tempfile reference,
|
|
126
|
-
# the caller can ensure it remains alive until all operations complete.
|
|
127
|
-
def self.calculate_checksum_from_io_with_tempfile(io)
|
|
128
|
-
io.rewind
|
|
129
|
-
|
|
130
|
-
# Create a tempfile to handle Windows file locking issues
|
|
131
|
-
tmpfile = Tempfile.new(["font", ".ttf"])
|
|
132
|
-
tmpfile.binmode
|
|
133
|
-
|
|
134
|
-
# Copy IO content to tempfile
|
|
135
|
-
IO.copy_stream(io, tmpfile)
|
|
136
|
-
tmpfile.close
|
|
137
|
-
|
|
138
|
-
# Calculate checksum from the tempfile
|
|
139
|
-
checksum = calculate_file_checksum(tmpfile.path)
|
|
140
|
-
|
|
141
|
-
# Return both checksum and tempfile to keep it alive
|
|
142
|
-
# The caller must keep the tempfile reference until done with checksum
|
|
143
|
-
[checksum, tmpfile]
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
private_class_method :calculate_checksum_from_io
|
|
147
103
|
end
|
|
148
104
|
end
|
|
149
105
|
end
|
|
@@ -68,7 +68,8 @@ module Fontisan
|
|
|
68
68
|
issues = []
|
|
69
69
|
|
|
70
70
|
return ["Font array cannot be empty"] if fonts.nil? || fonts.empty?
|
|
71
|
-
return ["Invalid format: #{format}"] unless %i[ttc otc
|
|
71
|
+
return ["Invalid format: #{format}"] unless %i[ttc otc
|
|
72
|
+
dfont].include?(format)
|
|
72
73
|
|
|
73
74
|
case format
|
|
74
75
|
when :ttc
|
|
@@ -100,7 +101,8 @@ module Fontisan
|
|
|
100
101
|
# @raise [ArgumentError] if invalid
|
|
101
102
|
def validate_format!(format)
|
|
102
103
|
unless %i[ttc otc dfont].include?(format)
|
|
103
|
-
raise ArgumentError,
|
|
104
|
+
raise ArgumentError,
|
|
105
|
+
"Invalid format: #{format}. Must be :ttc, :otc, or :dfont"
|
|
104
106
|
end
|
|
105
107
|
end
|
|
106
108
|
|
|
@@ -46,39 +46,29 @@ module Fontisan
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Check 2: Name table version must be valid (0 or 1)
|
|
49
|
-
check_table :name_version, "name", severity: :error
|
|
50
|
-
table.valid_version?
|
|
51
|
-
end
|
|
49
|
+
check_table :name_version, "name", severity: :error, &:valid_version?
|
|
52
50
|
|
|
53
51
|
# Check 3: Family name must be present and non-empty
|
|
54
|
-
check_table :family_name, "name", severity: :error
|
|
55
|
-
|
|
56
|
-
end
|
|
52
|
+
check_table :family_name, "name", severity: :error,
|
|
53
|
+
&:family_name_present?
|
|
57
54
|
|
|
58
55
|
# Check 4: PostScript name must be valid (alphanumeric + hyphens)
|
|
59
|
-
check_table :postscript_name, "name", severity: :error
|
|
60
|
-
|
|
61
|
-
end
|
|
56
|
+
check_table :postscript_name, "name", severity: :error,
|
|
57
|
+
&:postscript_name_valid?
|
|
62
58
|
|
|
63
59
|
# Check 5: Head table magic number must be correct
|
|
64
|
-
check_table :head_magic, "head", severity: :error
|
|
65
|
-
table.valid_magic?
|
|
66
|
-
end
|
|
60
|
+
check_table :head_magic, "head", severity: :error, &:valid_magic?
|
|
67
61
|
|
|
68
62
|
# Check 6: Units per em must be valid (16-16384)
|
|
69
|
-
check_table :units_per_em, "head", severity: :error
|
|
70
|
-
|
|
71
|
-
end
|
|
63
|
+
check_table :units_per_em, "head", severity: :error,
|
|
64
|
+
&:valid_units_per_em?
|
|
72
65
|
|
|
73
66
|
# Check 7: Number of glyphs must be at least 1 (.notdef)
|
|
74
|
-
check_table :num_glyphs, "maxp", severity: :error
|
|
75
|
-
table.valid_num_glyphs?
|
|
76
|
-
end
|
|
67
|
+
check_table :num_glyphs, "maxp", severity: :error, &:valid_num_glyphs?
|
|
77
68
|
|
|
78
69
|
# Check 8: Maxp metrics should be reasonable (not absurd values)
|
|
79
|
-
check_table :reasonable_metrics, "maxp", severity: :warning
|
|
80
|
-
|
|
81
|
-
end
|
|
70
|
+
check_table :reasonable_metrics, "maxp", severity: :warning,
|
|
71
|
+
&:reasonable_metrics?
|
|
82
72
|
end
|
|
83
73
|
end
|
|
84
74
|
end
|
|
@@ -36,94 +36,73 @@ module Fontisan
|
|
|
36
36
|
super
|
|
37
37
|
|
|
38
38
|
# Check 9: Name table Windows Unicode English encoding
|
|
39
|
-
check_table :name_windows_encoding,
|
|
39
|
+
check_table :name_windows_encoding, "name", severity: :error do |table|
|
|
40
40
|
table.has_valid_platform_combos?([3, 1, 0x0409]) # Windows Unicode English
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Check 10: Name table Mac Roman English encoding
|
|
44
|
-
check_table :name_mac_encoding,
|
|
44
|
+
check_table :name_mac_encoding, "name", severity: :warning do |table|
|
|
45
45
|
table.has_valid_platform_combos?([1, 0, 0]) # Mac Roman English
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Check 11: OS/2 table version must be valid
|
|
49
|
-
check_table :os2_version,
|
|
50
|
-
table.valid_version?
|
|
51
|
-
end
|
|
49
|
+
check_table :os2_version, "OS/2", severity: :error, &:valid_version?
|
|
52
50
|
|
|
53
51
|
# Check 12: OS/2 weight class must be valid (1-1000)
|
|
54
|
-
check_table :os2_weight_class,
|
|
55
|
-
|
|
56
|
-
end
|
|
52
|
+
check_table :os2_weight_class, "OS/2", severity: :error,
|
|
53
|
+
&:valid_weight_class?
|
|
57
54
|
|
|
58
55
|
# Check 13: OS/2 width class must be valid (1-9)
|
|
59
|
-
check_table :os2_width_class,
|
|
60
|
-
|
|
61
|
-
end
|
|
56
|
+
check_table :os2_width_class, "OS/2", severity: :error,
|
|
57
|
+
&:valid_width_class?
|
|
62
58
|
|
|
63
59
|
# Check 14: OS/2 vendor ID should be present
|
|
64
|
-
check_table :os2_vendor_id,
|
|
65
|
-
table.has_vendor_id?
|
|
66
|
-
end
|
|
60
|
+
check_table :os2_vendor_id, "OS/2", severity: :warning, &:has_vendor_id?
|
|
67
61
|
|
|
68
62
|
# Check 15: OS/2 PANOSE classification should be present
|
|
69
|
-
check_table :os2_panose,
|
|
70
|
-
table.has_panose?
|
|
71
|
-
end
|
|
63
|
+
check_table :os2_panose, "OS/2", severity: :info, &:has_panose?
|
|
72
64
|
|
|
73
65
|
# Check 16: OS/2 typographic metrics must be valid
|
|
74
|
-
check_table :os2_typo_metrics,
|
|
75
|
-
|
|
76
|
-
end
|
|
66
|
+
check_table :os2_typo_metrics, "OS/2", severity: :error,
|
|
67
|
+
&:valid_typo_metrics?
|
|
77
68
|
|
|
78
69
|
# Check 17: OS/2 Windows metrics must be valid
|
|
79
|
-
check_table :os2_win_metrics,
|
|
80
|
-
|
|
81
|
-
end
|
|
70
|
+
check_table :os2_win_metrics, "OS/2", severity: :error,
|
|
71
|
+
&:valid_win_metrics?
|
|
82
72
|
|
|
83
73
|
# Check 18: OS/2 Unicode ranges should be present
|
|
84
|
-
check_table :os2_unicode_ranges,
|
|
85
|
-
|
|
86
|
-
end
|
|
74
|
+
check_table :os2_unicode_ranges, "OS/2", severity: :warning,
|
|
75
|
+
&:has_unicode_ranges?
|
|
87
76
|
|
|
88
77
|
# Check 19: Head table bounding box must be valid
|
|
89
|
-
check_table :head_bounding_box,
|
|
90
|
-
|
|
91
|
-
end
|
|
78
|
+
check_table :head_bounding_box, "head", severity: :error,
|
|
79
|
+
&:valid_bounding_box?
|
|
92
80
|
|
|
93
81
|
# Check 20: Hhea ascent/descent must be valid
|
|
94
|
-
check_table :hhea_ascent_descent,
|
|
95
|
-
|
|
96
|
-
end
|
|
82
|
+
check_table :hhea_ascent_descent, "hhea", severity: :error,
|
|
83
|
+
&:valid_ascent_descent?
|
|
97
84
|
|
|
98
85
|
# Check 21: Hhea line gap should be valid
|
|
99
|
-
check_table :hhea_line_gap,
|
|
100
|
-
|
|
101
|
-
end
|
|
86
|
+
check_table :hhea_line_gap, "hhea", severity: :warning,
|
|
87
|
+
&:valid_line_gap?
|
|
102
88
|
|
|
103
89
|
# Check 22: Hhea horizontal metrics count must be valid
|
|
104
|
-
check_table :hhea_metrics_count,
|
|
105
|
-
|
|
106
|
-
end
|
|
90
|
+
check_table :hhea_metrics_count, "hhea", severity: :error,
|
|
91
|
+
&:valid_number_of_h_metrics?
|
|
107
92
|
|
|
108
93
|
# Check 23: Post table version must be valid
|
|
109
|
-
check_table :post_version,
|
|
110
|
-
table.valid_version?
|
|
111
|
-
end
|
|
94
|
+
check_table :post_version, "post", severity: :error, &:valid_version?
|
|
112
95
|
|
|
113
96
|
# Check 24: Post table italic angle should be valid
|
|
114
|
-
check_table :post_italic_angle,
|
|
115
|
-
|
|
116
|
-
end
|
|
97
|
+
check_table :post_italic_angle, "post", severity: :warning,
|
|
98
|
+
&:valid_italic_angle?
|
|
117
99
|
|
|
118
100
|
# Check 25: Post table underline metrics should be present
|
|
119
|
-
check_table :post_underline,
|
|
120
|
-
|
|
121
|
-
end
|
|
101
|
+
check_table :post_underline, "post", severity: :info,
|
|
102
|
+
&:has_underline_metrics?
|
|
122
103
|
|
|
123
104
|
# Check 26: Cmap table must have subtables
|
|
124
|
-
check_table :cmap_subtables,
|
|
125
|
-
table.has_subtables?
|
|
126
|
-
end
|
|
105
|
+
check_table :cmap_subtables, "cmap", severity: :error, &:has_subtables?
|
|
127
106
|
end
|
|
128
107
|
end
|
|
129
108
|
end
|
|
@@ -33,45 +33,44 @@ module Fontisan
|
|
|
33
33
|
super
|
|
34
34
|
|
|
35
35
|
# Check 27: Maxp TrueType metrics (only for version 1.0)
|
|
36
|
-
check_table :maxp_truetype_metrics,
|
|
36
|
+
check_table :maxp_truetype_metrics, "maxp",
|
|
37
|
+
severity: :warning do |table|
|
|
37
38
|
!table.version_1_0? || table.has_truetype_metrics?
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
# Check 28: Maxp max zones must be valid
|
|
41
|
-
check_table :maxp_zones,
|
|
42
|
-
table.valid_max_zones?
|
|
43
|
-
end
|
|
42
|
+
check_table :maxp_zones, "maxp", severity: :error, &:valid_max_zones?
|
|
44
43
|
|
|
45
44
|
# Check 29: Glyf glyphs must be accessible (TrueType fonts only)
|
|
46
45
|
check_glyphs :glyf_accessible, severity: :error do |font|
|
|
47
|
-
glyf = font.table(
|
|
46
|
+
glyf = font.table("glyf")
|
|
48
47
|
next true unless glyf # Skip if CFF font
|
|
49
48
|
|
|
50
|
-
loca = font.table(
|
|
51
|
-
head = font.table(
|
|
52
|
-
maxp = font.table(
|
|
49
|
+
loca = font.table("loca")
|
|
50
|
+
head = font.table("head")
|
|
51
|
+
maxp = font.table("maxp")
|
|
53
52
|
glyf.all_glyphs_accessible?(loca, head, maxp.num_glyphs)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
# Check 30: Glyf glyphs should not be clipped
|
|
57
56
|
check_glyphs :glyf_no_clipping, severity: :warning do |font|
|
|
58
|
-
glyf = font.table(
|
|
57
|
+
glyf = font.table("glyf")
|
|
59
58
|
next true unless glyf
|
|
60
59
|
|
|
61
|
-
loca = font.table(
|
|
62
|
-
head = font.table(
|
|
63
|
-
maxp = font.table(
|
|
60
|
+
loca = font.table("loca")
|
|
61
|
+
head = font.table("head")
|
|
62
|
+
maxp = font.table("maxp")
|
|
64
63
|
glyf.no_clipped_glyphs?(loca, head, maxp.num_glyphs)
|
|
65
64
|
end
|
|
66
65
|
|
|
67
66
|
# Check 31: Glyf contour counts must be valid
|
|
68
67
|
check_glyphs :glyf_valid_contours, severity: :error do |font|
|
|
69
|
-
glyf = font.table(
|
|
68
|
+
glyf = font.table("glyf")
|
|
70
69
|
next true unless glyf
|
|
71
70
|
|
|
72
|
-
loca = font.table(
|
|
73
|
-
head = font.table(
|
|
74
|
-
maxp = font.table(
|
|
71
|
+
loca = font.table("loca")
|
|
72
|
+
head = font.table("head")
|
|
73
|
+
maxp = font.table("maxp")
|
|
75
74
|
|
|
76
75
|
(0...maxp.num_glyphs).all? do |glyph_id|
|
|
77
76
|
glyf.valid_contour_count?(glyph_id, loca, head)
|
|
@@ -79,29 +78,26 @@ module Fontisan
|
|
|
79
78
|
end
|
|
80
79
|
|
|
81
80
|
# Check 32: Cmap must have Unicode mapping
|
|
82
|
-
check_table :cmap_unicode_mapping,
|
|
83
|
-
|
|
84
|
-
end
|
|
81
|
+
check_table :cmap_unicode_mapping, "cmap", severity: :error,
|
|
82
|
+
&:has_unicode_mapping?
|
|
85
83
|
|
|
86
84
|
# Check 33: Cmap should have BMP coverage
|
|
87
|
-
check_table :cmap_bmp_coverage,
|
|
88
|
-
|
|
89
|
-
end
|
|
85
|
+
check_table :cmap_bmp_coverage, "cmap", severity: :warning,
|
|
86
|
+
&:has_bmp_coverage?
|
|
90
87
|
|
|
91
88
|
# Check 34: Cmap must have format 4 subtable
|
|
92
|
-
check_table :cmap_format4,
|
|
93
|
-
|
|
94
|
-
end
|
|
89
|
+
check_table :cmap_format4, "cmap", severity: :error,
|
|
90
|
+
&:has_format_4_subtable?
|
|
95
91
|
|
|
96
92
|
# Check 35: Cmap glyph indices must be valid
|
|
97
93
|
check_structure :cmap_glyph_indices, severity: :error do |font|
|
|
98
|
-
cmap = font.table(
|
|
99
|
-
maxp = font.table(
|
|
94
|
+
cmap = font.table("cmap")
|
|
95
|
+
maxp = font.table("maxp")
|
|
100
96
|
cmap.valid_glyph_indices?(maxp.num_glyphs)
|
|
101
97
|
end
|
|
102
98
|
|
|
103
99
|
# Check 36: Table checksums (info level - many fonts have mismatches)
|
|
104
|
-
check_structure :checksum_valid, severity: :info do |
|
|
100
|
+
check_structure :checksum_valid, severity: :info do |_font|
|
|
105
101
|
# Table checksum validation (info level - for reference)
|
|
106
102
|
# Most fonts have checksum mismatches, so we make it info not error
|
|
107
103
|
true # Placeholder - actual checksum validation if desired
|