Class: Fontisan::Tables::Cff2::TableReader

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/tables/cff2/table_reader.rb

Overview

Table reader for CFF2 with Variable Store support

CFF2TableReader parses CFF2 tables and extracts variation data from the Variable Store, which is essential for applying hints to variable fonts with CFF2 outlines.

Variable Store Structure:

  • RegionList: Defines variation regions (min/peak/max per axis)
  • ItemVariationData: Contains delta arrays per region

Reference: Adobe Technical Note #5177 (CFF2) Reference: OpenType spec - Item Variation Store

Examples:

Reading CFF2 with Variable Store

reader = CFF2TableReader.new(cff2_data)
store = reader.read_variable_store
regions = store[:regions]
deltas = store[:deltas]

Constant Summary collapse

VSTORE_OPERATOR =

CFF2-specific operators

24

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ TableReader

Initialize reader with CFF2 data

Parameters:

  • data (String)

    Binary CFF2 table data



45
46
47
48
49
50
51
52
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 45

def initialize(data)
  @data = data
  @io = StringIO.new(data)
  @io.set_encoding(Encoding::BINARY)
  @header = nil
  @top_dict = nil
  @variable_store = nil
end

Instance Attribute Details

#dataString (readonly)

Returns Binary CFF2 data.

Returns:

  • (String)

    Binary CFF2 data



28
29
30
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 28

def data
  @data
end

#headerHash (readonly)

Returns CFF2 header information.

Returns:

  • (Hash)

    CFF2 header information



31
32
33
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 31

def header
  @header
end

#top_dictHash (readonly)

Returns Top DICT data.

Returns:

  • (Hash)

    Top DICT data



34
35
36
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 34

def top_dict
  @top_dict
end

#variable_storeHash? (readonly)

Returns Variable Store data.

Returns:

  • (Hash, nil)

    Variable Store data



37
38
39
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 37

def variable_store
  @variable_store
end

Instance Method Details

#read_charstrings(offset) ⇒ Cff::Index

Read CharStrings INDEX

Parameters:

  • offset (Integer)

    CharStrings offset from Top DICT

Returns:



233
234
235
236
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 233

def read_charstrings(offset)
  @io.seek(offset)
  Cff::Index.new(@io, start_offset: offset)
end

#read_headerHash

Read CFF2 header

Returns:

  • (Hash)

    Header information



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 57

def read_header
  @io.rewind
  @header = {
    major_version: read_uint8,
    minor_version: read_uint8,
    header_size: read_uint8,
    top_dict_length: read_uint16,
  }

  # Validate CFF2 version
  unless @header[:major_version] == 2 && @header[:minor_version].zero?
    raise CorruptedTableError,
          "Invalid CFF2 version: #{@header[:major_version]}.#{@header[:minor_version]}"
  end

  @header
end

#read_item_variation_dataArray<Hash>

Read Item Variation Data

Contains delta arrays per region for varying values

Returns:

  • (Array<Hash>)

    Array of item variation data



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 154

def read_item_variation_data
  data_count = read_uint16
  return [] if data_count.zero?

  item_variation_data = []

  data_count.times do |_idx|
    item_data = read_single_item_variation_data
    item_variation_data << item_data
  rescue EOFError
    # break
  end

  item_variation_data
end

#read_private_dict(size, offset) ⇒ Hash

Read Private DICT with blend support

Private DICT in CFF2 can contain blend operators for variable hint parameters.

Parameters:

  • size (Integer)

    Private DICT size

  • offset (Integer)

    Private DICT offset

Returns:

  • (Hash)

    Private DICT data



223
224
225
226
227
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 223

def read_private_dict(size, offset)
  @io.seek(offset)
  private_dict_data = @io.read(size)
  parse_dict(private_dict_data)
end

#read_regionHash

Read a single region

Returns:

  • (Hash)

    Region with axis coordinates



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 134

def read_region
  axis_count = read_uint16
  axes = []

  axis_count.times do
    axes << {
      start_coord: read_f2dot14,
      peak_coord: read_f2dot14,
      end_coord: read_f2dot14,
    }
  end

  { axis_count: axis_count, axes: axes }
end

#read_region_listArray<Hash>

Read Region List from Variable Store

Region List defines variation regions, where each region specifies min/peak/max values per axis.

Returns:

  • (Array<Hash>)

    Array of region definitions



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 119

def read_region_list
  region_count = read_uint16
  regions = []

  region_count.times do
    region = read_region
    regions << region
  end

  regions
end

#read_single_item_variation_dataHash

Read a single Item Variation Data entry

Returns:

  • (Hash)

    Item variation data with region indices and deltas



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 173

def read_single_item_variation_data
  item_count = read_uint16
  short_delta_count = read_uint16
  region_index_count = read_uint16

  # Read region indices
  region_indices = []
  region_index_count.times do
    region_indices << read_uint16
  end

  # Read delta sets
  delta_sets = []
  item_count.times do |_item_idx|
    deltas = []

    # Short deltas (16-bit)
    short_delta_count.times do
      break if @io.eof?

      deltas << read_int16
    end

    # Long deltas (8-bit) for remaining regions
    (region_index_count - short_delta_count).times do
      break if @io.eof?

      deltas << read_int8
    end

    delta_sets << deltas
  rescue EOFError
    # break
  end

  {
    item_count: item_count,
    region_indices: region_indices,
    delta_sets: delta_sets,
  }
end

#read_top_dictHash

Read Top DICT

Returns:

  • (Hash)

    Top DICT operators and values



78
79
80
81
82
83
84
85
86
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 78

def read_top_dict
  read_header unless @header

  # Seek to Top DICT (after header)
  @io.seek(@header[:header_size])

  top_dict_data = @io.read(@header[:top_dict_length])
  @top_dict = parse_dict(top_dict_data)
end

#read_variable_storeHash?

Read Variable Store from Top DICT

The Variable Store is referenced by the vstore operator (24) in the Top DICT. It contains regions and deltas for variation.

Returns:

  • (Hash, nil)

    Variable Store data with :regions and :deltas



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 94

def read_variable_store
  read_top_dict unless @top_dict

  # Check if Variable Store is present (operator 24)
  vstore_offset = @top_dict[VSTORE_OPERATOR]
  return nil unless vstore_offset

  # Seek to Variable Store
  @io.seek(vstore_offset)

  # Parse Variable Store structure
  @variable_store = {
    regions: read_region_list,
    item_variation_data: read_item_variation_data,
  }

  @variable_store
end