fontisan 0.2.13 → 0.2.16

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 (145) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +56 -196
  3. data/Gemfile +1 -1
  4. data/docs/.gitignore +17 -0
  5. data/docs/.vitepress/config.ts +317 -0
  6. data/docs/.vitepress/theme/components/ApiMethod.vue +127 -0
  7. data/docs/.vitepress/theme/components/Badge.vue +51 -0
  8. data/docs/.vitepress/theme/components/FeatureComparison.vue +87 -0
  9. data/docs/.vitepress/theme/components/HeroCodeBlock.vue +98 -0
  10. data/docs/.vitepress/theme/components/WithinHero.vue +30 -0
  11. data/docs/.vitepress/theme/index.ts +22 -0
  12. data/docs/.vitepress/theme/style.css +330 -0
  13. data/docs/api/conversion-options.md +141 -0
  14. data/docs/api/converters/curve-converter.md +34 -0
  15. data/docs/api/converters/hint-converter.md +34 -0
  16. data/docs/api/converters/outline-converter.md +27 -0
  17. data/docs/api/font-loader.md +111 -0
  18. data/docs/api/font-writer.md +103 -0
  19. data/docs/api/index.md +79 -0
  20. data/docs/api/models/glyph-accessor.md +43 -0
  21. data/docs/api/models/glyph.md +40 -0
  22. data/docs/api/models/table-analyzer.md +35 -0
  23. data/docs/api/sfnt-font.md +53 -0
  24. data/docs/api/type1-font.md +43 -0
  25. data/docs/api/validators/font-validator.md +31 -0
  26. data/docs/api/validators/helper.md +36 -0
  27. data/docs/api/validators/profile.md +39 -0
  28. data/docs/cli/convert.md +87 -0
  29. data/docs/cli/dump-table.md +110 -0
  30. data/docs/cli/export.md +176 -0
  31. data/docs/cli/features.md +124 -0
  32. data/docs/cli/glyphs.md +90 -0
  33. data/docs/cli/index.md +208 -0
  34. data/docs/cli/info.md +254 -0
  35. data/docs/cli/instance.md +122 -0
  36. data/docs/cli/ls.md +95 -0
  37. data/docs/cli/optical-size.md +94 -0
  38. data/docs/cli/pack.md +125 -0
  39. data/docs/cli/scripts.md +105 -0
  40. data/docs/cli/subset.md +39 -0
  41. data/docs/cli/tables.md +84 -0
  42. data/docs/cli/unicode.md +101 -0
  43. data/docs/cli/validate.md +48 -0
  44. data/docs/cli/variable.md +126 -0
  45. data/docs/cli/version.md +46 -0
  46. data/docs/guide/cli/convert.md +108 -0
  47. data/docs/guide/cli/export.md +138 -0
  48. data/docs/guide/cli/index.md +99 -0
  49. data/docs/guide/cli/info.md +144 -0
  50. data/docs/guide/cli/pack.md +155 -0
  51. data/docs/guide/cli/subset.md +118 -0
  52. data/docs/guide/cli/validate.md +139 -0
  53. data/docs/guide/color-fonts/bitmaps.md +177 -0
  54. data/docs/guide/color-fonts/colr-cpal.md +175 -0
  55. data/docs/guide/color-fonts/index.md +140 -0
  56. data/docs/guide/color-fonts/svg.md +154 -0
  57. data/docs/guide/color.md +51 -0
  58. data/docs/guide/comparisons/font-validator.md +222 -0
  59. data/docs/guide/comparisons/fonttools.md +200 -0
  60. data/docs/guide/comparisons/index.md +83 -0
  61. data/docs/guide/comparisons/lcdf-typetools.md +205 -0
  62. data/docs/guide/contributing.md +279 -0
  63. data/docs/guide/conversion/collections.md +251 -0
  64. data/docs/guide/conversion/curves.md +246 -0
  65. data/docs/guide/conversion/index.md +157 -0
  66. data/docs/guide/conversion/options.md +251 -0
  67. data/docs/guide/conversion/ttf-otf.md +184 -0
  68. data/docs/guide/conversion/type1.md +208 -0
  69. data/docs/guide/conversion/web.md +240 -0
  70. data/docs/guide/conversion.md +39 -0
  71. data/docs/guide/formats/collections.md +147 -0
  72. data/docs/guide/formats/dfont.md +99 -0
  73. data/docs/guide/formats/index.md +65 -0
  74. data/docs/guide/formats/otf.md +103 -0
  75. data/docs/guide/formats/svg.md +97 -0
  76. data/docs/guide/formats/ttf.md +105 -0
  77. data/docs/guide/formats/type1.md +118 -0
  78. data/docs/guide/formats/woff.md +115 -0
  79. data/docs/guide/hinting/autohint.md +141 -0
  80. data/docs/guide/hinting/conversion.md +161 -0
  81. data/docs/guide/hinting/index.md +86 -0
  82. data/docs/guide/hinting/postscript.md +149 -0
  83. data/docs/guide/hinting/truetype.md +135 -0
  84. data/docs/guide/hinting.md +44 -0
  85. data/docs/guide/index.md +152 -0
  86. data/docs/guide/installation.md +116 -0
  87. data/docs/guide/migrations/extract-ttc.md +549 -0
  88. data/docs/guide/migrations/font-validator.md +260 -0
  89. data/docs/guide/migrations/fonttools.md +208 -0
  90. data/docs/guide/migrations/index.md +64 -0
  91. data/docs/guide/migrations/otfinfo.md +197 -0
  92. data/docs/guide/quick-start.md +204 -0
  93. data/docs/guide/type1.md +58 -0
  94. data/docs/guide/universal-outline.md +151 -0
  95. data/docs/guide/validation/custom.md +195 -0
  96. data/docs/guide/validation/helpers.md +188 -0
  97. data/docs/guide/validation/index.md +132 -0
  98. data/docs/guide/validation/profiles.md +156 -0
  99. data/docs/guide/validation.md +47 -0
  100. data/docs/guide/variable-fonts/advanced.md +231 -0
  101. data/docs/guide/variable-fonts/axes.md +209 -0
  102. data/docs/guide/variable-fonts/conversion.md +197 -0
  103. data/docs/guide/variable-fonts/index.md +84 -0
  104. data/docs/guide/variable-fonts/instances.md +187 -0
  105. data/docs/guide/variable-fonts/named-instances.md +194 -0
  106. data/docs/guide/variable-fonts/static.md +168 -0
  107. data/docs/guide/variable.md +58 -0
  108. data/docs/guide/woff.md +59 -0
  109. data/docs/index.md +136 -0
  110. data/docs/lychee.toml +37 -0
  111. data/docs/package-lock.json +2560 -0
  112. data/docs/package.json +15 -0
  113. data/docs/public/apple-touch-icon.png +0 -0
  114. data/docs/public/favicon-96x96.png +0 -0
  115. data/docs/public/favicon.ico +0 -0
  116. data/docs/public/favicon.svg +1 -0
  117. data/docs/public/logo-full.svg +1 -0
  118. data/docs/public/logo.svg +1 -0
  119. data/docs/public/site.webmanifest +21 -0
  120. data/docs/public/web-app-manifest-192x192.png +0 -0
  121. data/docs/public/web-app-manifest-512x512.png +0 -0
  122. data/fontisan.gemspec +1 -1
  123. data/lib/fontisan/commands/features_command.rb +0 -1
  124. data/lib/fontisan/commands/info_command.rb +5 -5
  125. data/lib/fontisan/commands/scripts_command.rb +0 -1
  126. data/lib/fontisan/converters/format_converter.rb +2 -1
  127. data/lib/fontisan/converters/type1_converter.rb +65 -60
  128. data/lib/fontisan/hints/hint_converter.rb +2 -1
  129. data/lib/fontisan/loading_modes.rb +0 -2
  130. data/lib/fontisan/open_type_font.rb +0 -40
  131. data/lib/fontisan/sfnt_font.rb +41 -22
  132. data/lib/fontisan/tables/glyf/compound_glyph.rb +0 -1
  133. data/lib/fontisan/true_type_collection.rb +8 -8
  134. data/lib/fontisan/true_type_font.rb +1 -59
  135. data/lib/fontisan/type1/afm_parser.rb +2 -1
  136. data/lib/fontisan/type1/cff_to_type1_converter.rb +24 -19
  137. data/lib/fontisan/type1/private_dict.rb +28 -7
  138. data/lib/fontisan/type1/seac_expander.rb +22 -17
  139. data/lib/fontisan/variable/delta_applicator.rb +3 -3
  140. data/lib/fontisan/variation/optimizer.rb +0 -1
  141. data/lib/fontisan/version.rb +1 -1
  142. data/lib/fontisan/woff2_font.rb +2 -2
  143. data/lib/fontisan/woff_font.rb +3 -3
  144. data/lib/fontisan.rb +3 -2
  145. metadata +122 -4
