Class: Fontisan::Tables::Cpal
- Inherits:
-
Binary::BaseRecord
- Object
- BinData::Record
- Binary::BaseRecord
- Fontisan::Tables::Cpal
- 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 docs.microsoft.com/en-us/typography/opentype/spec/cpal
Constant Summary collapse
- TAG =
OpenType table tag for CPAL
"CPAL"
Instance Attribute Summary collapse
-
#color_records ⇒ Array<Hash>
readonly
Parsed color records (RGBA hashes).
-
#color_records_array_offset ⇒ Integer
readonly
Offset to color records array.
-
#num_color_records ⇒ Integer
readonly
Total number of color records.
-
#num_palette_entries ⇒ Integer
readonly
Number of color entries per palette.
-
#num_palettes ⇒ Integer
readonly
Number of palettes in this table.
-
#palette_indices ⇒ Array<Integer>
readonly
Palette indices (start index for each palette).
-
#raw_data ⇒ String
readonly
Raw binary data for the entire CPAL table.
-
#version ⇒ Integer
readonly
CPAL version (0 or 1).
Class Method Summary collapse
-
.read(io) ⇒ Cpal
Override read to parse CPAL structure.
Instance Method Summary collapse
-
#all_palettes ⇒ Array<Array<String>>
Get all palettes.
-
#color_at(palette_index, entry_index) ⇒ String?
Get color at specific palette and entry index.
-
#palette(index) ⇒ Array<String>?
Get a specific palette by index.
-
#parse!(data) ⇒ Object
Parse the CPAL table structure.
-
#valid? ⇒ Boolean
Validate the CPAL table structure.
Instance Attribute Details
#color_records ⇒ Array<Hash> (readonly)
Returns Parsed color records (RGBA hashes).
73 74 75 |
# File 'lib/fontisan/tables/cpal.rb', line 73 def color_records @color_records end |
#color_records_array_offset ⇒ Integer (readonly)
Returns Offset to color records array.
64 65 66 |
# File 'lib/fontisan/tables/cpal.rb', line 64 def color_records_array_offset @color_records_array_offset end |
#num_color_records ⇒ Integer (readonly)
Returns Total number of color records.
61 62 63 |
# File 'lib/fontisan/tables/cpal.rb', line 61 def num_color_records @num_color_records end |
#num_palette_entries ⇒ Integer (readonly)
Returns Number of color entries per palette.
55 56 57 |
# File 'lib/fontisan/tables/cpal.rb', line 55 def num_palette_entries @num_palette_entries end |
#num_palettes ⇒ Integer (readonly)
Returns Number of palettes in this table.
58 59 60 |
# File 'lib/fontisan/tables/cpal.rb', line 58 def num_palettes @num_palettes end |
#palette_indices ⇒ Array<Integer> (readonly)
Returns Palette indices (start index for each palette).
70 71 72 |
# File 'lib/fontisan/tables/cpal.rb', line 70 def palette_indices @palette_indices end |
#raw_data ⇒ String (readonly)
Returns Raw binary data for the entire CPAL table.
67 68 69 |
# File 'lib/fontisan/tables/cpal.rb', line 67 def raw_data @raw_data end |
#version ⇒ Integer (readonly)
Returns CPAL version (0 or 1).
52 53 54 |
# File 'lib/fontisan/tables/cpal.rb', line 52 def version @version end |
Class Method Details
.read(io) ⇒ Cpal
Override read to parse CPAL structure
79 80 81 82 83 84 85 86 |
# File 'lib/fontisan/tables/cpal.rb', line 79 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_palettes ⇒ Array<Array<String>>
Get all palettes
138 139 140 |
# File 'lib/fontisan/tables/cpal.rb', line 138 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
147 148 149 150 151 152 153 154 |
# File 'lib/fontisan/tables/cpal.rb', line 147 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.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/fontisan/tables/cpal.rb', line 119 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
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/fontisan/tables/cpal.rb', line 92 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.}" end |
#valid? ⇒ Boolean
Validate the CPAL table structure
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/fontisan/tables/cpal.rb', line 159 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 |