fontisan 0.4.22 → 0.4.24

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +240 -24
  3. data/Gemfile +6 -0
  4. data/Rakefile +9 -6
  5. data/benchmark/compile_benchmark.rb +13 -5
  6. data/docs/.vitepress/config.ts +1 -0
  7. data/docs/STITCHER_GUIDE.adoc +47 -3
  8. data/docs/api/layer.md +89 -0
  9. data/docs/cli/subset.md +25 -6
  10. data/docs/guide/cli/subset.md +31 -0
  11. data/docs/guide/color-fonts/bitmaps.md +17 -0
  12. data/lib/fontisan/cli.rb +2 -1
  13. data/lib/fontisan/collection/writer.rb +2 -1
  14. data/lib/fontisan/commands/validate_collection_command.rb +7 -1
  15. data/lib/fontisan/commands.rb +2 -1
  16. data/lib/fontisan/config/subset_profiles.yml +2 -0
  17. data/lib/fontisan/converters/collection_converter.rb +6 -1
  18. data/lib/fontisan/converters/svg_generator.rb +3 -1
  19. data/lib/fontisan/stitcher/cbdt_propagator.rb +44 -22
  20. data/lib/fontisan/stitcher/collection_result.rb +2 -1
  21. data/lib/fontisan/stitcher/glyph_cloner.rb +41 -0
  22. data/lib/fontisan/stitcher/glyph_copier.rb +5 -34
  23. data/lib/fontisan/stitcher/partition_strategy/by_block.rb +3 -1
  24. data/lib/fontisan/stitcher/partition_strategy/by_script.rb +6 -2
  25. data/lib/fontisan/stitcher/source.rb +2 -1
  26. data/lib/fontisan/stitcher/unique_glyph_name.rb +46 -0
  27. data/lib/fontisan/stitcher.rb +4 -1
  28. data/lib/fontisan/subset/shared_state.rb +42 -0
  29. data/lib/fontisan/subset/subset_context.rb +20 -0
  30. data/lib/fontisan/subset/table_strategy/cbdt.rb +30 -0
  31. data/lib/fontisan/subset/table_strategy/cblc.rb +27 -0
  32. data/lib/fontisan/subset/table_strategy/cmap.rb +146 -0
  33. data/lib/fontisan/subset/table_strategy/color_bitmap_placement.rb +20 -0
  34. data/lib/fontisan/subset/table_strategy/color_bitmap_strike_plan.rb +45 -0
  35. data/lib/fontisan/subset/table_strategy/color_bitmap_subset_plan.rb +62 -0
  36. data/lib/fontisan/subset/table_strategy/color_bitmap_subsetter.rb +182 -0
  37. data/lib/fontisan/subset/table_strategy/color_bitmap_subtable_plan.rb +59 -0
  38. data/lib/fontisan/subset/table_strategy/glyf.rb +29 -0
  39. data/lib/fontisan/subset/table_strategy/glyf_loca_builder.rb +146 -0
  40. data/lib/fontisan/subset/table_strategy/head.rb +42 -0
  41. data/lib/fontisan/subset/table_strategy/hhea.rb +67 -0
  42. data/lib/fontisan/subset/table_strategy/hmtx.rb +52 -0
  43. data/lib/fontisan/subset/table_strategy/loca.rb +41 -0
  44. data/lib/fontisan/subset/table_strategy/maxp.rb +23 -0
  45. data/lib/fontisan/subset/table_strategy/name.rb +19 -0
  46. data/lib/fontisan/subset/table_strategy/os2.rb +21 -0
  47. data/lib/fontisan/subset/table_strategy/pass_through.rb +20 -0
  48. data/lib/fontisan/subset/table_strategy/post.rb +37 -0
  49. data/lib/fontisan/subset/table_strategy.rb +75 -0
  50. data/lib/fontisan/subset/table_subsetter.rb +49 -616
  51. data/lib/fontisan/subset.rb +3 -0
  52. data/lib/fontisan/svg/standalone_glyph.rb +4 -1
  53. data/lib/fontisan/svg_to_glyf/assembler.rb +6 -3
  54. data/lib/fontisan/svg_to_glyf/geometry/transform_parser.rb +11 -4
  55. data/lib/fontisan/svg_to_glyf/geometry.rb +4 -2
  56. data/lib/fontisan/tables/cbdt.rb +66 -121
  57. data/lib/fontisan/tables/cblc.rb +110 -231
  58. data/lib/fontisan/tables/cblc_big_glyph_metrics.rb +28 -0
  59. data/lib/fontisan/tables/cblc_bitmap_size.rb +67 -0
  60. data/lib/fontisan/tables/cblc_glyph_bitmap_location.rb +28 -0
  61. data/lib/fontisan/tables/cblc_index_subtable.rb +104 -0
  62. data/lib/fontisan/tables/cblc_index_subtable_array_entry.rb +20 -0
  63. data/lib/fontisan/tables/cblc_index_subtable_format_parser.rb +150 -0
  64. data/lib/fontisan/tables/cblc_index_subtable_header.rb +18 -0
  65. data/lib/fontisan/tables/cblc_sbit_line_metrics.rb +28 -0
  66. data/lib/fontisan/tables/cff/cff2_charstring_builder.rb +11 -5
  67. data/lib/fontisan/tables/cff.rb +2 -1
  68. data/lib/fontisan/tables/cff2/index_builder.rb +2 -1
  69. data/lib/fontisan/tables.rb +12 -0
  70. data/lib/fontisan/ufo/cli.rb +4 -1
  71. data/lib/fontisan/ufo/compile/cbdt_cblc.rb +2 -1
  72. data/lib/fontisan/ufo/compile/cff.rb +6 -2
  73. data/lib/fontisan/ufo/compile/cmap.rb +11 -5
  74. data/lib/fontisan/ufo/compile/cpal.rb +2 -1
  75. data/lib/fontisan/ufo/compile/feature_writers/gdef.rb +3 -1
  76. data/lib/fontisan/ufo/compile/feature_writers/mark_family_base.rb +3 -1
  77. data/lib/fontisan/ufo/compile/feature_writers.rb +2 -1
  78. data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +4 -2
  79. data/lib/fontisan/ufo/compile/filters/remove_overlaps.rb +2 -1
  80. data/lib/fontisan/ufo/compile/fvar.rb +7 -3
  81. data/lib/fontisan/ufo/compile/glyf_loca.rb +4 -2
  82. data/lib/fontisan/ufo/compile/gvar.rb +5 -2
  83. data/lib/fontisan/ufo/compile/head.rb +2 -1
  84. data/lib/fontisan/ufo/compile/item_variation_store.rb +2 -1
  85. data/lib/fontisan/ufo/compile/name.rb +3 -1
  86. data/lib/fontisan/ufo/compile/sbix.rb +2 -1
  87. data/lib/fontisan/ufo/compile/stat.rb +6 -3
  88. data/lib/fontisan/ufo/convert/from_bin_data.rb +6 -2
  89. data/lib/fontisan/ufo/convert/to_dfont.rb +6 -2
  90. data/lib/fontisan/ufo/convert/to_otc.rb +4 -1
  91. data/lib/fontisan/ufo/convert/to_postscript.rb +8 -3
  92. data/lib/fontisan/ufo/convert/to_woff.rb +8 -3
  93. data/lib/fontisan/ufo/convert/to_woff2.rb +8 -3
  94. data/lib/fontisan/ufo/convert.rb +4 -1
  95. data/lib/fontisan/ufo/glyph_exists_error.rb +23 -0
  96. data/lib/fontisan/ufo/info.rb +4 -2
  97. data/lib/fontisan/ufo/layer.rb +42 -2
  98. data/lib/fontisan/ufo/plist.rb +7 -2
  99. data/lib/fontisan/ufo/reader.rb +3 -1
  100. data/lib/fontisan/ufo/writer.rb +13 -5
  101. data/lib/fontisan/ufo.rb +2 -1
  102. data/lib/fontisan/version.rb +1 -1
  103. data/lib/fontisan/woff2/collection_decoder.rb +4 -1
  104. data/lib/fontisan/woff2/collection_encoder.rb +4 -1
  105. data/lib/fontisan/woff2/glyf_canonicalizer.rb +2 -1
  106. data/lib/fontisan/woff2/glyf_loca_reconstruct.rb +5 -2
  107. data/lib/fontisan/woff2/sfnt_checksum.rb +2 -1
  108. data/lib/fontisan.rb +0 -1
  109. metadata +36 -4
  110. data/lib/fontisan/tasks/fixture_downloader.rb +0 -162
  111. data/lib/fontisan/tasks.rb +0 -11
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Tables
5
+ # Per-format CBLC IndexSubTable parser. Extracted as a sibling class so
6
+ # that CblcIndexSubTable remains a pure data model.
7
+ #
8
+ # The five OpenType formats differ in how the offset array encodes
9
+ # glyph lengths and CBDT offsets. This class hides that arithmetic
10
+ # behind a uniform [.locations] entry point that returns a flat list
11
+ # of CblcGlyphBitmapLocation objects (one per glyph in the range).
12
+ #
13
+ # Reference: OpenType CBLC spec, IndexSubTable formats 1–5.
14
+ class CblcIndexSubTableFormatParser
15
+ # Format 4 (bit-packed offsets) is rare and not currently parsed.
16
+ class UnsupportedFormat < Fontisan::Error; end
17
+
18
+ class << self
19
+ # Returns an Array of CblcGlyphBitmapLocation, one per glyph in
20
+ # the IndexSubTable's glyph range.
21
+ #
22
+ # @param header [CblcIndexSubTableHeader] the 8-byte IndexSubTable header
23
+ # @param bytes [String] the full CBLC table bytes
24
+ # @param base [Integer] absolute offset of this IndexSubTable within CBLC
25
+ # @param first [Integer] first glyph ID covered
26
+ # @param last [Integer] last glyph ID covered
27
+ # @return [Array<CblcGlyphBitmapLocation>]
28
+ def locations(header:, bytes:, base:, first:, last:)
29
+ case header.index_format
30
+ when 1 then format_1(header, bytes, base, first, last)
31
+ when 2 then format_2(header, bytes, base, first, last)
32
+ when 3 then format_3(header, bytes, base, first, last)
33
+ when 5 then format_5(header, bytes, base, first, last)
34
+ when 4
35
+ raise UnsupportedFormat,
36
+ "CBLC IndexSubTable format 4 (bit-packed) is unsupported"
37
+ else
38
+ raise UnsupportedFormat,
39
+ "Unknown CBLC IndexSubTable format: #{header.index_format}"
40
+ end
41
+ end
42
+
43
+ # Returns the raw bytes the IndexSubTable occupies in CBLC.
44
+ #
45
+ # @return [String]
46
+ def raw_bytes(header:, bytes:, base:, first:, last:)
47
+ count = last - first + 1
48
+ length = byte_length(header.index_format, count, bytes, base)
49
+ bytes[base, length]
50
+ end
51
+
52
+ private
53
+
54
+ # Format 1: variable metrics + variable size, uint32 offsetArray.
55
+ # offsets[0] is the first glyph's bitmap start (relative to
56
+ # imageDataOffset); offsets[i+1] - offsets[i] = glyph i's length.
57
+ def format_1(header, bytes, base, first, last)
58
+ count = last - first + 1
59
+ offsets = uint32_array(bytes, base + 8, count + 1)
60
+ build(first, count, offsets, header.image_format,
61
+ header.image_data_offset)
62
+ end
63
+
64
+ # Format 2: constant metrics + constant size. One imageSize field
65
+ # applies to every glyph; the i-th glyph's bitmap is at
66
+ # imageDataOffset + i * imageSize.
67
+ def format_2(header, bytes, base, first, last)
68
+ count = last - first + 1
69
+ image_size = bytes[base + 8, 4].unpack1("N")
70
+ Array.new(count) do |i|
71
+ CblcGlyphBitmapLocation.new(
72
+ glyph_id: first + i,
73
+ image_format: header.image_format,
74
+ cbdt_offset: header.image_data_offset + (i * image_size),
75
+ byte_length: image_size,
76
+ )
77
+ end
78
+ end
79
+
80
+ # Format 3: like format 1 but offsets are uint16 and stored as
81
+ # multiples of 4 (CBDT bitmap data is 4-byte aligned).
82
+ def format_3(header, bytes, base, first, last)
83
+ count = last - first + 1
84
+ raw = uint16_array(bytes, base + 8, count + 1)
85
+ offsets = raw.map { |v| v * 4 }
86
+ build(first, count, offsets, header.image_format,
87
+ header.image_data_offset)
88
+ end
89
+
90
+ # Format 5: constant metrics + constant size with an explicit
91
+ # uint32 offsetArray (count + 1 entries). Length per glyph is
92
+ # the constant imageSize; offset comes from the array.
93
+ def format_5(header, bytes, base, first, last)
94
+ count = last - first + 1
95
+ image_size = bytes[base + 8, 4].unpack1("N")
96
+ offset_array_start = base + 12 + 8 + 4
97
+ offsets = uint32_array(bytes, offset_array_start, count + 1)
98
+ Array.new(count) do |i|
99
+ start_off = offsets[i] || 0
100
+ CblcGlyphBitmapLocation.new(
101
+ glyph_id: first + i,
102
+ image_format: header.image_format,
103
+ cbdt_offset: header.image_data_offset + start_off,
104
+ byte_length: image_size,
105
+ )
106
+ end
107
+ end
108
+
109
+ def build(first, count, offsets, image_format, image_data_offset)
110
+ Array.new(count) do |i|
111
+ start_off = offsets[i] || 0
112
+ end_off = offsets[i + 1] || start_off
113
+ CblcGlyphBitmapLocation.new(
114
+ glyph_id: first + i,
115
+ image_format: image_format,
116
+ cbdt_offset: image_data_offset + start_off,
117
+ byte_length: end_off - start_off,
118
+ )
119
+ end
120
+ end
121
+
122
+ def byte_length(index_format, count, bytes, base)
123
+ case index_format
124
+ when 1, 3
125
+ entry_size = index_format == 1 ? 4 : 2
126
+ 8 + (count + 1) * entry_size
127
+ when 2
128
+ 8 + 4 + 8 # header + imageSize + bigGlyphMetrics
129
+ when 5
130
+ 8 + 4 + 8 + 4 + (count + 1) * 4
131
+ else
132
+ bytes.bytesize - base
133
+ end
134
+ end
135
+
136
+ def uint32_array(bytes, offset, count)
137
+ return [] if count.zero?
138
+
139
+ bytes[offset, count * 4].unpack("N*")
140
+ end
141
+
142
+ def uint16_array(bytes, offset, count)
143
+ return [] if count.zero?
144
+
145
+ bytes[offset, count * 2].unpack("n*")
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Tables
5
+ # 8-byte header common to all CBLC IndexSubTable formats.
6
+ #
7
+ # uint16 indexFormat
8
+ # uint16 imageFormat
9
+ # uint32 imageDataOffset (absolute, from start of CBDT)
10
+ #
11
+ # Format-specific data follows immediately after this header.
12
+ class CblcIndexSubTableHeader < Binary::BaseRecord
13
+ uint16 :index_format
14
+ uint16 :image_format
15
+ uint32 :image_data_offset
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Tables
5
+ # SbitLineMetrics record embedded in CBLC BitmapSize records.
6
+ #
7
+ # Two of these (horizontal + vertical) appear in every BitmapSize,
8
+ # totalling 24 bytes per strike. The fields are signed/unsigned 8-bit
9
+ # integers; OpenType specifies signed values for some, unsigned for
10
+ # others, mirroring the EBDT/BDF layout.
11
+ #
12
+ # Reference: OpenType CBLC spec, "SbitLineMetrics" sub-structure.
13
+ class CblcSbitLineMetrics < Binary::BaseRecord
14
+ int8 :ascender
15
+ int8 :descender
16
+ uint8 :width_max
17
+ int8 :caret_slope_numerator
18
+ int8 :caret_slope_denominator
19
+ int8 :caret_offset
20
+ int8 :min_origin_sb
21
+ int8 :min_advance_sb
22
+ int8 :max_before_bl
23
+ int8 :min_after_bl
24
+ int8 :pad1
25
+ int8 :pad2
26
+ end
27
+ end
28
+ end
@@ -40,8 +40,10 @@ module Fontisan
40
40
  # @param num_regions [Integer] number of variation regions
