Task 082: .CDR File Format

Task 082: .CDR File Format

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

The .CDR file format is proprietary to CorelDRAW and has evolved over versions, with no official public specification. Based on reverse-engineered documentation from sources like the Photopea CDR specification, Kaitai Struct definitions, and RIFF-based structure analyses, the format's intrinsic properties are tied to its binary structure (for versions 3–13/X3) or ZIP container (for versions 14/X4 and later). "Intrinsic to its file system" refers to core structural elements like headers, chunks, metadata, and data organization that define the file's integrity and content without external dependencies.

For pre-X4 versions (RIFF-based binary):

  • Signature: 4 bytes "RIFF" (ASCII).
  • Chunk Size: 4 bytes (uint32 LE), total file size minus 8 bytes.
  • Form Type: 4 bytes "CDR " (ASCII), sometimes "CDRv" with version indicator.
  • Version: 2 bytes (uint16 LE) in the "vrsn" chunk, e.g., 500 for version 5, up to 1300 for X3.
  • Thumbnail (DISP chunk):
  • Width: 4 bytes (uint32 LE).
  • Height: 4 bytes (uint32 LE).
  • Palette: 1024 bytes (256 colors × 4 bytes RGBA).
  • Pixel data: Width × Height bytes (palette indices).
  • Metadata Chunks (LIST INFO): Variable size, includes author, creation date, modification date, software version, and description (ASCII strings).
  • Compression Chunks (LIST cmpr): Two parts with PNG-like compression ("CPng").
  • Part 1 compressed size: 4 bytes (uint32 LE).
  • Part 1 uncompressed size: 4 bytes (uint32 LE).
  • Part 2 compressed size: 4 bytes (uint32 LE).
  • Part 2 uncompressed size: 4 bytes (uint32 LE).
  • Compression flags: 4 bytes (0x01000400).
  • Style List (LIST stlt): Variable size, defines global styles including color palette (RGB values), line styles (width, dash patterns), fill types (solid, gradient, pattern).
  • Page List (LIST page or similar): Variable size, includes:
  • Number of pages: Implicit from subchunks or 2 bytes (uint16).
  • Page dimensions: 8 bytes (doubl LE for width/height in points).
  • Page orientation: 1 byte (0 for portrait, 1 for landscape).
  • Margins: 8 bytes (4 doubles for left/right/top/bottom).
  • Object List (in main data chunks): Variable size, array of vector objects with:
  • Object count: 4 bytes (uint32 LE).
  • Object types: Enums like line (1), curve (2), rectangle (3), ellipse (4), polygon (5), text (6), bitmap (7), group (8).
  • Object properties: Position (2 doubles for x/y), size (2 doubles for width/height or bounding box), transformation matrix (6 doubles for affine transform), fill style ID, stroke style ID, layer ID.
  • Color Table: Variable, list of colors (4 bytes per color: CMYK or RGB + alpha).
  • Font List: Variable, list of embedded or referenced fonts (name string + ID).
  • Summary Chunk (sumi): 60 bytes, includes checksums or summary hashes for integrity.
  • Padding: 1 byte if chunk size is odd, for alignment.

For X4+ versions (ZIP-based):

  • Container Signature: "PK\003\004" (ZIP header).
  • Mimetype: "application/vnd.corel-draw" in mimetype file.
  • Root Entry: "content/root.dat" (RIFF-like structure with version).
  • Data Streams: External files in "content/data/*.dat" for large objects (sizes, offsets).
  • Embedded Files: Thumbnails, images (BMP/JPG), fonts as separate ZIP entries.
  • Version: In root.dat header, e.g., 1400 for X4.

Additional intrinsic properties across versions:

  • Coordinate System: Fixed point (sint16/1000 for <v6, sint32/254000 for >=v6) or double precision.
  • Endianness: Little-endian for all numeric fields.
  • Compression: RLE or PNG-like for data parts; ZIP for X4+.
  • Object Hierarchy: Layers, groups, with IDs for referencing styles/objects.
  • Checksum/Integrity: Optional MD5-like in sumi chunk or ZIP CRC.
  • File Extension: .cdr (native), but variants like .cdt (template), .cdx (compressed).

These properties ensure the file's self-contained nature for vector graphics, pages, and metadata.

3. Ghost Blog Embedded HTML JavaScript for Drag n Drop .CDR File

This is an embeddable HTML snippet for Ghost CMS (or any blog) with JavaScript for drag-and-drop. It reads the file as ArrayBuffer, parses basic RIFF header properties (signature, size, version, thumbnail dimensions), and dumps them to the screen in a

. For full parsing, it uses a simple binary reader. Paste this into a Ghost post's HTML card.

