dsh-zen-remote 1.1.13 → 1.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/docs/interface.md +46 -3
- package/lib/client.js +3421 -103
- package/lib/index.js +13 -0
- package/lib/index.js.map +1 -1
- package/lib/share-export.js +347 -0
- package/lib/share-export.js.map +1 -0
- package/lib/types/client/MobileAttachButton.d.ts +8 -1
- package/lib/types/client/MobileAttachButton.d.ts.map +1 -1
- package/lib/types/client/MobileAttachChips.d.ts.map +1 -1
- package/lib/types/client/MobileSessionHeader.d.ts +7 -8
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-avoid.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts +16 -11
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/host-attach.d.ts +38 -0
- package/lib/types/client/host-attach.d.ts.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +23 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/share/fetch-share.d.ts +77 -0
- package/lib/types/client/share/fetch-share.d.ts.map +1 -0
- package/lib/types/client/share/markdown.d.ts +97 -0
- package/lib/types/client/share/markdown.d.ts.map +1 -0
- package/lib/types/client/share/png-stitch.d.ts +96 -0
- package/lib/types/client/share/png-stitch.d.ts.map +1 -0
- package/lib/types/client/share/rasterize.d.ts +317 -0
- package/lib/types/client/share/rasterize.d.ts.map +1 -0
- package/lib/types/client/share/share-card.d.ts +93 -0
- package/lib/types/client/share/share-card.d.ts.map +1 -0
- package/lib/types/client/share/share-constants.d.ts +27 -0
- package/lib/types/client/share/share-constants.d.ts.map +1 -0
- package/lib/types/client/share/share-flow.d.ts +76 -0
- package/lib/types/client/share/share-flow.d.ts.map +1 -0
- package/lib/types/client/share/share-preview.d.ts +11 -0
- package/lib/types/client/share/share-preview.d.ts.map +1 -0
- package/lib/types/client/share/share-sheet.d.ts +17 -0
- package/lib/types/client/share/share-sheet.d.ts.map +1 -0
- package/lib/types/client/share/text-layout.d.ts +48 -0
- package/lib/types/client/share/text-layout.d.ts.map +1 -0
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/layout.css.d.ts +1 -1
- package/lib/types/client/styles/layout.css.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/share-export.d.ts +66 -0
- package/lib/types/share-export.d.ts.map +1 -0
- package/package.json +7 -2
- package/scripts/check-host-shadow.mjs +41 -9
- package/scripts/check-keyboard-avoid.mjs +4 -2
- package/scripts/check-share-image.mjs +970 -0
- package/scripts/check-upload-endpoint.mjs +10 -3
- package/src/client/MobileAttachButton.tsx +13 -0
- package/src/client/MobileAttachChips.tsx +8 -1
- package/src/client/MobileSessionHeader.tsx +90 -30
- package/src/client/MobileSessionInfo.tsx +234 -1
- package/src/client/effects/keyboard-avoid.ts +14 -7
- package/src/client/effects/keyboard-guard.ts +28 -18
- package/src/client/host-attach.ts +54 -0
- package/src/client/index.tsx +16 -0
- package/src/client/locales.ts +46 -0
- package/src/client/share/fetch-share.ts +161 -0
- package/src/client/share/markdown.ts +609 -0
- package/src/client/share/png-stitch.ts +281 -0
- package/src/client/share/rasterize.ts +1078 -0
- package/src/client/share/share-card.tsx +637 -0
- package/src/client/share/share-constants.ts +30 -0
- package/src/client/share/share-flow.ts +166 -0
- package/src/client/share/share-preview.tsx +337 -0
- package/src/client/share/share-sheet.tsx +222 -0
- package/src/client/share/text-layout.ts +147 -0
- package/src/client/styles/header.css.ts +30 -0
- package/src/client/styles/layout.css.ts +5 -2
- package/src/index.ts +13 -0
- package/src/share-export.ts +431 -0
|
@@ -20,6 +20,7 @@ export declare const zh: {
|
|
|
20
20
|
readonly sessionInfo: "会话信息";
|
|
21
21
|
readonly workbench: "工作台";
|
|
22
22
|
readonly workbenchClose: "关闭工作台";
|
|
23
|
+
readonly sidebar: "侧边栏";
|
|
23
24
|
readonly switchView: "切换视图";
|
|
24
25
|
readonly attach: "添加附件";
|
|
25
26
|
readonly attachBusy: "上传中…";
|
|
@@ -47,6 +48,28 @@ export declare const zh: {
|
|
|
47
48
|
readonly infoActionError: "操作失败:{message}";
|
|
48
49
|
readonly turnFold: "过程 · {count} 步";
|
|
49
50
|
readonly turnFoldRunning: "进行中 · {count} 步";
|
|
51
|
+
readonly shareCardTurns: "{count} 轮对话";
|
|
52
|
+
readonly shareCardGeneratedBy: "由 DeepSeek Harness 生成";
|
|
53
|
+
readonly shareCardImage: "图片附件";
|
|
54
|
+
readonly shareAction: "分享图";
|
|
55
|
+
readonly sharePickerTitle: "分享范围";
|
|
56
|
+
readonly sharePickerAll: "全部对话";
|
|
57
|
+
readonly sharePickerLast: "最近 {count} 轮";
|
|
58
|
+
readonly sharePickerConfirm: "生成分享图";
|
|
59
|
+
readonly sharePickerBusy: "生成中…";
|
|
60
|
+
readonly shareTruncatedNote: "内容过长,已省略前 {count} 轮";
|
|
61
|
+
readonly shareErrRouteMissing: "当前 DSH 版本未提供分享图接口,请更新 DSH 后重试";
|
|
62
|
+
readonly shareErrSessionNotFound: "会话不存在或已被删除";
|
|
63
|
+
readonly shareErrBadRequest: "分享请求无效,请重试";
|
|
64
|
+
readonly shareErrNetwork: "网络连接失败,请检查网络后重试";
|
|
65
|
+
readonly shareErrServer: "服务暂时不可用,请稍后重试";
|
|
66
|
+
readonly shareErrBody: "分享数据异常,请重试";
|
|
67
|
+
readonly shareErrProbe: "当前浏览器无法生成分享图";
|
|
68
|
+
readonly shareErrRender: "生成分享图失败,请重试";
|
|
69
|
+
readonly shareErrSliceBudget: "分享图单段内容超出画布上限,请缩小分享范围后重试";
|
|
70
|
+
readonly shareErrEmpty: "会话还没有可分享的内容";
|
|
71
|
+
readonly shareStitchUnsupported: "当前浏览器暂不支持合成单张长图,已导出为多张图片";
|
|
72
|
+
readonly shareStitchFailed: "合成单张长图失败,已改为多张图片导出";
|
|
50
73
|
readonly settings: "设置";
|
|
51
74
|
readonly chipTaskboard: "任务看板";
|
|
52
75
|
readonly chipSsh: "SSH";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,EAAE,cAAc,CAAA;AAE7B,mEAAmE;AACnE,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,EAAE,cAAc,CAAA;AAE7B,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0EL,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CA0E3C,CAAA;AAED,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Share-export fetching (ticket 05, PLAN §5.3): the ONE typed fetch over
|
|
3
|
+
* GET /_dsh/mobile-nav/share-export, shared by the real share flow
|
|
4
|
+
* (MobileSessionInfo) and the debug preview (?mobile-nav-share-preview=1)
|
|
5
|
+
* so query assembly and error handling never fork into two copies.
|
|
6
|
+
*
|
|
7
|
+
* Module-shape rules (same reason as rasterize.ts): the share-image check
|
|
8
|
+
* script imports this file through Node type stripping, which rejects JSX
|
|
9
|
+
* and runs no browser API — so every import of a JSX-bearing module is
|
|
10
|
+
* `import type` (fully erased) and browser globals appear only inside
|
|
11
|
+
* function bodies. The query builder and the error taxonomy below are pure
|
|
12
|
+
* and pinned there.
|
|
13
|
+
*/
|
|
14
|
+
import type { ShareTurn } from './share-card.tsx';
|
|
15
|
+
/** Route the host registers (mirror of src/share-export.ts — the client cannot import host code). */
|
|
16
|
+
export declare const SHARE_EXPORT_ROUTE = "/_dsh/mobile-nav/share-export";
|
|
17
|
+
/**
|
|
18
|
+
* Which slice of the transcript to export — exactly the `range`/`turns` pair
|
|
19
|
+
* the route validates (src/share-export.ts: `turns` next to `range=all` is a
|
|
20
|
+
* 400, so it is only ever appended for `range=last`).
|
|
21
|
+
*/
|
|
22
|
+
export type ShareRange = {
|
|
23
|
+
kind: 'all';
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'last';
|
|
26
|
+
turns: number;
|
|
27
|
+
};
|
|
28
|
+
/** Preset turn counts the range sheet offers (PLAN §6: presets, no free input). */
|
|
29
|
+
export declare const SHARE_TURNS_PRESETS: readonly number[];
|
|
30
|
+
/**
|
|
31
|
+
* Machine-readable fetch failure. `code` picks the localized copy; `status`
|
|
32
|
+
* is the HTTP status when a response existed (undefined = the request never
|
|
33
|
+
* completed). Fields are assigned in the constructor body, not declared as
|
|
34
|
+
* parameter properties — Node type stripping rejects that syntax (same
|
|
35
|
+
* reason as ShareRasterizeError / ShareExportError).
|
|
36
|
+
*/
|
|
37
|
+
export type ShareFetchErrorCode =
|
|
38
|
+
/** 404 without the route's JSON envelope: this DSH never mounted the route (too old). */
|
|
39
|
+
'route-missing'
|
|
40
|
+
/** 404 + the route's own session-not-found code: the session is gone. */
|
|
41
|
+
| 'session-not-found'
|
|
42
|
+
/** 400: malformed query the client should not have built. */
|
|
43
|
+
| 'bad-request'
|
|
44
|
+
/** Any other non-ok status (route error, gateway 502, …). */
|
|
45
|
+
| 'server'
|
|
46
|
+
/** fetch() itself threw (offline, connection reset). */
|
|
47
|
+
| 'network'
|
|
48
|
+
/** 2xx but the body is not the agreed shape. */
|
|
49
|
+
| 'bad-body';
|
|
50
|
+
/** Localizable fetch failure; ticket 05 maps `code` to locales shareErr*. */
|
|
51
|
+
export declare class ShareFetchError extends Error {
|
|
52
|
+
readonly code: ShareFetchErrorCode;
|
|
53
|
+
readonly status: number | undefined;
|
|
54
|
+
constructor(code: ShareFetchErrorCode, status: number | undefined, detail: string);
|
|
55
|
+
}
|
|
56
|
+
/** A validated export payload (the route's 200 body minus the envelope). */
|
|
57
|
+
export interface ShareExportData {
|
|
58
|
+
turns: ShareTurn[];
|
|
59
|
+
createdAt: number | undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Pure: assemble the route query. The session id arrives in its branded
|
|
63
|
+
* `session-<uuid>` form (useSessions row / session-scoped slot prop) and is
|
|
64
|
+
* passed through encodeURIComponent — a bare uuid would 404 (PLAN §2 note).
|
|
65
|
+
*/
|
|
66
|
+
export declare function buildShareExportQuery(session: string, range: ShareRange): string;
|
|
67
|
+
/**
|
|
68
|
+
* Fetch one export and classify every failure.
|
|
69
|
+
*
|
|
70
|
+
* Error mapping (the reason a 404 is split in two): this plugin's route
|
|
71
|
+
* answers 404 + `{error:{code:'session-not-found'}}` for an unknown session,
|
|
72
|
+
* while a DSH old enough to lack the route answers the web server's own 404
|
|
73
|
+
* — a different copy (“需要新版 DSH”) than “会话不存在”, decided by whether
|
|
74
|
+
* our envelope is present.
|
|
75
|
+
*/
|
|
76
|
+
export declare function fetchShareExport(session: string, range: ShareRange): Promise<ShareExportData>;
|
|
77
|
+
//# sourceMappingURL=fetch-share.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-share.d.ts","sourceRoot":"","sources":["../../../../src/client/share/fetch-share.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,kCAAkC,CAAA;AAEjE;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAe,CAAA;AAEhE;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB;AAC7B,yFAAyF;AACvF,eAAe;AACjB,yEAAyE;GACvE,mBAAmB;AACrB,6DAA6D;GAC3D,aAAa;AACf,6DAA6D;GAC3D,QAAQ;AACV,wDAAwD;GACtD,SAAS;AACX,gDAAgD;GAC9C,UAAU,CAAA;AAEd,6EAA6E;AAC7E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;gBAEvB,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM;CAMlF;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAIhF;AAyBD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CA8CnG"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure lenient Markdown-subset parser for the share card's assistant text
|
|
3
|
+
* (ticket 09, PLAN §5.1 v1.2).
|
|
4
|
+
*
|
|
5
|
+
* Scope = the constructs assistant transcripts actually use: ATX headings,
|
|
6
|
+
* paragraphs, ```/~~~ fenced code (with the language tag), 4-space indented
|
|
7
|
+
* code, blockquotes, ordered/unordered lists (two indent levels, 2-space
|
|
8
|
+
* multiples), GFM pipe tables with an alignment row, thematic breaks, and
|
|
9
|
+
* own-line image placeholders; inline bold/italic/strikethrough/code/link with
|
|
10
|
+
* backslash escapes. Nothing else — no HTML passthrough, no reference links,
|
|
11
|
+
* no setext headings, no syntax highlighting (PLAN §8 scope discipline).
|
|
12
|
+
*
|
|
13
|
+
* Leniency beats strictness ON PURPOSE (PLAN §5.1): a share image that shows
|
|
14
|
+
* stray asterisks is worse than one that shows plain text. Every marker pair
|
|
15
|
+
* must CLOSE before it counts — unclosed, empty or implausible markers fall
|
|
16
|
+
* back to their literal characters, so arbitrary prose round-trips as itself.
|
|
17
|
+
* The parser is also total: any input yields a tree (empty input yields []).
|
|
18
|
+
* Totality is enforced, not assumed: recursion (nested quotes, emphasis inside
|
|
19
|
+
* link text) carries a depth counter and a pathological 5000-deep "> > > …"
|
|
20
|
+
* degrades to literal paragraphs instead of overflowing the stack; one inline
|
|
21
|
+
* run longer than {@link INLINE_PLAIN_MAX} skips inline parsing entirely —
|
|
22
|
+
* every close-scan is then bounded, so no amount of unclosed markers can
|
|
23
|
+
* regress past linear time.
|
|
24
|
+
*
|
|
25
|
+
* Hard rule for this file: no imports, no JSX, no DOM, no module side effects.
|
|
26
|
+
* scripts/check-share-image.mjs loads it through Node type stripping, which
|
|
27
|
+
* EXECUTES every value import, and the client bundler inlines it next to
|
|
28
|
+
* share-card without new externals.
|
|
29
|
+
*/
|
|
30
|
+
/** One inline run. Emphasis nests by recursion; code/link leaves are atomic. */
|
|
31
|
+
export type MdSpan = {
|
|
32
|
+
kind: 'text';
|
|
33
|
+
text: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'bold';
|
|
36
|
+
spans: MdSpan[];
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'italic';
|
|
39
|
+
spans: MdSpan[];
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'strike';
|
|
42
|
+
spans: MdSpan[];
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'code';
|
|
45
|
+
text: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'link';
|
|
48
|
+
spans: MdSpan[];
|
|
49
|
+
href: string;
|
|
50
|
+
};
|
|
51
|
+
/** One list item: inline content plus one nested level (the parser's cap). */
|
|
52
|
+
export interface MdListItem {
|
|
53
|
+
spans: MdSpan[];
|
|
54
|
+
/** Ordered item number as written (undefined for bullets). */
|
|
55
|
+
number: number | undefined;
|
|
56
|
+
children: MdListItem[];
|
|
57
|
+
}
|
|
58
|
+
/** Column alignment from the delimiter row (null = unspecified/left). */
|
|
59
|
+
export type MdAlign = 'left' | 'center' | 'right' | null;
|
|
60
|
+
/** One recognized block. The renderer switches on `kind` only. */
|
|
61
|
+
export type MdBlock = {
|
|
62
|
+
kind: 'heading';
|
|
63
|
+
level: number;
|
|
64
|
+
spans: MdSpan[];
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'para';
|
|
67
|
+
spans: MdSpan[];
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'code';
|
|
70
|
+
lang: string | undefined;
|
|
71
|
+
lines: string[];
|
|
72
|
+
} | {
|
|
73
|
+
kind: 'quote';
|
|
74
|
+
children: MdBlock[];
|
|
75
|
+
} | {
|
|
76
|
+
kind: 'list';
|
|
77
|
+
ordered: boolean;
|
|
78
|
+
items: MdListItem[];
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'table';
|
|
81
|
+
align: MdAlign[];
|
|
82
|
+
header: MdSpan[][];
|
|
83
|
+
rows: MdSpan[][][];
|
|
84
|
+
} | {
|
|
85
|
+
kind: 'hr';
|
|
86
|
+
} | {
|
|
87
|
+
kind: 'image';
|
|
88
|
+
alt: string;
|
|
89
|
+
src: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Parse a raw text block into the share card's Markdown tree.
|
|
93
|
+
* @param text - raw text-block content from the share-export route.
|
|
94
|
+
* @returns blocks in reading order; `[]` for blank input.
|
|
95
|
+
*/
|
|
96
|
+
export declare function parseMarkdown(text: string): MdBlock[];
|
|
97
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../../src/client/share/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,gFAAgF;AAChF,MAAM,MAAM,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAInD,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AAED,yEAAyE;AACzE,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAA;AAExD,kEAAkE;AAClE,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GACd;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AA0hB/C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAErD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming PNG long-image stitcher (ticket 07, PLAN §4.5).
|
|
3
|
+
*
|
|
4
|
+
* The rasterizer still paints slice-by-slice (each slice stays under the iOS
|
|
5
|
+
* canvas budget), but the DELIVERABLE is one PNG: this module concatenates the
|
|
6
|
+
* slices' scanlines into a single image — PNG signature + IHDR (whole-image
|
|
7
|
+
* size, 8-bit RGBA) + IDAT (zlib stream, scanline filter 0) + IEND, CRC32
|
|
8
|
+
* computed against a self-carried table, compression via the platform's
|
|
9
|
+
* `CompressionStream('deflate')` whose output already IS the zlib-wrapped
|
|
10
|
+
* deflate stream the PNG IDAT chunk requires.
|
|
11
|
+
*
|
|
12
|
+
* Streaming discipline (the whole point of a custom stitcher): slices are
|
|
13
|
+
* decoded one at a time (createImageBitmap → offscreen canvas getImageData →
|
|
14
|
+
* per-row writeRow → release) so the raw pixels of the WHOLE image are never
|
|
15
|
+
* held; only the compressed output accumulates, as a Blob.
|
|
16
|
+
*
|
|
17
|
+
* Module contract (same one share-flow.ts honors): scripts/check-share-image.mjs
|
|
18
|
+
* imports this file through Node type stripping, which EXECUTES module scope —
|
|
19
|
+
* so everything here is pure math at module scope, and every browser global
|
|
20
|
+
* (CompressionStream, createImageBitmap, document, Blob) is touched only
|
|
21
|
+
* inside function/class bodies. The file is an import-free leaf.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* CRC-32 (PNG variant: no init/xor toggling beyond the standard reflected
|
|
25
|
+
* form — init 0xFFFFFFFF, final XOR 0xFFFFFFFF). Known answers pinned by the
|
|
26
|
+
* check script: crc32('123456789') === 0xCBF43926, crc32('IEND') === 0xAE426082.
|
|
27
|
+
*/
|
|
28
|
+
export declare function crc32(bytes: Uint8Array): number;
|
|
29
|
+
/** The 8-byte PNG file signature. */
|
|
30
|
+
export declare const PNG_SIGNATURE: Uint8Array<ArrayBuffer>;
|
|
31
|
+
/**
|
|
32
|
+
* One PNG chunk frame: 4-byte big-endian data length, the type, the data, and
|
|
33
|
+
* the big-endian CRC32 over TYPE+DATA (never the length field).
|
|
34
|
+
*/
|
|
35
|
+
export declare function pngChunk(type: string, data: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
36
|
+
/** IHDR payload: width/height big-endian, 8 bits/channel, RGBA (color type 6), deflate, filter 0, no interlace. */
|
|
37
|
+
export declare function ihdrData(width: number, height: number): Uint8Array<ArrayBuffer>;
|
|
38
|
+
/** The IEND chunk is a constant: empty payload, CRC 0xAE426082. */
|
|
39
|
+
export declare const IEND_CHUNK: Uint8Array<ArrayBuffer>;
|
|
40
|
+
/**
|
|
41
|
+
* IDAT payload split ceiling (1 MiB): a single IDAT may legally be any size,
|
|
42
|
+
* but decoders appreciate modest chunks — and splitting keeps the multi-IDAT
|
|
43
|
+
* path honest since most exports stay under it and emit exactly one.
|
|
44
|
+
*/
|
|
45
|
+
export declare const MAX_IDAT_CHUNK_BYTES: number;
|
|
46
|
+
/** Split compressed bytes into consecutive IDAT payloads (multi-chunk IDAT is legal and order-significant). */
|
|
47
|
+
export declare function splitIdatBytes(data: Uint8Array<ArrayBuffer>, maxBytes: number): Uint8Array<ArrayBuffer>[];
|
|
48
|
+
/**
|
|
49
|
+
* Row-by-row PNG assembler for one already-known raster size.
|
|
50
|
+
*
|
|
51
|
+
* Backpressure: the compressed readable side is drained by a loop started in
|
|
52
|
+
* the constructor — without a concurrent reader the transform stalls once its
|
|
53
|
+
* buffers fill and every writeRow would deadlock. Row bytes are NOT copied:
|
|
54
|
+
* writeRow's awaited write resolves only after the compressor has consumed the
|
|
55
|
+
* chunk, and the rasterizer feeds disjoint subarrays of an ImageData buffer
|
|
56
|
+
* that is never mutated after getImageData, so no staging buffer is needed.
|
|
57
|
+
*/
|
|
58
|
+
export declare class PngStitchWriter {
|
|
59
|
+
readonly width: number;
|
|
60
|
+
readonly height: number;
|
|
61
|
+
private rowsWritten;
|
|
62
|
+
private closed;
|
|
63
|
+
private readonly writer;
|
|
64
|
+
private readonly drained;
|
|
65
|
+
constructor(width: number, height: number);
|
|
66
|
+
/** Feed one scanline (`width × 4` RGBA bytes). The bytes must stay untouched until the returned promise resolves. */
|
|
67
|
+
writeRow(rgba: Uint8Array<ArrayBuffer>): Promise<void>;
|
|
68
|
+
/** Close the stream and assemble the file. Rejects unless exactly `height` rows were fed. */
|
|
69
|
+
finish(): Promise<Blob>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Feature probe for the stitch path (ticket 07): CompressionStream gates the
|
|
73
|
+
* whole mechanism (Safari gained it in 16.4, alongside createImageBitmap and
|
|
74
|
+
* Blob — the trio is checked anyway because each is independently load-bearing).
|
|
75
|
+
* Unavailable → the caller falls back to the legacy multi-file delivery.
|
|
76
|
+
*/
|
|
77
|
+
export declare function supportsPngStitch(): boolean;
|
|
78
|
+
/** One stitched slice: its painted PNG and its physical height. */
|
|
79
|
+
export interface StitchSlicePart {
|
|
80
|
+
blob: Blob;
|
|
81
|
+
/** Physical px height of the slice (the count of scanlines it contributes). */
|
|
82
|
+
height: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Stitch painted slices into one PNG of the shared physical width.
|
|
86
|
+
*
|
|
87
|
+
* Per slice: createImageBitmap → draw onto a throwaway canvas → getImageData →
|
|
88
|
+
* writeRow per scanline → zero the canvas and close the bitmap before the next
|
|
89
|
+
* slice. getImageData un-premultiplies alpha, which is lossy for translucent
|
|
90
|
+
* pixels — the share card paints on an opaque background (alpha 255), so the
|
|
91
|
+
* round-trip is exact here. Every slice must decode at exactly the global
|
|
92
|
+
* width and its declared height; a mismatch means a painting bug, and the
|
|
93
|
+
* writer's row accounting would corrupt the file, so it fails loudly instead.
|
|
94
|
+
*/
|
|
95
|
+
export declare function stitchSliceBlobs(parts: readonly StitchSlicePart[], width: number): Promise<Blob>;
|
|
96
|
+
//# sourceMappingURL=png-stitch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png-stitch.d.ts","sourceRoot":"","sources":["../../../../src/client/share/png-stitch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAiBH;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAM/C;AAED,qCAAqC;AACrC,eAAO,MAAM,aAAa,EAAE,UAAU,CAAC,WAAW,CAAoE,CAAA;AAgBtH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,CAYhF;AAED,mHAAmH;AACnH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAU/E;AAED,mEAAmE;AACnE,eAAO,MAAM,UAAU,EAAE,UAAU,CAAC,WAAW,CAAuC,CAAA;AAEtF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAU,CAAA;AAE3C,+GAA+G;AAC/G,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,EAAE,CAQzG;AAUD;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;gBAE9C,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAiCzC,qHAAqH;IAC/G,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D,6FAA6F;IACvF,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAe9B;AAOD;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,CAAA;IACV,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCtG"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Share-card slicing + rasterization (ticket 04, PLAN §5.2).
|
|
3
|
+
*
|
|
4
|
+
* Turns a rendered ShareCard DOM tree (ticket 03: fully inline-styled, marked
|
|
5
|
+
* with data-share-card / -header / -turn / -role / -footer) into a sequence of
|
|
6
|
+
* PNG blobs: slice by measured turn heights under a per-slice canvas pixel
|
|
7
|
+
* budget, then paint each slice through
|
|
8
|
+
* `<svg><foreignObject>` → Image → canvas → PNG export. The SVG document
|
|
9
|
+
* carries each slice's PHYSICAL target size (intrinsic = canvas), so the image
|
|
10
|
+
* rasterizes sharp at the canvas's own resolution and the draw is a 1:1 copy.
|
|
11
|
+
*
|
|
12
|
+
* File layout, and why it matters:
|
|
13
|
+
* - The top half is PURE planning math (no DOM, no React). It may import
|
|
14
|
+
* ONLY the pure constants module of this directory (share-constants.ts):
|
|
15
|
+
* scripts/check-share-image.mjs imports this file through Node type
|
|
16
|
+
* stripping, which EXECUTES every value import — JSX or DOM code would
|
|
17
|
+
* break the check, and any other module would widen the client bundle.
|
|
18
|
+
* - The DOM half below only MEASURES and EXECUTES the pure plan's decisions:
|
|
19
|
+
* slice grouping, width lifting, scale factors, pixel-ratio fallback and
|
|
20
|
+
* truncation are all decided by the pure functions, and the SVG document the
|
|
21
|
+
* painter draws is built by a pure serializer — so tests can pin all of it.
|
|
22
|
+
*
|
|
23
|
+
* Release policy per slice (PLAN §5.2): each slice is painted, blobbed and its
|
|
24
|
+
* canvas dropped before the next slice is built — the export never holds more
|
|
25
|
+
* than one live canvas. Ticket 07 adds the `unified` planning mode whose
|
|
26
|
+
* single physical width lets png-stitch.ts concatenate the slice PNGs into
|
|
27
|
+
* one long image.
|
|
28
|
+
*/
|
|
29
|
+
/** Per-slice physical pixel budget (iOS canvas hard cap 16.7M minus margin). */
|
|
30
|
+
export declare const SLICE_PIXEL_BUDGET = 12000000;
|
|
31
|
+
/** Slice count ceiling; beyond it the head is dropped and the first slice marked. */
|
|
32
|
+
export declare const MAX_SLICES = 24;
|
|
33
|
+
/** Pixel-ratio fallback ladder, walked top-down from min(devicePixelRatio, 2). */
|
|
34
|
+
export declare const PIXEL_RATIO_STEPS: readonly number[];
|
|
35
|
+
/** Safety px added around computed ultra widths (sub-pixel + rounding slack). */
|
|
36
|
+
export declare const ULTRA_WIDTH_MARGIN = 2;
|
|
37
|
+
/**
|
|
38
|
+
* Machine-readable failure kinds; ticket 05 maps them to localized copy.
|
|
39
|
+
* `slice-over-budget` (review 07+08): a unified-mode slice whose freshly
|
|
40
|
+
* MEASURED height busts the per-slice canvas budget at the global stitch
|
|
41
|
+
* width — the planner's global lever should have prevented it, and the old
|
|
42
|
+
* behavior (floor the height, silently clip the bottom) was invisible data
|
|
43
|
+
* loss, so it fails loudly instead.
|
|
44
|
+
*/
|
|
45
|
+
export type ShareRasterizeErrorCode = 'probe-failed' | 'image-load' | 'canvas' | 'empty-card' | 'slice-over-budget';
|
|
46
|
+
/** Localizable rasterize failure. `code` drives UI copy, `detail` is diagnostic. */
|
|
47
|
+
export declare class ShareRasterizeError extends Error {
|
|
48
|
+
readonly code: ShareRasterizeErrorCode;
|
|
49
|
+
readonly detail: string;
|
|
50
|
+
constructor(code: ShareRasterizeErrorCode, detail: string);
|
|
51
|
+
}
|
|
52
|
+
/** Descending pixel-ratio candidates for an already-clamped cap; never empty. */
|
|
53
|
+
export declare function ratioChain(cap: number): number[];
|
|
54
|
+
/** Physical pixels a slice occupies once painted (rounding up, canvas-style). */
|
|
55
|
+
export declare function slicePixelCount(outWidth: number, outHeight: number, pixelRatio: number): number;
|
|
56
|
+
/** Physical raster geometry of one slice: svg/foreignObject/canvas all take these. */
|
|
57
|
+
export interface RasterGeometry {
|
|
58
|
+
/** Physical px width (>= 1). */
|
|
59
|
+
width: number;
|
|
60
|
+
/** Physical px height (>= 1). */
|
|
61
|
+
height: number;
|
|
62
|
+
/** Wrapper transform = scale × pixelRatio (further shrunk when clamped). */
|
|
63
|
+
transformScale: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Physical target size for one slice's raster. The svg/foreignObject
|
|
67
|
+
* width/height attributes MUST be these numbers, never the logical ones: the
|
|
68
|
+
* browser rasterizes an SVG-as-image at its INTRINSIC size, so logical
|
|
69
|
+
* attributes make WebKit decode a small bitmap that drawImage then upscales
|
|
70
|
+
* (blurry on 2x screens) — and an unbounded ultra renderWidth would ask the
|
|
71
|
+
* decoder for an unbounded bitmap, sailing past the pixel budget before any
|
|
72
|
+
* canvas is allocated. The logical tree instead rides an inner wrapper scaled
|
|
73
|
+
* by `transformScale`, so the canvas draw is a 1:1 copy. Sizes are clamped
|
|
74
|
+
* into `budgetPx` (floor per side when clamping, so the product provably fits
|
|
75
|
+
* even where the unclamped per-side ceil would not have).
|
|
76
|
+
*/
|
|
77
|
+
export declare function rasterGeometry(renderWidth: number, logicalHeight: number, scale: number, pixelRatio: number, budgetPx: number): RasterGeometry;
|
|
78
|
+
/** Inputs of {@link buildSvgDocument}: logical tree size + physical target. */
|
|
79
|
+
export interface SvgDocumentSpec {
|
|
80
|
+
/** Logical px width of the slice tree (the wrapper's width). */
|
|
81
|
+
renderWidth: number;
|
|
82
|
+
/** Logical px height of the slice tree (the wrapper's height). */
|
|
83
|
+
logicalHeight: number;
|
|
84
|
+
/** Physical target width ({@link RasterGeometry.width}). */
|
|
85
|
+
width: number;
|
|
86
|
+
/** Physical target height ({@link RasterGeometry.height}). */
|
|
87
|
+
height: number;
|
|
88
|
+
/** Wrapper transform ({@link RasterGeometry.transformScale}). */
|
|
89
|
+
transformScale: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Serialize one slice subtree into a standalone SVG document sized at its
|
|
93
|
+
* PHYSICAL target: svg + foreignObject carry the physical px, the xhtml
|
|
94
|
+
* wrapper carries the logical size and a scale() transform, so the image
|
|
95
|
+
* rasterizes at exactly the resolution the canvas wants (no upscaled copy).
|
|
96
|
+
* `bodyHtml` is the prepared slice clone's outerHTML — namespace attributes
|
|
97
|
+
* must already be baked in by the caller (prepareXmlSerialization).
|
|
98
|
+
*/
|
|
99
|
+
export declare function buildSvgDocument(bodyHtml: string, spec: SvgDocumentSpec): string;
|
|
100
|
+
/**
|
|
101
|
+
* Card width that fits `contentWidth` of assistant-style rows: the row spans
|
|
102
|
+
* the card's whole content box, so only the card padding stacks on top.
|
|
103
|
+
*/
|
|
104
|
+
export declare function cardWidthForContent(contentWidth: number, cardPaddingX: number): number;
|
|
105
|
+
/**
|
|
106
|
+
* Card width that fits `contentWidth` inside a user bubble: the bubble is
|
|
107
|
+
* capped at {@link USER_BUBBLE_MAX_RATIO} of the row and adds its own padding,
|
|
108
|
+
* so the percentage constraint inverts into a division.
|
|
109
|
+
*/
|
|
110
|
+
export declare function cardWidthForUserBubble(contentWidth: number, cardPaddingX: number): number;
|
|
111
|
+
/** Card width a turn's measured content needs, by turn role. */
|
|
112
|
+
export declare function requiredCardWidth(role: 'user' | 'assistant', contentWidth: number, cardPaddingX: number): number;
|
|
113
|
+
/** Which slice geometry a turn needs, from its measured natural content width. */
|
|
114
|
+
export type TurnWidthBucket = 'base' | 'wide' | 'ultra';
|
|
115
|
+
/** Bucket one turn: base width / widened (~780) / wider-than-wide (scale-to-fit). */
|
|
116
|
+
export declare function bucketTurnWidth(role: 'user' | 'assistant', contentWidth: number, cardPaddingX: number, baseWidth: number, wideWidth: number): TurnWidthBucket;
|
|
117
|
+
/**
|
|
118
|
+
* Scale-to-fit geometry for content whose needed card width exceeds the wide
|
|
119
|
+
* width: render the slice at full natural width, shrink geometrically on
|
|
120
|
+
* output (pinch-zoom in the image viewer recovers readability — PLAN §5.2).
|
|
121
|
+
*/
|
|
122
|
+
export declare function ultraSliceWidth(role: 'user' | 'assistant', contentWidth: number, cardPaddingX: number, wideWidth: number): {
|
|
123
|
+
renderWidth: number;
|
|
124
|
+
scale: number;
|
|
125
|
+
};
|
|
126
|
+
/** One turn's final geometry as the DOM measured it at {@link renderWidth}. */
|
|
127
|
+
export interface PlannedTurnInput {
|
|
128
|
+
/** Logical px height of the turn at {@link renderWidth}. */
|
|
129
|
+
height: number;
|
|
130
|
+
/** Logical px width the turn's slice renders at (base / wide / ultra). */
|
|
131
|
+
renderWidth: number;
|
|
132
|
+
/** Geometric output downscale (1 except ultra turns). */
|
|
133
|
+
scale: number;
|
|
134
|
+
}
|
|
135
|
+
/** Fixed card chrome the planner must reserve height for. */
|
|
136
|
+
export interface PlanExtras {
|
|
137
|
+
/** Vertical gap between the card root's column children. */
|
|
138
|
+
gap: number;
|
|
139
|
+
/** Card root top + bottom padding. */
|
|
140
|
+
paddingY: number;
|
|
141
|
+
/** Header strip height (reserved on the first slice). */
|
|
142
|
+
headerHeight: number;
|
|
143
|
+
/** Footer strip height (reserved on the last slice). */
|
|
144
|
+
footerHeight: number;
|
|
145
|
+
/** Truncation-note bar height (reserved on a truncated last slice). */
|
|
146
|
+
noteHeight: number;
|
|
147
|
+
}
|
|
148
|
+
export interface PlanOptions {
|
|
149
|
+
budgetPx: number;
|
|
150
|
+
maxSlices: number;
|
|
151
|
+
/** Descending pixel-ratio candidates (see {@link ratioChain}). */
|
|
152
|
+
ratios: readonly number[];
|
|
153
|
+
}
|
|
154
|
+
/** One planned output image. */
|
|
155
|
+
export interface PlannedSlice {
|
|
156
|
+
/** Inclusive index of the first turn in this slice. */
|
|
157
|
+
from: number;
|
|
158
|
+
/** Inclusive index of the last turn in this slice. */
|
|
159
|
+
to: number;
|
|
160
|
+
/** Logical width the slice must be cloned/rendered at. */
|
|
161
|
+
renderWidth: number;
|
|
162
|
+
/** Total geometric downscale (ultra fit × budget lever; 1 = native). */
|
|
163
|
+
scale: number;
|
|
164
|
+
/** Logical height at {@link renderWidth}, chrome included. */
|
|
165
|
+
height: number;
|
|
166
|
+
/** Output logical width (renderWidth × scale). */
|
|
167
|
+
outWidth: number;
|
|
168
|
+
/** Output logical height (height × scale). */
|
|
169
|
+
outHeight: number;
|
|
170
|
+
/** Extra shrink applied to satisfy the pixel budget (1 = none). */
|
|
171
|
+
lever: number;
|
|
172
|
+
/** Carries the header strip (and the note when {@link truncated}). */
|
|
173
|
+
first: boolean;
|
|
174
|
+
/** Carries the footer strip. */
|
|
175
|
+
last: boolean;
|
|
176
|
+
/** True only on the first slice of a truncated export (marks the dropped head). */
|
|
177
|
+
truncated: boolean;
|
|
178
|
+
}
|
|
179
|
+
export interface SlicePlan {
|
|
180
|
+
pixelRatio: number;
|
|
181
|
+
slices: PlannedSlice[];
|
|
182
|
+
truncated: boolean;
|
|
183
|
+
/** Turns dropped from the HEAD by the {@link PlanOptions.maxSlices} ceiling (the tail — the recent turns a share is about — is always kept). */
|
|
184
|
+
droppedTurns: number;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Plan the slice sequence (legacy multi-file geometry, ticket 04). Beyond the
|
|
188
|
+
* shared walk, any slice still over budget (an un-splittable giant turn, or
|
|
189
|
+
* the header/footer chrome nudging a full slice past the line) is shrunk
|
|
190
|
+
* uniformly PER SLICE (the `lever`) — geometric scaling keeps completeness,
|
|
191
|
+
* zoom restores size. Per-slice levers make physical widths diverge, which is
|
|
192
|
+
* fine for separate files and exactly what the stitched planner removes.
|
|
193
|
+
*
|
|
194
|
+
* Deterministic pure function of its inputs.
|
|
195
|
+
*/
|
|
196
|
+
export declare function planShareSlices(turns: readonly PlannedTurnInput[], extras: PlanExtras, opts: PlanOptions): SlicePlan;
|
|
197
|
+
/**
|
|
198
|
+
* Global logical width of a stitched export: 390 unless ANY turn needs wide
|
|
199
|
+
* content, then 780 for the WHOLE card — one long image must not mix widths
|
|
200
|
+
* (scanlines concatenate only when every slice has the same pixel width; a
|
|
201
|
+
* mid-image line-length jump would read as a glitch anyway).
|
|
202
|
+
*/
|
|
203
|
+
export declare function unifiedCardWidth(anyTurnNeedsWide: boolean): number;
|
|
204
|
+
/**
|
|
205
|
+
* Plan the slice sequence for a stitched single-PNG export. Same walk and
|
|
206
|
+
* truncation semantics as {@link planShareSlices}, but the budget lever is
|
|
207
|
+
* GLOBAL: one multiplier shared by every slice, so every slice's output width
|
|
208
|
+
* is identical and the PNG stitcher can concatenate scanlines directly. The
|
|
209
|
+
* inputs must already be normalized to the global width by the caller (the
|
|
210
|
+
* DOM half measures the card once at {@link unifiedCardWidth}; turns still
|
|
211
|
+
* wider than that keep their per-turn ultra scale-to-fit, which lands on the
|
|
212
|
+
* same output width by construction).
|
|
213
|
+
*/
|
|
214
|
+
export declare function planShareSlicesUnified(turns: readonly PlannedTurnInput[], extras: PlanExtras, opts: PlanOptions): SlicePlan;
|
|
215
|
+
/**
|
|
216
|
+
* Authoritative physical width of a stitched plan — the IHDR width of the
|
|
217
|
+
* single long image. Every slice's own `outWidth × pixelRatio` must already
|
|
218
|
+
* agree; the max absorbs the ±ε float noise of ultra scales
|
|
219
|
+
* (renderWidth × W/renderWidth), so one integer serves the whole card and the
|
|
220
|
+
* painter FORCES it (rasterGeometryStitch) instead of re-deriving it per slice.
|
|
221
|
+
*/
|
|
222
|
+
export declare function stitchPixelWidth(plan: SlicePlan): number;
|
|
223
|
+
/** Stitch-mode raster geometry: the width is the GLOBAL one, never this slice's own arithmetic. */
|
|
224
|
+
export interface StitchRasterGeometry {
|
|
225
|
+
/** Physical px width — always the caller's global width, byte-for-byte. */
|
|
226
|
+
width: number;
|
|
227
|
+
/** Physical px height (>= 1); when `clamped`, the floor'd budget-capped height. */
|
|
228
|
+
height: number;
|
|
229
|
+
/** Wrapper transform = globalWidth / renderWidth (ultra fit × lever × ratio all folded in). */
|
|
230
|
+
transformScale: number;
|
|
231
|
+
/**
|
|
232
|
+
* True when the measured height busts the per-slice budget at the global
|
|
233
|
+
* width (the global lever should have prevented this). The pure function
|
|
234
|
+
* still returns the floor'd geometry so the check script can pin the
|
|
235
|
+
* arithmetic — the DOM half REFUSES to paint a clamped slice (a floor'd
|
|
236
|
+
* height means the bottom of the card is silently cut off: visible data
|
|
237
|
+
* loss, review 07+08 plan B).
|
|
238
|
+
*/
|
|
239
|
+
clamped: boolean;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Physical raster geometry of one slice in stitch mode. The width is pinned to
|
|
243
|
+
* `globalWidth` so every slice's canvas — and the scanlines the stitcher reads
|
|
244
|
+
* from it — is exactly as wide as the final image; a slice that freshly
|
|
245
|
+
* measures past the per-slice budget would need a height CLIP (never a width
|
|
246
|
+
* shrink: that would break concatenation, never aspect distortion), and the
|
|
247
|
+
* painter rejects that case instead of clipping (see `clamped`). The
|
|
248
|
+
* planner's global lever is the mechanism that keeps slices in budget; this
|
|
249
|
+
* clamp is the loud tripwire behind it.
|
|
250
|
+
*/
|
|
251
|
+
export declare function rasterGeometryStitch(renderWidth: number, logicalHeight: number, globalWidth: number, budgetPx: number): StitchRasterGeometry;
|
|
252
|
+
export interface RasterizeOptions {
|
|
253
|
+
/** Upper bound for the pixel ratio; default min(devicePixelRatio, 2). */
|
|
254
|
+
pixelRatioCap?: number;
|
|
255
|
+
/** Truncation-note copy, fed the count of dropped head turns (ticket 05 localizes it). */
|
|
256
|
+
truncatedNote?: (droppedTurns: number) => string;
|
|
257
|
+
/** Called 0/N once and done/N after each slice. */
|
|
258
|
+
onProgress?: (done: number, total: number) => void;
|
|
259
|
+
/**
|
|
260
|
+
* Stitch mode (ticket 07, PLAN §4.5): plan for a single-PNG stitched export —
|
|
261
|
+
* one global logical width (390/780), one global pixel ratio and one global
|
|
262
|
+
* budget lever, so every slice shares the same physical width and
|
|
263
|
+
* png-stitch.ts can concatenate scanlines. Default false keeps the legacy
|
|
264
|
+
* per-slice-width multi-file geometry (the fallback path for browsers
|
|
265
|
+
* without CompressionStream).
|
|
266
|
+
*/
|
|
267
|
+
unified?: boolean;
|
|
268
|
+
}
|
|
269
|
+
export interface RasterizeResult {
|
|
270
|
+
pixelRatio: number;
|
|
271
|
+
truncated: boolean;
|
|
272
|
+
droppedTurns: number;
|
|
273
|
+
slices: RasterizedSlice[];
|
|
274
|
+
/**
|
|
275
|
+
* Unified mode only: the physical pixel width every slice shares — the IHDR
|
|
276
|
+
* width of the stitched long image (undefined in legacy mode).
|
|
277
|
+
*/
|
|
278
|
+
stitchWidth: number | undefined;
|
|
279
|
+
}
|
|
280
|
+
export interface RasterizedSlice {
|
|
281
|
+
index: number;
|
|
282
|
+
blob: Blob;
|
|
283
|
+
/** Physical px size of the PNG. */
|
|
284
|
+
width: number;
|
|
285
|
+
height: number;
|
|
286
|
+
/** Logical slice width (before scaling). */
|
|
287
|
+
logicalWidth: number;
|
|
288
|
+
/** Total geometric scale applied on output. */
|
|
289
|
+
scale: number;
|
|
290
|
+
/** Transcript seq of the first/last turn in this slice (debug + ordering). */
|
|
291
|
+
fromSeq: number;
|
|
292
|
+
toSeq: number;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* 2×2 feature probe (PLAN §5.2): before the first export, rasterize a tiny
|
|
296
|
+
* known card (four solid color cells through the exact foreignObject → Image
|
|
297
|
+
* → canvas path) and read the pixels back. Old Safari renders foreignObject
|
|
298
|
+
* images as nothing — exporting then would produce blank PNGs, so the probe
|
|
299
|
+
* fails loudly once per page load instead. Failure is sticky.
|
|
300
|
+
*/
|
|
301
|
+
export declare function ensureProbe(): Promise<void>;
|
|
302
|
+
/**
|
|
303
|
+
* Rasterize a rendered share card into a PNG sequence.
|
|
304
|
+
*
|
|
305
|
+
* Pipeline: probe once → measure a clone at base width → bucket turns (pure)
|
|
306
|
+
* → measure wide/ultra turns at their final widths → plan slices (pure) →
|
|
307
|
+
* per slice: prune a clone to the slice, measure, serialize into SVG, paint,
|
|
308
|
+
* release. The returned blobs are owned by the caller (object URLs etc.).
|
|
309
|
+
*
|
|
310
|
+
* `unified` (ticket 07) flips both planning decisions the stitch depends on:
|
|
311
|
+
* one global logical width (re-measured once at 390 or 780 — a card that
|
|
312
|
+
* lifts ANY turn to wide renders every turn wide) and one global budget
|
|
313
|
+
* lever, so `stitchWidth` comes back as the single physical width every slice
|
|
314
|
+
* shares and png-stitch.ts can concatenate the scanlines into one PNG.
|
|
315
|
+
*/
|
|
316
|
+
export declare function rasterizeShareCard(card: HTMLElement, options?: RasterizeOptions): Promise<RasterizeResult>;
|
|
317
|
+
//# sourceMappingURL=rasterize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.d.ts","sourceRoot":"","sources":["../../../../src/client/share/rasterize.ts"],"names":[],"mappings":"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"}
|