diff --git a/examples/README.md b/examples/README.md index d1105c7..ede0ab0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -61,6 +61,25 @@ This folder contains example files for the ByteGrid plugin. - **Bitfield example**: 16 CPU flags - CF, ZF, SF, IF, DF, OF, IOPL, etc. +### Auto Color Examples + +#### 8. auto-color-examples.md +- **Interactive guide** with 4 examples showing automatic color assignment +- Open this file in Obsidian to see live examples +- Explains auto color modes and behavior + +#### 9. auto-color-basic.yaml +- Basic auto color example (8 fields, 8 colors) +- Default behavior: colors assigned automatically + +#### 10. auto-color-manual.yaml +- Manual color mode (`autoColor: false`) +- Only explicit colors shown, others gray + +#### 11. auto-color-mixed.yaml +- Mixed mode: combine auto and manual colors +- Explicit colors take precedence + ## Important Notes ⚠️ **Watch Out for Indentation When Copying** @@ -81,32 +100,56 @@ To visualize a new structure: name: Structure Name size: total byte size layout: 16 # bytes per row (optional, default: 16) +layoutUnit: byte # byte or bit (optional, auto-inferred from suffix) + +# Color options +autoColor: true # auto-assign colors (optional, default: true) +colorScheme: default # default, dark, or light (optional, default: default) + +# Legend options legendPosition: right # right, left, bottom, none (optional, default: right) +legendColumns: 1 # number of columns in legend (optional, default: 1) + +# Other options showFooter: true # show/hide footer (optional, default: true) + fields: - offset: 0-3 name: FieldName type: uint32_t - color: blue # blue, cyan, yellow, green, orange, purple, mint, pink, gray + color: blue # blue, cyan, yellow, green, orange, purple, mint, pink, gray (optional) description: "Description" # optional ``` +### Color Options Explained + +**autoColor** (default: `true`): +- `true`: Fields without explicit colors get auto-assigned (blue, cyan, yellow, green, orange, purple, mint, pink) +- `false`: Fields without explicit colors default to gray + +**colorScheme** (default: `default`): +- `default`: Original pastel colors +- `dark`: Reduced saturation and lightness (easier on eyes) +- `light`: Increased lightness (better for bright backgrounds) + +**Tip**: Start with `autoColor: true` and only specify colors for fields you want to emphasize! + ## Bit-Level Layout Examples -### 8. simple-bit-layout.yaml +### 12. simple-bit-layout.yaml - Basic bit-level layout test (16 bits) - **Suffix notation**: `size: 16b`, `layout: 16b`, `offset: 0-7b` - `layoutUnit` can be omitted (auto-inferred from suffix) -### 9. nibble-test.yaml +### 13. nibble-test.yaml - 4-bit field (nibble) test - Each nibble represented independently -### 10. mixed-bit-byte.yaml +### 14. mixed-bit-byte.yaml - Mixed bit and byte offsets - `0-3b` (bit), `1B` (Byte), `2-3` (byte, default) -### 11. ipv4-bit-layout.yaml +### 15. ipv4-bit-layout.yaml - IPv4 header in bit-level representation - Sub-byte fields like Version(4bit) + IHL(4bit) diff --git a/examples/auto-color-basic.yaml b/examples/auto-color-basic.yaml new file mode 100644 index 0000000..7284ef9 --- /dev/null +++ b/examples/auto-color-basic.yaml @@ -0,0 +1,38 @@ +name: Auto Color Basic Example +size: 32 +layout: 16 +# autoColor: true is the default + +fields: + - offset: 0-3 + name: Field1 + type: uint32_t + description: "Auto-assigned blue" + - offset: 4-7 + name: Field2 + type: uint32_t + description: "Auto-assigned cyan" + - offset: 8-11 + name: Field3 + type: uint32_t + description: "Auto-assigned yellow" + - offset: 12-15 + name: Field4 + type: uint32_t + description: "Auto-assigned green" + - offset: 16-19 + name: Field5 + type: uint32_t + description: "Auto-assigned orange" + - offset: 20-23 + name: Field6 + type: uint32_t + description: "Auto-assigned purple" + - offset: 24-27 + name: Field7 + type: uint32_t + description: "Auto-assigned mint" + - offset: 28-31 + name: Field8 + type: uint32_t + description: "Auto-assigned pink" diff --git a/examples/auto-color-examples.md b/examples/auto-color-examples.md new file mode 100644 index 0000000..9e39b96 --- /dev/null +++ b/examples/auto-color-examples.md @@ -0,0 +1,215 @@ +# Auto Color Examples + +ByteGrid supports automatic color assignment for fields. This feature makes it easy to create visualizations without manually specifying colors for each field. + +## Example 1: Basic Auto Color (Default) + +When `autoColor` is enabled (default), fields without explicit colors are automatically assigned distinct colors in sequence: blue, cyan, yellow, green, orange, purple, mint, pink. + +```bytegrid +name: WAV Header (Auto Color) +size: 44 +layout: 16 + +fields: + - offset: 0-3 + name: ChunkID + type: char[4] + description: "RIFF magic number" + - offset: 4-7 + name: ChunkSize + type: uint32_t + description: "File size - 8" + - offset: 8-11 + name: Format + type: char[4] + description: "WAVE format" + - offset: 12-15 + name: Subchunk1ID + type: char[4] + description: "fmt subchunk" + - offset: 16-19 + name: Subchunk1Size + type: uint32_t + description: "Size of fmt chunk" + - offset: 20-21 + name: AudioFormat + type: uint16_t + description: "Audio format (1=PCM)" + - offset: 22-23 + name: NumChannels + type: uint16_t + description: "Number of channels" + - offset: 24-27 + name: SampleRate + type: uint32_t + description: "Sample rate (Hz)" + - offset: 28-31 + name: ByteRate + type: uint32_t + description: "Byte rate" +``` + +**Result**: Each field gets a different color automatically! + +--- + +## Example 2: Manual Color Mode + +When `autoColor: false`, only explicitly specified colors are used. Fields without colors default to gray. + +```bytegrid +name: TCP Header (Manual Colors) +size: 20 +layout: 16 +autoColor: false + +fields: + - offset: 0-1 + name: SourcePort + type: uint16_t + color: blue + description: "Source port number" + - offset: 2-3 + name: DestPort + type: uint16_t + color: cyan + description: "Destination port number" + - offset: 4-7 + name: SeqNumber + type: uint32_t + description: "Sequence number (no color = gray)" + - offset: 8-11 + name: AckNumber + type: uint32_t + description: "Acknowledgment number (gray)" + - offset: 12-13 + name: Flags + type: uint16_t + color: yellow + description: "Flags and offset" + - offset: 14-15 + name: WindowSize + type: uint16_t + description: "Window size (gray)" +``` + +**Result**: Only SourcePort (blue), DestPort (cyan), and Flags (yellow) have colors. Others are gray. + +--- + +## Example 3: Mixed Mode (Auto + Manual) + +You can combine automatic colors with explicit colors. Explicit colors always take precedence. + +```bytegrid +name: Custom Packet (Mixed Colors) +size: 32 +layout: 16 + +fields: + - offset: 0-3 + name: MagicNumber + type: uint32_t + color: pink + description: "Custom magic number (explicit pink)" + - offset: 4-7 + name: Version + type: uint32_t + description: "Protocol version (auto: cyan)" + - offset: 8-11 + name: Length + type: uint32_t + description: "Packet length (auto: yellow)" + - offset: 12-15 + name: Checksum + type: uint32_t + color: mint + description: "CRC32 checksum (explicit mint)" + - offset: 16-19 + name: Timestamp + type: uint32_t + description: "Unix timestamp (auto: green)" + - offset: 20-23 + name: SourceID + type: uint32_t + description: "Source identifier (auto: orange)" + - offset: 24-27 + name: DestID + type: uint32_t + description: "Destination ID (auto: purple)" + - offset: 28-31 + name: Payload + type: uint32_t + description: "Payload data (auto: mint again, cycles)" +``` + +**Result**: MagicNumber is pink (explicit), Checksum is mint (explicit), others are auto-assigned. + +--- + +## Example 4: With Padding/Reserved Fields + +Padding and reserved fields are always gray, regardless of `autoColor` setting. + +```bytegrid +name: File Header (With Padding) +size: 24 +layout: 16 + +fields: + - offset: 0-3 + name: Signature + type: char[4] + description: "File signature (auto: blue)" + - offset: 4-7 + name: Version + type: uint32_t + description: "File version (auto: cyan)" + - offset: 8-11 + name: Reserved1 + type: reserved + description: "Reserved for future use (always gray)" + - offset: 12-15 + name: DataOffset + type: uint32_t + description: "Offset to data (auto: green)" + - offset: 16-19 + name: DataSize + type: uint32_t + description: "Size of data (auto: orange)" + - offset: 20-23 + name: Padding + type: padding + description: "Alignment padding (always gray)" +``` + +**Result**: Reserved1 and Padding are gray (special types), others are auto-colored. + +--- + +## Color Cycle + +When you have more than 8 fields, colors cycle back to the beginning: + +1. blue +2. cyan +3. yellow +4. green +5. orange +6. purple +7. mint +8. pink +9. **blue** (cycles back) +10. **cyan** +11. ... + +--- + +## Summary + +- **Default behavior**: `autoColor: true` (automatic colors) +- **Manual mode**: `autoColor: false` (only explicit colors, others gray) +- **Mixed mode**: Combine auto and manual colors +- **Special fields**: `reserved` and `padding` are always gray +- **8 colors**: blue, cyan, yellow, green, orange, purple, mint, pink diff --git a/examples/auto-color-manual.yaml b/examples/auto-color-manual.yaml new file mode 100644 index 0000000..d2a4427 --- /dev/null +++ b/examples/auto-color-manual.yaml @@ -0,0 +1,33 @@ +name: Manual Color Mode Example +size: 24 +layout: 16 +autoColor: false # Disable auto color assignment + +fields: + - offset: 0-3 + name: Header + type: uint32_t + color: blue + description: "Explicit blue color" + - offset: 4-7 + name: NoColor1 + type: uint32_t + description: "No color specified = gray" + - offset: 8-11 + name: Important + type: uint32_t + color: yellow + description: "Explicit yellow to highlight" + - offset: 12-15 + name: NoColor2 + type: uint32_t + description: "No color specified = gray" + - offset: 16-19 + name: Footer + type: uint32_t + color: cyan + description: "Explicit cyan color" + - offset: 20-23 + name: NoColor3 + type: uint32_t + description: "No color specified = gray" diff --git a/examples/auto-color-mixed.yaml b/examples/auto-color-mixed.yaml new file mode 100644 index 0000000..aaef240 --- /dev/null +++ b/examples/auto-color-mixed.yaml @@ -0,0 +1,36 @@ +name: Mixed Color Mode Example +size: 28 +layout: 16 +# autoColor: true (default) - mix auto and manual colors + +fields: + - offset: 0-3 + name: MagicNumber + type: uint32_t + color: pink + description: "Custom color (explicit pink)" + - offset: 4-7 + name: Version + type: uint32_t + description: "Auto-assigned (cyan)" + - offset: 8-11 + name: Length + type: uint32_t + description: "Auto-assigned (yellow)" + - offset: 12-15 + name: Reserved + type: reserved + description: "Always gray for reserved" + - offset: 16-19 + name: Checksum + type: uint32_t + color: mint + description: "Custom color (explicit mint)" + - offset: 20-23 + name: Timestamp + type: uint32_t + description: "Auto-assigned (orange)" + - offset: 24-27 + name: Data + type: uint32_t + description: "Auto-assigned (purple)" diff --git a/packages/core/src/layoutEngine.ts b/packages/core/src/layoutEngine.ts index 5902790..c8bf0b2 100644 --- a/packages/core/src/layoutEngine.ts +++ b/packages/core/src/layoutEngine.ts @@ -17,6 +17,12 @@ export function createLayout(config: ByteGridConfig): LayoutBlock[] { const layoutUnit = config.layoutUnit || 'byte'; const blocks: LayoutBlock[] = []; + // Auto color enabled by default + const autoColor = config.autoColor !== undefined ? config.autoColor : true; + + // Colors for auto-assignment (excluding gray for non-padding fields) + const autoColors: ColorName[] = ['blue', 'cyan', 'yellow', 'green', 'orange', 'purple', 'mint', 'pink']; + // Convert offset to bits for unified processing const toBits = (offset: ReturnType): { start: number; end: number; size: number } => { if (layoutUnit === 'bit') { @@ -31,12 +37,25 @@ export function createLayout(config: ByteGridConfig): LayoutBlock[] { return { start: offset.start, end: offset.end, size: offset.size }; }; - for (const field of config.fields) { + for (let fieldIndex = 0; fieldIndex < config.fields.length; fieldIndex++) { + const field = config.fields[fieldIndex]; const offset = parseOffset(field.offset); const offsetInUnits = toBits(offset); - const color = (field.color || 'gray') as ColorName; const isPadding = field.type === 'reserved' || field.type === 'padding'; + // Determine color + let color: ColorName; + if (field.color) { + // Explicit color specified + color = field.color as ColorName; + } else if (autoColor && !isPadding) { + // Auto-assign color for non-padding fields + color = autoColors[fieldIndex % autoColors.length]; + } else { + // Default to gray (manual mode or padding) + color = 'gray'; + } + // Check if field spans multiple rows const startRow = Math.floor(offsetInUnits.start / layout); const endRow = Math.floor(offsetInUnits.end / layout); diff --git a/packages/core/src/parser.ts b/packages/core/src/parser.ts index 6c549df..e0af535 100644 --- a/packages/core/src/parser.ts +++ b/packages/core/src/parser.ts @@ -242,6 +242,11 @@ export function parse(source: string): ByteGridConfig { ? (obj.colorScheme as 'default' | 'dark' | 'light') : undefined; + // Parse autoColor (optional, default: true) + const autoColor = obj.autoColor !== undefined && typeof obj.autoColor === 'boolean' + ? obj.autoColor + : undefined; + // Parse legendPosition (optional) const legendPosition = obj.legendPosition && typeof obj.legendPosition === 'string' ? (obj.legendPosition as 'right' | 'left' | 'bottom' | 'none') @@ -266,6 +271,7 @@ export function parse(source: string): ByteGridConfig { layout, layoutUnit: finalLayoutUnit, colorScheme, + autoColor, legendPosition, legendColumns, showFooter, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4d87c5c..080ab53 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -91,6 +91,7 @@ export interface ByteGridConfig { layout?: number; // Units per row (default: 16), unit depends on layoutUnit layoutUnit?: LayoutUnit; // 'byte' (default) or 'bit' colorScheme?: ColorScheme; + autoColor?: boolean; // Auto-assign colors to fields (default: true) legendPosition?: LegendPosition; // 'right' (default), 'left', 'bottom', or 'none' legendColumns?: number; // Number of columns in legend (default: 1) showFooter?: boolean; // Show footer with total size and layout info (default: true) diff --git a/packages/core/tests/layoutEngine.test.ts b/packages/core/tests/layoutEngine.test.ts index b915ce8..541f5cf 100644 --- a/packages/core/tests/layoutEngine.test.ts +++ b/packages/core/tests/layoutEngine.test.ts @@ -109,10 +109,11 @@ describe('LayoutEngine', () => { expect(blocks[2].offsetEnd).toBe(11); }); - it('should default to gray color for fields without color', () => { + it('should default to gray color for fields without color when autoColor is false', () => { const config: ByteGridConfig = { name: 'Test', size: 8, + autoColor: false, fields: [ { offset: '0-3', name: 'Field1', type: 'uint32_t' }, ], @@ -384,4 +385,124 @@ describe('LayoutEngine', () => { expect(blocks[1].span).toBe(4); }); }); + + describe('autoColor feature', () => { + it('should auto-assign colors when autoColor is true (default)', () => { + const config: ByteGridConfig = { + name: 'Test', + size: 16, + layout: 16, + fields: [ + { offset: '0-3', name: 'Field1', type: 'uint32_t' }, // No color specified + { offset: '4-7', name: 'Field2', type: 'uint32_t' }, // No color specified + { offset: '8-11', name: 'Field3', type: 'uint32_t' }, // No color specified + ], + }; + + const blocks = createLayout(config); + + expect(blocks).toHaveLength(3); + // Should auto-assign: blue, cyan, yellow + expect(blocks[0].color).toBe('blue'); + expect(blocks[1].color).toBe('cyan'); + expect(blocks[2].color).toBe('yellow'); + }); + + it('should use gray when autoColor is false and no color specified', () => { + const config: ByteGridConfig = { + name: 'Test', + size: 16, + layout: 16, + autoColor: false, + fields: [ + { offset: '0-3', name: 'Field1', type: 'uint32_t' }, // No color specified + { offset: '4-7', name: 'Field2', type: 'uint32_t' }, // No color specified + ], + }; + + const blocks = createLayout(config); + + expect(blocks).toHaveLength(2); + // Should default to gray + expect(blocks[0].color).toBe('gray'); + expect(blocks[1].color).toBe('gray'); + }); + + it('should respect explicit colors even when autoColor is true', () => { + const config: ByteGridConfig = { + name: 'Test', + size: 16, + layout: 16, + autoColor: true, + fields: [ + { offset: '0-3', name: 'Field1', type: 'uint32_t', color: 'pink' }, // Explicit color + { offset: '4-7', name: 'Field2', type: 'uint32_t' }, // Auto-assign + { offset: '8-11', name: 'Field3', type: 'uint32_t', color: 'mint' }, // Explicit color + ], + }; + + const blocks = createLayout(config); + + expect(blocks).toHaveLength(3); + expect(blocks[0].color).toBe('pink'); // Explicit + expect(blocks[1].color).toBe('cyan'); // Auto (fieldIndex=1 -> cyan) + expect(blocks[2].color).toBe('mint'); // Explicit + }); + + it('should use gray for padding/reserved fields regardless of autoColor', () => { + const config: ByteGridConfig = { + name: 'Test', + size: 16, + layout: 16, + autoColor: true, + fields: [ + { offset: '0-3', name: 'Field1', type: 'uint32_t' }, // Auto-assign + { offset: '4-7', name: 'Reserved', type: 'reserved' }, // Should be gray + { offset: '8-11', name: 'Field2', type: 'uint32_t' }, // Auto-assign + ], + }; + + const blocks = createLayout(config); + + expect(blocks).toHaveLength(3); + expect(blocks[0].color).toBe('blue'); // Auto + expect(blocks[1].color).toBe('gray'); // Reserved + expect(blocks[2].color).toBe('yellow'); // Auto (fieldIndex=2 -> yellow) + }); + + it('should cycle through colors for many fields', () => { + const config: ByteGridConfig = { + name: 'Test', + size: 36, + layout: 16, + autoColor: true, + fields: [ + { offset: '0-3', name: 'F1', type: 'uint32_t' }, + { offset: '4-7', name: 'F2', type: 'uint32_t' }, + { offset: '8-11', name: 'F3', type: 'uint32_t' }, + { offset: '12-15', name: 'F4', type: 'uint32_t' }, + { offset: '16-19', name: 'F5', type: 'uint32_t' }, + { offset: '20-23', name: 'F6', type: 'uint32_t' }, + { offset: '24-27', name: 'F7', type: 'uint32_t' }, + { offset: '28-31', name: 'F8', type: 'uint32_t' }, + { offset: '32-35', name: 'F9', type: 'uint32_t' }, // Should cycle back to blue + ], + }; + + const blocks = createLayout(config); + + expect(blocks).toHaveLength(9); + // First 8 colors + expect(blocks[0].color).toBe('blue'); + expect(blocks[1].color).toBe('cyan'); + expect(blocks[2].color).toBe('yellow'); + expect(blocks[3].color).toBe('green'); + expect(blocks[4].color).toBe('orange'); + expect(blocks[5].color).toBe('purple'); + expect(blocks[6].color).toBe('mint'); + expect(blocks[7].color).toBe('pink'); + // Cycle back + expect(blocks[8].color).toBe('blue'); + }); + }); }); diff --git a/packages/core/tests/parser.test.ts b/packages/core/tests/parser.test.ts index b4aa6e6..0fe4ea0 100644 --- a/packages/core/tests/parser.test.ts +++ b/packages/core/tests/parser.test.ts @@ -740,5 +740,55 @@ fields: expect(result.colorScheme).toBeUndefined(); }); }); + + // autoColor tests + describe('autoColor', () => { + it('should parse autoColor: true', () => { + const yaml = ` +name: Test +size: 16 +autoColor: true +fields: + - offset: 0-3 + name: Field1 + type: uint32_t +`; + + const result = parse(yaml); + + expect(result.autoColor).toBe(true); + }); + + it('should parse autoColor: false', () => { + const yaml = ` +name: Test +size: 16 +autoColor: false +fields: + - offset: 0-3 + name: Field1 + type: uint32_t +`; + + const result = parse(yaml); + + expect(result.autoColor).toBe(false); + }); + + it('should return undefined when autoColor is not specified', () => { + const yaml = ` +name: Test +size: 16 +fields: + - offset: 0-3 + name: Field1 + type: uint32_t +`; + + const result = parse(yaml); + + expect(result.autoColor).toBeUndefined(); + }); + }); }); });