Class: Fontisan::Tables::Sbix

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

Overview

sbix (Standard Bitmap Graphics) table parser

The sbix table contains embedded bitmap graphics (PNG, JPEG, TIFF) organized by strike sizes. This is Apple’s format for color emoji.

sbix Table Structure: “‘ sbix Table = Header (8 bytes)

+ Strike Offsets Array (4 bytes × numStrikes)
+ Strike Data (variable)

“‘

Header (8 bytes):

  • version (uint16): Table version (1)

  • flags (uint16): Flags (0)

  • numStrikes (uint32): Number of bitmap strikes

Each Strike contains:

  • ppem (uint16): Pixels per em

  • ppi (uint16): Pixels per inch (usually 72)

  • glyphDataOffsets (uint32 × numGlyphs+1): Array of glyph data offsets

  • glyph data records (variable)

Glyph Data Record:

  • originOffsetX (int16): X offset

  • originOffsetY (int16): Y offset

  • graphicType (uint32): ‘png ’, ‘jpg ’, ‘tiff’, ‘dupe’, ‘mask’

  • data (variable): Image data

Reference: docs.microsoft.com/en-us/typography/opentype/spec/sbix

Examples:

Reading an sbix table

data = font.table_data['sbix']
sbix = Fontisan::Tables::Sbix.read(data)
strikes = sbix.strikes
png_data = sbix.glyph_data(42, 64)  # Get glyph 42 at 64 ppem

Constant Summary collapse

TAG =

OpenType table tag for sbix

"sbix"
VERSION_1 =

Supported sbix version

1
GRAPHIC_TYPE_PNG =

Graphic type constants (4-byte ASCII codes)

0x706E6720
GRAPHIC_TYPE_JPG =

‘png ’

0x6A706720
GRAPHIC_TYPE_TIFF =

‘jpg ’

0x74696666
GRAPHIC_TYPE_DUPE =

‘tiff’

0x64757065
GRAPHIC_TYPE_MASK =

‘dupe’

0x6D61736B
GRAPHIC_TYPE_NAMES =

Graphic type names

