Task 178: .EMAKER File Format

Task 178: .EMAKER File Format

File Format Specifications for .EMF

The .EMF (Enhanced Metafile) file format is a Windows graphics format for storing device-independent vector images. It consists of a header record (EMR_HEADER) followed by a series of records representing graphics operations, and ends with an EMR_EOF record. The format is specified in Microsoft's [MS-EMF] documentation, which details the structure, record types, and processing rules. EMF files support 32-bit color, transparency, and OpenGL commands in extensions, and are backward-compatible with the older WMF format but with enhancements for better printing and display.

  1. List of properties intrinsic to the .EMF file format:

These are the fields from the EMR_HEADER record, including the base Header object and optional extensions. They define the metafile's metadata, bounds, size, and optional features like pixel format and OpenGL support. Properties are listed with name, data type, size in bytes, and description (offsets are relative to the start of their containing object).

  • Bounds: RectL object (struct with Left, Top, Right, Bottom as signed 32-bit integers), 16 bytes, Defines the bounding rectangle in logical units for the image.
  • Frame: RectL object (struct with Left, Top, Right, Bottom as signed 32-bit integers), 16 bytes, Defines the frame rectangle in 0.01 mm units.
  • RecordSignature: Unsigned integer, 4 bytes, Must be 0x464D4520 (ASCII " EMF").
  • Version: Unsigned integer, 4 bytes, EMF version, typically 0x00010000.
  • Bytes: Unsigned integer, 4 bytes, Total size of the metafile in bytes.
  • Records: Unsigned integer, 4 bytes, Number of records in the metafile.
  • Handles: Unsigned 16-bit integer, 2 bytes, Number of graphics objects used in the metafile.
  • Reserved: Unsigned 16-bit integer, 2 bytes, Must be 0x0000 (ignored).
  • nDescription: Unsigned integer, 4 bytes, Number of characters in the optional description string (0 if none).
  • offDescription: Unsigned integer, 4 bytes, Offset to the optional description string from the start of the EMR_HEADER record.
  • nPalEntries: Unsigned integer, 4 bytes, Number of entries in the optional palette (in EMR_EOF record).
  • Device: SizeL object (struct with Width and Height as unsigned 32-bit integers), 8 bytes, Reference device size in pixels.
  • Millimeters: SizeL object (struct with Width and Height as unsigned 32-bit integers), 8 bytes, Reference device size in millimeters.
  • cbPixelFormat (optional, in Extension1): Unsigned integer, 4 bytes, Size of the optional PixelFormatDescriptor (0 if none).
  • offPixelFormat (optional, in Extension1): Unsigned integer, 4 bytes, Offset to the optional PixelFormatDescriptor (0 if none).
  • bOpenGL (optional, in Extension1): Unsigned integer, 4 bytes, Flag indicating if OpenGL records are present (0x00000001 if yes, 0x00000000 if no).
  • MicrometersX (optional, in Extension2): Unsigned integer, 4 bytes, Horizontal size of reference device in micrometers.
  • MicrometersY (optional, in Extension2): Unsigned integer, 4 bytes, Vertical size of reference device in micrometers.
  1. Two direct download links for .EMF files:
  1. Ghost blog embedded HTML JavaScript for drag-and-drop .EMF file dump:

This is an HTML snippet with JavaScript that can be embedded in a Ghost blog post. It creates a drop zone; when an .EMF file is dropped, it reads the file as an ArrayBuffer, parses the header using DataView, extracts the properties, and displays them on the screen.

Drag and drop .EMF file here
  1. Python class for .EMF handling:

This class uses struct to decode the header, print properties, and supports basic write (creates a minimal EMF with header).

import struct

class EmfHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}

    def read_decode(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        # Unpack Type and Size (first 8 bytes)
        type_, size = struct.unpack('<II', data[:8])
        if type_ != 1:
            raise ValueError("Not an EMF file")
        # Base Header at offset 8
        self.properties['Bounds'] = struct.unpack('<iiii', data[8:24])  # Left, Top, Right, Bottom
        self.properties['Frame'] = struct.unpack('<iiii', data[24:40])
        self.properties['RecordSignature'] = struct.unpack('<I', data[40:44])[0]
        self.properties['Version'] = struct.unpack('<I', data[44:48])[0]
        self.properties['Bytes'] = struct.unpack('<I', data[48:52])[0]
        self.properties['Records'] = struct.unpack('<I', data[52:56])[0]
        self.properties['Handles'] = struct.unpack('<H', data[56:58])[0]
        self.properties['Reserved'] = struct.unpack('<H', data[58:60])[0]
        self.properties['nDescription'] = struct.unpack('<I', data[60:64])[0]
        self.properties['offDescription'] = struct.unpack('<I', data[64:68])[0]
        self.properties['nPalEntries'] = struct.unpack('<I', data[68:72])[0]
        self.properties['Device'] = struct.unpack('<II', data[72:80])  # Width, Height
        self.properties['Millimeters'] = struct.unpack('<II', data[80:88])
        # Extension1 if size >=100
        if size >= 100:
            self.properties['cbPixelFormat'] = struct.unpack('<I', data[88:92])[0]
            self.properties['offPixelFormat'] = struct.unpack('<I', data[92:96])[0]
            self.properties['bOpenGL'] = struct.unpack('<I', data[96:100])[0]
        # Extension2 if size >=108
        if size >= 108:
            self.properties['MicrometersX'] = struct.unpack('<I', data[100:104])[0]
            self.properties['MicrometersY'] = struct.unpack('<I', data[104:108])[0]

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

    def write(self, output_filename):
        # Minimal EMF: header + EOF
        header_data = struct.pack('<II', 1, 88)  # Type, Size (base)
        header_data += struct.pack('<iiiiiiiiI I I I H H I I I II II', 
            0,0,100,100,  # Bounds
            0,0,1000,1000,  # Frame
            0x464D4520,  # Signature
            0x00010000,  # Version
            100,  # Bytes (minimal)
            2,  # Records (header + EOF)
            0,  # Handles
            0,  # Reserved
            0,  # nDescription
            0,  # offDescription
            0,  # nPalEntries
            800,600,  # Device
            210,297)  # Millimeters (A4 example)
        eof_data = struct.pack('<II I I H H', 14, 20, 0, 0, 0, 0)  # EMR_EOF minimal
        with open(output_filename, 'wb') as f:
            f.write(header_data + eof_data)

# Example usage:
# handler = EmfHandler('input.emf')
# handler.read_decode()
# handler.print_properties()
# handler.write('output.emf')
  1. Java class for .EMF handling:

This class uses DataInputStream to read, decode, and print properties, and DataOutputStream for basic write.

import java.io.*;

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

    public EmfHandler(String filename) {
        this.filename = filename;
    }

    public void readDecode() throws IOException {
        try (DataInputStream dis = new DataInputStream(new FileInputStream(filename))) {
            int type = Integer.reverseBytes(dis.readInt());
            int size = Integer.reverseBytes(dis.readInt());
            if (type != 1) throw new IOException("Not an EMF file");
            // Base Header
            properties.put("Bounds", new int[]{dis.readInt(), dis.readInt(), dis.readInt(), dis.readInt()});
            properties.put("Frame", new int[]{dis.readInt(), dis.readInt(), dis.readInt(), dis.readInt()});
            properties.put("RecordSignature", dis.readInt());
            properties.put("Version", dis.readInt());
            properties.put("Bytes", dis.readInt());
            properties.put("Records", dis.readInt());
            properties.put("Handles", (short) dis.readShort());
            properties.put("Reserved", (short) dis.readShort());
            properties.put("nDescription", dis.readInt());
            properties.put("offDescription", dis.readInt());
            properties.put("nPalEntries", dis.readInt());
            properties.put("Device", new int[]{dis.readInt(), dis.readInt()});
            properties.put("Millimeters", new int[]{dis.readInt(), dis.readInt()});
            // Extension1 if size >=100
            if (size >= 100) {
                properties.put("cbPixelFormat", dis.readInt());
                properties.put("offPixelFormat", dis.readInt());
                properties.put("bOpenGL", dis.readInt());
            }
            // Extension2 if size >=108
            if (size >= 108) {
                properties.put("MicrometersX", dis.readInt());
                properties.put("MicrometersY", dis.readInt());
            }
        }
    }

    public void printProperties() {
        properties.forEach((key, val) -> System.out.println(key + ": " + java.util.Arrays.toString(val instanceof int[] ? (int[])val : new Object[]{val})));
    }

    public void write(String outputFilename) throws IOException {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(outputFilename))) {
            // Minimal header
            dos.writeInt(Integer.reverseBytes(1));  // Type
            dos.writeInt(Integer.reverseBytes(88));  // Size
            dos.writeInt(0); dos.writeInt(0); dos.writeInt(100); dos.writeInt(100);  // Bounds
            dos.writeInt(0); dos.writeInt(0); dos.writeInt(1000); dos.writeInt(1000);  // Frame
            dos.writeInt(0x464D4520);  // Signature
            dos.writeInt(0x00010000);  // Version
            dos.writeInt(100);  // Bytes
            dos.writeInt(2);  // Records
            dos.writeShort(0);  // Handles
            dos.writeShort(0);  // Reserved
            dos.writeInt(0);  // nDescription
            dos.writeInt(0);  // offDescription
            dos.writeInt(0);  // nPalEntries
            dos.writeInt(800); dos.writeInt(600);  // Device
            dos.writeInt(210); dos.writeInt(297);  // Millimeters
            // Minimal EOF
            dos.writeInt(Integer.reverseBytes(14));  // Type
            dos.writeInt(Integer.reverseBytes(20));  // Size
            dos.writeInt(0); dos.writeInt(0); dos.writeShort(0); dos.writeShort(0);
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     EmfHandler handler = new EmfHandler("input.emf");
    //     handler.readDecode();
    //     handler.printProperties();
    //     handler.write("output.emf");
    // }
}
  1. JavaScript class for .EMF handling:

