fontisan 0.2.7 → 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 +106 -319
- data/Gemfile +1 -1
- data/README.adoc +81 -14
- data/Rakefile +12 -7
- data/benchmark/variation_quick_bench.rb +1 -1
- data/lib/fontisan/cli.rb +45 -13
- data/lib/fontisan/collection/dfont_builder.rb +2 -1
- 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/font_loader.rb +7 -6
- 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 +4 -2
data/lib/fontisan/tables/cmap.rb
CHANGED
|
@@ -278,22 +278,20 @@ module Fontisan
|
|
|
278
278
|
end
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
-
public
|
|
282
|
-
|
|
283
281
|
# Validation helper: Check if version is valid
|
|
284
282
|
#
|
|
285
283
|
# cmap version should be 0
|
|
286
284
|
#
|
|
287
285
|
# @return [Boolean] True if version is 0
|
|
288
286
|
def valid_version?
|
|
289
|
-
version
|
|
287
|
+
version.zero?
|
|
290
288
|
end
|
|
291
289
|
|
|
292
290
|
# Validation helper: Check if at least one subtable exists
|
|
293
291
|
#
|
|
294
292
|
# @return [Boolean] True if num_tables > 0
|
|
295
293
|
def has_subtables?
|
|
296
|
-
num_tables
|
|
294
|
+
num_tables&.positive?
|
|
297
295
|
end
|
|
298
296
|
|
|
299
297
|
# Validation helper: Check if Unicode mapping exists
|
|
@@ -357,7 +355,9 @@ module Fontisan
|
|
|
357
355
|
mappings = unicode_mappings
|
|
358
356
|
return true if mappings.nil? || mappings.empty?
|
|
359
357
|
|
|
360
|
-
mappings.values.all?
|
|
358
|
+
mappings.values.all? do |glyph_id|
|
|
359
|
+
glyph_id >= 0 && glyph_id < max_glyph_id
|
|
360
|
+
end
|
|
361
361
|
end
|
|
362
362
|
end
|
|
363
363
|
end
|
data/lib/fontisan/tables/glyf.rb
CHANGED
|
@@ -166,7 +166,7 @@ module Fontisan
|
|
|
166
166
|
# @param head [Head] Head table for context
|
|
167
167
|
# @param num_glyphs [Integer] Total number of glyphs to check
|
|
168
168
|
# @return [Boolean] True if all non-special glyphs have contours
|
|
169
|
-
def no_empty_glyphs_except_special?(loca,
|
|
169
|
+
def no_empty_glyphs_except_special?(loca, _head, num_glyphs)
|
|
170
170
|
# Check glyphs 1 through num_glyphs-1 (.notdef at 0 can be empty)
|
|
171
171
|
(1...num_glyphs).all? do |glyph_id|
|
|
172
172
|
size = loca.size_of(glyph_id)
|
|
@@ -194,7 +194,7 @@ module Fontisan
|
|
|
194
194
|
|
|
195
195
|
(0...num_glyphs).all? do |glyph_id|
|
|
196
196
|
glyph = glyph_for(glyph_id, loca, head)
|
|
197
|
-
next true if glyph.nil?
|
|
197
|
+
next true if glyph.nil? # Empty glyphs are OK
|
|
198
198
|
|
|
199
199
|
# Check if glyph bounds are within font bounds
|
|
200
200
|
glyph.x_min >= font_x_min &&
|
|
@@ -218,7 +218,7 @@ module Fontisan
|
|
|
218
218
|
def instructions_sound?(loca, head, num_glyphs)
|
|
219
219
|
(0...num_glyphs).all? do |glyph_id|
|
|
220
220
|
glyph = glyph_for(glyph_id, loca, head)
|
|
221
|
-
next true if glyph.nil?
|
|
221
|
+
next true if glyph.nil? # Empty glyphs are OK
|
|
222
222
|
|
|
223
223
|
# Simple glyphs have instructions
|
|
224
224
|
if glyph.respond_to?(:instruction_length)
|
|
@@ -242,7 +242,7 @@ module Fontisan
|
|
|
242
242
|
# @return [Boolean] True if contour count is valid
|
|
243
243
|
def valid_contour_count?(glyph_id, loca, head)
|
|
244
244
|
glyph = glyph_for(glyph_id, loca, head)
|
|
245
|
-
return true if glyph.nil?
|
|
245
|
+
return true if glyph.nil? # Empty glyphs are OK
|
|
246
246
|
|
|
247
247
|
# Simple glyphs: contours should be >= 0
|
|
248
248
|
# Compound glyphs: numberOfContours = -1
|
|
@@ -265,12 +265,10 @@ module Fontisan
|
|
|
265
265
|
# @return [Boolean] True if all glyphs can be accessed
|
|
266
266
|
def all_glyphs_accessible?(loca, head, num_glyphs)
|
|
267
267
|
(0...num_glyphs).all? do |glyph_id|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
false
|
|
273
|
-
end
|
|
268
|
+
glyph_for(glyph_id, loca, head)
|
|
269
|
+
true
|
|
270
|
+
rescue Fontisan::CorruptedTableError
|
|
271
|
+
false
|
|
274
272
|
end
|
|
275
273
|
rescue StandardError
|
|
276
274
|
false
|
data/lib/fontisan/tables/head.rb
CHANGED
|
@@ -95,7 +95,7 @@ module Fontisan
|
|
|
95
95
|
#
|
|
96
96
|
# @return [Boolean] True if version is 1.0
|
|
97
97
|
def valid_version?
|
|
98
|
-
version_raw == 0x00010000
|
|
98
|
+
version_raw == 0x00010000 # Version 1.0
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
# Validation helper: Check if units per em is valid
|
|
@@ -130,7 +130,7 @@ module Fontisan
|
|
|
130
130
|
#
|
|
131
131
|
# @return [Boolean] True if format is 0 or 1
|
|
132
132
|
def valid_index_to_loc_format?
|
|
133
|
-
|
|
133
|
+
[0, 1].include?(index_to_loc_format)
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
# Validation helper: Check if glyph_data_format is valid
|
|
@@ -139,7 +139,7 @@ module Fontisan
|
|
|
139
139
|
#
|
|
140
140
|
# @return [Boolean] True if format is 0
|
|
141
141
|
def valid_glyph_data_format?
|
|
142
|
-
glyph_data_format
|
|
142
|
+
glyph_data_format.zero?
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
# Validate magic number and raise error if invalid
|
data/lib/fontisan/tables/hhea.rb
CHANGED
|
@@ -112,7 +112,7 @@ module Fontisan
|
|
|
112
112
|
#
|
|
113
113
|
# @return [Boolean] True if format is 0
|
|
114
114
|
def valid_metric_data_format?
|
|
115
|
-
metric_data_format
|
|
115
|
+
metric_data_format.zero?
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
# Validation helper: Check if number of h metrics is valid
|
|
@@ -130,7 +130,7 @@ module Fontisan
|
|
|
130
130
|
#
|
|
131
131
|
# @return [Boolean] True if ascent/descent have correct signs
|
|
132
132
|
def valid_ascent_descent?
|
|
133
|
-
ascent
|
|
133
|
+
ascent.positive? && descent.negative?
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
# Validation helper: Check if line gap is non-negative
|
|
@@ -148,7 +148,7 @@ module Fontisan
|
|
|
148
148
|
#
|
|
149
149
|
# @return [Boolean] True if advance_width_max > 0
|
|
150
150
|
def valid_advance_width_max?
|
|
151
|
-
advance_width_max
|
|
151
|
+
advance_width_max&.positive?
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
# Validation helper: Check if caret slope is valid
|
|
@@ -168,7 +168,7 @@ module Fontisan
|
|
|
168
168
|
#
|
|
169
169
|
# @return [Boolean] True if x_max_extent > 0
|
|
170
170
|
def valid_x_max_extent?
|
|
171
|
-
x_max_extent
|
|
171
|
+
x_max_extent.positive?
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
# Validate the table and raise error if invalid
|
data/lib/fontisan/tables/maxp.rb
CHANGED
|
@@ -179,9 +179,9 @@ module Fontisan
|
|
|
179
179
|
#
|
|
180
180
|
# @return [Boolean] True if maxZones is valid or not applicable
|
|
181
181
|
def valid_max_zones?
|
|
182
|
-
return true if version_0_5?
|
|
182
|
+
return true if version_0_5? # Not applicable for CFF
|
|
183
183
|
|
|
184
|
-
max_zones
|
|
184
|
+
max_zones&.between?(1, 2)
|
|
185
185
|
end
|
|
186
186
|
|
|
187
187
|
# Validation helper: Check if all TrueType metrics are present
|
data/lib/fontisan/tables/name.rb
CHANGED
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
|