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 Header (24 bytes) adds:
- paletteTypesArrayOffset (uint32): Offset to palette types array
- paletteLabelsArrayOffset (uint32): Offset to palette labels array
- paletteEntryLabelsArrayOffset (uint32): Offset to palette entry 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
Constant Summary collapse
- TAG =
OpenType table tag for CPAL
"CPAL"- PALETTE_TYPE_LIGHT_ON_DARK =
Palette type bit flags (CPAL v1, paletteTypesArray entries).
0x01- PALETTE_TYPE_DARK_ON_LIGHT =
0x02- NULL_OFFSET =
Offset that means "no table present" in CPAL v1.
0
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_entry_labels ⇒ Array<Integer>?
readonly
Name-record IDs (name table) for each palette entry index (shared across palettes).
-
#palette_indices ⇒ Array<Integer>
readonly
Palette indices (start index for each palette).
-
#palette_labels ⇒ Array<Integer>?
readonly
Name-record IDs (name table) for each palette.
-
#palette_types ⇒ Array<Integer>?
readonly
Palette type bit flags, one per 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.
-
#dark_on_light?(palette_index) ⇒ Boolean
Whether a palette is intended for dark-on-light use (CPAL v1).
-
#light_on_dark?(palette_index) ⇒ Boolean
Whether a palette is intended for light-on-dark use (CPAL v1).
-
#palette(index) ⇒ Array<String>?
Get a specific palette by index.
-
#palette_entry_label(entry_index) ⇒ Integer?
Get the name-record ID for a palette entry's human-readable label.
-
#palette_label(palette_index) ⇒ Integer?
Get the name-record ID for a palette's human-readable label.
-
#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).
82 83 84 |
# File 'lib/fontisan/tables/cpal.rb', line 82 def color_records @color_records end |
#color_records_array_offset ⇒ Integer (readonly)
Returns Offset to color records array.
73 74 75 |
# File 'lib/fontisan/tables/cpal.rb', line 73 def color_records_array_offset @color_records_array_offset end |
#num_color_records ⇒ Integer (readonly)
Returns Total number of color records.
70 71 72 |
# File 'lib/fontisan/tables/cpal.rb', line 70 def num_color_records @num_color_records end |
#num_palette_entries ⇒ Integer (readonly)
Returns Number of color entries per palette.
64 65 66 |
# File 'lib/fontisan/tables/cpal.rb', line 64 def num_palette_entries @num_palette_entries end |
#num_palettes ⇒ Integer (readonly)
Returns Number of palettes in this table.
67 68 69 |
# File 'lib/fontisan/tables/cpal.rb', line 67 def num_palettes @num_palettes end |
#palette_entry_labels ⇒ Array<Integer>? (readonly)
Returns Name-record IDs (name table) for each palette entry index (shared across palettes). nil for CPAL v0 or when v1 has no entry-labels array.
95 96 97 |
# File 'lib/fontisan/tables/cpal.rb', line 95 def palette_entry_labels @palette_entry_labels end |
#palette_indices ⇒ Array<Integer> (readonly)
Returns Palette indices (start index for each palette).
79 80 81 |
# File 'lib/fontisan/tables/cpal.rb', line 79 def palette_indices @palette_indices end |
#palette_labels ⇒ Array<Integer>? (readonly)
Returns Name-record IDs (name table) for each palette. nil for CPAL v0 or when v1 has no labels array.
90 91 92 |
# File 'lib/fontisan/tables/cpal.rb', line 90 def palette_labels @palette_labels end |
#palette_types ⇒ Array<Integer>? (readonly)
Returns Palette type bit flags, one per palette. nil for CPAL v0 or when v1 has no palette types array.
86 87 88 |
# File 'lib/fontisan/tables/cpal.rb', line 86 def palette_types @palette_types end |
#raw_data ⇒ String (readonly)
Returns Raw binary data for the entire CPAL table.
76 77 78 |
# File 'lib/fontisan/tables/cpal.rb', line 76 def raw_data @raw_data end |
#version ⇒ Integer (readonly)
Returns CPAL version (0 or 1).
61 62 63 |
# File 'lib/fontisan/tables/cpal.rb', line 61 def version @version end |
Class Method Details
.read(io) ⇒ Cpal
Override read to parse CPAL structure
101 102 103 104 105 106 107 108 |
# File 'lib/fontisan/tables/cpal.rb', line 101 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
160 161 162 |
# File 'lib/fontisan/tables/cpal.rb', line 160 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
169 170 171 172 173 174 175 176 |
# File 'lib/fontisan/tables/cpal.rb', line 169 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 |
#dark_on_light?(palette_index) ⇒ Boolean
Whether a palette is intended for dark-on-light use (CPAL v1).
212 213 214 |
# File 'lib/fontisan/tables/cpal.rb', line 212 def dark_on_light?(palette_index) palette_type_set?(palette_index, PALETTE_TYPE_DARK_ON_LIGHT) end |
#light_on_dark?(palette_index) ⇒ Boolean
Whether a palette is intended for light-on-dark use (CPAL v1).
204 205 206 |
# File 'lib/fontisan/tables/cpal.rb', line 204 def light_on_dark?(palette_index) palette_type_set?(palette_index, PALETTE_TYPE_LIGHT_ON_DARK) 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.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/fontisan/tables/cpal.rb', line 141 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 |
#palette_entry_label(entry_index) ⇒ Integer?
Get the name-record ID for a palette entry's human-readable label.
193 194 195 196 197 198 |
# File 'lib/fontisan/tables/cpal.rb', line 193 def palette_entry_label(entry_index) return nil unless palette_entry_labels return nil if entry_index.negative? || entry_index >= palette_entry_labels.length palette_entry_labels[entry_index] end |
#palette_label(palette_index) ⇒ Integer?
Get the name-record ID for a palette's human-readable label.
182 183 184 185 186 187 |
# File 'lib/fontisan/tables/cpal.rb', line 182 def palette_label(palette_index) return nil unless palette_labels return nil if palette_index.negative? || palette_index >= palette_labels.length palette_labels[palette_index] end |
#parse!(data) ⇒ Object
Parse the CPAL table structure
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/fontisan/tables/cpal.rb', line 114 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: palette types + labels (lazily walked via offsets) if version == 1 rescue StandardError => e raise CorruptedTableError, "Failed to parse CPAL table: #{e.}" end |
#valid? ⇒ Boolean
Validate the CPAL table structure
219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/fontisan/tables/cpal.rb', line 219 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 |