/* ============================================================================= * spreadsheet_autofit.ts (2단계 · 주인 sub_laptop_1) * 열 머리 경계를 두 번 눌러 폭 자동 맞춤 — 그 열 안 글자 수로 어림(엑셀 「글자 단위」). * 진짜 픽셀 값은 C(격자)가 쓰는 글꼴로 재야 더 정확 — `measure` 자리에 C 의 실제 재기 함수를 * 나중에 꽂을 수 있게 열어 둠(안 주면 아래 어림 규칙을 씀). * * 잇는 법(D · sub7 · C 격자 머리) — 열 머리 경계에서 두 번 누르면(dblclick) * `autofitColumn(ctx, col)` 을 부름. C 가 완성되면 `measure` 자리에 캔버스 `measureText` 를 넘겨 더 정밀하게. * * ── 이미 계약에 있어 새 파일이 필요 없는 것(붙일 자리만 확인) ────────────────────── * · 여러 범위(Ctrl+누르기) — `spreadsheet_view_types.ts` `Selection.범위` 가 이미 배열(D `spreadsheet_selection.ts` 가 씀). * · 눈금선 끄기 — `Command`(`종류:"눈금선"`) · `Sheet.보기?.눈금선` 이미 있음(A 가 처리). * · 확대 · 축소 — 값 자체를 저장하지 않는 화면 전용 CSS 배율(C 격자 뿌리에 transform) · 계약 밖. * ========================================================================== */ import { colIndex, colName, parseRange } from "./spreadsheet_address"; import type { Sheet, Workbook } from "./spreadsheet_types"; import type { SpreadsheetContext } from "./spreadsheet_view_types"; export type TextMeasurer = (text: string) => number; const PADDING = 2; const MIN_WIDTH = 4; /** 글꼴을 모를 때 어림 — 한글 · 한자 · 전각은 1.9 배 폭 · 나머지는 1 배. */ const defaultMeasure: TextMeasurer = (text) => { let width = 0; for (const ch of text) width += ch.codePointAt(0)! >= 0x1100 ? 1.9 : 1; return width; }; const colFromKey = (key: string): number => { const m = /^([A-Z]+)\d+$/.exec(key); return m ? colIndex(m[1]) : -1; }; /** 열마다 가장 넓은 칸 글자 폭(식 칸은 식 글로 어림 — 계산값은 여기서 모름) · 여러 열 병합 칸은 뺌(제목 줄). */ function widestByColumn(sheet: Sheet, measure: TextMeasurer): Map { const wide = new Set(); for (const text of sheet.병합 ?? []) { const r = parseRange(text); if (r && r.c1 > r.c0) wide.add(`${colName(r.c0)}${r.r0 + 1}`); } const out = new Map(); for (const [key, cell] of Object.entries(sheet.칸)) { const text = cell.식 !== undefined ? cell.식 : cell.값 !== undefined ? String(cell.값) : ""; const col = colFromKey(key); if (!text || col < 0 || wide.has(key)) continue; out.set(col, Math.max(out.get(col) ?? 0, measure(text))); } return out; } /** 그 열에 든 칸 글자 중 가장 넓은 것 + 여백. */ export function estimateColumnWidth( book: Workbook, sheetId: string, col: number, measure: TextMeasurer = defaultMeasure, ): number { const sheet = book.시트.find((s) => s.id === sheetId); if (!sheet) return MIN_WIDTH; return Math.max(MIN_WIDTH, Math.ceil((widestByColumn(sheet, measure).get(col) ?? 0) + PADDING)); } /** 폭 정보 없는 열(엑셀 폭을 못 읽은 파일)에 글 길이로 처음 폭을 채움 — 최소 · 최대 안(읽기 전용 보기). */ export function fillMissingWidths(book: Workbook, min = 6, max = 60): void { for (const sheet of book.시트) { for (const [col, widest] of widestByColumn(sheet, defaultMeasure)) { const letter = colName(col); if (sheet.열?.[letter]?.폭 !== undefined) continue; const 폭 = Math.min(max, Math.max(min, Math.ceil(widest + PADDING))); sheet.열 = { ...sheet.열, [letter]: { ...sheet.열?.[letter], 폭 } }; } } } /** 폭을 자동 맞춤 값으로 바꿈(열넓이 명령 하나). */ export function autofitColumn(ctx: SpreadsheetContext, col: number, measure?: TextMeasurer): void { const width = estimateColumnWidth(ctx.book, ctx.selection.시트, col, measure); ctx.dispatch({ 종류: "열폭", 시트: ctx.selection.시트, 열: [col], 폭: width }); }