Class: Fontisan::Tables::Loca

Inherits:
Binary::BaseRecord show all
Defined in:
lib/fontisan/tables/loca.rb

Overview

Parser for the 'loca' (Index to Location) table

The loca table provides offsets to glyph data in the glyf table. Each glyph has an entry in this table indicating where its data begins in the glyf table. An additional entry marks the end of the last glyph's data.

The table has two formats:

  • Short format (0): uint16 offsets divided by 2 (actual offset = value × 2)
  • Long format (1): uint32 offsets used as-is

The format is determined by head.indexToLocFormat:

  • 0 = short format (uint16, multiply by 2)
  • 1 = long format (uint32, use as-is)

The table always contains (numGlyphs + 1) offsets, where the last offset marks the end of the last glyph's data in the glyf table.

The table is context-dependent and requires:

  • indexToLocFormat from head table (format selection)
  • numGlyphs from maxp table (number of offsets to read)

Reference: OpenType specification, loca table https://docs.microsoft.com/en-us/typography/opentype/spec/loca

Examples:

Parsing loca with context

# Get required tables first
head = font.table('head')
maxp = font.table('maxp')

# Parse loca with context
data = font.read_table_data('loca')
loca = Fontisan::Tables::Loca.read(data)
loca.parse_with_context(head.index_to_loc_format, maxp.num_glyphs)

# Get offset for a glyph
offset = loca.offset_for(42)
size = loca.size_of(42)
is_empty = loca.empty?(42)

Constant Summary collapse

FORMAT_SHORT =

Short format constant (from head.indexToLocFormat)

0
FORMAT_LONG =

Long format constant (from head.indexToLocFormat)

1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Binary::BaseRecord

#valid?

Instance Attribute Details

#formatInteger (readonly)

Format of the loca table (0 = short, 1 = long)

Returns:

  • (Integer)

    Format indicator



60
61
62
# File 'lib/fontisan/tables/loca.rb', line 60

def format
  @format
end

#num_glyphsInteger (readonly)

Total number of glyphs from maxp table

Returns:

  • (Integer)

    Total glyph count



64
65
66
# File 'lib/fontisan/tables/loca.rb', line 64

def num_glyphs
  @num_glyphs
end

#offsetsArray<Integer> (readonly)

Parsed offsets array

Returns:

  • (Array<Integer>)

    Array of glyph offsets in glyf table



56
57
58
# File 'lib/fontisan/tables/loca.rb', line 56

def offsets
  @offsets
end

#raw_dataObject

Store the raw data for deferred parsing



52
53
54
# File 'lib/fontisan/tables/loca.rb', line 52

def raw_data
  @raw_data
end

Class Method Details

.read(io) ⇒ Loca

Override read to capture raw data

Parameters:

  • io (IO, String)

    Input data

Returns:

  • (Loca)

    Parsed table instance



70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/fontisan/tables/loca.rb', line 70

def self.read(io)
  instance = new

  # Handle nil or empty data gracefully
  instance.raw_data = if io.nil?
                        "".b
                      elsif io.is_a?(String)
                        io
                      else
                        io.read || "".b
                      end

  instance
end

Instance Method Details

#empty?(glyph_id) ⇒ Boolean?

Check if a glyph has no outline data

A glyph is empty when its size is 0, which occurs when consecutive offsets are equal. Empty glyphs are used for space characters and other non-visible glyphs.

Examples:

Checking if glyph is empty

is_empty = loca.empty?(32)  # Check if space character is empty

Parameters:

  • glyph_id (Integer)

    Glyph ID (0-based)

Returns:

  • (Boolean, nil)

    True if empty, false if has data, nil if invalid ID

Raises:

  • (RuntimeError)

    If table has not been parsed with context



164
165
166
167
# File 'lib/fontisan/tables/loca.rb', line 164

def empty?(glyph_id)
  size = size_of(glyph_id)
  size&.zero?
end

