# LayoutSpec 规范文档 — 自定义"歌词布局"

> 本文档译自日语原版。**日语版为正式版本**，如有出入以日语版为准。

本文档是 movlyric 歌词布局定义“LayoutSpec”的完整规范。
在应用的“样式编辑器 →“布局”标签 → 布局预设 →“自定义”→ 用 AI 生成…”中使用。

**有两个名称相近但完全不同的东西，请不要混淆。**

| 决定的内容 | 规范文档 |
| --- | --- |
| 歌词放在画面的**什么位置**（本文档） | `LAYOUT_SPEC.md` |
| 歌词文字**如何运动**（登场、退场、明灭） | `ANIMATION_SPEC.md` |
| 在歌词**背后绘制的图形、线条** | `GRAPHIC_SPEC.md` |

本文档的目标是：只读过此文档的 AI（或人类）也能写出可以直接粘贴到应用中并被接受的 JSON。
文档末尾附有可交给 AI 使用的提示词模板。将本文档整体作为提示词的一部分贴入同样可以正常使用。

---

## 1. 概述

LayoutSpec 只决定"一句歌词放在画面何处、如何排列"这一件事。
其中不含任何随时间变化的要素（运动由 AnimationSpec 负责）。

由以下三项决定：

1. **位置** — 以画面上的哪个点为基准来放置（`anchors` / `placement`）
2. **排版方式** — 横排还是竖排、最多折成几行（`writing` / `maxLines` / `lineGap` / `scaleMode`）
3. **错落** — 是否不排成直线而做出错落感（`scatter` / `baselineRotateDeg` / `path` / `charScaleRange` / `focusWord`）

**所有用到随机数的项目，在同一首曲子内都是固定的。** 只要曲子和样式相同，每次结果都一致，
预览与导出也会一致（`placement: "random"` / `scatter` / `charScaleRange` /
`focusWord.pick: "random"` 全部由样式的 `seed` 决定）。

---

## 2. JSON 格式完整规范

### 2.1 根节点：LayoutSpec

```json
{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
  "placement": "fixed"
}
```

| 键 | 必需 | 类型 | 含义 |
| --- | --- | --- | --- |
| `writing` | ✅ | `"horizontal"` \| `"vertical"` | 横排 / 竖排（从上到下，列从右到左） |
| `anchors` | ✅ | Anchor 数组（1 个以上） | 放置候选点 |
| `placement` | ✅ | `"fixed"` \| `"cycle"` \| `"random"` | 如何把乐句分配到候选点 |
| `maxLines` | | 整数，1 以上 | 折行数（竖排时为列数）。默认 1 = 不折行 |
| `lineGap` | | 数值，0 以上 | 行间距（列间距），相对字号的比例。默认 0.15 |
| `scaleMode` | | `"fixed"` \| `"fill"` | `fill` = 排满画面宽度（竖排时为高度）的大字排版。默认 `fixed` |
| `scatter` | | Scatter | 逐字的散落效果 |
| `baselineRotateDeg` | | 数值 | 整句的倾斜角度（度）。默认 0 |
| `path` | | Path | 非直线的排列方式（**仅横排**） |
| `charScaleRange` | | `[最小值, 最大值]` | 逐字大小的浮动范围（**仅横排**） |
| `focusWord` | | FocusWord | 放大其中一个单词（**仅横排**） |

**取值与默认值相同的键请不要写。** 例如 `maxLines: 1` 或 `scaleMode: "fixed"`
应当省略不写（写与不写含义相同，写出来只会让保存内容徒增无用信息）。

### 2.2 Anchor（放置候选点）

```json
{ "x": 0.5, "y": 0.88, "align": "center" }
```

| 键 | 必需 | 类型 | 含义 |
| --- | --- | --- | --- |
| `x` | ✅ | 0..1 | 画面左端为 0 ～ 右端为 1 |
| `y` | ✅ | 0..1 | 画面上端为 0 ～ 下端为 1 |
| `align` | ✅ | `"start"` \| `"center"` \| `"end"` | 以锚点为基准，歌词向哪一侧延伸 |