41
41
  # @param width [Integer, nil] advance width
42
42
  # @return [String] CFF2 charstring bytes with blend sequences
43
- def self.build_variable(outline, master_outlines:, num_regions:, width: nil)
44
- new.build_variable_outline(outline, master_outlines, num_regions, width)
43
+ def self.build_variable(outline, master_outlines:, num_regions:,
44
+ width: nil)
45
+ new.build_variable_outline(outline, master_outlines, num_regions,
46
+ width)
45
47
  end
46
48
 
47
49
  # ---------- variable-font encoding ----------
@@ -65,7 +67,9 @@ module Fontisan
65
67
 
66
68
  # Process an outline with blend operators for each varying coordinate.
67
69
  def encode_variable_outline(outline, master_outlines, num_regions)
68
- @master_states = master_outlines.map { MasterState.new(current_x: 0, current_y: 0) }
70
+ @master_states = master_outlines.map do
71
+ MasterState.new(current_x: 0, current_y: 0)
72
+ end
69
73
  @num_regions = num_regions
70
74
  @has_blend = false
71
75
 
@@ -90,7 +94,8 @@ module Fontisan
90
94
  # ---------- variable moveto ----------
91
95
 
92
96
  def encode_variable_moveto(cmd, master_cmds)
93
- dx, dy, dx_deltas, dy_deltas = compute_variable_deltas(cmd, master_cmds)
97
+ dx, dy, dx_deltas, dy_deltas = compute_variable_deltas(cmd,
98
+ master_cmds)
94
99
 
