Commit d196ee0
2
files
+58
added
-0
deleted
@@ -174,6 +174,14 @@ impl SoftwareRenderer {
174
174
}
175
175
DrawCommand::Text(run) => {
176
176
let (w, h) = (pixmap.width() as usize, pixmap.height() as usize);
177
+ // Tekst nie korzysta z maski kształtów — obcinamy wprost piksele.
178
+ let k = scale.factor();
179
+ let clip = clip_stack.last().map(|r| crate::text::ClipRect {
180
+ x0: (r.x() * k).floor() as i64,
181
+ y0: (r.y() * k).floor() as i64,
182
+ x1: (r.right() * k).ceil() as i64,
183
+ y1: (r.bottom() * k).ceil() as i64,
184
+ });
177
185
text.draw(
178
186
pixmap.data_mut(),
179
187
w,
@@ -185,6 +193,7 @@ impl SoftwareRenderer {
185
193
run.weight,
186
194
run.color,
187
195
scale.factor(),
196
+ clip,
188
197
);
189
198
}
190
199
DrawCommand::Image { rect, data } => {
@@ -87,6 +87,8 @@ impl TextEngine {
87
87
/// Rysuje tekst do bufora RGBA8888 **premnożonego** (format tiny-skia).
88
88
///
89
89
/// `origin` w pikselach fizycznych, `size` skalowana przez `scale`.
90
+ /// `clip` ogranicza rysowanie do prostokąta (piksele fizyczne) — używane
91
+ /// przy aktywnym `Scene::push_clip`.
90
92
#[allow(clippy::too_many_arguments)]
91
93
pub fn draw(
92
94
&self,
@@ -100,6 +102,7 @@ impl TextEngine {
100
102
weight: u16,
101
103
color: Color,
102
104
scale: f64,
105
+ clip: Option<ClipRect>,
103
106
) {
104
107
let Some(font) = self.font_for(weight) else {
105
108
return;
@@ -130,6 +133,7 @@ impl TextEngine {
130
133
g,
131
134
b,
132
135
alpha,
136
+ clip,
133
137
);
134
138
}
135
139
pen_x += metrics.advance_width as f64;
@@ -150,6 +154,15 @@ fn load_font(path: &str) -> Option<Font> {
150
154
Font::from_bytes(bytes, FontSettings::default()).ok()
151
155
}
152
156
157
+/// Prostokąt obcinania w pikselach fizycznych (półotwarty: `x0..x1`, `y0..y1`).
158
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159
+pub struct ClipRect {
160
+ pub x0: i64,
161
+ pub y0: i64,
162
+ pub x1: i64,
163
+ pub y1: i64,
164
+}
165
+
153
166
/// Nakłada bitmapę krycia glifu na bufor premnożony (operacja „over”).
154
167
#[allow(clippy::too_many_arguments)]
155
168
fn blend_glyph(
@@ -165,17 +178,28 @@ fn blend_glyph(
165
178
sg: f32,
166
179
sb: f32,
167
180
sa: f32,
181
+ clip: Option<ClipRect>,
168
182
) {
169
183
for row in 0..gh {
170
184
let y = gy + row as i64;
171
185
if y < 0 || y >= buf_h as i64 {
172
186
continue;
173
187
}
188
+ if let Some(c) = clip {
189
+ if y < c.y0 || y >= c.y1 {
190
+ continue;
191
+ }
192
+ }
174
193
for col in 0..gw {
175
194
let x = gx + col as i64;
176
195
if x < 0 || x >= buf_w as i64 {
177
196
continue;
178
197
}
198
+ if let Some(c) = clip {
199
+ if x < c.x0 || x >= c.x1 {
200
+ continue;
201
+ }
202
+ }
179
203
let cov = coverage[row * gw + col] as f32 / 255.0;
180
204
if cov <= 0.0 {
181
205
continue;
@@ -228,4 +252,29 @@ mod tests {
228
252
let s = engine.measure("AG", 14.0, weight::NORMAL);
229
253
assert!(s.width > 0.0);
230
254
}
255
+
256
+ #[test]
257
+ fn blend_glyph_respects_clip() {
258
+ // Białe 4×2, pełne krycie glifu; czerwony glif obcięty do kolumn 0..2.
259
+ let (w, h) = (4usize, 2usize);
260
+ let mut buf = vec![255u8; w * h * 4];
261
+ let cov = vec![255u8; w * h];
262
+ let clip = Some(ClipRect {
263
+ x0: 0,
264
+ y0: 0,
265
+ x1: 2,
266
+ y1: 2,
267
+ });
268
+ blend_glyph(&mut buf, w, h, 0, 0, w, h, &cov, 1.0, 0.0, 0.0, 1.0, clip);
269
+ let px = |x: usize, y: usize| {
270
+ let i = (y * w + x) * 4;
271
+ (buf[i], buf[i + 1], buf[i + 2], buf[i + 3])
272
+ };
273
+ // W klipie: czerwony, pełne krycie.
274
+ assert_eq!(px(0, 0), (255, 0, 0, 255));
275
+ assert_eq!(px(1, 1), (255, 0, 0, 255));
276
+ // Poza klipem: nietknięte białe tło.
277
+ assert_eq!(px(2, 0), (255, 255, 255, 255));
278
+ assert_eq!(px(3, 1), (255, 255, 255, 255));
279
+ }
231
280
}