Files
Aislo/tools/est-test/print-format.cjs
T

140 lines
5.3 KiB
JavaScript

// 원본 인쇄양식 파일(.pfot — mnuPrintFormatSave 0x10ed000 · Load 0x10ed318): 인쇄정보표(DatPrintOption) MIDAS 이진 한 벌.
// 읽기는 import-print.cjs 와 같은 꼴. 쓰기는 같은 꼴의 바탕 파일(필드 설명자)을 받아 행 부분만 새로 만든다.
// .gfot(화면 격자 열 배치)는 웹 격자와 호환되지 않아 다루지 않는다.
const { readPrintOptions } = require("./import-print.cjs");
function layout(buffer) {
if (buffer.length < 24 || buffer.readUInt32LE(0) !== 0xbde01996)
throw Error("인쇄양식 파일 헤더 오류");
const count = buffer.readUInt16LE(12),
start = buffer.readUInt16LE(22);
let at = 24;
const fields = [];
for (let i = 0; i < count; i++) {
const length = buffer[at];
const name = buffer.subarray(at + 1, at + 1 + length).toString("ascii");
at += 1 + length;
const width = buffer.readUInt16LE(at),
kind = buffer.readUInt16LE(at + 2),
attributes = buffer.readUInt16LE(at + 6);
at += 8;
for (let j = 0; j < attributes; j++) {
at += 1 + buffer[at];
const size = buffer.readUInt16LE(at),
type = buffer.readUInt16LE(at + 2);
at += 4;
at += type === 73 ? 2 + buffer.readUInt16LE(at) : size;
}
fields.push({ name, width, kind });
}
return { count, start, fields, flag: buffer[start] };
}
// rows: 인쇄정보표 행들(읽은 값). BLOB(75) 칸은 비워 씀.
function write(template, rows) {
const { count, start, fields, flag } = layout(template);
const encoder = (s) => {
const bytes = [];
for (const ch of String(s)) {
const code = ch.codePointAt(0);
if (code < 0x80) bytes.push(code);
else {
const found = cp949Table().get(ch);
if (found == null) throw Error(`인쇄양식 글자를 cp949 로 바꿀 수 없습니다: ${ch}`);
bytes.push(found >> 8, found & 0xff);
}
}
return Buffer.from(bytes);
};
const parts = [Buffer.from(template.subarray(0, start))];
parts[0].writeUInt16LE(rows.length, 14);
for (const row of rows) {
const status = Buffer.alloc(Math.ceil(count / 4)),
values = [];
fields.forEach(({ name, width, kind }, i) => {
const v = row[name];
if (kind === 75 && v?.binary != null) {
const data = Buffer.from(v.binary, "base64"),
length = Buffer.alloc(4);
length.writeUInt32LE(data.length);
values.push(length, data);
return;
}
if (v == null || kind === 75) {
status[i >> 2] |= (row.__nulls?.[name] || 1) << ((i % 4) * 2);
return;
}
if (kind === 1 && width === 4) {
const b = Buffer.alloc(4);
b.writeInt32LE(Number(v) | 0);
values.push(b);
} else if (kind === 3 && width === 2) {
const b = Buffer.alloc(2);
b.writeInt16LE(Number(v) | 0);
values.push(b);
} else if (kind === 4 && width === 8) {
const b = Buffer.alloc(8);
b.writeDoubleLE(Number(v));
values.push(b);
} else if (kind === 73 && width === 1) {
const text = encoder(v);
if (text.length > 255) throw Error(`인쇄양식 글이 너무 깁니다: ${name}`);
values.push(Buffer.from([text.length]), text);
} else throw Error(`인쇄양식 필드형 오류: ${name}/${kind}/${width}`);
});
parts.push(Buffer.from([flag]), status, ...values);
}
return Buffer.concat(parts);
}
let table = null;
function cp949Table() {
if (table) return table;
table = new Map();
const decoder = new TextDecoder("euc-kr");
for (let hi = 0x81; hi <= 0xfe; hi++)
for (let lo = 0x41; lo <= 0xfe; lo++) {
const ch = decoder.decode(Buffer.from([hi, lo]));
if (ch.length === 1 && ch !== "�" && !table.has(ch)) table.set(ch, (hi << 8) | lo);
}
return table;
}
// BLOB(설명 등)까지 담아 읽기 — 그대로 다시 쓰면 원본과 같은 바이트
function readFull(buffer) {
const { count, start, fields } = layout(buffer),
rows = buffer.readUInt16LE(14),
decoder = new TextDecoder("euc-kr");
let at = start;
const out = [];
for (let r = 0; r < rows; r++) {
at += 1;
const status = buffer.subarray(at, at + Math.ceil(count / 4));
at += status.length;
const row = {};
fields.forEach(({ name, width, kind }, i) => {
const state = (status[i >> 2] >> ((i % 4) * 2)) & 3;
if (state) {
if (state !== 1) (row.__nulls ||= {})[name] = state; // 빈 칸 표지(1·2)도 그대로 되살리게
return;
}
if (kind === 1 && width === 4) ((row[name] = buffer.readInt32LE(at)), (at += 4));
else if (kind === 3 && width === 2) ((row[name] = buffer.readInt16LE(at)), (at += 2));
else if (kind === 4 && width === 8) ((row[name] = buffer.readDoubleLE(at)), (at += 8));
else if (kind === 73 && width === 1) {
const n = buffer[at];
row[name] = decoder.decode(buffer.subarray(at + 1, at + 1 + n));
at += 1 + n;
} else if (kind === 75) {
const n = buffer.readUInt32LE(at);
row[name] = { binary: buffer.subarray(at + 4, at + 4 + n).toString("base64") };
at += 4 + n;
} else throw Error(`인쇄양식 필드형 오류: ${name}/${kind}/${width}`);
});
out.push(row);
}
return out;
}
module.exports = { read: (buffer) => readPrintOptions(buffer), readFull, write, layout };