Module: Fontisan::Woff2::Directory

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

Overview

WOFF2 Table Directory Entry

Woff2::Directory 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: https://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_NULL =

Transformation versions per WOFF2 spec section 5.1, 5.3, 5.4.

glyf/loca:

- version 0: transformed format (spec section 5.1 / 5.3)
- version 3: null transform — original bytes passed to brotli as-is
(Chrome's OTS does not accept version 3 for glyf/loca; encoders
that want to interoperate with browsers MUST use version 0).

hmtx:

- version 0: null transform (default)
- version 1: transformed format (spec section 5.4)

All other tables use version 0 for the null transform.

3
TRANSFORM_NONE =

glyf/loca null transform

TRANSFORM_NULL
TRANSFORM_GLYF_LOCA =

deprecated alias kept for older callers

0
TRANSFORM_HMTX =

glyf/loca transformed format

1
TRANSFORM_HMTX_NULL =

hmtx transformed format

0
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



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

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



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

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



248
249
250
251
252
253
254
255
256
257
258
# File 'lib/fontisan/woff2/directory.rb', line 248

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



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

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