Task 184: .EPS File Format

Task 184: .EPS File Format

File Format Specifications for .EPS

The .EPS (Encapsulated PostScript) file format is a subset of the PostScript language, designed for embedding graphics in other documents. It conforms to Adobe's Document Structuring Conventions (DSC) version 3.0. EPS files are typically ASCII-based but can include binary previews for compatibility with systems like Windows or Macintosh. The format ensures portability for printing and graphics, restricting certain PostScript operators to prevent device-specific behavior. Key specifications are drawn from Adobe's official documentation. EPS files must be self-contained, with a maximum of one page, and include a bounding box for proper scaling and clipping.

List of all the properties of this file format intrinsic to its file system:

  • Binary Header Presence: Boolean indicating if the optional 30-byte DOS/Windows binary header exists (starts with magic bytes \xC5\xD0\xD3\xC6).
  • Binary Header Magic: 4 bytes (\xC5\xD0\xD3\xC6 if present).
  • PostScript Section Offset: 4-byte unsigned integer (bytes 4-7 if binary header present).
  • PostScript Section Length: 4-byte unsigned integer (bytes 8-11 if binary header present).
  • Metafile Preview Offset: 4-byte unsigned integer (bytes 12-15 if binary header present; 0 if absent).
  • Metafile Preview Length: 4-byte unsigned integer (bytes 16-19 if binary header present; 0 if absent).
  • TIFF Preview Offset: 4-byte unsigned integer (bytes 20-23 if binary header present; 0 if absent).
  • TIFF Preview Length: 4-byte unsigned integer (bytes 24-27 if binary header present; 0 if absent).
  • Header Checksum: 2-byte unsigned integer (bytes 28-29 if binary header present; \xFF\xFF to ignore).
  • Version Header: String starting with "%!PS-Adobe-3.0 EPSF-3.0" (required).
  • Title: String from "%%Title:" comment (recommended).
  • Creator: String from "%%Creator:" comment (recommended).
  • Creation Date: String from "%%CreationDate:" comment (recommended).
  • Bounding Box: Four values (llx, lly, urx, ury) from "%%BoundingBox:" comment (required; integers or "(atend)").
  • Language Level: Integer from "%%LanguageLevel:" comment (conditionally required if >1).
  • Extensions: List of strings from "%%Extensions:" comment (conditionally required if extensions used).
  • Document Needed Resources: List from "%%DocumentNeededResources:" and related comments (conditionally required if resources like fonts are needed).
  • Preview Type: "None", "EPSI" (if "%%BeginPreview" present), "TIFF", or "Metafile" (based on binary header).
  • File Portability Flags: Indicators for 7-bit ASCII compliance, line length <=255 chars, and restricted operators (intrinsic constraints).

Two direct download links for files of format .EPS:

Ghost blog embedded HTML JavaScript for drag and drop .EPS file to dump properties:

EPS Properties Dumper
Drag and drop .EPS file here
  1. Python class for .EPS handling:
import struct
import os

class EPSHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}
        self.content = None
        self.has_binary_header = False
        self.binary_header = {}
        self.read_and_decode()

    def read_and_decode(self):
        with open(self.filepath, 'rb') as f:
            self.content = f.read()

        # Check for binary header
        if len(self.content) >= 30 and self.content[0:4] == b'\xC5\xD0\xD3\xC6':
            self.has_binary_header = True
            (magic, ps_offset, ps_length, mf_offset, mf_length, tiff_offset, tiff_length, checksum) = struct.unpack('<I I I I I I I H', self.content[0:30])
            self.binary_header = {
                'magic': hex(magic),
                'ps_offset': ps_offset,
                'ps_length': ps_length,
                'mf_offset': mf_offset,
                'mf_length': mf_length,
                'tiff_offset': tiff_offset,
                'tiff_length': tiff_length,
                'checksum': hex(checksum)
            }
            ascii_start = ps_offset
            ascii_content = self.content[ascii_start:ascii_start + ps_length].decode('utf-8', errors='ignore')
        else:
            ascii_content = self.content.decode('utf-8', errors='ignore')

        # Parse DSC
        lines = ascii_content.splitlines()
        self.properties = {
            'has_binary_header': 'Yes' if self.has_binary_header else 'No',
            **self.binary_header,
            'version': '',
            'title': '',
            'creator': '',
            'creation_date': '',
            'bounding_box': '',
            'language_level': '',
            'extensions': '',
            'document_needed_resources': '',
            'preview_type': 'None'
        }

        for line in lines:
            if line.startswith('%!PS-Adobe-3.0 EPSF-3.0'):
                self.properties['version'] = line
            elif line.startswith('%%Title:'):
                self.properties['title'] = line[8:].strip()
            elif line.startswith('%%Creator:'):
                self.properties['creator'] = line[10:].strip()
            elif line.startswith('%%CreationDate:'):
                self.properties['creation_date'] = line[15:].strip()
            elif line.startswith('%%BoundingBox:'):
                self.properties['bounding_box'] = line[14:].strip()
            elif line.startswith('%%LanguageLevel:'):
                self.properties['language_level'] = line[16:].strip()
            elif line.startswith('%%Extensions:'):
                self.properties['extensions'] = line[13:].strip()
            elif line.startswith('%%DocumentNeededResources:'):
                self.properties['document_needed_resources'] = line[26:].strip()
            elif line.startswith('%%BeginPreview'):
                self.properties['preview_type'] = 'EPSI'

        if self.has_binary_header:
            if self.binary_header['mf_length'] > 0:
                self.properties['preview_type'] = 'Metafile'
            elif self.binary_header['tiff_length'] > 0:
                self.properties['preview_type'] = 'TIFF'

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

    def write(self, new_filepath, modify_props=None):
        if modify_props:
            # Simple modify: replace in ASCII content (not handling binary mods fully)
            ascii_content = self.content[30:] if self.has_binary_header else self.content
            lines = ascii_content.decode('utf-8', errors='ignore').splitlines()
            for i, line in enumerate(lines):
                for key, value in modify_props.items():
                    if key == 'title' and line.startswith('%%Title:'):
                        lines[i] = f'%%Title: {value}'
                    # Add similar for other props...
            new_ascii = '\n'.join(lines).encode('utf-8')
            new_content = self.content[:30] + new_ascii if self.has_binary_header else new_ascii
        else:
            new_content = self.content

        with open(new_filepath, 'wb') as f:
            f.write(new_content)

# Example usage:
# handler = EPSHandler('example.eps')
# handler.print_properties()
# handler.write('modified.eps', {'title': 'New Title'})
  1. Java class for .EPS handling:
import java.io.*;
import java.nio.*;
import java.util.*;

public class EPSHandler {
    private String filepath;
    private Map<String, String> properties = new HashMap<>();
    private byte[] content;
    private boolean hasBinaryHeader = false;
    private Map<String, Object> binaryHeader = new HashMap<>();

    public EPSHandler(String filepath) {
        this.filepath = filepath;
        readAndDecode();
    }