Drag and drop a .CDR file here to dump properties.

4. Python Class for .CDR File

This class uses struct to parse basic RIFF properties (header, version, thumbnail). For write, it creates a basic skeleton file with header (not full content). Run with python class.py input.cdr to print properties. Requires no external libs beyond standard library. Full decode/write for objects requires libcdr or full spec implementation.

import struct
import sys
import os

class CDRParser:
    def __init__(self, filename=None):
        self.filename = filename
        self.properties = {}
        if filename:
            self.read(filename)

    def read(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        self.file_size = len(data)
        offset = 0
        # RIFF header
        sig = struct.unpack_from('<4s', data, offset)[0].decode('ascii')
        self.properties['signature'] = sig
        offset += 4
        chunk_size = struct.unpack_from('<I', data, offset)[0]
        self.properties['chunk_size'] = chunk_size
        offset += 4
        form_type = struct.unpack_from('<4s', data, offset)[0].decode('ascii')
        self.properties['form_type'] = form_type
        offset += 4
        # Find vrsn chunk
        while offset < min(200, self.file_size):
            chunk_id = struct.unpack_from('<4s', data, offset)[0].decode('ascii')
            chunk_len = struct.unpack_from('<I', data, offset + 4)[0]
            if chunk_id == 'vrsn':
                version = struct.unpack_from('<H', data, offset + 8)[0]
                self.properties['version'] = version
                break
            offset += 8 + chunk_len + (chunk_len % 2)
        # Find DISP for thumbnail
        while offset < min(500, self.file_size):
            chunk_id = struct.unpack_from('<4s', data, offset)[0].decode('ascii')
            chunk_len = struct.unpack_from('<I', data, offset + 4)[0]
            if chunk_id == 'DISP':
                # Approximate offsets for thumbnail (after 8 unknown + 4? wait, per spec ~ offset+12/16
                width = struct.unpack_from('<I', data, offset + 12)[0]
                height = struct.unpack_from('<I', data, offset + 16)[0]
                self.properties['thumbnail_width'] = width
                self.properties['thumbnail_height'] = height
                break
            offset += 8 + chunk_len + (chunk_len % 2)
        self.properties['file_size'] = self.file_size
        self.properties['is_riff'] = sig == 'RIFF'
        # For ZIP (X4+), check if starts with PK
        if data[:4] == b'PK\x03\x04':
            self.properties['container_type'] = 'ZIP'
        # Note: Compressed parts, objects not fully parsed here.

    def write(self, output_filename, version=500, thumbnail_width=100, thumbnail_height=100):
        # Basic skeleton write: RIFF header + vrsn + DISP stub
        file_size = 36 + 1024  # Approximate minimal
        data = b'RIFF' + struct.pack('<I', file_size - 8) + b'CDR '
        # vrsn chunk
        data += b'vrsn' + struct.pack('<I', 2) + struct.pack('<H', version) + b'\x00'  # Pad
        # DISP chunk stub
        disp_content = b'\x00' * 8 + struct.pack('<II', thumbnail_width, thumbnail_height) + b'\x00' * 28 + b'\x00' * 1024  # Palette stub
        data += b'DISP' + struct.pack('<I', len(disp_content)) + disp_content
        if len(disp_content) % 2:
            data += b'\x00'
        # Adjust file_size
        actual_size = len(data)
        # Rewrite header (simplified, not adjusting for full)
        with open(output_filename, 'wb') as f:
            f.write(data)
        print(f"Basic skeleton written to {output_filename}. Full write requires full spec.")

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

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python cdr_parser.py <input.cdr> [output.cdr]")
        sys.exit(1)
    parser = CDRParser(sys.argv[1])
    parser.print_properties()
    if len(sys.argv) > 2:
        parser.write(sys.argv[2])

5. Java Class for .CDR File

This Java class uses ByteBuffer and FileChannel for binary parsing (header, version, thumbnail). Compile with javac CDRParser.java, run with java CDRParser input.cdr to print. Write creates basic skeleton. Full decode requires external lib like Apache POI or custom.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;

class CDRParser {
    private String filename;
    private java.util.Map<String, Object> properties = new java.util.HashMap<>();

    public CDRParser(String filename) {
        this.filename = filename;
        if (filename != null) {
            read(filename);
        }
    }

    public void read(String filename) {
        try (FileInputStream fis = new FileInputStream(filename);
             FileChannel channel = fis.getChannel()) {
            MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            long fileSize = channel.size();
            int offset = 0;
            // RIFF header
            byte[] sigBytes = new byte[4];
            buffer.get(sigBytes, offset, 4);
            String sig = new String(sigBytes, java.nio.charset.StandardCharsets.US_ASCII);
            properties.put("signature", sig);
            offset += 4;
            int chunkSize = buffer.getInt(offset);
            properties.put("chunk_size", chunkSize);
            offset += 4;
            byte[] formBytes = new byte[4];
            buffer.get(formBytes, offset, 4);
            String formType = new String(formBytes, java.nio.charset.StandardCharsets.US_ASCII);
            properties.put("form_type", formType);
            offset += 4;
            // Find vrsn
            while (offset < Math.min(200, fileSize)) {
                byte[] chunkIdBytes = new byte[4];
                buffer.get(chunkIdBytes, offset, 4);
                String chunkId = new String(chunkIdBytes, java.nio.charset.StandardCharsets.US_ASCII);
                int chunkLen = buffer.getInt(offset + 4);
                if ("vrsn".equals(chunkId)) {
                    short version = buffer.getShort(offset + 8);
                    properties.put("version", version);
                    break;
                }
                offset += 8 + chunkLen + (chunkLen % 2);
            }
            // Find DISP
            while (offset < Math.min(500, fileSize)) {
                byte[] chunkIdBytes = new byte[4];
                buffer.get(chunkIdBytes, offset, 4);
                String chunkId = new String(chunkIdBytes, java.nio.charset.StandardCharsets.US_ASCII);
                int chunkLen = buffer.getInt(offset + 4);
                if ("DISP".equals(chunkId)) {
                    int width = buffer.getInt(offset + 12);
                    int height = buffer.getInt(offset + 16);
                    properties.put("thumbnail_width", width);
                    properties.put("thumbnail_height", height);
                    break;
                }
                offset += 8 + chunkLen + (chunkLen % 2);
            }
            properties.put("file_size", fileSize);
            properties.put("is_riff", "RIFF".equals(sig));
            // ZIP check
            if (buffer.get(0) == 'P' && buffer.get(1) == 'K' && buffer.get(2) == 3 && buffer.get(3) == 4) {
                properties.put("container_type", "ZIP");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void write(String outputFilename, short version, int thumbnailWidth, int thumbnailHeight) {
        // Basic skeleton
        byte[] data = "RIFF".getBytes(java.nio.charset.StandardCharsets.US_ASCII);
        int fileSize = 36 + 1024; // Approx
        data = concat(data, ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(fileSize - 8).array());
        data = concat(data, "CDR ".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
        // vrsn
        data = concat(data, "vrsn".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
        data = concat(data, ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(2).array());
        data = concat(data, ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(version).array());
        data = concat(data, new byte[] {0}); // Pad
        // DISP stub
        byte[] dispContent = new byte[8 + 4 + 4 + 28 + 1024];
        ByteBuffer dispBuf = ByteBuffer.wrap(dispContent).order(ByteOrder.LITTLE_ENDIAN);
        dispBuf.putInt(12, thumbnailWidth);
        dispBuf.putInt(16, thumbnailHeight);
        data = concat(data, "DISP".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
        data = concat(data, ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(dispContent.length).array());
        data = concat(data, dispContent);
        if (dispContent.length % 2 != 0) {
            data = concat(data, new byte[] {0});
        }
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Basic skeleton written to " + outputFilename);
    }

    private byte[] concat(byte[] a, byte[] b) {
        byte[] c = new byte[a.length + b.length];
        System.arraycopy(a, 0, c, 0, a.length);
        System.arraycopy(b, 0, c, a.length, b.length);
        return c;
    }

    public void printProperties() {
        properties.forEach((k, v) -> System.out.println(k + ": " + v));
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java CDRParser <input.cdr> [output.cdr]");
            return;
        }
        CDRParser parser = new CDRParser(args[0]);
        parser.printProperties();
        if (args.length > 1) {
            parser.write(args[1], (short) 500, 100, 100);
        }
    }
}

6. JavaScript Class for .CDR File

This Node.js class (use with node cdr_parser.js input.cdr) uses fs to read binary. For browser, adapt with FileReader. Parses basic properties. Write creates skeleton file. Install no deps.

const fs = require('fs');

class CDRParser {
  constructor(filename = null) {
    this.filename = filename;
    this.properties = {};
    if (filename) {
      this.read(filename);
    }
  }

  read(filename) {
    const data = fs.readFileSync(filename);
    this.fileSize = data.length;
    let offset = 0;
    // RIFF header
    const sig = data.toString('ascii', offset, offset + 4);
    this.properties.signature = sig;
    offset += 4;
    const chunkSize = data.readUInt32LE(offset);
    this.properties.chunk_size = chunkSize;
    offset += 4;
    const formType = data.toString('ascii', offset, offset + 4);
    this.properties.form_type = formType;
    offset += 4;
    // Find vrsn
    while (offset < Math.min(200, this.fileSize)) {
      const chunkId = data.toString('ascii', offset, offset + 4);
      const chunkLen = data.readUInt32LE(offset + 4);
      if (chunkId === 'vrsn') {
        const version = data.readUInt16LE(offset + 8);
        this.properties.version = version;
        break;
      }
      offset += 8 + chunkLen + (chunkLen % 2);
    }
    // Find DISP
    while (offset < Math.min(500, this.fileSize)) {
      const chunkId = data.toString('ascii', offset, offset + 4);
      const chunkLen = data.readUInt32LE(offset + 4);
      if (chunkId === 'DISP') {
        const width = data.readUInt32LE(offset + 12);
        const height = data.readUInt32LE(offset + 16);
        this.properties.thumbnail_width = width;
        this.properties.thumbnail_height = height;
        break;
      }
      offset += 8 + chunkLen + (chunkLen % 2);
    }
    this.properties.file_size = this.fileSize;
    this.properties.is_riff = sig === 'RIFF';
    // ZIP check
    if (data[0] === 0x50 && data[1] === 0x4B && data[2] === 0x03 && data[3] === 0x04) {
      this.properties.container_type = 'ZIP';
    }
  }

  write(outputFilename, version = 500, thumbnailWidth = 100, thumbnailHeight = 100) {
    // Basic skeleton
    let data = Buffer.from('RIFF', 'ascii');
    let fileSize = 36 + 1024;
    data = Buffer.concat([data, Buffer.alloc(4).writeUInt32LE(fileSize - 8, 0)]);
    data = Buffer.concat([data, Buffer.from('CDR ', 'ascii')]);
    // vrsn
    let vrsn = Buffer.from('vrsn', 'ascii');
    vrsn = Buffer.concat([vrsn, Buffer.alloc(4).writeUInt32LE(2, 0)]);
    vrsn = Buffer.concat([vrsn, Buffer.alloc(2).writeUInt16LE(version, 0), Buffer.from([0])]); // Pad
    data = Buffer.concat([data, vrsn]);
    // DISP stub
    let dispContent = Buffer.alloc(8 + 4 + 4 + 28 + 1024);
    dispContent.writeUInt32LE(thumbnailWidth, 12);
    dispContent.writeUInt32LE(thumbnailHeight, 16);
    let disp = Buffer.from('DISP', 'ascii');
    disp = Buffer.concat([disp, Buffer.alloc(4).writeUInt32LE(dispContent.length, 0)]);
    data = Buffer.concat([data, disp, dispContent]);
    if (dispContent.length % 2 !== 0) {
      data = Buffer.concat([data, Buffer.from([0])]);
    }
    fs.writeFileSync(outputFilename, data);
    console.log(`Basic skeleton written to ${outputFilename}`);
  }

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

if (require.main === module) {
  const args = process.argv.slice(2);
  if (args.length < 1) {
    console.log('Usage: node cdr_parser.js <input.cdr> [output.cdr]');
    process.exit(1);
  }
  const parser = new CDRParser(args[0]);
  parser.printProperties();
  if (args.length > 1) {
    parser.write(args[1]);
  }
}

module.exports = CDRParser;

7. C Class for .CDR File

This C code uses fopen and fread for parsing (header, version, thumbnail). Compile with gcc cdr_parser.c -o cdr_parser, run ./cdr_parser input.cdr to print. Write creates skeleton. Basic implementation; full requires more.

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

typedef struct {
    char signature[5];
    uint32_t chunk_size;
    char form_type[5];
    uint16_t version;
    uint32_t thumbnail_width;
    uint32_t thumbnail_height;
    size_t file_size;
    int is_riff;
} CDRProperties;

typedef struct {
    CDRProperties props;
    FILE *file;
} CDRParser;

void init_parser(CDRParser *parser, const char *filename);
void read_properties(CDRParser *parser);
void write_skeleton(CDRParser *parser, const char *output_filename, uint16_t version, uint32_t tw, uint32_t th);
void print_properties(CDRParser *parser);

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <input.cdr> [output.cdr]\n", argv[0]);
        return 1;
    }
    CDRParser parser = {0};
    init_parser(&parser, argv[1]);
    read_properties(&parser);
    print_properties(&parser);
    if (argc > 2) {
        write_skeleton(&parser, argv[2], 500, 100, 100);
    }
    if (parser.file) fclose(parser.file);
    return 0;
}

void init_parser(CDRParser *parser, const char *filename) {
    parser->file = fopen(filename, "rb");
    if (!parser->file) {
        perror("Error opening file");
        exit(1);
    }
    fseek(parser->file, 0, SEEK_END);
    parser->props.file_size = ftell(parser->file);
    fseek(parser->file, 0, SEEK_SET);
}

void read_properties(CDRParser *parser) {
    uint8_t buffer[4];
    size_t offset = 0;
    // RIFF header
    fread(buffer, 1, 4, parser->file);
    buffer[4] = 0;
    strcpy(parser->props.signature, (char*)buffer);
    offset += 4;
    fseek(parser->file, offset, SEEK_SET);
    fread(buffer, 1, 4, parser->file);
    memcpy(&parser->props.chunk_size, buffer, 4);
    offset += 4;
    fseek(parser->file, offset, SEEK_SET);
    fread(buffer, 1, 4, parser->file);
    buffer[4] = 0;
    strcpy(parser->props.form_type, (char*)buffer);
    offset += 4;
    fseek(parser->file, offset, SEEK_SET);
    // Find vrsn (simple loop)
    char chunk_id[5];
    uint32_t chunk_len;
    while (offset < 200 && offset < parser->props.file_size) {
        fseek(parser->file, offset, SEEK_SET);
        fread(chunk_id, 1, 4, parser->file);
        chunk_id[4] = 0;
        fread(&chunk_len, 4, 1, parser->file);
        if (strcmp(chunk_id, "vrsn") == 0) {
            uint16_t ver;
            fseek(parser->file, offset + 8, SEEK_SET);
            fread(&ver, 2, 1, parser->file);
            parser->props.version = ver;
            break;
        }
        offset += 8 + chunk_len + (chunk_len % 2);
    }
    // Find DISP
    while (offset < 500 && offset < parser->props.file_size) {
        fseek(parser->file, offset, SEEK_SET);
        fread(chunk_id, 1, 4, parser->file);
        chunk_id[4] = 0;
        fread(&chunk_len, 4, 1, parser->file);
        if (strcmp(chunk_id, "DISP") == 0) {
            uint32_t w, h;
            fseek(parser->file, offset + 12, SEEK_SET);
            fread(&w, 4, 1, parser->file);
            fread(&h, 4, 1, parser->file);
            parser->props.thumbnail_width = w;
            parser->props.thumbnail_height = h;
            break;
        }
        offset += 8 + chunk_len + (chunk_len % 2);
    }
    parser->props.is_riff = strcmp(parser->props.signature, "RIFF") == 0;
    // ZIP check approximate
    fseek(parser->file, 0, SEEK_SET);
    fread(buffer, 1, 4, parser->file);
    if (buffer[0] == 0x50 && buffer[1] == 0x4B && buffer[2] == 0x03 && buffer[3] == 0x04) {
        printf("ZIP container detected.\n");
    }
}

void write_skeleton(CDRParser *parser, const char *output_filename, uint16_t version, uint32_t tw, uint32_t th) {
    FILE *out = fopen(output_filename, "wb");
    if (!out) {
        perror("Error writing file");
        return;
    }
    // RIFF header
    fwrite("RIFF", 1, 4, out);
    uint32_t fsize = 36 + 1024;
    fwrite(&fsize, 4, 1, out); // -8 later if needed
    fwrite("CDR ", 1, 4, out);
    // vrsn
    fwrite("vrsn", 1, 4, out);
    uint32_t vlen = 2;
    fwrite(&vlen, 4, 1, out);
    fwrite(&version, 2, 1, out);
    fputc(0, out); // Pad
    // DISP stub
    uint8_t disp_stub[1064] = {0}; // 8+4+4+28+1024
    memcpy(disp_stub + 12, &tw, 4);
    memcpy(disp_stub + 16, &th, 4);
    fwrite("DISP", 1, 4, out);
    uint32_t dlen = 1064;
    fwrite(&dlen, 4, 1, out);
    fwrite(disp_stub, 1, 1064, out);
    fputc(0, out); // Pad if odd
    fclose(out);
    printf("Basic skeleton written to %s\n", output_filename);
}

void print_properties(CDRParser *parser) {
    printf("signature: %s\n", parser->props.signature);
    printf("chunk_size: %u\n", parser->props.chunk_size);
    printf("form_type: %s\n", parser->props.form_type);
    printf("version: %u\n", parser->props.version);
    printf("thumbnail_width: %u\n", parser->props.thumbnail_width);
    printf("thumbnail_height: %u\n", parser->props.thumbnail_height);
    printf("file_size: %zu\n", parser->props.file_size);
    printf("is_riff: %d\n", parser->props.is_riff);
}