Task 020: .AI File Format

Task 020: .AI File Format

The .AI file format is a proprietary vector graphics format developed by Adobe for Adobe Illustrator. It is based on the PostScript language and encapsulates vector-based drawings, typically in EPS or PDF formats, with additional metadata and features specific to Illustrator. However, Adobe does not publicly provide a complete and detailed specification for the .AI file format, as it is considered proprietary. Some older versions (e.g., AI7) have partial documentation, but modern .AI files (post-2000, starting with Illustrator 9) are more complex and often include embedded PDF or EPS data, making full decoding challenging without Adobe’s proprietary tools or libraries.

Below, I’ll address each task to the best of my ability based on available information, with the caveat that complete decoding of modern .AI files may not be fully achievable due to the proprietary nature and lack of comprehensive public specifications. I’ll focus on the core properties that can be inferred from available documentation and provide simplified classes for reading, writing, and printing properties, acknowledging that some operations (e.g., full decoding) may require third-party libraries or Adobe’s SDK, which is not publicly accessible without enrollment in Adobe’s prerelease program.


1. List of Properties of the .AI File Format Intrinsic to Its File System

Based on available information from sources like Adobe community discussions, file format references, and general knowledge of the .AI format, the intrinsic properties of the .AI file format include:

  • File Extension: .ai
  • MIME Type: application/postscript, application/pdf, or application/illustrator
  • Type of Format: Vector graphics (primarily), with optional embedded raster images
  • Developed By: Adobe Systems
  • Initial Release: 1987
  • File Structure:
  • Prolog: Contains metadata for file interpretation, such as:
  • Bounding box (defines the dimensions of the artwork).
  • Resource information (e.g., fonts, procedure definitions, or “procsets”).
  • Script: Describes graphic elements, including:
  • Setup sequence (initializes resources).
  • Descriptive operators (defines paths, shapes, and text).
  • Trailer (deactivates resources).
  • Optional Embedded Data: May include a PDF-compatible version or EPS data for compatibility.
  • Compression: Optional (can be compressed or uncompressed; older versions required “Use Compression” unchecked for readability).
  • Vector-Based: Uses mathematical paths (Bezier splines) for scalable graphics without quality loss.
  • Supports Layers: Allows multiple layers for organizing complex designs.
  • Supports Transparency: Enables transparent elements in designs.
  • Color Models: Supports RGB, CMYK, LAB, and Grayscale.
  • Metadata: Supports metadata for document properties (e.g., author, creation date).
  • Embeddable: Can embed fonts, images, and other resources.
  • Scripting Support: Supports JavaScript for automation in Illustrator.
  • File Preview Option: May include a bitmap preview for non-PostScript applications.
  • Interlacing: Not supported.
  • Animation: Not supported.
  • Maximum Dimensions: No strict limit (vector-based, scalable).
  • Multipage: Single-page by default, though PDF-compatible versions may support multiple pages.
  • Compatibility: Primarily compatible with Adobe Illustrator, with partial support in Adobe Acrobat, CorelDraw, and other graphics software.
  • Proprietary Nature: Full specification is not publicly available; partial specs exist for older versions (e.g., AI7).

Notes:

  • The .AI format is a “dual path” format, often embedding a PGF (Progressive Graphics File, Adobe’s native format) alongside PDF or EPS data for compatibility.
  • Modern .AI files are essentially enriched PDF files, but decoding requires understanding Illustrator-specific extensions.
  • Without Adobe’s full specification, some properties (e.g., exact encoding of procsets or proprietary metadata) cannot be fully decoded programmatically.

Sources:,,,,,,,


2. Python Class for .AI File Operations

Since .AI is proprietary, fully decoding it requires Adobe’s SDK or libraries like Aspose.PSD, which support limited AI versions (e.g., AI3, AI8). The following Python class uses the PyPDF2 library to read PDF-compatible .AI files (as many modern .AI files embed PDF data) and extracts basic properties. Writing is limited to creating a simple .AI-compatible PDF structure, as full .AI writing is not feasible without proprietary tools.

import PyPDF2
import os
from datetime import datetime

class AIFileHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {
            "file_extension": ".ai",
            "mime_type": "application/pdf",  # Assuming PDF-compatible
            "type": "Vector graphics",
            "developed_by": "Adobe Systems",
            "initial_release": "1987",
            "compression": "Optional",
            "supports_layers": True,
            "supports_transparency": True,
            "color_models": ["RGB", "CMYK", "LAB", "Grayscale"],
            "metadata": {},
            "has_preview": False,
            "interlacing": False,
            "animation": False,
            "max_dimensions": "Unlimited (vector)",
            "multipage": False,
            "compatibility": ["Adobe Illustrator", "Adobe Acrobat", "CorelDraw"]
        }

    def read(self):
        """Read properties from an .AI file (PDF-compatible)."""
        try:
            if not os.path.exists(self.filepath):
                raise FileNotFoundError(f"File {self.filepath} not found.")
            
            # Attempt to read as PDF (modern .AI files often embed PDF)
            with open(self.filepath, "rb") as file:
                pdf = PyPDF2.PdfReader(file)
                self.properties["metadata"] = pdf.metadata or {}
                self.properties["has_preview"] = bool(pdf.metadata.get("/Preview", False))
                self.properties["multipage"] = len(pdf.pages) > 1
                # Note: Detailed vector data (prolog, script) requires Adobe SDK
                print("Read successful. Properties updated.")
        except Exception as e:
            print(f"Error reading .AI file: {e}")

    def write(self, output_path):
        """Write a basic .AI-compatible PDF file (simplified)."""
        try:
            writer = PyPDF2.PdfWriter()
            # Add a blank page (simplified; real .AI needs PostScript data)
            writer.add_blank_page(width=595, height=842)  # A4 size in points
            writer.add_metadata({
                "/Creator": "AIFileHandler",
                "/CreationDate": datetime.now().strftime("D:%Y%m%d%H%M%S")
            })
            with open(output_path, "wb") as file:
                writer.write(file)
            print(f"File written to {output_path}")
        except Exception as e:
            print(f"Error writing .AI file: {e}")

    def print_properties(self):
        """Print all properties to console."""
        print("\n.AI File Properties:")
        for key, value in self.properties.items():
            print(f"{key}: {value}")

# Example usage
if __name__ == "__main__":
    ai_file = AIFileHandler("sample.ai")
    ai_file.read()
    ai_file.print_properties()
    ai_file.write("output.ai")

Limitations:

  • This class assumes PDF compatibility, as many .AI files embed PDF data. Full .AI decoding (e.g., PostScript prolog and script) requires Adobe’s proprietary SDK.
  • Writing is limited to creating a basic PDF structure, as generating a fully compliant .AI file with Illustrator-specific features is not possible without Adobe’s tools.
  • Use libraries like Aspose.PSD for more advanced .AI manipulation (requires licensing).

3. Java Class for .AI File Operations

