@akairos/typeset-studio 0.2.0 → 0.2.2
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 +2 -3
- package/dist/assets/index-DDeEGeHD.js +317 -0
- package/dist/index.html +1 -1
- package/dist/lib/adapter.js +424 -543
- package/dist/lib/index.js +424 -543
- package/package.json +2 -5
- package/src/adapter.ts +85 -267
- package/src/core/canvas-draw.ts +9 -9
- package/src/core/engine.ts +14 -22
- package/src/core/pretext.ts +0 -8
- package/src/core/template.ts +1 -1
- package/src/core/typography.ts +0 -14
- package/src/index.ts +2 -2
- package/src/server/index.ts +1 -1
- package/src/server/routes/fonts.ts +1 -1
- package/src/server/routes/templates.ts +54 -25
- package/src/web/App.tsx +0 -2
- package/src/web/components/EditorSidebar.tsx +24 -48
- package/src/web/components/Header.tsx +1 -1
- package/src/web/components/TemplateModal.tsx +3 -14
- package/src/web/hooks/useTypeset.ts +21 -69
- package/dist/assets/index-Dpnz-F2g.js +0 -317
package/src/server/index.ts
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { dirname, join, resolve } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
3
4
|
import { Hono } from "hono";
|
|
4
5
|
import { getDefaultConfig } from "../../core/engine";
|
|
5
6
|
import { TemplateSlot, TypesetConfig } from "../../core/types";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
export function resolveDataDir(): string {
|
|
9
|
+
if (process.env.TYPESET_DATA_DIR) {
|
|
10
|
+
return process.env.TYPESET_DATA_DIR;
|
|
11
|
+
}
|
|
12
|
+
const currentDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const candidateLib = resolve(currentDir, "../../data");
|
|
14
|
+
if (existsSync(candidateLib)) return candidateLib;
|
|
15
|
+
const candidateSrc = resolve(currentDir, "../../../data");
|
|
16
|
+
if (existsSync(candidateSrc)) return candidateSrc;
|
|
17
|
+
const candidateCwd = resolve(process.cwd(), "data");
|
|
18
|
+
if (existsSync(candidateCwd)) return candidateCwd;
|
|
19
|
+
return candidateLib;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TEMPLATES_DIR = join(resolveDataDir(), "templates");
|
|
8
23
|
|
|
9
24
|
function ensureTemplatesDir(): void {
|
|
10
25
|
if (!existsSync(TEMPLATES_DIR)) {
|
|
11
|
-
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(TEMPLATES_DIR, { recursive: true });
|
|
28
|
+
} catch {
|
|
29
|
+
// Ignore write errors in read-only environments
|
|
30
|
+
}
|
|
12
31
|
}
|
|
13
32
|
seedDefaultTemplates();
|
|
14
33
|
}
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
const presets: TypesetConfig[] = [
|
|
35
|
+
export const BUILTIN_PRESETS: TypesetConfig[] = [
|
|
18
36
|
{
|
|
19
37
|
...getDefaultConfig("wechat-cover-2.35"),
|
|
20
38
|
id: "tpl_wechat_cover_minimal",
|
|
@@ -325,13 +343,21 @@ function seedDefaultTemplates(): void {
|
|
|
325
343
|
},
|
|
326
344
|
];
|
|
327
345
|
|
|
328
|
-
|
|
346
|
+
function seedDefaultTemplates(): void {
|
|
347
|
+
if (!existsSync(TEMPLATES_DIR)) return;
|
|
348
|
+
for (const preset of BUILTIN_PRESETS) {
|
|
329
349
|
const filePath = join(TEMPLATES_DIR, `${preset.id}.json`);
|
|
330
|
-
|
|
350
|
+
if (!existsSync(filePath)) {
|
|
351
|
+
try {
|
|
352
|
+
writeFileSync(filePath, JSON.stringify(preset, null, 2), "utf-8");
|
|
353
|
+
} catch {
|
|
354
|
+
// Ignore write failures in package directories
|
|
355
|
+
}
|
|
356
|
+
}
|
|
331
357
|
}
|
|
332
358
|
}
|
|
333
359
|
|
|
334
|
-
const DEFAULT_SETTINGS_PATH = join(
|
|
360
|
+
const DEFAULT_SETTINGS_PATH = join(resolveDataDir(), "default-template.json");
|
|
335
361
|
|
|
336
362
|
export const BUILTIN_SLOT_DEFAULTS: Record<TemplateSlot, string> = {
|
|
337
363
|
article_cover: "tpl_wechat_cover_minimal",
|
|
@@ -411,7 +437,21 @@ export function setDefaultTemplateId(id: string): void {
|
|
|
411
437
|
}
|
|
412
438
|
|
|
413
439
|
export function findTemplateByKeyOrName(key: string): TypesetConfig | null {
|
|
414
|
-
|
|
440
|
+
const lowerKey = key.trim().toLowerCase();
|
|
441
|
+
|
|
442
|
+
// 1. Direct match in built-in memory presets
|
|
443
|
+
const builtin = BUILTIN_PRESETS.find(
|
|
444
|
+
(p) => p.id?.toLowerCase() === lowerKey || p.name.toLowerCase() === lowerKey,
|
|
445
|
+
);
|
|
446
|
+
if (builtin) return builtin;
|
|
447
|
+
|
|
448
|
+
// 2. Partial match in built-in memory presets
|
|
449
|
+
const builtinPartial = BUILTIN_PRESETS.find((p) => p.name.toLowerCase().includes(lowerKey));
|
|
450
|
+
if (builtinPartial) return builtinPartial;
|
|
451
|
+
|
|
452
|
+
// 3. Scan disk if templates directory exists
|
|
453
|
+
if (!existsSync(TEMPLATES_DIR)) return null;
|
|
454
|
+
|
|
415
455
|
const directPath = join(TEMPLATES_DIR, `${key}.json`);
|
|
416
456
|
if (existsSync(directPath)) {
|
|
417
457
|
try {
|
|
@@ -421,10 +461,10 @@ export function findTemplateByKeyOrName(key: string): TypesetConfig | null {
|
|
|
421
461
|
}
|
|
422
462
|
}
|
|
423
463
|
|
|
424
|
-
// Scan all templates
|
|
464
|
+
// Scan all templates on disk
|
|
425
465
|
const files = readdirSync(TEMPLATES_DIR).filter((f) => f.endsWith(".json"));
|
|
426
|
-
const lowerKey = key.trim().toLowerCase();
|
|
427
466
|
|
|
467
|
+
const templates: TypesetConfig[] = [];
|
|
428
468
|
for (const file of files) {
|
|
429
469
|
try {
|
|
430
470
|
const content = readFileSync(join(TEMPLATES_DIR, file), "utf-8");
|
|
@@ -432,25 +472,14 @@ export function findTemplateByKeyOrName(key: string): TypesetConfig | null {
|
|
|
432
472
|
if (tpl.id?.toLowerCase() === lowerKey || tpl.name.toLowerCase() === lowerKey) {
|
|
433
473
|
return tpl;
|
|
434
474
|
}
|
|
475
|
+
templates.push(tpl);
|
|
435
476
|
} catch {
|
|
436
477
|
// ignore
|
|
437
478
|
}
|
|
438
479
|
}
|
|
439
480
|
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
const content = readFileSync(join(TEMPLATES_DIR, file), "utf-8");
|
|
444
|
-
const tpl = JSON.parse(content) as TypesetConfig;
|
|
445
|
-
if (tpl.name.toLowerCase().includes(lowerKey)) {
|
|
446
|
-
return tpl;
|
|
447
|
-
}
|
|
448
|
-
} catch {
|
|
449
|
-
// ignore
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return null;
|
|
481
|
+
// 精确匹配优先,名称模糊匹配复用已读取的模板。
|
|
482
|
+
return templates.find((tpl) => tpl.name.toLowerCase().includes(lowerKey)) ?? null;
|
|
454
483
|
}
|
|
455
484
|
|
|
456
485
|
export const templatesRouter = new Hono();
|
package/src/web/App.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { toPng } from "html-to-image";
|
|
3
2
|
import { AuthModal } from "./components/AuthModal";
|
|
4
3
|
import { CanvasViewport } from "./components/CanvasViewport";
|
|
5
4
|
import { EditorSidebar } from "./components/EditorSidebar";
|
|
@@ -197,7 +196,6 @@ export const App: React.FC = () => {
|
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
if (exportSucceeded) {
|
|
200
|
-
setIsExporting(false);
|
|
201
199
|
return;
|
|
202
200
|
}
|
|
203
201
|
}
|
|
@@ -56,6 +56,26 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = ({
|
|
|
56
56
|
const file = e.target.files?.[0];
|
|
57
57
|
if (!file) return;
|
|
58
58
|
|
|
59
|
+
// 上传失败时仍可用本地图片继续编辑。
|
|
60
|
+
const addUploadedImage = (src: string) => onAddImage({
|
|
61
|
+
src,
|
|
62
|
+
alt: file.name,
|
|
63
|
+
caption: "",
|
|
64
|
+
x: 40,
|
|
65
|
+
y: 400,
|
|
66
|
+
width: 440,
|
|
67
|
+
height: 300,
|
|
68
|
+
captionHeight: 36,
|
|
69
|
+
layoutPreset: "auto",
|
|
70
|
+
});
|
|
71
|
+
const readLocalImage = () => {
|
|
72
|
+
const reader = new FileReader();
|
|
73
|
+
reader.onload = () => {
|
|
74
|
+
if (typeof reader.result === "string") addUploadedImage(reader.result);
|
|
75
|
+
};
|
|
76
|
+
reader.readAsDataURL(file);
|
|
77
|
+
};
|
|
78
|
+
|
|
59
79
|
setIsUploading(true);
|
|
60
80
|
try {
|
|
61
81
|
const formData = new FormData();
|
|
@@ -69,56 +89,12 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = ({
|
|
|
69
89
|
|
|
70
90
|
const data = await res.json();
|
|
71
91
|
if (data.ok && data.url) {
|
|
72
|
-
|
|
73
|
-
src: data.url,
|
|
74
|
-
alt: file.name,
|
|
75
|
-
caption: "",
|
|
76
|
-
x: 40,
|
|
77
|
-
y: 400,
|
|
78
|
-
width: 440,
|
|
79
|
-
height: 300,
|
|
80
|
-
captionHeight: 36,
|
|
81
|
-
layoutPreset: "auto",
|
|
82
|
-
});
|
|
92
|
+
addUploadedImage(data.url);
|
|
83
93
|
} else {
|
|
84
|
-
|
|
85
|
-
const reader = new FileReader();
|
|
86
|
-
reader.onload = (event) => {
|
|
87
|
-
if (event.target?.result) {
|
|
88
|
-
onAddImage({
|
|
89
|
-
src: event.target.result as string,
|
|
90
|
-
alt: file.name,
|
|
91
|
-
caption: "",
|
|
92
|
-
x: 40,
|
|
93
|
-
y: 400,
|
|
94
|
-
width: 440,
|
|
95
|
-
height: 300,
|
|
96
|
-
captionHeight: 36,
|
|
97
|
-
layoutPreset: "auto",
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
reader.readAsDataURL(file);
|
|
94
|
+
readLocalImage();
|
|
102
95
|
}
|
|
103
96
|
} catch {
|
|
104
|
-
|
|
105
|
-
const reader = new FileReader();
|
|
106
|
-
reader.onload = (event) => {
|
|
107
|
-
if (event.target?.result) {
|
|
108
|
-
onAddImage({
|
|
109
|
-
src: event.target.result as string,
|
|
110
|
-
alt: file.name,
|
|
111
|
-
caption: "",
|
|
112
|
-
x: 40,
|
|
113
|
-
y: 400,
|
|
114
|
-
width: 440,
|
|
115
|
-
height: 300,
|
|
116
|
-
captionHeight: 36,
|
|
117
|
-
layoutPreset: "auto",
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
reader.readAsDataURL(file);
|
|
97
|
+
readLocalImage();
|
|
122
98
|
} finally {
|
|
123
99
|
setIsUploading(false);
|
|
124
100
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
@@ -375,7 +351,7 @@ export const EditorSidebar: React.FC<EditorSidebarProps> = ({
|
|
|
375
351
|
<select
|
|
376
352
|
value={img.layoutPreset || "auto"}
|
|
377
353
|
onChange={(e) =>
|
|
378
|
-
onUpdateImage(img.id, { layoutPreset: e.target.value as
|
|
354
|
+
onUpdateImage(img.id, { layoutPreset: e.target.value as FlowImage["layoutPreset"] })
|
|
379
355
|
}
|
|
380
356
|
className="w-full bg-white dark:bg-[#181818] border border-[#d4d4d4] dark:border-[#262626] rounded px-1.5 py-1 text-black dark:text-white text-[11px] focus:outline-none"
|
|
381
357
|
>
|
|
@@ -52,7 +52,7 @@ export const Header: React.FC<HeaderProps> = ({
|
|
|
52
52
|
</span>
|
|
53
53
|
<span>Typeset Studio</span>
|
|
54
54
|
</div>
|
|
55
|
-
<span className="text-xs text-[#888888] dark:text-[#555555] font-mono hidden md:inline">v0.
|
|
55
|
+
<span className="text-xs text-[#888888] dark:text-[#555555] font-mono hidden md:inline">v0.2.2</span>
|
|
56
56
|
<div className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-[#f5f5f5] dark:bg-[#111111] border border-[#e5e5e5] dark:border-[#222222] text-[11px] text-emerald-600 dark:text-[#00e676]">
|
|
57
57
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-[#00e676] animate-pulse" />
|
|
58
58
|
<span className="font-mono">Ready</span>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { Check, FolderPlus, Trash2, X, Sparkles, Star
|
|
2
|
+
import { Check, FolderPlus, Trash2, X, Sparkles, Star } from "lucide-react";
|
|
3
3
|
import { TemplateSlot, TypesetConfig } from "../../core/types";
|
|
4
4
|
|
|
5
5
|
interface TemplateModalProps {
|
|
@@ -20,12 +20,6 @@ interface SlotsResponse {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const SLOT_LABELS: Record<TemplateSlot, string> = {
|
|
24
|
-
article_cover: "文章封面 (2.35:1)",
|
|
25
|
-
newspic_cover: "贴图封面 (3:4)",
|
|
26
|
-
newspic_content: "贴图内容 (3:4)",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
23
|
export const TemplateModal: React.FC<TemplateModalProps> = ({
|
|
30
24
|
isOpen,
|
|
31
25
|
onClose,
|
|
@@ -114,7 +108,6 @@ export const TemplateModal: React.FC<TemplateModalProps> = ({
|
|
|
114
108
|
id: undefined, // auto generate
|
|
115
109
|
name: templateName.trim(),
|
|
116
110
|
slot: saveSlot !== "none" ? saveSlot : undefined,
|
|
117
|
-
images: currentConfig.images ? [...currentConfig.images] : undefined,
|
|
118
111
|
};
|
|
119
112
|
|
|
120
113
|
const res = await fetch("/api/templates", {
|
|
@@ -192,7 +185,7 @@ export const TemplateModal: React.FC<TemplateModalProps> = ({
|
|
|
192
185
|
/>
|
|
193
186
|
<select
|
|
194
187
|
value={saveSlot}
|
|
195
|
-
onChange={(e) => setSaveSlot(e.target.value as
|
|
188
|
+
onChange={(e) => setSaveSlot(e.target.value as TemplateSlot | "none")}
|
|
196
189
|
className="bg-white dark:bg-[#0a0a0a] border border-[#d4d4d4] dark:border-[#2a2a2a] rounded px-2 py-1.5 text-xs text-black dark:text-white focus:outline-none focus:border-black dark:focus:border-[#444444]"
|
|
197
190
|
>
|
|
198
191
|
<option value="article_cover">槽位: 文章封面 (2.35:1)</option>
|
|
@@ -224,11 +217,7 @@ export const TemplateModal: React.FC<TemplateModalProps> = ({
|
|
|
224
217
|
<span>默认槽位配置账号:</span>
|
|
225
218
|
<select
|
|
226
219
|
value={account}
|
|
227
|
-
onChange={(e) =>
|
|
228
|
-
const acc = e.target.value;
|
|
229
|
-
setAccount(acc);
|
|
230
|
-
fetchSlots(acc);
|
|
231
|
-
}}
|
|
220
|
+
onChange={(e) => setAccount(e.target.value)}
|
|
232
221
|
className="bg-white dark:bg-[#141414] border border-[#d4d4d4] dark:border-[#2a2a2a] rounded px-2 py-0.5 text-xs text-black dark:text-white focus:outline-none font-mono"
|
|
233
222
|
>
|
|
234
223
|
<option value="default">default (主账号)</option>
|
|
@@ -27,9 +27,23 @@ const INITIAL_MARKDOWN = `# 大模型纯算法排版深度剖析
|
|
|
27
27
|
- 任意画板比例智能自适应
|
|
28
28
|
`;
|
|
29
29
|
|
|
30
|
+
// 统一预设配图的页内序号计算,手动坐标和自由图层保持原样。
|
|
31
|
+
function resolvePresetImage(
|
|
32
|
+
image: FlowImage,
|
|
33
|
+
images: FlowImage[],
|
|
34
|
+
stageWidth: number,
|
|
35
|
+
stageHeight: number,
|
|
36
|
+
): FlowImage {
|
|
37
|
+
if (image.isAbsolute || !image.layoutPreset || image.layoutPreset === "custom") return image;
|
|
38
|
+
const page = image.pageIndex || 1;
|
|
39
|
+
const indexOnPage = images.filter((item) => (item.pageIndex || 1) === page)
|
|
40
|
+
.findIndex((item) => item.id === image.id);
|
|
41
|
+
return resolveImagePlacement(image, indexOnPage, stageWidth, stageHeight);
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
export function useTypeset() {
|
|
31
|
-
const [aspectRatio, setAspectRatio] = useState<AspectRatioType>("longform-3-4");
|
|
32
45
|
const [config, setConfig] = useState<TypesetConfig>(() => getDefaultConfig("longform-3-4"));
|
|
46
|
+
const aspectRatio = config.aspectRatio;
|
|
33
47
|
const [markdown, setMarkdown] = useState<string>(INITIAL_MARKDOWN);
|
|
34
48
|
const [images, setImages] = useState<FlowImage[]>([
|
|
35
49
|
{
|
|
@@ -117,7 +131,6 @@ export function useTypeset() {
|
|
|
117
131
|
}, []);
|
|
118
132
|
|
|
119
133
|
const switchAspectRatio = (ratio: AspectRatioType) => {
|
|
120
|
-
setAspectRatio(ratio);
|
|
121
134
|
const spec = getCanvasSpec(ratio);
|
|
122
135
|
const nextDefault = getDefaultConfig(ratio);
|
|
123
136
|
const { stageWidth, stageHeight } = computeStageDimensions(
|
|
@@ -125,24 +138,7 @@ export function useTypeset() {
|
|
|
125
138
|
nextDefault.algorithm.safeArea,
|
|
126
139
|
nextDefault.algorithm.contentBottomGap,
|
|
127
140
|
);
|
|
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
|
-
);
|
|
141
|
+
setImages((prev) => prev.map((image) => resolvePresetImage(image, prev, stageWidth, stageHeight)));
|
|
146
142
|
setConfig((prev) => {
|
|
147
143
|
return {
|
|
148
144
|
...prev,
|
|
@@ -235,28 +231,15 @@ export function useTypeset() {
|
|
|
235
231
|
|
|
236
232
|
// If non-custom flow preset, recalculate clean coordinates based on its page index
|
|
237
233
|
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
234
|
const spec = getCanvasSpec(aspectRatio);
|
|
243
235
|
const { stageWidth, stageHeight } = computeStageDimensions(
|
|
244
236
|
spec,
|
|
245
237
|
config.algorithm.safeArea,
|
|
246
238
|
config.algorithm.contentBottomGap,
|
|
247
239
|
);
|
|
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
|
-
);
|
|
240
|
+
return nextImages.map((image) => image.id === id
|
|
241
|
+
? resolvePresetImage(image, nextImages, stageWidth, stageHeight)
|
|
242
|
+
: image);
|
|
260
243
|
}
|
|
261
244
|
|
|
262
245
|
return nextImages;
|
|
@@ -272,22 +255,7 @@ export function useTypeset() {
|
|
|
272
255
|
config.algorithm.safeArea,
|
|
273
256
|
config.algorithm.contentBottomGap,
|
|
274
257
|
);
|
|
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
|
-
});
|
|
258
|
+
return remaining.map((image) => resolvePresetImage(image, remaining, stageWidth, stageHeight));
|
|
291
259
|
});
|
|
292
260
|
};
|
|
293
261
|
|
|
@@ -309,28 +277,12 @@ export function useTypeset() {
|
|
|
309
277
|
config.algorithm.contentBottomGap,
|
|
310
278
|
);
|
|
311
279
|
|
|
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
|
-
});
|
|
280
|
+
return next.map((image) => resolvePresetImage(image, next, stageWidth, stageHeight));
|
|
328
281
|
});
|
|
329
282
|
};
|
|
330
283
|
|
|
331
284
|
const loadTemplate = (tpl: TypesetConfig) => {
|
|
332
285
|
setConfig(tpl);
|
|
333
|
-
setAspectRatio(tpl.aspectRatio);
|
|
334
286
|
if (tpl.images) {
|
|
335
287
|
setImages((prev) => applyTemplateImages(prev, tpl.images));
|
|
336
288
|
}
|