@@ -32,7 +32,7 @@ module Fontisan
32
32
 
33
33
  # Get a single font from the collection
34
34
  #
35
- # Overrides BaseCollection to use TrueType-specific from_ttc method.
35
+ # Uses the base class from_collection method.
36
36
  #
37
37
  # @param index [Integer] Index of the font (0-based)
38
38
  # @param io [IO] Open file handle
@@ -42,13 +42,13 @@ module Fontisan
42
42
  return nil if index >= num_fonts
43
43
 
44
44
  require_relative "true_type_font"
45
- TrueTypeFont.from_ttc(io, font_offsets[index], mode: mode)
45
+ TrueTypeFont.from_collection(io, font_offsets[index], mode: mode)
46
46
  end
47
47
 
48
48
  # Extract fonts as TrueTypeFont objects
49
49
  #
50
50
  # Reads each font from the TTC file and returns them as TrueTypeFont objects.
51
- # This method uses the TTC-specific from_ttc method.
51
+ # Uses the base class from_collection method.
52
52
  #
53
53
  # @param io [IO] Open file handle to read fonts from
54
54
  # @return [Array<TrueTypeFont>] Array of font objects
@@ -56,13 +56,13 @@ module Fontisan
56
56
  require_relative "true_type_font"
57
57
 