This class uses Node.js fs for file I/O, Buffer for decoding, and prints to console. For write, creates a minimal EMF.

const fs = require('fs');

class EmfHandler {
  constructor(filename) {
    this.filename = filename;
    this.properties = {};
  }

  readDecode() {
    const data = fs.readFileSync(this.filename);
    const view = new DataView(data.buffer);
    const type = view.getUint32(0, true);
    const size = view.getUint32(4, true);
    if (type !== 1) throw new Error('Not an EMF file');
    // Base Header at offset 8
    this.properties.Bounds = {left: view.getInt32(8, true), top: view.getInt32(12, true), right: view.getInt32(16, true), bottom: view.getInt32(20, true)};
    this.properties.Frame = {left: view.getInt32(24, true), top: view.getInt32(28, true), right: view.getInt32(32, true), bottom: view.getInt32(36, true)};
    this.properties.RecordSignature = view.getUint32(40, true);
    this.properties.Version = view.getUint32(44, true);
    this.properties.Bytes = view.getUint32(48, true);
    this.properties.Records = view.getUint32(52, true);
    this.properties.Handles = view.getUint16(56, true);
    this.properties.Reserved = view.getUint16(58, true);
    this.properties.nDescription = view.getUint32(60, true);
    this.properties.offDescription = view.getUint32(64, true);
    this.properties.nPalEntries = view.getUint32(68, true);
    this.properties.Device = {width: view.getUint32(72, true), height: view.getUint32(76, true)};
    this.properties.Millimeters = {width: view.getUint32(80, true), height: view.getUint32(84, true)};
    // Extension1
    if (size >= 100) {
      this.properties.cbPixelFormat = view.getUint32(88, true);
      this.properties.offPixelFormat = view.getUint32(92, true);
      this.properties.bOpenGL = view.getUint32(96, true);
    }
    // Extension2
    if (size >= 108) {
      this.properties.MicrometersX = view.getUint32(100, true);
      this.properties.MicrometersY = view.getUint32(104, true);
    }
  }

  printProperties() {
    console.log(this.properties);
  }

