Task 081: .CDR File Format

Task 081: .CDR File Format

1. List of All Properties of the .CDR File Format Intrinsic to Its Structure

The .CDR (CorelDRAW) file format is a proprietary vector graphics format developed by Corel Corporation. It is not a traditional file system but a structured binary format for storing vector drawings, including objects, styles, and metadata. There is no official public specification from Corel, but reverse-engineered details (e.g., from Kaitai Struct and other analyses) reveal its intrinsic structural properties. The format evolves across versions, with significant changes in organization.

Key properties intrinsic to the format's structure include:

  • Magic Number/Signature: Always starts with the RIFF (Resource Interchange File Format) signature 0x52494646 (ASCII "RIFF") in little-endian byte order. This is the top-level envelope for versions up to X3 (13.0).
  • Chunk Size: A 4-byte little-endian unsigned integer immediately following the signature, indicating the total size of the RIFF chunk data (excluding the 8-byte RIFF header). This defines the overall file payload length.
  • RIFF Type/Format Identifier: A 4-byte ASCII string starting with "CDR" followed by a version indicator (e.g., "CDR5" for version 5, "CDRv" where v is a digit or letter representing the CorelDRAW version, such as 3–13 for pre-X4). This identifies the format version and compatibility.
  • Endianness: Little-endian for all multi-byte numeric values (e.g., sizes, coordinates, colors).
  • Compression:
  • Older versions (up to X3): Uncompressed or internally compressed chunks (e.g., RLE or LZW in specific sub-chunks).
  • Versions X4 (14.0) and later: The entire file is a ZIP archive. Intrinsic ZIP properties include central directory, local file headers, and compressed streams (Deflate algorithm). The actual CDR data is in entries like content/riffData.cdr (X4/X5) or content/root.dat + content/data/*.dat (X6+).
  • Chunk-Based Organization: For non-ZIP (pre-X4) or inner RIFF data:
  • Sub-chunks prefixed with 4-byte chunk ID (ASCII, e.g., "LIST", "PARM", "FONT"), 4-byte little-endian size, and optional sub-type.
  • Common chunk types: "COLR" (color palette), "PAGE" (page setup), "LAYR" (layers), "OBJD" (object definitions like paths, rectangles, ellipses, text, bitmaps), "STYL" (styles for fills/outlines/gradients), "TXST" (text styles), "TRNF" (transformation matrices for objects).
  • Object Hierarchy: Vector objects stored as hierarchical lists:
  • Types: Paths (bezier curves with control points), polygons, rectangles (with width/height/position), ellipses (radii/position), text (fonts, strings, kerning), embedded images (bitmaps, often JPEG/PNG compressed).
  • Geometry: Floating-point coordinates (double-precision for positions, single for some params), edge flags (e.g., open/closed paths).
  • Properties per object: Layer ID, visibility, lock status, blend modes.
  • Style Properties:
  • Fills: Solid color (RGB/CMYK values), gradients (linear/radial with color stops), patterns (tiled bitmaps).
  • Outlines: Width (float), color, dash patterns (arrays of lengths), arrowheads (styles/shapes).
  • Effects: Shadows, transparencies (alpha values 0–1).
  • Metadata Properties: Document info (author, creation date as timestamps), page count, page sizes (width/height in points), resolution (DPI, default 72), color mode (RGB/CMYK).
  • Version-Specific Structures:
  • Pre-X4 (up to 13): Flat RIFF chunks.
  • X4/X5: ZIP with single content/riffData.cdr entry containing RIFF data.
  • X6+ (16+): ZIP with content/root.dat (index/header) referencing external compressed .dat files in content/data/ for payloads (streams of chunks).
  • File Extensions and Variants: Core .CDR is the main format; related intrinsics include .CDX (compressed .CDR), .CDT (templates with default styles).
  • Numerical Precision: Coordinates and sizes use 64-bit doubles for accuracy; colors as 32-bit ARGB or CMYK.
  • Error Handling Intrinsics: Checksums or CRC in ZIP parts (for X4+); no built-in validation for older versions, leading to potential corruption risks.

These properties form the core "file system" of .CDR, organizing data into parseable chunks/objects rather than a traditional FS like FAT. Full decoding requires version detection and handling ZIP/RIFF duality.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .CDR Property Dump

This is a self-contained HTML snippet with embedded JavaScript for Ghost CMS (or any blog). Paste it into a Ghost post via HTML card. It allows drag-and-drop of a .CDR file, detects if it's ZIP or RIFF, extracts basic intrinsic properties (signature, version, size, ZIP entries if applicable), and dumps them to the screen. Full object decoding is beyond client-side JS scope without a full parser library; this focuses on header/chunk intrinsics.

Drag and drop a .CDR file here to analyze its properties.

4. Python Class for Opening, Decoding, Reading, Writing, and Printing .CDR Properties

This Python class uses built-in libraries (zipfile for ZIP detection, struct for binary parsing). It reads intrinsic properties (header, version, basic structure). Writing is implemented for basic RIFF header creation (full write requires reverse-engineering full chunks, which is complex; this stubs object write). Prints to console.

import struct
import zipfile
from io import BytesIO

class CDRDecoder:
    def __init__(self, filepath):
        self.filepath = filepath
        with open(filepath, 'rb') as f:
            self.data = f.read()
        self.properties = {}

    def read_properties(self):
        uint8 = self.data
        # ZIP check (X4+)
        if uint8[:4] == b'PK\x03\x04':
            self.properties['structure'] = 'ZIP Archive (X4+)'
            self.properties['compression'] = 'Deflate'
            self.properties['endianness'] = 'Little-endian'
            with zipfile.ZipFile(BytesIO(uint8)) as zf:
                entries = [e.filename for e in zf.infolist()]
                self.properties['zip_entries'] = entries  # e.g., ['content/riffData.cdr']
                self.properties['version_note'] = 'Inferred from ZIP; inner RIFF for X4/X5, root.dat for X6+'
        else:
            # RIFF check
            if uint8[:4] == b'RIFF':
                self.properties['structure'] = 'RIFF Chunk (pre-X4)'
                size = struct.unpack('<I', uint8[4:8])[0]
                self.properties['chunk_size'] = size
                riff_type = uint8[8:12].decode('ascii', errors='ignore')
                self.properties['version'] = riff_type  # e.g., 'CDR5'
                self.properties['magic'] = 'RIFF'
                self.properties['endianness'] = 'Little-endian'
                # Stub for chunks: would parse sub-chunks here
                self.properties['chunks'] = 'COLR, PAGE, LAYR, OBJD (objects: paths/rects/etc.), STYL (fills/outlines)'
                self.properties['objects'] = 'Vectors: paths (beziers), rects (w/h), ellipses, text, images'
                self.properties['styles'] = 'Fills (RGB/CMYK, gradients), outlines (width/dash), transforms (matrices)'
            else:
                self.properties['error'] = 'Invalid CDR'
        self.print_properties()

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write_properties(self, output_path):
        # Basic write: Create minimal RIFF CDR5 file (stub; full write needs chunk data)
        header = b'RIFF' + struct.pack('<I', 12) + b'CDR5'  # Minimal size 12 for empty
        # Stub object chunk (e.g., empty page)
        chunk_id = b'LIST'
        chunk_size = 4
        sub_type = b'PAGE'
        chunk_data = chunk_id + struct.pack('<I', chunk_size) + sub_type
        full_data = header + chunk_data
        with open(output_path, 'wb') as f:
            f.write(full_data)
        print(f"Written basic .CDR stub to {output_path}")
        # For ZIP write: Use zipfile to create archive with inner RIFF
        # (Omitted for brevity; similar to read but reverse)

# Usage
decoder = CDRDecoder('sample.cdr')
decoder.read_properties()
decoder.write_properties('output.cdr')

5. Java Class for Opening, Decoding, Reading, Writing, and Printing .CDR Properties

This Java class uses java.util.zip for ZIP and ByteBuffer for binary parsing. Reads header properties; writing stubs a basic RIFF file. Prints to console via System.out.

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.ArrayList;
import java.util.List;

public class CDRDecoder {
    private String filepath;
    private byte[] data;
    private java.util.Map<String, Object> properties = new java.util.HashMap<>();

    public CDRDecoder(String filepath) throws IOException {
        this.filepath = filepath;
        FileInputStream fis = new FileInputStream(filepath);
        data = fis.readAllBytes();
        fis.close();
    }

    public void readProperties() throws IOException {
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        // ZIP check (X4+)
        if (data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04) {
            properties.put("structure", "ZIP Archive (X4+)");
            properties.put("compression", "Deflate");
            properties.put("endianness", "Little-endian");
            List<String> entries = new ArrayList<>();
            try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(data))) {
                ZipEntry entry;
                while ((entry = zis.getNextEntry()) != null) {
                    entries.add(entry.getName());
                }
            }
            properties.put("zip_entries", entries);
            properties.put("version_note", "Inferred from ZIP; inner RIFF for X4/X5, root.dat for X6+");
        } else {
            // RIFF check
            if (bb.getInt(0) == 0x46464952) {  // RIFF little-endian
                properties.put("structure", "RIFF Chunk (pre-X4)");
                int size = bb.getInt(4);
                properties.put("chunk_size", size);
                byte[] typeBytes = new byte[4];
                bb.position(8);
                bb.get(typeBytes);
                String riffType = new String(typeBytes).trim();
                properties.put("version", riffType);  // e.g., 'CDR5'
                properties.put("magic", "RIFF");
                properties.put("endianness", "Little-endian");
                properties.put("chunks", "COLR, PAGE, LAYR, OBJD (objects: paths/rects/etc.), STYL (fills/outlines)");
                properties.put("objects", "Vectors: paths (beziers), rects (w/h), ellipses, text, images");
                properties.put("styles", "Fills (RGB/CMYK, gradients), outlines (width/dash), transforms (matrices)");
            } else {
                properties.put("error", "Invalid CDR");
            }
        }
        printProperties();
    }

    private void printProperties() {
        for (java.util.Map.Entry<String, Object> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void writeProperties(String outputPath) throws IOException {
        // Basic RIFF write stub
        ByteBuffer bb = ByteBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN);
        bb.put("RIFF".getBytes());
        bb.putInt(12);  // Minimal size
        bb.put("CDR5".getBytes());
        // Stub chunk
        bb.put("LIST".getBytes());
        bb.putInt(4);
        bb.put("PAGE".getBytes());
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            fos.write(bb.array());
        }
        System.out.println("Written basic .CDR stub to " + outputPath);
        // ZIP write omitted for brevity (use ZipOutputStream similarly)
    }

    // Usage: CDRDecoder decoder = new CDRDecoder("sample.cdr"); decoder.readProperties(); decoder.writeProperties("output.cdr");
}

6. JavaScript Class for Opening, Decoding, Reading, Writing, and Printing .CDR Properties

This Node.js-compatible JS class (use fs module). Reads via Buffer; writes basic RIFF. Prints to console. For browser, adapt with FileReader. Focuses on header; full decode needs external lib like jszip.

const fs = require('fs');

class CDRDecoder {
  constructor(filepath) {
    this.filepath = filepath;
    this.data = fs.readFileSync(filepath);
    this.properties = {};
  }

  readProperties() {
    const uint8 = new Uint8Array(this.data);
    // ZIP check
    if (uint8[0] === 0x50 && uint8[1] === 0x4B && uint8[2] === 0x03 && uint8[3] === 0x04) {
      this.properties.structure = 'ZIP Archive (X4+)';
      this.properties.compression = 'Deflate';
      this.properties.endianness = 'Little-endian';
      // Stub ZIP entries (full requires jszip lib)
      this.properties.zip_entries = 'Includes content/riffData.cdr or root.dat + data/*.dat';
      this.properties.version_note = 'Inferred from ZIP';
    } else {
      // RIFF check
      if (uint8.slice(0,4).toString() === String.fromCharCode(0x52,0x49,0x46,0x46)) {
        this.properties.structure = 'RIFF Chunk (pre-X4)';
        const size = (uint8[7] << 0) | (uint8[6] << 8) | (uint8[5] << 16) | (uint8[4] << 24);
        this.properties.chunk_size = size;
        const riffType = String.fromCharCode(...uint8.slice(8,12));
        this.properties.version = riffType;
        this.properties.magic = 'RIFF';
        this.properties.endianness = 'Little-endian';
        this.properties.chunks = 'COLR, PAGE, LAYR, OBJD, STYL';
        this.properties.objects = 'Paths, rects, ellipses, text, images';
        this.properties.styles = 'Fills/gradients, outlines, transforms';
      } else {
        this.properties.error = 'Invalid CDR';
      }
    }
    this.printProperties();
  }

  printProperties() {
    Object.entries(this.properties).forEach(([key, value]) => console.log(`${key}: ${value}`));
  }

  writeProperties(outputPath) {
    // Basic RIFF stub
    const header = Buffer.from([0x52,0x49,0x46,0x46, 0x0C,0x00,0x00,0x00, 0x43,0x44,0x52,0x35]);  // RIFF + size 12 + CDR5
    const chunk = Buffer.from([0x4C,0x49,0x53,0x54, 0x04,0x00,0x00,0x00, 0x50,0x41,0x47,0x45]);  // LIST PAGE stub
    const full = Buffer.concat([header, chunk]);
    fs.writeFileSync(outputPath, full);
    console.log(`Written basic .CDR stub to ${outputPath}`);
  }
}

// Usage
const decoder = new CDRDecoder('sample.cdr');
decoder.readProperties();
decoder.writeProperties('output.cdr');

7. C Class for Opening, Decoding, Reading, Writing, and Printing .CDR Properties

This C class (struct-based) uses stdio for file I/O and manual binary parsing. Reads header; writes basic RIFF. Compile with gcc. Prints to stdout. Limited to intrinsics due to no ZIP lib (use libzip for full).

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    char structure[32];
    uint32_t chunk_size;
    char version[8];
    char magic[8];
    char endianness[16];
    // Add more as needed
} CDRProperties;

typedef struct {
    FILE *file;
    uint8_t *data;
    size_t size;
    CDRProperties props;
} CDRDecoder;

void read_properties(CDRDecoder *decoder) {
    uint8_t *data = decoder->data;
    // ZIP check (simplified; full needs libzip)
    if (data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04) {
        strcpy(decoder->props.structure, "ZIP Archive (X4+)");
        strcpy(decoder->props.compression, "Deflate");  // Note: compression added
        strcpy(decoder->props.endianness, "Little-endian");
        strcpy(decoder->props.version, "Inferred from ZIP");
        // Entries stub
        printf("ZIP entries: content/riffData.cdr or root.dat + data/*.dat\n");
    } else {
        // RIFF check
        if (data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46) {
            strcpy(decoder->props.structure, "RIFF Chunk (pre-X4)");
            decoder->props.chunk_size = data[7] | (data[6] << 8) | (data[5] << 16) | (data[4] << 24);
            memcpy(decoder->props.version, &data[8], 4);
            decoder->props.version[4] = '\0';
            strcpy(decoder->props.magic, "RIFF");
            strcpy(decoder->props.endianness, "Little-endian");
            printf("Chunks: COLR, PAGE, LAYR, OBJD, STYL\n");
            printf("Objects: Paths, rects, ellipses, text, images\n");
            printf("Styles: Fills/gradients, outlines, transforms\n");
        } else {
            strcpy(decoder->props.structure, "Invalid CDR");
        }
    }
    // Print
    printf("structure: %s\n", decoder->props.structure);
    printf("chunk_size: %u\n", decoder->props.chunk_size);
    printf("version: %s\n", decoder->props.version);
    printf("magic: %s\n", decoder->props.magic);
    printf("endianness: %s\n", decoder->props.endianness);
}

void write_properties(CDRDecoder *decoder, const char *output_path) {
    // Basic RIFF stub
    uint8_t header[12] = {0x52,0x49,0x46,0x46, 0x0C,0x00,0x00,0x00, 0x43,0x44,0x52,0x35};
    uint8_t chunk[12] = {0x4C,0x49,0x53,0x54, 0x04,0x00,0x00,0x00, 0x50,0x41,0x47,0x45};
    FILE *out = fopen(output_path, "wb");
    if (out) {
        fwrite(header, 1, 12, out);
        fwrite(chunk, 1, 12, out);
        fclose(out);
        printf("Written basic .CDR stub to %s\n", output_path);
    }
}

int main(int argc, char **argv) {
    if (argc < 2) return 1;
    CDRDecoder decoder = {0};
    decoder.file = fopen(argv[1], "rb");
    if (!decoder.file) return 1;
    fseek(decoder.file, 0, SEEK_END);
    decoder.size = ftell(decoder.file);
    fseek(decoder.file, 0, SEEK_SET);
    decoder.data = malloc(decoder.size);
    fread(decoder.data, 1, decoder.size, decoder.file);
    fclose(decoder.file);
    
    read_properties(&decoder);
    write_properties(&decoder, "output.cdr");
    free(decoder.data);
    return 0;
}