95
100
  write_variable_number(dx, dx_deltas)
96
101
  write_variable_number(dy, dy_deltas)
@@ -102,7 +107,8 @@ module Fontisan
102
107
  # ---------- variable lineto ----------
103
108
 
104
109
  def encode_variable_lineto(cmd, master_cmds)
105
- dx, dy, dx_deltas, dy_deltas = compute_variable_deltas(cmd, master_cmds)
110
+ dx, dy, dx_deltas, dy_deltas = compute_variable_deltas(cmd,
111
+ master_cmds)
106
112
 
107
113
  write_variable_number(dx, dx_deltas)
108
114
  write_variable_number(dy, dy_deltas)
@@ -47,7 +47,8 @@ module Fontisan
47
47
  autoload :Charset, "fontisan/tables/cff/charset"
48
48
  autoload :CharString, "fontisan/tables/cff/charstring"
49
49
  autoload :CharStringBuilder, "fontisan/tables/cff/charstring_builder"
50
- autoload :Cff2CharStringBuilder, "fontisan/tables/cff/cff2_charstring_builder"
50
+ autoload :Cff2CharStringBuilder,
51
+ "fontisan/tables/cff/cff2_charstring_builder"
51
52
  autoload :CharStringParser, "fontisan/tables/cff/charstring_parser"
