← 목록
기타 2026-07-24 8KB 읽기 9분

크라우니 오피스 문서도구 native 배선 3종 (W-B123: B1 DOCX 여백 + B2 변경추적 저자 + B3 PDF WebKit 통일)

개요

크라우니AI 오피스 앱의 native(ObjC) 계층에 남아있던 3가지 잔여 배선을 완료:

  • B1: DOCX 내보내기 시 여백/용지가 항상 A4·1417twips 하드코딩이던 것을 사용자가 설정한
pagesetup(용지·mm 여백)이 실제 twips로 동적 반영되도록 배선.
  • B2: 변경추적(트랙체인지) 저자명이 crownyBridge.js 훅(window.__crownyOfficeSetAuthor)까지만
있고 native 호출이 0건이던 것을, 로그인 계정명으로 실제 주입.
  • B3: 메뉴 "PDF 내보내기"가 VM 텍스트 PDF(서식 미반영)였던 것을 ⌘P와 동일한 WebKit
createPDFWithConfiguration: 벡터 렌더로 통일(macOS 11+, 실패 시 기존 VM 경로로 fail-open).

무엇을 했는지

B1 — DOCX 여백/용지 동적 배선

  • 한선씨 엔진(bundle/office/bridge/내보내기.한선): 문서XML조립()이 고정 리터럴 sectPr을
