← luna

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 }