@cut-workbench/core 0.3.0 → 0.4.0
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/dist/chunks/{canvas-size-iWL8zZPl.js → canvas-size-CM2oKApi.js} +3 -144
- package/dist/chunks/{canvas-size-iWL8zZPl.js.map → canvas-size-CM2oKApi.js.map} +1 -1
- package/dist/chunks/{canvas-size-IzoEg2cq.cjs → canvas-size-Ct61OYjW.cjs} +26 -287
- package/dist/chunks/canvas-size-Ct61OYjW.cjs.map +1 -0
- package/dist/chunks/import-pipeline-A8KkqtZT.cjs +610 -0
- package/dist/chunks/import-pipeline-A8KkqtZT.cjs.map +1 -0
- package/dist/chunks/import-pipeline-DLIdgrIb.js +521 -0
- package/dist/chunks/import-pipeline-DLIdgrIb.js.map +1 -0
- package/dist/chunks/rolldown-runtime-B2SfzNBj.cjs +17 -0
- package/dist/chunks/rolldown-runtime-D8jQpDG7.js +12 -0
- package/dist/chunks/{snap-Dc022smD.js → snap-CBV9G4xq.js} +2 -2
- package/dist/chunks/{snap-Dc022smD.js.map → snap-CBV9G4xq.js.map} +1 -1
- package/dist/chunks/{snap-CeYuTN6d.cjs → snap-DyIuO2hU.cjs} +3 -3
- package/dist/chunks/{snap-CeYuTN6d.cjs.map → snap-DyIuO2hU.cjs.map} +1 -1
- package/dist/chunks/srt-D539yJ-n.js +51 -0
- package/dist/chunks/srt-D539yJ-n.js.map +1 -0
- package/dist/chunks/srt-DaL36mXN.cjs +73 -0
- package/dist/chunks/srt-DaL36mXN.cjs.map +1 -0
- package/dist/chunks/time-C_gvkVl1.js +145 -0
- package/dist/chunks/time-C_gvkVl1.js.map +1 -0
- package/dist/chunks/time-CnMfSVYX.cjs +264 -0
- package/dist/chunks/time-CnMfSVYX.cjs.map +1 -0
- package/dist/chunks/{waveform-CWU8Fa-n.js → waveform-DJkRtMct.js} +82 -12
- package/dist/chunks/waveform-DJkRtMct.js.map +1 -0
- package/dist/chunks/{waveform-Bhvv2t7-.cjs → waveform-Ie-G5Lle.cjs} +121 -21
- package/dist/chunks/waveform-Ie-G5Lle.cjs.map +1 -0
- package/dist/editor/create-editor.d.ts +8 -106
- package/dist/editor/editor-store.d.ts +4 -0
- package/dist/editor/handle-types.d.ts +256 -0
- package/dist/engine/index.cjs +961 -823
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +935 -803
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/webav/export-runner.d.ts +61 -0
- package/dist/engine/webav/import-files.d.ts +43 -0
- package/dist/engine/webav/import-pipeline.d.ts +13 -1
- package/dist/errors.d.ts +12 -0
- package/dist/export/types.d.ts +49 -0
- package/dist/index.cjs +75 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +46 -43
- package/dist/index.js.map +1 -1
- package/dist/ports/host-bridge.d.ts +7 -0
- package/dist/project/file-storage.d.ts +23 -0
- package/dist/timeline/index.cjs +17 -16
- package/dist/timeline/index.js +3 -2
- package/package.json +78 -77
- package/dist/chunks/canvas-size-IzoEg2cq.cjs.map +0 -1
- package/dist/chunks/waveform-Bhvv2t7-.cjs.map +0 -1
- package/dist/chunks/waveform-CWU8Fa-n.js.map +0 -1
|
@@ -1,145 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
// src/constants.ts
|
|
3
|
-
/**
|
|
4
|
-
* cut-core 自有常量。
|
|
5
|
-
*
|
|
6
|
-
* 只放「逻辑层必须知道」的数值:默认时长、默认音量、工程 schema 版本、
|
|
7
|
-
* 历史栈深度。UI 相关的量(缩放档位、面板高度、吸附像素阈值)留在宿主侧,
|
|
8
|
-
* 避免组件库被时间轴界面的调参牵着走。
|
|
9
|
-
*/
|
|
10
|
-
/** 图片 / 文字素材默认时长(微秒) */
|
|
11
|
-
var DEFAULT_STILL_DURATION_US = 5e6;
|
|
12
|
-
/** 默认音量,避免多轨直接相加削波 */
|
|
13
|
-
var DEFAULT_CLIP_VOLUME = .8;
|
|
14
|
-
/** 工程 schema 版本 */
|
|
15
|
-
var PROJECT_SCHEMA_VERSION = 1;
|
|
16
|
-
/** 撤销栈最大深度 */
|
|
17
|
-
var MAX_HISTORY_STEPS = 50;
|
|
18
|
-
/**
|
|
19
|
-
* 连续同质操作的合并时间窗口(毫秒)。
|
|
20
|
-
*
|
|
21
|
-
* 拖动滑块 / 画布内拖拽素材时会连续触发大量更新;窗口内相同 mergeKey 的记录
|
|
22
|
-
* 会被合并成一步撤销。取值兼顾「连续拖动算一次」与「停顿后再改算新一步」。
|
|
23
|
-
*/
|
|
24
|
-
var HISTORY_MERGE_WINDOW_MS = 800;
|
|
25
|
-
// src/lib/id.ts
|
|
26
|
-
/**
|
|
27
|
-
* 生成短 id。scope 仅用于可读性(例如 `clip_xxx`),不影响唯一性。
|
|
28
|
-
*/
|
|
29
|
-
function createId(scope) {
|
|
30
|
-
const id = nanoid(10);
|
|
31
|
-
return scope ? `${scope}_${id}` : id;
|
|
32
|
-
}
|
|
33
|
-
// src/timeline/time.ts
|
|
34
|
-
var US_PER_SECOND = 1e6;
|
|
35
|
-
var US_PER_MS = 1e3;
|
|
36
|
-
/** 秒 → 微秒 */
|
|
37
|
-
function secToUs(sec) {
|
|
38
|
-
return Math.round(sec * US_PER_SECOND);
|
|
39
|
-
}
|
|
40
|
-
/** 微秒 → 秒 */
|
|
41
|
-
function usToSec(us) {
|
|
42
|
-
return us / US_PER_SECOND;
|
|
43
|
-
}
|
|
44
|
-
/** 毫秒 → 微秒 */
|
|
45
|
-
function msToUs(ms) {
|
|
46
|
-
return Math.round(ms * US_PER_MS);
|
|
47
|
-
}
|
|
48
|
-
/** 微秒 → 毫秒 */
|
|
49
|
-
function usToMs(us) {
|
|
50
|
-
return Math.round(us / US_PER_MS);
|
|
51
|
-
}
|
|
52
|
-
/** 数字 → 微秒(用于从 DOM/WebAV 回调拿到的裸数字) */
|
|
53
|
-
function asUs(n) {
|
|
54
|
-
return Math.round(n);
|
|
55
|
-
}
|
|
56
|
-
/** 数字 → 像素 */
|
|
57
|
-
function asPx(n) {
|
|
58
|
-
return n;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* 微秒 → 像素。
|
|
62
|
-
* @param us 时间(微秒)
|
|
63
|
-
* @param pxPerSecond 每秒对应多少像素
|
|
64
|
-
*/
|
|
65
|
-
function usToPx(us, pxPerSecond) {
|
|
66
|
-
return us / US_PER_SECOND * pxPerSecond;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* 像素 → 微秒。
|
|
70
|
-
* @param px 像素
|
|
71
|
-
* @param pxPerSecond 每秒对应多少像素
|
|
72
|
-
*/
|
|
73
|
-
function pxToUs(px, pxPerSecond) {
|
|
74
|
-
return Math.round(px / pxPerSecond * US_PER_SECOND);
|
|
75
|
-
}
|
|
76
|
-
/** 微秒 → `HH:MM:SS.mmm` */
|
|
77
|
-
function formatTimecode(us, showMs = true) {
|
|
78
|
-
const totalMs = Math.floor(Math.max(0, us) / 1e3);
|
|
79
|
-
const ms = totalMs % 1e3;
|
|
80
|
-
const totalSec = Math.floor(totalMs / 1e3);
|
|
81
|
-
const sec = totalSec % 60;
|
|
82
|
-
const min = Math.floor(totalSec / 60) % 60;
|
|
83
|
-
const hour = Math.floor(totalSec / 3600);
|
|
84
|
-
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
85
|
-
const base = `${pad(hour)}:${pad(min)}:${pad(sec)}`;
|
|
86
|
-
return showMs ? `${base}.${pad(ms, 3)}` : base;
|
|
87
|
-
}
|
|
88
|
-
/** 微秒 → `MM:SS` 或 `HH:MM:SS`,列表展示用 */
|
|
89
|
-
function formatDuration(us) {
|
|
90
|
-
if (!Number.isFinite(us)) return "∞";
|
|
91
|
-
const totalSec = Math.max(0, Math.round(us / US_PER_SECOND));
|
|
92
|
-
const sec = totalSec % 60;
|
|
93
|
-
const min = Math.floor(totalSec / 60) % 60;
|
|
94
|
-
const hour = Math.floor(totalSec / 3600);
|
|
95
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
96
|
-
return hour > 0 ? `${hour}:${pad(min)}:${pad(sec)}` : `${pad(min)}:${pad(sec)}`;
|
|
97
|
-
}
|
|
98
|
-
var TICK_STEPS_US = [
|
|
99
|
-
1e5,
|
|
100
|
-
2e5,
|
|
101
|
-
5e5,
|
|
102
|
-
1e6,
|
|
103
|
-
2e6,
|
|
104
|
-
5e6,
|
|
105
|
-
1e7,
|
|
106
|
-
15e6,
|
|
107
|
-
3e7,
|
|
108
|
-
6e7,
|
|
109
|
-
12e7,
|
|
110
|
-
3e8,
|
|
111
|
-
6e8
|
|
112
|
-
];
|
|
113
|
-
/**
|
|
114
|
-
* 根据缩放级别生成刻度尺。
|
|
115
|
-
*
|
|
116
|
-
* @param pxPerSecond 每秒像素
|
|
117
|
-
* @param viewportWidthPx 可视宽度(像素)
|
|
118
|
-
* @param scrollLeftPx 横向滚动偏移(像素)
|
|
119
|
-
* @returns 主刻度(带文字)与次刻度
|
|
120
|
-
*/
|
|
121
|
-
function buildTicks(pxPerSecond, viewportWidthPx, scrollLeftPx = 0) {
|
|
122
|
-
const startUs = pxToUs(scrollLeftPx, pxPerSecond);
|
|
123
|
-
const endUs = pxToUs(scrollLeftPx + viewportWidthPx, pxPerSecond);
|
|
124
|
-
const minLabelSpacingPx = 72;
|
|
125
|
-
const step = TICK_STEPS_US.find((s) => usToPx(s, pxPerSecond) >= minLabelSpacingPx) ?? TICK_STEPS_US[TICK_STEPS_US.length - 1];
|
|
126
|
-
const minorStep = step / 5;
|
|
127
|
-
const first = Math.floor(startUs / step) * step;
|
|
128
|
-
const ticks = [];
|
|
129
|
-
for (let us = first; us <= endUs + step; us += minorStep) {
|
|
130
|
-
if (us < 0) continue;
|
|
131
|
-
ticks.push({
|
|
132
|
-
us,
|
|
133
|
-
px: usToPx(us, pxPerSecond),
|
|
134
|
-
major: us % step === 0
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
return ticks;
|
|
138
|
-
}
|
|
139
|
-
/** 保证值落在 [min, max] */
|
|
140
|
-
function clamp(value, min, max) {
|
|
141
|
-
return Math.min(max, Math.max(min, value));
|
|
142
|
-
}
|
|
1
|
+
import { _ as DEFAULT_STILL_DURATION_US, g as DEFAULT_CLIP_VOLUME, h as createId, i as asUs } from "./time-C_gvkVl1.js";
|
|
143
2
|
// src/timeline/model.ts
|
|
144
3
|
/**
|
|
145
4
|
* 时间轴纯数据模型与工厂函数。
|
|
@@ -634,6 +493,6 @@ function normalizeSize(width, height) {
|
|
|
634
493
|
height: even(height)
|
|
635
494
|
};
|
|
636
495
|
}
|
|
637
|
-
export {
|
|
496
|
+
export { createEmptyProject as A, rangesOverlap as C, trimClipData as D, splitClipData as E, getTrack as F, getTrackClips as I, getTrackEnd as L, getClipEnd as M, getProjectDuration as N, clampStillDuration as O, getSourceSpanUs as P, isTrackAudible as R, moveClipData as S, reorderTracksData as T, sameTransform as _, remapClipsForSize as a, duplicateClipsData as b, RESIZE_HANDLES as c, handleCursor as d, handleLocalOffset as f, resizeTransform as g, rectCenter as h, ratioLabel as i, createTrack as j, createClip as k, degToRad as l, pointInTransform as m, isCustomSize as n, remapTransform as o, normalizeAngle as p, normalizeSize as r, MIN_TRANSFORM_SIZE as s, ASPECT_PRESETS as t, fromLocalPoint as u, toLocalPoint as v, removeClipsData as w, findOverlaps as x, compactTrackData as y };
|
|
638
497
|
|
|
639
|
-
//# sourceMappingURL=canvas-size-
|
|
498
|
+
//# sourceMappingURL=canvas-size-CM2oKApi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-size-iWL8zZPl.js","names":[],"sources":["../../src/constants.ts","../../src/lib/id.ts","../../src/timeline/time.ts","../../src/timeline/model.ts","../../src/timeline/ops.ts","../../src/timeline/geometry.ts","../../src/timeline/canvas-size.ts"],"sourcesContent":["/**\n * cut-core 自有常量。\n *\n * 只放「逻辑层必须知道」的数值:默认时长、默认音量、工程 schema 版本、\n * 历史栈深度。UI 相关的量(缩放档位、面板高度、吸附像素阈值)留在宿主侧,\n * 避免组件库被时间轴界面的调参牵着走。\n */\n\n/** 图片 / 文字素材默认时长(微秒) */\nexport const DEFAULT_STILL_DURATION_US = 5_000_000\n\n/** 默认音量,避免多轨直接相加削波 */\nexport const DEFAULT_CLIP_VOLUME = 0.8\n\n/** 工程 schema 版本 */\nexport const PROJECT_SCHEMA_VERSION = 1\n\n/** 撤销栈最大深度 */\nexport const MAX_HISTORY_STEPS = 50\n\n/**\n * 连续同质操作的合并时间窗口(毫秒)。\n *\n * 拖动滑块 / 画布内拖拽素材时会连续触发大量更新;窗口内相同 mergeKey 的记录\n * 会被合并成一步撤销。取值兼顾「连续拖动算一次」与「停顿后再改算新一步」。\n */\nexport const HISTORY_MERGE_WINDOW_MS = 800\n","import { nanoid } from 'nanoid'\n\n/**\n * 生成短 id。scope 仅用于可读性(例如 `clip_xxx`),不影响唯一性。\n */\nexport function createId(scope?: string): string {\n const id = nanoid(10)\n return scope ? `${scope}_${id}` : id\n}\n","/**\n * 时间与像素的单位换算。\n *\n * 内部统一使用微秒(µs)。为避免中途混用毫秒/秒,这里定义 branded type:\n * - `Us` 微秒\n * - `Ms` 毫秒\n * - `Px` 像素\n *\n * 只在函数边界处做 cast,业务代码不允许出现裸 number 参与时间计算。\n */\n\ndeclare const usBrand: unique symbol\ndeclare const msBrand: unique symbol\ndeclare const pxBrand: unique symbol\n\nexport type Us = number & { readonly [usBrand]: true }\nexport type Ms = number & { readonly [msBrand]: true }\nexport type Px = number & { readonly [pxBrand]: true }\n\nexport const US_PER_SECOND = 1_000_000\nexport const US_PER_MS = 1_000\n\n/** 秒 → 微秒 */\nexport function secToUs(sec: number): Us {\n return Math.round(sec * US_PER_SECOND) as Us\n}\n\n/** 微秒 → 秒 */\nexport function usToSec(us: number): number {\n return us / US_PER_SECOND\n}\n\n/** 毫秒 → 微秒 */\nexport function msToUs(ms: number): Us {\n return Math.round(ms * US_PER_MS) as Us\n}\n\n/** 微秒 → 毫秒 */\nexport function usToMs(us: number): Ms {\n return Math.round(us / US_PER_MS) as Ms\n}\n\n/** 数字 → 微秒(用于从 DOM/WebAV 回调拿到的裸数字) */\nexport function asUs(n: number): Us {\n return Math.round(n) as Us\n}\n\n/** 数字 → 像素 */\nexport function asPx(n: number): Px {\n return n as Px\n}\n\n/**\n * 微秒 → 像素。\n * @param us 时间(微秒)\n * @param pxPerSecond 每秒对应多少像素\n */\nexport function usToPx(us: number, pxPerSecond: number): Px {\n return ((us / US_PER_SECOND) * pxPerSecond) as Px\n}\n\n/**\n * 像素 → 微秒。\n * @param px 像素\n * @param pxPerSecond 每秒对应多少像素\n */\nexport function pxToUs(px: number, pxPerSecond: number): Us {\n return Math.round((px / pxPerSecond) * US_PER_SECOND) as Us\n}\n\n/** 微秒 → `HH:MM:SS.mmm` */\nexport function formatTimecode(us: number, showMs = true): string {\n const safe = Math.max(0, us)\n const totalMs = Math.floor(safe / 1000)\n const ms = totalMs % 1000\n const totalSec = Math.floor(totalMs / 1000)\n const sec = totalSec % 60\n const min = Math.floor(totalSec / 60) % 60\n const hour = Math.floor(totalSec / 3600)\n const pad = (n: number, len = 2) => String(n).padStart(len, '0')\n const base = `${pad(hour)}:${pad(min)}:${pad(sec)}`\n return showMs ? `${base}.${pad(ms, 3)}` : base\n}\n\n/** 微秒 → `MM:SS` 或 `HH:MM:SS`,列表展示用 */\nexport function formatDuration(us: number): string {\n if (!Number.isFinite(us)) return '∞'\n const totalSec = Math.max(0, Math.round(us / US_PER_SECOND))\n const sec = totalSec % 60\n const min = Math.floor(totalSec / 60) % 60\n const hour = Math.floor(totalSec / 3600)\n const pad = (n: number) => String(n).padStart(2, '0')\n return hour > 0 ? `${hour}:${pad(min)}:${pad(sec)}` : `${pad(min)}:${pad(sec)}`\n}\n\nexport interface RulerTick {\n us: Us\n px: Px\n major: boolean\n}\n\nconst TICK_STEPS_US = [\n 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 15_000_000,\n 30_000_000, 60_000_000, 120_000_000, 300_000_000, 600_000_000,\n]\n\n/**\n * 根据缩放级别生成刻度尺。\n *\n * @param pxPerSecond 每秒像素\n * @param viewportWidthPx 可视宽度(像素)\n * @param scrollLeftPx 横向滚动偏移(像素)\n * @returns 主刻度(带文字)与次刻度\n */\nexport function buildTicks(\n pxPerSecond: number,\n viewportWidthPx: number,\n scrollLeftPx = 0,\n): RulerTick[] {\n const startUs = pxToUs(scrollLeftPx, pxPerSecond)\n const endUs = pxToUs(scrollLeftPx + viewportWidthPx, pxPerSecond)\n const minLabelSpacingPx = 72\n const step =\n TICK_STEPS_US.find((s) => usToPx(s, pxPerSecond) >= minLabelSpacingPx) ??\n TICK_STEPS_US[TICK_STEPS_US.length - 1]\n const minorStep = step / 5\n const first = Math.floor(startUs / step) * step\n const ticks: RulerTick[] = []\n for (let us = first; us <= endUs + step; us += minorStep) {\n if (us < 0) continue\n ticks.push({\n us: us as Us,\n px: usToPx(us, pxPerSecond),\n major: us % step === 0,\n })\n }\n return ticks\n}\n\n/** 保证值落在 [min, max] */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","/**\n * 时间轴纯数据模型与工厂函数。\n *\n * 这里没有任何 WebAV / React 依赖,所有函数都是纯函数或纯构造。\n * 时间单位统一微秒(µs)。\n */\n\nimport { createId } from '../lib/id'\nimport { DEFAULT_CLIP_VOLUME, DEFAULT_STILL_DURATION_US } from '../constants'\nimport type { Project, TimelineClip, Track } from '../types'\nimport { asUs } from './time'\n\nexport function createTrack(kind: Track['kind'], name: string): Track {\n return {\n id: createId('track'),\n name,\n kind,\n locked: false,\n muted: false,\n hidden: false,\n }\n}\n\nexport function createEmptyProject(name = '未命名工程'): Project {\n return {\n id: createId('project'),\n name,\n width: 1280,\n height: 720,\n tracks: [createTrack('visual', '视频 1'), createTrack('audio', '音频 1')],\n clips: [],\n updatedAt: Date.now(),\n }\n}\n\nexport interface CreateClipInput {\n assetId: string\n trackId: string\n startUs: number\n durationUs: number\n inPointUs?: number\n assetWidth?: number\n assetHeight?: number\n}\n\n/** 新建 clip 的默认几何:等比铺满工程画面 */\nexport function createClip(input: CreateClipInput, project: Project): TimelineClip {\n const { assetWidth = 0, assetHeight = 0 } = input\n let w = project.width\n let h = project.height\n if (assetWidth > 0 && assetHeight > 0) {\n const scale = Math.min(project.width / assetWidth, project.height / assetHeight)\n w = Math.round(assetWidth * scale)\n h = Math.round(assetHeight * scale)\n }\n return {\n id: createId('clip'),\n trackId: input.trackId,\n assetId: input.assetId,\n startUs: asUs(input.startUs),\n durationUs: asUs(input.durationUs),\n inPointUs: asUs(input.inPointUs ?? 0),\n playbackRate: 1,\n volume: DEFAULT_CLIP_VOLUME,\n muted: false,\n opacity: 1,\n flip: null,\n transform: {\n x: Math.round((project.width - w) / 2),\n y: Math.round((project.height - h) / 2),\n w,\n h,\n angle: 0,\n },\n fadeInUs: asUs(0),\n fadeOutUs: asUs(0),\n }\n}\n\n/** 静态素材(图片 / 文字 / 字幕)需要有限时长 */\nexport function clampStillDuration(us: number): number {\n return us > 0 && Number.isFinite(us) ? us : DEFAULT_STILL_DURATION_US\n}\n\nexport function getClipEnd(clip: TimelineClip): number {\n return clip.startUs + clip.durationUs\n}\n\n/** 工程总时长(取所有 clip 结束时间的最大值) */\nexport function getProjectDuration(project: Project): number {\n return project.clips.reduce((max, c) => Math.max(max, getClipEnd(c)), 0)\n}\n\nexport function getTrack(project: Project, trackId: string): Track | undefined {\n return project.tracks.find((t) => t.id === trackId)\n}\n\nexport function isTrackAudible(project: Project, trackId: string): boolean {\n const track = getTrack(project, trackId)\n if (!track) return false\n return !track.muted && !track.hidden\n}\n\n/** 某轨道上按时间排序的 clip */\nexport function getTrackClips(project: Project, trackId: string): TimelineClip[] {\n return project.clips\n .filter((c) => c.trackId === trackId)\n .sort((a, b) => a.startUs - b.startUs)\n}\n\n/** 轨道内最大结束时间,用于「轨道末尾」吸附点 */\nexport function getTrackEnd(project: Project, trackId: string): number {\n return getTrackClips(project, trackId).reduce((max, c) => Math.max(max, getClipEnd(c)), 0)\n}\n\n/** 源素材被消费的时长 = 时间轴时长 × 速率 */\nexport function getSourceSpanUs(clip: TimelineClip): number {\n return Math.round(clip.durationUs * clip.playbackRate)\n}\n","/**\n * 时间轴编辑操作(纯函数)。\n *\n * 全部返回新的数组/对象,不修改入参;异步部分(如真正切割 IClip 实例)由\n * `core/webav/clip-registry.ts` 负责,模型层只处理时间与轨道数据。\n */\n\nimport type { TimelineClip, Track } from '../types'\nimport { asUs } from './time'\nimport { getClipEnd } from './model'\n\n/** 在时间轴时刻 `atUs` 分割 clip,返回 [左, 右];时刻不在 clip 内部时返回 null */\nexport function splitClipData(clip: TimelineClip, atUs: number): [TimelineClip, TimelineClip] | null {\n const start = clip.startUs\n const end = getClipEnd(clip)\n if (atUs <= start || atUs >= end) return null\n const leftDuration = atUs - start\n const rightDuration = end - atUs\n const sourceOffset = Math.round(leftDuration * clip.playbackRate)\n\n const left: TimelineClip = {\n ...clip,\n durationUs: asUs(leftDuration),\n fadeOutUs: asUs(Math.min(clip.fadeOutUs, leftDuration)),\n }\n const right: TimelineClip = {\n ...clip,\n id: `${clip.id}_r`,\n startUs: asUs(atUs),\n durationUs: asUs(rightDuration),\n inPointUs: asUs(clip.inPointUs + sourceOffset),\n fadeInUs: asUs(Math.min(clip.fadeInUs, rightDuration)),\n }\n left.fadeInUs = asUs(Math.min(clip.fadeInUs, leftDuration))\n return [left, right]\n}\n\n/** 移动 clip:更新轨道与起点(不允许早于 0) */\nexport function moveClipData(clip: TimelineClip, trackId: string, startUs: number): TimelineClip {\n return { ...clip, trackId, startUs: asUs(Math.max(0, startUs)) }\n}\n\nexport type TrimEdge = 'start' | 'end'\n\n/**\n * 裁剪 clip 的边缘。\n *\n * - start:改变起点与源入点,保持右边缘不动\n * - end:只改变时长\n *\n * `minDurationUs` 保证不会裁成 0 长度。\n */\nexport function trimClipData(\n clip: TimelineClip,\n edge: TrimEdge,\n deltaUs: number,\n minDurationUs = 100_000,\n): TimelineClip {\n const end = getClipEnd(clip)\n\n if (edge === 'start') {\n const nextStart = Math.min(\n Math.max(0, clip.startUs + deltaUs),\n end - minDurationUs,\n )\n const applied = nextStart - clip.startUs\n const sourceDelta = Math.round(applied * clip.playbackRate)\n const nextInPoint = Math.max(0, clip.inPointUs + sourceDelta)\n return {\n ...clip,\n startUs: asUs(nextStart),\n inPointUs: asUs(nextInPoint),\n durationUs: asUs(end - nextStart),\n }\n }\n\n const nextDuration = Math.max(minDurationUs, clip.durationUs + deltaUs)\n return { ...clip, durationUs: asUs(nextDuration) }\n}\n\n/** 删除指定 id 的 clip */\nexport function removeClipsData(clips: TimelineClip[], ids: string[]): TimelineClip[] {\n const set = new Set(ids)\n return clips.filter((c) => !set.has(c.id))\n}\n\n/** 复制 clip,起点顺延 `offsetUs`,返回 { originalId, copy } 便于重建 IClip 实例 */\nexport function duplicateClipsData(\n clips: TimelineClip[],\n ids: string[],\n offsetUs: number,\n makeId: () => string,\n): Array<{ sourceId: string; clip: TimelineClip }> {\n const set = new Set(ids)\n return clips\n .filter((c) => set.has(c.id))\n .map((c) => ({\n sourceId: c.id,\n clip: {\n ...c,\n id: makeId(),\n startUs: asUs(c.startUs + offsetUs),\n transform: { ...c.transform },\n },\n }))\n}\n\n/** 自动排布:把同一轨道上的 clip 按顺序首尾相接(用于「磁吸排序」) */\nexport function compactTrackData(clips: TimelineClip[], trackId: string): TimelineClip[] {\n const onTrack = clips\n .filter((c) => c.trackId === trackId)\n .sort((a, b) => a.startUs - b.startUs)\n let cursor = 0\n const updated = new Map<string, number>()\n for (const c of onTrack) {\n updated.set(c.id, cursor)\n cursor += c.durationUs\n }\n return clips.map((c) => {\n const next = updated.get(c.id)\n return next == null ? c : { ...c, startUs: asUs(next) }\n })\n}\n\n/** 重新排序轨道(拖拽调整垂直顺序) */\nexport function reorderTracksData(tracks: Track[], trackId: string, toIndex: number): Track[] {\n const from = tracks.findIndex((t) => t.id === trackId)\n if (from < 0) return tracks\n const next = [...tracks]\n const [moved] = next.splice(from, 1)\n if (!moved) return tracks\n next.splice(Math.max(0, Math.min(next.length, toIndex)), 0, moved)\n return next\n}\n\n/** 判断两个区间是否重叠(半开区间) */\nexport function rangesOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number): boolean {\n return aStart < bEnd && bStart < aEnd\n}\n\n/** 找出与给定区间在指定轨道上重叠的 clip(排除自身) */\nexport function findOverlaps(\n clips: TimelineClip[],\n trackId: string,\n startUs: number,\n endUs: number,\n excludeId?: string,\n): TimelineClip[] {\n return clips.filter(\n (c) =>\n c.trackId === trackId &&\n c.id !== excludeId &&\n rangesOverlap(startUs, endUs, c.startUs, getClipEnd(c)),\n )\n}\n","/**\n * 画布几何计算(纯函数)。\n *\n * 坐标系:工程分辨率像素(`project.width × project.height`),\n * 与 `clip.transform` 完全一致,也与导出坐标映射的输入一致。\n *\n * 为什么单独放 core:画布的命中测试、自绘选框的手柄布局、缩放反算\n * 这三处必须用同一套数学,否则「点中的框」和「画出的框」会错位。\n */\n\nimport type { ClipTransform } from '../types'\n\n/** 手柄方位。边用 4 个字母,角用两两组合,另加旋转 */\nexport type HandleKey = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'\n\nexport const RESIZE_HANDLES: HandleKey[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']\n\n/** 最小尺寸(工程像素),与 WebAV 的内部控制点下限保持一致 */\nexport const MIN_TRANSFORM_SIZE = 10\n\nexport interface Point {\n x: number\n y: number\n}\n\nexport interface Rect {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport function rectCenter(rect: Rect): Point {\n return { x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 }\n}\n\n/** 角度(度)→ 弧度 */\nexport function degToRad(deg: number): number {\n return (deg * Math.PI) / 180\n}\n\n/**\n * 把世界坐标点转换到矩形自身的局部坐标(以中心为原点、已抵消旋转)。\n *\n * 旋转方向与 Canvas `rotate()` 一致(顺时针为正),因此用 `-angle` 做逆变换。\n */\nexport function toLocalPoint(point: Point, transform: Pick<ClipTransform, 'x' | 'y' | 'w' | 'h' | 'angle'>): Point {\n const cx = transform.x + transform.w / 2\n const cy = transform.y + transform.h / 2\n const rad = -degToRad(transform.angle)\n const dx = point.x - cx\n const dy = point.y - cy\n return {\n x: dx * Math.cos(rad) - dy * Math.sin(rad),\n y: dx * Math.sin(rad) + dy * Math.cos(rad),\n }\n}\n\n/** 局部坐标(相对中心)→ 世界坐标 */\nexport function fromLocalPoint(local: Point, transform: Pick<ClipTransform, 'x' | 'y' | 'w' | 'h' | 'angle'>): Point {\n const cx = transform.x + transform.w / 2\n const cy = transform.y + transform.h / 2\n const rad = degToRad(transform.angle)\n return {\n x: cx + local.x * Math.cos(rad) - local.y * Math.sin(rad),\n y: cy + local.x * Math.sin(rad) + local.y * Math.cos(rad),\n }\n}\n\n/** 点是否落在(可能旋转的)矩形内 */\nexport function pointInTransform(point: Point, transform: ClipTransform): boolean {\n if (transform.w <= 0 || transform.h <= 0) return false\n const local = toLocalPoint(point, transform)\n return Math.abs(local.x) <= transform.w / 2 && Math.abs(local.y) <= transform.h / 2\n}\n\n/**\n * 按方位返回手柄在**局部坐标**中的位置(相对中心)。\n *\n * 边手柄固定一个方向为 0,角手柄两个方向都取半宽/半高。\n */\nexport function handleLocalOffset(handle: HandleKey, w: number, h: number): Point {\n const hw = w / 2\n const hh = h / 2\n const x = handle.includes('w') ? -hw : handle.includes('e') ? hw : 0\n const y = handle.includes('n') ? -hh : handle.includes('s') ? hh : 0\n return { x, y }\n}\n\n/**\n * 计算缩放后的 transform。\n *\n * 采用「固定对角/对边」的直觉行为:拖右边缘时左边缘不动,拖左上角时右下角不动。\n * 指针先被投影到局部坐标轴,因此素材旋转后手柄仍沿自身方向缩放(而不是屏幕方向)。\n *\n * @param start 拖动开始时的 transform 快照\n * @param handle 被拖动的方位\n * @param pointer 当前指针(工程坐标)\n * @param keepAspect\n * - `false`(默认):自由缩放\n * - `true`:所有手柄都等比(Shift 临时约束)\n * - `'corners'`:只有角手柄等比,边手柄仍单维自由\n * (片段自身「锁定宽高比」用这个 —— 若边手柄也等比,\n * 就会出现「拖右边、上下也跟着变」的诡异手感)\n */\nexport function resizeTransform(\n start: ClipTransform,\n handle: HandleKey,\n pointer: Point,\n keepAspect: boolean | 'corners' = false,\n): ClipTransform {\n const local = toLocalPoint(pointer, start)\n const hw = start.w / 2\n const hh = start.h / 2\n\n // 局部坐标下的四条边;拖动某条边只改它,其余固定不动(= 固定对角/对边)\n let left = -hw\n let right = hw\n let top = -hh\n let bottom = hh\n\n if (handle.includes('w')) left = Math.min(local.x, right - MIN_TRANSFORM_SIZE)\n if (handle.includes('e')) right = Math.max(local.x, left + MIN_TRANSFORM_SIZE)\n if (handle.includes('n')) top = Math.min(local.y, bottom - MIN_TRANSFORM_SIZE)\n if (handle.includes('s')) bottom = Math.max(local.y, top + MIN_TRANSFORM_SIZE)\n\n let nextW = right - left\n let nextH = bottom - top\n\n const isCorner = handle.length === 2\n const aspectActive = keepAspect === true || (keepAspect === 'corners' && isCorner)\n\n if (aspectActive && start.w > 0 && start.h > 0) {\n const ratio = start.w / start.h\n if (isCorner) {\n // 以宽为准反算高,再按被拖动的方向贴住不动的对边\n nextH = nextW / ratio\n if (handle.includes('n')) top = bottom - nextH\n else bottom = top + nextH\n } else if (handle === 'e' || handle === 'w') {\n nextH = nextW / ratio\n top = -nextH / 2\n bottom = nextH / 2\n } else {\n nextW = nextH * ratio\n left = -nextW / 2\n right = nextW / 2\n }\n }\n\n nextW = Math.max(MIN_TRANSFORM_SIZE, right - left)\n nextH = Math.max(MIN_TRANSFORM_SIZE, bottom - top)\n\n // 新中心(局部坐标)→ 世界坐标 → 左上角\n const localCenter = { x: (left + right) / 2, y: (top + bottom) / 2 }\n const worldCenter = fromLocalPoint(localCenter, start)\n return {\n x: worldCenter.x - nextW / 2,\n y: worldCenter.y - nextH / 2,\n w: nextW,\n h: nextH,\n angle: start.angle,\n }\n}\n\n/** 把角度归一化到 (-180, 180],避免旋转多圈后数值无限增大 */\nexport function normalizeAngle(deg: number): number {\n let a = deg % 360\n if (a > 180) a -= 360\n if (a <= -180) a += 360\n return a\n}\n\n/** 按手柄方位给出鼠标光标(已考虑素材自身旋转) */\nexport function handleCursor(handle: HandleKey | 'rotate', angleDeg: number): string {\n if (handle === 'rotate') return 'grab'\n const base: Record<HandleKey, number> = {\n n: 0,\n ne: 45,\n e: 90,\n se: 135,\n s: 180,\n sw: 225,\n w: 270,\n nw: 315,\n }\n const cursors = ['ns-resize', 'nesw-resize', 'ew-resize', 'nwse-resize']\n // 把素材旋转叠加到手柄方位上,再落到 4 档光标\n const deg = ((base[handle] + angleDeg) % 180 + 180) % 180\n const index = Math.round(deg / 45) % 4\n return cursors[index] ?? 'default'\n}\n\n/** 两个尺寸是否近似相等(避免浮点噪声导致无谓的 store 写入) */\nexport function sameTransform(a: ClipTransform, b: ClipTransform, epsilon = 0.5): boolean {\n return (\n Math.abs(a.x - b.x) < epsilon &&\n Math.abs(a.y - b.y) < epsilon &&\n Math.abs(a.w - b.w) < epsilon &&\n Math.abs(a.h - b.h) < epsilon &&\n Math.abs(a.angle - b.angle) < epsilon\n )\n}\n","/**\n * 画布(工程)尺寸与比例。\n *\n * 为什么需要单独一层:时间轴里所有 `clip.transform` 都以工程分辨率为坐标系,\n * 换比例时如果不做变换,1280×720 里铺满的素材放到 1080×1920 画布里就会\n * 只剩顶部一条 —— 这正是「换了比例看起来没生效 / 画面全是黑的」的成因。\n *\n * 因此尺寸变更分两种语义:\n * - `scale`(保持构图):把所有素材按同样的比例缩放并平移,视觉上「整幅画面\n * 被重新装进新画布」,适合横屏切竖屏、改分辨率。\n * - `keep`(保持素材原样):只换画布,素材坐标原封不动,适合「画布留白变多」。\n *\n * 纯函数,不依赖 React。\n */\n\nimport type { ClipTransform, Project, TimelineClip } from '../types'\n\nexport interface AspectPreset {\n id: string\n label: string\n hint: string\n width: number\n height: number\n}\n\n/**\n * 常用比例预设。\n *\n * 尺寸都取该比例下「编辑器默认档」的常见值:横屏 720p、竖屏 720×1280、\n * 方形 1080×1080、竖版 1080×1920、横版 1920×1080。用户也可以自定义。\n */\nexport const ASPECT_PRESETS: AspectPreset[] = [\n { id: '16:9', label: '16:9', hint: '横屏 1280×720', width: 1280, height: 720 },\n { id: '9:16', label: '9:16', hint: '竖屏 720×1280', width: 720, height: 1280 },\n { id: '1:1', label: '1:1', hint: '方形 1080×1080', width: 1080, height: 1080 },\n { id: '4:3', label: '4:3', hint: '标准 960×720', width: 960, height: 720 },\n { id: '3:4', label: '3:4', hint: '竖版 720×960', width: 720, height: 960 },\n { id: '4:5', label: '4:5', hint: '社交媒体 864×1080', width: 864, height: 1080 },\n { id: '21:9', label: '21:9', hint: '宽银幕 1680×720', width: 1680, height: 720 },\n]\n\nexport type SizeChangeMode = 'scale' | 'keep'\n\n/** 求最大公约数(用于把尺寸化简成比例) */\nfunction gcd(a: number, b: number): number {\n let x = Math.abs(Math.round(a))\n let y = Math.abs(Math.round(b))\n while (y > 0) {\n const t = x % y\n x = y\n y = t\n }\n return x || 1\n}\n\n/** `1920×1080` → `16:9`;无法化简时返回 `1920:1080` 形式 */\nexport function ratioLabel(width: number, height: number): string {\n if (width <= 0 || height <= 0) return '—'\n const divisor = gcd(width, height)\n return `${width / divisor}:${height / divisor}`\n}\n\n/** 保留两位小数,避免属性面板里出现 123.456789 */\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100\n}\n\n/**\n * 把素材几何从 `from` 画布映射到 `to` 画布。\n *\n * 用**同一个**缩放系数(取宽高比中较小者),保证素材不变形:\n * 16:9 → 9:16 时画面按高度缩小、水平居中,两侧留黑 —— 与剪辑软件里\n * 「按比例适配」的行为一致。若分别按宽、高缩放,人物会被横向拉扁。\n */\nexport function remapTransform(\n transform: ClipTransform,\n from: { width: number; height: number },\n to: { width: number; height: number },\n): ClipTransform {\n const fromW = Math.max(1, from.width)\n const fromH = Math.max(1, from.height)\n const scale = Math.min(to.width / fromW, to.height / fromH)\n const centerX = (transform.x + transform.w / 2) * scale + (to.width - fromW * scale) / 2\n const centerY = (transform.y + transform.h / 2) * scale + (to.height - fromH * scale) / 2\n const w = transform.w * scale\n const h = transform.h * scale\n return {\n x: round2(centerX - w / 2),\n y: round2(centerY - h / 2),\n w: round2(w),\n h: round2(h),\n angle: transform.angle,\n }\n}\n\n/**\n * 根据模式生成新的 clips 数组。\n *\n * @param mode `scale` = 素材随画布等比缩放;`keep` = 素材坐标不动\n */\nexport function remapClipsForSize(\n clips: TimelineClip[],\n from: { width: number; height: number },\n to: { width: number; height: number },\n mode: SizeChangeMode,\n): TimelineClip[] {\n if (mode === 'keep') return clips\n return clips.map((clip) => ({\n ...clip,\n transform: remapTransform(clip.transform, from, to),\n }))\n}\n\n/** 判断工程是否已是非标尺寸(不在预设里) */\nexport function isCustomSize(project: Project): boolean {\n return !ASPECT_PRESETS.some((p) => p.width === project.width && p.height === project.height)\n}\n\n/** 尺寸合法化:不小于 16,偶数(编码器要求) */\nexport function normalizeSize(width: number, height: number): { width: number; height: number } {\n const even = (n: number) => Math.max(16, Math.round(n / 2) * 2)\n return { width: even(width), height: even(height) }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"canvas-size-CM2oKApi.js","names":[],"sources":["../../src/timeline/model.ts","../../src/timeline/ops.ts","../../src/timeline/geometry.ts","../../src/timeline/canvas-size.ts"],"sourcesContent":["/**\n * 时间轴纯数据模型与工厂函数。\n *\n * 这里没有任何 WebAV / React 依赖,所有函数都是纯函数或纯构造。\n * 时间单位统一微秒(µs)。\n */\n\nimport { createId } from '../lib/id'\nimport { DEFAULT_CLIP_VOLUME, DEFAULT_STILL_DURATION_US } from '../constants'\nimport type { Project, TimelineClip, Track } from '../types'\nimport { asUs } from './time'\n\nexport function createTrack(kind: Track['kind'], name: string): Track {\n return {\n id: createId('track'),\n name,\n kind,\n locked: false,\n muted: false,\n hidden: false,\n }\n}\n\nexport function createEmptyProject(name = '未命名工程'): Project {\n return {\n id: createId('project'),\n name,\n width: 1280,\n height: 720,\n tracks: [createTrack('visual', '视频 1'), createTrack('audio', '音频 1')],\n clips: [],\n updatedAt: Date.now(),\n }\n}\n\nexport interface CreateClipInput {\n assetId: string\n trackId: string\n startUs: number\n durationUs: number\n inPointUs?: number\n assetWidth?: number\n assetHeight?: number\n}\n\n/** 新建 clip 的默认几何:等比铺满工程画面 */\nexport function createClip(input: CreateClipInput, project: Project): TimelineClip {\n const { assetWidth = 0, assetHeight = 0 } = input\n let w = project.width\n let h = project.height\n if (assetWidth > 0 && assetHeight > 0) {\n const scale = Math.min(project.width / assetWidth, project.height / assetHeight)\n w = Math.round(assetWidth * scale)\n h = Math.round(assetHeight * scale)\n }\n return {\n id: createId('clip'),\n trackId: input.trackId,\n assetId: input.assetId,\n startUs: asUs(input.startUs),\n durationUs: asUs(input.durationUs),\n inPointUs: asUs(input.inPointUs ?? 0),\n playbackRate: 1,\n volume: DEFAULT_CLIP_VOLUME,\n muted: false,\n opacity: 1,\n flip: null,\n transform: {\n x: Math.round((project.width - w) / 2),\n y: Math.round((project.height - h) / 2),\n w,\n h,\n angle: 0,\n },\n fadeInUs: asUs(0),\n fadeOutUs: asUs(0),\n }\n}\n\n/** 静态素材(图片 / 文字 / 字幕)需要有限时长 */\nexport function clampStillDuration(us: number): number {\n return us > 0 && Number.isFinite(us) ? us : DEFAULT_STILL_DURATION_US\n}\n\nexport function getClipEnd(clip: TimelineClip): number {\n return clip.startUs + clip.durationUs\n}\n\n/** 工程总时长(取所有 clip 结束时间的最大值) */\nexport function getProjectDuration(project: Project): number {\n return project.clips.reduce((max, c) => Math.max(max, getClipEnd(c)), 0)\n}\n\nexport function getTrack(project: Project, trackId: string): Track | undefined {\n return project.tracks.find((t) => t.id === trackId)\n}\n\nexport function isTrackAudible(project: Project, trackId: string): boolean {\n const track = getTrack(project, trackId)\n if (!track) return false\n return !track.muted && !track.hidden\n}\n\n/** 某轨道上按时间排序的 clip */\nexport function getTrackClips(project: Project, trackId: string): TimelineClip[] {\n return project.clips\n .filter((c) => c.trackId === trackId)\n .sort((a, b) => a.startUs - b.startUs)\n}\n\n/** 轨道内最大结束时间,用于「轨道末尾」吸附点 */\nexport function getTrackEnd(project: Project, trackId: string): number {\n return getTrackClips(project, trackId).reduce((max, c) => Math.max(max, getClipEnd(c)), 0)\n}\n\n/** 源素材被消费的时长 = 时间轴时长 × 速率 */\nexport function getSourceSpanUs(clip: TimelineClip): number {\n return Math.round(clip.durationUs * clip.playbackRate)\n}\n","/**\n * 时间轴编辑操作(纯函数)。\n *\n * 全部返回新的数组/对象,不修改入参;异步部分(如真正切割 IClip 实例)由\n * `core/webav/clip-registry.ts` 负责,模型层只处理时间与轨道数据。\n */\n\nimport type { TimelineClip, Track } from '../types'\nimport { asUs } from './time'\nimport { getClipEnd } from './model'\n\n/** 在时间轴时刻 `atUs` 分割 clip,返回 [左, 右];时刻不在 clip 内部时返回 null */\nexport function splitClipData(clip: TimelineClip, atUs: number): [TimelineClip, TimelineClip] | null {\n const start = clip.startUs\n const end = getClipEnd(clip)\n if (atUs <= start || atUs >= end) return null\n const leftDuration = atUs - start\n const rightDuration = end - atUs\n const sourceOffset = Math.round(leftDuration * clip.playbackRate)\n\n const left: TimelineClip = {\n ...clip,\n durationUs: asUs(leftDuration),\n fadeOutUs: asUs(Math.min(clip.fadeOutUs, leftDuration)),\n }\n const right: TimelineClip = {\n ...clip,\n id: `${clip.id}_r`,\n startUs: asUs(atUs),\n durationUs: asUs(rightDuration),\n inPointUs: asUs(clip.inPointUs + sourceOffset),\n fadeInUs: asUs(Math.min(clip.fadeInUs, rightDuration)),\n }\n left.fadeInUs = asUs(Math.min(clip.fadeInUs, leftDuration))\n return [left, right]\n}\n\n/** 移动 clip:更新轨道与起点(不允许早于 0) */\nexport function moveClipData(clip: TimelineClip, trackId: string, startUs: number): TimelineClip {\n return { ...clip, trackId, startUs: asUs(Math.max(0, startUs)) }\n}\n\nexport type TrimEdge = 'start' | 'end'\n\n/**\n * 裁剪 clip 的边缘。\n *\n * - start:改变起点与源入点,保持右边缘不动\n * - end:只改变时长\n *\n * `minDurationUs` 保证不会裁成 0 长度。\n */\nexport function trimClipData(\n clip: TimelineClip,\n edge: TrimEdge,\n deltaUs: number,\n minDurationUs = 100_000,\n): TimelineClip {\n const end = getClipEnd(clip)\n\n if (edge === 'start') {\n const nextStart = Math.min(\n Math.max(0, clip.startUs + deltaUs),\n end - minDurationUs,\n )\n const applied = nextStart - clip.startUs\n const sourceDelta = Math.round(applied * clip.playbackRate)\n const nextInPoint = Math.max(0, clip.inPointUs + sourceDelta)\n return {\n ...clip,\n startUs: asUs(nextStart),\n inPointUs: asUs(nextInPoint),\n durationUs: asUs(end - nextStart),\n }\n }\n\n const nextDuration = Math.max(minDurationUs, clip.durationUs + deltaUs)\n return { ...clip, durationUs: asUs(nextDuration) }\n}\n\n/** 删除指定 id 的 clip */\nexport function removeClipsData(clips: TimelineClip[], ids: string[]): TimelineClip[] {\n const set = new Set(ids)\n return clips.filter((c) => !set.has(c.id))\n}\n\n/** 复制 clip,起点顺延 `offsetUs`,返回 { originalId, copy } 便于重建 IClip 实例 */\nexport function duplicateClipsData(\n clips: TimelineClip[],\n ids: string[],\n offsetUs: number,\n makeId: () => string,\n): Array<{ sourceId: string; clip: TimelineClip }> {\n const set = new Set(ids)\n return clips\n .filter((c) => set.has(c.id))\n .map((c) => ({\n sourceId: c.id,\n clip: {\n ...c,\n id: makeId(),\n startUs: asUs(c.startUs + offsetUs),\n transform: { ...c.transform },\n },\n }))\n}\n\n/** 自动排布:把同一轨道上的 clip 按顺序首尾相接(用于「磁吸排序」) */\nexport function compactTrackData(clips: TimelineClip[], trackId: string): TimelineClip[] {\n const onTrack = clips\n .filter((c) => c.trackId === trackId)\n .sort((a, b) => a.startUs - b.startUs)\n let cursor = 0\n const updated = new Map<string, number>()\n for (const c of onTrack) {\n updated.set(c.id, cursor)\n cursor += c.durationUs\n }\n return clips.map((c) => {\n const next = updated.get(c.id)\n return next == null ? c : { ...c, startUs: asUs(next) }\n })\n}\n\n/** 重新排序轨道(拖拽调整垂直顺序) */\nexport function reorderTracksData(tracks: Track[], trackId: string, toIndex: number): Track[] {\n const from = tracks.findIndex((t) => t.id === trackId)\n if (from < 0) return tracks\n const next = [...tracks]\n const [moved] = next.splice(from, 1)\n if (!moved) return tracks\n next.splice(Math.max(0, Math.min(next.length, toIndex)), 0, moved)\n return next\n}\n\n/** 判断两个区间是否重叠(半开区间) */\nexport function rangesOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number): boolean {\n return aStart < bEnd && bStart < aEnd\n}\n\n/** 找出与给定区间在指定轨道上重叠的 clip(排除自身) */\nexport function findOverlaps(\n clips: TimelineClip[],\n trackId: string,\n startUs: number,\n endUs: number,\n excludeId?: string,\n): TimelineClip[] {\n return clips.filter(\n (c) =>\n c.trackId === trackId &&\n c.id !== excludeId &&\n rangesOverlap(startUs, endUs, c.startUs, getClipEnd(c)),\n )\n}\n","/**\n * 画布几何计算(纯函数)。\n *\n * 坐标系:工程分辨率像素(`project.width × project.height`),\n * 与 `clip.transform` 完全一致,也与导出坐标映射的输入一致。\n *\n * 为什么单独放 core:画布的命中测试、自绘选框的手柄布局、缩放反算\n * 这三处必须用同一套数学,否则「点中的框」和「画出的框」会错位。\n */\n\nimport type { ClipTransform } from '../types'\n\n/** 手柄方位。边用 4 个字母,角用两两组合,另加旋转 */\nexport type HandleKey = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'\n\nexport const RESIZE_HANDLES: HandleKey[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']\n\n/** 最小尺寸(工程像素),与 WebAV 的内部控制点下限保持一致 */\nexport const MIN_TRANSFORM_SIZE = 10\n\nexport interface Point {\n x: number\n y: number\n}\n\nexport interface Rect {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport function rectCenter(rect: Rect): Point {\n return { x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 }\n}\n\n/** 角度(度)→ 弧度 */\nexport function degToRad(deg: number): number {\n return (deg * Math.PI) / 180\n}\n\n/**\n * 把世界坐标点转换到矩形自身的局部坐标(以中心为原点、已抵消旋转)。\n *\n * 旋转方向与 Canvas `rotate()` 一致(顺时针为正),因此用 `-angle` 做逆变换。\n */\nexport function toLocalPoint(point: Point, transform: Pick<ClipTransform, 'x' | 'y' | 'w' | 'h' | 'angle'>): Point {\n const cx = transform.x + transform.w / 2\n const cy = transform.y + transform.h / 2\n const rad = -degToRad(transform.angle)\n const dx = point.x - cx\n const dy = point.y - cy\n return {\n x: dx * Math.cos(rad) - dy * Math.sin(rad),\n y: dx * Math.sin(rad) + dy * Math.cos(rad),\n }\n}\n\n/** 局部坐标(相对中心)→ 世界坐标 */\nexport function fromLocalPoint(local: Point, transform: Pick<ClipTransform, 'x' | 'y' | 'w' | 'h' | 'angle'>): Point {\n const cx = transform.x + transform.w / 2\n const cy = transform.y + transform.h / 2\n const rad = degToRad(transform.angle)\n return {\n x: cx + local.x * Math.cos(rad) - local.y * Math.sin(rad),\n y: cy + local.x * Math.sin(rad) + local.y * Math.cos(rad),\n }\n}\n\n/** 点是否落在(可能旋转的)矩形内 */\nexport function pointInTransform(point: Point, transform: ClipTransform): boolean {\n if (transform.w <= 0 || transform.h <= 0) return false\n const local = toLocalPoint(point, transform)\n return Math.abs(local.x) <= transform.w / 2 && Math.abs(local.y) <= transform.h / 2\n}\n\n/**\n * 按方位返回手柄在**局部坐标**中的位置(相对中心)。\n *\n * 边手柄固定一个方向为 0,角手柄两个方向都取半宽/半高。\n */\nexport function handleLocalOffset(handle: HandleKey, w: number, h: number): Point {\n const hw = w / 2\n const hh = h / 2\n const x = handle.includes('w') ? -hw : handle.includes('e') ? hw : 0\n const y = handle.includes('n') ? -hh : handle.includes('s') ? hh : 0\n return { x, y }\n}\n\n/**\n * 计算缩放后的 transform。\n *\n * 采用「固定对角/对边」的直觉行为:拖右边缘时左边缘不动,拖左上角时右下角不动。\n * 指针先被投影到局部坐标轴,因此素材旋转后手柄仍沿自身方向缩放(而不是屏幕方向)。\n *\n * @param start 拖动开始时的 transform 快照\n * @param handle 被拖动的方位\n * @param pointer 当前指针(工程坐标)\n * @param keepAspect\n * - `false`(默认):自由缩放\n * - `true`:所有手柄都等比(Shift 临时约束)\n * - `'corners'`:只有角手柄等比,边手柄仍单维自由\n * (片段自身「锁定宽高比」用这个 —— 若边手柄也等比,\n * 就会出现「拖右边、上下也跟着变」的诡异手感)\n */\nexport function resizeTransform(\n start: ClipTransform,\n handle: HandleKey,\n pointer: Point,\n keepAspect: boolean | 'corners' = false,\n): ClipTransform {\n const local = toLocalPoint(pointer, start)\n const hw = start.w / 2\n const hh = start.h / 2\n\n // 局部坐标下的四条边;拖动某条边只改它,其余固定不动(= 固定对角/对边)\n let left = -hw\n let right = hw\n let top = -hh\n let bottom = hh\n\n if (handle.includes('w')) left = Math.min(local.x, right - MIN_TRANSFORM_SIZE)\n if (handle.includes('e')) right = Math.max(local.x, left + MIN_TRANSFORM_SIZE)\n if (handle.includes('n')) top = Math.min(local.y, bottom - MIN_TRANSFORM_SIZE)\n if (handle.includes('s')) bottom = Math.max(local.y, top + MIN_TRANSFORM_SIZE)\n\n let nextW = right - left\n let nextH = bottom - top\n\n const isCorner = handle.length === 2\n const aspectActive = keepAspect === true || (keepAspect === 'corners' && isCorner)\n\n if (aspectActive && start.w > 0 && start.h > 0) {\n const ratio = start.w / start.h\n if (isCorner) {\n // 以宽为准反算高,再按被拖动的方向贴住不动的对边\n nextH = nextW / ratio\n if (handle.includes('n')) top = bottom - nextH\n else bottom = top + nextH\n } else if (handle === 'e' || handle === 'w') {\n nextH = nextW / ratio\n top = -nextH / 2\n bottom = nextH / 2\n } else {\n nextW = nextH * ratio\n left = -nextW / 2\n right = nextW / 2\n }\n }\n\n nextW = Math.max(MIN_TRANSFORM_SIZE, right - left)\n nextH = Math.max(MIN_TRANSFORM_SIZE, bottom - top)\n\n // 新中心(局部坐标)→ 世界坐标 → 左上角\n const localCenter = { x: (left + right) / 2, y: (top + bottom) / 2 }\n const worldCenter = fromLocalPoint(localCenter, start)\n return {\n x: worldCenter.x - nextW / 2,\n y: worldCenter.y - nextH / 2,\n w: nextW,\n h: nextH,\n angle: start.angle,\n }\n}\n\n/** 把角度归一化到 (-180, 180],避免旋转多圈后数值无限增大 */\nexport function normalizeAngle(deg: number): number {\n let a = deg % 360\n if (a > 180) a -= 360\n if (a <= -180) a += 360\n return a\n}\n\n/** 按手柄方位给出鼠标光标(已考虑素材自身旋转) */\nexport function handleCursor(handle: HandleKey | 'rotate', angleDeg: number): string {\n if (handle === 'rotate') return 'grab'\n const base: Record<HandleKey, number> = {\n n: 0,\n ne: 45,\n e: 90,\n se: 135,\n s: 180,\n sw: 225,\n w: 270,\n nw: 315,\n }\n const cursors = ['ns-resize', 'nesw-resize', 'ew-resize', 'nwse-resize']\n // 把素材旋转叠加到手柄方位上,再落到 4 档光标\n const deg = ((base[handle] + angleDeg) % 180 + 180) % 180\n const index = Math.round(deg / 45) % 4\n return cursors[index] ?? 'default'\n}\n\n/** 两个尺寸是否近似相等(避免浮点噪声导致无谓的 store 写入) */\nexport function sameTransform(a: ClipTransform, b: ClipTransform, epsilon = 0.5): boolean {\n return (\n Math.abs(a.x - b.x) < epsilon &&\n Math.abs(a.y - b.y) < epsilon &&\n Math.abs(a.w - b.w) < epsilon &&\n Math.abs(a.h - b.h) < epsilon &&\n Math.abs(a.angle - b.angle) < epsilon\n )\n}\n","/**\n * 画布(工程)尺寸与比例。\n *\n * 为什么需要单独一层:时间轴里所有 `clip.transform` 都以工程分辨率为坐标系,\n * 换比例时如果不做变换,1280×720 里铺满的素材放到 1080×1920 画布里就会\n * 只剩顶部一条 —— 这正是「换了比例看起来没生效 / 画面全是黑的」的成因。\n *\n * 因此尺寸变更分两种语义:\n * - `scale`(保持构图):把所有素材按同样的比例缩放并平移,视觉上「整幅画面\n * 被重新装进新画布」,适合横屏切竖屏、改分辨率。\n * - `keep`(保持素材原样):只换画布,素材坐标原封不动,适合「画布留白变多」。\n *\n * 纯函数,不依赖 React。\n */\n\nimport type { ClipTransform, Project, TimelineClip } from '../types'\n\nexport interface AspectPreset {\n id: string\n label: string\n hint: string\n width: number\n height: number\n}\n\n/**\n * 常用比例预设。\n *\n * 尺寸都取该比例下「编辑器默认档」的常见值:横屏 720p、竖屏 720×1280、\n * 方形 1080×1080、竖版 1080×1920、横版 1920×1080。用户也可以自定义。\n */\nexport const ASPECT_PRESETS: AspectPreset[] = [\n { id: '16:9', label: '16:9', hint: '横屏 1280×720', width: 1280, height: 720 },\n { id: '9:16', label: '9:16', hint: '竖屏 720×1280', width: 720, height: 1280 },\n { id: '1:1', label: '1:1', hint: '方形 1080×1080', width: 1080, height: 1080 },\n { id: '4:3', label: '4:3', hint: '标准 960×720', width: 960, height: 720 },\n { id: '3:4', label: '3:4', hint: '竖版 720×960', width: 720, height: 960 },\n { id: '4:5', label: '4:5', hint: '社交媒体 864×1080', width: 864, height: 1080 },\n { id: '21:9', label: '21:9', hint: '宽银幕 1680×720', width: 1680, height: 720 },\n]\n\nexport type SizeChangeMode = 'scale' | 'keep'\n\n/** 求最大公约数(用于把尺寸化简成比例) */\nfunction gcd(a: number, b: number): number {\n let x = Math.abs(Math.round(a))\n let y = Math.abs(Math.round(b))\n while (y > 0) {\n const t = x % y\n x = y\n y = t\n }\n return x || 1\n}\n\n/** `1920×1080` → `16:9`;无法化简时返回 `1920:1080` 形式 */\nexport function ratioLabel(width: number, height: number): string {\n if (width <= 0 || height <= 0) return '—'\n const divisor = gcd(width, height)\n return `${width / divisor}:${height / divisor}`\n}\n\n/** 保留两位小数,避免属性面板里出现 123.456789 */\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100\n}\n\n/**\n * 把素材几何从 `from` 画布映射到 `to` 画布。\n *\n * 用**同一个**缩放系数(取宽高比中较小者),保证素材不变形:\n * 16:9 → 9:16 时画面按高度缩小、水平居中,两侧留黑 —— 与剪辑软件里\n * 「按比例适配」的行为一致。若分别按宽、高缩放,人物会被横向拉扁。\n */\nexport function remapTransform(\n transform: ClipTransform,\n from: { width: number; height: number },\n to: { width: number; height: number },\n): ClipTransform {\n const fromW = Math.max(1, from.width)\n const fromH = Math.max(1, from.height)\n const scale = Math.min(to.width / fromW, to.height / fromH)\n const centerX = (transform.x + transform.w / 2) * scale + (to.width - fromW * scale) / 2\n const centerY = (transform.y + transform.h / 2) * scale + (to.height - fromH * scale) / 2\n const w = transform.w * scale\n const h = transform.h * scale\n return {\n x: round2(centerX - w / 2),\n y: round2(centerY - h / 2),\n w: round2(w),\n h: round2(h),\n angle: transform.angle,\n }\n}\n\n/**\n * 根据模式生成新的 clips 数组。\n *\n * @param mode `scale` = 素材随画布等比缩放;`keep` = 素材坐标不动\n */\nexport function remapClipsForSize(\n clips: TimelineClip[],\n from: { width: number; height: number },\n to: { width: number; height: number },\n mode: SizeChangeMode,\n): TimelineClip[] {\n if (mode === 'keep') return clips\n return clips.map((clip) => ({\n ...clip,\n transform: remapTransform(clip.transform, from, to),\n }))\n}\n\n/** 判断工程是否已是非标尺寸(不在预设里) */\nexport function isCustomSize(project: Project): boolean {\n return !ASPECT_PRESETS.some((p) => p.width === project.width && p.height === project.height)\n}\n\n/** 尺寸合法化:不小于 16,偶数(编码器要求) */\nexport function normalizeSize(width: number, height: number): { width: number; height: number } {\n const even = (n: number) => Math.max(16, Math.round(n / 2) * 2)\n return { width: even(width), height: even(height) }\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,YAAY,MAAqB,MAAqB;CACpE,OAAO;EACL,IAAI,SAAS,OAAO;EACpB;EACA;EACA,QAAQ;EACR,OAAO;EACP,QAAQ;CACV;AACF;AAEA,SAAgB,mBAAmB,OAAO,SAAkB;CAC1D,OAAO;EACL,IAAI,SAAS,SAAS;EACtB;EACA,OAAO;EACP,QAAQ;EACR,QAAQ,CAAC,YAAY,UAAU,MAAM,GAAG,YAAY,SAAS,MAAM,CAAC;EACpE,OAAO,CAAC;EACR,WAAW,KAAK,IAAI;CACtB;AACF;;AAaA,SAAgB,WAAW,OAAwB,SAAgC;CACjF,MAAM,EAAE,aAAa,GAAG,cAAc,MAAM;CAC5C,IAAI,IAAI,QAAQ;CAChB,IAAI,IAAI,QAAQ;CAChB,IAAI,aAAa,KAAK,cAAc,GAAG;EACrC,MAAM,QAAQ,KAAK,IAAI,QAAQ,QAAQ,YAAY,QAAQ,SAAS,WAAW;EAC/E,IAAI,KAAK,MAAM,aAAa,KAAK;EACjC,IAAI,KAAK,MAAM,cAAc,KAAK;CACpC;CACA,OAAO;EACL,IAAI,SAAS,MAAM;EACnB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,SAAS,KAAK,MAAM,OAAO;EAC3B,YAAY,KAAK,MAAM,UAAU;EACjC,WAAW,KAAK,MAAM,aAAa,CAAC;EACpC,cAAc;EACd,QAAQ;EACR,OAAO;EACP,SAAS;EACT,MAAM;EACN,WAAW;GACT,GAAG,KAAK,OAAO,QAAQ,QAAQ,KAAK,CAAC;GACrC,GAAG,KAAK,OAAO,QAAQ,SAAS,KAAK,CAAC;GACtC;GACA;GACA,OAAO;EACT;EACA,UAAU,KAAK,CAAC;EAChB,WAAW,KAAK,CAAC;CACnB;AACF;;AAGA,SAAgB,mBAAmB,IAAoB;CACrD,OAAO,KAAK,KAAK,OAAO,SAAS,EAAE,IAAI,KAAK;AAC9C;AAEA,SAAgB,WAAW,MAA4B;CACrD,OAAO,KAAK,UAAU,KAAK;AAC7B;;AAGA,SAAgB,mBAAmB,SAA0B;CAC3D,OAAO,QAAQ,MAAM,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC;AACzE;AAEA,SAAgB,SAAS,SAAkB,SAAoC;CAC7E,OAAO,QAAQ,OAAO,MAAM,MAAM,EAAE,OAAO,OAAO;AACpD;AAEA,SAAgB,eAAe,SAAkB,SAA0B;CACzE,MAAM,QAAQ,SAAS,SAAS,OAAO;CACvC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,CAAC,MAAM,SAAS,CAAC,MAAM;AAChC;;AAGA,SAAgB,cAAc,SAAkB,SAAiC;CAC/E,OAAO,QAAQ,MACZ,QAAQ,MAAM,EAAE,YAAY,OAAO,CAAC,CACpC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AACzC;;AAGA,SAAgB,YAAY,SAAkB,SAAyB;CACrE,OAAO,cAAc,SAAS,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC;AAC3F;;AAGA,SAAgB,gBAAgB,MAA4B;CAC1D,OAAO,KAAK,MAAM,KAAK,aAAa,KAAK,YAAY;AACvD;;;AC1GA,SAAgB,cAAc,MAAoB,MAAmD;CACnG,MAAM,QAAQ,KAAK;CACnB,MAAM,MAAM,WAAW,IAAI;CAC3B,IAAI,QAAQ,SAAS,QAAQ,KAAK,OAAO;CACzC,MAAM,eAAe,OAAO;CAC5B,MAAM,gBAAgB,MAAM;CAC5B,MAAM,eAAe,KAAK,MAAM,eAAe,KAAK,YAAY;CAEhE,MAAM,OAAqB;EACzB,GAAG;EACH,YAAY,KAAK,YAAY;EAC7B,WAAW,KAAK,KAAK,IAAI,KAAK,WAAW,YAAY,CAAC;CACxD;CACA,MAAM,QAAsB;EAC1B,GAAG;EACH,IAAI,GAAG,KAAK,GAAG;EACf,SAAS,KAAK,IAAI;EAClB,YAAY,KAAK,aAAa;EAC9B,WAAW,KAAK,KAAK,YAAY,YAAY;EAC7C,UAAU,KAAK,KAAK,IAAI,KAAK,UAAU,aAAa,CAAC;CACvD;CACA,KAAK,WAAW,KAAK,KAAK,IAAI,KAAK,UAAU,YAAY,CAAC;CAC1D,OAAO,CAAC,MAAM,KAAK;AACrB;;AAGA,SAAgB,aAAa,MAAoB,SAAiB,SAA+B;CAC/F,OAAO;EAAE,GAAG;EAAM;EAAS,SAAS,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;CAAE;AACjE;;;;;;;;;AAYA,SAAgB,aACd,MACA,MACA,SACA,gBAAgB,KACF;CACd,MAAM,MAAM,WAAW,IAAI;CAE3B,IAAI,SAAS,SAAS;EACpB,MAAM,YAAY,KAAK,IACrB,KAAK,IAAI,GAAG,KAAK,UAAU,OAAO,GAClC,MAAM,aACR;EACA,MAAM,UAAU,YAAY,KAAK;EACjC,MAAM,cAAc,KAAK,MAAM,UAAU,KAAK,YAAY;EAC1D,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,YAAY,WAAW;EAC5D,OAAO;GACL,GAAG;GACH,SAAS,KAAK,SAAS;GACvB,WAAW,KAAK,WAAW;GAC3B,YAAY,KAAK,MAAM,SAAS;EAClC;CACF;CAEA,MAAM,eAAe,KAAK,IAAI,eAAe,KAAK,aAAa,OAAO;CACtE,OAAO;EAAE,GAAG;EAAM,YAAY,KAAK,YAAY;CAAE;AACnD;;AAGA,SAAgB,gBAAgB,OAAuB,KAA+B;CACpF,MAAM,MAAM,IAAI,IAAI,GAAG;CACvB,OAAO,MAAM,QAAQ,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAC3C;;AAGA,SAAgB,mBACd,OACA,KACA,UACA,QACiD;CACjD,MAAM,MAAM,IAAI,IAAI,GAAG;CACvB,OAAO,MACJ,QAAQ,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAC5B,KAAK,OAAO;EACX,UAAU,EAAE;EACZ,MAAM;GACJ,GAAG;GACH,IAAI,OAAO;GACX,SAAS,KAAK,EAAE,UAAU,QAAQ;GAClC,WAAW,EAAE,GAAG,EAAE,UAAU;EAC9B;CACF,EAAE;AACN;;AAGA,SAAgB,iBAAiB,OAAuB,SAAiC;CACvF,MAAM,UAAU,MACb,QAAQ,MAAM,EAAE,YAAY,OAAO,CAAC,CACpC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;CACvC,IAAI,SAAS;CACb,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,KAAK,SAAS;EACvB,QAAQ,IAAI,EAAE,IAAI,MAAM;EACxB,UAAU,EAAE;CACd;CACA,OAAO,MAAM,KAAK,MAAM;EACtB,MAAM,OAAO,QAAQ,IAAI,EAAE,EAAE;EAC7B,OAAO,QAAQ,OAAO,IAAI;GAAE,GAAG;GAAG,SAAS,KAAK,IAAI;EAAE;CACxD,CAAC;AACH;;AAGA,SAAgB,kBAAkB,QAAiB,SAAiB,SAA0B;CAC5F,MAAM,OAAO,OAAO,WAAW,MAAM,EAAE,OAAO,OAAO;CACrD,IAAI,OAAO,GAAG,OAAO;CACrB,MAAM,OAAO,CAAC,GAAG,MAAM;CACvB,MAAM,CAAC,SAAS,KAAK,OAAO,MAAM,CAAC;CACnC,IAAI,CAAC,OAAO,OAAO;CACnB,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,OAAO,CAAC,GAAG,GAAG,KAAK;CACjE,OAAO;AACT;;AAGA,SAAgB,cAAc,QAAgB,MAAc,QAAgB,MAAuB;CACjG,OAAO,SAAS,QAAQ,SAAS;AACnC;;AAGA,SAAgB,aACd,OACA,SACA,SACA,OACA,WACgB;CAChB,OAAO,MAAM,QACV,MACC,EAAE,YAAY,WACd,EAAE,OAAO,aACT,cAAc,SAAS,OAAO,EAAE,SAAS,WAAW,CAAC,CAAC,CAC1D;AACF;;AC3IA,IAAa,iBAA8B;CAAC;CAAM;CAAK;CAAM;CAAK;CAAM;CAAK;CAAM;AAAG;;AAGtF,IAAa,qBAAqB;AAclC,SAAgB,WAAW,MAAmB;CAC5C,OAAO;EAAE,GAAG,KAAK,IAAI,KAAK,IAAI;EAAG,GAAG,KAAK,IAAI,KAAK,IAAI;CAAE;AAC1D;;AAGA,SAAgB,SAAS,KAAqB;CAC5C,OAAQ,MAAM,KAAK,KAAM;AAC3B;;;;;;AAOA,SAAgB,aAAa,OAAc,WAAwE;CACjH,MAAM,KAAK,UAAU,IAAI,UAAU,IAAI;CACvC,MAAM,KAAK,UAAU,IAAI,UAAU,IAAI;CACvC,MAAM,MAAM,CAAC,SAAS,UAAU,KAAK;CACrC,MAAM,KAAK,MAAM,IAAI;CACrB,MAAM,KAAK,MAAM,IAAI;CACrB,OAAO;EACL,GAAG,KAAK,KAAK,IAAI,GAAG,IAAI,KAAK,KAAK,IAAI,GAAG;EACzC,GAAG,KAAK,KAAK,IAAI,GAAG,IAAI,KAAK,KAAK,IAAI,GAAG;CAC3C;AACF;;AAGA,SAAgB,eAAe,OAAc,WAAwE;CACnH,MAAM,KAAK,UAAU,IAAI,UAAU,IAAI;CACvC,MAAM,KAAK,UAAU,IAAI,UAAU,IAAI;CACvC,MAAM,MAAM,SAAS,UAAU,KAAK;CACpC,OAAO;EACL,GAAG,KAAK,MAAM,IAAI,KAAK,IAAI,GAAG,IAAI,MAAM,IAAI,KAAK,IAAI,GAAG;EACxD,GAAG,KAAK,MAAM,IAAI,KAAK,IAAI,GAAG,IAAI,MAAM,IAAI,KAAK,IAAI,GAAG;CAC1D;AACF;;AAGA,SAAgB,iBAAiB,OAAc,WAAmC;CAChF,IAAI,UAAU,KAAK,KAAK,UAAU,KAAK,GAAG,OAAO;CACjD,MAAM,QAAQ,aAAa,OAAO,SAAS;CAC3C,OAAO,KAAK,IAAI,MAAM,CAAC,KAAK,UAAU,IAAI,KAAK,KAAK,IAAI,MAAM,CAAC,KAAK,UAAU,IAAI;AACpF;;;;;;AAOA,SAAgB,kBAAkB,QAAmB,GAAW,GAAkB;CAChF,MAAM,KAAK,IAAI;CACf,MAAM,KAAK,IAAI;CAGf,OAAO;EAAE,GAFC,OAAO,SAAS,GAAG,IAAI,CAAC,KAAK,OAAO,SAAS,GAAG,IAAI,KAAK;EAEvD,GADF,OAAO,SAAS,GAAG,IAAI,CAAC,KAAK,OAAO,SAAS,GAAG,IAAI,KAAK;CACrD;AAChB;;;;;;;;;;;;;;;;;AAkBA,SAAgB,gBACd,OACA,QACA,SACA,aAAkC,OACnB;CACf,MAAM,QAAQ,aAAa,SAAS,KAAK;CACzC,MAAM,KAAK,MAAM,IAAI;CACrB,MAAM,KAAK,MAAM,IAAI;CAGrB,IAAI,OAAO,CAAC;CACZ,IAAI,QAAQ;CACZ,IAAI,MAAM,CAAC;CACX,IAAI,SAAS;CAEb,IAAI,OAAO,SAAS,GAAG,GAAG,OAAO,KAAK,IAAI,MAAM,GAAG,QAAA,EAA0B;CAC7E,IAAI,OAAO,SAAS,GAAG,GAAG,QAAQ,KAAK,IAAI,MAAM,GAAG,OAAA,EAAyB;CAC7E,IAAI,OAAO,SAAS,GAAG,GAAG,MAAM,KAAK,IAAI,MAAM,GAAG,SAAA,EAA2B;CAC7E,IAAI,OAAO,SAAS,GAAG,GAAG,SAAS,KAAK,IAAI,MAAM,GAAG,MAAA,EAAwB;CAE7E,IAAI,QAAQ,QAAQ;CACpB,IAAI,QAAQ,SAAS;CAErB,MAAM,WAAW,OAAO,WAAW;CAGnC,KAFqB,eAAe,QAAS,eAAe,aAAa,aAErD,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG;EAC9C,MAAM,QAAQ,MAAM,IAAI,MAAM;EAC9B,IAAI,UAAU;GAEZ,QAAQ,QAAQ;GAChB,IAAI,OAAO,SAAS,GAAG,GAAG,MAAM,SAAS;QACpC,SAAS,MAAM;EACtB,OAAO,IAAI,WAAW,OAAO,WAAW,KAAK;GAC3C,QAAQ,QAAQ;GAChB,MAAM,CAAC,QAAQ;GACf,SAAS,QAAQ;EACnB,OAAO;GACL,QAAQ,QAAQ;GAChB,OAAO,CAAC,QAAQ;GAChB,QAAQ,QAAQ;EAClB;CACF;CAEA,QAAQ,KAAK,IAAA,IAAwB,QAAQ,IAAI;CACjD,QAAQ,KAAK,IAAA,IAAwB,SAAS,GAAG;CAIjD,MAAM,cAAc,eAAe;EADb,IAAI,OAAO,SAAS;EAAG,IAAI,MAAM,UAAU;CAC9B,GAAa,KAAK;CACrD,OAAO;EACL,GAAG,YAAY,IAAI,QAAQ;EAC3B,GAAG,YAAY,IAAI,QAAQ;EAC3B,GAAG;EACH,GAAG;EACH,OAAO,MAAM;CACf;AACF;;AAGA,SAAgB,eAAe,KAAqB;CAClD,IAAI,IAAI,MAAM;CACd,IAAI,IAAI,KAAK,KAAK;CAClB,IAAI,KAAK,MAAM,KAAK;CACpB,OAAO;AACT;;AAGA,SAAgB,aAAa,QAA8B,UAA0B;CACnF,IAAI,WAAW,UAAU,OAAO;CAChC,MAAM,OAAkC;EACtC,GAAG;EACH,IAAI;EACJ,GAAG;EACH,IAAI;EACJ,GAAG;EACH,IAAI;EACJ,GAAG;EACH,IAAI;CACN;CACA,MAAM,UAAU;EAAC;EAAa;EAAe;EAAa;CAAa;CAEvE,MAAM,QAAQ,KAAK,UAAU,YAAY,MAAM,OAAO;CAEtD,OAAO,QADO,KAAK,MAAM,MAAM,EAAE,IAAI,MACZ;AAC3B;;AAGA,SAAgB,cAAc,GAAkB,GAAkB,UAAU,IAAc;CACxF,OACE,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,WACtB,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,WACtB,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,WACtB,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,WACtB,KAAK,IAAI,EAAE,QAAQ,EAAE,KAAK,IAAI;AAElC;;;;;;;;AC3KA,IAAa,iBAAiC;CAC5C;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAe,OAAO;EAAM,QAAQ;CAAI;CAC3E;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAe,OAAO;EAAK,QAAQ;CAAK;CAC3E;EAAE,IAAI;EAAO,OAAO;EAAO,MAAM;EAAgB,OAAO;EAAM,QAAQ;CAAK;CAC3E;EAAE,IAAI;EAAO,OAAO;EAAO,MAAM;EAAc,OAAO;EAAK,QAAQ;CAAI;CACvE;EAAE,IAAI;EAAO,OAAO;EAAO,MAAM;EAAc,OAAO;EAAK,QAAQ;CAAI;CACvE;EAAE,IAAI;EAAO,OAAO;EAAO,MAAM;EAAiB,OAAO;EAAK,QAAQ;CAAK;CAC3E;EAAE,IAAI;EAAQ,OAAO;EAAQ,MAAM;EAAgB,OAAO;EAAM,QAAQ;CAAI;AAC9E;;AAKA,SAAS,IAAI,GAAW,GAAmB;CACzC,IAAI,IAAI,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC;CAC9B,IAAI,IAAI,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC;CAC9B,OAAO,IAAI,GAAG;EACZ,MAAM,IAAI,IAAI;EACd,IAAI;EACJ,IAAI;CACN;CACA,OAAO,KAAK;AACd;;AAGA,SAAgB,WAAW,OAAe,QAAwB;CAChE,IAAI,SAAS,KAAK,UAAU,GAAG,OAAO;CACtC,MAAM,UAAU,IAAI,OAAO,MAAM;CACjC,OAAO,GAAG,QAAQ,QAAQ,GAAG,SAAS;AACxC;;AAGA,SAAS,OAAO,GAAmB;CACjC,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;;;;;;AASA,SAAgB,eACd,WACA,MACA,IACe;CACf,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK;CACpC,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM;CACrC,MAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,OAAO,GAAG,SAAS,KAAK;CAC1D,MAAM,WAAW,UAAU,IAAI,UAAU,IAAI,KAAK,SAAS,GAAG,QAAQ,QAAQ,SAAS;CACvF,MAAM,WAAW,UAAU,IAAI,UAAU,IAAI,KAAK,SAAS,GAAG,SAAS,QAAQ,SAAS;CACxF,MAAM,IAAI,UAAU,IAAI;CACxB,MAAM,IAAI,UAAU,IAAI;CACxB,OAAO;EACL,GAAG,OAAO,UAAU,IAAI,CAAC;EACzB,GAAG,OAAO,UAAU,IAAI,CAAC;EACzB,GAAG,OAAO,CAAC;EACX,GAAG,OAAO,CAAC;EACX,OAAO,UAAU;CACnB;AACF;;;;;;AAOA,SAAgB,kBACd,OACA,MACA,IACA,MACgB;CAChB,IAAI,SAAS,QAAQ,OAAO;CAC5B,OAAO,MAAM,KAAK,UAAU;EAC1B,GAAG;EACH,WAAW,eAAe,KAAK,WAAW,MAAM,EAAE;CACpD,EAAE;AACJ;;AAGA,SAAgB,aAAa,SAA2B;CACtD,OAAO,CAAC,eAAe,MAAM,MAAM,EAAE,UAAU,QAAQ,SAAS,EAAE,WAAW,QAAQ,MAAM;AAC7F;;AAGA,SAAgB,cAAc,OAAe,QAAmD;CAC9F,MAAM,QAAQ,MAAc,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC;CAC9D,OAAO;EAAE,OAAO,KAAK,KAAK;EAAG,QAAQ,KAAK,MAAM;CAAE;AACpD"}
|