52
53
  autoload :CharStringRebuilder, "fontisan/tables/cff/charstring_rebuilder"
53
54
  autoload :CharstringsIndex, "fontisan/tables/cff/charstrings_index"
@@ -66,7 +66,8 @@ module Fontisan
66
66
  case off_size
67
67
  when 1 then [value].pack("C")
68
68
  when 2 then [value].pack("n")
69
- when 3 then [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF].pack("CCC")
69
+ when 3 then [(value >> 16) & 0xFF, (value >> 8) & 0xFF,
70
+ value & 0xFF].pack("CCC")
70
71
  when 4 then [value].pack("N")
71
72
  else raise ArgumentError, "invalid off_size: #{off_size}"
72
73
  end
@@ -4,6 +4,18 @@
4
4
 
5
5
  module Fontisan
6
6
  module Tables
7
+ autoload :CblcBigGlyphMetrics, "fontisan/tables/cblc_big_glyph_metrics"
8
+ autoload :CblcBitmapSize, "fontisan/tables/cblc_bitmap_size"
9
+ autoload :CblcGlyphBitmapLocation,
10
+ "fontisan/tables/cblc_glyph_bitmap_location"
11
+ autoload :CblcIndexSubTable, "fontisan/tables/cblc_index_subtable"
12
+ autoload :CblcIndexSubTableArrayEntry,
13
+ "fontisan/tables/cblc_index_subtable_array_entry"
14
+ autoload :CblcIndexSubTableFormatParser,
15
+ "fontisan/tables/cblc_index_subtable_format_parser"
16
+ autoload :CblcIndexSubTableHeader,
17
+ "fontisan/tables/cblc_index_subtable_header"
18
+ autoload :CblcSbitLineMetrics, "fontisan/tables/cblc_sbit_line_metrics"
7
19
  autoload :Cbdt, "fontisan/tables/cbdt"