The Java class uses the Apache PDFBox library to handle PDF-compatible .AI files, similar to the Python approach. Full .AI decoding is not possible without Adobe’s SDK.

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class AIFileHandler {
    private String filepath;
    private Map<String, Object> properties;

    public AIFileHandler(String filepath) {
        this.filepath = filepath;
        this.properties = new HashMap<>();
        initializeProperties();
    }

    private void initializeProperties() {
        properties.put("file_extension", ".ai");
        properties.put("mime_type", "application/pdf");
        properties.put("type", "Vector graphics");
        properties.put("developed_by", "Adobe Systems");
        properties.put("initial_release", "1987");
        properties.put("compression", "Optional");
        properties.put("supports_layers", true);
        properties.put("supports_transparency", true);
        properties.put("color_models", Arrays.asList("RGB", "CMYK", "LAB", "Grayscale"));
        properties.put("metadata", new HashMap<String, String>());
        properties.put("has_preview", false);
        properties.put("interlacing", false);
        properties.put("animation", false);
        properties.put("max_dimensions", "Unlimited (vector)");
        properties.put("multipage", false);
        properties.put("compatibility", Arrays.asList("Adobe Illustrator", "Adobe Acrobat", "CorelDraw"));
    }

    public void read() {
        try {
            File file = new File(filepath);
            if (!file.exists()) {
                throw new Exception("File " + filepath + " not found.");
            }

            PDDocument doc = PDDocument.load(file);
            PDDocumentInformation info = doc.getDocumentInformation();
            Map<String, String> metadata = new HashMap<>();
            if (info.getMetadataKeys() != null) {
                for (String key : info.getMetadataKeys()) {
                    metadata.put(key, info.getMetadata().get(key));
                }
            }
            properties.put("metadata", metadata);
            properties.put("has_preview", metadata.containsKey("Preview"));
            properties.put("multipage", doc.getNumberOfPages() > 1);
            System.out.println("Read successful. Properties updated.");
            doc.close();
        } catch (Exception e) {
            System.out.println("Error reading .AI file: " + e.getMessage());
        }
    }

    public void write(String outputPath) {
        try {
            PDDocument doc = new PDDocument();
            doc.addPage(new PDPage());
            PDDocumentInformation info = doc.getDocumentInformation();
            info.setCreator("AIFileHandler");
            info.setCreationDate(java.util.Calendar.getInstance());
            doc.save(outputPath);
            doc.close();
            System.out.println("File written to " + outputPath);
        } catch (Exception e) {
            System.out.println("Error writing .AI file: " + e.getMessage());
        }
    }

    public void printProperties() {
        System.out.println("\n.AI File Properties:");
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public static void main(String[] args) {
        AIFileHandler aiFile = new AIFileHandler("sample.ai");
        aiFile.read();
        aiFile.printProperties();
        aiFile.write("output.ai");
    }
}

Limitations:

  • Similar to Python, this relies on PDF compatibility. Full .AI parsing is not possible without Adobe’s SDK.
  • Writing creates a basic PDF, not a fully compliant .AI file.

4. JavaScript Class for .AI File Operations

JavaScript lacks native file parsing for .AI files, and browser-based file handling is limited. This class uses the pdf.js library to read PDF-compatible .AI files and provides a basic structure for properties. Writing is simulated, as full .AI creation is not feasible in JavaScript without server-side libraries.

const pdfjsLib = require('pdfjs-dist'); // Requires pdf.js library (e.g., via npm)

class AIFileHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {
            file_extension: '.ai',
            mime_type: 'application/pdf',
            type: 'Vector graphics',
            developed_by: 'Adobe Systems',
            initial_release: '1987',
            compression: 'Optional',
            supports_layers: true,
            supports_transparency: true,
            color_models: ['RGB', 'CMYK', 'LAB', 'Grayscale'],
            metadata: {},
            has_preview: false,
            interlacing: false,
            animation: false,
            max_dimensions: 'Unlimited (vector)',
            multipage: false,
            compatibility: ['Adobe Illustrator', 'Adobe Acrobat', 'CorelDraw']
        };
    }

    async read() {
        try {
            // Note: Requires Node.js or a server environment for file access
            const fs = require('fs');
            if (!fs.existsSync(this.filepath)) {
                throw new Error(`File ${this.filepath} not found.`);
            }

            const data = new Uint8Array(fs.readFileSync(this.filepath));
            const pdf = await pdfjsLib.getDocument(data).promise;
            this.properties.metadata = pdf.metadata?.metadata || {};
            this.properties.has_preview = !!pdf.metadata?.metadata?.Preview;
            this.properties.multipage = (await pdf.numPages) > 1;
            console.log('Read successful. Properties updated.');
        } catch (error) {
            console.error('Error reading .AI file:', error.message);
        }
    }

    async write(outputPath) {
        try {
            // Writing a full .AI file is not feasible in JS; simulate with basic PDF
            console.log(`Simulating write to ${outputPath} (actual .AI writing requires Adobe tools)`);
            // For a real implementation, use a server-side library like Aspose.PSD
        } catch (error) {
            console.error('Error writing .AI file:', error.message);
        }
    }

    printProperties() {
        console.log('\n.AI File Properties:');
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${JSON.stringify(value)}`);
        }
    }
}

// Example usage (Node.js)
(async () => {
    const aiFile = new AIFileHandler('sample.ai');
    await aiFile.read();
    aiFile.printProperties();
    await aiFile.write('output.ai');
})();

Limitations:

  • Requires a Node.js environment with pdfjs-dist for PDF parsing.
  • Writing is not fully implemented, as JavaScript cannot natively create .AI files.
  • Full decoding requires Adobe’s SDK or server-side tools.

5. C Class for .AI File Operations

C lacks high-level libraries for .AI parsing, and the proprietary nature of the format makes full decoding difficult. This class uses a basic file I/O approach to read metadata (assuming PDF compatibility) and prints properties. Writing is limited to creating a simple PDF-like structure.

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

typedef struct {
    const char* file_extension;
    const char* mime_type;
    const char* type;
    const char* developed_by;
    const char* initial_release;
    const char* compression;
    bool supports_layers;
    bool supports_transparency;
    const char* color_models[4];
    char metadata[256];
    bool has_preview;
    bool interlacing;
    bool animation;
    const char* max_dimensions;
    bool multipage;
    const char* compatibility[3];
} AIProperties;

typedef struct {
    char* filepath;
    AIProperties properties;
} AIFileHandler;

void initAIFileHandler(AIFileHandler* handler, const char* filepath) {
    handler->filepath = strdup(filepath);
    handler->properties.file_extension = ".ai";
    handler->properties.mime_type = "application/pdf";
    handler->properties.type = "Vector graphics";
    handler->properties.developed_by = "Adobe Systems";
    handler->properties.initial_release = "1987";
    handler->properties.compression = "Optional";
    handler->properties.supports_layers = true;
    handler->properties.supports_transparency = true;
    handler->properties.color_models[0] = "RGB";
    handler->properties.color_models[1] = "CMYK";
    handler->properties.color_models[2] = "LAB";
    handler->properties.color_models[3] = "Grayscale";
    strcpy(handler->properties.metadata, "{}");
    handler->properties.has_preview = false;
    handler->properties.interlacing = false;
    handler->properties.animation = false;
    handler->properties.max_dimensions = "Unlimited (vector)";
    handler->properties.multipage = false;
    handler->properties.compatibility[0] = "Adobe Illustrator";
    handler->properties.compatibility[1] = "Adobe Acrobat";
    handler->properties.compatibility[2] = "CorelDraw";
}

void readAIFile(AIFileHandler* handler) {
    FILE* file = fopen(handler->filepath, "rb");
    if (!file) {
        printf("Error: File %s not found.\n", handler->filepath);
        return;
    }

    // Simplified: Check for PDF header (many .AI files start with %PDF)
    char buffer[5];
    if (fread(buffer, 1, 4, file) == 4) {
        buffer[4] = '\0';
        if (strcmp(buffer, "%PDF") == 0) {
            strcpy(handler->properties.metadata, "{\"format\": \"PDF-compatible\"}");
            handler->properties.has_preview = true; // Assume preview if PDF
        }
    }
    fclose(file);
    printf("Read successful. Properties updated.\n");
}

void writeAIFile(AIFileHandler* handler, const char* outputPath) {
    FILE* file = fopen(outputPath, "wb");
    if (!file) {
        printf("Error: Could not write to %s.\n", outputPath);
        return;
    }

    // Write a minimal PDF header (simplified)
    fprintf(file, "%%PDF-1.4\n%%%%EOF\n");
    fclose(file);
    printf("File written to %s\n", outputPath);
}

void printProperties(AIFileHandler* handler) {
    printf("\n.AI File Properties:\n");
    printf("file_extension: %s\n", handler->properties.file_extension);
    printf("mime_type: %s\n", handler->properties.mime_type);
    printf("type: %s\n", handler->properties.type);
    printf("developed_by: %s\n", handler->properties.developed_by);
    printf("initial_release: %s\n", handler->properties.initial_release);
    printf("compression: %s\n", handler->properties.compression);
    printf("supports_layers: %s\n", handler->properties.supports_layers ? "true" : "false");
    printf("supports_transparency: %s\n", handler->properties.supports_transparency ? "true" : "false");
    printf("color_models: [%s, %s, %s, %s]\n", 
           handler->properties.color_models[0], 
           handler->properties.color_models[1], 
           handler->properties.color_models[2], 
           handler->properties.color_models[3]);
    printf("metadata: %s\n", handler->properties.metadata);
    printf("has_preview: %s\n", handler->properties.has_preview ? "true" : "false");
    printf("interlacing: %s\n", handler->properties.interlacing ? "true" : "false");
    printf("animation: %s\n", handler->properties.animation ? "true" : "false");
    printf("max_dimensions: %s\n", handler->properties.max_dimensions);
    printf("multipage: %s\n", handler->properties.multipage ? "true" : "false");
    printf("compatibility: [%s, %s, %s]\n", 
           handler->properties.compatibility[0], 
           handler->properties.compatibility[1], 
           handler->properties.compatibility[2]);
}

void freeAIFileHandler(AIFileHandler* handler) {
    free(handler->filepath);
}

int main() {
    AIFileHandler handler;
    initAIFileHandler(&handler, "sample.ai");
    readAIFile(&handler);
    printProperties(&handler);
    writeAIFile(&handler, "output.ai");
    freeAIFileHandler(&handler);
    return 0;
}

Limitations:

  • Parsing is rudimentary and assumes PDF compatibility.
  • Writing creates a minimal PDF-like file, not a true .AI file.
  • Full .AI support requires Adobe’s SDK or libraries like Aspose.PSD.

General Notes and Limitations

  • Proprietary Format: The .AI format’s full specification is not publicly available, limiting the ability to fully decode or create .AI files. The classes above rely on PDF compatibility, which is partial and may not capture Illustrator-specific features (e.g., prolog, script, or proprietary metadata).
  • Third-Party Libraries: Libraries like Aspose.PSD or Adobe’s SDK (available through Adobe’s prerelease program) offer partial support for specific .AI versions (e.g., AI3, AI8), but they are not free or fully comprehensive.
  • Workaround: For viewing .AI files without Illustrator, renaming to .pdf may allow partial access in PDF readers, but this doesn’t provide full vector data or Illustrator-specific features.
  • Recommendations: For production use, consider using Adobe Illustrator’s scripting API (JavaScript-based) or contacting Adobe’s prerelease program for access to detailed specifications (http://adobeprerelease.com/).
  • Sources Used: The properties and approach are informed by web sources, including Adobe community discussions, file format references, and technical documentation.,,,,,,,

If you need further assistance or want to explore specific aspects (e.g., using Adobe’s SDK or third-party libraries), please let me know!

1. List of all the properties of the .AI file format intrinsic to its file system

The .AI file format is proprietary to Adobe, and full specifications for modern versions (AI9+) are not publicly available as they are based on PDF with embedded proprietary data. Based on the available documentation for legacy versions (up to AI7/AI8), which are PostScript-based, the intrinsic properties are primarily metadata stored in the file's header comments. These properties include structural, descriptive, and configuration details that define the file's content, compatibility, and rendering behavior. The list below compiles all mentioned properties from the specification, including header comments, version-specific tags, and related metadata elements that can be parsed, read, and modified.

  • Creator: The software that created the file (e.g., "Adobe Illustrator(r) 6.0").
  • For: The user and organization (e.g., "(username) (organization)").
  • Title: The title of the illustration (e.g., "(illustration title)").
  • CreationDate: Date and time of creation (e.g., "(date) (time)").
  • BoundingBox: Integer coordinates for the bounding box (llx lly urx ury, e.g., "224 631 268 661").
  • HiResBoundingBox: High-resolution floating-point bounding box (e.g., "224.065 631 268 660.5208").
  • DocumentProcessColors: List of process colors used (e.g., "Cyan Magenta Yellow").
  • DocumentCustomColors: List of custom colors (e.g., "(PANTONE 156 CV)").
  • CMYKCustomColor: CMYK definitions for custom colors (e.g., "0.1 0.5 0.0 0.0 (PANTONE 156 CV)").
  • RGBCustomColor: RGB definitions for custom colors (e.g., "0.8 0.6 0.4 (Custom RGB)").
  • DocumentFonts: List of fonts used (e.g., "CooperBlack Minion-Regular").
  • DocumentNeededFonts: Fonts needed but not supplied.
  • DocumentSuppliedFonts: Fonts supplied in the file.
  • DocumentSuppliedResources: Other resources supplied (e.g., "procset AdobeCustomColor").
  • DocumentNeededResources: Resources needed but not supplied.
  • AI5_FileFormat: File format version (e.g., "3" for AI7).
  • AI5_ArtSize: Artboard size (e.g., "612 792").
  • AI5_RulerUnits: Unit of measurement (0=inches, 1=millimeters, 2=points, 3=picas, 4=centimeters).
  • AI5_ArtFlags: Flags for features like cropmarks, transparency (e.g., binary flags).
  • AI5_TargetResolution: Target output resolution (e.g., "300").
  • AI5_NumLayers: Number of layers in the document.
  • AI5_OpenToView: View settings on open (e.g., position, zoom).
  • AI5_OpenViewLayers: Layers visible on open.
  • AI3_ColorUsage: Color mode (e.g., "Black&White" or "Color").
  • AI3_DocumentPreview: Preview type (e.g., "EPS TIFF").
  • AI3_TemplateBox: Template bounding box (llx lly urx ury).
  • AI3_TemplateFile: Path to template file.
  • AI3_TileBox: Tile box coordinates.
  • AI3_PaperRect: Paper rectangle dimensions.
  • AI7_GridSettings: Grid control settings (e.g., spacing, style).
  • AI6_ColorSeparationSet: Color separation settings.
  • AI7_ImageSettings: Image placement and resolution settings.
  • Template: Template filename (Windows-specific).
  • PageOrigin: Page origin coordinates (Windows-specific).
  • PrinterName: Printer name (Windows-specific).
  • PrinterRect: Printer rectangle (Windows-specific).
  • IncludeFont: Fonts to include.
  • IncludeResource: Resources to include.

Note: For modern .AI files (PDF-based), properties are stored in the PDF /Info dictionary and XMP metadata, but parsing requires a full PDF decoder, which is complex and beyond basic implementation due to the proprietary nature. The classes below assume the legacy PostScript-based format for parsing text-based headers.

2. Python class

import os

class AIFile:
    def __init__(self, filename):
        if not filename.lower().endswith('.ai'):
            raise ValueError("File must have .ai extension")
        self.filename = filename
        self.properties = {}
        self.lines = []
        self.load()

    def load(self):
        with open(self.filename, 'r') as f:
            self.lines = f.readlines()
        in_header = True
        for line in self.lines:
            if in_header and '%%EndComments' in line:
                in_header = False
            if in_header and (line.startswith('%%') or line.startswith('%AI') or line.startswith('%_')):
                key, value = self.parse_line(line)
                if key:
                    self.properties[key] = value

    def parse_line(self, line):
        stripped = line.strip()
        if ':' in stripped:
            key, value = stripped.split(':', 1)
            key = key.lstrip('%').strip()
            value = value.strip()
        else:
            parts = stripped.split()
            if len(parts) > 1:
                key = parts[0].lstrip('%').strip()
                value = ' '.join(parts[1:])
            else:
                return None, None
        return key, value

    def read_property(self, key):
        return self.properties.get(key)

    def write_property(self, key, value):
        self.properties[key] = value

    def save(self):
        new_lines = []
        for line in self.lines:
            stripped = line.strip()
            if stripped.startswith('%%') or stripped.startswith('%AI') or stripped.startswith('%_'):
                key, _ = self.parse_line(line)
                if key in self.properties:
                    new_line = self.format_line(key, self.properties[key])
                    new_lines.append(new_line + '\n')
                    continue
            new_lines.append(line)
        with open(self.filename, 'w') as f:
            f.writelines(new_lines)

    def format_line(self, key, value):
        if key.startswith('AI') or key.startswith('_'):
            prefix = '%'
            separator = ' '
        else:
            prefix = '%%'
            separator = ': '
        return f"{prefix}{key}{separator}{value}"

3. Java class

import java.io.*;
import java.util.*;

public class AIFile {
    private String filename;
    private Map<String, String> properties = new HashMap<>();
    private List<String> lines = new ArrayList<>();

    public AIFile(String filename) throws IOException {
        if (!filename.toLowerCase().endsWith(".ai")) {
            throw new IllegalArgumentException("File must have .ai extension");
        }
        this.filename = filename;
        load();
    }

    private void load() throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            String line;
            boolean inHeader = true;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
                if (inHeader && line.contains("%%EndComments")) {
                    inHeader = false;
                }
                if (inHeader && (line.startsWith("%%") || line.startsWith("%AI") || line.startsWith("%_"))) {
                    String[] parsed = parseLine(line);
                    if (parsed[0] != null) {
                        properties.put(parsed[0], parsed[1]);
                    }
                }
            }
        }
    }

    private String[] parseLine(String line) {
        String stripped = line.trim();
        String key = null;
        String value = null;
        if (stripped.contains(":")) {
            int index = stripped.indexOf(":");
            key = stripped.substring(0, index).replaceAll("^%+", "").trim();
            value = stripped.substring(index + 1).trim();
        } else {
            String[] parts = stripped.split("\\s+");
            if (parts.length > 1) {
                key = parts[0].replaceAll("^%+", "").trim();
                value = String.join(" ", Arrays.copyOfRange(parts, 1, parts.length));
            }
        }
        return new String[]{key, value};
    }

    public String readProperty(String key) {
        return properties.get(key);
    }

    public void writeProperty(String key, String value) {
        properties.put(key, value);
    }

    public void save() throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
            for (String line : lines) {
                String stripped = line.trim();
                if (stripped.startsWith("%%") || stripped.startsWith("%AI") || stripped.startsWith("%_")) {
                    String[] parsed = parseLine(line);
                    if (parsed[0] != null && properties.containsKey(parsed[0])) {
                        String newLine = formatLine(parsed[0], properties.get(parsed[0]));
                        writer.write(newLine + "\n");
                        continue;
                    }
                }
                writer.write(line + "\n");
            }
        }
    }

    private String formatLine(String key, String value) {
        String prefix = key.startsWith("AI") || key.startsWith("_") ? "%" : "%%";
        String separator = value.matches("^\\d+|\\(.*") ? " " : ": ";
        return prefix + key + separator + value;
    }
}

4. Javascript class

const fs = require('fs');

class AIFile {
    constructor(filename) {
        if (!filename.toLowerCase().endsWith('.ai')) {
            throw new Error('File must have .ai extension');
        }
        this.filename = filename;
        this.properties = {};
        this.lines = [];
        this.load();
    }

    load() {
        const data = fs.readFileSync(this.filename, 'utf8');
        this.lines = data.split('\n');
        let inHeader = true;
        for (let line of this.lines) {
            if (inHeader && line.includes('%%EndComments')) {
                inHeader = false;
            }
            if (inHeader && (line.startsWith('%%') || line.startsWith('%AI') || line.startsWith('%_'))) {
                const [key, value] = this.parseLine(line);
                if (key) {
                    this.properties[key] = value;
                }
            }
        }
    }

    parseLine(line) {
        const stripped = line.trim();
        let key = null;
        let value = null;
        if (stripped.includes(':')) {
            [key, value] = stripped.split(':', 2);
            key = key.replace(/^%+/, '').trim();
            value = value.trim();
        } else {
            const parts = stripped.split(/\s+/);
            if (parts.length > 1) {
                key = parts[0].replace(/^%+/, '').trim();
                value = parts.slice(1).join(' ');
            }
        }
        return [key, value];
    }

    readProperty(key) {
        return this.properties[key];
    }

    writeProperty(key, value) {
        this.properties[key] = value;
    }

    save() {
        const newLines = [];
        for (let line of this.lines) {
            const stripped = line.trim();
            if (stripped.startsWith('%%') || stripped.startsWith('%AI') || stripped.startsWith('%_')) {
                const [key] = this.parseLine(line);
                if (key && key in this.properties) {
                    const newLine = this.formatLine(key, this.properties[key]);
                    newLines.push(newLine);
                    continue;
                }
            }
            newLines.push(line);
        }
        fs.writeFileSync(this.filename, newLines.join('\n'));
    }

    formatLine(key, value) {
        const prefix = (key.startsWith('AI') || key.startsWith('_')) ? '%' : '%%';
        const separator = /^\d+|^\(/.test(value) ? ' ' : ': ';
        return `${prefix}${key}${separator}${value}`;
    }
}

5. C class (implemented as C++ class for class support)

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

class AIFile {
private:
    std::string filename;
    std::map<std::string, std::string> properties;
    std::vector<std::string> lines;

    void load() {
        std::ifstream file(filename);
        if (!file) {
            throw std::runtime_error("Cannot open file");
        }
        std::string line;
        bool inHeader = true;
        while (std::getline(file, line)) {
            lines.push_back(line);
            if (inHeader && line.find("%%EndComments") != std::string::npos) {
                inHeader = false;
            }
            if (inHeader && (line.rfind("%%", 0) == 0 || line.rfind("%AI", 0) == 0 || line.rfind("%_", 0) == 0)) {
                auto parsed = parseLine(line);
                if (!parsed.first.empty()) {
                    properties[parsed.first] = parsed.second;
                }
            }
        }
        file.close();
    }

    std::pair<std::string, std::string> parseLine(const std::string& line) {
        std::string stripped = line;
        // Trim whitespace
        stripped.erase(0, stripped.find_first_not_of(" \t"));
        stripped.erase(stripped.find_last_not_of(" \t") + 1);
        std::string key = "";
        std::string value = "";
        size_t colonPos = stripped.find(':');
        if (colonPos != std::string::npos) {
            key = stripped.substr(0, colonPos);
            key.erase(0, key.find_first_not_of("%"));
            value = stripped.substr(colonPos + 1);
            // Trim value
            value.erase(0, value.find_first_not_of(" \t"));
        } else {
            std::regex wordRegex(R"(\S+)");
            auto words_begin = std::sregex_iterator(stripped.begin(), stripped.end(), wordRegex);
            auto words_end = std::sregex_iterator();
            if (std::distance(words_begin, words_end) > 1) {
                key = words_begin->str();
                key.erase(0, key.find_first_not_of("%"));
                ++words_begin;
                value = "";
                for (std::sregex_iterator i = words_begin; i != words_end; ++i) {
                    value += i->str() + " ";
                }
                value.pop_back(); // Remove trailing space
            }
        }
        return {key, value};
    }

    std::string formatLine(const std::string& key, const std::string& value) {
        std::string prefix = (key.rfind("AI", 0) == 0 || key.rfind("_", 0) == 0) ? "%" : "%%";
        std::string separator = (std::isdigit(value[0]) || value[0] == '(') ? " " : ": ";
        return prefix + key + separator + value;
    }

public:
    AIFile(const std::string& fname) : filename(fname) {
        if (filename.size() < 3 || filename.substr(filename.size() - 3) != ".ai") {
            throw std::invalid_argument("File must have .ai extension");
        }
        load();
    }

    std::string readProperty(const std::string& key) {
        auto it = properties.find(key);
        return (it != properties.end()) ? it->second : "";
    }

    void writeProperty(const std::string& key, const std::string& value) {
        properties[key] = value;
    }

    void save() {
        std::ofstream file(filename);
        if (!file) {
            throw std::runtime_error("Cannot save file");
        }
        for (const auto& line : lines) {
            std::string stripped = line;
            stripped.erase(0, stripped.find_first_not_of(" \t"));
            stripped.erase(stripped.find_last_not_of(" \t") + 1);
            if (stripped.rfind("%%", 0) == 0 || stripped.rfind("%AI", 0) == 0 || stripped.rfind("%_", 0) == 0) {
                auto parsed = parseLine(line);
                if (!parsed.first.empty() && properties.find(parsed.first) != properties.end()) {
                    std::string newLine = formatLine(parsed.first, properties[parsed.first]);
                    file << newLine << std::endl;
                    continue;
                }
            }
            file << line << std::endl;
        }
        file.close();
    }
};