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:
- https://filesamples.com/samples/font/pfb/fontawesome-webfont.pfb
- https://filesamples.com/samples/font/pfb/Lato-Regular.pfb
Ghost blog embedded HTML/JavaScript for drag-and-drop .PFB file dump:
Drag and drop a .PFB file here
- 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])
- 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]);
}
}
}
- 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]);
}
- 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;
}