// 원본 인쇄양식 파일(.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 };