refactor(文书纸): 抽出 A4_PAPER_MM 统一屏纸与 PDF 的页边距口径
This commit is contained in:
@@ -22,27 +22,67 @@
|
||||
* 它们与「签署态」语义强相关,统一在 styles/signing-document.css。
|
||||
*/
|
||||
|
||||
/**
|
||||
* A4 纸的物理规格(mm)—— **屏幕上的纸、导出的 PDF 共用这一份**。
|
||||
*
|
||||
* 屏幕那侧把它换算成「占纸宽的百分比」(% padding 以包含块宽度为基准,纸宽 == 包含块宽),
|
||||
* PDF 那侧(`utils/signing-pdf.ts`)直接用 mm 当页边距。
|
||||
* **改这里两边一起变** —— 曾经 PDF 自己写死 15/12/14mm,与屏幕的 31.75/14.34mm 对不上,
|
||||
* 表现为「下载的 PDF 和预览的边距不一样」(2026-09-27 修)。
|
||||
*/
|
||||
export const A4_PAPER_MM = {
|
||||
width: 210,
|
||||
height: 297,
|
||||
/** 左右页边距 3.18cm(Word 中文默认的「一般文书」留白) */
|
||||
marginX: 31.75,
|
||||
/**
|
||||
* 上下页边距 1.4cm(原型工作台 `#previewBox .paper` 的 56px 那一档)。
|
||||
* 比 Word 默认(2.54cm)紧是刻意的:820px 宽的纸上 2.54cm 会在顶部空出一大片。
|
||||
*/
|
||||
marginY: 14.34,
|
||||
} as const
|
||||
|
||||
/** 版心宽(mm):正文实际占的宽度。屏幕与 PDF 都按它排版,换行位置才对得上 */
|
||||
export const A4_CONTENT_WIDTH_MM = A4_PAPER_MM.width - A4_PAPER_MM.marginX * 2
|
||||
|
||||
/** 屏幕上的纸宽上限(CSS px)。A4 在 96dpi 下约 794px,取 820 让正文更舒展 */
|
||||
export const A4_PAPER_MAX_WIDTH_PX = 820
|
||||
|
||||
/** 纸宽百分比(% padding 四边都以包含块宽度为基准,所以上下也用纸宽算) */
|
||||
const pct = (mm: number) => `${((mm / A4_PAPER_MM.width) * 100).toFixed(2)}%`
|
||||
|
||||
/** 纸宽封顶时的页边距 px(= clamp 的上界),也是导出截图要用的版心尺寸 */
|
||||
export const A4_PAPER_PAD_X_PX = Math.round(
|
||||
A4_PAPER_MAX_WIDTH_PX * (A4_PAPER_MM.marginX / A4_PAPER_MM.width),
|
||||
)
|
||||
export const A4_PAPER_PAD_Y_PX = Math.round(
|
||||
A4_PAPER_MAX_WIDTH_PX * (A4_PAPER_MM.marginY / A4_PAPER_MM.width),
|
||||
)
|
||||
|
||||
/** 屏幕上的版心宽(px):导出 PDF 时按它排,换行位置才与屏幕一致 */
|
||||
export const A4_CONTENT_WIDTH_PX = A4_PAPER_MAX_WIDTH_PX - A4_PAPER_PAD_X_PX * 2
|
||||
|
||||
export const DOCUMENT_PAPER_CSS = `
|
||||
/* ============ A4 纸规格(全项目唯一一份) ============
|
||||
A4 = 210mm × 297mm。页边距:
|
||||
- 左右 3.18cm(Word 中文默认的「一般文书」留白)→ 占纸宽 15.14%
|
||||
- 上下 1.4cm(原型工作台 #previewBox .paper 的 56px 那一档)→ 占纸宽 6.83%
|
||||
- 左右 3.18cm(Word 中文默认的「一般文书」留白)→ 占纸宽 ${pct(A4_PAPER_MM.marginX)}
|
||||
- 上下 1.4cm(原型工作台 #previewBox .paper 的 56px 那一档)→ 占纸宽 ${pct(A4_PAPER_MM.marginY)}
|
||||
上下比左右紧是刻意的:820px 宽的纸上 2.54cm(Word 默认)会在顶部空出一大片,
|
||||
屏幕上很显眼;原型用的就是这一档。
|
||||
纸宽上限 820px 时对应的 px 值(124px / 56px)就是 clamp 的上界。
|
||||
纸宽上限 ${A4_PAPER_MAX_WIDTH_PX}px 时对应的 px 值(${A4_PAPER_PAD_X_PX}px / ${A4_PAPER_PAD_Y_PX}px)就是 clamp 的上界。
|
||||
**数值来源是文件顶部的 A4_PAPER_MM(mm)**,导出 PDF 的页边距也从那里取 —— 别在这里写死数字。
|
||||
|
||||
为什么用 clamp 而不是裸百分比:% padding 以**包含块宽度**为基准,不是自身宽。
|
||||
纸在窄容器里 width:100% 时两者相等(裸百分比正好);纸一旦被 max-width 收窄,
|
||||
百分比就会按更宽的包含块算 → 页边距偏大。clamp 让「纸 == 容器」和「纸被收窄」
|
||||
两种情形都得到同一套页边距。
|
||||
⚠️ 换纸宽时两个 --doc-paper-pad-* 要跟着改(与 --doc-paper-max-w 是一组)。
|
||||
⚠️ 本段在模板字符串里,注释内**不要写反引号**,会提前结束字符串。 */
|
||||
:root {
|
||||
--doc-paper-max-w: 820px;
|
||||
--doc-paper-pad-x: clamp(20px, 15.14%, 124px);
|
||||
--doc-paper-pad-y: clamp(16px, 6.83%, 56px);
|
||||
/* A4 的**最小高度**:纸宽封顶 820 时就是 1159.71px(= 820 × 297/210);
|
||||
容器比 820 窄时纸也会跟着窄,那时按容器宽 × 297/210 算(cqw = 容器宽的 1%)。
|
||||
--doc-paper-max-w: ${A4_PAPER_MAX_WIDTH_PX}px;
|
||||
--doc-paper-pad-x: clamp(20px, ${pct(A4_PAPER_MM.marginX)}, ${A4_PAPER_PAD_X_PX}px);
|
||||
--doc-paper-pad-y: clamp(16px, ${pct(A4_PAPER_MM.marginY)}, ${A4_PAPER_PAD_Y_PX}px);
|
||||
/* A4 的**最小高度**:纸宽封顶 ${A4_PAPER_MAX_WIDTH_PX} 时就是 ${Math.round(A4_PAPER_MAX_WIDTH_PX * (A4_PAPER_MM.height / A4_PAPER_MM.width))}px(= 纸宽 × 297/210);
|
||||
容器比它窄时纸也会跟着窄,那时按容器宽 × 297/210 算(cqw = 容器宽的 1%)。
|
||||
两个分支在「容器 == 纸宽」处正好接上,所以 min() 挑哪个都对。
|
||||
|
||||
⚠️ **不要用 aspect-ratio 表达这个下限**(2026-09-27 踩过):
|
||||
@@ -52,7 +92,10 @@ export const DOCUMENT_PAPER_CSS = `
|
||||
|
||||
⚠️ cqw 相对**最近的容器查询祖先**解析 —— 纸的父级必须带
|
||||
container-type: inline-size(probe-a4-paper.cjs 会逐处断言纵向不溢出)。 */
|
||||
--doc-paper-min-h: min(calc(var(--doc-paper-max-w) * 297 / 210), 141.43cqw);
|
||||
--doc-paper-min-h: min(
|
||||
calc(var(--doc-paper-max-w) * ${(A4_PAPER_MM.height / A4_PAPER_MM.width).toFixed(6)}),
|
||||
${((A4_PAPER_MM.height / A4_PAPER_MM.width) * 100).toFixed(2)}cqw
|
||||
);
|
||||
}
|
||||
|
||||
/* 一页 A4:至少一页的高度 + 一般文书页边距。
|
||||
|
||||
@@ -16,16 +16,31 @@
|
||||
* 输出为位图 PDF:文本不可选中,仅供原型演示;正式文书以服务端生成的 PDF 为准。
|
||||
*/
|
||||
|
||||
const PAGE_WIDTH_MM = 210
|
||||
const PAGE_HEIGHT_MM = 297
|
||||
const MARGIN_X_MM = 15
|
||||
const MARGIN_TOP_MM = 12
|
||||
const MARGIN_BOTTOM_MM = 14
|
||||
const CONTENT_WIDTH_MM = PAGE_WIDTH_MM - MARGIN_X_MM * 2
|
||||
import { A4_CONTENT_WIDTH_MM, A4_CONTENT_WIDTH_PX, A4_PAPER_MM } from './document-paper'
|
||||
|
||||
const PAGE_WIDTH_MM = A4_PAPER_MM.width
|
||||
const PAGE_HEIGHT_MM = A4_PAPER_MM.height
|
||||
/**
|
||||
* 页边距与屏幕上的纸**同一份口径**(`utils/document-paper.ts` 的 `A4_PAPER_MM`)。
|
||||
*
|
||||
* ⚠️ 这里曾经自己写死 15 / 12 / 14mm,与屏幕的 31.75 / 14.34mm 对不上 ——
|
||||
* 表现为「下载的 PDF 和预览的边距不一样」,而且版心宽度也差 33mm、换行位置跟着变。
|
||||
* **不要再在这里写 mm 数字**,要调就调 `A4_PAPER_MM`。
|
||||
*/
|
||||
const MARGIN_X_MM = A4_PAPER_MM.marginX
|
||||
const MARGIN_TOP_MM = A4_PAPER_MM.marginY
|
||||
const MARGIN_BOTTOM_MM = A4_PAPER_MM.marginY
|
||||
const CONTENT_WIDTH_MM = A4_CONTENT_WIDTH_MM
|
||||
const CONTENT_HEIGHT_MM = PAGE_HEIGHT_MM - MARGIN_TOP_MM - MARGIN_BOTTOM_MM
|
||||
|
||||
/** 截图版心宽度(px):约等于 180mm @96dpi,让 PDF 版面接近实际纸张 */
|
||||
const CAPTURE_WIDTH_PX = 680
|
||||
/**
|
||||
* 截图版心宽度(px)= 屏幕上纸的版心宽度(`A4_CONTENT_WIDTH_PX`)。
|
||||
*
|
||||
* 为什么必须是这个值而不是「约 180mm @96dpi」:截图时把克隆体宽度钉成它,
|
||||
* 正文的换行位置才与屏幕完全一致(都是同一个版心宽);
|
||||
* 再按 `CONTENT_WIDTH_MM / canvas.width` 缩放铺进 PDF,物理尺寸也就对了。
|
||||
*/
|
||||
const CAPTURE_WIDTH_PX = A4_CONTENT_WIDTH_PX
|
||||
/** 截图倍率:2 倍下正文与小字都还清晰 */
|
||||
const CAPTURE_SCALE = 2
|
||||
/** 量不到页眉时的兜底高度(px) */
|
||||
|
||||
Reference in New Issue
Block a user