1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//! Per-segment positioning for the software pipeline: anchoring lines, applying
//! alignment offsets, and advancing the line pen.
use super::types::{LineLayout, Pen, RunCtx};
use crate::pipeline::shaping::ShapedText;
use crate::pipeline::tag_processor::ProcessedTags;
impl super::super::SoftwarePipeline {
/// Compute the top-left render position of a segment, advancing the line's
/// pen and seeding it on the first segment of the line.
pub(super) fn position_segment(
&self,
tags: &ProcessedTags,
shaped: &ShapedText,
layout: &LineLayout,
pen: &mut Pen,
ctx: &RunCtx,
default_alignment: u8,
) -> (f32, f32) {
let event = ctx.event;
let context = ctx.context;
let time_cs = ctx.time_cs;
let is_multi_segment = layout.is_multi_segment;
let line_total_width = layout.line_total_width;
let num_lines = layout.num_lines;
let line_index = layout.line_index;
let estimated_line_height = layout.estimated_line_height;
let line_spacing_multiplier = layout.line_spacing_multiplier;
let line_y_offset = layout.line_y_offset;
if tags.position.is_some() || tags.movement.is_some() {
// Explicit \pos/\move override (inherited by every segment of the
// line). The first segment anchors the whole line — centred on the
// full line width for multi-segment lines — and seeds current_x;
// later segments continue left-to-right from current_x instead of
// each re-centering on the same point (which stacked karaoke and
// colour-split segments on top of one another).
let (anchor_x, anchor_y) =
self.calculate_position_from_tags(tags, event, context, time_cs, default_alignment);
let alignment = tags.formatting.alignment.unwrap_or(default_alignment);
if pen.needs_initial_position {
let line_width = if is_multi_segment {
line_total_width
} else {
shaped.width
};
let (x, y) = self.apply_alignment_offset(
anchor_x,
anchor_y,
line_width,
shaped.height,
alignment,
);
pen.current_x = x;
pen.needs_initial_position = false;
(x, y)
} else {
let (_, y) = self.apply_alignment_offset(
anchor_x,
anchor_y,
shaped.width,
shaped.height,
alignment,
);
(pen.current_x, y)
}
} else if pen.needs_initial_position {
// First segment of the line: position the whole line. Later
// segments continue from current_x (below) so they lay out
// left-to-right instead of each re-centering.
let alignment = tags.formatting.alignment.unwrap_or(default_alignment);
let (anchor_x, anchor_y) =
self.calculate_position_from_alignment(alignment, event, context);
// For multi-line text, adjust positioning based on alignment
let adjusted_y = if num_lines > 1 {
match alignment {
1..=3 => {
// Bottom alignment: stack lines upward from bottom
// First line is at the bottom, subsequent lines above it
let line_offset = (num_lines - 1 - line_index) as f32
* estimated_line_height
* line_spacing_multiplier;
anchor_y - line_offset
}
7..=9 => {
// Top alignment: stack lines downward from top
anchor_y + line_y_offset
}
_ => {
// Middle alignment: center the block vertically
let block_offset = -((num_lines as f32 - 1.0)
* estimated_line_height
* line_spacing_multiplier
/ 2.0);
anchor_y + block_offset + line_y_offset
}
}
} else {
anchor_y
};
// For multi-line text, use the line height for vertical positioning
// to ensure proper spacing between lines
let height_for_positioning = if num_lines > 1 {
estimated_line_height
} else {
shaped.height
};
// Multi-segment lines centre on the whole-line width; single
// segments use their own shaped width (reflects \t animations).
let line_width = if is_multi_segment {
line_total_width
} else {
shaped.width
};
let (x, y) = self.apply_alignment_offset(
anchor_x,
adjusted_y,
line_width,
height_for_positioning,
alignment,
);
// Start the pen at the line's left edge; the post-draw advance
// below moves current_x past each segment (centering uses the
// whole line's width so multi-segment lines align as a unit).
pen.current_x = x;
pen.needs_initial_position = false;
(x, y)
} else {
// Continue from previous segment position
// Need to maintain the absolute Y position from the previous segment
// For now, calculate it based on alignment and line offset
let alignment = tags.formatting.alignment.unwrap_or(default_alignment);
let (anchor_x, anchor_y) =
self.calculate_position_from_alignment(alignment, event, context);
let adjusted_y = if num_lines > 1 {
match alignment {
1..=3 => {
// Bottom alignment: stack lines upward from bottom
let line_offset = (num_lines - 1 - line_index) as f32
* estimated_line_height
* line_spacing_multiplier;
anchor_y - line_offset
}
7..=9 => {
// Top alignment: stack lines downward from top
anchor_y + line_y_offset
}
_ => {
// Middle alignment: center the block vertically
let block_offset = -((num_lines as f32 - 1.0)
* estimated_line_height
* line_spacing_multiplier
/ 2.0);
anchor_y + block_offset + line_y_offset
}
}
} else {
anchor_y
};
// For multi-line text, use the line height for vertical positioning
let height_for_positioning = if num_lines > 1 {
estimated_line_height
} else {
shaped.height
};
let (_, y) = self.apply_alignment_offset(
anchor_x,
adjusted_y,
shaped.width,
height_for_positioning,
alignment,
);
(pen.current_x, y)
}
}
}