@akairos/typeset-studio 0.2.2 → 0.2.3
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 +90 -93
- package/dist/assets/index-3Ko1UCBU.css +1 -0
- package/dist/assets/index-C9J0f4Lf.js +317 -0
- package/dist/index.html +2 -2
- package/dist/lib/adapter.js +6 -7582
- package/dist/lib/canvas-renderer-cxnar75n.js +23 -0
- package/dist/lib/font-loader-j28mt43t.js +11 -0
- package/dist/lib/index-0370qz9e.js +303 -0
- package/dist/lib/index-37x76zdn.js +4 -0
- package/dist/lib/index-6c43c7h4.js +3880 -0
- package/dist/lib/index-t9w3p2p9.js +3366 -0
- package/dist/lib/index-zdhqg5f0.js +459 -0
- package/dist/lib/index.js +26 -7582
- package/dist/lib/render-xxd348m1.js +11 -0
- package/package.json +2 -3
- package/src/adapter.test.ts +117 -0
- package/src/adapter.ts +121 -30
- package/src/core/canvas-draw.ts +141 -38
- package/src/core/canvas-renderer.test.ts +58 -0
- package/src/core/canvas-renderer.ts +2 -1
- package/src/core/data-dir.ts +13 -0
- package/src/core/engine.ts +74 -36
- package/src/core/font-loader.test.ts +20 -0
- package/src/core/font-loader.ts +6 -2
- package/src/core/image-layout.ts +4 -0
- package/src/core/inline-text.ts +6 -4
- package/src/core/obstacle-flow.ts +29 -15
- package/src/core/page-images.test.ts +90 -0
- package/src/core/pretext.ts +4 -2
- package/src/core/template.test.ts +38 -1
- package/src/core/template.ts +52 -40
- package/src/core/types.ts +46 -0
- package/src/index.ts +4 -1
- package/src/server/auth.ts +18 -13
- package/src/server/index.ts +10 -15
- package/src/server/routes/render.ts +60 -17
- package/src/server/routes/slots.test.ts +96 -0
- package/src/server/routes/templates.ts +34 -35
- package/src/server/routes/upload.ts +2 -1
- package/src/web/App.tsx +15 -4
- package/src/web/components/CanvasViewport.tsx +56 -42
- package/src/web/components/EditorSidebar.tsx +35 -0
- package/src/web/components/TemplateModal.tsx +22 -14
- package/src/web/hooks/image-layout.ts +31 -0
- package/src/web/hooks/useLocalFonts.ts +10 -1
- package/src/web/hooks/useTypeset.ts +119 -32
- package/data/templates/.gitkeep +0 -0
- package/data/templates/tpl_1791681833354_iofsk.json +0 -46
- package/data/templates/tpl_1791682603199_875ny.json +0 -66
- package/data/templates/tpl_1791682780929_qnuiy.json +0 -66
- package/data/templates/tpl_longform_paper_sage.json +0 -47
- package/data/templates/tpl_longform_vintage_news.json +0 -47
- package/data/templates/tpl_newspic_poster_cover.json +0 -64
- package/data/templates/tpl_square_quote_card.json +0 -46
- package/data/templates/tpl_wechat_cover_editorial.json +0 -64
- package/data/templates/tpl_wechat_cover_minimal.json +0 -64
- package/data/templates/tpl_wide_tech_banner.json +0 -46
- package/dist/assets/index-CZ352D7X.css +0 -1
- package/dist/assets/index-DDeEGeHD.js +0 -317
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { clearCache } from "@chenglou/pretext";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
3
|
import { getDefaultConfig, runTypeset } from "../../core/engine";
|
|
3
4
|
import { computeStageDimensions, getCanvasSpec } from "../../core/geometry";
|
|
4
5
|
import { resolveImagePlacement } from "../../core/image-layout";
|
|
5
|
-
import { applyTemplateImages } from "../../core/template";
|
|
6
|
+
import { applyTemplateImages, validateTemplateAssets } from "../../core/template";
|
|
7
|
+
import { backgroundImageLayout, restoreImageLayout, snapshotImageLayout, type ImageLayout } from "./image-layout";
|
|
6
8
|
import {
|
|
7
9
|
AlgorithmParams,
|
|
8
10
|
AspectRatioType,
|
|
@@ -36,12 +38,14 @@ function resolvePresetImage(
|
|
|
36
38
|
): FlowImage {
|
|
37
39
|
if (image.isAbsolute || !image.layoutPreset || image.layoutPreset === "custom") return image;
|
|
38
40
|
const page = image.pageIndex || 1;
|
|
39
|
-
const indexOnPage = images.filter((item) => (item.pageIndex || 1) === page)
|
|
41
|
+
const indexOnPage = images.filter((item) => Boolean(item.templateAssetKey) === Boolean(image.templateAssetKey) && (item.pageIndex || 1) === page)
|
|
40
42
|
.findIndex((item) => item.id === image.id);
|
|
41
43
|
return resolveImagePlacement(image, indexOnPage, stageWidth, stageHeight);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
export function useTypeset() {
|
|
46
|
+
export function useTypeset(adminKey: string, isVerified: boolean | null) {
|
|
47
|
+
const [error, setError] = useState<string | null>(null);
|
|
48
|
+
const [fontVersion, setFontVersion] = useState(0);
|
|
45
49
|
const [config, setConfig] = useState<TypesetConfig>(() => getDefaultConfig("longform-3-4"));
|
|
46
50
|
const aspectRatio = config.aspectRatio;
|
|
47
51
|
const [markdown, setMarkdown] = useState<string>(INITIAL_MARKDOWN);
|
|
@@ -60,18 +64,54 @@ export function useTypeset() {
|
|
|
60
64
|
},
|
|
61
65
|
]);
|
|
62
66
|
const [highlightWords, setHighlightWords] = useState<string[]>(["算法排版", "确定性", "空间秩序"]);
|
|
67
|
+
// 上传或应用模板时记录初始布局,背景切换另存一份勾选前的布局。
|
|
68
|
+
const initialImageLayouts = useRef(new Map(images.map((image) => [image.id, snapshotImageLayout(image)])));
|
|
69
|
+
const [backgroundLayouts, setBackgroundLayouts] = useState<Record<string, ImageLayout>>({});
|
|
63
70
|
|
|
64
71
|
// Calculate typeset result synchronously (instant 60fps)
|
|
65
|
-
const result
|
|
66
|
-
|
|
67
|
-
markdown,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const { result, layoutError } = useMemo(() => {
|
|
73
|
+
try {
|
|
74
|
+
return { result: runTypeset({ markdown, images, config, highlightWords }), layoutError: null };
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
result: { pages: [], pageCount: 0, canvasSpec: getCanvasSpec(config.aspectRatio), config } satisfies TypesetResult,
|
|
78
|
+
layoutError: err instanceof Error ? err.message : "排版失败",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}, [markdown, images, config, highlightWords, fontVersion]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
let active = true;
|
|
85
|
+
let refreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
const refresh = () => {
|
|
87
|
+
if (!active) return;
|
|
88
|
+
clearTimeout(refreshTimer);
|
|
89
|
+
refreshTimer = setTimeout(() => {
|
|
90
|
+
clearCache();
|
|
91
|
+
setFontVersion((version) => version + 1);
|
|
92
|
+
}, 0);
|
|
93
|
+
};
|
|
94
|
+
document.fonts.ready.then(refresh);
|
|
95
|
+
document.fonts.addEventListener("loadingdone", refresh);
|
|
96
|
+
window.addEventListener("typeset-fonts-loaded", refresh);
|
|
97
|
+
return () => {
|
|
98
|
+
active = false;
|
|
99
|
+
clearTimeout(refreshTimer);
|
|
100
|
+
document.fonts.removeEventListener("loadingdone", refresh);
|
|
101
|
+
window.removeEventListener("typeset-fonts-loaded", refresh);
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
73
104
|
|
|
74
105
|
useEffect(() => {
|
|
106
|
+
if (!isVerified) return;
|
|
107
|
+
let active = true;
|
|
108
|
+
const headers = { "x-admin-key": adminKey };
|
|
109
|
+
const readTemplate = async (url: string) => {
|
|
110
|
+
const response = await fetch(url, { headers });
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
if (!response.ok || !data.ok) throw new Error(data.message || data.error || "加载模板失败");
|
|
113
|
+
return data;
|
|
114
|
+
};
|
|
75
115
|
const initFromUrlAndDefaults = async () => {
|
|
76
116
|
try {
|
|
77
117
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -90,45 +130,43 @@ export function useTypeset() {
|
|
|
90
130
|
|
|
91
131
|
// 1. Handle explicit template loading
|
|
92
132
|
if (urlTemplate) {
|
|
93
|
-
const
|
|
94
|
-
const data = await res.json();
|
|
133
|
+
const data = await readTemplate(`/api/templates/resolve/${encodeURIComponent(urlTemplate)}`);
|
|
95
134
|
if (data.ok && data.template) {
|
|
96
|
-
loadTemplate(data.template);
|
|
135
|
+
if (active) loadTemplate(data.template);
|
|
97
136
|
return;
|
|
98
137
|
}
|
|
99
138
|
}
|
|
100
139
|
|
|
101
140
|
// 2. Handle slot loading
|
|
102
141
|
if (urlSlot) {
|
|
103
|
-
const
|
|
104
|
-
const data = await res.json();
|
|
142
|
+
const data = await readTemplate(`/api/templates/slots?account=${encodeURIComponent(urlAccount)}`);
|
|
105
143
|
if (data.ok && data.slots?.[urlSlot]?.template) {
|
|
106
|
-
loadTemplate(data.slots[urlSlot].template);
|
|
144
|
+
if (active) loadTemplate(data.slots[urlSlot].template);
|
|
107
145
|
return;
|
|
108
146
|
}
|
|
109
147
|
}
|
|
110
148
|
|
|
111
149
|
// 3. If no template or slot in URL, check slot defaults or global default
|
|
112
|
-
const
|
|
113
|
-
const data = await res.json();
|
|
150
|
+
const data = await readTemplate(`/api/templates/slots?account=${encodeURIComponent(urlAccount)}`);
|
|
114
151
|
if (data.ok && data.slots?.newspic_content?.template) {
|
|
115
|
-
loadTemplate(data.slots.newspic_content.template);
|
|
152
|
+
if (active) loadTemplate(data.slots.newspic_content.template);
|
|
116
153
|
return;
|
|
117
154
|
}
|
|
118
155
|
|
|
119
156
|
// 4. Fallback default
|
|
120
|
-
const
|
|
121
|
-
const defData = await defRes.json();
|
|
157
|
+
const defData = await readTemplate("/api/templates/default");
|
|
122
158
|
if (defData.ok && defData.template) {
|
|
123
|
-
loadTemplate(defData.template);
|
|
159
|
+
if (active) loadTemplate(defData.template);
|
|
124
160
|
}
|
|
125
|
-
} catch {
|
|
126
|
-
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (active) setError(err instanceof Error ? err.message : "加载模板失败");
|
|
127
163
|
}
|
|
128
164
|
};
|
|
129
165
|
|
|
166
|
+
setError(null);
|
|
130
167
|
initFromUrlAndDefaults();
|
|
131
|
-
|
|
168
|
+
return () => { active = false; };
|
|
169
|
+
}, [adminKey, isVerified]);
|
|
132
170
|
|
|
133
171
|
const switchAspectRatio = (ratio: AspectRatioType) => {
|
|
134
172
|
const spec = getCanvasSpec(ratio);
|
|
@@ -193,7 +231,7 @@ export function useTypeset() {
|
|
|
193
231
|
config.algorithm.contentBottomGap,
|
|
194
232
|
);
|
|
195
233
|
const targetPage = image.pageIndex || 1;
|
|
196
|
-
const samePageImages = images.filter((im) => (im.pageIndex || 1) === targetPage);
|
|
234
|
+
const samePageImages = images.filter((im) => !im.templateAssetKey && (im.pageIndex || 1) === targetPage);
|
|
197
235
|
const indexOnPage = samePageImages.length;
|
|
198
236
|
|
|
199
237
|
const temp: FlowImage = {
|
|
@@ -208,14 +246,17 @@ export function useTypeset() {
|
|
|
208
246
|
width: resolved.width,
|
|
209
247
|
height: resolved.height,
|
|
210
248
|
};
|
|
249
|
+
initialImageLayouts.current.set(newImage.id, snapshotImageLayout(newImage));
|
|
211
250
|
setImages((prev) => [...prev, newImage]);
|
|
212
251
|
};
|
|
213
252
|
|
|
214
253
|
const updateImage = (id: string, partial: Partial<FlowImage>) => {
|
|
254
|
+
const previousBackground = partial.isAbsolute === false ? backgroundLayouts[id] : undefined;
|
|
255
|
+
if (previousBackground) clearBackgroundLayout(id);
|
|
215
256
|
setImages((prev) => {
|
|
216
257
|
const nextImages = prev.map((img) => {
|
|
217
258
|
if (img.id !== id) return img;
|
|
218
|
-
const merged
|
|
259
|
+
const merged = restoreImageLayout(img, { ...previousBackground, ...partial });
|
|
219
260
|
if (partial.layoutPreset && partial.layoutPreset !== "custom") {
|
|
220
261
|
// Clear stale preset coordinates when switching presets
|
|
221
262
|
delete merged.x;
|
|
@@ -246,7 +287,40 @@ export function useTypeset() {
|
|
|
246
287
|
});
|
|
247
288
|
};
|
|
248
289
|
|
|
290
|
+
// 清除背景状态,避免恢复布局后复选框仍处于选中状态。
|
|
291
|
+
const clearBackgroundLayout = (id: string) => setBackgroundLayouts((prev) => {
|
|
292
|
+
const next = { ...prev };
|
|
293
|
+
delete next[id];
|
|
294
|
+
return next;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// 背景复选框只覆盖当前布局,取消时恢复勾选前的快照。
|
|
298
|
+
const toggleImageBackground = (id: string, enabled: boolean) => {
|
|
299
|
+
const image = images.find((item) => item.id === id);
|
|
300
|
+
if (!image) return;
|
|
301
|
+
if (enabled) {
|
|
302
|
+
if (backgroundLayouts[id]) return;
|
|
303
|
+
setBackgroundLayouts((prev) => ({ ...prev, [id]: snapshotImageLayout(image) }));
|
|
304
|
+
updateImage(id, backgroundImageLayout(config));
|
|
305
|
+
} else {
|
|
306
|
+
const layout = backgroundLayouts[id];
|
|
307
|
+
if (!layout) return;
|
|
308
|
+
clearBackgroundLayout(id);
|
|
309
|
+
setImages((prev) => prev.map((item) => item.id === id ? restoreImageLayout(item, layout) : item));
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// 重置到本次上传或应用模板时的布局,不改内容和固定素材标识。
|
|
314
|
+
const resetImageLayout = (id: string) => {
|
|
315
|
+
const layout = initialImageLayouts.current.get(id);
|
|
316
|
+
if (!layout) return;
|
|
317
|
+
clearBackgroundLayout(id);
|
|
318
|
+
setImages((prev) => prev.map((image) => image.id === id ? restoreImageLayout(image, layout) : image));
|
|
319
|
+
};
|
|
320
|
+
|
|
249
321
|
const removeImage = (id: string) => {
|
|
322
|
+
initialImageLayouts.current.delete(id);
|
|
323
|
+
clearBackgroundLayout(id);
|
|
250
324
|
setImages((prev) => {
|
|
251
325
|
const remaining = prev.filter((img) => img.id !== id);
|
|
252
326
|
const spec = getCanvasSpec(aspectRatio);
|
|
@@ -282,9 +356,18 @@ export function useTypeset() {
|
|
|
282
356
|
};
|
|
283
357
|
|
|
284
358
|
const loadTemplate = (tpl: TypesetConfig) => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
359
|
+
try {
|
|
360
|
+
validateTemplateAssets(tpl.images);
|
|
361
|
+
setConfig(tpl);
|
|
362
|
+
setBackgroundLayouts({});
|
|
363
|
+
setImages((prev) => {
|
|
364
|
+
const next = applyTemplateImages(prev, tpl.images);
|
|
365
|
+
initialImageLayouts.current = new Map(next.map((image) => [image.id, snapshotImageLayout(image)]));
|
|
366
|
+
return next;
|
|
367
|
+
});
|
|
368
|
+
setError(null);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
setError(err instanceof Error ? err.message : "模板素材无效");
|
|
288
371
|
}
|
|
289
372
|
};
|
|
290
373
|
|
|
@@ -296,6 +379,7 @@ export function useTypeset() {
|
|
|
296
379
|
};
|
|
297
380
|
|
|
298
381
|
return {
|
|
382
|
+
error: layoutError || error,
|
|
299
383
|
aspectRatio,
|
|
300
384
|
switchAspectRatio,
|
|
301
385
|
config,
|
|
@@ -305,6 +389,9 @@ export function useTypeset() {
|
|
|
305
389
|
images,
|
|
306
390
|
addImage,
|
|
307
391
|
updateImage,
|
|
392
|
+
toggleImageBackground,
|
|
393
|
+
resetImageLayout,
|
|
394
|
+
backgroundImageIds: Object.keys(backgroundLayouts),
|
|
308
395
|
removeImage,
|
|
309
396
|
moveImage,
|
|
310
397
|
highlightWords,
|
package/data/templates/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "封面1",
|
|
3
|
-
"aspectRatio": "wechat-cover-2.35",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 56,
|
|
6
|
-
"scaleRatio": 1.3,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "center",
|
|
12
|
-
"verticalAlign": "center",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 40,
|
|
15
|
-
"right": 60,
|
|
16
|
-
"bottom": 40,
|
|
17
|
-
"left": 60
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#f9fcfa",
|
|
22
|
-
"textColor": "#22201d",
|
|
23
|
-
"headingColor": "#0f2e22",
|
|
24
|
-
"accentColor": "#1d4f39",
|
|
25
|
-
"quoteColor": "#474038",
|
|
26
|
-
"captionColor": "#71717a",
|
|
27
|
-
"watermarkColor": "#52525b",
|
|
28
|
-
"cardBg": "#ffffff"
|
|
29
|
-
},
|
|
30
|
-
"typography": {
|
|
31
|
-
"headingFont": "PingFang SC",
|
|
32
|
-
"bodyFont": "SourceHanSansSC, sans-serif"
|
|
33
|
-
},
|
|
34
|
-
"footerText": "TECH ENGINEERING INSIGHTS",
|
|
35
|
-
"watermarkText": "DEV // ARCHITECTURE",
|
|
36
|
-
"chrome": {
|
|
37
|
-
"showHeader": false,
|
|
38
|
-
"showWatermark": true,
|
|
39
|
-
"showPageNumber": false,
|
|
40
|
-
"showFooter": true,
|
|
41
|
-
"showFooterDivider": false,
|
|
42
|
-
"showFooterText": true,
|
|
43
|
-
"showFillRatio": false
|
|
44
|
-
},
|
|
45
|
-
"id": "tpl_1791681833354_iofsk"
|
|
46
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "封面2",
|
|
3
|
-
"aspectRatio": "wechat-cover-2.35",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 56,
|
|
6
|
-
"scaleRatio": 1.25,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "center",
|
|
12
|
-
"verticalAlign": "center",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 40,
|
|
15
|
-
"right": 60,
|
|
16
|
-
"bottom": 40,
|
|
17
|
-
"left": 60
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#ffffff",
|
|
22
|
-
"textColor": "#171717",
|
|
23
|
-
"headingColor": "#171717",
|
|
24
|
-
"accentColor": "#059669",
|
|
25
|
-
"quoteColor": "#525252",
|
|
26
|
-
"captionColor": "#737373",
|
|
27
|
-
"watermarkColor": "#a3a3a3",
|
|
28
|
-
"cardBg": "#f5f5f5",
|
|
29
|
-
"borderSubtle": "#e5e5e5"
|
|
30
|
-
},
|
|
31
|
-
"typography": {
|
|
32
|
-
"headingFont": "PingFang SC",
|
|
33
|
-
"bodyFont": "Songti SC"
|
|
34
|
-
},
|
|
35
|
-
"footerText": "Typeset Studio · 纯算法排版",
|
|
36
|
-
"watermarkText": "@Antigravity",
|
|
37
|
-
"chrome": {
|
|
38
|
-
"showHeader": true,
|
|
39
|
-
"showWatermark": true,
|
|
40
|
-
"showPageNumber": true,
|
|
41
|
-
"showFooter": true,
|
|
42
|
-
"showFooterDivider": false,
|
|
43
|
-
"showFooterText": true,
|
|
44
|
-
"showFillRatio": false
|
|
45
|
-
},
|
|
46
|
-
"images": [
|
|
47
|
-
{
|
|
48
|
-
"id": "img_demo_1",
|
|
49
|
-
"src": "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=800&auto=format&fit=crop&q=80",
|
|
50
|
-
"alt": "抽象建筑线条",
|
|
51
|
-
"caption": "图 1:建筑结构中的空间秩序感",
|
|
52
|
-
"x": 1276,
|
|
53
|
-
"y": 170,
|
|
54
|
-
"width": 700,
|
|
55
|
-
"height": 590,
|
|
56
|
-
"captionHeight": 40,
|
|
57
|
-
"layoutPreset": "custom",
|
|
58
|
-
"isAbsolute": true,
|
|
59
|
-
"zIndex": 0,
|
|
60
|
-
"rotate": -14,
|
|
61
|
-
"opacity": 0.65,
|
|
62
|
-
"borderRadius": 136
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
"id": "tpl_1791682603199_875ny"
|
|
66
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "封面2-3",
|
|
3
|
-
"aspectRatio": "wechat-cover-2.35",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 56,
|
|
6
|
-
"scaleRatio": 1.25,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "center",
|
|
12
|
-
"verticalAlign": "center",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 40,
|
|
15
|
-
"right": 60,
|
|
16
|
-
"bottom": 40,
|
|
17
|
-
"left": 60
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#0a0a0a",
|
|
22
|
-
"textColor": "#ededed",
|
|
23
|
-
"headingColor": "#ffffff",
|
|
24
|
-
"accentColor": "#00e676",
|
|
25
|
-
"quoteColor": "#a0a0a0",
|
|
26
|
-
"captionColor": "#737373",
|
|
27
|
-
"watermarkColor": "#a3a3a3",
|
|
28
|
-
"cardBg": "#121212",
|
|
29
|
-
"borderSubtle": "#e5e5e5"
|
|
30
|
-
},
|
|
31
|
-
"typography": {
|
|
32
|
-
"headingFont": "PingFang SC",
|
|
33
|
-
"bodyFont": "Songti SC"
|
|
34
|
-
},
|
|
35
|
-
"footerText": "Typeset Studio · 纯算法排版",
|
|
36
|
-
"watermarkText": "@Antigravity",
|
|
37
|
-
"chrome": {
|
|
38
|
-
"showHeader": true,
|
|
39
|
-
"showWatermark": true,
|
|
40
|
-
"showPageNumber": true,
|
|
41
|
-
"showFooter": true,
|
|
42
|
-
"showFooterDivider": false,
|
|
43
|
-
"showFooterText": true,
|
|
44
|
-
"showFillRatio": false
|
|
45
|
-
},
|
|
46
|
-
"images": [
|
|
47
|
-
{
|
|
48
|
-
"src": "/uploads/1791682691652_fsgtx6.png",
|
|
49
|
-
"alt": "头像-捧脸大笑-v4.png",
|
|
50
|
-
"caption": "",
|
|
51
|
-
"x": 1276,
|
|
52
|
-
"y": 170,
|
|
53
|
-
"width": 700,
|
|
54
|
-
"height": 590,
|
|
55
|
-
"captionHeight": 40,
|
|
56
|
-
"layoutPreset": "custom",
|
|
57
|
-
"id": "img_1791682691661_1zpg",
|
|
58
|
-
"isAbsolute": true,
|
|
59
|
-
"rotate": -14,
|
|
60
|
-
"opacity": 0.65,
|
|
61
|
-
"zIndex": 0,
|
|
62
|
-
"borderRadius": 136
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
"id": "tpl_1791682780929_qnuiy"
|
|
66
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "纸白典雅长图文 (3:4)",
|
|
3
|
-
"aspectRatio": "longform-3-4",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 40,
|
|
6
|
-
"scaleRatio": 1.25,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "left",
|
|
12
|
-
"verticalAlign": "top",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 96,
|
|
15
|
-
"right": 88,
|
|
16
|
-
"bottom": 96,
|
|
17
|
-
"left": 88
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#fbfcfb",
|
|
22
|
-
"textColor": "#1c1917",
|
|
23
|
-
"headingColor": "#0c0a09",
|
|
24
|
-
"accentColor": "#047857",
|
|
25
|
-
"quoteColor": "#44403c",
|
|
26
|
-
"captionColor": "#78716c",
|
|
27
|
-
"watermarkColor": "#a8a29e",
|
|
28
|
-
"cardBg": "#ffffff"
|
|
29
|
-
},
|
|
30
|
-
"typography": {
|
|
31
|
-
"headingFont": "Songti SC",
|
|
32
|
-
"bodyFont": "Songti SC"
|
|
33
|
-
},
|
|
34
|
-
"footerText": "Typeset Studio · 纯算法空间排版",
|
|
35
|
-
"watermarkText": "@ANTIGRAVITY",
|
|
36
|
-
"chrome": {
|
|
37
|
-
"showHeader": true,
|
|
38
|
-
"showWatermark": true,
|
|
39
|
-
"showPageNumber": true,
|
|
40
|
-
"showFooter": true,
|
|
41
|
-
"showFooterDivider": true,
|
|
42
|
-
"showFooterText": true,
|
|
43
|
-
"showFillRatio": false
|
|
44
|
-
},
|
|
45
|
-
"id": "tpl_longform_paper_sage",
|
|
46
|
-
"slot": "newspic_content"
|
|
47
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "复古报刊多图 (3:4)",
|
|
3
|
-
"aspectRatio": "longform-3-4",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 38,
|
|
6
|
-
"scaleRatio": 1.25,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "left",
|
|
12
|
-
"verticalAlign": "top",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 96,
|
|
15
|
-
"right": 88,
|
|
16
|
-
"bottom": 96,
|
|
17
|
-
"left": 88
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#f7f4ee",
|
|
22
|
-
"textColor": "#262320",
|
|
23
|
-
"headingColor": "#141210",
|
|
24
|
-
"accentColor": "#991b1b",
|
|
25
|
-
"quoteColor": "#57534e",
|
|
26
|
-
"captionColor": "#78716c",
|
|
27
|
-
"watermarkColor": "#a8a29e",
|
|
28
|
-
"cardBg": "#eee9df"
|
|
29
|
-
},
|
|
30
|
-
"typography": {
|
|
31
|
-
"headingFont": "SourceHanSerifSC, serif",
|
|
32
|
-
"bodyFont": "SourceHanSerifSC, serif"
|
|
33
|
-
},
|
|
34
|
-
"footerText": "WEEKLY REPORT · 观察与沉淀",
|
|
35
|
-
"watermarkText": "VOL. 42 // THE CHRONICLE",
|
|
36
|
-
"chrome": {
|
|
37
|
-
"showHeader": true,
|
|
38
|
-
"showWatermark": true,
|
|
39
|
-
"showPageNumber": true,
|
|
40
|
-
"showFooter": true,
|
|
41
|
-
"showFooterDivider": true,
|
|
42
|
-
"showFooterText": true,
|
|
43
|
-
"showFillRatio": false
|
|
44
|
-
},
|
|
45
|
-
"id": "tpl_longform_vintage_news",
|
|
46
|
-
"slot": "newspic_content"
|
|
47
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "贴图封面海报 (3:4)",
|
|
3
|
-
"aspectRatio": "longform-3-4",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 56,
|
|
6
|
-
"scaleRatio": 1.35,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "center",
|
|
12
|
-
"verticalAlign": "center",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 96,
|
|
15
|
-
"right": 88,
|
|
16
|
-
"bottom": 96,
|
|
17
|
-
"left": 88
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#121212",
|
|
22
|
-
"textColor": "#ffffff",
|
|
23
|
-
"headingColor": "#ffffff",
|
|
24
|
-
"accentColor": "#f59e0b",
|
|
25
|
-
"quoteColor": "#a3a3a3",
|
|
26
|
-
"captionColor": "#737373",
|
|
27
|
-
"watermarkColor": "#525252",
|
|
28
|
-
"cardBg": "#1c1c1c"
|
|
29
|
-
},
|
|
30
|
-
"typography": {
|
|
31
|
-
"headingFont": "PingFang SC",
|
|
32
|
-
"bodyFont": "Songti SC"
|
|
33
|
-
},
|
|
34
|
-
"footerText": "Typeset Studio · 贴图精选",
|
|
35
|
-
"watermarkText": "FEATURED NEWSPIC",
|
|
36
|
-
"chrome": {
|
|
37
|
-
"showHeader": true,
|
|
38
|
-
"showWatermark": true,
|
|
39
|
-
"showPageNumber": false,
|
|
40
|
-
"showFooter": true,
|
|
41
|
-
"showFooterDivider": false,
|
|
42
|
-
"showFooterText": true,
|
|
43
|
-
"showFillRatio": false
|
|
44
|
-
},
|
|
45
|
-
"id": "tpl_newspic_poster_cover",
|
|
46
|
-
"slot": "newspic_cover",
|
|
47
|
-
"images": [
|
|
48
|
-
{
|
|
49
|
-
"id": "tpl_img_poster_bg",
|
|
50
|
-
"src": "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80",
|
|
51
|
-
"alt": "海报主图",
|
|
52
|
-
"isAbsolute": true,
|
|
53
|
-
"x": 100,
|
|
54
|
-
"y": 780,
|
|
55
|
-
"width": 1000,
|
|
56
|
-
"height": 650,
|
|
57
|
-
"zIndex": 10,
|
|
58
|
-
"rotate": 0,
|
|
59
|
-
"opacity": 1,
|
|
60
|
-
"borderRadius": 20,
|
|
61
|
-
"layoutPreset": "custom"
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "金句留白方卡 (1:1)",
|
|
3
|
-
"aspectRatio": "square-1-1",
|
|
4
|
-
"algorithm": {
|
|
5
|
-
"baseFontSize": 50,
|
|
6
|
-
"scaleRatio": 1.3,
|
|
7
|
-
"lineHeightMultiplier": 1.45,
|
|
8
|
-
"obstacleGap": 24,
|
|
9
|
-
"targetFillRatio": 0.8,
|
|
10
|
-
"contentBottomGap": 20,
|
|
11
|
-
"textAlign": "center",
|
|
12
|
-
"verticalAlign": "center",
|
|
13
|
-
"safeArea": {
|
|
14
|
-
"top": 100,
|
|
15
|
-
"right": 100,
|
|
16
|
-
"bottom": 100,
|
|
17
|
-
"left": 100
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"theme": {
|
|
21
|
-
"bgColor": "#18181b",
|
|
22
|
-
"textColor": "#fafafa",
|
|
23
|
-
"headingColor": "#ffffff",
|
|
24
|
-
"accentColor": "#f59e0b",
|
|
25
|
-
"quoteColor": "#d4d4d8",
|
|
26
|
-
"captionColor": "#a1a1aa",
|
|
27
|
-
"watermarkColor": "#71717a",
|
|
28
|
-
"cardBg": "#27272a"
|
|
29
|
-
},
|
|
30
|
-
"typography": {
|
|
31
|
-
"headingFont": "PingFang SC",
|
|
32
|
-
"bodyFont": "Kaiti SC"
|
|
33
|
-
},
|
|
34
|
-
"footerText": "",
|
|
35
|
-
"watermarkText": "",
|
|
36
|
-
"chrome": {
|
|
37
|
-
"showHeader": false,
|
|
38
|
-
"showWatermark": false,
|
|
39
|
-
"showPageNumber": false,
|
|
40
|
-
"showFooter": false,
|
|
41
|
-
"showFooterDivider": false,
|
|
42
|
-
"showFooterText": false,
|
|
43
|
-
"showFillRatio": false
|
|
44
|
-
},
|
|
45
|
-
"id": "tpl_square_quote_card"
|
|
46
|
-
}
|