Task 085: .CGM File Format

Task 085: .CGM File Format

File Format Specifications for the .CGM File Format

The .CGM file format, known as Computer Graphics Metafile, is an international standard defined by ISO/IEC 8632:1999 for the storage and exchange of 2D vector graphics, raster graphics, and text in a device-independent manner. It supports three encoding types: binary (for efficient access and exchange), clear text (for human readability), and character (for compact storage). The format structures data as a sequence of elements, each comprising a command header and parameters, facilitating the reconstruction of graphical primitives such as lines, polygons, and text. In binary encoding, elements are organized in 16-bit words, with a header specifying class (4 bits), element ID (7 bits), and parameter length (5 bits for short form or extended for long form), followed by parameters and optional padding to ensure word alignment.

List of Properties Intrinsic to the File Format

The properties intrinsic to the .CGM file format are primarily the elements within the Metafile Descriptor (Class 1 elements), which define metadata for interpreting the file. These are standardized in ISO/IEC 8632 and include the following:

  • Metafile Version: An integer specifying the CGM version (e.g., 1, 2, 3, or 4).
  • Metafile Description: A string providing descriptive information, such as author, creation date, or software used.
  • VDC Type: An enumerated value indicating Virtual Device Coordinates type (0 for integer, 1 for real).
  • Integer Precision: Parameters defining the bit width and range for integer values (e.g., minimum and maximum values).
  • Real Precision: Parameters specifying precision for real numbers (e.g., exponent and mantissa widths).
  • Index Precision: Bit width for index values.
  • Colour Precision: Precision for direct color values.
  • Colour Index Precision: Precision for indexed colors.
  • Maximum Colour Index: The highest allowable color index.
  • Colour Value Extent: Range of color values (e.g., RGB minima and maxima).
  • Metafile Element List: A list of supported elements in the metafile.
  • Font List: A list of font names available for text rendering.
  • Character Set List: A list of character sets supported.
  • Character Coding Announcer: An enumerated value for character encoding (e.g., basic 7-bit or extended).
  • Name Precision: Precision for name indices (in higher versions).
  • Maximum VDC Extent: Bounds for the virtual device coordinate space.
  • Colour Model: Enumerated value for color space (e.g., RGB, CMYK).

These properties appear after the BEGIN METAFILE element and before the first picture, ensuring consistent interpretation across systems.

Two Direct Download Links for .CGM Files

Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .CGM File Analysis

The following is an HTML snippet with embedded JavaScript suitable for embedding in a Ghost blog post. It creates a drag-and-drop zone where users can upload a .CGM file (assuming binary encoding). The script parses the file, extracts the properties listed above, and displays them on the screen.

Drag and drop a .CGM file here

Python Class for .CGM File Handling

The following Python class opens a .CGM file (binary encoding), decodes and reads the properties, prints them to the console, and supports writing the original file content back (with potential for property modification in extensions).

import struct

class CGMParser:
    def __init__(self, filename):
        with open(filename, 'rb') as f:
            self.data = f.read()
        self.pos = 0
        self.properties = {}
        self.parse()

    def read_uint16(self):
        val, = struct.unpack('>H', self.data[self.pos:self.pos+2])
        self.pos += 2
        return val

    def read_int16(self):
        val, = struct.unpack('>h', self.data[self.pos:self.pos+2])
        self.pos += 2
        return val

    def read_string(self, length):
        s = self.data[self.pos:self.pos+length].decode('ascii')
        self.pos += length
        return s

    def parse(self):
        while self.pos < len(self.data):
            word = self.read_uint16()
            class_id = (word >> 12) & 0xF
            elem_id = (word >> 5) & 0x7F
            param_len = word & 0x1F
            if param_len == 0x1F:
                param_len = self.read_uint16()
            start_pos = self.pos

            if class_id == 0 and elem_id == 1:  # BEGIN METAFILE
                pass  # Skip
            elif class_id == 1:
                if elem_id == 1:  # METAFILE VERSION
                    self.properties['Metafile Version'] = self.read_int16()
                elif elem_id == 2:  # METAFILE DESCRIPTION
                    self.properties['Metafile Description'] = self.read_string(param_len)
                elif elem_id == 3:  # VDC TYPE
                    self.properties['VDC Type'] = 'Integer' if self.read_int16() == 0 else 'Real'
                elif elem_id == 4:  # INTEGER PRECISION
                    self.properties['Integer Precision'] = f"Min: {self.read_int16()}, Max: {self.read_int16()}"
                elif elem_id == 5:  # REAL PRECISION
                    self.properties['Real Precision'] = f"Mantissa: {self.read_int16()}, Exponent: {self.read_int16()}"
                # Extend for other properties (e.g., elem_id 6: Index Precision, etc.)
            self.pos = start_pos + param_len
            if param_len % 2 == 1:
                self.pos += 1  # Padding
            if class_id == 0 and elem_id == 3:  # Stop at BEGIN PICTURE
                break

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

    def write(self, output_filename):
        with open(output_filename, 'wb') as f:
            f.write(self.data)  # Writes original; extend for modified properties