58
58
  font_offsets.map do |offset|
59
- TrueTypeFont.from_ttc(io, offset)
59
+ TrueTypeFont.from_collection(io, offset)
60
60
  end
61
61
  end
62
62
 
63
63
  # List all fonts in the collection with basic metadata
64
64
  #
65
- # Overrides BaseCollection to use TrueType-specific from_ttc method.
65
+ # Uses the base class from_collection method.
66
66
  #
67
67
  # @param io [IO] Open file handle to read fonts from
68
68
  # @return [CollectionListInfo] List of fonts with metadata
@@ -73,7 +73,7 @@ module Fontisan
73
73
  require_relative "tables/name"
74
74
 
75
75
  fonts = font_offsets.map.with_index do |offset, index|
76
- font = TrueTypeFont.from_ttc(io, offset)
76
+ font = TrueTypeFont.from_collection(io, offset)
77
77
 
78
78
  # Extract basic font info
79
79
  name_table = font.table("name")
@@ -119,7 +119,7 @@ module Fontisan
119
119
 
120
120
  # Calculate table sharing statistics
121
121
  #
122
- # Overrides BaseCollection to use TrueType-specific from_ttc method.
122
+ # Uses the base class from_collection method.
123
123
  #
124
124
  # @param io [IO] Open file handle
125
125
  # @return [TableSharingInfo] Sharing statistics
@@ -129,7 +129,7 @@ module Fontisan
129
129
 
130
130
  # Extract all fonts
131
131
  fonts = font_offsets.map do |offset|
132
- TrueTypeFont.from_ttc(io, offset)
132
+ TrueTypeFont.from_collection(io, offset)
133
133
  end
134
134
 
135
135
  # Build table hash map (checksum -> size)
@@ -23,66 +23,8 @@ module Fontisan
23
23
  # ttf.to_file("output.ttf")
24
24
  #
25
25
  # @example Reading from TTC collection
26
- # ttf = TrueTypeFont.from_ttc(io, offset)
26
+ # ttf = TrueTypeFont.from_collection(io, offset)
27
27
  class TrueTypeFont < SfntFont
