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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +240 -24
- data/Gemfile +6 -0
- data/Rakefile +9 -6
- data/benchmark/compile_benchmark.rb +13 -5
- data/docs/.vitepress/config.ts +1 -0
- data/docs/STITCHER_GUIDE.adoc +47 -3
- data/docs/api/layer.md +89 -0
- data/docs/cli/subset.md +25 -6
- data/docs/guide/cli/subset.md +31 -0
- data/docs/guide/color-fonts/bitmaps.md +17 -0
- data/lib/fontisan/cli.rb +2 -1
- data/lib/fontisan/collection/writer.rb +2 -1
- data/lib/fontisan/commands/validate_collection_command.rb +7 -1
- data/lib/fontisan/commands.rb +2 -1
- data/lib/fontisan/config/subset_profiles.yml +2 -0
- data/lib/fontisan/converters/collection_converter.rb +6 -1
- data/lib/fontisan/converters/svg_generator.rb +3 -1
- data/lib/fontisan/stitcher/cbdt_propagator.rb +44 -22
- data/lib/fontisan/stitcher/collection_result.rb +2 -1
- data/lib/fontisan/stitcher/glyph_cloner.rb +41 -0
- data/lib/fontisan/stitcher/glyph_copier.rb +5 -34
- data/lib/fontisan/stitcher/partition_strategy/by_block.rb +3 -1
- data/lib/fontisan/stitcher/partition_strategy/by_script.rb +6 -2
- data/lib/fontisan/stitcher/source.rb +2 -1
- data/lib/fontisan/stitcher/unique_glyph_name.rb +46 -0
- data/lib/fontisan/stitcher.rb +4 -1
- data/lib/fontisan/subset/shared_state.rb +42 -0
- data/lib/fontisan/subset/subset_context.rb +20 -0
- data/lib/fontisan/subset/table_strategy/cbdt.rb +30 -0
- data/lib/fontisan/subset/table_strategy/cblc.rb +27 -0
- data/lib/fontisan/subset/table_strategy/cmap.rb +146 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_placement.rb +20 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_strike_plan.rb +45 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_subset_plan.rb +62 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_subsetter.rb +182 -0
- data/lib/fontisan/subset/table_strategy/color_bitmap_subtable_plan.rb +59 -0
- data/lib/fontisan/subset/table_strategy/glyf.rb +29 -0
- data/lib/fontisan/subset/table_strategy/glyf_loca_builder.rb +146 -0
- data/lib/fontisan/subset/table_strategy/head.rb +42 -0
- data/lib/fontisan/subset/table_strategy/hhea.rb +67 -0
- data/lib/fontisan/subset/table_strategy/hmtx.rb +52 -0
- data/lib/fontisan/subset/table_strategy/loca.rb +41 -0
- data/lib/fontisan/subset/table_strategy/maxp.rb +23 -0
- data/lib/fontisan/subset/table_strategy/name.rb +19 -0
- data/lib/fontisan/subset/table_strategy/os2.rb +21 -0
- data/lib/fontisan/subset/table_strategy/pass_through.rb +20 -0
- data/lib/fontisan/subset/table_strategy/post.rb +37 -0
- data/lib/fontisan/subset/table_strategy.rb +75 -0
- data/lib/fontisan/subset/table_subsetter.rb +49 -616
- data/lib/fontisan/subset.rb +3 -0
- data/lib/fontisan/svg/standalone_glyph.rb +4 -1
- data/lib/fontisan/svg_to_glyf/assembler.rb +6 -3
- data/lib/fontisan/svg_to_glyf/geometry/transform_parser.rb +11 -4
- data/lib/fontisan/svg_to_glyf/geometry.rb +4 -2
- data/lib/fontisan/tables/cbdt.rb +66 -121
- data/lib/fontisan/tables/cblc.rb +110 -231
- data/lib/fontisan/tables/cblc_big_glyph_metrics.rb +28 -0
- data/lib/fontisan/tables/cblc_bitmap_size.rb +67 -0
- data/lib/fontisan/tables/cblc_glyph_bitmap_location.rb +28 -0
- data/lib/fontisan/tables/cblc_index_subtable.rb +104 -0
- data/lib/fontisan/tables/cblc_index_subtable_array_entry.rb +20 -0
- data/lib/fontisan/tables/cblc_index_subtable_format_parser.rb +150 -0
- data/lib/fontisan/tables/cblc_index_subtable_header.rb +18 -0
- data/lib/fontisan/tables/cblc_sbit_line_metrics.rb +28 -0
- data/lib/fontisan/tables/cff/cff2_charstring_builder.rb +11 -5
- data/lib/fontisan/tables/cff.rb +2 -1
- data/lib/fontisan/tables/cff2/index_builder.rb +2 -1
- data/lib/fontisan/tables.rb +12 -0
- data/lib/fontisan/ufo/cli.rb +4 -1
- data/lib/fontisan/ufo/compile/cbdt_cblc.rb +2 -1
- data/lib/fontisan/ufo/compile/cff.rb +6 -2
- data/lib/fontisan/ufo/compile/cmap.rb +11 -5
- data/lib/fontisan/ufo/compile/cpal.rb +2 -1
- data/lib/fontisan/ufo/compile/feature_writers/gdef.rb +3 -1
- data/lib/fontisan/ufo/compile/feature_writers/mark_family_base.rb +3 -1
- data/lib/fontisan/ufo/compile/feature_writers.rb +2 -1
- data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +4 -2
- data/lib/fontisan/ufo/compile/filters/remove_overlaps.rb +2 -1
- data/lib/fontisan/ufo/compile/fvar.rb +7 -3
- data/lib/fontisan/ufo/compile/glyf_loca.rb +4 -2
- data/lib/fontisan/ufo/compile/gvar.rb +5 -2
- data/lib/fontisan/ufo/compile/head.rb +2 -1
- data/lib/fontisan/ufo/compile/item_variation_store.rb +2 -1
- data/lib/fontisan/ufo/compile/name.rb +3 -1
- data/lib/fontisan/ufo/compile/sbix.rb +2 -1
- data/lib/fontisan/ufo/compile/stat.rb +6 -3
- data/lib/fontisan/ufo/convert/from_bin_data.rb +6 -2
- data/lib/fontisan/ufo/convert/to_dfont.rb +6 -2
- data/lib/fontisan/ufo/convert/to_otc.rb +4 -1
- data/lib/fontisan/ufo/convert/to_postscript.rb +8 -3
- data/lib/fontisan/ufo/convert/to_woff.rb +8 -3
- data/lib/fontisan/ufo/convert/to_woff2.rb +8 -3
- data/lib/fontisan/ufo/convert.rb +4 -1
- data/lib/fontisan/ufo/glyph_exists_error.rb +23 -0
- data/lib/fontisan/ufo/info.rb +4 -2
- data/lib/fontisan/ufo/layer.rb +42 -2
- data/lib/fontisan/ufo/plist.rb +7 -2
- data/lib/fontisan/ufo/reader.rb +3 -1
- data/lib/fontisan/ufo/writer.rb +13 -5
- data/lib/fontisan/ufo.rb +2 -1
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/collection_decoder.rb +4 -1
- data/lib/fontisan/woff2/collection_encoder.rb +4 -1
- data/lib/fontisan/woff2/glyf_canonicalizer.rb +2 -1
- data/lib/fontisan/woff2/glyf_loca_reconstruct.rb +5 -2
- data/lib/fontisan/woff2/sfnt_checksum.rb +2 -1
- data/lib/fontisan.rb +0 -1
- metadata +36 -4
- data/lib/fontisan/tasks/fixture_downloader.rb +0 -162
- 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:,
|
|
44
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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)
|
data/lib/fontisan/tables/cff.rb
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
data/lib/fontisan/tables.rb
CHANGED
|
@@ -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"
|
data/lib/fontisan/ufo/cli.rb
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
48
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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?
|
|
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
|
|
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,
|
|
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,
|
|
138
|
-
|
|
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],
|
|
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,
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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?
|
|
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,
|
|
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,
|
|
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]),
|
|
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
|
|
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,
|
|
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,
|
|
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:,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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")
|