    private void readAndDecode() {
        try (FileInputStream fis = new FileInputStream(filepath)) {
            content = fis.readAllBytes();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        ByteBuffer bb = ByteBuffer.wrap(content).order(ByteOrder.LITTLE_ENDIAN);

        // Check binary header
        if (content.length >= 30 && bb.getInt(0) == 0xC6D3D0C5) { // Adjusted for little-endian
            hasBinaryHeader = true;
            binaryHeader.put("magic", "0xC5D0D3C6");
            binaryHeader.put("ps_offset", bb.getInt(4));
            binaryHeader.put("ps_length", bb.getInt(8));
            binaryHeader.put("mf_offset", bb.getInt(12));
            binaryHeader.put("mf_length", bb.getInt(16));
            binaryHeader.put("tiff_offset", bb.getInt(20));
            binaryHeader.put("tiff_length", bb.getInt(24));
            binaryHeader.put("checksum", Integer.toHexString(bb.getShort(28) & 0xFFFF).toUpperCase());
            int asciiStart = (int) binaryHeader.get("ps_offset");
            String asciiContent = new String(content, asciiStart, (int) binaryHeader.get("ps_length"));
            parseDSC(asciiContent);
        } else {
            parseDSC(new String(content));
        }
    }

    private void parseDSC(String asciiContent) {
        properties.put("has_binary_header", hasBinaryHeader ? "Yes" : "No");
        properties.putAll(binaryHeader.entrySet().stream().collect(
            HashMap::new, (m, e) -> m.put(e.getKey(), e.getValue().toString()), HashMap::putAll));
        properties.put("version", "");
        properties.put("title", "");
        properties.put("creator", "");
        properties.put("creation_date", "");
        properties.put("bounding_box", "");
        properties.put("language_level", "");
        properties.put("extensions", "");
        properties.put("document_needed_resources", "");
        properties.put("preview_type", "None");

        String[] lines = asciiContent.split("\n");
        for (String line : lines) {
            if (line.startsWith("%!PS-Adobe-3.0 EPSF-3.0")) {
                properties.put("version", line);
            } else if (line.startsWith("%%Title:")) {
                properties.put("title", line.substring(8).trim());
            } else if (line.startsWith("%%Creator:")) {
                properties.put("creator", line.substring(10).trim());
            } else if (line.startsWith("%%CreationDate:")) {
                properties.put("creation_date", line.substring(15).trim());
            } else if (line.startsWith("%%BoundingBox:")) {
                properties.put("bounding_box", line.substring(14).trim());
            } else if (line.startsWith("%%LanguageLevel:")) {
                properties.put("language_level", line.substring(16).trim());
            } else if (line.startsWith("%%Extensions:")) {
                properties.put("extensions", line.substring(13).trim());
            } else if (line.startsWith("%%DocumentNeededResources:")) {
                properties.put("document_needed_resources", line.substring(26).trim());
            } else if (line.startsWith("%%BeginPreview")) {
                properties.put("preview_type", "EPSI");
            }
        }

        if (hasBinaryHeader) {
            if ((int) binaryHeader.get("mf_length") > 0) {
                properties.put("preview_type", "Metafile");
            } else if ((int) binaryHeader.get("tiff_length") > 0) {
                properties.put("preview_type", "TIFF");
            }
        }
    }

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

    public void write(String newFilepath, Map<String, String> modifyProps) throws IOException {
        byte[] newContent = content.clone();
        if (modifyProps != null && !modifyProps.isEmpty()) {
            // Simple replace in ASCII (not full binary handling)
            int asciiStart = hasBinaryHeader ? (int) binaryHeader.get("ps_offset") : 0;
            int asciiLength = hasBinaryHeader ? (int) binaryHeader.get("ps_length") : content.length;
            String ascii = new String(content, asciiStart, asciiLength);
            for (Map.Entry<String, String> entry : modifyProps.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (key.equals("title")) {
                    ascii = ascii.replaceAll("%%Title:.*", "%%Title: " + value);
                }
                // Add similar for others...
            }
            byte[] newAscii = ascii.getBytes();
            newContent = new byte[asciiStart + newAscii.length + (content.length - asciiStart - asciiLength)];
            System.arraycopy(content, 0, newContent, 0, asciiStart);
            System.arraycopy(newAscii, 0, newContent, asciiStart, newAscii.length);
            System.arraycopy(content, asciiStart + asciiLength, newContent, asciiStart + newAscii.length, content.length - asciiStart - asciiLength);
        }

        try (FileOutputStream fos = new FileOutputStream(newFilepath)) {
            fos.write(newContent);
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     EPSHandler handler = new EPSHandler("example.eps");
    //     handler.printProperties();
    //     Map<String, String> mods = new HashMap<>();
    //     mods.put("title", "New Title");
    //     handler.write("modified.eps", mods);
    // }
}
  1. JavaScript class for .EPS handling (Node.js compatible):
const fs = require('fs');

class EPSHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
        this.content = null;
        this.hasBinaryHeader = false;
        this.binaryHeader = {};
        this.readAndDecode();
    }

    readAndDecode() {
        this.content = fs.readFileSync(this.filepath);
        const dv = new DataView(this.content.buffer);

        // Check binary header
        if (this.content.length >= 30 && dv.getUint32(0, true) === 0xC6D3D0C5) { // Little-endian
            this.hasBinaryHeader = true;
            this.binaryHeader = {
                magic: '0xC5D0D3C6',
                psOffset: dv.getUint32(4, true),
                psLength: dv.getUint32(8, true),
                mfOffset: dv.getUint32(12, true),
                mfLength: dv.getUint32(16, true),
                tiffOffset: dv.getUint32(20, true),
                tiffLength: dv.getUint32(24, true),
                checksum: dv.getUint16(28, true).toString(16).toUpperCase()
            };
            const asciiStart = this.binaryHeader.psOffset;
            const asciiContent = this.content.subarray(asciiStart, asciiStart + this.binaryHeader.psLength).toString('utf-8');
            this.parseDSC(asciiContent);
        } else {
            this.parseDSC(this.content.toString('utf-8'));
        }
    }

    parseDSC(asciiContent) {
        this.properties = {
            hasBinaryHeader: this.hasBinaryHeader ? 'Yes' : 'No',
            ...this.binaryHeader,
            version: '',
            title: '',
            creator: '',
            creationDate: '',
            boundingBox: '',
            languageLevel: '',
            extensions: '',
            documentNeededResources: '',
            previewType: 'None'
        };

        const lines = asciiContent.split(/\r?\n/);
        lines.forEach(line => {
            if (line.startsWith('%!PS-Adobe-3.0 EPSF-3.0')) this.properties.version = line;
            else if (line.startsWith('%%Title:')) this.properties.title = line.slice(8).trim();
            else if (line.startsWith('%%Creator:')) this.properties.creator = line.slice(10).trim();
            else if (line.startsWith('%%CreationDate:')) this.properties.creationDate = line.slice(15).trim();
            else if (line.startsWith('%%BoundingBox:')) this.properties.boundingBox = line.slice(14).trim();
            else if (line.startsWith('%%LanguageLevel:')) this.properties.languageLevel = line.slice(16).trim();
            else if (line.startsWith('%%Extensions:')) this.properties.extensions = line.slice(13).trim();
            else if (line.startsWith('%%DocumentNeededResources:')) this.properties.documentNeededResources = line.slice(26).trim();
            else if (line.startsWith('%%BeginPreview')) this.properties.previewType = 'EPSI';
        });

        if (this.hasBinaryHeader) {
            if (this.binaryHeader.mfLength > 0) this.properties.previewType = 'Metafile';
            else if (this.binaryHeader.tiffLength > 0) this.properties.previewType = 'TIFF';
        }
    }

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

    write(newFilepath, modifyProps = null) {
        let newContent = Buffer.from(this.content);
        if (modifyProps) {
            // Simple modify in ASCII
            const asciiStart = this.hasBinaryHeader ? this.binaryHeader.psOffset : 0;
            const asciiLength = this.hasBinaryHeader ? this.binaryHeader.psLength : this.content.length;
            let ascii = this.content.subarray(asciiStart, asciiStart + asciiLength).toString('utf-8');
            for (const [key, value] of Object.entries(modifyProps)) {
                if (key === 'title') {
                    ascii = ascii.replace(/%%Title:.*/, `%%Title: ${value}`);
                }
                // Add similar for others...
            }
            const newAscii = Buffer.from(ascii, 'utf-8');
            newContent = Buffer.concat([
                this.content.subarray(0, asciiStart),
                newAscii,
                this.content.subarray(asciiStart + asciiLength)
            ]);
        }
        fs.writeFileSync(newFilepath, newContent);
    }
}

// Example usage:
// const handler = new EPSHandler('example.eps');
// handler.printProperties();
// handler.write('modified.eps', { title: 'New Title' });
  1. C++ class for .EPS handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <map>
#include <cstdint>
#include <cstring>

class EPSHandler {
private:
    std::string filepath;
    std::map<std::string, std::string> properties;
    std::vector<uint8_t> content;
    bool has_binary_header = false;
    std::map<std::string, uint32_t> binary_header;

