Skip to content

Commit 8609c91

Browse files
committed
feat(layout): 重构布局构建逻辑以简化节点转换和增强可读性
- 移除冗余的转换函数,直接在布局构建中实现节点转换,提升代码的清晰度和可维护性。 - 更新 SNLayoutBuilder 中的构建方法,确保节点的宽度和高度计算逻辑更加直观。 - 增强对 Measure、Page、Score 和 Section 节点的处理,确保布局的动态调整能力。 - 更新文档注释,提升代码可读性,帮助开发者更好地理解布局计算的流程。 该变更提升了布局构建的灵活性和可维护性,确保元素在视觉上更加协调。
1 parent 36fd54b commit 8609c91

17 files changed

+640
-687
lines changed

packages/simple-notation/src/layout/builder.ts

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { SNParserRoot } from '@data/node';
2-
import type { SNLayoutRoot } from '@layout/node';
2+
import { SNLayoutRoot } from '@layout/node';
33
import { LayoutConfig, ScoreConfig } from '@manager/config';
4-
import { transformRoot } from './trans';
54
import { buildPages, buildScores, finalizeNodeLayout } from './builder/index';
65

76
/**
@@ -51,7 +50,7 @@ export class SNLayoutBuilder {
5150
containerSize?: { width: number; height: number },
5251
): SNLayoutRoot {
5352
// 先创建 Root 节点(宽度在 transformRoot 中已设置)
54-
const root = transformRoot(dataTree, this.layoutConfig, containerSize);
53+
const root = this.transformRoot(dataTree, containerSize);
5554

5655
// 获取页面配置
5756
const pageConfig = this.layoutConfig.getPage();
@@ -87,4 +86,69 @@ export class SNLayoutBuilder {
8786
getLayoutTree(): SNLayoutRoot {
8887
return this.layoutTree;
8988
}
89+
90+
/**
91+
* 转换 Root 节点
92+
* @param root - 数据层 Root 节点
93+
* @param containerSize - 容器尺寸
94+
*/
95+
private transformRoot(
96+
root: SNParserRoot,
97+
containerSize?: { width: number; height: number },
98+
): SNLayoutRoot {
99+
const layoutRoot = new SNLayoutRoot(`layout-${root.id}`);
100+
layoutRoot.data = root;
101+
102+
const globalConfig = this.layoutConfig.getGlobal();
103+
const pageConfig = this.layoutConfig.getPage();
104+
105+
// 计算画布尺寸
106+
let canvasWidth: number | null = null;
107+
let canvasHeight: number | null = null;
108+
109+
const globalWidth = globalConfig.size.width;
110+
if (globalWidth !== null && typeof globalWidth === 'number') {
111+
canvasWidth = globalWidth;
112+
} else if (containerSize) {
113+
canvasWidth = containerSize.width;
114+
}
115+
116+
const globalHeight = globalConfig.size.height;
117+
if (globalHeight !== null && typeof globalHeight === 'number') {
118+
canvasHeight = globalHeight;
119+
} else if (containerSize && globalConfig.size.autoHeight) {
120+
canvasHeight = null;
121+
} else if (containerSize) {
122+
canvasHeight = containerSize.height;
123+
}
124+
125+
// 启用分页时使用页面尺寸,否则使用画布尺寸
126+
const effectiveWidth = pageConfig.enable
127+
? pageConfig.size.width
128+
: canvasWidth;
129+
130+
const globalPadding = globalConfig.spacing.padding ?? {
131+
top: 0,
132+
right: 0,
133+
bottom: 0,
134+
left: 0,
135+
};
136+
const globalMargin = globalConfig.spacing.margin ?? {
137+
top: 0,
138+
right: 0,
139+
bottom: 0,
140+
left: 0,
141+
};
142+
143+
layoutRoot.updateLayout({
144+
x: 0,
145+
y: 0,
146+
width: effectiveWidth ?? 0, // 0表示撑满容器
147+
height: canvasHeight ?? 0, // 由布局计算根据内容撑开
148+
padding: globalPadding,
149+
margin: globalMargin,
150+
});
151+
152+
return layoutRoot;
153+
}
90154
}

packages/simple-notation/src/layout/builder/build-element-children.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { SNParserNode } from '@data/node';
22
import type { SNParserLyric } from '@data/node';
3-
import type { SNLayoutElement } from '@layout/node';
3+
import { SNLayoutElement } from '@layout/node';
44
import { ScoreConfig } from '@manager/config';
5-
import { transformMeasureElement } from '../trans';
65
import { calculateNodeHeight } from './calculate-height';
76

