fontisan 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +103 -0
  3. data/.rubocop_todo.yml +65 -361
  4. data/CHANGELOG.md +116 -0
  5. data/Gemfile +1 -1
  6. data/README.adoc +106 -27
  7. data/Rakefile +12 -7
  8. data/benchmark/variation_quick_bench.rb +1 -1
  9. data/docs/APPLE_LEGACY_FONTS.adoc +173 -0
  10. data/docs/COLLECTION_VALIDATION.adoc +143 -0
  11. data/docs/COLOR_FONTS.adoc +127 -0
  12. data/docs/DOCUMENTATION_SUMMARY.md +141 -0
  13. data/docs/FONT_HINTING.adoc +9 -1
  14. data/docs/VALIDATION.adoc +254 -0
  15. data/docs/WOFF_WOFF2_FORMATS.adoc +94 -0
  16. data/lib/fontisan/cli.rb +45 -13
  17. data/lib/fontisan/collection/dfont_builder.rb +2 -1
  18. data/lib/fontisan/commands/convert_command.rb +2 -4
  19. data/lib/fontisan/commands/info_command.rb +3 -3
  20. data/lib/fontisan/commands/pack_command.rb +2 -1
  21. data/lib/fontisan/commands/validate_command.rb +157 -6
  22. data/lib/fontisan/converters/collection_converter.rb +22 -13
  23. data/lib/fontisan/converters/svg_generator.rb +2 -1
  24. data/lib/fontisan/converters/woff2_encoder.rb +6 -6
  25. data/lib/fontisan/converters/woff_writer.rb +3 -1
  26. data/lib/fontisan/font_loader.rb +7 -6
  27. data/lib/fontisan/formatters/text_formatter.rb +18 -14
  28. data/lib/fontisan/hints/hint_converter.rb +1 -1
  29. data/lib/fontisan/hints/hint_validator.rb +13 -10
  30. data/lib/fontisan/hints/truetype_instruction_analyzer.rb +15 -8
  31. data/lib/fontisan/hints/truetype_instruction_generator.rb +1 -1
  32. data/lib/fontisan/models/collection_validation_report.rb +104 -0
  33. data/lib/fontisan/models/font_report.rb +24 -0
  34. data/lib/fontisan/models/validation_report.rb +7 -2
  35. data/lib/fontisan/open_type_font.rb +18 -425
  36. data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
  37. data/lib/fontisan/optimizers/subroutine_optimizer.rb +6 -2
  38. data/lib/fontisan/sfnt_font.rb +699 -0
  39. data/lib/fontisan/sfnt_table.rb +264 -0
  40. data/lib/fontisan/subset/glyph_mapping.rb +2 -0
  41. data/lib/fontisan/subset/table_subsetter.rb +2 -2
  42. data/lib/fontisan/tables/cblc.rb +8 -4
  43. data/lib/fontisan/tables/cff/index.rb +2 -0
  44. data/lib/fontisan/tables/cff.rb +6 -3
  45. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +1 -1
  46. data/lib/fontisan/tables/cff2.rb +1 -1
  47. data/lib/fontisan/tables/cmap.rb +5 -5
  48. data/lib/fontisan/tables/cmap_table.rb +231 -0
  49. data/lib/fontisan/tables/glyf.rb +8 -10
  50. data/lib/fontisan/tables/glyf_table.rb +255 -0
  51. data/lib/fontisan/tables/head.rb +3 -3
  52. data/lib/fontisan/tables/head_table.rb +111 -0
  53. data/lib/fontisan/tables/hhea.rb +4 -4
  54. data/lib/fontisan/tables/hhea_table.rb +255 -0
  55. data/lib/fontisan/tables/hmtx_table.rb +191 -0
  56. data/lib/fontisan/tables/loca_table.rb +212 -0
  57. data/lib/fontisan/tables/maxp.rb +2 -2
  58. data/lib/fontisan/tables/maxp_table.rb +258 -0
  59. data/lib/fontisan/tables/name.rb +1 -1
  60. data/lib/fontisan/tables/name_table.rb +176 -0
  61. data/lib/fontisan/tables/os2.rb +8 -8
  62. data/lib/fontisan/tables/os2_table.rb +329 -0
  63. data/lib/fontisan/tables/post.rb +2 -2
  64. data/lib/fontisan/tables/post_table.rb +183 -0
  65. data/lib/fontisan/tables/sbix.rb +5 -4
  66. data/lib/fontisan/true_type_font.rb +12 -464
  67. data/lib/fontisan/utilities/checksum_calculator.rb +0 -44
  68. data/lib/fontisan/validation/collection_validator.rb +4 -2
  69. data/lib/fontisan/validators/basic_validator.rb +11 -21
  70. data/lib/fontisan/validators/font_book_validator.rb +29 -50
  71. data/lib/fontisan/validators/opentype_validator.rb +24 -28
  72. data/lib/fontisan/validators/validator.rb +87 -66
  73. data/lib/fontisan/validators/web_font_validator.rb +16 -21
  74. data/lib/fontisan/version.rb +1 -1
  75. data/lib/fontisan/woff2/glyf_transformer.rb +31 -8
  76. data/lib/fontisan/woff2/hmtx_transformer.rb +2 -1
  77. data/lib/fontisan/woff2/table_transformer.rb +4 -2
  78. data/lib/fontisan/woff2_font.rb +4 -2
  79. data/lib/fontisan/woff_font.rb +46 -30
  80. data/lib/fontisan.rb +2 -2
  81. data/scripts/compare_stack_aware.rb +1 -1
  82. data/scripts/measure_optimization.rb +1 -2
  83. metadata +23 -2
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../sfnt_table"
4
+ require_relative "post"
5
+
6
+ module Fontisan
7
+ module Tables
8
+ # OOP representation of the 'post' (PostScript) table
9
+ #
10
+ # The post table contains PostScript information, primarily glyph names.
11
+ # Different versions exist (1.0, 2.0, 2.5, 3.0, 4.0) with varying
12
+ # glyph name storage strategies.
13
+ #
14
+ # This class extends SfntTable to provide post-specific validation and
15
+ # convenience methods for accessing PostScript metrics and glyph names.
16
+ #
17
+ # @example Accessing post table data
18
+ # post = font.sfnt_table("post")
19
+ # post.italic_angle # => 0.0
20
+ # post.underline_position # => -100
21
+ # post.underline_thickness # => 50
22
+ # post.glyph_name_for(42) # => "A"
23
+ class PostTable < SfntTable
24
+ # Get post table version
25
+ #
26
+ # @return [Float, nil] Version number (1.0, 2.0, 2.5, 3.0, or 4.0)
27
+ def version
28
+ return nil unless parsed
29
+
30
+ parsed.version
31
+ end
32
+
33
+ # Get italic angle in degrees
34
+ #
35
+ # Positive value means counter-clockwise tilt
36
+ #
37
+ # @return [Float, nil] Italic angle in degrees, or nil if not parsed
38
+ def italic_angle
39
+ return nil unless parsed
40
+
41
+ parsed.italic_angle
42
+ end
43
+
44
+ # Check if font is italic
45
+ #
46
+ # @return [Boolean] true if italic_angle != 0
47
+ def italic?
48
+ angle = italic_angle
49
+ !angle.nil? && angle != 0
50
+ end
51
+
52
+ # Get underline position
53
+ #
54
+ # Distance from baseline to top of underline (negative for under baseline)
55
+ #
56
+ # @return [Integer, nil] Underline position in FUnits, or nil if not parsed
57
+ def underline_position
58
+ parsed&.underline_position
59
+ end
60
+
61
+ # Get underline thickness
62
+ #
63
+ # @return [Integer, nil] Underline thickness in FUnits, or nil if not parsed
64
+ def underline_thickness
65
+ parsed&.underline_thickness
66
+ end
67
+
68
+ # Check if font is fixed pitch (monospaced)
69
+ #
70
+ # @return [Boolean] true if font is monospaced
71
+ def fixed_pitch?
72
+ return false unless parsed
73
+
74
+ parsed.is_fixed_pitch == 1
75
+ end
76
+
77
+ # Get minimum memory for Type 42 fonts
78
+ #
79
+ # @return [Integer, nil] Minimum memory in bytes, or nil if not parsed
80
+ def min_mem_type42
81
+ parsed&.min_mem_type42
82
+ end
83
+
84
+ # Get maximum memory for Type 42 fonts
85
+ #
86
+ # @return [Integer, nil] Maximum memory in bytes, or nil if not parsed
87
+ def max_mem_type42
88
+ parsed&.max_mem_type42
89
+ end
90
+
91
+ # Get minimum memory for Type 1 fonts
92
+ #
93
+ # @return [Integer, nil] Minimum memory in bytes, or nil if not parsed
94
+ def min_mem_type1
95
+ parsed&.min_mem_type1
96
+ end
97
+
98
+ # Get maximum memory for Type 1 fonts
99
+ #
100
+ # @return [Integer, nil] Maximum memory in bytes, or nil if not parsed
101
+ def max_mem_type1
102
+ parsed&.max_mem_type1
103
+ end
104
+
105
+ # Get all glyph names
106
+ #
107
+ # Only available for version 1.0 and 2.0
108
+ #
109
+ # @return [Array<String>] Array of glyph names
110
+ def glyph_names
111
+ return [] unless parsed
112
+
113
+ parsed.glyph_names || []
114
+ end
115
+
116
+ # Get glyph name by ID
117
+ #
118
+ # @param glyph_id [Integer] Glyph ID
119
+ # @return [String, nil] Glyph name, or nil if not found
120
+ def glyph_name_for(glyph_id)
121
+ names = glyph_names
122
+ return nil if glyph_id.negative? || glyph_id >= names.length
123
+
124
+ names[glyph_id]
125
+ end
126
+
127
+ # Check if glyph names are available
128
+ #
129
+ # @return [Boolean] true if glyph names can be retrieved
130
+ def has_glyph_names?
131
+ return false unless parsed
132
+
133
+ parsed.has_glyph_names?
134
+ end
135
+
136
+ # Get the number of glyphs with names
137
+ #
138
+ # @return [Integer] Number of named glyphs
139
+ def named_glyph_count
140
+ glyph_names.length
141
+ end
142
+
143
+ protected
144
+
145
+ # Validate the parsed post table
146
+ #
147
+ # @return [Boolean] true if valid
148
+ # @raise [InvalidFontError] if post table is invalid
149
+ def validate_parsed_table?
150
+ return true unless parsed
151
+
152
+ # Validate version
153
+ unless parsed.valid_version?
154
+ raise InvalidFontError,
155
+ "Invalid post table version: #{parsed.version} " \
156
+ "(must be 1.0, 2.0, 2.5, 3.0, or 4.0)"
157
+ end
158
+
159
+ # Validate italic angle
160
+ unless parsed.valid_italic_angle?
161
+ raise InvalidFontError,
162
+ "Invalid post italic angle: #{parsed.italic_angle} " \
163
+ "(must be between -60 and 60 degrees)"
164
+ end
165
+
166
+ # Validate fixed pitch flag
167
+ unless parsed.valid_fixed_pitch_flag?
168
+ raise InvalidFontError,
169
+ "Invalid post is_fixed_pitch: #{parsed.is_fixed_pitch} " \
170
+ "(must be 0 or 1)"
171
+ end
172
+
173
+ # Validate version 2.0 data completeness
174
+ unless parsed.complete_version_2_data?
175
+ raise InvalidFontError,
176
+ "Invalid post version 2.0 table: incomplete data"
177
+ end
178
+
179
+ true
180
+ end
181
+ end
182
+ end
183
+ end
@@ -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", "mask"].include?(format_name)
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 # Empty glyph
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 # Need at least header
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 # Empty glyph
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