    void read_and_decode() {
        std::ifstream file(filepath, std::ios::binary);
        if (!file) {
            std::cerr << "Failed to open file." << std::endl;
            return;
        }
        content.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());

        // Check binary header
        if (content.size() >= 30 && std::memcmp(&content[0], "\xC5\xD0\xD3\xC6", 4) == 0) {
            has_binary_header = true;
            uint32_t magic = *reinterpret_cast<uint32_t*>(&content[0]);
            uint32_t ps_offset = *reinterpret_cast<uint32_t*>(&content[4]);
            uint32_t ps_length = *reinterpret_cast<uint32_t*>(&content[8]);
            uint32_t mf_offset = *reinterpret_cast<uint32_t*>(&content[12]);
            uint32_t mf_length = *reinterpret_cast<uint32_t*>(&content[16]);
            uint32_t tiff_offset = *reinterpret_cast<uint32_t*>(&content[20]);
            uint32_t tiff_length = *reinterpret_cast<uint32_t*>(&content[24]);
            uint16_t checksum = *reinterpret_cast<uint16_t*>(&content[28]);
            binary_header["magic"] = magic;
            binary_header["ps_offset"] = ps_offset;
            binary_header["ps_length"] = ps_length;
            binary_header["mf_offset"] = mf_offset;
            binary_header["mf_length"] = mf_length;
            binary_header["tiff_offset"] = tiff_offset;
            binary_header["tiff_length"] = tiff_length;
            binary_header["checksum"] = checksum;
            std::string ascii_content(content.begin() + ps_offset, content.begin() + ps_offset + ps_length);
            parse_dsc(ascii_content);
        } else {
            std::string ascii_content(content.begin(), content.end());
            parse_dsc(ascii_content);
        }
    }