  write(outputFilename) {
    const buffer = Buffer.alloc(108);  // Minimal base size
    const view = new DataView(buffer.buffer);
    view.setUint32(0, 1, true);  // Type
    view.setUint32(4, 88, true);  // Size
    // Bounds
    view.setInt32(8, 0, true); view.setInt32(12, 0, true); view.setInt32(16, 100, true); view.setInt32(20, 100, true);
    // Frame
    view.setInt32(24, 0, true); view.setInt32(28, 0, true); view.setInt32(32, 1000, true); view.setInt32(36, 1000, true);
    view.setUint32(40, 0x464D4520, true);  // Signature
    view.setUint32(44, 0x00010000, true);  // Version
    view.setUint32(48, 100, true);  // Bytes
    view.setUint32(52, 2, true);  // Records
    view.setUint16(56, 0, true);  // Handles
    view.setUint16(58, 0, true);  // Reserved
    view.setUint32(60, 0, true);  // nDescription
    view.setUint32(64, 0, true);  // offDescription
    view.setUint32(68, 0, true);  // nPalEntries
    view.setUint32(72, 800, true); view.setUint32(76, 600, true);  // Device
    view.setUint32(80, 210, true); view.setUint32(84, 297, true);  // Millimeters
    // Append minimal EOF (Type 14, Size 20)
    const eofBuffer = Buffer.alloc(20);
    const eofView = new DataView(eofBuffer.buffer);
    eofView.setUint32(0, 14, true);
    eofView.setUint32(4, 20, true);
    fs.writeFileSync(outputFilename, Buffer.concat([buffer.slice(0, 88), eofBuffer]));
  }
}

// Example usage:
// const handler = new EmfHandler('input.emf');
// handler.readDecode();
// handler.printProperties();
// handler.write('output.emf');
  1. C "class" (using struct and functions) for .EMF handling:

C doesn't have classes, so using a struct with functions. Uses fread for read, printf for print, fwrite for write.

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

typedef struct {
    int32_t bounds[4];  // left, top, right, bottom
    int32_t frame[4];
    uint32_t recordSignature;
    uint32_t version;
    uint32_t bytes;
    uint32_t records;
    uint16_t handles;
    uint16_t reserved;
    uint32_t nDescription;
    uint32_t offDescription;
    uint32_t nPalEntries;
    uint32_t device[2];  // width, height
    uint32_t millimeters[2];
    uint32_t cbPixelFormat;
    uint32_t offPixelFormat;
    uint32_t bOpenGL;
    uint32_t micrometersX;
    uint32_t micrometersY;
    int hasExtension1;
    int hasExtension2;
} EmfProperties;

void read_decode(const char* filename, EmfProperties* props) {
    FILE* f = fopen(filename, "rb");
    if (!f) return;
    uint32_t type, size;
    fread(&type, sizeof(uint32_t), 1, f);
    fread(&size, sizeof(uint32_t), 1, f);
    if (type != 1) {
        fclose(f);
        return;
    }
    fread(props->bounds, sizeof(int32_t), 4, f);
    fread(props->frame, sizeof(int32_t), 4, f);
    fread(&props->recordSignature, sizeof(uint32_t), 1, f);
    fread(&props->version, sizeof(uint32_t), 1, f);
    fread(&props->bytes, sizeof(uint32_t), 1, f);
    fread(&props->records, sizeof(uint32_t), 1, f);
    fread(&props->handles, sizeof(uint16_t), 1, f);
    fread(&props->reserved, sizeof(uint16_t), 1, f);
    fread(&props->nDescription, sizeof(uint32_t), 1, f);
    fread(&props->offDescription, sizeof(uint32_t), 1, f);
    fread(&props->nPalEntries, sizeof(uint32_t), 1, f);
    fread(props->device, sizeof(uint32_t), 2, f);
    fread(props->millimeters, sizeof(uint32_t), 2, f);
    props->hasExtension1 = (size >= 100);
    if (props->hasExtension1) {
        fread(&props->cbPixelFormat, sizeof(uint32_t), 1, f);
        fread(&props->offPixelFormat, sizeof(uint32_t), 1, f);
        fread(&props->bOpenGL, sizeof(uint32_t), 1, f);
    }
    props->hasExtension2 = (size >= 108);
    if (props->hasExtension2) {
        fread(&props->micrometersX, sizeof(uint32_t), 1, f);
        fread(&props->micrometersY, sizeof(uint32_t), 1, f);
    }
    fclose(f);
}