`align` 在横排时表示左对齐／居中对齐／右对齐，竖排时表示上对齐／居中对齐／下对齐。

**放在画面边缘时，请配合调整 `align`。** 例如给 `x: 0.08` 指定 `align: "center"`，
较长的乐句会溢出画面左侧。想贴靠左端的话应使用 `align: "start"`。

### 2.3 placement（乐句的分配方式）

| 值 | 含义 |
| --- | --- |
| `"fixed"` | 始终使用第 1 个候选点（候选点只有 1 个时用这个） |
| `"cycle"` | 按乐句顺序依次轮流候选点 |
| `"random"` | 从候选点中随机选取（同一首曲子内固定） |

**候选点只有 1 个时，这三种取值结果都相同。** 只有写了 2 个以上候选点时才有实际意义。

### 2.4 Scatter（逐字散落）

```json
{ "offsetRatio": 0.25, "rotateDeg": 8 }
```

| 键 | 必需 | 类型 | 含义 |
| --- | --- | --- | --- |
| `offsetRatio` | ✅ | 数值，0 以上 | 位置偏移幅度，相对字号的比例 |
| `rotateDeg` | ✅ | 数值，0 以上 | 旋转摆动幅度（度） |

两者都在同一首曲子内固定。取 0.3 / 12 左右会呈现相当散乱的效果。

### 2.5 Path（非直线的排列方式，仅横排）

```json
{ "kind": "wave", "amplitude": 0.3, "period": 8 }
```

| 键 | 必需 | 类型 | 含义 |
| --- | --- | --- | --- |
| `kind` | ✅ | `"wave"` \| `"arc"` \| `"steps"` | 波浪 / 圆弧 / 阶梯 |
| `amplitude` | ✅ | 数值 | 摆动幅度，相对字号的比例 |
| `period` | | 数值，大于 0 | 多少个字符一个周期。省略时 `wave` 为 8，`steps` 为 3 |

`wave` 和 `arc` 会**让文字的旋转跟随曲线的切线方向**（沿排列方向倾斜）。
`arc` 不使用 `period`（整句歌词共同构成一条弧线）。
有多行时，各行会独立应用此效果。

### 2.6 charScaleRange（文字大小的浮动，仅横排）

```json
[0.85, 1.3]
```

由 `[最小值, 最大值]` 两个元素构成。会为每个字符确定一个在该曲子内固定的倍率。
该倍率同时作用于字号与字符间距，因此行宽计算、折行、居中对齐都会一并将这种浮动纳入考虑，保持一致。

### 2.7 FocusWord（放大其中一个单词，仅横排）

```json
{ "scale": 1.4, "pick": "longest" }
```

| 键 | 必需 | 类型 | 含义 |
| --- | --- | --- | --- |
| `scale` | ✅ | 数值，大于 0 | 目标单词的放大倍率 |
| `pick` | ✅ | `"longest"` \| `"random"` | 字符数最多（相同则取最前面的）／随机（同一首曲子内固定） |

与 `charScaleRange` 同时使用时，倍率会相乘。

---

## 3. 常见错误（禁止事项）

- **不要在竖排中写 `path` / `charScaleRange` / `focusWord`。** 引擎只在横排时解析这些字段，
  写了也不会产生任何效果（也不会报错，因此很容易没有察觉）。
- **不要把 `anchors` 设为空数组。** 这样会没有放置位置，导致歌词不显示。
- **不要给 `x` / `y` 写像素值。** 它们是 0～1 的比例（应写 `0.5` 而不是 `960`）。
- **不要写与默认值相同的键**（如 `maxLines: 1` / `lineGap: 0.15` / `scaleMode: "fixed"`，或候选点只有 1 个时实质无意义的 `placement: "cycle"` 等）。
- **不要写随时间或音频变化的值。** LayoutSpec 中没有数学表达式或动画。
  想要运动效果，请交给 AnimationSpec（`ANIMATION_SPEC.md`）负责。
- 不要写不存在的属性、注释、末尾逗号。

---

## 4. 示例

### 4.1 画面下部单行（卡拉OK经典样式）

