Class: Fontisan::Tables::Cpal

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

Overview

CPAL (Color Palette) table parser

The CPAL table defines color palettes used by COLR layers. Each palette contains an array of RGBA color values that can be referenced by the COLR table's palette indices.

CPAL Table Structure:

CPAL Table = Header
           + Palette Indices Array
           + Color Records Array
           + [Palette Types Array] (version 1)
           + [Palette Labels Array] (version 1)
           + [Palette Entry Labels Array] (version 1)

Version 0 Header (12 bytes):

  • version (uint16): Table version (0 or 1)
  • numPaletteEntries (uint16): Number of colors per palette
  • numPalettes (uint16): Number of palettes
  • numColorRecords (uint16): Total number of color records
  • colorRecordsArrayOffset (uint32): Offset to color records array

Version 1 adds optional metadata for palette types and labels.

Color Record Structure (4 bytes, BGRA format):

  • blue (uint8)
  • green (uint8)
  • red (uint8)
  • alpha (uint8)

Reference: OpenType CPAL specification https://docs.microsoft.com/en-us/typography/opentype/spec/cpal

Examples:

Reading a CPAL table

data = font.table_data['CPAL']
cpal = Fontisan::Tables::Cpal.read(data)
palette = cpal.palette(0)  # Get first palette
puts palette.colors.first  # => "#RRGGBBAA"

Constant Summary collapse

TAG =

OpenType table tag for CPAL

"CPAL"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#color_recordsArray<Hash> (readonly)

Returns Parsed color records (RGBA hashes).

Returns:

  • (Array<Hash>)

    Parsed color records (RGBA hashes)



72
73
74
# File 'lib/fontisan/tables/cpal.rb', line 72

def color_records
  @color_records
end

#color_records_array_offsetInteger (readonly)

Returns Offset to color records array.

Returns:

  • (Integer)

    Offset to color records array



63
64
65
# File 'lib/fontisan/tables/cpal.rb', line 63

def color_records_array_offset
  @color_records_array_offset
end

#num_color_recordsInteger (readonly)

Returns Total number of color records.

Returns:

  • (Integer)

    Total number of color records



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

def num_color_records
  @num_color_records
end

#num_palette_entriesInteger (readonly)

Returns Number of color entries per palette.

Returns:

  • (Integer)

    Number of color entries per palette



54
55
56
# File 'lib/fontisan/tables/cpal.rb', line 54

def num_palette_entries
  @num_palette_entries
end

#num_palettesInteger (readonly)

Returns Number of palettes in this table.

Returns:

  • (Integer)

    Number of palettes in this table



57
58
59
# File 'lib/fontisan/tables/cpal.rb', line 57

def num_palettes
  @num_palettes
end

#palette_indicesArray<Integer> (readonly)

Returns Palette indices (start index for each palette).

Returns:

  • (Array<Integer>)

    Palette indices (start index for each palette)



69
70
71
# File 'lib/fontisan/tables/cpal.rb', line 69

def palette_indices
  @palette_indices
end

#raw_dataString (readonly)

Returns Raw binary data for the entire CPAL table.

Returns:

  • (String)

    Raw binary data for the entire CPAL table



66
67
68
# File 'lib/fontisan/tables/cpal.rb', line 66

def raw_data
  @raw_data
end

#versionInteger (readonly)

Returns CPAL version (0 or 1).

Returns:

  • (Integer)

    CPAL version (0 or 1)



51
52
53
# File 'lib/fontisan/tables/cpal.rb', line 51

def version
  @version
end

Class Method Details

.read(io) ⇒ Cpal

Override read to parse CPAL structure

Parameters:

  • io (IO, String)

    Binary data to read

Returns:

  • (Cpal)

    Parsed CPAL table



78
79
80
81
82
83
84
85
# File 'lib/fontisan/tables/cpal.rb', line 78

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

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

Instance Method Details

#all_palettesArray<Array<String>>

Get all palettes

Returns:

  • (Array<Array<String>>)

    Array of palettes, each an array of hex colors



137
138
139
# File 'lib/fontisan/tables/cpal.rb', line 137

def all_palettes
  (0...num_palettes).map { |i| palette(i) }
end

#color_at(palette_index, entry_index) ⇒ String?

Get color at specific palette and entry index

Parameters:

  • palette_index (Integer)

    Palette index

  • entry_index (Integer)

    Entry index within palette

Returns:

  • (String, nil)

    Hex color string or nil



146
147
148
149
150
151
152
153
# File 'lib/fontisan/tables/cpal.rb', line 146

def color_at(palette_index, entry_index)
  return nil if palette_index.negative? || palette_index >= num_palettes
  return nil if entry_index.negative? || entry_index >= num_palette_entries

  start_index = palette_indices[palette_index]
  color_record = color_records[start_index + entry_index]
  color_record ? color_to_hex(color_record) : nil
end

#palette(index) ⇒ Array<String>?

Get a specific palette by index

Returns an array of color strings in hex format (#RRGGBBAA). Each palette contains num_palette_entries colors.

Parameters:

  • index (Integer)

    Palette index (0-based)

Returns:

  • (Array<String>, nil)

    Array of hex color strings, or nil if invalid



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/fontisan/tables/cpal.rb', line 118

def palette(index)
  return nil if index.negative? || index >= num_palettes

  # Get starting index for this palette
  start_index = palette_indices[index]

  # Extract colors for this palette
  colors = []
  num_palette_entries.times do |i|
    color_record = color_records[start_index + i]
    colors << color_to_hex(color_record) if color_record
  end

  colors
end

#parse!(data) ⇒ Object

Parse the CPAL table structure

Parameters:

  • data (String)

    Binary data for the CPAL table

Raises:



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/fontisan/tables/cpal.rb', line 91

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

  # Parse CPAL header
  parse_header(io)
  validate_header!

  # Parse palette indices array
  parse_palette_indices(io)

  # Parse color records
  parse_color_records(io)

  # Version 1 features (palette types, labels) not implemented yet
  # TODO: Add version 1 features in follow-up task
rescue StandardError => e
  raise CorruptedTableError, "Failed to parse CPAL table: #{e.message}"
end

#valid?Boolean

Validate the CPAL table structure

Returns:

  • (Boolean)

    True if valid



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/fontisan/tables/cpal.rb', line 158

def valid?
  return false if version.nil?
  return false unless [0, 1].include?(version)
  return false if num_palette_entries.nil? || num_palette_entries.negative?
  return false if num_palettes.nil? || num_palettes.negative?
  return false if num_color_records.nil? || num_color_records.negative?
  return false unless palette_indices
  return false unless color_records

  true
end