|
|
import isEqual from 'deep-equal';
|
|
|
import r from 'restructure';
|
|
|
import CFFOperand from './CFFOperand';
|
|
|
import { PropertyDescriptor } from 'restructure/src/utils';
|
|
|
|
|
|
export default class CFFDict {
|
|
|
constructor(ops = []) {
|
|
|
this.ops = ops;
|
|
|
this.fields = {};
|
|
|
for (let field of ops) {
|
|
|
let key = Array.isArray(field[0]) ? field[0][0] << 8 | field[0][1] : field[0];
|
|
|
this.fields[key] = field;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
decodeOperands(type, stream, ret, operands) {
|
|
|
if (Array.isArray(type)) {
|
|
|
return operands.map((op, i) => this.decodeOperands(type[i], stream, ret, [op]));
|
|
|
} else if (type.decode != null) {
|
|
|
return type.decode(stream, ret, operands);
|
|
|
} else {
|
|
|
switch (type) {
|
|
|
case 'number':
|
|
|
case 'offset':
|
|
|
case 'sid':
|
|
|
return operands[0];
|
|
|
case 'boolean':
|
|
|
return !!operands[0];
|
|
|
default:
|
|
|
return operands;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
encodeOperands(type, stream, ctx, operands) {
|
|
|
if (Array.isArray(type)) {
|
|
|
return operands.map((op, i) => this.encodeOperands(type[i], stream, ctx, op)[0]);
|
|
|
} else if (type.encode != null) {
|
|
|
return type.encode(stream, operands, ctx);
|
|
|
} else if (typeof operands === 'number') {
|
|
|
return [operands];
|
|
|
} else if (typeof operands === 'boolean') {
|
|
|
return [+operands];
|
|
|
} else if (Array.isArray(operands)) {
|
|
|
return operands;
|
|
|
} else {
|
|
|
return [operands];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
decode(stream, parent) {
|
|
|
let end = stream.pos + parent.length;
|
|
|
let ret = {};
|
|
|
let operands = [];
|
|
|
|
|
|
// define hidden properties
|
|
|
Object.defineProperties(ret, {
|
|
|
parent: { value: parent },
|
|
|
_startOffset: { value: stream.pos }
|
|
|
});
|
|
|
|
|
|
// fill in defaults
|
|
|
for (let key in this.fields) {
|
|
|
let field = this.fields[key];
|
|
|
ret[field[1]] = field[3];
|
|
|
}
|
|
|
|
|
|
while (stream.pos < end) {
|
|
|
let b = stream.readUInt8();
|
|
|
if (b < 28) {
|
|
|
if (b === 12) {
|
|
|
b = (b << 8) | stream.readUInt8();
|
|
|
}
|
|
|
|
|
|
let field = this.fields[b];
|
|
|
if (!field) {
|
|
|
throw new Error(`Unknown operator ${b}`);
|
|
|
}
|
|
|
|
|
|
let val = this.decodeOperands(field[2], stream, ret, operands);
|
|
|
if (val != null) {
|
|
|
if (val instanceof PropertyDescriptor) {
|
|
|
Object.defineProperty(ret, field[1], val);
|
|
|
} else {
|
|
|
ret[field[1]] = val;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
operands = [];
|
|
|
} else {
|
|
|
operands.push(CFFOperand.decode(stream, b));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
size(dict, parent, includePointers = true) {
|
|
|
let ctx = {
|
|
|
parent,
|
|
|
val: dict,
|
|
|
pointerSize: 0,
|
|
|
startOffset: parent.startOffset || 0
|
|
|
};
|
|
|
|
|
|
let len = 0;
|
|
|
|
|
|
for (let k in this.fields) {
|
|
|
let field = this.fields[k];
|
|
|
let val = dict[field[1]];
|
|
|
if (val == null || isEqual(val, field[3])) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
let operands = this.encodeOperands(field[2], null, ctx, val);
|
|
|
for (let op of operands) {
|
|
|
len += CFFOperand.size(op);
|
|
|
}
|
|
|
|
|
|
let key = Array.isArray(field[0]) ? field[0] : [field[0]];
|
|
|
len += key.length;
|
|
|
}
|
|
|
|
|
|
if (includePointers) {
|
|
|
len += ctx.pointerSize;
|
|
|
}
|
|
|
|
|
|
return len;
|
|
|
}
|
|
|
|
|
|
encode(stream, dict, parent) {
|
|
|
let ctx = {
|
|
|
pointers: [],
|
|
|
startOffset: stream.pos,
|
|
|
parent,
|
|
|
val: dict,
|
|
|
pointerSize: 0
|
|
|
};
|
|
|
|
|
|
ctx.pointerOffset = stream.pos + this.size(dict, ctx, false);
|
|
|
|
|
|
for (let field of this.ops) {
|
|
|
let val = dict[field[1]];
|
|
|
if (val == null || isEqual(val, field[3])) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
let operands = this.encodeOperands(field[2], stream, ctx, val);
|
|
|
for (let op of operands) {
|
|
|
CFFOperand.encode(stream, op);
|
|
|
}
|
|
|
|
|
|
let key = Array.isArray(field[0]) ? field[0] : [field[0]];
|
|
|
for (let op of key) {
|
|
|
stream.writeUInt8(op);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
let i = 0;
|
|
|
while (i < ctx.pointers.length) {
|
|
|
let ptr = ctx.pointers[i++];
|
|
|
ptr.type.encode(stream, ptr.val, ptr.parent);
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|