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; } }