# Example usage: parser = CGMParser('sample.cgm'); parser.print_properties(); parser.write('output.cgm')

Java Class for .CGM File Handling

The following Java class opens a .CGM file, decodes and reads the properties, prints them to the console, and supports writing the file.

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class CGMParser {
    private byte[] data;
    private int pos = 0;
    private java.util.Map<String, String> properties = new java.util.HashMap<>();

    public CGMParser(String filename) throws IOException {
        try (FileInputStream fis = new FileInputStream(filename)) {
            data = fis.readAllBytes();
        }
        parse();
    }

    private int readUint16() {
        ByteBuffer bb = ByteBuffer.wrap(data, pos, 2).order(ByteOrder.BIG_ENDIAN);
        int val = bb.getShort() & 0xFFFF;
        pos += 2;
        return val;
    }

    private short readInt16() {
        ByteBuffer bb = ByteBuffer.wrap(data, pos, 2).order(ByteOrder.BIG_ENDIAN);
        short val = bb.getShort();
        pos += 2;
        return val;
    }

    private String readString(int length) {
        String s = new String(data, pos, length);
        pos += length;
        return s;
    }

    private void parse() {
        while (pos < data.length) {
            int word = readUint16();
            int classId = (word >> 12) & 0xF;
            int elemId = (word >> 5) & 0x7F;
            int paramLen = word & 0x1F;
            if (paramLen == 0x1F) {
                paramLen = readUint16();
            }
            int startPos = pos;

            if (classId == 0 && elemId == 1) { // BEGIN METAFILE
                // Skip
            } else if (classId == 1) {
                if (elemId == 1) { // METAFILE VERSION
                    properties.put("Metafile Version", String.valueOf(readInt16()));
                } else if (elemId == 2) { // METAFILE DESCRIPTION
                    properties.put("Metafile Description", readString(paramLen));
                } else if (elemId == 3) { // VDC TYPE
                    properties.put("VDC Type", readInt16() == 0 ? "Integer" : "Real");
                } else if (elemId == 4) { // INTEGER PRECISION
                    properties.put("Integer Precision", "Min: " + readInt16() + ", Max: " + readInt16());
                } else if (elemId == 5) { // REAL PRECISION
                    properties.put("Real Precision", "Mantissa: " + readInt16() + ", Exponent: " + readInt16());
                } // Extend for others
            }
            pos = startPos + paramLen;
            if (paramLen % 2 == 1) pos += 1; // Padding
            if (classId == 0 && elemId == 3) break; // BEGIN PICTURE
        }
    }

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

    public void write(String outputFilename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(data);
        }
    }

    // Example: CGMParser parser = new CGMParser("sample.cgm"); parser.printProperties(); parser.write("output.cgm");
}

JavaScript Class for .CGM File Handling

The following JavaScript class (Node.js compatible) opens a .CGM file, decodes and reads the properties, prints them to the console, and supports writing.

const fs = require('fs');

class CGMParser {
    constructor(filename) {
        this.data = fs.readFileSync(filename);
        this.pos = 0;
        this.properties = {};
        this.parse();
    }

    readUint16() {
        const val = (this.data[this.pos] << 8) | this.data[this.pos + 1];
        this.pos += 2;
        return val;
    }

    readInt16() {
        let val = (this.data[this.pos] << 8) | this.data[this.pos + 1];
        if (val & 0x8000) val -= 0x10000; // Signed
        this.pos += 2;
        return val;
    }

    readString(len) {
        let str = '';
        for (let i = 0; i < len; i++) {
            str += String.fromCharCode(this.data[this.pos + i]);
        }
        this.pos += len;
        return str;
    }

