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.
- 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.
- Two direct download links for .EMF files:
- https://raw.githubusercontent.com/jeremysanders/pyemf3/master/examples/test-1.emf
- https://raw.githubusercontent.com/jeremysanders/pyemf3/master/examples/test-drawing1.emf
- 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.
- 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')
- 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");
// }
}
- 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');
- 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;
// }