28
- # Read TrueType Font from a file
29
- #
30
- # @param path [String] Path to the TTF file
31
- # @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
32
- # @param lazy [Boolean] If true, load tables on demand (default: false)
33
- # @return [TrueTypeFont] A new instance
34
- # @raise [ArgumentError] if path is nil or empty, or if mode is invalid
35
- # @raise [Errno::ENOENT] if file does not exist
36
- # @raise [RuntimeError] if file format is invalid
37
- def self.from_file(path, mode: LoadingModes::FULL, lazy: false)
38
- if path.nil? || path.to_s.empty?
39
- raise ArgumentError,
40
- "path cannot be nil or empty"
41
- end
42
- raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path)
43
-
44
- # Validate mode
45
- LoadingModes.validate_mode!(mode)
46
-
47
- File.open(path, "rb") do |io|
48
- font = read(io)
49
- font.initialize_storage
50
- font.loading_mode = mode
51
- font.lazy_load_enabled = lazy
52
-
53
- if lazy
54
- # Reuse existing IO handle by duplicating it to prevent double file open
55
- # The dup ensures the handle stays open after this block closes
56
- font.io_source = io.dup
57
- font.setup_finalizer
58
- else
59
- # Read tables upfront
60
- font.read_table_data(io)
61
- end
62
-
63
- font
64
- end
65
- rescue BinData::ValidityError, EOFError => e
66
- raise "Invalid TTF file: #{e.message}"
67
- end
68
-
69
- # Read TrueType Font from TTC at specific offset
70
- #
71
- # @param io [IO] Open file handle
72
- # @param offset [Integer] Byte offset to the font
73
- # @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
74
- # @return [TrueTypeFont] A new instance
75
- def self.from_ttc(io, offset, mode: LoadingModes::FULL)
76
- LoadingModes.validate_mode!(mode)
77
-
78
- io.seek(offset)
79
- font = read(io)
80
- font.initialize_storage
81
- font.loading_mode = mode
82
- font.read_table_data(io)
83
- font
84
- end
85
-
86
28
  # Check if font is TrueType flavored
87
29
  #
88
30
  # @return [Boolean] true for TrueType fonts
@@ -153,7 +153,8 @@ module Fontisan
153
153
  elsif @copyright.nil? && (match = line.match(/^Notice\s+(\S.*)/i))
154
154
  @copyright = match[1].strip
155
155
  elsif @font_bbox.nil? && (match = line.match(/^FontBBox\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)$/i))
156
- @font_bbox = [match[1].to_i, match[2].to_i, match[3].to_i, match[4].to_i]
156
+ @font_bbox = [match[1].to_i, match[2].to_i, match[3].to_i,
157
+ match[4].to_i]
157
158
  end
158
159
 
159
160
  # Break early if all metrics found
@@ -49,7 +49,7 @@ module Fontisan
49
49
  cntrmask: 20, # CFF: 20, Type 1: Not supported (skip)
50
50
 
51
51
  # End char
52
- endchar: 14, # CFF: 14, Type 1: 14
52
+ endchar: 14, # CFF: 14, Type 1: 14
53
53
 
54
54
  # Miscellaneous
55
55
  callsubr: 10, # CFF: 10, Type 1: 10
@@ -83,7 +83,7 @@ module Fontisan
83
83
  def convert(cff_charstring, private_dict: {})
84
84
  # Parse CFF CharString into operations
85
85
  parser = Tables::Cff::CharStringParser.new(cff_charstring,
86
- stem_count: private_dict[:stem_count]&.to_i || 0)
86
+ stem_count: private_dict[:stem_count].to_i)
87
87
  operations = parser.parse
88
88
 
89
89
  # Extract width from operations (CFF spec: odd stack before first move = width)
@@ -106,13 +106,14 @@ module Fontisan
106
106
 
107
107
  # Find first move operator
108
108
  first_move_idx = operations.index do |op|
109
- %i[rmoveto hmoveto vmoveto rcurveline rrcurveline vvcurveto hhcurveto].include?(op[:name])
109
+ %i[rmoveto hmoveto vmoveto rcurveline rrcurveline vvcurveto
110
+ hhcurveto].include?(op[:name])
110
111
  end
111
112
 
112
113
  return @default_width unless first_move_idx
113
114
 
