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



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

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



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

def data
  @data
end

#headerHash (readonly)

Returns CFF2 header information.

Returns:

  • (Hash)

    CFF2 header information



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

def header
  @header
end

#top_dictHash (readonly)

Returns Top DICT data.

Returns:

  • (Hash)

    Top DICT data



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

def top_dict
  @top_dict
end

#variable_storeHash? (readonly)

Returns Variable Store data.

Returns:

  • (Hash, nil)

    Variable Store data



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

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:



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

def read_charstrings(offset)
  @io.seek(offset)
  require_relative "../cff/index"
  Cff::Index.new(@io, start_offset: offset)
end

#read_headerHash

Read CFF2 header

Returns:

  • (Hash)

    Header information



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

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



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

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



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

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



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

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



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

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



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
214
# File 'lib/fontisan/tables/cff2/table_reader.rb', line 174

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



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

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



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

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