반환하던 것을, 페이지설정꼬리조립()(신규) 호출로 교체. 이 함수가 환경변수()CROWNY_OFFICE_PAPER / CROWNY_OFFICE_MARGIN_{TOP,RIGHT,BOTTOM,LEFT}_MM을 읽어
  • 용지코드 → twips 치수(용지트윕스치수(): A4=11906×16838, Letter=12240×15840,
  • Legal=12240×20160, A3=16838×23811, B5(JIS)=10318×14570)
  • mm 여백 → twips(mm트윕스(): 반올림(mm * 56.6929))
  • 둘 다 미설정(글자수==0)이면 기존 리터럴(A4, 1417twips 4방향, header/footer=708, gutter=0)을 그대로 유지 — 회귀 불변. header/footer/gutter는 이번 배선 범위 밖(고정 유지).
    • native(native/crowny-ai-content.m): caExportOfficeDocumentFormat을 리팩터링 —
    실행 마무리 로직을 finishExport 블록으로 분리하고, docx 모드에서만 kCAOfficePageSetupJS (⌘P 경로 caPrintOfficeDocument와 동일 소스, §26 노출)를 추가로 읽어 pagesetup JSON을 파싱, 위 5개 환경변수로 변환해 VM 스테이지(caOfficeExportRunVMStage)에 전달. kCAOfficePageSetupJS 상수는 원래 caExportOfficeDocumentFormat보다 뒤에 선언돼 있어 전방참조 컴파일 에러가 나므로 kCAOfficeExtractJS 옆으로 재배치.

    B2 — 변경추적 저자 주입

    • native/crowny-ai-content.mcaInjectOfficeAuthorForWebView: / caOnAccountChangedForOfficeAuthor:
    2개 메서드 신규(CAOfficeIO 카테고리, caIsActiveTabOffice 바로 뒤). 로그인 상태([CAAccount shared].loggedIn) + username 존재 시에만 window.__crownyOfficeSetAuthor('<이스케이프된 이름>') 호출, 미로그인 이면 호출 생략(기존 '나' 폴백 유지 — 회귀 불변).
    • 호출 지점 2곳:
    1. webView:didFinishNavigation:에서 로드 완료된 탭이 오피스 문서(URL 판정은 caIsActiveTabOffice와 동일 기준, 다만 임의 탭이라 인라인 재적용)면 즉시 주입. 2. 계정 변경 알림 — initWithFrame:에 옵저버 등록. 알림 이름은 문자열 리터럴 "crowny.ai.accountChanged"(SSOT=crowny-ai-main.m:55CAAccountChangedNotification 상수값 미러 — 그 상수 자체는 extern 선언이 없어 심볼 참조 대신 리터럴로 관찰, main.m 무수정 원칙 준수). 발화 시 모든 탭을 순회해 오피스인 탭 전부에 재주입.
    • CAContentController (CAOfficeAuthorForward) forward-declare 카테고리를 CADocListForward
    옆에 추가(기존 파일의 forward-declare 관용구 그대로 사용 — 두 메서드가 본체 코드보다 뒤에 구현되므로 필요).

    B3 — 메뉴 PDF WebKit 통일

    • caExportOfficeDocumentPDF를 완전 재작성: @available(macOS 11.0, *)이면
    wv createPDFWithConfiguration:completionHandler: 호출 → 성공 시 그 NSDatacaOfficeDocsDir()에 직접 저장(파일명 규칙은 기존 VM 경로와 동일 — caSanitizeOfficeTitle(title)-yyyyMMdd-HHmm.pdf).
    • 실패 조건(구형 macOS·webView 없음·createPDF 에러·빈 데이터) 전부 기존
    caExportOfficeDocumentFormat:@"pdf" ... VM 경로로 fail-open — 그 함수 자체는 무수정.
    • 페이지설정 반영은 추가 배선 없이 crownyBridge.js가 이미 <head>에 상주시키는
    @page/@media print CSS(§26 buildPageCSS, pagesetup 패널 1회 이상 적용 시 생성)에 의존 — createPDFWithConfiguration:은 WebKit 내부 인쇄 파이프라인을 경유하므로 ⌘P와 동일 CSS 소스를 그대로 공유한다는 것이 이 방식 채택의 핵심 근거(스펙 문서 "소스 이중화 금지").

    검증 (실측)

    • 한선씨 컴파일: hanseonc_high 내보내기.한선 에러 0(경고 0), 46200 큐브 생성.
    • 한선씨 자체시험: crownyc runPASS 38/38(환경변수 미설정 상태 — 기존 자체시험은
    pagesetup env를 건드리지 않으므로 회귀 확인 그 자체).
  • B1 CLI 실측(진짜 DOCX 파일 생성, self-test 이상의 라이브 검증):
  • CASE A(환경변수 전부 미설정) → unzip -p out.docx word/document.xml:
  • w:pgSz w:w="11906" w:h="16838" / w:pgMar w:top="1417" w:right="1417" w:bottom="1417" w:left="1417" w:header="708" w:footer="708" w:gutter="0" — 기존 하드코딩 리터럴과 완전 일치(회귀 없음 확인).
  • CASE B(CROWNY_OFFICE_PAPER=Letter, top=30mm/right=bottom=left=20mm) →
  • w:pgSz w:w="12240" w:h="15840" / w:pgMar w:top="1701" w:right="1134" w:bottom="1134" w:left="1134" w:header="708" w:footer="708" w:gutter="0" — Letter 표(12240×15840) 정확, 30mm×56.6929=1700.79→1701, 20mm×56.6929=1133.86→1134 정확.
    • native 빌드: make ai 에러 0, 경고 4(전부 사전존재 — crowny-ai-main.m ivar 미참조 3건 +
    crowny-ai-butler.m 미사용 함수 1건, B1/B2/B3 신규 경고 0).
    • B2/B3: 정적 코드경로 추적만(실행 앱 미기동 — 아래 "잔여" 참조).

    관련 파일

    • /Users/ef/CrownyBrowser/native/crowny-ai-content.m
    (4946→5110줄, 백업 crowny-ai-content.m.bak-b123-1784822511)
    • /Users/ef/CrownyBrowser/bundle/office/bridge/내보내기.한선
    (2735→2813줄, 백업 내보내기.한선.bak-b123-1784822511)
    • 학습: crownycode-learn.sh add docx_sectpr_동적생성, add twips환산_mm트윕스

    잔여·위임 항목

    1. 실행 앱 반영 미완료: make ainative/CrownyAI 바이너리만 갱신하고 실행 중인
    CrownyAI.app 번들은 교체하지 않는다(CrownyBrowser/CLAUDE.md 경고 항목). 실기동 검증 (B2 로그인 주입/B3 벡터 PDF 육안 확인)은 install-ai-app.sh 실행 + 앱 재시작 이후 — 이 세션은 권한 범위상 설치 스크립트를 실행하지 않았다(메인 세션 권한, 지시사항 준수).
    1. B2 알림 이름은 문자열 리터럴("crowny.ai.accountChanged")로 관찰 — CAAccountChangedNotification
    심볼 자체를 참조하지 않음(소유 파일 범위상 main.m/헤더에 extern 선언 추가 안 함). main.m에서 문자열 상수값이 바뀌면 이 리터럴도 함께 갱신 필요(드리프트 리스크, 낮음 — 그 상수는 2026-06-12 이후 변경 이력 없음). 이후 값이 바뀌면 이 문서와 content.m 양쪽 갱신 필요.
    1. B3 pagesetup 반영은 가정 기반: createPDFWithConfiguration:이 WebKit 인쇄 파이프라인을
    통해 @page/@media print CSS를 실제로 적용하는지는 Apple 공개 문서에 명시적 보장이 없고, 이 세션에서 GUI 실측(실제 PDF 열어 여백 확인)은 못 했다(위 1번과 동일 사유). 통합 단계에서 pagesetup 터치 후 PDF 내보내기 → Preview.app에서 여백 실측 필요.
    1. B1의 header/footer/gutter는 이번 배선 범위 밖(708/708/0 고정) — 필요 시 후속 배선.