114
115
  # Count operands before first move
115
- operand_count = operations[0...first_move_idx].sum(0) do |op|
116
+ operand_count = operations[0...first_move_idx].sum do |op|
116
117
  op[:operands]&.length || 0
117
118
  end
118
119
 
@@ -140,10 +141,10 @@ module Fontisan
140
141
 
141
142
  # Add hsbw (horizontal sidebearing and width) at start
142
143
  # This is the standard width operator for horizontal fonts
143
- result << encode_number(0) # left sidebearing (usually 0 for CFF)
144
+ result << encode_number(0) # left sidebearing (usually 0 for CFF)
144
145
  result << encode_number(glyph_width)
145
146
  result << ESCAPE_BYTE
146
- result << 34 # hsbw operator (two-byte: 12 34)
147
+ result << 34 # hsbw operator (two-byte: 12 34)
147
148
 
148
149
  x = 0
149
150
  y = 0
@@ -154,11 +155,12 @@ module Fontisan
154
155
  if width && operations.any?
155
156
  # Count operands before first move to determine if width was in stack
156
157
  first_move_idx = operations.index do |op|
157
- %i[rmoveto hmoveto vmoveto rcurveline rrcurveline vvcurveto hhcurveto].include?(op[:name])
158
+ %i[rmoveto hmoveto vmoveto rcurveline rrcurveline vvcurveto
159
+ hhcurveto].include?(op[:name])
158
160
  end
159
161
 
160
162
  if first_move_idx
161
- operand_count = operations[0...first_move_idx].sum(0) do |op|
163
+ operand_count = operations[0...first_move_idx].sum do |op|
162
164
  op[:operands]&.length || 0
163
165
  end
164
166
 
@@ -182,16 +184,17 @@ module Fontisan
182
184
  end
183
185
 
184
186
  if operands.length >= 2
185
- dx, dy = operands[0], operands[1]
187
+ dx = operands[0]
188
+ dy = operands[1]
186
189
  x += dx
187
190
  y += dy
188
191
  result << encode_number(dx)
189
192
  result << encode_number(dy)
190
- result << 21 # rmoveto
193
+ result << 21 # rmoveto
191
194
  elsif operands.length == 1
192
195
  # Only dy (hmoveto/vmoveto style)
193
196
  result << encode_number(operands.first)
194
- result << 4 # vmoveto (closest approximation)
197
+ result << 4 # vmoveto (closest approximation)
195
198
  end
196
199
  first_move = false
197
200
  when :hmoveto
@@ -205,14 +208,14 @@ module Fontisan
205
208
  dx = operands.first
206
209
  x += dx if dx
207
210
  result << encode_number(dx)
208
- result << 22 # hmoveto
211
+ result << 22 # hmoveto
209
212
  first_move = false
210
213
  when :vmoveto
211
214
  # vmoveto dy
212
215
  dy = op[:operands].first
213
216
  y += dy if dy
214
217
  result << encode_number(dy)
215
- result << 4 # vmoveto
218
+ result << 4 # vmoveto
216
219
  first_move = false
217
220
  when :rlineto
218
221
  # rlineto dx dy
@@ -221,29 +224,31 @@ module Fontisan
221
224
  y += dy
222
225
  result << encode_number(dx)
223
226
  result << encode_number(dy)
224
- result << 5 # rlineto
227
+ result << 5 # rlineto
225
228
  first_move = false
226
229
  when :hlineto
227
230
  # hlineto dx
228
231
  dx = op[:operands].first
229
232
  x += dx
230
233
  result << encode_number(dx)
231
- result << 6 # hlineto
234
+ result << 6 # hlineto
232
235
  first_move = false
233
236
  when :vlineto
234
237
  # vlineto dy
235
238
  dy = op[:operands].first
236
239
  y += dy
237
240
  result << encode_number(dy)
238
- result << 7 # vlineto
241
+ result << 7 # vlineto
239
242
  first_move = false
240
243
  when :rrcurveto
241
244
  # rrcurveto dx1 dy1 dx2 dy2 dx3 dy3
242
245
  dx1, dy1, dx2, dy2, dx3, dy3 = op[:operands]
