- 서버: xlsx 시트 XML 열 폭 · 숨김을 보냄(엑셀 보기 격자 `열`) - 화면: 폭 없는 열은 글 길이로 처음 폭(최소 6 · 최대 60 · 여러 열 병합 제목 칸은 뺌) - 읽기 전용 격자도 열 폭 끌기 · 경계 두 번 누름 맞춤(보기만 · 저장 · 되돌리기 · onChange 없음) - 열 경계 오른쪽 반에서도 경계로 읽음 · 단가 카드 미리보기는 사본으로 보임 - 시험: test_741_sheet_widths · test_stage2_autofit(esbuild 묶어 node) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
83 lines
4.1 KiB
TypeScript
83 lines
4.1 KiB
TypeScript
/* =============================================================================
|
|
* 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<number, number> {
|
|
const wide = new Set<string>();
|
|
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<number, number>();
|
|
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 });
|
|
}
|