    void parse_dsc(const std::string& ascii_content) {
        properties["has_binary_header"] = has_binary_header ? "Yes" : "No";
        if (has_binary_header) {
            properties["magic"] = "0xC5D0D3C6";
            properties["ps_offset"] = std::to_string(binary_header["ps_offset"]);
            properties["ps_length"] = std::to_string(binary_header["ps_length"]);
            properties["mf_offset"] = std::to_string(binary_header["mf_offset"]);
            properties["mf_length"] = std::to_string(binary_header["mf_length"]);
            properties["tiff_offset"] = std::to_string(binary_header["tiff_offset"]);
            properties["tiff_length"] = std::to_string(binary_header["tiff_length"]);
            properties["checksum"] = std::to_string(binary_header["checksum"]);
        }
        properties["version"] = "";
        properties["title"] = "";
        properties["creator"] = "";
        properties["creation_date"] = "";
        properties["bounding_box"] = "";
        properties["language_level"] = "";
        properties["extensions"] = "";
        properties["document_needed_resources"] = "";
        properties["preview_type"] = "None";

        size_t pos = 0;
        std::string line;
        while ((pos = ascii_content.find('\n', pos)) != std::string::npos || pos < ascii_content.size()) {
            line = ascii_content.substr(0, pos);
            if (line.rfind("%!PS-Adobe-3.0 EPSF-3.0", 0) == 0) {
                properties["version"] = line;
            } else if (line.rfind("%%Title:", 0) == 0) {
                properties["title"] = line.substr(8);
            } else if (line.rfind("%%Creator:", 0) == 0) {
                properties["creator"] = line.substr(10);
            } else if (line.rfind("%%CreationDate:", 0) == 0) {
                properties["creation_date"] = line.substr(15);
            } else if (line.rfind("%%BoundingBox:", 0) == 0) {
                properties["bounding_box"] = line.substr(14);
            } else if (line.rfind("%%LanguageLevel:", 0) == 0) {
                properties["language_level"] = line.substr(16);
            } else if (line.rfind("%%Extensions:", 0) == 0) {
                properties["extensions"] = line.substr(13);
            } else if (line.rfind("%%DocumentNeededResources:", 0) == 0) {
                properties["document_needed_resources"] = line.substr(26);
            } else if (line.rfind("%%BeginPreview", 0) == 0) {
                properties["preview_type"] = "EPSI";
            }
            if (pos == std::string::npos) break;
            ascii_content = ascii_content.substr(pos + 1);
            pos = 0;
        }

        if (has_binary_header) {
            if (binary_header["mf_length"] > 0) properties["preview_type"] = "Metafile";
            else if (binary_header["tiff_length"] > 0) properties["preview_type"] = "TIFF";
        }
    }

public:
    EPSHandler(const std::string& fp) : filepath(fp) {
        read_and_decode();
    }

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

    void write(const std::string& new_filepath, const std::map<std::string, std::string>& modify_props = {}) {
        std::vector<uint8_t> new_content = content;
        if (!modify_props.empty()) {
            // Simple modify in ASCII
            uint32_t ascii_start = has_binary_header ? binary_header["ps_offset"] : 0;
            uint32_t ascii_length = has_binary_header ? binary_header["ps_length"] : content.size();
            std::string ascii(new_content.begin() + ascii_start, new_content.begin() + ascii_start + ascii_length);
            for (const auto& mod : modify_props) {
                if (mod.first == "title") {
                    size_t pos = ascii.find("%%Title:");
                    if (pos != std::string::npos) {
                        size_t end = ascii.find('\n', pos);
                        ascii.replace(pos, end - pos, "%%Title: " + mod.second);
                    }
                }
                // Add similar for others...
            }
            new_content.erase(new_content.begin() + ascii_start, new_content.begin() + ascii_start + ascii_length);
            new_content.insert(new_content.begin() + ascii_start, ascii.begin(), ascii.end());
        }

        std::ofstream out(new_filepath, std::ios::binary);
        out.write(reinterpret_cast<const char*>(new_content.data()), new_content.size());
    }
};

// Example usage:
// int main() {
//     EPSHandler handler("example.eps");
//     handler.print_properties();
//     std::map<std::string, std::string> mods = {{"title", "New Title"}};
//     handler.write("modified.eps", mods);
//     return 0;
// }