#expected_sizeInteger?

Get the expected size for this table

Returns:

  • (Integer, nil)

    Expected size in bytes, or nil if not parsed



193
194
195
196
197
198
199
200
201
202
# File 'lib/fontisan/tables/loca.rb', line 193

def expected_size
  return nil unless parsed?

  offset_count = num_glyphs + 1
  if short_format?
    offset_count * 2  # uint16
  else
    offset_count * 4  # uint32
  end
end

#long_format?Boolean

Check if using long format (format 1)

Returns:

  • (Boolean)

    True if long format, false otherwise



186
187
188
# File 'lib/fontisan/tables/loca.rb', line 186

def long_format?
  format == FORMAT_LONG
end

#offset_for(glyph_id) ⇒ Integer?

Get the offset for a specific glyph ID in the glyf table

Examples:

Getting glyph offset

offset = loca.offset_for(0)  # .notdef glyph offset

Parameters:

  • glyph_id (Integer)

    Glyph ID (0-based)

Returns:

  • (Integer, nil)

    Byte offset in glyf table, or nil if invalid ID

Raises:

  • (RuntimeError)

    If table has not been parsed with context



123
124
125
126
127
128
129
# File 'lib/fontisan/tables/loca.rb', line 123

def offset_for(glyph_id)
  raise "Table not parsed. Call parse_with_context first." unless @offsets

  return nil if glyph_id >= num_glyphs || glyph_id.negative?

  offsets[glyph_id]
end

#parse_with_context(index_to_loc_format, num_glyphs) ⇒ Object

Parse the table with font context

This method must be called after reading the table data, providing the indexToLocFormat from head and numGlyphs from maxp.

Parameters:

  • index_to_loc_format (Integer)

    Format (0 = short, 1 = long) from head table

  • num_glyphs (Integer)

    Total number of glyphs from maxp table

Raises:



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

def parse_with_context(index_to_loc_format, num_glyphs)
  validate_context_params(index_to_loc_format, num_glyphs)

  @format = index_to_loc_format
  @num_glyphs = num_glyphs

  io = StringIO.new(raw_data)
  io.set_encoding(Encoding::BINARY)

  # Number of offsets is numGlyphs + 1 (extra offset marks end of last glyph)
  offset_count = num_glyphs + 1

  @offsets = if short_format?
               parse_short_offsets(io, offset_count)
             else
               parse_long_offsets(io, offset_count)
             end

  validate_parsed_data!(io, offset_count)
end

#parsed?Boolean

Check if the table has been parsed with context

Returns:

  • (Boolean)

    True if parsed, false otherwise



172
173
174
# File 'lib/fontisan/tables/loca.rb', line 172

def parsed?
  !@offsets.nil?
end

#short_format?Boolean

Check if using short format (format 0)

Returns:

  • (Boolean)

    True if short format, false otherwise



179
180
181
# File 'lib/fontisan/tables/loca.rb', line 179

def short_format?
  format == FORMAT_SHORT
end

#size_of(glyph_id) ⇒ Integer?

Calculate the size of glyph data for a specific glyph ID

The size is calculated as the difference between consecutive offsets: size = offsets[glyph_id + 1] - offsets

A size of 0 indicates an empty glyph (no outline data).

Examples:

Calculating glyph size

size = loca.size_of(42)  # Size of glyph 42 in bytes

Parameters:

  • glyph_id (Integer)

    Glyph ID (0-based)

Returns:

  • (Integer, nil)

    Size in bytes, or nil if invalid ID

Raises:

  • (RuntimeError)

    If table has not been parsed with context



144
145
146
147
148
149
150
# File 'lib/fontisan/tables/loca.rb', line 144

def size_of(glyph_id)
  raise "Table not parsed. Call parse_with_context first." unless @offsets

  return nil if glyph_id >= num_glyphs || glyph_id.negative?

  offsets[glyph_id + 1] - offsets[glyph_id]
end