87
/**
@@ -50,7 +49,7 @@ export function buildElementChildren(
5049
// 对每个 verseNumber,可能有多个歌词(如 multi-word 的情况)
5150
// 这里我们为每个歌词创建一个布局元素
5251
for (const lyric of verseLyrics) {
53-
// 使用 transformMeasureElement 转换歌词元素
52+
// 转换歌词元素
5453
const lyricLayoutElement = transformMeasureElement(
5554
lyric,
5655
scoreConfig,
@@ -112,3 +111,44 @@ export function buildElementChildren(
112111
// 确保父节点高度包含所有歌词
113112
calculateNodeHeight(parentLayoutElement);
114113
}
114+
115+
/**
116+
* 转换 Measure 内部的元素(Note/Rest/Lyric等)
117+
* @param element - 数据层元素节点
118+
* @param scoreConfig - 乐谱配置
119+
* @param parentNode - 父布局节点(Element)
120+
*/
121+
function transformMeasureElement(
122+
element: SNParserNode,
123+
_scoreConfig: ScoreConfig,
124+
parentNode: SNLayoutElement,
125+
): SNLayoutElement | null {
126+
const layoutElement = new SNLayoutElement(`layout-${element.id}`);
127+
layoutElement.data = element;
128+
129+
// 根据元素类型设置不同的宽度和高度
130+
let elementWidth = 20;
131+
let elementHeight = 0;
132+
if (element.type === 'note') {
133+
elementWidth = 30;
134+
} else if (element.type === 'rest') {
135+
elementWidth = 25;
136+
} else if (element.type === 'lyric') {
137+
elementWidth = 40;
138+
elementHeight = 14; // 歌词固定高度(对应字体大小 14px)
139+
} else if (element.type === 'tuplet') {
140+
elementWidth = 50; // 连音可能包含多个音符
141+
} else if (element.type === 'tie') {
142+
elementWidth = 40; // 连音线默认更宽一些
143+
}
144+
145+
layoutElement.updateLayout({
146+
x: 0,
147+
y: 0,
148+
width: elementWidth,
149+
height: elementHeight, // 歌词有固定高度,其他元素自适应
150+
});
151+
152+
parentNode.addChildren(layoutElement);
153+
return layoutElement;
154+
}

packages/simple-notation/src/layout/builder/build-measure-elements.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { SNParserNode } from '@data/node';
2-
import type { SNLayoutElement } from '@layout/node';
2+
import { SNLayoutElement } from '@layout/node';
33
import { ScoreConfig } from '@manager/config';
44
import { getTimeUnitFromNode, measureDuration } from '@core/utils/time-unit';
5-
import { transformMeasureElement } from '../trans';
65
import { buildElementChildren } from './build-element-children';
76
import { calculateNodeHeight } from './calculate-height';
87

