Task 535: .PFB File Format

Task 535: .PFB File Format

File Format Specifications for .PFB

The .PFB (Printer Font Binary) file format is a binary container for Adobe PostScript Type 1 fonts. It is essentially a segmented format that wraps the font data to allow efficient storage, with parts in ASCII and binary. The full specification is documented in the Adobe Type 1 Font Format (version 1.1), available at https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf, with supplements in https://adobe-type-tools.github.io/font-tech-notes/pdfs/5015.Type1_Supp.pdf.

List of all properties intrinsic to the .PFB file format:

  • Magic/start byte: 0x80 (decimal 128) at the beginning of each segment header.
  • Segment types: 1 (ASCII text section), 2 (binary data section), 3 (end-of-file marker).
  • Length field: 4-byte unsigned integer in little-endian order, specifying the size of the segment data following the header.
  • Structure: Typically consists of exactly three segments (type 1 ASCII, type 2 binary, type 1 ASCII), followed by a type 3 EOF marker.
  • Header size: 6 bytes per segment (0x80 + type + 4-byte length).
  • EOF marker: 0x80 followed by 0x03, with no length or data.
  • Content constraints: First and third segments must be valid ASCII (printable characters, often PostScript code); second segment is raw binary (typically eexec-encrypted font data).
  • File size: Variable, but typically smaller than equivalent .PFA (ASCII) files due to binary encoding.
  • Endianness: Little-endian for length fields.
  • No version field: The format does not include a built-in version number; compatibility is assumed based on structure.

Two direct download links for .PFB files:

Ghost blog embedded HTML/JavaScript for drag-and-drop .PFB file dump:

PFB File Properties Dumper
Drag and drop a .PFB file here
  1. Python class for .PFB handling:
import struct
import sys

class PFBHandler:
    def __init__(self, filename):
        self.filename = filename
        self.segments = []
        self.read_and_decode()

    def read_and_decode(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        pos = 0
        while pos < len(data):
            if data[pos] != 0x80:
                raise ValueError("Invalid PFB start byte")
            pos += 1
            type_ = data[pos]
            pos += 1
            if type_ == 3:
                break
            length = struct.unpack('<I', data[pos:pos+4])[0]
            pos += 4
            segment_data = data[pos:pos+length]
            self.segments.append((type_, length, segment_data))
            pos += length

    def print_properties(self):
        print("PFB Properties:")
        for i, (type_, length, _) in enumerate(self.segments, 1):
            print(f"Segment {i}: Type {type_} (1=ASCII, 2=Binary), Length: {length}")
        print(f"Total segments: {len(self.segments)}")
        print("EOF marker: Present")

    def write(self, output_filename):
        with open(output_filename, 'wb') as f:
            for type_, length, data in self.segments:
                f.write(b'\x80')
                f.write(bytes([type_]))
                f.write(struct.pack('<I', length))
                f.write(data)
            f.write(b'\x80\x03')

# Example usage:
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python script.py input.pfb [output.pfb]")
        sys.exit(1)
    handler = PFBHandler(sys.argv[1])
    handler.print_properties()
    if len(sys.argv) > 2:
        handler.write(sys.argv[2])
  1. Java class for .PFB handling:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class PFBHandler {
    private String filename;
    private Segment[] segments;
    private int segmentCount;

    static class Segment {
        int type;
        int length;
        byte[] data;

        Segment(int type, int length, byte[] data) {
            this.type = type;
            this.length = length;
            this.data = data;
        }
    }

    public PFBHandler(String filename) throws IOException {
        this.filename = filename;
        readAndDecode();
    }

    private void readAndDecode() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) > 0) {
                baos.write(buffer, 0, len);
            }
            byte[] data = baos.toByteArray();

            int pos = 0;
            segmentCount = 0;
            segments = new Segment[3]; // Typically 3

            while (pos < data.length) {
                if (data[pos] != (byte) 0x80) {
                    throw new IOException("Invalid PFB start byte");
                }
                pos++;
                int type = data[pos] & 0xFF;
                pos++;
                if (type == 3) {
                    break;
                }
                ByteBuffer bb = ByteBuffer.wrap(data, pos, 4).order(ByteOrder.LITTLE_ENDIAN);
                int length = bb.getInt();
                pos += 4;
                byte[] segmentData = new byte[length];
                System.arraycopy(data, pos, segmentData, 0, length);
                segments[segmentCount] = new Segment(type, length, segmentData);
                segmentCount++;
                pos += length;
            }
        }
    }

    public void printProperties() {
        System.out.println("PFB Properties:");
        for (int i = 0; i < segmentCount; i++) {
            System.out.printf("Segment %d: Type %d (1=ASCII, 2=Binary), Length: %d%n",
                    i + 1, segments[i].type, segments[i].length);
        }
        System.out.printf("Total segments: %d%n", segmentCount);
        System.out.println("EOF marker: Present");
    }

    public void write(String outputFilename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            for (int i = 0; i < segmentCount; i++) {
                fos.write(0x80);
                fos.write(segments[i].type);
                ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
                bb.putInt(segments[i].length);
                fos.write(bb.array());
                fos.write(segments[i].data);
            }
            fos.write(0x80);
            fos.write(3);
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            System.out.println("Usage: java PFBHandler input.pfb [output.pfb]");
            System.exit(1);
        }
        PFBHandler handler = new PFBHandler(args[0]);
        handler.printProperties();
        if (args.length > 1) {
            handler.write(args[1]);
        }
    }
}
  1. JavaScript class for .PFB handling (Node.js compatible):