8
20
  autoload :Cblc, "fontisan/tables/cblc"
9
21
  autoload :Cff, "fontisan/tables/cff"
@@ -74,7 +74,10 @@ module Fontisan
74
74
  def extract(ufo, glyph_name, output)
75
75
  font = Font.open(ufo)
76
76
  glyph = font.glyph(glyph_name)
77
- raise ArgumentError, "glyph not found: #{glyph_name.inspect}" unless glyph
77
+ unless glyph
78
+ raise ArgumentError,
79
+ "glyph not found: #{glyph_name.inspect}"
80
+ end
78
81
 
79
82
  head = font.info
80
83
  renderer = Fontisan::Svg::StandaloneGlyph.new(
@@ -65,7 +65,8 @@ module Fontisan
65
65
  # SbitLineMetrics (12 bytes): height, width, hori/vert bearings
66
66
  def self.build_sbit_line_metrics(ppem, num_glyphs)
67
67
  io = +""
68
- io << [ppem & 0xFF, 0, ppem & 0xFF, 0, 0, 0, ppem & 0xFF, 0, 0, 0, 0].pack("C11")
68
+ io << [ppem & 0xFF, 0, ppem & 0xFF, 0, 0, 0, ppem & 0xFF, 0, 0, 0,
69
+ 0].pack("C11")
69
70
  io << [12 + num_glyphs * 8].pack("n")
70
71
  io
71
72
  end
@@ -155,7 +155,10 @@ module Fontisan
155
155
  return +"" if glyphs.size <= 1
156
156
 
157
157
  bytes = +"\x00"
158
- (1...glyphs.size).each { |_| bytes << [0].pack("n") } # SID 0 placeholder
158
+ # SID 0 placeholder
159
+ (1...glyphs.size).each do |_|
160
+ bytes << [0].pack("n")
161
+ end
159
162
  bytes
160
163
  end
161
164
 
@@ -193,7 +196,8 @@ module Fontisan
193
196
  when 1 then [value].pack("C")
194
197
  when 2 then [value].pack("n")
195
198
  when 3
196
- [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF].pack("CCC")
199
+ [(value >> 16) & 0xFF, (value >> 8) & 0xFF,
200
+ value & 0xFF].pack("CCC")
197
201
  when 4 then [value].pack("N")
198
202
  end
199
203
  end
@@ -36,7 +36,9 @@ module Fontisan
36
36
  end
37
37
  end
38
38
 
39
- subtable_bmp = format4_subtable(mappings.reject { |cp, _| cp > 0xFFFF })
39
+ subtable_bmp = format4_subtable(mappings.reject do |cp, _|
40
+ cp > 0xFFFF
41
+ end)
40
42
  subtable_full = format12_subtable(mappings)
41
43
 
42
44
  header_size = 4 + (2 * 8) # version + numTables + 2 records
@@ -44,8 +46,10 @@ module Fontisan
44
46
  offset_full = header_size + subtable_bmp.bytesize
45
47
 
46
48
  header = [0, 2].pack("nn")
47
- header << subtable_record(PLATFORM_WINDOWS, ENCODING_WINDOWS_BMP, offset_bmp)
48
- header << subtable_record(PLATFORM_WINDOWS, ENCODING_WINDOWS_FULL, offset_full)
49
+ header << subtable_record(PLATFORM_WINDOWS, ENCODING_WINDOWS_BMP,
50
+ offset_bmp)
51
+ header << subtable_record(PLATFORM_WINDOWS, ENCODING_WINDOWS_FULL,
52
+ offset_full)
49
53
  header + subtable_bmp + subtable_full
50
54
  end
51
55
 
@@ -82,7 +86,8 @@ module Fontisan
82
86
  segments = build_segments(mappings) + [0xFFFF..0xFFFF] # sentinel
83
87
  seg_count = segments.size
84
88
  search_range = largest_pow2_le(seg_count) * 2
85
- entry_selector = (Math.log([1, search_range / 2].max) / Math.log(2)).to_i
89
+ entry_selector = (Math.log([1,
90
+ search_range / 2].max) / Math.log(2)).to_i
86
91
  range_shift = seg_count * 2 - search_range
87
92
 
88
93
  end_codes = segments.map(&:end)
@@ -98,7 +103,8 @@ module Fontisan
98
103
  end
99
104
 
100
105
  body = +""
101
- body << [seg_count * 2, search_range, entry_selector, range_shift].pack("nnnn")
106
+ body << [seg_count * 2, search_range, entry_selector,
107
+ range_shift].pack("nnnn")
102
108
  body << end_codes.pack("n*")
103
109
  body << [0].pack("n") # reservedPad
104
110
  body << start_codes.pack("n*")
@@ -38,7 +38,8 @@ module Fontisan
38
38
  offset = HEADER_SIZE + (num_palettes * 2) # header + indices
39
39
 
40
40
  io = +""
41
- io << [VERSION, num_entries, num_palettes, num_records, offset].pack("nnnnN")
41
+ io << [VERSION, num_entries, num_palettes, num_records,
42
+ offset].pack("nnnnN")
42
43
  indices.each { |idx| io << [idx].pack("n") }
43
44
  records.each { |c| io << color_bytes(c) }
44
45
  io
@@ -78,7 +78,9 @@ module Fontisan
78
78
  def heuristic_class(glyph)
79
79
  anchor_names = glyph.anchors.map(&:name)
80
80
  mark_anchor_pattern = /\A_[a-zA-Z]+\z/
81
- return 3 if anchor_names.any? { |n| mark_anchor_pattern.match?(n.to_s) }
81
+ return 3 if anchor_names.any? do |n|
82
+ mark_anchor_pattern.match?(n.to_s)
83
+ end
82
84
  return 2 if glyph.name.to_s.end_with?(".liga")
83
85
 
84
86
  nil
@@ -28,7 +28,9 @@ module Fontisan
28
28
 
29
29
  attachments[class_name] ||= new_class_entry
30
30
  attachments[class_name][:marks][mark_name] = mark_anchor
31
- bases.each { |n, a| attachments[class_name][base_bucket_key][n] = a }
31
+ bases.each do |n, a|
32
+ attachments[class_name][base_bucket_key][n] = a
33
+ end
32
34
  end
33
35
  end
34
36
 
@@ -16,7 +16,8 @@ module Fontisan
16
16
  # entry. No edits to the compiler.
17
17
  module FeatureWriters
18
18
  autoload :Base, "fontisan/ufo/compile/feature_writers/base"
19
- autoload :MarkFamilyBase, "fontisan/ufo/compile/feature_writers/mark_family_base"
19
+ autoload :MarkFamilyBase,
20
+ "fontisan/ufo/compile/feature_writers/mark_family_base"
20
21
  autoload :Kern, "fontisan/ufo/compile/feature_writers/kern"
21
22
  autoload :Kern2, "fontisan/ufo/compile/feature_writers/kern2"
22
23
  autoload :Gdef, "fontisan/ufo/compile/feature_writers/gdef"
@@ -134,8 +134,10 @@ module Fontisan
134
134
  right_c2 = Point.new(x: r2x, y: r2y, type: "offcurve")
135
135
  right_p3 = p3
136
136
 
137
- left = subdivide_cubic(left_p0, left_c1, left_c2, left_p3, tolerance)
138
- right = subdivide_cubic(right_p0, right_c1, right_c2, right_p3, tolerance)
137
+ left = subdivide_cubic(left_p0, left_c1, left_c2, left_p3,
138
+ tolerance)
139
+ right = subdivide_cubic(right_p0, right_c1, right_c2, right_p3,
140
+ tolerance)
139
141
 
140
142
  # Drop the duplicated midpoint point between halves
141
143
  left + right
@@ -56,7 +56,8 @@ module Fontisan
56
56
  next if i == j || drop.include?(j)
57
57
 
58
58
  # Drop inner when its bbox is fully inside outer's.
59
- if contained_in?(bboxes[j], bboxes[i]) && (inner.points.size <= outer.points.size)
59
+ if contained_in?(bboxes[j],
60
+ bboxes[i]) && (inner.points.size <= outer.points.size)
60
61
  # Sanity: only drop if the point count is smaller —
61
62
  # a fully-contained contour with MORE points is
62
63
  # almost certainly the outer one in a malformed
@@ -28,7 +28,8 @@ module Fontisan
28
28
 
29
29
  axes_bytes = build_axes(axes)
30
30
  instances_bytes = build_instances(instances || [], axes.size)
31
- header = build_header(axes.size, axes_bytes, instances_bytes, instances)
31
+ header = build_header(axes.size, axes_bytes, instances_bytes,
32
+ instances)
32
33
  header + axes_bytes + instances_bytes
33
34
  end
34
35
 
@@ -38,7 +39,8 @@ module Fontisan
38
39
  # majorVersion (2) + minorVersion (2) + axesArrayOffset (2)
39
40
  # + reserved (2) + axisCount (2) + axisSize (2)
40
41
  # + instanceCount (2) + instanceSize (2)
41
- def self.build_header(axis_count, axes_bytes, _instances_bytes, instances)
42
+ def self.build_header(axis_count, axes_bytes, _instances_bytes,
43
+ instances)
42
44
  axes_offset = 16
43
45
  axes_offset + axes_bytes.bytesize
44
46
 
@@ -86,7 +88,9 @@ module Fontisan
86
88
  name_id = inst[:name_id] || inst["name_id"] || 0
87
89
  flags = inst[:flags] || inst["flags"] || 0
88
90
  coords = inst[:coords] || inst["coords"] || []
89
- padded = coords.first(axis_count) + Array.new([axis_count - coords.size, 0].max, 0)
91
+ padded = coords.first(axis_count) + Array.new(
92
+ [axis_count - coords.size, 0].max, 0
93
+ )
90
94
  padded = padded.first(axis_count).map { |c| fixed_value(c) }
91
95
 
92
96
  io << [name_id, flags].pack("nn")
@@ -41,7 +41,8 @@ module Fontisan
41
41
  offsets.map { |o| o / 2 }.pack("n*")
42
42
  end
43
43
 
44
- { "glyf" => glyf_bytes, "loca" => loca_bytes, :loca_format => use_long ? 1 : 0 }
44
+ { "glyf" => glyf_bytes, "loca" => loca_bytes,
45
+ :loca_format => use_long ? 1 : 0 }
45
46
  end
46
47
 
47
48
  # Encode a single glyph into glyf bytes. Empty glyphs (no
@@ -138,7 +139,8 @@ module Fontisan
138
139
  # nothing).
139
140
  ""
140
141
  end
141
- private_class_method :encode_glyph, :encode_simple, :encode_points, :encode_composite
142
+ private_class_method :encode_glyph, :encode_simple, :encode_points,
143
+ :encode_composite
142
144
  end
143
145
  end
144
146
  end
@@ -121,7 +121,9 @@ module Fontisan
121
121
  deltas = tuple[:deltas]
122
122
 
123
123
  # Encode delta data
124
- all_fit_int8 = deltas.all? { |dx, dy| dx.between?(-127, 127) && dy.between?(-127, 127) }
124
+ all_fit_int8 = deltas.all? do |dx, dy|
125
+ dx.between?(-127, 127) && dy.between?(-127, 127)
126
+ end
125
127
 
126
128
  point_count = deltas.size
127
129
  flags = point_count & POINT_COUNT_MASK
@@ -195,7 +197,8 @@ module Fontisan
195
197
 
196
198
  def self.build_empty(glyph_count)
197
199
  # Minimal gvar with no variation data
198
- header = [VERSION_MAJOR, VERSION_MINOR, 0, 0, 0, glyph_count, 1, glyph_count + 16].pack("nnnnnnNN")
200
+ header = [VERSION_MAJOR, VERSION_MINOR, 0, 0, 0, glyph_count, 1,
201
+ glyph_count + 16].pack("nnnnnnNN")
199
202
  offsets = Array.new(glyph_count + 1, 0).pack("C*")
200
203
  header + offsets
201
204
  end
@@ -19,7 +19,8 @@ module Fontisan
19
19
  # @param glyphs [Array<Fontisan::Ufo::Glyph>] (unused here;
20
20
  # bbox is computed by the caller for performance)
21
21
  # @return [Fontisan::Tables::Head]
22
- def self.build(font, glyphs:, units_per_em: nil, loca_format: LOCA_FORMAT_LONG)
22
+ def self.build(font, glyphs:, units_per_em: nil,
23
+ loca_format: LOCA_FORMAT_LONG)
23
24
  bbox = font_bbox(font, glyphs)
24
25
  Fontisan::Tables::Head.new(
25
26
  version_raw: 0x00010000,
@@ -62,7 +62,8 @@ module Fontisan
62
62
  io << [regions.size].pack("n") # regionCount
63
63
  regions.each do |region|
64
64
  region.each do |coords|
65
- io << [f2dot14(coords[:start]), f2dot14(coords[:peak]), f2dot14(coords[:end])].pack("nnn")
65
+ io << [f2dot14(coords[:start]), f2dot14(coords[:peak]),
66
+ f2dot14(coords[:end])].pack("nnn")
66
67
  end
67
68
  end
68
69
  io
@@ -51,7 +51,9 @@ module Fontisan
51
51
 
52
52
  def self.format0_bytes(records)
53
53
  # Strings are stored UTF-16BE on disk (Windows Unicode).
54
- encoded = records.map { |r| r[:value].encode("UTF-16BE").force_encoding("BINARY") }
54
+ encoded = records.map do |r|
55
+ r[:value].encode("UTF-16BE").force_encoding("BINARY")
56
+ end
55
57
  storage = encoded.join
56
58
  storage_offset = 6 + (records.size * 12)
57
59
 
@@ -75,7 +75,8 @@ module Fontisan
75
75
  if glyph && glyph[:data]
76
76
  offsets << current_offset
77
77
  data = glyph[:data].b
78
- glyph_data << [glyph[:origin_x] || 0, glyph[:origin_y] || 0].pack("nn")
78
+ glyph_data << [glyph[:origin_x] || 0,
79
+ glyph[:origin_y] || 0].pack("nn")
79
80
  glyph_data << (glyph[:graphic_type] || PNG_TYPE).ljust(4)[0, 4]
80
81
  glyph_data << data
81
82
  current_offset += 8 + data.bytesize
@@ -26,7 +26,8 @@ module Fontisan
26
26
  axis_value_count = axis_value_list.size
27
27
 
28
28
  design_axes = serialize_design_axes(axes)
29
- value_tables, value_offsets = serialize_axis_values(axis_value_list, design_axes.bytesize)
29
+ value_tables, value_offsets = serialize_axis_values(axis_value_list,
30
+ design_axes.bytesize)
30
31
 
31
32
  header = serialize_header(
32
33
  design_axis_count: design_axis_count,
@@ -45,7 +46,8 @@ module Fontisan
45
46
  io
46
47
  end
47
48
 
48
- def self.serialize_header(design_axis_count:, design_axes_size:, axis_value_count:, elided_name_id:)
49
+ def self.serialize_header(design_axis_count:, design_axes_size:,
50
+ axis_value_count:, elided_name_id:)
49
51
  design_axes_offset = HEADER_SIZE
50
52
  offset_to_axis_value_offsets = design_axes_offset + design_axes_size
51
53
 
@@ -86,7 +88,8 @@ module Fontisan
86
88
  flags = av[:flags] || av["flags"] || 0
87
89
  name_id = av[:name_id] || av["name_id"] || 0
88
90
  value = f2dot14(av[:value] || av["value"] || 0)
89
- value_tables << [format, axis_idx, flags, name_id, value].pack("nnnnn")
91
+ value_tables << [format, axis_idx, flags, name_id,
92
+ value].pack("nnnnn")
90
93
  end
91
94
 
92
95
  [value_tables, value_offsets]
@@ -82,7 +82,8 @@ module Fontisan
82
82
 
83
83
  if raw && raw.encoding == Encoding::UTF_16BE
84
84
  raw.encode("UTF-8")
85
- elsif raw && raw.bytesize >= 2 && raw.getbyte(0).between?(0, 127) && raw.getbyte(1).zero?
85
+ elsif raw && raw.bytesize >= 2 && raw.getbyte(0).between?(0,
86
+ 127) && raw.getbyte(1).zero?
86
87
  # Looks like UTF-16BE
87
88
  raw.force_encoding("UTF-16BE").encode("UTF-8")
88
89
  else
@@ -165,7 +166,10 @@ module Fontisan
165
166
  return unless glyf && loca && head
166
167
 
167
168
  # Tables need context-aware initialization before per-glyph access.
168
- loca.parse_with_context(head.index_to_loc_format, num_glyphs) if loca.respond_to?(:parse_with_context)
169
+ if loca.respond_to?(:parse_with_context)
170
+ loca.parse_with_context(head.index_to_loc_format,
171
+ num_glyphs)
172
+ end
169
173
 
170
174
  num_glyphs.times do |gid|
171
175
  glyph_name = glyph_name_for(font, gid) || "glyph#{gid}"
@@ -17,10 +17,14 @@ module Fontisan
17
17
  # @return [String] the output_path
18
18
  def self.convert(ufo, output_path:, compiler: :ttf, **_opts)
19
19
  compiler_class = Convert::COMPILER_FOR_FORMAT[compiler.to_sym]
20
- raise ArgumentError, "unknown intermediate compiler: #{compiler.inspect}" unless compiler_class
20
+ unless compiler_class
21
+ raise ArgumentError,
22
+ "unknown intermediate compiler: #{compiler.inspect}"
23
+ end
21
24
 
22
25
  Dir.mktmpdir do |dir|
23
- intermediate_path = File.join(dir, "intermediate#{ext_for(compiler)}")
26
+ intermediate_path = File.join(dir,
27
+ "intermediate#{ext_for(compiler)}")
24
28
  compiler_class.new(ufo).compile(output_path: intermediate_path)
25
29
 
26
30
  loaded = Fontisan::FontLoader.load(intermediate_path)
@@ -18,7 +18,10 @@ module Fontisan
18
18
  # @return [String] the output_path
19
19
  def self.convert(ufo, output_path:, compiler: :otf, **_opts)
20
20
  compiler_class = Convert::COMPILER_FOR_FORMAT[compiler.to_sym]
21
- raise ArgumentError, "unknown intermediate compiler: #{compiler.inspect}" unless compiler_class
21
+ unless compiler_class
22
+ raise ArgumentError,
23
+ "unknown intermediate compiler: #{compiler.inspect}"
24
+ end
22
25
 
23
26
  Dir.mktmpdir do |dir|
24
27
  intermediate_path = File.join(dir, "intermediate.otf")