```json
{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.88, "align": "center" }],
  "placement": "fixed"
}
```

### 4.2 上下分布，最多折成 2 行

```json
{
  "writing": "horizontal",
  "anchors": [
    { "x": 0.5, "y": 0.3, "align": "center" },
    { "x": 0.5, "y": 0.7, "align": "center" }
  ],
  "placement": "cycle",
  "maxLines": 2,
  "lineGap": 0.4
}
```

每句歌词会按"上→下→上…"依次轮流切换。

### 4.3 竖排，从右侧起 3 列

```json
{
  "writing": "vertical",
  "anchors": [{ "x": 0.82, "y": 0.15, "align": "start" }],
  "placement": "fixed",
  "maxLines": 3,
  "lineGap": 0.5
}
```

竖排文字从上到下书写，列从右向左依次增加。这里用 `align: "start"` 让文字顶部对齐。

### 4.4 铺满画面的大字排版并倾斜

```json
{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
  "placement": "fixed",
  "scaleMode": "fill",
  "baselineRotateDeg": -6
}
```

`scaleMode: "fill"` 会把整句歌词放大到铺满画面宽度，因此文字大小会随字符数量变化。

### 4.5 散布在四角形成错落

```json
{
  "writing": "horizontal",
  "anchors": [
    { "x": 0.2, "y": 0.25, "align": "start" },
    { "x": 0.8, "y": 0.4, "align": "end" },
    { "x": 0.25, "y": 0.62, "align": "start" },
    { "x": 0.75, "y": 0.8, "align": "end" }
  ],
  "placement": "random",
  "scatter": { "offsetRatio": 0.3, "rotateDeg": 12 },
  "charScaleRange": [0.85, 1.3]
}
```

边缘的锚点分别用 `align` 的 `start` / `end` 来配合，避免歌词溢出画面。

### 4.6 波浪排列并突出一个单词

```json
{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
  "placement": "fixed",
  "path": { "kind": "wave", "amplitude": 0.35, "period": 6 },
  "focusWord": { "scale": 1.5, "pick": "longest" }
}
```

---

## 5. 供 AI 使用的提示词模板

请先贴上本文档全文，然后继续贴上以下内容。

**以下为复制起点**

```text
你是 movlyric 的歌词布局设计师。请严格遵循上面贴出的“LayoutSpec 规范文档”，创建一个 LayoutSpec 的 JSON。

输出规则:
- 只输出 JSON（不要写说明、前言或结语；可以用代码围栏包裹 JSON）
- 根节点只能写 writing, anchors, placement, maxLines, lineGap, scaleMode, scatter, baselineRotateDeg, path, charScaleRange, focusWord
- writing, anchors, placement 为必需项，其余只在需要时书写
- **不要写与默认值相同的键**（maxLines:1 / lineGap:0.15 / scaleMode:"fixed"）
- writing 为 "horizontal" / "vertical" 之一
- anchors 为 1～8 个，每个元素为 {"x":0..1, "y":0..1, "align":"start"|"center"|"end"}
- x, y 是**相对画面的比例**（不是像素），0.5 为居中
- 想贴靠画面边缘时，将 align 设为 start / end（保持 center 会溢出）
- placement 为 "fixed" / "cycle" / "random"。anchors 只有 1 个时应设为 "fixed"
- scatter 为 {"offsetRatio":0以上, "rotateDeg":0以上}
- path 为 {"kind":"wave"|"arc"|"steps", "amplitude":数值, "period":大于0（可选）}
- charScaleRange 为 [最小值, 最大值] 两个元素
- focusWord 为 {"scale":大于0, "pick":"longest"|"random"}
- **writing 为 "vertical" 时不要写 path / charScaleRange / focusWord**（会被忽略）
- 不能写随时间、音频、随机数变化的值或数学表达式（运动由另一套规范 AnimationSpec 负责）
- 不要写不存在的属性、注释、末尾逗号

想要的歌词布局:
{在此写下你想要的布局}

（仅在想修改已有布局时，请在下方粘贴当前的 JSON。若是新建，请删除这一整节）
当前 JSON:
{当前 JSON}
```

**复制到此为止**
