Module: Fontisan::Woff2::Directory

Defined in:
lib/fontisan/woff2/directory.rb

Overview

WOFF2 Table Directory Entry

[‘Woff2::Directory`](lib/fontisan/woff2/directory.rb) represents a single table entry in the WOFF2 table directory. Unlike WOFF, WOFF2 uses variable-length encoding for sizes and supports table transformations for better compression.

Each entry contains:

  • flags (1 byte): Contains tag index and transformation version

  • tag (0 or 4 bytes): Table tag (omitted if using known tag index)

  • origLength (UIntBase128): Original uncompressed table length

  • transformLength (UIntBase128, optional): Transformed data length

Flags byte structure:

  • Bits 0-5: Table tag index (0-62 = known tags, 63 = custom tag)

  • Bits 6-7: Transformation version

Reference: www.w3.org/TR/WOFF2/#table_dir_format

Examples:

Create entry for known table

entry = Directory::Entry.new
entry.tag = "glyf"
entry.orig_length = 12000
entry.flags = entry.calculate_flags

Create entry for custom table

entry = Directory::Entry.new
entry.tag = "CUST"
entry.orig_length = 5000
entry.flags = 0x3F # Custom tag indicator

Defined Under Namespace

Classes: Entry

Constant Summary collapse

KNOWN_TAGS =

Known table tags with assigned indices (0-62) Index 63 (0x3F) indicates a custom tag follows

[
  "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "post",
  "cvt ", "fpgm", "glyf", "loca", "prep", "CFF ", "VORG", "EBDT",
  "EBLC", "gasp", "hdmx", "kern", "LTSH", "PCLT", "VDMX", "vhea",
  "vmtx", "BASE", "GDEF", "GPOS", "GSUB", "EBSC", "JSTF", "MATH",
  "CBDT", "CBLC", "COLR", "CPAL", "SVG ", "sbix", "acnt", "avar",
  "bdat", "bloc", "bsln", "cvar", "fdsc", "feat", "fmtx", "fvar",
  "gvar", "hsty", "just", "lcar", "mort", "morx", "opbd", "prop",
  "trak", "Zapf", "Silf", "Glat", "Gloc", "Feat", "Sill"
].freeze
TRANSFORM_NONE =

Transformation versions According to WOFF2 spec:

  • glyf/loca: version 0 or 3 WITH transformLength = transformed

  • glyf/loca: version 1 or 2 WITHOUT transformLength = not transformed

  • hmtx: version 1 WITH transformLength = transformed

  • hmtx: version 0, 2, or 3 WITHOUT transformLength = not transformed

3
TRANSFORM_GLYF_LOCA =

Use version 3 when not transformed (works for all tables)

0
TRANSFORM_HMTX =

glyf/loca use version 0 when transformed

1
CUSTOM_TAG_INDEX =

Custom tag indicator

0x3F

Class Method Summary collapse

Class Method Details

.decode_255_uint16(io) ⇒ Integer

Decode 255UInt16 from IO

Parameters:

  • io (IO)

    Input stream

Returns:

  • (Integer)

    Decoded value



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/fontisan/woff2/directory.rb', line 267

def self.decode_255_uint16(io)
  first = io.read(1)&.unpack1("C")
  return nil unless first

  case first
  when 0..252
    first
  when 253
    second = io.read(1)&.unpack1("C")
    253 + second
  when 254
    io.read(2)&.unpack1("n")
  when 255
    value = io.read(2)&.unpack1("n")
    value + 506
  end
end

.decode_uint_base128(io) ⇒ Integer

Decode UIntBase128 from IO

Parameters:

  • io (IO)

    Input stream

Returns:

  • (Integer)

    Decoded value

Raises:

  • (Error)

    If encoding is invalid



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/fontisan/woff2/directory.rb', line 223

def self.decode_uint_base128(io)
  result = 0
  5.times do
    byte = io.read(1)&.unpack1("C")
    return nil unless byte

    # Check if high bit is set (continuation)
    if (byte & 0x80).zero?
      return (result << 7) | byte
    else
      result = (result << 7) | (byte & 0x7F)
    end
  end

  # If we're here, encoding is invalid (> 5 bytes)
  raise Fontisan::Error, "Invalid UIntBase128 encoding"
end

.encode_255_uint16(value) ⇒ String

Encode 255UInt16 value

Used in transformed glyf table:

  • 0-252: value itself (1 byte)

  • 253: next byte + 253 (2 bytes)

  • 254: next 2 bytes as big-endian (3 bytes)

  • 255: next 2 bytes + 506 (3 bytes)

Parameters:

  • value (Integer)

    Value to encode (0-65535)

Returns:

  • (String)

    Binary encoded data



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/fontisan/woff2/directory.rb', line 251

def self.encode_255_uint16(value)
  if value < 253
    [value].pack("C")
  elsif value < 506
    [253, value - 253].pack("C*")
  elsif value < 65536
    [254].pack("C") + [value].pack("n")
  else
    [255].pack("C") + [value - 506].pack("n")
  end
end

.encode_uint_base128(value) ⇒ String

Encode an integer as UIntBase128

Variable-length encoding where:

  • If value < 128, use 1 byte

  • Otherwise, use high bit to indicate continuation

Parameters:

  • value (Integer)

    Value to encode

Returns:

  • (String)

    Binary encoded data



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/fontisan/woff2/directory.rb', line 197

def self.encode_uint_base128(value)
  return [value].pack("C") if value < 128

  bytes = []
  v = value

  # Build bytes from least to most significant
  loop do
    bytes.unshift(v & 0x7F)
    v >>= 7
    break if v.zero?
  end

  # Set high bit on all but last byte
  (0...bytes.length - 1).each do |i|
    bytes[i] |= 0x80
  end

  bytes.pack("C*")
end