void print_properties(const EmfProperties* props) {
    printf("Bounds: [%d, %d, %d, %d]\n", props->bounds[0], props->bounds[1], props->bounds[2], props->bounds[3]);
    printf("Frame: [%d, %d, %d, %d]\n", props->frame[0], props->frame[1], props->frame[2], props->frame[3]);
    printf("RecordSignature: %u\n", props->recordSignature);
    printf("Version: %u\n", props->version);
    printf("Bytes: %u\n", props->bytes);
    printf("Records: %u\n", props->records);
    printf("Handles: %u\n", props->handles);
    printf("Reserved: %u\n", props->reserved);
    printf("nDescription: %u\n", props->nDescription);
    printf("offDescription: %u\n", props->offDescription);
    printf("nPalEntries: %u\n", props->nPalEntries);
    printf("Device: [%u, %u]\n", props->device[0], props->device[1]);
    printf("Millimeters: [%u, %u]\n", props->millimeters[0], props->millimeters[1]);
    if (props->hasExtension1) {
        printf("cbPixelFormat: %u\n", props->cbPixelFormat);
        printf("offPixelFormat: %u\n", props->offPixelFormat);
        printf("bOpenGL: %u\n", props->bOpenGL);
    }
    if (props->hasExtension2) {
        printf("MicrometersX: %u\n", props->micrometersX);
        printf("MicrometersY: %u\n", props->micrometersY);
    }
}

void write_emf(const char* output_filename) {
    FILE* f = fopen(output_filename, "wb");
    if (!f) return;
    uint32_t type = 1, size = 88;
    fwrite(&type, sizeof(uint32_t), 1, f);
    fwrite(&size, sizeof(uint32_t), 1, f);
    int32_t bounds[4] = {0, 0, 100, 100};
    fwrite(bounds, sizeof(int32_t), 4, f);
    int32_t frame[4] = {0, 0, 1000, 1000};
    fwrite(frame, sizeof(int32_t), 4, f);
    uint32_t sig = 0x464D4520;
    fwrite(&sig, sizeof(uint32_t), 1, f);
    uint32_t ver = 0x00010000;
    fwrite(&ver, sizeof(uint32_t), 1, f);
    uint32_t bytes = 100;
    fwrite(&bytes, sizeof(uint32_t), 1, f);
    uint32_t recs = 2;
    fwrite(&recs, sizeof(uint32_t), 1, f);
    uint16_t handles = 0;
    fwrite(&handles, sizeof(uint16_t), 1, f);
    uint16_t res = 0;
    fwrite(&res, sizeof(uint16_t), 1, f);
    uint32_t ndesc = 0;
    fwrite(&ndesc, sizeof(uint32_t), 1, f);
    uint32_t offdesc = 0;
    fwrite(&offdesc, sizeof(uint32_t), 1, f);
    uint32_t npal = 0;
    fwrite(&npal, sizeof(uint32_t), 1, f);
    uint32_t dev[2] = {800, 600};
    fwrite(dev, sizeof(uint32_t), 2, f);
    uint32_t mm[2] = {210, 297};
    fwrite(mm, sizeof(uint32_t), 2, f);
    // EOF
    uint32_t eof_type = 14, eof_size = 20;
    fwrite(&eof_type, sizeof(uint32_t), 1, f);
    fwrite(&eof_size, sizeof(uint32_t), 1, f);
    uint32_t zero = 0;
    fwrite(&zero, sizeof(uint32_t), 1, f);
    fwrite(&zero, sizeof(uint32_t), 1, f);
    uint16_t zero16 = 0;
    fwrite(&zero16, sizeof(uint16_t), 1, f);
    fwrite(&zero16, sizeof(uint16_t), 1, f);
    fclose(f);
}

// Example usage:
// int main() {
//     EmfProperties props = {0};
//     read_decode("input.emf", &props);
//     print_properties(&props);
//     write_emf("output.emf");
//     return 0;
// }