243
246
  x += dx1 + dx2 + dx3
244
247
  y += dy1 + dy2 + dy3
245
- [dx1, dy1, dx2, dy2, dx3, dy3].each { |val| result << encode_number(val) }
246
- result << 8 # rrcurveto
248
+ [dx1, dy1, dx2, dy2, dx3, dy3].each do |val|
249
+ result << encode_number(val)
250
+ end
251
+ result << 8 # rrcurveto
247
252
  first_move = false
248
253
  when :hhcurveto, :hvcurveto, :vhcurveto
249
254
  # Flexible curve operators
@@ -293,7 +298,7 @@ module Fontisan
293
298
  [num + 139].pack("C")
294
299
  else
295
300
  # Use escape sequence (255) followed by 2-byte signed integer
296
- num += 32768 if num < 0
301
+ num += 32768 if num.negative?
297
302
  [255, num % 256, num >> 8].pack("C*")
298
303
  end
299
304
  end
@@ -176,18 +176,39 @@ module Fontisan
176
176
  # puts priv.to_type1_format
177
177
  def to_type1_format
178
178
  result = []
179
- result << array_to_type1(:BlueValues, @blue_values) unless @blue_values.empty?
180
- result << array_to_type1(:OtherBlues, @other_blues) unless @other_blues.empty?
181
- result << array_to_type1(:FamilyBlues, @family_blues) unless @family_blues.empty?
182
- result << array_to_type1(:FamilyOtherBlues, @family_other_blues) unless @family_other_blues.empty?
179
+ unless @blue_values.empty?
180
+ result << array_to_type1(:BlueValues,
181
+ @blue_values)
182
+ end
183
+ unless @other_blues.empty?
184
+ result << array_to_type1(:OtherBlues,
185
+ @other_blues)
186
+ end
187
+ unless @family_blues.empty?
188
+ result << array_to_type1(:FamilyBlues,
189
+ @family_blues)
190
+ end
191
+ unless @family_other_blues.empty?
192
+ result << array_to_type1(:FamilyOtherBlues,
193
+ @family_other_blues)
194
+ end
183
195
  result << scalar_to_type1(:BlueScale, @blue_scale)
184
196
  result << scalar_to_type1(:BlueShift, @blue_shift)
185
197
  result << scalar_to_type1(:BlueFuzz, @blue_fuzz)
186
198
  result << array_to_type1(:StdHW, @std_hw) unless @std_hw.empty?
187
199
  result << array_to_type1(:StdVW, @std_vw) unless @std_vw.empty?
188
- result << array_to_type1(:StemSnapH, @stem_snap_h) unless @stem_snap_h.empty?
189
- result << array_to_type1(:StemSnapV, @stem_snap_v) unless @stem_snap_v.empty?
190
- result << boolean_to_type1(:ForceBold, @force_bold) unless @force_bold == false
200
+ unless @stem_snap_h.empty?
201
+ result << array_to_type1(:StemSnapH,
202
+ @stem_snap_h)
203
+ end
204
+ unless @stem_snap_v.empty?
205
+ result << array_to_type1(:StemSnapV,
206
+ @stem_snap_v)
207
+ end
208
+ unless @force_bold == false
209
+ result << boolean_to_type1(:ForceBold,
210
+ @force_bold)
211
+ end
191
212
  result << scalar_to_type1(:lenIV, @len_iv)
192
213
 
193
214
  result.join("\n")
@@ -68,11 +68,13 @@ module Fontisan
68
68
  accent_glyph_name = @charstrings.encoding[components[:accent]]
69
69
 
70
70
  if base_glyph_name.nil?
71
- raise Fontisan::Error, "Base glyph for char code #{components[:base]} not found"
71
+ raise Fontisan::Error,
72
+ "Base glyph for char code #{components[:base]} not found"
72
73
  end
73
74
 
74
75
  if accent_glyph_name.nil?
75
- raise Fontisan::Error, "Accent glyph for char code #{components[:accent]} not found"
76
+ raise Fontisan::Error,
77
+ "Accent glyph for char code #{components[:accent]} not found"
76
78
  end
77
79
 
78
80
  # Get CharStrings for base and accent