    parse() {
        while (this.pos < this.data.length) {
            const word = this.readUint16();
            const classId = (word >> 12) & 0xF;
            const elemId = (word >> 5) & 0x7F;
            let paramLen = word & 0x1F;
            if (paramLen === 0x1F) {
                paramLen = this.readUint16();
            }
            const startPos = this.pos;

            if (classId === 0 && elemId === 1) { // BEGIN METAFILE
                // Skip
            } else if (classId === 1) {
                if (elemId === 1) { // METAFILE VERSION
                    this.properties['Metafile Version'] = this.readInt16();
                } else if (elemId === 2) { // METAFILE DESCRIPTION
                    this.properties['Metafile Description'] = this.readString(paramLen);
                } else if (elemId === 3) { // VDC TYPE
                    this.properties['VDC Type'] = this.readInt16() === 0 ? 'Integer' : 'Real';
                } else if (elemId === 4) { // INTEGER PRECISION
                    this.properties['Integer Precision'] = `Min: ${this.readInt16()}, Max: ${this.readInt16()}`;
                } else if (elemId === 5) { // REAL PRECISION
                    this.properties['Real Precision'] = `Mantissa: ${this.readInt16()}, Exponent: ${this.readInt16()}`;
                } // Extend for others
            }
            this.pos = startPos + paramLen;
            if (paramLen % 2 === 1) this.pos += 1; // Padding
            if (classId === 0 && elemId === 3) break; // BEGIN PICTURE
        }
    }

    printProperties() {
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
    }

    write(outputFilename) {
        fs.writeFileSync(outputFilename, this.data);
    }
}

// Example: const parser = new CGMParser('sample.cgm'); parser.printProperties(); parser.write('output.cgm');

C Class (Implemented as C++ for Object-Oriented Structure) for .CGM File Handling

The following C++ class opens a .CGM file, decodes and reads the properties, prints them to the console, and supports writing.

#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>

class CGMParser {
private:
    std::vector<unsigned char> data;
    size_t pos = 0;
    std::map<std::string, std::string> properties;

    unsigned short readUint16() {
        unsigned short val = (data[pos] << 8) | data[pos + 1];
        pos += 2;
        return val;
    }

    short readInt16() {
        unsigned short uval = readUint16();
        return static_cast<short>(uval);
    }

    std::string readString(size_t len) {
        std::string s(data.begin() + pos, data.begin() + pos + len);
        pos += len;
        return s;
    }

    void parse() {
        while (pos < data.size()) {
            unsigned short word = readUint16();
            int classId = (word >> 12) & 0xF;
            int elemId = (word >> 5) & 0x7F;
            int paramLen = word & 0x1F;
            if (paramLen == 0x1F) {
                paramLen = readUint16();
            }
            size_t startPos = pos;

            if (classId == 0 && elemId == 1) { // BEGIN METAFILE
                // Skip
            } else if (classId == 1) {
                if (elemId == 1) { // METAFILE VERSION
                    properties["Metafile Version"] = std::to_string(readInt16());
                } else if (elemId == 2) { // METAFILE DESCRIPTION
                    properties["Metafile Description"] = readString(paramLen);
                } else if (elemId == 3) { // VDC TYPE
                    properties["VDC Type"] = readInt16() == 0 ? "Integer" : "Real";
                } else if (elemId == 4) { // INTEGER PRECISION
                    properties["Integer Precision"] = "Min: " + std::to_string(readInt16()) + ", Max: " + std::to_string(readInt16());
                } else if (elemId == 5) { // REAL PRECISION
                    properties["Real Precision"] = "Mantissa: " + std::to_string(readInt16()) + ", Exponent: " + std::to_string(readInt16());
                } // Extend for others
            }
            pos = startPos + paramLen;
            if (paramLen % 2 == 1) pos += 1; // Padding
            if (classId == 0 && elemId == 3) break; // BEGIN PICTURE
        }
    }

public:
    CGMParser(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        data = std::vector<unsigned char>(std::istreambuf_iterator<char>(file), {});
        parse();
    }

    void printProperties() const {
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void write(const std::string& outputFilename) const {
        std::ofstream file(outputFilename, std::ios::binary);
        file.write(reinterpret_cast<const char*>(data.data()), data.size());
    }
};

// Example: CGMParser parser("sample.cgm"); parser.printProperties(); parser.write("output.cgm");