Class: Fontisan::Woff2::HmtxTransformer

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/woff2/hmtx_transformer.rb

Overview

Reconstructs hmtx table from WOFF2 transformed format

WOFF2 hmtx transformation optimizes horizontal metrics by:

  • Using variable-length encoding for advance widths
  • Optionally deriving LSB from glyf bounding boxes
  • Omitting redundant trailing advance widths

See: https://www.w3.org/TR/WOFF2/#hmtx_table_format

Examples:

Reconstructing hmtx table

hmtx_data = HmtxTransformer.reconstruct(
  transformed_data,
  num_glyphs,
  number_of_h_metrics
)

Constant Summary collapse

HMTX_FLAG_EXPLICIT_ADVANCE_WIDTHS =

Flags for hmtx transformation

0x01
HMTX_FLAG_EXPLICIT_LSB_VALUES =
0x02
HMTX_FLAG_SYMMETRIC =
0x04

Class Method Summary collapse

Class Method Details

.build_hmtx_table(advance_widths, lsbs, num_h_metrics, num_glyphs) ⇒ String

Build standard hmtx table format

Standard hmtx format:

  • longHorMetric (advanceWidth, lsb pairs)
  • int16[numGlyphs - numberOfHMetrics] (additional LSBs)

Parameters:

  • advance_widths (Array<Integer>)

    Advance widths

  • lsbs (Array<Integer>)

    Left side bearings

  • num_h_metrics (Integer)

    Number of entries with full hMetrics

  • num_glyphs (Integer)

    Total number of glyphs

Returns:

  • (String)

    Standard hmtx table data



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/fontisan/woff2/hmtx_transformer.rb', line 102

def self.build_hmtx_table(advance_widths, lsbs, num_h_metrics, num_glyphs)
  data = +""

  # Write longHorMetric array (advanceWidth + lsb pairs)
  num_h_metrics.times do |i|
    advance_width = advance_widths[i] || advance_widths.last
    lsb = lsbs[i] || 0

    data << [advance_width].pack("n")  # uint16 advanceWidth
    data << [lsb].pack("n")            # int16 lsb
  end

  # Write remaining LSB values
  # These glyphs all share the last advance width from the array
  (num_h_metrics...num_glyphs).each do |i|
    lsb = lsbs[i] || 0
    data << [lsb].pack("n") # int16 lsb
  end

  data
end

.read_int16(io) ⇒ Object



134
135
136
137
# File 'lib/fontisan/woff2/hmtx_transformer.rb', line 134

def self.read_int16(io)
  value = read_uint16(io)
  value > 0x7FFF ? value - 0x10000 : value
end

.read_uint16(io) ⇒ Object



130
131
132
# File 'lib/fontisan/woff2/hmtx_transformer.rb', line 130

def self.read_uint16(io)
  io.read(2)&.unpack1("n") || raise(EOFError, "Unexpected end of stream")
end

.read_uint8(io) ⇒ Object

Helper methods for reading binary data



126
127
128
# File 'lib/fontisan/woff2/hmtx_transformer.rb', line 126

def self.read_uint8(io)
  io.read(1)&.unpack1("C") || raise(EOFError, "Unexpected end of stream")
end

.reconstruct(transformed_data, num_glyphs, num_h_metrics, glyf_lsbs = nil) ⇒ String

Reconstruct hmtx table from transformed data

Parameters:

  • transformed_data (String)

    The transformed hmtx table data

  • num_glyphs (Integer)

    Number of glyphs

  • num_h_metrics (Integer)

    From hhea.numberOfHMetrics

  • glyf_lsbs (Array<Integer>, nil) (defaults to: nil)

    LSB values from glyf bboxes (optional)

Returns:

  • (String)

    Standard hmtx table data

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/fontisan/woff2/hmtx_transformer.rb', line 36

def self.reconstruct(transformed_data, num_glyphs, num_h_metrics,
glyf_lsbs = nil)
  io = StringIO.new(transformed_data)

  # Read transformation flags
  flags = read_uint8(io)

  # Read advance widths
  advance_widths = []

  if (flags & HMTX_FLAG_EXPLICIT_ADVANCE_WIDTHS).zero?
    # Proportional encoding - read deltas
    # First advance width is explicit
    first_advance = UInt255.decode(io)
    advance_widths << first_advance

    # Remaining are deltas from previous
    (num_h_metrics - 1).times do
      delta = read_int16(io)
      advance_widths << (advance_widths.last + delta)
    end
  else
    # Explicit advance widths in transformed format
    num_h_metrics.times do
      advance_widths << UInt255.decode(io)
    end
  end

  # Read LSB values
  lsbs = []

  if (flags & HMTX_FLAG_EXPLICIT_LSB_VALUES) != 0
    # Explicit LSB values
    num_glyphs.times do
      lsbs << read_int16(io)
    end
  elsif glyf_lsbs
    # Use LSB values from glyf bounding boxes
    lsbs = glyf_lsbs
  else
    # Need to read LSB values for long metrics
    num_h_metrics.times do
      lsbs << read_int16(io)
    end

    # Remaining LSBs for glyphs that share the last advance width
    (num_glyphs - num_h_metrics).times do
      lsbs << read_int16(io)
    end
  end

  # Build standard hmtx table
  build_hmtx_table(advance_widths, lsbs, num_h_metrics, num_glyphs)
end