@@ -80,11 +82,13 @@ module Fontisan
80
82
  accent_charstring = @charstrings[accent_glyph_name]
81
83
 
82
84
  if base_charstring.nil?
83
- raise Fontisan::Error, "CharString not found for base glyph #{base_glyph_name}"
85
+ raise Fontisan::Error,
86
+ "CharString not found for base glyph #{base_glyph_name}"
84
87
  end
85
88
 
86
89
  if accent_charstring.nil?
87
- raise Fontisan::Error, "CharString not found for accent glyph #{accent_glyph_name}"
90
+ raise Fontisan::Error,
91
+ "CharString not found for accent glyph #{accent_glyph_name}"
88
92
  end
89
93
 
90
94
  # Parse both CharStrings into command sequences
@@ -92,7 +96,8 @@ module Fontisan
92
96
  accent_commands = parse_charstring_to_commands(accent_charstring)
93
97
 
94
98
  # Transform accent by (adx, ady) offset
95
- accent_commands = transform_commands(accent_commands, components[:adx], components[:ady])
99
+ accent_commands = transform_commands(accent_commands, components[:adx],
100
+ components[:ady])
96
101
 
97
102
  # Merge base and accent commands
98
103
  merged_commands = merge_outline_commands(base_commands, accent_commands)
@@ -278,8 +283,8 @@ module Fontisan
278
283
  dx1, dy1, dx2, dy2, dx3, dy3 = cmd[1..6]
279
284
  control_x = x + dx1
280
285
  control_y = y + dy1
281
- anchor_x = x + dx1 + dx2
282
- anchor_y = y + dy1 + dy2
286
+ x + dx1 + dx2
287
+ y + dy1 + dy2
283
288
  end_x = x + dx1 + dx2 + dx3
284
289
  end_y = y + dy1 + dy2 + dy3
285
290
 
@@ -288,7 +293,7 @@ module Fontisan
288
293
  cx: control_x,
289
294
  cy: control_y,
290
295
  x: end_x,
291
- y: end_y
296
+ y: end_y,
292
297
  }
293
298
  x = end_x
294
299
  y = end_y
@@ -326,13 +331,13 @@ module Fontisan
326
331
  # vhcurveto: dy1 dx2 dy2 dx3
327
332
  # hvcurveto: dx1 dy2 dx3 dy3
328
333
  if cmd[0] == :vhcurveto
329
- dy1, dx2, dy2, dx3 = cmd[1..4]
334
+ dy1, dx2, dy2, = cmd[1..4]
330
335
  control_x = x
331
336
  control_y = y + dy1
332
337
  end_x = x + dx2
333
338
  end_y = y + dy1 + dy2
334
339
  else
335
- dx1, dy2, dx3, dy3 = cmd[1..4]
340
+ dx1, dy2, _, dy3 = cmd[1..4]
336
341
  control_x = x + dx1
337
342
  control_y = y
338
343
  end_x = x + dx1 + dx2
@@ -344,7 +349,7 @@ module Fontisan
344
349
  cx: control_x,
345
350
  cy: control_y,
346
351
  x: end_x,
347
- y: end_y
352
+ y: end_y,
348
353
  }
349
354
  end
350
355
 
@@ -355,7 +360,7 @@ module Fontisan
355
360
  # @param dy [Integer] Y offset
356
361
  # @return [Array<Hash>] Transformed commands
357
362
  def transform_commands(commands, dx, dy)
358
- return commands if dx == 0 && dy == 0
363
+ return commands if dx.zero? && dy.zero?
359
364
 
360
365
  commands.map do |cmd|
361
366
  case cmd[:type]
@@ -367,7 +372,7 @@ module Fontisan
367
372
  cx: cmd[:cx] + dx,
368
373
  cy: cmd[:cy] + dy,
369
374
  x: cmd[:x] + dx,
370
- y: cmd[:y] + dy
375
+ y: cmd[:y] + dy,
371
376
  }
372
377
  when :curve_to
373
378
  {
@@ -377,7 +382,7 @@ module Fontisan
377
382
  cx2: cmd[:cx2] + dx,
378
383
  cy2: cmd[:cy2] + dy,
379
384
  x: cmd[:x] + dx,
380
- y: cmd[:y] + dy
385
+ y: cmd[:y] + dy,
381
386
  }
