返回 oh-my-ppt
slide-size.ts
根目录 / src / shared / slide-size.ts
1 export type SlideSizePresetId =
2 | 'wide-16-9'
3 | 'vertical-9-16'
4 | 'standard-4-3'
5 | 'square-1-1'
6 | 'vertical-3-4'
7 | 'xiaohongshu-note'
8
9 export interface SlideSizePreset {
10 id: SlideSizePresetId
11 label: string
12 width: number
13 height: number
14 }
15
16 export const DEFAULT_SLIDE_SIZE_ID: SlideSizePresetId = 'wide-16-9'
17
18 export const SLIDE_SIZE_PRESETS: readonly SlideSizePreset[] = [
19 { id: 'wide-16-9', label: '宽屏 16:9', width: 1600, height: 900 },
20 { id: 'vertical-9-16', label: '竖屏 9:16', width: 900, height: 1600 },
21 { id: 'standard-4-3', label: '标准 4:3', width: 1600, height: 1200 },
22 { id: 'square-1-1', label: '方图 1:1', width: 1200, height: 1200 },
23 { id: 'vertical-3-4', label: '竖版 3:4', width: 1200, height: 1600 },
24 { id: 'xiaohongshu-note', label: '小红书', width: 1242, height: 1660 }
25 ] as const
26
27 const PRESET_BY_ID = new Map(SLIDE_SIZE_PRESETS.map((preset) => [preset.id, preset]))
28
29 const toPositiveInteger = (value: unknown): number | undefined => {
30 const parsed = Number(value)
31 return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : undefined
32 }
33
34 const isSlideSizePresetId = (value: unknown): value is SlideSizePresetId =>
35 typeof value === 'string' && PRESET_BY_ID.has(value as SlideSizePresetId)
36
37 export function resolveSlideSize(input?: {
38 id?: unknown
39 width?: unknown
40 height?: unknown
41 }): SlideSizePreset {
42 const id = isSlideSizePresetId(input?.id) ? input.id : DEFAULT_SLIDE_SIZE_ID
43 const preset = PRESET_BY_ID.get(id) ?? PRESET_BY_ID.get(DEFAULT_SLIDE_SIZE_ID)!
44 const width = toPositiveInteger(input?.width)
45 const height = toPositiveInteger(input?.height)
46
47 return {
48 ...preset,
49 width: width ?? preset.width,
50 height: height ?? preset.height
51 }
52 }
53
54 export function requireSlideSize(input: {
55 id?: unknown
56 width?: unknown
57 height?: unknown
58 }): SlideSizePreset {
59 if (!isSlideSizePresetId(input.id)) {
60 throw new Error(`Invalid slide size id: ${String(input.id ?? '')}`)
61 }
62 const preset = PRESET_BY_ID.get(input.id)!
63 const width = input.width === undefined || input.width === null ? preset.width : toPositiveInteger(input.width)
64 const height =
65 input.height === undefined || input.height === null ? preset.height : toPositiveInteger(input.height)
66 if (!width || !height) {
67 throw new Error(`Invalid slide size dimensions for ${input.id}`)
68 }
69 return { ...preset, width, height }
70 }
71
72 export const requireSlideSizePreset = (id: unknown): SlideSizePreset => requireSlideSize({ id })
73
74 export function requirePersistedSlideSize(input: {
75 id?: unknown
76 width?: unknown
77 height?: unknown
78 }): SlideSizePreset {
79 if (!isSlideSizePresetId(input.id)) {
80 throw new Error(`Invalid slide size id: ${String(input.id ?? '')}`)
81 }
82 if (input.width === undefined || input.width === null || input.height === undefined || input.height === null) {
83 throw new Error(`Invalid slide size dimensions for ${String(input.id ?? '')}`)
84 }
85 return requireSlideSize(input)
86 }
87
88 export function resolveSessionSlideSize(session?: unknown): SlideSizePreset {
89 return requireSessionSlideSize(session)
90 }
91
92 export function trySessionSlideSize(session?: unknown): SlideSizePreset | null {
93 if (!session) return null
94 try {
95 return requireSessionSlideSize(session)
96 } catch {
97 return null
98 }
99 }
100
101 export function requireSessionSlideSize(session: unknown): SlideSizePreset {
102 const record =
103 session && typeof session === 'object' && !Array.isArray(session)
104 ? (session as Record<string, unknown>)
105 : {}
106 return requirePersistedSlideSize({
107 id: record.slideSizeId ?? record.slide_size_id,
108 width: record.slideWidth ?? record.slide_width,
109 height: record.slideHeight ?? record.slide_height
110 })
111 }
112
113 export function resolveSlideSizeFromHtml(html: string): SlideSizePreset {
114 return requireSlideSizeFromHtml(html)
115 }
116
117 export function requireSlideSizeFromHtml(html: string): SlideSizePreset {
118 const metadataMatch = html.match(
119 /<script[^>]+id=["']deck-metadata["'][^>]*>([\s\S]*?)<\/script>/i
120 )
121 if (metadataMatch?.[1]) {
122 const metadata = JSON.parse(metadataMatch[1]) as Record<string, unknown>
123 return requirePersistedSlideSize({
124 id: metadata.slideSizeId,
125 width: metadata.width,
126 height: metadata.height
127 })
128 }
129 return requirePersistedSlideSize({
130 id: html.match(/\bdata-ppt-slide-size-id=["']([^"']+)["']/i)?.[1],
131 width: html.match(/\bdata-ppt-width=["'](\d+)["']/i)?.[1],
132 height: html.match(/\bdata-ppt-height=["'](\d+)["']/i)?.[1]
133 })
134 }
135
136 export const isDefaultSlideSize = (slideSize: SlideSizePreset): boolean =>
137 slideSize.id === DEFAULT_SLIDE_SIZE_ID &&
138 slideSize.width === 1600 &&
139 slideSize.height === 900
140
141 export type PptxExportSlideSizeId = Extract<
142 SlideSizePresetId,
143 'wide-16-9' | 'standard-4-3'
144 >
145
146 export const isPptxExportSupported = (
147 slideSize: SlideSizePreset
148 ): slideSize is SlideSizePreset & { id: PptxExportSlideSizeId } =>
149 slideSize.id === 'wide-16-9' || slideSize.id === 'standard-4-3'
150
151 export const PPTX_SLIDE_SIZE_ERROR =
152 '当前 PPTX 导出仅支持 16:9 和 4:3。请使用这两种演示尺寸,或导出 PNG/PDF/视频。'
153
154 export function assertPptxExportSupported(slideSize: SlideSizePreset): void {
155 if (!isPptxExportSupported(slideSize)) throw new Error(PPTX_SLIDE_SIZE_ERROR)
156 }
157
157 lines TYPESCRIPT