@akairos/typeset-studio 0.2.1 → 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 +91 -95
- 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 -7734
- 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 -7734
- package/dist/lib/render-xxd348m1.js +11 -0
- package/package.json +3 -7
- package/src/adapter.test.ts +117 -0
- package/src/adapter.ts +190 -281
- package/src/core/canvas-draw.ts +150 -47
- 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 +86 -56
- 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 -10
- package/src/core/template.test.ts +38 -1
- package/src/core/template.ts +53 -41
- package/src/core/types.ts +46 -0
- package/src/core/typography.ts +0 -14
- package/src/index.ts +6 -3
- package/src/server/auth.ts +18 -13
- package/src/server/index.ts +11 -16
- package/src/server/routes/fonts.ts +1 -1
- package/src/server/routes/render.ts +60 -17
- package/src/server/routes/slots.test.ts +96 -0
- package/src/server/routes/templates.ts +37 -48
- package/src/server/routes/upload.ts +2 -1
- package/src/web/App.tsx +15 -6
- package/src/web/components/CanvasViewport.tsx +56 -42
- package/src/web/components/EditorSidebar.tsx +59 -48
- package/src/web/components/Header.tsx +1 -1
- package/src/web/components/TemplateModal.tsx +25 -28
- package/src/web/hooks/image-layout.ts +31 -0
- package/src/web/hooks/useLocalFonts.ts +10 -1
- package/src/web/hooks/useTypeset.ts +139 -100
- 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-Dpnz-F2g.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,
|
|
@@ -27,9 +29,25 @@ const INITIAL_MARKDOWN = `# 大模型纯算法排版深度剖析
|
|
|
27
29
|
- 任意画板比例智能自适应
|
|
28
30
|
`;
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// 统一预设配图的页内序号计算,手动坐标和自由图层保持原样。
|
|
33
|
+
function resolvePresetImage(
|
|
34
|
+
image: FlowImage,
|
|
35
|
+
images: FlowImage[],
|
|
36
|
+
stageWidth: number,
|
|
37
|
+
stageHeight: number,
|
|
38
|
+
): FlowImage {
|
|
39
|
+
if (image.isAbsolute || !image.layoutPreset || image.layoutPreset === "custom") return image;
|
|
40
|
+
const page = image.pageIndex || 1;
|
|
41
|
+
const indexOnPage = images.filter((item) => Boolean(item.templateAssetKey) === Boolean(image.templateAssetKey) && (item.pageIndex || 1) === page)
|
|
42
|
+
.findIndex((item) => item.id === image.id);
|
|
43
|
+
return resolveImagePlacement(image, indexOnPage, stageWidth, stageHeight);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useTypeset(adminKey: string, isVerified: boolean | null) {
|
|
47
|
+
const [error, setError] = useState<string | null>(null);
|
|
48
|
+
const [fontVersion, setFontVersion] = useState(0);
|
|
32
49
|
const [config, setConfig] = useState<TypesetConfig>(() => getDefaultConfig("longform-3-4"));
|
|
50
|
+
const aspectRatio = config.aspectRatio;
|
|
33
51
|
const [markdown, setMarkdown] = useState<string>(INITIAL_MARKDOWN);
|
|
34
52
|
const [images, setImages] = useState<FlowImage[]>([
|
|
35
53
|
{
|
|
@@ -46,18 +64,54 @@ export function useTypeset() {
|
|
|
46
64
|
},
|
|
47
65
|
]);
|
|
48
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>>({});
|
|
49
70
|
|
|
50
71
|
// Calculate typeset result synchronously (instant 60fps)
|
|
51
|
-
const result
|
|
52
|
-
|
|
53
|
-
markdown,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
}, []);
|
|
59
104
|
|
|
60
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
|
+
};
|
|
61
115
|
const initFromUrlAndDefaults = async () => {
|
|
62
116
|
try {
|
|
63
117
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -76,48 +130,45 @@ export function useTypeset() {
|
|
|
76
130
|
|
|
77
131
|
// 1. Handle explicit template loading
|
|
78
132
|
if (urlTemplate) {
|
|
79
|
-
const
|
|
80
|
-
const data = await res.json();
|
|
133
|
+
const data = await readTemplate(`/api/templates/resolve/${encodeURIComponent(urlTemplate)}`);
|
|
81
134
|
if (data.ok && data.template) {
|
|
82
|
-
loadTemplate(data.template);
|
|
135
|
+
if (active) loadTemplate(data.template);
|
|
83
136
|
return;
|
|
84
137
|
}
|
|
85
138
|
}
|
|
86
139
|
|
|
87
140
|
// 2. Handle slot loading
|
|
88
141
|
if (urlSlot) {
|
|
89
|
-
const
|
|
90
|
-
const data = await res.json();
|
|
142
|
+
const data = await readTemplate(`/api/templates/slots?account=${encodeURIComponent(urlAccount)}`);
|
|
91
143
|
if (data.ok && data.slots?.[urlSlot]?.template) {
|
|
92
|
-
loadTemplate(data.slots[urlSlot].template);
|
|
144
|
+
if (active) loadTemplate(data.slots[urlSlot].template);
|
|
93
145
|
return;
|
|
94
146
|
}
|
|
95
147
|
}
|
|
96
148
|
|
|
97
149
|
// 3. If no template or slot in URL, check slot defaults or global default
|
|
98
|
-
const
|
|
99
|
-
const data = await res.json();
|
|
150
|
+
const data = await readTemplate(`/api/templates/slots?account=${encodeURIComponent(urlAccount)}`);
|
|
100
151
|
if (data.ok && data.slots?.newspic_content?.template) {
|
|
101
|
-
loadTemplate(data.slots.newspic_content.template);
|
|
152
|
+
if (active) loadTemplate(data.slots.newspic_content.template);
|
|
102
153
|
return;
|
|
103
154
|
}
|
|
104
155
|
|
|
105
156
|
// 4. Fallback default
|
|
106
|
-
const
|
|
107
|
-
const defData = await defRes.json();
|
|
157
|
+
const defData = await readTemplate("/api/templates/default");
|
|
108
158
|
if (defData.ok && defData.template) {
|
|
109
|
-
loadTemplate(defData.template);
|
|
159
|
+
if (active) loadTemplate(defData.template);
|
|
110
160
|
}
|
|
111
|
-
} catch {
|
|
112
|
-
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (active) setError(err instanceof Error ? err.message : "加载模板失败");
|
|
113
163
|
}
|
|
114
164
|
};
|
|
115
165
|
|
|
166
|
+
setError(null);
|
|
116
167
|
initFromUrlAndDefaults();
|
|
117
|
-
|
|
168
|
+
return () => { active = false; };
|
|
169
|
+
}, [adminKey, isVerified]);
|
|
118
170
|
|
|
119
171
|
const switchAspectRatio = (ratio: AspectRatioType) => {
|
|
120
|
-
setAspectRatio(ratio);
|
|
121
172
|
const spec = getCanvasSpec(ratio);
|
|
122
173
|
const nextDefault = getDefaultConfig(ratio);
|
|
123
174
|
const { stageWidth, stageHeight } = computeStageDimensions(
|
|
@@ -125,24 +176,7 @@ export function useTypeset() {
|
|
|
125
176
|
nextDefault.algorithm.safeArea,
|
|
126
177
|
nextDefault.algorithm.contentBottomGap,
|
|
127
178
|
);
|
|
128
|
-
setImages((prev) =>
|
|
129
|
-
prev.map((img) => {
|
|
130
|
-
if (!img.isAbsolute && img.layoutPreset && img.layoutPreset !== "custom") {
|
|
131
|
-
const page = img.pageIndex || 1;
|
|
132
|
-
const samePageList = prev.filter((im) => (im.pageIndex || 1) === page);
|
|
133
|
-
const indexOnPage = Math.max(0, samePageList.findIndex((im) => im.id === img.id));
|
|
134
|
-
const resolved = resolveImagePlacement(img, indexOnPage, stageWidth, stageHeight);
|
|
135
|
-
return {
|
|
136
|
-
...img,
|
|
137
|
-
x: resolved.x,
|
|
138
|
-
y: resolved.y,
|
|
139
|
-
width: resolved.width,
|
|
140
|
-
height: resolved.height,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
return img;
|
|
144
|
-
}),
|
|
145
|
-
);
|
|
179
|
+
setImages((prev) => prev.map((image) => resolvePresetImage(image, prev, stageWidth, stageHeight)));
|
|
146
180
|
setConfig((prev) => {
|
|
147
181
|
return {
|
|
148
182
|
...prev,
|
|
@@ -197,7 +231,7 @@ export function useTypeset() {
|
|
|
197
231
|
config.algorithm.contentBottomGap,
|
|
198
232
|
);
|
|
199
233
|
const targetPage = image.pageIndex || 1;
|
|
200
|
-
const samePageImages = images.filter((im) => (im.pageIndex || 1) === targetPage);
|
|
234
|
+
const samePageImages = images.filter((im) => !im.templateAssetKey && (im.pageIndex || 1) === targetPage);
|
|
201
235
|
const indexOnPage = samePageImages.length;
|
|
202
236
|
|
|
203
237
|
const temp: FlowImage = {
|
|
@@ -212,14 +246,17 @@ export function useTypeset() {
|
|
|
212
246
|
width: resolved.width,
|
|
213
247
|
height: resolved.height,
|
|
214
248
|
};
|
|
249
|
+
initialImageLayouts.current.set(newImage.id, snapshotImageLayout(newImage));
|
|
215
250
|
setImages((prev) => [...prev, newImage]);
|
|
216
251
|
};
|
|
217
252
|
|
|
218
253
|
const updateImage = (id: string, partial: Partial<FlowImage>) => {
|
|
254
|
+
const previousBackground = partial.isAbsolute === false ? backgroundLayouts[id] : undefined;
|
|
255
|
+
if (previousBackground) clearBackgroundLayout(id);
|
|
219
256
|
setImages((prev) => {
|
|
220
257
|
const nextImages = prev.map((img) => {
|
|
221
258
|
if (img.id !== id) return img;
|
|
222
|
-
const merged
|
|
259
|
+
const merged = restoreImageLayout(img, { ...previousBackground, ...partial });
|
|
223
260
|
if (partial.layoutPreset && partial.layoutPreset !== "custom") {
|
|
224
261
|
// Clear stale preset coordinates when switching presets
|
|
225
262
|
delete merged.x;
|
|
@@ -235,35 +272,55 @@ export function useTypeset() {
|
|
|
235
272
|
|
|
236
273
|
// If non-custom flow preset, recalculate clean coordinates based on its page index
|
|
237
274
|
if (!updatedImg.isAbsolute && updatedImg.layoutPreset && updatedImg.layoutPreset !== "custom") {
|
|
238
|
-
const page = updatedImg.pageIndex || 1;
|
|
239
|
-
const samePageList = nextImages.filter((im) => (im.pageIndex || 1) === page);
|
|
240
|
-
const indexOnPage = Math.max(0, samePageList.findIndex((im) => im.id === id));
|
|
241
|
-
|
|
242
275
|
const spec = getCanvasSpec(aspectRatio);
|
|
243
276
|
const { stageWidth, stageHeight } = computeStageDimensions(
|
|
244
277
|
spec,
|
|
245
278
|
config.algorithm.safeArea,
|
|
246
279
|
config.algorithm.contentBottomGap,
|
|
247
280
|
);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
? {
|
|
252
|
-
...im,
|
|
253
|
-
x: resolved.x,
|
|
254
|
-
y: resolved.y,
|
|
255
|
-
width: resolved.width,
|
|
256
|
-
height: resolved.height,
|
|
257
|
-
}
|
|
258
|
-
: im,
|
|
259
|
-
);
|
|
281
|
+
return nextImages.map((image) => image.id === id
|
|
282
|
+
? resolvePresetImage(image, nextImages, stageWidth, stageHeight)
|
|
283
|
+
: image);
|
|
260
284
|
}
|
|
261
285
|
|
|
262
286
|
return nextImages;
|
|
263
287
|
});
|
|
264
288
|
};
|
|
265
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
|
+
|
|
266
321
|
const removeImage = (id: string) => {
|
|
322
|
+
initialImageLayouts.current.delete(id);
|
|
323
|
+
clearBackgroundLayout(id);
|
|
267
324
|
setImages((prev) => {
|
|
268
325
|
const remaining = prev.filter((img) => img.id !== id);
|
|
269
326
|
const spec = getCanvasSpec(aspectRatio);
|
|
@@ -272,22 +329,7 @@ export function useTypeset() {
|
|
|
272
329
|
config.algorithm.safeArea,
|
|
273
330
|
config.algorithm.contentBottomGap,
|
|
274
331
|
);
|
|
275
|
-
return remaining.map((
|
|
276
|
-
if (!img.isAbsolute && img.layoutPreset && img.layoutPreset !== "custom") {
|
|
277
|
-
const page = img.pageIndex || 1;
|
|
278
|
-
const samePageList = remaining.filter((im) => (im.pageIndex || 1) === page);
|
|
279
|
-
const indexOnPage = Math.max(0, samePageList.findIndex((im) => im.id === img.id));
|
|
280
|
-
const resolved = resolveImagePlacement(img, indexOnPage, stageWidth, stageHeight);
|
|
281
|
-
return {
|
|
282
|
-
...img,
|
|
283
|
-
x: resolved.x,
|
|
284
|
-
y: resolved.y,
|
|
285
|
-
width: resolved.width,
|
|
286
|
-
height: resolved.height,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
return img;
|
|
290
|
-
});
|
|
332
|
+
return remaining.map((image) => resolvePresetImage(image, remaining, stageWidth, stageHeight));
|
|
291
333
|
});
|
|
292
334
|
};
|
|
293
335
|
|
|
@@ -309,30 +351,23 @@ export function useTypeset() {
|
|
|
309
351
|
config.algorithm.contentBottomGap,
|
|
310
352
|
);
|
|
311
353
|
|
|
312
|
-
return next.map((
|
|
313
|
-
if (!img.isAbsolute && img.layoutPreset && img.layoutPreset !== "custom") {
|
|
314
|
-
const page = img.pageIndex || 1;
|
|
315
|
-
const samePageList = next.filter((im) => (im.pageIndex || 1) === page);
|
|
316
|
-
const indexOnPage = Math.max(0, samePageList.findIndex((im) => im.id === img.id));
|
|
317
|
-
const resolved = resolveImagePlacement(img, indexOnPage, stageWidth, stageHeight);
|
|
318
|
-
return {
|
|
319
|
-
...img,
|
|
320
|
-
x: resolved.x,
|
|
321
|
-
y: resolved.y,
|
|
322
|
-
width: resolved.width,
|
|
323
|
-
height: resolved.height,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
return img;
|
|
327
|
-
});
|
|
354
|
+
return next.map((image) => resolvePresetImage(image, next, stageWidth, stageHeight));
|
|
328
355
|
});
|
|
329
356
|
};
|
|
330
357
|
|
|
331
358
|
const loadTemplate = (tpl: TypesetConfig) => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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 : "模板素材无效");
|
|
336
371
|
}
|
|
337
372
|
};
|
|
338
373
|
|
|
@@ -344,6 +379,7 @@ export function useTypeset() {
|
|
|
344
379
|
};
|
|
345
380
|
|
|
346
381
|
return {
|
|
382
|
+
error: layoutError || error,
|
|
347
383
|
aspectRatio,
|
|
348
384
|
switchAspectRatio,
|
|
349
385
|
config,
|
|
@@ -353,6 +389,9 @@ export function useTypeset() {
|
|
|
353
389
|
images,
|
|
354
390
|
addImage,
|
|
355
391
|
updateImage,
|
|
392
|
+
toggleImageBackground,
|
|
393
|
+
resetImageLayout,
|
|
394
|
+
backgroundImageIds: Object.keys(backgroundLayouts),
|
|
356
395
|
removeImage,
|
|
357
396
|
moveImage,
|
|
358
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
|
-
}
|