382
387
  else
383
388
  cmd # close_path, etc. pass through
@@ -428,8 +433,8 @@ module Fontisan
428
433
  # Use hsbw to set initial position
429
434
  dx = cmd[:x] - x
430
435
  # hsbw is a two-byte operator: 12 34
431
- charstring << encode_number(dx) # sbw value
432
- charstring << encode_number(0) # width (always 0 for decomposed glyphs)
436
+ charstring << encode_number(dx) # sbw value
437
+ charstring << encode_number(0) # width (always 0 for decomposed glyphs)
433
438
  charstring << 12 # First byte of two-byte operator
434
439
  charstring << 34 # Second byte - hsbw
435
440
  x = cmd[:x]
@@ -492,7 +497,7 @@ module Fontisan
492
497
  [num + 139].pack("C*")
493
498
  else
494
499
  # Use escape sequence (255) followed by 2-byte signed integer
495
- num += 32768 if num < 0
500
+ num += 32768 if num.negative?
496
501
  [255, num % 256, num >> 8].pack("C*")
497
502
  end
498
503
  end
@@ -136,13 +136,13 @@ module Fontisan
136
136
  normalized_coords = @axis_normalizer.normalize(user_coords)
137
137
  region_scalars = @region_matcher.match(normalized_coords)
138
138
 
139
- glyph_ids.each_with_object({}) do |glyph_id, results|
140
- results[glyph_id] = {
139
+ glyph_ids.to_h do |glyph_id|
140
+ [glyph_id, {
141
141
  outline_deltas: @glyph_delta_processor&.apply_deltas(glyph_id,
142
142
  region_scalars),
143
143
  metric_deltas: @metric_delta_processor.apply_deltas(glyph_id,
144
144
  region_scalars),
145
- }
145
+ }]
146
146
  end
147
147
  end
148
148
 
@@ -352,7 +352,6 @@ module Fontisan
352
352
  #
353
353
  # @return [Set<Integer>] Set of used indices
354
354
  def collect_used_variation_indices
355
- require "set"
356
355
  used = Set.new
357
356
 
358
357
  glyph_count = @cff2.glyph_count
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.13"
4
+ VERSION = "0.2.16"
5
5
  end
@@ -373,11 +373,11 @@ module Fontisan
373
373
  raise InvalidFontError,
374
374
  "Unknown WOFF2 flavor: 0x#{woff2.header.flavor.to_s(16)}"
375
375
  end
376
+ rescue BinData::ValidityError, EOFError => e
377
+ raise InvalidFontError, "Invalid WOFF2 file: #{e.message}"
376
378
  end
377
379
 
378
380
  woff2
379
- rescue BinData::ValidityError, EOFError => e
380
- raise InvalidFontError, "Invalid WOFF2 file: #{e.message}"
381
381
  end
382
382
 
383
383
  # Read table directory from IO
@@ -98,10 +98,10 @@ module Fontisan
98
98
  font.io_source = io
99
99
  font.read_compressed_table_data(io)
100
100
  font
101
+ rescue BinData::ValidityError, EOFError => e
102
+ Kernel.raise(::Fontisan::InvalidFontError,
103
+ "Invalid WOFF file: #{e.message}")
101
104
  end
102
- rescue BinData::ValidityError, EOFError => e
103
- Kernel.raise(::Fontisan::InvalidFontError,
104
- "Invalid WOFF file: #{e.message}")
105
105
  end
106
106
 
107
107
  # Initialize storage hashes
data/lib/fontisan.rb CHANGED
@@ -33,10 +33,11 @@ require "bindata"
33
33
  require "zlib"
34
34
  require "stringio"
35
35
  require "lutaml/model"
36
- require "lutaml/model/xml_adapter/nokogiri_adapter"
37
36
 
38
37
  # Configure lutaml-model to use Nokogiri adapter for XML serialization
39
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::NokogiriAdapter
38
+ Lutaml::Model::Config.configure do |config|
39
+ config.xml_adapter_type = :nokogiri
40
+ end
40
41
 
41
42
  # Core
42
43
  require_relative "fontisan/version"