@@ -58,7 +57,7 @@ export function buildMeasureElements(
5857
const dataElement = elements[i];
5958
const elementDuration = dataElement.duration || 0;
6059

61-
// 使用 transformMeasureElement 转换元素
60+
// 转换元素
6261
const layoutElement = transformMeasureElement(
6362
dataElement,
6463
scoreConfig,
@@ -143,3 +142,44 @@ export function buildMeasureElements(
143142
calculateNodeHeight(parentNode);
144143
}
145144
}
145+
146+
/**
147+
* 转换 Measure 内部的元素(Note/Rest/Lyric等)
148+
* @param element - 数据层元素节点
149+
* @param scoreConfig - 乐谱配置
150+
* @param parentNode - 父布局节点(Element)
151+
*/
152+
function transformMeasureElement(
153+
element: SNParserNode,
154+
_scoreConfig: ScoreConfig,
155+
parentNode: SNLayoutElement,
156+
): SNLayoutElement | null {
157+
const layoutElement = new SNLayoutElement(`layout-${element.id}`);
158+
layoutElement.data = element;
159+
160+
// 根据元素类型设置不同的宽度和高度
161+
let elementWidth = 20;
162+
let elementHeight = 0;
163+
if (element.type === 'note') {
164+
elementWidth = 30;
165+
} else if (element.type === 'rest') {
166+
elementWidth = 25;
167+
} else if (element.type === 'lyric') {
168+
elementWidth = 40;
169+
elementHeight = 14; // 歌词固定高度(对应字体大小 14px)
170+
} else if (element.type === 'tuplet') {
171+
elementWidth = 50; // 连音可能包含多个音符
172+
} else if (element.type === 'tie') {
173+
elementWidth = 40; // 连音线默认更宽一些
174+
}
175+
176+
layoutElement.updateLayout({
177+
x: 0,
178+
y: 0,
179+
width: elementWidth,
180+
height: elementHeight, // 歌词有固定高度,其他元素自适应
181+
});
182+
183+
parentNode.addChildren(layoutElement);
184+
return layoutElement;
185+
}

packages/simple-notation/src/layout/builder/build-measures.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { SNParserNode } from '@data/node';
2+
import { SNLayoutElement } from '@layout/node';
23
import type { SNLayoutLine } from '@layout/node';
34
import { ScoreConfig } from '@manager/config';
4-
import { transformMeasure } from '../trans';
55
import { buildMeasureElements } from './build-measure-elements';
66
import { calculateNodeHeight } from './calculate-height';
77
import { computeMeasureWidthByTicks } from './utils';
@@ -45,7 +45,7 @@ export function buildMeasures(
4545
// 4. 构建每个小节并设置宽度
4646
for (let i = 0; i < measures.length; i++) {
4747
const measure = measures[i];
48-
// 使用 transformMeasure 转换 Measure 为 Element
48+
// 转换 Measure 为 Element
4949
const element = transformMeasure(measure, scoreConfig, parentNode);
5050

5151
if (!element) continue;
@@ -70,3 +70,33 @@ export function buildMeasures(
7070
// (buildMeasureElements 中已经计算了内部元素的高度,并更新了 Measure Element 的高度)
7171
calculateNodeHeight(parentNode);
7272
}
73+
74+
/**
75+
* 转换 Measure 节点
76+
* @param measure - 数据层 Measure 节点
77+
* @param scoreConfig - 乐谱配置
78+
* @param parentNode - 父布局节点(Line)
79+
*/
80+
function transformMeasure(
81+
measure: SNParserNode,
82+
_scoreConfig: ScoreConfig,
83+
parentNode: SNLayoutLine,
84+
): SNLayoutElement | null {
85+
if (measure.type !== 'measure') {
86+
return null;
87+
}
88+
89+
const element = new SNLayoutElement(`layout-${measure.id}`);
90+
element.data = measure;
91+
92+
element.updateLayout({
93+
x: 0,
94+
y: 0,
95+
width: 100, // 临时值,后续会根据实际音符宽度计算
96+
height: 0, // 自适应行高
97+
margin: { top: 0, right: 0, bottom: 0, left: 0 },
98+
});
99+
100+
parentNode.addChildren(element);
101+
return element;
102+
}

packages/simple-notation/src/layout/builder/build-pages.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { SNParserScore } from '@data/node';
2+
import { SNLayoutPage } from '@layout/node';
23
import type { SNLayoutRoot } from '@layout/node';
34
import { LayoutConfig, ScoreConfig } from '@manager/config';
4-
import { transformPage } from '../trans';
55
import { buildScores } from './build-scores';
66
import { finalizeNodeLayout } from './finalize-node-layout';
77

@@ -23,7 +23,7 @@ export function buildPages(
2323
scoreConfig: ScoreConfig,
2424
): void {
2525
for (const score of scores) {
26-
// 使用 transformPage 转换 Score 为 Page
26+
// 转换 Score 为 Page
2727
// Page 的宽度在 transformPage 中已经设置(基于配置)
2828
const page = transformPage(score, layoutConfig, parentNode);
2929

@@ -35,3 +35,35 @@ export function buildPages(
3535
finalizeNodeLayout(page);
3636
}
3737
}
38+
39+
/**
40+
* 转换 Score 节点为 Page 节点
41+
* @param score - 数据层 Score 节点
42+
* @param layoutConfig - 布局配置
43+
* @param parentNode - 父布局节点(Root)
44+
*/
45+
function transformPage(
46+
score: SNParserScore,
47+
layoutConfig: LayoutConfig,
48+
parentNode: SNLayoutRoot,
49+
): SNLayoutPage {
50+
const pageConfig = layoutConfig.getPage();
51+
const page = new SNLayoutPage(`layout-${score.id}`);
52+
page.data = score;
53+
54+
const pageSize = pageConfig.size;
55+
const pageMargin = pageConfig.spacing.margin;
56+
const pagePadding = pageConfig.spacing.padding;
57+
58+
page.updateLayout({
59+
x: 0,
60+
y: 0,
61+
width: pageSize.width - pageMargin.left - pageMargin.right,
62+
height: pageSize.height - pageMargin.top - pageMargin.bottom,
63+
padding: pagePadding,
64+
margin: pageMargin,
65+
});
66+
67+
parentNode.addChildren(page);
68+
return page;
69+
}

0 commit comments

Comments
 (0)