fontisan 0.4.22 → 0.4.23
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 +5 -0
- data/lib/fontisan/config/subset_profiles.yml +2 -0
- data/lib/fontisan/stitcher/cbdt_propagator.rb +44 -22
- data/lib/fontisan/stitcher/glyph_cloner.rb +41 -0
- data/lib/fontisan/stitcher/glyph_copier.rb +3 -33
- data/lib/fontisan/stitcher/unique_glyph_name.rb +46 -0
- data/lib/fontisan/stitcher.rb +2 -0
- 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 +145 -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/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.rb +11 -0
- data/lib/fontisan/ufo/glyph_exists_error.rb +23 -0
- data/lib/fontisan/ufo/layer.rb +42 -2
- data/lib/fontisan/ufo.rb +2 -1
- data/lib/fontisan/version.rb +1 -1
- metadata +35 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# 8-byte bigGlyphMetrics record embedded in CBLC IndexSubTable formats
|
|
6
|
+
# 2 and 5 (constant-metrics strikes). Layout is smallGlyphMetrics (5
|
|
7
|
+
# bytes) plus four additional signed advance fields.
|
|
8
|
+
#
|
|
9
|
+
# uint8 height
|
|
10
|
+
# uint8 width
|
|
11
|
+
# int8 bearing_x
|
|
12
|
+
# int8 bearing_y
|
|
13
|
+
# int8 advance
|
|
14
|
+
# int8 reserved
|
|
15
|
+
# int8 advance_lsb
|
|
16
|
+
# int8 advance_rsb
|
|
17
|
+
class CblcBigGlyphMetrics < Binary::BaseRecord
|
|
18
|
+
uint8 :height
|
|
19
|
+
uint8 :width
|
|
20
|
+
int8 :bearing_x
|
|
21
|
+
int8 :bearing_y
|
|
22
|
+
int8 :advance
|
|
23
|
+
int8 :reserved
|
|
24
|
+
int8 :advance_lsb
|
|
25
|
+
int8 :advance_rsb
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Tables
|
|
7
|
+
# BitmapSize record embedded in a CBLC table (48 bytes).
|
|
8
|
+
#
|
|
9
|
+
# Each BitmapSize describes one bitmap strike: a glyph range rendered
|
|
10
|
+
# at a specific ppem with a specific bit depth. It points (via
|
|
11
|
+
# `index_subtable_array_offset`) at a sequence of IndexSubTableArray
|
|
12
|
+
# records that further describe how to find the bitmap data for each
|
|
13
|
+
# glyph in CBDT.
|
|
14
|
+
#
|
|
15
|
+
# Layout (48 bytes):
|
|
16
|
+
# uint32 index_subtable_array_offset
|
|
17
|
+
# uint32 index_tables_size
|
|
18
|
+
# uint32 number_of_index_subtables
|
|
19
|
+
# uint32 color_ref
|
|
20
|
+
# CblcSbitLineMetrics hori (12 bytes)
|
|
21
|
+
# CblcSbitLineMetrics vert (12 bytes)
|
|
22
|
+
# uint16 start_glyph_index
|
|
23
|
+
# uint16 end_glyph_index
|
|
24
|
+
# uint8 ppem_x
|
|
25
|
+
# uint8 ppem_y
|
|
26
|
+
# uint8 bit_depth
|
|
27
|
+
# int8 flags
|
|
28
|
+
#
|
|
29
|
+
# Reference: OpenType CBLC spec, "BitmapSize" record.
|
|
30
|
+
class CblcBitmapSize < Binary::BaseRecord
|
|
31
|
+
uint32 :index_subtable_array_offset
|
|
32
|
+
uint32 :index_tables_size
|
|
33
|
+
uint32 :number_of_index_subtables
|
|
34
|
+
uint32 :color_ref
|
|
35
|
+
CblcSbitLineMetrics :hori, type: Fontisan::Tables::CblcSbitLineMetrics
|
|
36
|
+
CblcSbitLineMetrics :vert, type: Fontisan::Tables::CblcSbitLineMetrics
|
|
37
|
+
uint16 :start_glyph_index
|
|
38
|
+
uint16 :end_glyph_index
|
|
39
|
+
uint8 :ppem_x
|
|
40
|
+
uint8 :ppem_y
|
|
41
|
+
uint8 :bit_depth
|
|
42
|
+
int8 :flags
|
|
43
|
+
|
|
44
|
+
# Pixels per em, assuming square pixels (the common case).
|
|
45
|
+
#
|
|
46
|
+
# @return [Integer] ppem value
|
|
47
|
+
def ppem
|
|
48
|
+
ppem_x
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Glyph ID range covered by this strike.
|
|
52
|
+
#
|
|
53
|
+
# @return [Range<Integer>] inclusive glyph ID range
|
|
54
|
+
def glyph_range
|
|
55
|
+
start_glyph_index..end_glyph_index
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Whether the strike covers a specific glyph.
|
|
59
|
+
#
|
|
60
|
+
# @param glyph_id [Integer] source glyph ID
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def includes_glyph?(glyph_id)
|
|
63
|
+
glyph_range.include?(glyph_id)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Immutable value object locating one glyph's bitmap inside a CBDT table.
|
|
6
|
+
#
|
|
7
|
+
# A CblcIndexSubTable produces one of these per glyph in its range.
|
|
8
|
+
# The subsetting pipeline reads them to know which CBDT byte range to
|
|
9
|
+
# retain, then computes fresh offsets for the subset output.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute glyph_id
|
|
12
|
+
# @return [Integer] source glyph ID
|
|
13
|
+
# @!attribute image_format
|
|
14
|
+
# @return [Integer] CBDT image format (e.g. 17, 18, 19)
|
|
15
|
+
# @!attribute cbdt_offset
|
|
16
|
+
# @return [Integer] absolute byte offset within the source CBDT
|
|
17
|
+
# @!attribute byte_length
|
|
18
|
+
# @return [Integer] length of the bitmap block in CBDT
|
|
19
|
+
CblcGlyphBitmapLocation = Struct.new(:glyph_id, :image_format,
|
|
20
|
+
:cbdt_offset, :byte_length,
|
|
21
|
+
keyword_init: true) do
|
|
22
|
+
def initialize(*)
|
|
23
|
+
super
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Parsed IndexSubTable from a CBLC table.
|
|
6
|
+
#
|
|
7
|
+
# The IndexSubTable is the format-specific directory describing how to
|
|
8
|
+
# locate each glyph's bitmap inside CBDT. Five formats are defined by
|
|
9
|
+
# the OpenType spec:
|
|
10
|
+
#
|
|
11
|
+
# 1 — variable metrics, variable size (uint32 offset array)
|
|
12
|
+
# 2 — constant metrics, constant size (single imageSize for the range)
|
|
13
|
+
# 3 — variable metrics, variable size, 4-byte-aligned (uint16 offsets × 4)
|
|
14
|
+
# 4 — variable metrics, variable size, bit-packed offsets (rare)
|
|
15
|
+
# 5 — constant metrics, constant size, with explicit uint32 offsetArray
|
|
16
|
+
#
|
|
17
|
+
# Each parsed IndexSubTable exposes one [CblcGlyphBitmapLocation] per
|
|
18
|
+
# glyph in its range, plus the raw bytes so a subsetter can preserve
|
|
19
|
+
# unchanged subtables verbatim if needed.
|
|
20
|
+
#
|
|
21
|
+
# Format-specific parsing is delegated to
|
|
22
|
+
# [CblcIndexSubTableFormatParser]; this class only owns the data model.
|
|
23
|
+
#
|
|
24
|
+
# Reference: OpenType CBLC spec, IndexSubTable formats 1–5.
|
|
25
|
+
class CblcIndexSubTable
|
|
26
|
+
# @return [Integer] indexFormat field (1, 2, 3, or 5)
|
|
27
|
+
attr_reader :index_format
|
|
28
|
+
|
|
29
|
+
# @return [Integer] imageFormat field (e.g. 17 = small metrics + PNG)
|
|
30
|
+
attr_reader :image_format
|
|
31
|
+
|
|
32
|
+
# @return [Integer] absolute imageDataOffset from start of CBDT
|
|
33
|
+
attr_reader :image_data_offset
|
|
34
|
+
|
|
35
|
+
# @return [Integer] first glyph ID covered by this subtable
|
|
36
|
+
attr_reader :first_glyph_index
|
|
37
|
+
|
|
38
|
+
# @return [Integer] last glyph ID covered by this subtable
|
|
39
|
+
attr_reader :last_glyph_index
|
|
40
|
+
|
|
41
|
+
# @return [Array<CblcGlyphBitmapLocation>] one per glyph in range
|
|
42
|
+
attr_reader :locations
|
|
43
|
+
|
|
44
|
+
# @return [String] raw bytes of this IndexSubTable in the source CBLC
|
|
45
|
+
attr_reader :raw_bytes
|
|
46
|
+
|
|
47
|
+
def initialize(index_format:, image_format:, image_data_offset:,
|
|
48
|
+
first_glyph_index:, last_glyph_index:, locations:,
|
|
49
|
+
raw_bytes:)
|
|
50
|
+
@index_format = index_format
|
|
51
|
+
@image_format = image_format
|
|
52
|
+
@image_data_offset = image_data_offset
|
|
53
|
+
@first_glyph_index = first_glyph_index
|
|
54
|
+
@last_glyph_index = last_glyph_index
|
|
55
|
+
@locations = locations
|
|
56
|
+
@raw_bytes = raw_bytes
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Parse an IndexSubTable from a CBLC binary blob.
|
|
60
|
+
#
|
|
61
|
+
# @param cblc_bytes [String] the full CBLC table bytes
|
|
62
|
+
# @param index_subtable_offset [Integer] absolute byte offset within
|
|
63
|
+
# `cblc_bytes` where this IndexSubTable begins
|
|
64
|
+
# @param first_glyph_index [Integer] first glyph ID covered
|
|
65
|
+
# @param last_glyph_index [Integer] last glyph ID covered
|
|
66
|
+
# @return [CblcIndexSubTable]
|
|
67
|
+
def self.parse(cblc_bytes, index_subtable_offset:, first_glyph_index:,
|
|
68
|
+
last_glyph_index:)
|
|
69
|
+
header = CblcIndexSubTableHeader.read(
|
|
70
|
+
cblc_bytes[index_subtable_offset, 8],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
new(
|
|
74
|
+
index_format: header.index_format,
|
|
75
|
+
image_format: header.image_format,
|
|
76
|
+
image_data_offset: header.image_data_offset,
|
|
77
|
+
first_glyph_index: first_glyph_index,
|
|
78
|
+
last_glyph_index: last_glyph_index,
|
|
79
|
+
locations: CblcIndexSubTableFormatParser.locations(
|
|
80
|
+
header: header,
|
|
81
|
+
bytes: cblc_bytes,
|
|
82
|
+
base: index_subtable_offset,
|
|
83
|
+
first: first_glyph_index,
|
|
84
|
+
last: last_glyph_index,
|
|
85
|
+
),
|
|
86
|
+
raw_bytes: CblcIndexSubTableFormatParser.raw_bytes(
|
|
87
|
+
header: header,
|
|
88
|
+
bytes: cblc_bytes,
|
|
89
|
+
base: index_subtable_offset,
|
|
90
|
+
first: first_glyph_index,
|
|
91
|
+
last: last_glyph_index,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Number of glyphs covered by this subtable.
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer]
|
|
99
|
+
def glyph_count
|
|
100
|
+
last_glyph_index - first_glyph_index + 1
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Tables
|
|
5
|
+
# Entry in a CBLC IndexSubTableArray (8 bytes).
|
|
6
|
+
#
|
|
7
|
+
# The IndexSubTableArray sits at `BitmapSize#index_subtable_array_offset`
|
|
8
|
+
# (relative to the start of CBLC) and contains `number_of_index_subtables`
|
|
9
|
+
# of these entries. Each entry points (via `additional_offset_to_index_subtable`,
|
|
10
|
+
# relative to the IndexSubTableArray's own location) at an IndexSubTable
|
|
11
|
+
# record describing how to find the bitmaps for a glyph range.
|
|
12
|
+
#
|
|
13
|
+
# Reference: OpenType CBLC spec, IndexSubTableArray record.
|
|
14
|
+
class CblcIndexSubTableArrayEntry < Binary::BaseRecord
|
|
15
|
+
uint16 :first_glyph_index
|
|
16
|
+
uint16 :last_glyph_index
|
|
17
|
+
uint32 :additional_offset_to_index_subtable
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -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
|
data/lib/fontisan/tables.rb
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
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, "fontisan/tables/cblc_glyph_bitmap_location"
|
|
10
|
+
autoload :CblcIndexSubTable, "fontisan/tables/cblc_index_subtable"
|
|
11
|
+
autoload :CblcIndexSubTableArrayEntry,
|
|
12
|
+
"fontisan/tables/cblc_index_subtable_array_entry"
|
|
13
|
+
autoload :CblcIndexSubTableFormatParser,
|
|
14
|
+
"fontisan/tables/cblc_index_subtable_format_parser"
|
|
15
|
+
autoload :CblcIndexSubTableHeader,
|
|
16
|
+
"fontisan/tables/cblc_index_subtable_header"
|
|
17
|
+
autoload :CblcSbitLineMetrics, "fontisan/tables/cblc_sbit_line_metrics"
|
|
7
18
|
autoload :Cbdt, "fontisan/tables/cbdt"
|
|
8
19
|
autoload :Cblc, "fontisan/tables/cblc"
|
|
9
20
|
autoload :Cff, "fontisan/tables/cff"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ufo
|
|
5
|
+
# Raised by +Layer#add+ when a glyph with the same name is already
|
|
6
|
+
# present. Surfaces what would otherwise be a silent overwrite so
|
|
7
|
+
# the caller can decide between +Layer#put+ (intentional replace)
|
|
8
|
+
# and +Stitcher::UniqueGlyphName.in+ (auto-rename).
|
|
9
|
+
#
|
|
10
|
+
# Extracted as a sibling of +Layer+ (rather than nested inside it)
|
|
11
|
+
# so the namespace stays flat and the error can be referenced
|
|
12
|
+
# without pulling in the full Layer implementation.
|
|
13
|
+
class GlyphExistsError < StandardError
|
|
14
|
+
attr_reader :name
|
|
15
|
+
|
|
16
|
+
def initialize(name)
|
|
17
|
+
@name = name
|
|
18
|
+
super("a glyph named #{name.inspect} already exists; use #put " \
|
|
19
|
+
"to overwrite or UniqueGlyphName.in to deconflict")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/fontisan/ufo/layer.rb
CHANGED
|
@@ -5,8 +5,27 @@ module Fontisan
|
|
|
5
5
|
# A single layer in a UFO source. A Layer holds a set of glyphs
|
|
6
6
|
# keyed by name. The default layer is `public.default` per UFO 3.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# == Naming contract
|
|
9
|
+
#
|
|
10
|
+
# Glyph names are the layer's primary key. Two distinct glyphs with
|
|
11
|
+
# the same name cannot coexist — adding the second would silently
|
|
12
|
+
# destroy the first, a class of bug that has historically cost real
|
|
13
|
+
# data (e.g. CJK Ext G cmap loss when CBDT placeholders collided
|
|
14
|
+
# with outline glyphs sharing "gid{N}" names).
|
|
15
|
+
#
|
|
16
|
+
# To make that contract unbreakable, +#add+ RAISES +GlyphExistsError+
|
|
17
|
+
# on conflict. Callers who want one of the other two semantics pick
|
|
18
|
+
# the method that names it:
|
|
19
|
+
#
|
|
20
|
+
# +#add+:: insert; raise if the name is taken (the safe default)
|
|
21
|
+
# +#put+:: insert; overwrite any existing glyph with the same name
|
|
22
|
+
#
|
|
23
|
+
# Callers that need auto-renaming (insertion without giving up on a
|
|
24
|
+
# collision) call +Stitcher::UniqueGlyphName.in(target, base)+ first,
|
|
25
|
+
# then +#add+ the glyph under the returned name.
|
|
26
|
+
#
|
|
27
|
+
# The error class itself lives at +Fontisan::Ufo::GlyphExistsError+
|
|
28
|
+
# (sibling, not nested) so the namespace stays flat.
|
|
10
29
|
class Layer
|
|
11
30
|
DEFAULT_NAME = "public.default"
|
|
12
31
|
|
|
@@ -21,7 +40,28 @@ module Fontisan
|
|
|
21
40
|
@glyphs[glyph_name.to_s]
|
|
22
41
|
end
|
|
23
42
|
|
|
43
|
+
# Insert +glyph+. Raises +GlyphExistsError+ if a glyph with the
|
|
44
|
+
# same name is already present — this is the contract that keeps
|
|
45
|
+
# the layer's key namespace trustworthy.
|
|
46
|
+
#
|
|
47
|
+
# @param glyph [Glyph]
|
|
48
|
+
# @return [Glyph] the same glyph
|
|
49
|
+
# @raise [GlyphExistsError] if +glyph.name+ is already taken
|
|
24
50
|
def add(glyph)
|
|
51
|
+
name = glyph.name.to_s
|
|
52
|
+
raise GlyphExistsError, name if @glyphs.key?(name)
|
|
53
|
+
|
|
54
|
+
@glyphs[name] = glyph
|
|
55
|
+
glyph
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Insert +glyph+, replacing any existing glyph with the same
|
|
59
|
+
# name. Use this when the caller has positively decided that the
|
|
60
|
+
# previous glyph (if any) should be discarded.
|
|
61
|
+
#
|
|
62
|
+
# @param glyph [Glyph]
|
|
63
|
+
# @return [Glyph] the same glyph
|
|
64
|
+
def put(glyph)
|
|
25
65
|
@glyphs[glyph.name.to_s] = glyph
|
|
26
66
|
glyph
|
|
27
67
|
end
|
data/lib/fontisan/ufo.rb
CHANGED
|
@@ -17,7 +17,8 @@ module Fontisan
|
|
|
17
17
|
#
|
|
18
18
|
# Reference: https://unifiedfontobject.org
|
|
19
19
|
module Ufo
|
|
20
|
-
autoload :Font,
|
|
20
|
+
autoload :Font, "fontisan/ufo/font"
|
|
21
|
+
autoload :GlyphExistsError, "fontisan/ufo/glyph_exists_error"
|
|
21
22
|
autoload :Info, "fontisan/ufo/info"
|
|
22
23
|
autoload :Layer, "fontisan/ufo/layer"
|
|
23
24
|
autoload :LayerSet, "fontisan/ufo/layer_set"
|
data/lib/fontisan/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.23
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -440,6 +440,7 @@ files:
|
|
|
440
440
|
- lib/fontisan/stitcher/collection_result.rb
|
|
441
441
|
- lib/fontisan/stitcher/deduplicator.rb
|
|
442
442
|
- lib/fontisan/stitcher/format_metadata.rb
|
|
443
|
+
- lib/fontisan/stitcher/glyph_cloner.rb
|
|
443
444
|
- lib/fontisan/stitcher/glyph_copier.rb
|
|
444
445
|
- lib/fontisan/stitcher/glyph_limit.rb
|
|
445
446
|
- lib/fontisan/stitcher/glyph_signature.rb
|
|
@@ -455,12 +456,35 @@ files:
|
|
|
455
456
|
- lib/fontisan/stitcher/selector/gid.rb
|
|
456
457
|
- lib/fontisan/stitcher/selector/range.rb
|
|
457
458
|
- lib/fontisan/stitcher/source.rb
|
|
459
|
+
- lib/fontisan/stitcher/unique_glyph_name.rb
|
|
458
460
|
- lib/fontisan/stitcher_cli.rb
|
|
459
461
|
- lib/fontisan/subset.rb
|
|
460
462
|
- lib/fontisan/subset/builder.rb
|
|
461
463
|
- lib/fontisan/subset/glyph_mapping.rb
|
|
462
464
|
- lib/fontisan/subset/options.rb
|
|
463
465
|
- lib/fontisan/subset/profile.rb
|
|
466
|
+
- lib/fontisan/subset/shared_state.rb
|
|
467
|
+
- lib/fontisan/subset/subset_context.rb
|
|
468
|
+
- lib/fontisan/subset/table_strategy.rb
|
|
469
|
+
- lib/fontisan/subset/table_strategy/cbdt.rb
|
|
470
|
+
- lib/fontisan/subset/table_strategy/cblc.rb
|
|
471
|
+
- lib/fontisan/subset/table_strategy/cmap.rb
|
|
472
|
+
- lib/fontisan/subset/table_strategy/color_bitmap_placement.rb
|
|
473
|
+
- lib/fontisan/subset/table_strategy/color_bitmap_strike_plan.rb
|
|
474
|
+
- lib/fontisan/subset/table_strategy/color_bitmap_subset_plan.rb
|
|
475
|
+
- lib/fontisan/subset/table_strategy/color_bitmap_subsetter.rb
|
|
476
|
+
- lib/fontisan/subset/table_strategy/color_bitmap_subtable_plan.rb
|
|
477
|
+
- lib/fontisan/subset/table_strategy/glyf.rb
|
|
478
|
+
- lib/fontisan/subset/table_strategy/glyf_loca_builder.rb
|
|
479
|
+
- lib/fontisan/subset/table_strategy/head.rb
|
|
480
|
+
- lib/fontisan/subset/table_strategy/hhea.rb
|
|
481
|
+
- lib/fontisan/subset/table_strategy/hmtx.rb
|
|
482
|
+
- lib/fontisan/subset/table_strategy/loca.rb
|
|
483
|
+
- lib/fontisan/subset/table_strategy/maxp.rb
|
|
484
|
+
- lib/fontisan/subset/table_strategy/name.rb
|
|
485
|
+
- lib/fontisan/subset/table_strategy/os2.rb
|
|
486
|
+
- lib/fontisan/subset/table_strategy/pass_through.rb
|
|
487
|
+
- lib/fontisan/subset/table_strategy/post.rb
|
|
464
488
|
- lib/fontisan/subset/table_subsetter.rb
|
|
465
489
|
- lib/fontisan/svg.rb
|
|
466
490
|
- lib/fontisan/svg/font_face_generator.rb
|
|
@@ -483,6 +507,14 @@ files:
|
|
|
483
507
|
- lib/fontisan/tables.rb
|
|
484
508
|
- lib/fontisan/tables/cbdt.rb
|
|
485
509
|
- lib/fontisan/tables/cblc.rb
|
|
510
|
+
- lib/fontisan/tables/cblc_big_glyph_metrics.rb
|
|
511
|
+
- lib/fontisan/tables/cblc_bitmap_size.rb
|
|
512
|
+
- lib/fontisan/tables/cblc_glyph_bitmap_location.rb
|
|
513
|
+
- lib/fontisan/tables/cblc_index_subtable.rb
|
|
514
|
+
- lib/fontisan/tables/cblc_index_subtable_array_entry.rb
|
|
515
|
+
- lib/fontisan/tables/cblc_index_subtable_format_parser.rb
|
|
516
|
+
- lib/fontisan/tables/cblc_index_subtable_header.rb
|
|
517
|
+
- lib/fontisan/tables/cblc_sbit_line_metrics.rb
|
|
486
518
|
- lib/fontisan/tables/cff.rb
|
|
487
519
|
- lib/fontisan/tables/cff/cff2_charstring_builder.rb
|
|
488
520
|
- lib/fontisan/tables/cff/cff_glyph.rb
|
|
@@ -657,6 +689,7 @@ files:
|
|
|
657
689
|
- lib/fontisan/ufo/features.rb
|
|
658
690
|
- lib/fontisan/ufo/font.rb
|
|
659
691
|
- lib/fontisan/ufo/glyph.rb
|
|
692
|
+
- lib/fontisan/ufo/glyph_exists_error.rb
|
|
660
693
|
- lib/fontisan/ufo/guideline.rb
|
|
661
694
|
- lib/fontisan/ufo/image.rb
|
|
662
695
|
- lib/fontisan/ufo/image_set.rb
|