const fs = require('fs');

class PFBHandler {
    constructor(filename) {
        this.filename = filename;
        this.segments = [];
        this.readAndDecode();
    }

    readAndDecode() {
        const data = fs.readFileSync(this.filename);
        let pos = 0;
        while (pos < data.length) {
            if (data[pos] !== 0x80) {
                throw new Error('Invalid PFB start byte');
            }
            pos++;
            const type = data[pos];
            pos++;
            if (type === 3) {
                break;
            }
            const length = data.readUInt32LE(pos);
            pos += 4;
            const segmentData = data.slice(pos, pos + length);
            this.segments.push({ type, length, data: segmentData });
            pos += length;
        }
    }

    printProperties() {
        console.log('PFB Properties:');
        this.segments.forEach((seg, i) => {
            console.log(`Segment ${i + 1}: Type ${seg.type} (1=ASCII, 2=Binary), Length: ${seg.length}`);
        });
        console.log(`Total segments: ${this.segments.length}`);
        console.log('EOF marker: Present');
    }

    write(outputFilename) {
        const buffers = [];
        this.segments.forEach(seg => {
            buffers.push(Buffer.from([0x80, seg.type]));
            const lenBuf = Buffer.alloc(4);
            lenBuf.writeUInt32LE(seg.length);
            buffers.push(lenBuf);
            buffers.push(seg.data);
        });
        buffers.push(Buffer.from([0x80, 0x03]));
        fs.writeFileSync(outputFilename, Buffer.concat(buffers));
    }
}

// Example usage:
// node script.js input.pfb [output.pfb]
if (process.argv.length < 3) {
    console.log('Usage: node script.js input.pfb [output.pfb]');
    process.exit(1);
}
const handler = new PFBHandler(process.argv[2]);
handler.printProperties();
if (process.argv[3]) {
    handler.write(process.argv[3]);
}
  1. C++ class for .PFB handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>

struct Segment {
    uint8_t type;
    uint32_t length;
    std::vector<uint8_t> data;
};

class PFBHandler {
private:
    std::string filename;
    std::vector<Segment> segments;

public:
    PFBHandler(const std::string& fn) : filename(fn) {
        readAndDecode();
    }

    void readAndDecode() {
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Cannot open file");
        }
        std::vector<uint8_t> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        size_t pos = 0;
        while (pos < data.size()) {
            if (data[pos] != 0x80) {
                throw std::runtime_error("Invalid PFB start byte");
            }
            ++pos;
            uint8_t type = data[pos];
            ++pos;
            if (type == 3) {
                break;
            }
            uint32_t length = 0;
            memcpy(&length, &data[pos], sizeof(length)); // Assumes little-endian host or handle conversion
            pos += 4;
            Segment seg;
            seg.type = type;
            seg.length = length;
            seg.data.resize(length);
            memcpy(seg.data.data(), &data[pos], length);
            segments.push_back(seg);
            pos += length;
        }
    }

    void printProperties() const {
        std::cout << "PFB Properties:" << std::endl;
        for (size_t i = 0; i < segments.size(); ++i) {
            std::cout << "Segment " << (i + 1) << ": Type " << static_cast<int>(segments[i].type)
                      << " (1=ASCII, 2=Binary), Length: " << segments[i].length << std::endl;
        }
        std::cout << "Total segments: " << segments.size() << std::endl;
        std::cout << "EOF marker: Present" << std::endl;
    }

    void write(const std::string& outputFilename) const {
        std::ofstream file(outputFilename, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Cannot open output file");
        }
        for (const auto& seg : segments) {
            file.put(0x80);
            file.put(seg.type);
            uint32_t len = seg.length; // Assumes little-endian
            file.write(reinterpret_cast<const char*>(&len), sizeof(len));
            file.write(reinterpret_cast<const char*>(seg.data.data()), seg.length);
        }
        file.put(0x80);
        file.put(0x03);
    }
};

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " input.pfb [output.pfb]" << std::endl;
        return 1;
    }
    try {
        PFBHandler handler(argv[1]);
        handler.printProperties();
        if (argc > 2) {
            handler.write(argv[2]);
        }
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}