{
  GRAPHIC_TYPE_PNG => "PNG",
  GRAPHIC_TYPE_JPG => "JPEG",
  GRAPHIC_TYPE_TIFF => "TIFF",
  GRAPHIC_TYPE_DUPE => "dupe",
  GRAPHIC_TYPE_MASK => "mask",
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#flagsInteger (readonly)

Returns Flags (reserved, should be 0).

Returns:

  • (Integer)

    Flags (reserved, should be 0)



71
72
73
# File 'lib/fontisan/tables/sbix.rb', line 71

def flags
  @flags
end

#num_strikesInteger (readonly)

Returns Number of bitmap strikes.

Returns:

  • (Integer)

    Number of bitmap strikes



74
75
76
# File 'lib/fontisan/tables/sbix.rb', line 74

def num_strikes
  @num_strikes
end

#raw_dataString (readonly)

Returns Raw binary data for the entire sbix table.

Returns:

  • (String)

    Raw binary data for the entire sbix table



83
84
85
# File 'lib/fontisan/tables/sbix.rb', line 83

def raw_data
  @raw_data
end

#strike_offsetsArray<Integer> (readonly)

Returns Offsets to strike data from start of table.

Returns:

  • (Array<Integer>)

    Offsets to strike data from start of table



77
78
79
# File 'lib/fontisan/tables/sbix.rb', line 77

def strike_offsets
  @strike_offsets
end

#strikesArray<Hash> (readonly)

Returns Parsed strike records.

Returns:

  • (Array<Hash>)

    Parsed strike records



80
81
82
# File 'lib/fontisan/tables/sbix.rb', line 80

def strikes
  @strikes
end

#versionInteger (readonly)

Returns sbix version (should be 1).

Returns:

  • (Integer)

    sbix version (should be 1)



68
69
70
# File 'lib/fontisan/tables/sbix.rb', line 68

def version
  @version
end

Class Method Details

.read(io) ⇒ Sbix

Override read to parse sbix structure

Parameters:

  • io (IO, String)

    Binary data to read

Returns:

  • (Sbix)

    Parsed sbix table



89
90
91
92
93
94
95
96
# File 'lib/fontisan/tables/sbix.rb', line 89

def self.read(io)
  sbix = new
  return sbix if io.nil?

  data = io.is_a?(String) ? io : io.read
  sbix.parse!(data)
  sbix
end

Instance Method Details

#glyph_data(glyph_id, ppem) ⇒ Hash?

Get glyph data at specific ppem

Parameters:

  • glyph_id (Integer)

    Glyph ID

  • ppem (Integer)

    Pixels per em

Returns:

  • (Hash, nil)

    Glyph data hash with keys: :origin_x, :origin_y, :graphic_type, :data



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

def glyph_data(glyph_id, ppem)
  strike = strike_for_ppem(ppem)
  return nil unless strike

  extract_glyph_data(strike, glyph_id)
end

#has_glyph_at_ppem?(glyph_id, ppem) ⇒ Boolean

Check if glyph has bitmap at ppem

Parameters:

  • glyph_id (Integer)

    Glyph ID

  • ppem (Integer)

    Pixels per em

Returns:

  • (Boolean)

    True if glyph has bitmap



153
154
155
156
# File 'lib/fontisan/tables/sbix.rb', line 153

def has_glyph_at_ppem?(glyph_id, ppem)
  data = glyph_data(glyph_id, ppem)
  !data.nil? && data[:data] && !data[:data].empty?
end

#parse!(data) ⇒ Object

Parse the sbix table structure

Parameters:

  • data (String)

    Binary data for the sbix table

Raises:



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/fontisan/tables/sbix.rb', line 102

def parse!(data)
  @raw_data = data
  io = StringIO.new(data)

  # Parse sbix header (8 bytes)
  parse_header(io)
  validate_header!

  # Parse strike offsets
  parse_strike_offsets(io)

  # Parse strike records
  parse_strikes
rescue StandardError => e
  raise CorruptedTableError, "Failed to parse sbix table: #{e.message}"
end

#ppem_sizesArray<Integer>

Get all ppem sizes

Returns:

  • (Array<Integer>)

    Sorted array of ppem sizes



142
143
144
145
146
# File 'lib/fontisan/tables/sbix.rb', line 142

def ppem_sizes
  return [] unless strikes

  strikes.map { |s| s[:ppem] }.uniq.sort
end

#strike_for_ppem(ppem) ⇒ Hash?

Get strike for specific ppem

Parameters:

  • ppem (Integer)

    Pixels per em

Returns:

  • (Hash, nil)

    Strike record or nil



135
136
137
# File 'lib/fontisan/tables/sbix.rb', line 135

def strike_for_ppem(ppem)
  strikes&.find { |s| s[:ppem] == ppem }
end

#supported_formatsArray<String>

Get supported graphic formats across all strikes

Returns:

  • (Array<String>)

    Array of format names (e.g., [“PNG”, “JPEG”])



161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/fontisan/tables/sbix.rb', line 161

def supported_formats
  return [] unless strikes

  formats = []
  strikes.each do |strike|
    # Sample first few glyphs to detect formats
    strike[:graphic_types]&.each do |type|
      format_name = GRAPHIC_TYPE_NAMES[type]
      formats << format_name if format_name && !["dupe", "mask"].include?(format_name)
    end
  end
  formats.uniq.compact
end

#valid?Boolean

Validate the sbix table structure

Returns:

  • (Boolean)

    True if valid



178
179
180
181
182
183
184
185
# File 'lib/fontisan/tables/sbix.rb', line 178

def valid?
  return false if version.nil?
  return false if version != VERSION_1
  return false if num_strikes.nil? || num_strikes.negative?
  return false unless strikes

  true
end