Skip to main content

atmos/os_lib/
canvas2d.rs

1//! Canvas 2D の変換行列とパス幾何(純粋モジュール)。
2//!
3//! `<canvas>` の 2D コンテキストは、現状 API 表面だけが存在し
4//! 全メソッドが `dom_noop` へ接続されている(=一切描画されない)。
5//! 実描画を入れる前提として、ラスタライズに依存しない部分——
6//! **アフィン変換の合成**と**パスの折れ線化**——をここへ集約する。
7//!
8//! ハードウェアにもグローバル状態にも依存しないので、ホスト側で検証できる。
9//!
10//! # 座標系
11//!
12//! 変換行列は CSS/Canvas と同じ `[a b c d e f]` の並びで、点 (x, y) は
13//!
14//! ```text
15//! x' = a*x + c*y + e
16//! y' = b*x + d*y + f
17//! ```
18//!
19//! へ移る。`transform()` は**現在の行列へ右から掛ける**(後から積んだ変換が
20//! 点に対して先に効く)。この順序を取り違えると `translate` してから
21//! `rotate` した図形が原点回りに回ってしまうので、試験で固定する。
22
23extern crate alloc;
24use alloc::vec::Vec;
25
26/// アフィン変換行列 `[a b c d e f]`。
27#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct Matrix {
29    pub a: f32,
30    pub b: f32,
31    pub c: f32,
32    pub d: f32,
33    pub e: f32,
34    pub f: f32,
35}
36
37impl Default for Matrix {
38    fn default() -> Self {
39        Self::identity()
40    }
41}
42
43impl Matrix {
44    pub fn identity() -> Self {
45        Matrix { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
46    }
47
48    pub fn translate(tx: f32, ty: f32) -> Self {
49        Matrix { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: tx, f: ty }
50    }
51
52    pub fn scale(sx: f32, sy: f32) -> Self {
53        Matrix { a: sx, b: 0.0, c: 0.0, d: sy, e: 0.0, f: 0.0 }
54    }
55
56    /// 角度はラジアン。正の角度で「x 軸から y 軸へ」回る(Canvas の既定は
57    /// y 軸が下向きなので、画面上は時計回りに見える)。
58    pub fn rotate(rad: f32) -> Self {
59        let (s, c) = (libm::sinf(rad), libm::cosf(rad));
60        Matrix { a: c, b: s, c: -s, d: c, e: 0.0, f: 0.0 }
61    }
62
63    /// `self` に `m` を**右から**掛ける(`self * m`)。
64    ///
65    /// Canvas の `ctx.transform(...)` の意味論。後から積んだ変換ほど
66    /// 点に対して先に効く。
67    pub fn multiply(&self, m: &Matrix) -> Matrix {
68        Matrix {
69            a: self.a * m.a + self.c * m.b,
70            b: self.b * m.a + self.d * m.b,
71            c: self.a * m.c + self.c * m.d,
72            d: self.b * m.c + self.d * m.d,
73            e: self.a * m.e + self.c * m.f + self.e,
74            f: self.b * m.e + self.d * m.f + self.f,
75        }
76    }
77
78    /// 点を変換する。
79    pub fn apply(&self, x: f32, y: f32) -> (f32, f32) {
80        (
81            self.a * x + self.c * y + self.e,
82            self.b * x + self.d * y + self.f,
83        )
84    }
85
86    /// 行列式。0 なら退化(逆行列を持たない)。
87    pub fn determinant(&self) -> f32 {
88        self.a * self.d - self.b * self.c
89    }
90
91    /// 逆行列。退化していれば `None`(呼び出し側は変換を諦める)。
92    ///
93    /// `isPointInPath` のようにデバイス座標をユーザー座標へ戻す用途で要る。
94    pub fn invert(&self) -> Option<Matrix> {
95        let det = self.determinant();
96        if !det.is_finite() || libm::fabsf(det) < 1e-12 {
97            return None;
98        }
99        let inv = 1.0 / det;
100        Some(Matrix {
101            a: self.d * inv,
102            b: -self.b * inv,
103            c: -self.c * inv,
104            d: self.a * inv,
105            e: (self.c * self.f - self.d * self.e) * inv,
106            f: (self.b * self.e - self.a * self.f) * inv,
107        })
108    }
109}
110
111/// パスを構成する副パス(連続した折れ線)。
112#[derive(Debug, Clone, PartialEq)]
113pub struct SubPath {
114    /// 折れ線の頂点(変換適用済みのデバイス座標)。
115    pub points: Vec<(f32, f32)>,
116    /// `closePath()` で閉じられたか。
117    pub closed: bool,
118}
119
120/// パス構築器。曲線は折れ線へ分割して保持する。
121///
122/// ラスタライザは「折れ線の集まり」だけを相手にすればよくなるので、
123/// 曲線の分割規則をここへ閉じ込めて試験できる。
124#[derive(Debug, Clone, Default)]
125pub struct PathBuilder {
126    subpaths: Vec<SubPath>,
127    current: Option<SubPath>,
128    /// 直近の点(**変換前**のユーザー座標)。曲線の始点に使う。
129    last_user: Option<(f32, f32)>,
130}
131
132/// 円弧を折れ線化するときの、90 度あたりの分割数。
133const ARC_SEGMENTS_PER_QUARTER: usize = 8;
134/// ベジェ 1 本あたりの分割数。
135const BEZIER_SEGMENTS: usize = 16;
136
137impl PathBuilder {
138    pub fn new() -> Self {
139        Self::default()
140    }
141
142    /// 現在の副パスを確定して次へ移る。
143    ///
144    /// 点が 1 個以下の副パスは描画に寄与しないので捨てる
145    /// (`moveTo` だけして何も引かなかった場合など)。
146    fn flush(&mut self) {
147        if let Some(sp) = self.current.take() {
148            if sp.points.len() >= 2 {
149                self.subpaths.push(sp);
150            }
151        }
152    }
153
154    pub fn begin_path(&mut self) {
155        self.subpaths.clear();
156        self.current = None;
157        self.last_user = None;
158    }
159
160    pub fn move_to(&mut self, m: &Matrix, x: f32, y: f32) {
161        self.flush();
162        self.current = Some(SubPath {
163            points: alloc::vec![m.apply(x, y)],
164            closed: false,
165        });
166        self.last_user = Some((x, y));
167    }
168
169    pub fn line_to(&mut self, m: &Matrix, x: f32, y: f32) {
170        // `moveTo` 無しの `lineTo` は、仕様上その点への `moveTo` と同じ扱い。
171        if self.current.is_none() {
172            self.move_to(m, x, y);
173            return;
174        }
175        if let Some(sp) = self.current.as_mut() {
176            sp.points.push(m.apply(x, y));
177        }
178        self.last_user = Some((x, y));
179    }
180
181    pub fn close_path(&mut self) {
182        if let Some(sp) = self.current.as_mut() {
183            sp.closed = true;
184        }
185        self.flush();
186    }
187
188    pub fn add_path(&mut self, other: &PathBuilder, transform: Option<&Matrix>) {
189        self.flush();
190        let id_mat = Matrix::identity();
191        let m = transform.unwrap_or(&id_mat);
192        for sp in &other.subpaths {
193            let pts = sp.points.iter().map(|p| m.apply(p.0, p.1)).collect();
194            self.subpaths.push(SubPath {
195                points: pts,
196                closed: sp.closed,
197            });
198        }
199        if let Some(ref sp) = other.current {
200            if sp.points.len() >= 2 {
201                let pts = sp.points.iter().map(|p| m.apply(p.0, p.1)).collect();
202                self.subpaths.push(SubPath {
203                    points: pts,
204                    closed: sp.closed,
205                });
206            }
207        }
208        if let Some((lx, ly)) = other.last_user {
209            let lp = m.apply(lx, ly);
210            self.last_user = Some((lp.0, lp.1));
211        }
212    }
213
214    /// 3 次ベジェ。始点は直近の点。
215    pub fn bezier_curve_to(
216        &mut self,
217        m: &Matrix,
218        c1x: f32,
219        c1y: f32,
220        c2x: f32,
221        c2y: f32,
222        x: f32,
223        y: f32,
224    ) {
225        let (x0, y0) = match self.last_user {
226            Some(p) => p,
227            None => {
228                self.move_to(m, c1x, c1y);
229                (c1x, c1y)
230            }
231        };
232        for i in 1..=BEZIER_SEGMENTS {
233            let t = i as f32 / BEZIER_SEGMENTS as f32;
234            let mt = 1.0 - t;
235            let px = mt * mt * mt * x0
236                + 3.0 * mt * mt * t * c1x
237                + 3.0 * mt * t * t * c2x
238                + t * t * t * x;
239            let py = mt * mt * mt * y0
240                + 3.0 * mt * mt * t * c1y
241                + 3.0 * mt * t * t * c2y
242                + t * t * t * y;
243            self.line_to(m, px, py);
244        }
245        self.last_user = Some((x, y));
246    }
247
248    /// 2 次ベジェ。3 次へ昇格させて処理する。
249    pub fn quadratic_curve_to(&mut self, m: &Matrix, cx: f32, cy: f32, x: f32, y: f32) {
250        let (x0, y0) = match self.last_user {
251            Some(p) => p,
252            None => {
253                self.move_to(m, cx, cy);
254                (cx, cy)
255            }
256        };
257        // 2 次 → 3 次の標準的な昇格。
258        let c1x = x0 + 2.0 / 3.0 * (cx - x0);
259        let c1y = y0 + 2.0 / 3.0 * (cy - y0);
260        let c2x = x + 2.0 / 3.0 * (cx - x);
261        let c2y = y + 2.0 / 3.0 * (cy - y);
262        self.bezier_curve_to(m, c1x, c1y, c2x, c2y, x, y);
263    }
264
265    /// 円弧。`anticlockwise` が真なら反時計回り。
266    pub fn arc(
267        &mut self,
268        m: &Matrix,
269        cx: f32,
270        cy: f32,
271        r: f32,
272        start: f32,
273        end: f32,
274        anticlockwise: bool,
275    ) {
276        // 負の半径・非有限値は仕様上エラーだが、ここでは黙って無視する
277        // (描画系でパニックさせない方が安全)。
278        if !r.is_finite() || r < 0.0 || !start.is_finite() || !end.is_finite() {
279            return;
280        }
281        let sweep = normalize_sweep(start, end, anticlockwise);
282        let steps = arc_steps(sweep);
283        for i in 0..=steps {
284            let t = i as f32 / steps as f32;
285            let ang = start + sweep * t;
286            let px = cx + r * libm::cosf(ang);
287            let py = cy + r * libm::sinf(ang);
288            if i == 0 && self.current.is_none() {
289                self.move_to(m, px, py);
290            } else {
291                self.line_to(m, px, py);
292            }
293        }
294    }
295
296    /// 矩形。独立した閉じた副パスになる。
297    pub fn rect(&mut self, m: &Matrix, x: f32, y: f32, w: f32, h: f32) {
298        self.flush();
299        self.current = Some(SubPath {
300            points: alloc::vec![
301                m.apply(x, y),
302                m.apply(x + w, y),
303                m.apply(x + w, y + h),
304                m.apply(x, y + h),
305            ],
306            closed: true,
307        });
308        self.flush();
309        self.last_user = Some((x, y));
310    }
311
312    /// 構築済みの副パス一覧を返す(未確定の副パスも含める)。
313    pub fn finish(&self) -> Vec<SubPath> {
314        let mut out = self.subpaths.clone();
315        if let Some(sp) = &self.current {
316            if sp.points.len() >= 2 {
317                out.push(sp.clone());
318            }
319        }
320        out
321    }
322}
323
324/// 円弧の掃引角を求める。
325///
326/// Canvas の仕様では、時計回り指定で `end < start` のときは 2π を足し、
327/// 反時計回り指定で `end > start` のときは 2π を引く。
328/// 差が 2π を超える場合は 1 周に丸める(分割数が発散しないように)。
329pub fn normalize_sweep(start: f32, end: f32, anticlockwise: bool) -> f32 {
330    let tau = core::f32::consts::PI * 2.0;
331    let mut sweep = end - start;
332    if anticlockwise {
333        if sweep > 0.0 {
334            sweep -= tau;
335        }
336        if sweep < -tau {
337            sweep = -tau;
338        }
339    } else {
340        if sweep < 0.0 {
341            sweep += tau;
342        }
343        if sweep > tau {
344            sweep = tau;
345        }
346    }
347    sweep
348}
349
350/// 掃引角に応じた分割数(最低 1)。
351pub fn arc_steps(sweep: f32) -> usize {
352    let quarters = libm::fabsf(sweep) / (core::f32::consts::PI / 2.0);
353    let n = (quarters * ARC_SEGMENTS_PER_QUARTER as f32) as usize;
354    n.max(1)
355}
356
357// ───────────── ラスタライズ(走査線) ─────────────
358
359/// 塗りつぶし規則。
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub enum FillRule {
362    /// `nonzero`(既定)。巻き数が 0 でなければ内側。
363    NonZero,
364    /// `evenodd`。交差回数が奇数なら内側。
365    EvenOdd,
366}
367
368/// 走査線 `y` とパスの交点を求め、塗るべき x 区間を返す(純粋関数)。
369///
370/// 返り値は `(x_start, x_end)` の並びで、いずれも**昇順・非重複**。
371/// 閉じていない副パスも、塗りのときは仕様上「閉じているものとして」扱う。
372///
373/// # なぜ純粋関数にするか
374///
375/// 巻き数の数え方(`nonzero` と `evenodd` の違い)と、頂点をちょうど
376/// 走査線が通るときの二重計上は、目視では気づけない典型的なバグ源。
377/// ここを試験で固定しておけば、実際のピクセル書き込み側は
378/// 「区間を塗るだけ」の単純な処理に保てる。
379///
380/// 計算量: **O(辺の数 log 辺の数)**(交点の整列ぶん)。
381pub fn scanline_spans(subpaths: &[SubPath], y: f32, rule: FillRule) -> Vec<(f32, f32)> {
382    // (x, 巻き方向) の組を集める。
383    let mut hits: Vec<(f32, i32)> = Vec::new();
384    for sp in subpaths {
385        let n = sp.points.len();
386        if n < 2 {
387            continue;
388        }
389        // 塗りでは常に閉じたものとして扱う(最後の点と最初の点を結ぶ)。
390        for i in 0..n {
391            let (x0, y0) = sp.points[i];
392            let (x1, y1) = sp.points[(i + 1) % n];
393            if !y0.is_finite() || !y1.is_finite() || !x0.is_finite() || !x1.is_finite() {
394                continue;
395            }
396            // 水平な辺は交差を作らない。
397            if (y0 - y1).abs() < f32::EPSILON {
398                continue;
399            }
400            // 半開区間 [min, max) で判定する。
401            //
402            // これをしないと、頂点をちょうど走査線が通るときに
403            // 上下 2 本の辺が同じ交点を二重に数え、`evenodd` で
404            // 内外が反転する(塗り残し・塗り過ぎの典型的な原因)。
405            let (ylo, yhi) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
406            if y < ylo || y >= yhi {
407                continue;
408            }
409            let t = (y - y0) / (y1 - y0);
410            let x = x0 + t * (x1 - x0);
411            // 下向きの辺を +1、上向きを -1 とする。
412            let dir = if y1 > y0 { 1 } else { -1 };
413            hits.push((x, dir));
414        }
415    }
416    if hits.is_empty() {
417        return Vec::new();
418    }
419    // x 昇順に並べる(NaN は上で除外済み)。
420    hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
421
422    let mut spans = Vec::new();
423    match rule {
424        FillRule::EvenOdd => {
425            let mut i = 0;
426            while i + 1 < hits.len() {
427                spans.push((hits[i].0, hits[i + 1].0));
428                i += 2;
429            }
430        }
431        FillRule::NonZero => {
432            let mut winding = 0i32;
433            let mut start = 0.0f32;
434            for &(x, dir) in &hits {
435                let was_inside = winding != 0;
436                winding += dir;
437                let now_inside = winding != 0;
438                if !was_inside && now_inside {
439                    start = x;
440                } else if was_inside && !now_inside {
441                    spans.push((start, x));
442                }
443            }
444        }
445    }
446    spans
447}
448
449
450// ───────────── グラデーション ─────────────
451
452/// グラデーションのカラーストップ。
453#[derive(Debug, Clone, Copy, PartialEq)]
454pub struct ColorStop {
455    pub offset: f32, // 0.0 ..= 1.0
456    pub color: u32,  // ARGB8888
457}
458
459/// グラデーションの種別と幾何パラメータ。
460#[derive(Debug, Clone, PartialEq)]
461pub enum GradientKind {
462    Linear {
463        x0: f32,
464        y0: f32,
465        x1: f32,
466        y1: f32,
467    },
468    Radial {
469        x0: f32,
470        y0: f32,
471        r0: f32,
472        x1: f32,
473        y1: f32,
474        r1: f32,
475    },
476}
477
478/// カラーストップ配列から位置 `t`(0.0..=1.0)に対応する ARGB8888 色を線形補間する。
479pub fn sample_gradient_stops(stops: &[ColorStop], t: f32) -> u32 {
480    if stops.is_empty() {
481        return 0; // 透明
482    }
483    let t = t.clamp(0.0, 1.0);
484    if t <= stops[0].offset {
485        return stops[0].color;
486    }
487    let last = &stops[stops.len() - 1];
488    if t >= last.offset {
489        return last.color;
490    }
491    for i in 0..stops.len() - 1 {
492        let s0 = &stops[i];
493        let s1 = &stops[i + 1];
494        if t >= s0.offset && t <= s1.offset {
495            let span = s1.offset - s0.offset;
496            let ratio = if span > 1e-6 { (t - s0.offset) / span } else { 0.0 };
497            let a0 = ((s0.color >> 24) & 0xFF) as f32;
498            let r0 = ((s0.color >> 16) & 0xFF) as f32;
499            let g0 = ((s0.color >> 8) & 0xFF) as f32;
500            let b0 = (s0.color & 0xFF) as f32;
501
502            let a1 = ((s1.color >> 24) & 0xFF) as f32;
503            let r1 = ((s1.color >> 16) & 0xFF) as f32;
504            let g1 = ((s1.color >> 8) & 0xFF) as f32;
505            let b1 = (s1.color & 0xFF) as f32;
506
507            let a = ((1.0 - ratio) * a0 + ratio * a1 + 0.5) as u32;
508            let r = ((1.0 - ratio) * r0 + ratio * r1 + 0.5) as u32;
509            let g = ((1.0 - ratio) * g0 + ratio * g1 + 0.5) as u32;
510            let b = ((1.0 - ratio) * b0 + ratio * b1 + 0.5) as u32;
511
512            return (a.min(255) << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255);
513        }
514    }
515    last.color
516}
517
518/// Canvas 2D のグラデーションオブジェクト。
519#[derive(Debug, Clone, PartialEq)]
520pub struct CanvasGradient {
521    pub kind: GradientKind,
522    pub stops: Vec<ColorStop>,
523}
524
525impl CanvasGradient {
526    pub fn new_linear(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
527        CanvasGradient {
528            kind: GradientKind::Linear { x0, y0, x1, y1 },
529            stops: Vec::new(),
530        }
531    }
532
533    pub fn new_radial(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> Self {
534        CanvasGradient {
535            kind: GradientKind::Radial { x0, y0, r0, x1, y1, r1 },
536            stops: Vec::new(),
537        }
538    }
539
540    pub fn add_color_stop(&mut self, offset: f32, color: u32) {
541        let offset = offset.clamp(0.0, 1.0);
542        let stop = ColorStop { offset, color };
543        let idx = self.stops.partition_point(|s| s.offset <= offset);
544        self.stops.insert(idx, stop);
545    }
546
547    /// 256段の高速ルックアップテーブル(LUT)を生成する。
548    pub fn build_lut(&self) -> [u32; 256] {
549        let mut lut = [0u32; 256];
550        if self.stops.is_empty() {
551            return lut;
552        }
553        for i in 0..256 {
554            let t = i as f32 / 255.0;
555            lut[i] = sample_gradient_stops(&self.stops, t);
556        }
557        lut
558    }
559
560    /// 座標 (x, y) における射影パラメータ t (0.0..=1.0) を計算する。
561    pub fn calculate_t(&self, x: f32, y: f32) -> f32 {
562        match self.kind {
563            GradientKind::Linear { x0, y0, x1, y1 } => {
564                let dx = x1 - x0;
565                let dy = y1 - y0;
566                let len_sq = dx * dx + dy * dy;
567                if len_sq < 1e-6 {
568                    return 0.0;
569                }
570                let proj = (x - x0) * dx + (y - y0) * dy;
571                (proj / len_sq).clamp(0.0, 1.0)
572            }
573            GradientKind::Radial { x0: _, y0: _, r0, x1, y1, r1 } => {
574                let dx = x - x1;
575                let dy = y - y1;
576                let dist = libm::sqrtf(dx * dx + dy * dy);
577                let r_span = r1 - r0;
578                if libm::fabsf(r_span) < 1e-6 {
579                    return 0.0;
580                }
581                ((dist - r0) / r_span).clamp(0.0, 1.0)
582            }
583        }
584    }
585}
586
587
588// ───────────── パターン ─────────────
589
590/// `createPattern` のリピート方式。
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
592pub enum Repetition {
593    Repeat,
594    RepeatX,
595    RepeatY,
596    NoRepeat,
597}
598
599/// 文字列から `Repetition` を判定する(仕様上、大文字小文字無視、空文字や未知の値は Repeat)。
600pub fn parse_repetition(s: &str) -> Repetition {
601    let s = s.trim();
602    if s.is_empty() {
603        return Repetition::Repeat;
604    }
605    if s.eq_ignore_ascii_case("repeat") {
606        Repetition::Repeat
607    } else if s.eq_ignore_ascii_case("repeat-x") {
608        Repetition::RepeatX
609    } else if s.eq_ignore_ascii_case("repeat-y") {
610        Repetition::RepeatY
611    } else if s.eq_ignore_ascii_case("no-repeat") {
612        Repetition::NoRepeat
613    } else {
614        Repetition::Repeat
615    }
616}
617
618/// Canvas 2D のパターンオブジェクト。
619#[derive(Debug, Clone, PartialEq)]
620pub struct CanvasPattern {
621    pub width: u32,
622    pub height: u32,
623    pub pixels: Vec<u32>,
624    pub repetition: Repetition,
625    pub transform: Matrix,
626}
627
628impl CanvasPattern {
629    pub fn new(width: u32, height: u32, pixels: Vec<u32>, repetition: Repetition) -> Self {
630        CanvasPattern {
631            width,
632            height,
633            pixels,
634            repetition,
635            transform: Matrix::identity(),
636        }
637    }
638
639    pub fn set_transform(&mut self, m: Matrix) {
640        self.transform = m;
641    }
642
643    /// 座標 (x, y) におけるピクセル色 (ARGB8888) をサンプリングする。
644    /// 範囲外(no-repeat等)は 0(透明)を返す。
645    pub fn sample(&self, x: f32, y: f32) -> u32 {
646        if self.width == 0 || self.height == 0 || self.pixels.is_empty() {
647            return 0;
648        }
649        let (u, v) = if let Some(inv) = self.transform.invert() {
650            inv.apply(x, y)
651        } else {
652            (x, y)
653        };
654        let w = self.width as i32;
655        let h = self.height as i32;
656
657        let (ix, iy) = match self.repetition {
658            Repetition::Repeat => (
659                (libm::floorf(u) as i32).rem_euclid(w),
660                (libm::floorf(v) as i32).rem_euclid(h),
661            ),
662            Repetition::RepeatX => {
663                let y_int = libm::floorf(v) as i32;
664                if y_int < 0 || y_int >= h {
665                    return 0;
666                }
667                ((libm::floorf(u) as i32).rem_euclid(w), y_int)
668            }
669            Repetition::RepeatY => {
670                let x_int = libm::floorf(u) as i32;
671                if x_int < 0 || x_int >= w {
672                    return 0;
673                }
674                (x_int, (libm::floorf(v) as i32).rem_euclid(h))
675            }
676            Repetition::NoRepeat => {
677                let x_int = libm::floorf(u) as i32;
678                let y_int = libm::floorf(v) as i32;
679                if x_int < 0 || x_int >= w || y_int < 0 || y_int >= h {
680                    return 0;
681                }
682                (x_int, y_int)
683            }
684        };
685
686        let idx = (iy as usize) * (self.width as usize) + (ix as usize);
687        self.pixels.get(idx).copied().unwrap_or(0)
688    }
689}
690
691
692// ───────────── バッキングストアへの描画 ─────────────
693
694/// Canvas のバッキングストア(ARGB8888 のピクセル配列)。
695///
696/// 画面へ直接描かず、まずここへ描く。`<canvas>` は
697/// 「レイアウトで決まった矩形へ、自分のピクセルを貼る」要素なので、
698/// 画面座標ではなく**キャンバス内座標**で完結させる必要がある。
699#[derive(Debug, Clone)]
700pub struct Surface {
701    pub width: u32,
702    pub height: u32,
703    /// 長さは `width * height`。各要素は 0xAARRGGBB。
704    pub pixels: Vec<u32>,
705    /// 有効なクリップ領域。`None` なら全面に描ける。
706    ///
707    /// 状態側(`DrawState`)が持ち主で、描画の直前に `sync_clip` で写す。
708    /// 塗りの実装すべてが最後は `blend_pixel` を通るので、
709    /// ここで一度だけ見れば経路の取りこぼしが起きない。
710    pub clip: Option<alloc::sync::Arc<ClipMask>>,
711}
712
713impl Surface {
714    /// 透明で初期化した面を作る。
715    ///
716    /// 面積が過大な場合は `None`(ヒープを食い潰さないための上限)。
717    /// 実サイトの canvas は大きくても数千×数千なので、
718    /// 4096×4096(約 64MB)を上限とする。
719    pub fn new(width: u32, height: u32) -> Option<Self> {
720        const MAX_SIDE: u32 = 4096;
721        if width == 0 || height == 0 || width > MAX_SIDE || height > MAX_SIDE {
722            return None;
723        }
724        let n = (width as usize).checked_mul(height as usize)?;
725        Some(Surface {
726            width,
727            height,
728            pixels: alloc::vec![0u32; n],
729            clip: None,
730        })
731    }
732
733    /// 指定座標のピクセル(0xAARRGGBB)を返す。範囲外は 0(透明)。
734    pub fn get_pixel(&self, x: i32, y: i32) -> u32 {
735        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
736            return 0;
737        }
738        let idx = y as usize * self.width as usize + x as usize;
739        self.pixels[idx]
740    }
741
742    /// 1 ピクセルを source-over で合成する。範囲外は黙って無視する。
743    pub fn blend_pixel(&mut self, x: i32, y: i32, argb: u32) {
744        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
745            return;
746        }
747        if let Some(c) = &self.clip {
748            if !c.allows(x, y) {
749                return;
750            }
751        }
752        let a = (argb >> 24) & 0xFF;
753        if a == 0 {
754            return;
755        }
756        let idx = y as usize * self.width as usize + x as usize;
757        if a == 255 {
758            self.pixels[idx] = argb;
759            return;
760        }
761        let dst = self.pixels[idx];
762        let inv = 255 - a;
763        // source-over: out = src + dst * (1 - srcA)
764        let da = (dst >> 24) & 0xFF;
765        let out_a = a + da * inv / 255;
766        let mix = |shift: u32| -> u32 {
767            let s = (argb >> shift) & 0xFF;
768            let d = (dst >> shift) & 0xFF;
769            (s * a + d * inv) / 255
770        };
771        self.pixels[idx] =
772            (out_a.min(255) << 24) | (mix(16) << 16) | (mix(8) << 8) | mix(0);
773    }
774
775    /// 矩形を塗りつぶす(`clearRect` は `argb=0` かつ `replace=true` で使う)。
776    ///
777    /// `replace` が真なら合成せず上書きする(`clearRect` の意味論)。
778    pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, argb: u32, replace: bool) {
779        mark_canvas_dirty();
780        if w <= 0 || h <= 0 {
781            return;
782        }
783        let x0 = x.max(0);
784        let y0 = y.max(0);
785        let x1 = (x.saturating_add(w)).min(self.width as i32);
786        let y1 = (y.saturating_add(h)).min(self.height as i32);
787        for py in y0..y1 {
788            for px in x0..x1 {
789                if replace {
790                    // `clearRect` も合成を飛ばすだけで、クリップは効く(仕様)。
791                    // ここは `blend_pixel` を通らないので個別に見る。
792                    if let Some(c) = &self.clip {
793                        if !c.allows(px, py) {
794                            continue;
795                        }
796                    }
797                    let idx = py as usize * self.width as usize + px as usize;
798                    self.pixels[idx] = argb;
799                } else {
800                    self.blend_pixel(px, py, argb);
801                }
802            }
803        }
804    }
805
806    /// パスを塗りつぶす。
807    ///
808    /// 走査線はピクセル中心(`y + 0.5`)で判定する。境界ちょうどの
809    /// 走査線を使うと、隣り合う図形の継ぎ目に隙間や二重塗りが出る。
810    pub fn fill_path(&mut self, subpaths: &[SubPath], rule: FillRule, argb: u32) {
811        mark_canvas_dirty();
812        if subpaths.is_empty() {
813            return;
814        }
815        // 走査する y 範囲をパスの境界から絞る(全面走査を避ける)。
816        let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
817        for sp in subpaths {
818            for &(_, py) in &sp.points {
819                if py.is_finite() {
820                    ymin = ymin.min(py);
821                    ymax = ymax.max(py);
822                }
823            }
824        }
825        if ymin > ymax {
826            return;
827        }
828        let y_start = (libm::floorf(ymin) as i32).max(0);
829        let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
830        for py in y_start..y_end {
831            let spans = scanline_spans(subpaths, py as f32 + 0.5, rule);
832            for (sx, ex) in spans {
833                // 区間の両端はピクセル境界へ丸める。
834                let x0 = (libm::roundf(sx) as i32).max(0);
835                let x1 = (libm::roundf(ex) as i32).min(self.width as i32);
836                for px in x0..x1 {
837                    self.blend_pixel(px, py, argb);
838                }
839            }
840        }
841    }
842
843    /// パスをグラデーション(線形または円形)で塗りつぶす。
844    pub fn fill_path_gradient(
845        &mut self,
846        subpaths: &[SubPath],
847        rule: FillRule,
848        gradient: &CanvasGradient,
849        alpha_multiplier: f32,
850    ) {
851        mark_canvas_dirty();
852        if subpaths.is_empty() || gradient.stops.is_empty() {
853            return;
854        }
855        let lut = gradient.build_lut();
856        let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
857        for sp in subpaths {
858            for &(_, py) in &sp.points {
859                if py.is_finite() {
860                    ymin = ymin.min(py);
861                    ymax = ymax.max(py);
862                }
863            }
864        }
865        if ymin > ymax {
866            return;
867        }
868        let y_start = (libm::floorf(ymin) as i32).max(0);
869        let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
870
871        match gradient.kind {
872            GradientKind::Linear { x0, y0, x1, y1 } => {
873                let dx = x1 - x0;
874                let dy = y1 - y0;
875                let len_sq = dx * dx + dy * dy;
876                let (cx, cy, c_base) = if len_sq >= 1e-6 {
877                    let inv = 1.0 / len_sq;
878                    (dx * inv, dy * inv, -(x0 * dx + y0 * dy) * inv)
879                } else {
880                    (0.0, 0.0, 0.0)
881                };
882
883                for py in y_start..y_end {
884                    let y_mid = py as f32 + 0.5;
885                    let t_row_base = c_base + cy * y_mid;
886                    let spans = scanline_spans(subpaths, y_mid, rule);
887                    for (sx, ex) in spans {
888                        let px0 = (libm::roundf(sx) as i32).max(0);
889                        let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
890                        for px in px0..px1 {
891                            let x_mid = px as f32 + 0.5;
892                            let t = (t_row_base + cx * x_mid).clamp(0.0, 1.0);
893                            let lut_idx = (t * 255.0 + 0.5) as usize;
894                            let mut color = lut[lut_idx.min(255)];
895                            if alpha_multiplier < 1.0 {
896                                let a = (((color >> 24) & 0xFF) as f32 * alpha_multiplier + 0.5) as u32;
897                                color = (a.min(255) << 24) | (color & 0x00FF_FFFF);
898                            }
899                            self.blend_pixel(px, py, color);
900                        }
901                    }
902                }
903            }
904            GradientKind::Radial { .. } => {
905                for py in y_start..y_end {
906                    let y_mid = py as f32 + 0.5;
907                    let spans = scanline_spans(subpaths, y_mid, rule);
908                    for (sx, ex) in spans {
909                        let px0 = (libm::roundf(sx) as i32).max(0);
910                        let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
911                        for px in px0..px1 {
912                            let x_mid = px as f32 + 0.5;
913                            let t = gradient.calculate_t(x_mid, y_mid);
914                            let lut_idx = (t * 255.0 + 0.5) as usize;
915                            let mut color = lut[lut_idx.min(255)];
916                            if alpha_multiplier < 1.0 {
917                                let a = (((color >> 24) & 0xFF) as f32 * alpha_multiplier + 0.5) as u32;
918                                color = (a.min(255) << 24) | (color & 0x00FF_FFFF);
919                            }
920                            self.blend_pixel(px, py, color);
921                        }
922                    }
923                }
924            }
925        }
926    }
927
928    /// パスをパターン(タイリング画像)で塗りつぶす。
929    pub fn fill_path_pattern(
930        &mut self,
931        subpaths: &[SubPath],
932        rule: FillRule,
933        pattern: &CanvasPattern,
934        alpha_multiplier: f32,
935    ) {
936        mark_canvas_dirty();
937        if subpaths.is_empty() || pattern.width == 0 || pattern.height == 0 || pattern.pixels.is_empty() {
938            return;
939        }
940        let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
941        for sp in subpaths {
942            for &(_, py) in &sp.points {
943                if py.is_finite() {
944                    ymin = ymin.min(py);
945                    ymax = ymax.max(py);
946                }
947            }
948        }
949        if ymin > ymax {
950            return;
951        }
952        let y_start = (libm::floorf(ymin) as i32).max(0);
953        let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
954
955        for py in y_start..y_end {
956            let y_mid = py as f32 + 0.5;
957            let spans = scanline_spans(subpaths, y_mid, rule);
958            for (sx, ex) in spans {
959                let px0 = (libm::roundf(sx) as i32).max(0);
960                let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
961                for px in px0..px1 {
962                    let x_mid = px as f32 + 0.5;
963                    let color = pattern.sample(x_mid, y_mid);
964                    let a = (color >> 24) & 0xFF;
965                    if a == 0 {
966                        continue;
967                    }
968                    let final_color = if alpha_multiplier < 1.0 {
969                        let final_a = libm::roundf(a as f32 * alpha_multiplier).clamp(0.0, 255.0) as u32;
970                        (final_a << 24) | (color & 0x00FF_FFFF)
971                    } else {
972                        color
973                    };
974                    self.blend_pixel(px, py, final_color);
975                }
976            }
977        }
978    }
979
980    /// 折れ線を線幅 1 で描く(`stroke` の最小実装)。
981    ///
982    /// 線幅・線端・線結合は未対応。まず「線が見える」ところまでを担保する。
983    pub fn stroke_path(&mut self, subpaths: &[SubPath], argb: u32) {
984        mark_canvas_dirty();
985        for sp in subpaths {
986            let n = sp.points.len();
987            if n < 2 {
988                continue;
989            }
990            let last = if sp.closed { n } else { n - 1 };
991            for i in 0..last {
992                let (x0, y0) = sp.points[i];
993                let (x1, y1) = sp.points[(i + 1) % n];
994                self.draw_line(x0, y0, x1, y1, argb);
995            }
996        }
997    }
998
999    /// Bresenham で線分を引く。非有限座標は無視する。
1000    fn draw_line(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, argb: u32) {
1001        if !x0.is_finite() || !y0.is_finite() || !x1.is_finite() || !y1.is_finite() {
1002            return;
1003        }
1004        let mut x = libm::roundf(x0) as i32;
1005        let mut y = libm::roundf(y0) as i32;
1006        let xe = libm::roundf(x1) as i32;
1007        let ye = libm::roundf(y1) as i32;
1008        let dx = (xe - x).abs();
1009        let dy = -(ye - y).abs();
1010        let sx = if x < xe { 1 } else { -1 };
1011        let sy = if y < ye { 1 } else { -1 };
1012        let mut err = dx + dy;
1013        // 上限を設けて、万一の座標異常でも無限ループにしない。
1014        let mut guard = 0i32;
1015        let limit = (self.width as i32 + self.height as i32) * 4 + 16;
1016        loop {
1017            self.blend_pixel(x, y, argb);
1018            if (x == xe && y == ye) || guard > limit {
1019                break;
1020            }
1021            guard += 1;
1022            let e2 = err * 2;
1023            if e2 >= dy {
1024                err += dy;
1025                x += sx;
1026            }
1027            if e2 <= dx {
1028                err += dx;
1029                y += sy;
1030            }
1031        }
1032    }
1033}
1034
1035
1036// ───────────── 2D コンテキストの状態 ─────────────
1037
1038/// `save()`/`restore()` で退避・復帰する描画状態。
1039#[derive(Debug, Clone)]
1040pub struct DrawState {
1041    pub transform: Matrix,
1042    /// 塗り色(ARGB8888)。
1043    pub fill: u32,
1044    /// 塗り色の文字列表現(例: "#000000", "red")。
1045    pub fill_str: alloc::string::String,
1046    /// 線色(ARGB8888)。
1047    pub stroke: u32,
1048    /// 線色の文字列表現。
1049    pub stroke_str: alloc::string::String,
1050    pub line_width: f32,
1051    /// `lineCap`。
1052    pub line_cap: LineCap,
1053    /// `lineJoin`。
1054    pub line_join: LineJoin,
1055    /// `miterLimit`。
1056    pub miter_limit: f32,
1057    /// `setLineDash` のパターン。空なら実線。
1058    pub line_dash: Vec<f32>,
1059    /// `lineDashOffset`。
1060    pub line_dash_offset: f32,
1061    /// クリップ領域。`None` なら全面。
1062    pub clip: Option<alloc::sync::Arc<ClipMask>>,
1063    /// `globalAlpha`(0.0〜1.0)。
1064    pub global_alpha: f32,
1065    /// フォント指定文字列(例: "10px sans-serif")。
1066    pub font_str: alloc::string::String,
1067    /// 水平揃え文字列(例: "start")。
1068    pub text_align_str: alloc::string::String,
1069    /// 垂直ベースライン文字列(例: "alphabetic")。
1070    pub text_baseline_str: alloc::string::String,
1071    /// 画像平滑化有効フラグ(imageSmoothingEnabled)。
1072    pub image_smoothing_enabled: bool,
1073    /// 塗りグラデーション(指定時は単色 fill より優先)。
1074    pub fill_gradient: Option<CanvasGradient>,
1075    /// 線グラデーション。
1076    pub stroke_gradient: Option<CanvasGradient>,
1077    /// 塗りパターン(指定時は単色 fill より優先)。
1078    pub fill_pattern: Option<CanvasPattern>,
1079    /// 線パターン。
1080    pub stroke_pattern: Option<CanvasPattern>,
1081}
1082
1083impl Default for DrawState {
1084    fn default() -> Self {
1085        DrawState {
1086            transform: Matrix::identity(),
1087            // Canvas の既定は不透明な黒。
1088            fill: 0xFF00_0000,
1089            fill_str: alloc::string::String::from("#000000"),
1090            stroke: 0xFF00_0000,
1091            stroke_str: alloc::string::String::from("#000000"),
1092            line_width: 1.0,
1093            line_cap: LineCap::Butt,
1094            line_join: LineJoin::Miter,
1095            miter_limit: DEFAULT_MITER_LIMIT,
1096            line_dash: Vec::new(),
1097            line_dash_offset: 0.0,
1098            clip: None,
1099            global_alpha: 1.0,
1100            font_str: alloc::string::String::from("10px sans-serif"),
1101            text_align_str: alloc::string::String::from("start"),
1102            text_baseline_str: alloc::string::String::from("alphabetic"),
1103            image_smoothing_enabled: true,
1104            fill_gradient: None,
1105            stroke_gradient: None,
1106            fill_pattern: None,
1107            stroke_pattern: None,
1108        }
1109    }
1110}
1111
1112/// 1 つの `<canvas>` に対する 2D コンテキストの全状態。
1113///
1114/// JS 側のオブジェクトは軽いハンドルだけを持ち、実体はこれを指す。
1115/// パスと面はどちらもここに持たせて、描画の途中経過が
1116/// JS オブジェクトの寿命に左右されないようにする。
1117#[derive(Debug)]
1118pub struct Canvas2dContext {
1119    pub surface: Surface,
1120    pub path: PathBuilder,
1121    pub state: DrawState,
1122    /// `save()` の退避スタック。
1123    stack: Vec<DrawState>,
1124}
1125
1126/// `save()` の入れ子上限。
1127///
1128/// 際限なく積むと、壊れた(`restore` を呼ばない)スクリプトで
1129/// ヒープを食い潰す。実用上 32 段もあれば足りる。
1130const MAX_SAVE_DEPTH: usize = 32;
1131
1132impl Canvas2dContext {
1133    pub fn new(width: u32, height: u32) -> Option<Self> {
1134        Some(Canvas2dContext {
1135            surface: Surface::new(width, height)?,
1136            path: PathBuilder::new(),
1137            state: DrawState::default(),
1138            stack: Vec::new(),
1139        })
1140    }
1141
1142    /// 現在の状態を退避する。上限を超えたら黙って無視する
1143    /// (`restore` との対応が崩れるが、落ちるよりはよい)。
1144    pub fn save(&mut self) {
1145        if self.stack.len() >= MAX_SAVE_DEPTH {
1146            return;
1147        }
1148        self.stack.push(self.state.clone());
1149    }
1150
1151    /// 直近の退避状態へ戻す。スタックが空なら何もしない
1152    /// (仕様どおり。`restore` の呼び過ぎはエラーではない)。
1153    pub fn restore(&mut self) {
1154        if let Some(s) = self.stack.pop() {
1155            self.state = s;
1156        }
1157    }
1158
1159    /// `globalAlpha` を適用した色を返す。
1160    ///
1161    /// アルファは掛け算で合成する。`globalAlpha=0.5` の半透明色は
1162    /// さらに薄くなる(仕様どおり)。
1163    pub fn apply_alpha(&self, argb: u32) -> u32 {
1164        let ga = self.state.global_alpha.clamp(0.0, 1.0);
1165        if ga >= 1.0 {
1166            return argb;
1167        }
1168        let a = ((argb >> 24) & 0xFF) as f32 * ga;
1169        let a = libm::roundf(a).clamp(0.0, 255.0) as u32;
1170        (a << 24) | (argb & 0x00FF_FFFF)
1171    }
1172
1173    /// 現在の変換を適用して矩形を塗る。
1174    ///
1175    /// 変換が平行移動と拡大だけなら矩形のまま塗れるが、回転が入ると
1176    /// 軸平行でなくなる。判定を分けず、常にパス経由で塗ることで
1177    /// 回転時も正しくなるようにしている。
1178    pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1179        self.sync_clip();
1180        let mut p = PathBuilder::new();
1181        p.rect(&self.state.transform, x, y, w, h);
1182        let sub = p.finish();
1183        if let Some(ref grad) = self.state.fill_gradient {
1184            self.surface.fill_path_gradient(&sub, FillRule::NonZero, grad, self.state.global_alpha.clamp(0.0, 1.0));
1185        } else if let Some(ref pat) = self.state.fill_pattern {
1186            self.surface.fill_path_pattern(&sub, FillRule::NonZero, pat, self.state.global_alpha.clamp(0.0, 1.0));
1187        } else {
1188            let color = self.apply_alpha(self.state.fill);
1189            self.surface.fill_path(&sub, FillRule::NonZero, color);
1190        }
1191    }
1192
1193    /// 矩形を透明で消す。`globalAlpha` の影響は受けない(仕様どおり)。
1194    ///
1195    /// 回転が入っていると軸平行に消せないため、パスの外接矩形を消す。
1196    /// 厳密ではないが、`clearRect` に回転を掛ける使い方は稀。
1197    pub fn clear_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1198        self.sync_clip();
1199        let m = &self.state.transform;
1200        let pts = [
1201            m.apply(x, y),
1202            m.apply(x + w, y),
1203            m.apply(x + w, y + h),
1204            m.apply(x, y + h),
1205        ];
1206        let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
1207        for (px, py) in pts {
1208            if !px.is_finite() || !py.is_finite() {
1209                return;
1210            }
1211            x0 = x0.min(px);
1212            y0 = y0.min(py);
1213            x1 = x1.max(px);
1214            y1 = y1.max(py);
1215        }
1216        self.surface.fill_rect(
1217            libm::floorf(x0) as i32,
1218            libm::floorf(y0) as i32,
1219            libm::ceilf(x1 - x0) as i32,
1220            libm::ceilf(y1 - y0) as i32,
1221            0,
1222            true,
1223        );
1224    }
1225
1226    /// 現在のパスを塗る。
1227    pub fn fill(&mut self, rule: FillRule) {
1228        self.sync_clip();
1229        let sub = self.path.finish();
1230        if let Some(ref grad) = self.state.fill_gradient {
1231            self.surface.fill_path_gradient(&sub, rule, grad, self.state.global_alpha.clamp(0.0, 1.0));
1232        } else if let Some(ref pat) = self.state.fill_pattern {
1233            self.surface.fill_path_pattern(&sub, rule, pat, self.state.global_alpha.clamp(0.0, 1.0));
1234        } else {
1235            let color = self.apply_alpha(self.state.fill);
1236            self.surface.fill_path(&sub, rule, color);
1237        }
1238    }
1239
1240    /// 現在のパスを線描する。
1241    pub fn stroke(&mut self) {
1242        self.sync_clip();
1243        let color = self.apply_alpha(self.state.stroke);
1244        let sub = self.path.finish();
1245        self.surface.stroke_path(&sub, color);
1246    }
1247
1248    /// 現在の変換へ平行移動を積む。
1249    pub fn translate(&mut self, tx: f32, ty: f32) {
1250        self.state.transform = self.state.transform.multiply(&Matrix::translate(tx, ty));
1251    }
1252
1253    /// 現在の変換へ拡大縮小を積む。
1254    pub fn scale(&mut self, sx: f32, sy: f32) {
1255        self.state.transform = self.state.transform.multiply(&Matrix::scale(sx, sy));
1256    }
1257
1258    /// 現在の変換へ回転を積む。
1259    pub fn rotate(&mut self, rad: f32) {
1260        self.state.transform = self.state.transform.multiply(&Matrix::rotate(rad));
1261    }
1262
1263    /// 変換行列を直接置き換える(`setTransform`)。
1264    pub fn set_transform(&mut self, m: Matrix) {
1265        self.state.transform = m;
1266    }
1267
1268    /// 変換を単位行列へ戻す(`resetTransform`)。
1269    pub fn reset_transform(&mut self) {
1270        self.state.transform = Matrix::identity();
1271    }
1272}
1273
1274
1275// ───────────── コンテキスト登録表 ─────────────
1276
1277/// `<canvas>` ごとの 2D コンテキストを id で保持する表。
1278///
1279/// JS 側のオブジェクトは `ObjKind::Host("canvas2d:<id>")` というタグだけを
1280/// 持ち、実体はここにある。JS の値へ Rust の可変状態を直接埋めると
1281/// 借用と寿命の扱いが複雑になるため、既存の DOM プロキシ(style/classList)
1282/// と同じ「タグで引く」流儀に合わせた。
1283static CONTEXTS: spin::Mutex<alloc::collections::BTreeMap<u32, Canvas2dContext>> =
1284    spin::Mutex::new(alloc::collections::BTreeMap::new());
1285
1286/// 次に払い出す id。
1287static NEXT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
1288
1289/// 同時に保持するコンテキスト数の上限。
1290///
1291/// ページ遷移のたびに増え続けると、面(最大 64MB)が積み上がって
1292/// ヒープを食い潰す。上限を超えたら最も古い id から捨てる。
1293const MAX_CONTEXTS: usize = 8;
1294
1295/// 新しいコンテキストを作り、その id を返す。
1296///
1297/// 面が確保できない(サイズが不正・過大)場合は `None`。
1298pub fn create_context(width: u32, height: u32) -> Option<u32> {
1299    let ctx = Canvas2dContext::new(width, height)?;
1300    let id = NEXT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1301    let mut map = CONTEXTS.lock();
1302    // 上限を超えたら最も古い(id が小さい)ものから捨てる。
1303    while map.len() >= MAX_CONTEXTS {
1304        let Some(&oldest) = map.keys().next() else {
1305            break;
1306        };
1307        map.remove(&oldest);
1308    }
1309    map.insert(id, ctx);
1310    Some(id)
1311}
1312
1313/// id のコンテキストへ可変アクセスする。
1314///
1315/// 見つからなければ `f` を呼ばず `None` を返す(ページ遷移で
1316/// 破棄された後の呼び出しは黙って無視する)。
1317pub fn with_context<R>(id: u32, f: impl FnOnce(&mut Canvas2dContext) -> R) -> Option<R> {
1318    let mut map = CONTEXTS.lock();
1319    map.get_mut(&id).map(f)
1320}
1321
1322/// 保持している全コンテキストを破棄する(ページ遷移時に呼ぶ)。
1323pub fn clear_contexts() {
1324    CONTEXTS.lock().clear();
1325}
1326
1327/// `ObjKind::Host` のタグ文字列から id を取り出す。
1328///
1329/// 形式は `canvas2d:<id>`。合致しなければ `None`。
1330pub fn id_from_tag(tag: &str) -> Option<u32> {
1331    tag.strip_prefix("canvas2d:")?.parse::<u32>().ok()
1332}
1333
1334/// id からタグ文字列を作る。
1335pub fn tag_for_id(id: u32) -> alloc::string::String {
1336    alloc::format!("canvas2d:{}", id)
1337}
1338
1339impl PathBuilder {
1340    /// 楕円弧(`ellipse`)。
1341    ///
1342    /// `arc` の一般形で、x/y に別々の半径と、楕円自体の回転を持つ。
1343    /// 単位円上の点を「半径で伸ばす → 回転する → 中心へ移す」順で写す。
1344    /// この順序を取り違えると、回転した楕円が歪む。
1345    #[allow(clippy::too_many_arguments)]
1346    pub fn ellipse(
1347        &mut self,
1348        m: &Matrix,
1349        cx: f32,
1350        cy: f32,
1351        rx: f32,
1352        ry: f32,
1353        rotation: f32,
1354        start: f32,
1355        end: f32,
1356        anticlockwise: bool,
1357    ) {
1358        if !rx.is_finite()
1359            || !ry.is_finite()
1360            || rx < 0.0
1361            || ry < 0.0
1362            || !start.is_finite()
1363            || !end.is_finite()
1364            || !rotation.is_finite()
1365        {
1366            return;
1367        }
1368        let sweep = normalize_sweep(start, end, anticlockwise);
1369        let steps = arc_steps(sweep);
1370        let (rs, rc) = (libm::sinf(rotation), libm::cosf(rotation));
1371        for i in 0..=steps {
1372            let t = i as f32 / steps as f32;
1373            let ang = start + sweep * t;
1374            // 単位円 → 半径で伸ばす
1375            let ux = rx * libm::cosf(ang);
1376            let uy = ry * libm::sinf(ang);
1377            // → 楕円の回転 → 中心へ移す
1378            let px = cx + ux * rc - uy * rs;
1379            let py = cy + ux * rs + uy * rc;
1380            if i == 0 && self.current.is_none() {
1381                self.move_to(m, px, py);
1382            } else {
1383                self.line_to(m, px, py);
1384            }
1385        }
1386    }
1387
1388    /// 角丸矩形(`roundRect`)。
1389    ///
1390    /// 半径は 4 隅ぶん(左上・右上・右下・左下)。
1391    /// 半径の合計が辺の長さを超える場合は、仕様どおり**比率を保ったまま
1392    /// 縮小**する。しないと角が重なって図形が破綻する。
1393    #[allow(clippy::too_many_arguments)]
1394    pub fn round_rect(
1395        &mut self,
1396        m: &Matrix,
1397        x: f32,
1398        y: f32,
1399        w: f32,
1400        h: f32,
1401        radii: [f32; 4],
1402    ) {
1403        if !w.is_finite() || !h.is_finite() || w == 0.0 || h == 0.0 {
1404            return;
1405        }
1406        // 負の幅・高さは反対側から描く(仕様)。
1407        let (x, w) = if w < 0.0 { (x + w, -w) } else { (x, w) };
1408        let (y, h) = if h < 0.0 { (y + h, -h) } else { (y, h) };
1409
1410        let mut r = [0.0f32; 4];
1411        for (i, v) in radii.iter().enumerate() {
1412            r[i] = if v.is_finite() && *v > 0.0 { *v } else { 0.0 };
1413        }
1414        // 辺ごとに「両端の半径の和」が辺長を超えないよう縮小率を求める。
1415        let mut scale = 1.0f32;
1416        let pairs = [
1417            (r[0] + r[1], w), // 上辺: 左上 + 右上
1418            (r[2] + r[3], w), // 下辺: 右下 + 左下
1419            (r[1] + r[2], h), // 右辺: 右上 + 右下
1420            (r[3] + r[0], h), // 左辺: 左下 + 左上
1421        ];
1422        for (sum, len) in pairs {
1423            if sum > 0.0 && sum > len {
1424                scale = scale.min(len / sum);
1425            }
1426        }
1427        for v in r.iter_mut() {
1428            *v *= scale;
1429        }
1430
1431        let half_pi = core::f32::consts::PI / 2.0;
1432        let pi = core::f32::consts::PI;
1433        self.flush();
1434        // 左上から時計回りに、直線 → 角の 1/4 円 を繰り返す。
1435        self.move_to(m, x + r[0], y);
1436        self.line_to(m, x + w - r[1], y);
1437        if r[1] > 0.0 {
1438            self.arc(m, x + w - r[1], y + r[1], r[1], -half_pi, 0.0, false);
1439        }
1440        self.line_to(m, x + w, y + h - r[2]);
1441        if r[2] > 0.0 {
1442            self.arc(m, x + w - r[2], y + h - r[2], r[2], 0.0, half_pi, false);
1443        }
1444        self.line_to(m, x + r[3], y + h);
1445        if r[3] > 0.0 {
1446            self.arc(m, x + r[3], y + h - r[3], r[3], half_pi, pi, false);
1447        }
1448        self.line_to(m, x, y + r[0]);
1449        if r[0] > 0.0 {
1450            self.arc(m, x + r[0], y + r[0], r[0], pi, pi + half_pi, false);
1451        }
1452        self.close_path();
1453    }
1454}
1455
1456impl Surface {
1457    /// 線幅つきで折れ線を描く。
1458    ///
1459    /// 各線分を「線幅ぶんの太さを持つ四角形」に展開して塗る。
1460    /// 線端・線結合(`lineCap`/`lineJoin`)は未対応で、
1461    /// 結合部には円を置いて隙間を埋める(`round` 相当の近似)。
1462    ///
1463    /// # なぜ四角形へ展開するか
1464    ///
1465    /// Bresenham を平行にずらして何本も引く方法は、斜線で
1466    /// 太さが不均一になる(見た目が痩せる)。線分に垂直な方向へ
1467    /// 幅の半分ずつ広げた四角形を塗れば、角度によらず一定の太さになる。
1468    /// 線幅つきで折れ線を描く(線端 `Butt`・結合 `Round`)。
1469    ///
1470    /// 線端・結合を選ぶ場合は `stroke_path_styled` を使う。
1471    pub fn stroke_path_width(&mut self, subpaths: &[SubPath], argb: u32, width: f32) {
1472        self.stroke_path_styled(
1473            subpaths,
1474            argb,
1475            width,
1476            LineCap::Butt,
1477            LineJoin::Round,
1478            DEFAULT_MITER_LIMIT,
1479        );
1480    }
1481
1482    /// 中心 (cx, cy)・半径 r の円を塗る(線の結合部を埋めるのに使う)。
1483    fn fill_disc(&mut self, cx: f32, cy: f32, r: f32, argb: u32) {
1484        if !cx.is_finite() || !cy.is_finite() || !r.is_finite() || r <= 0.0 {
1485            return;
1486        }
1487        let y0 = (libm::floorf(cy - r) as i32).max(0);
1488        let y1 = (libm::ceilf(cy + r) as i32).min(self.height as i32);
1489        let r2 = r * r;
1490        for py in y0..y1 {
1491            let dy = py as f32 + 0.5 - cy;
1492            let d = r2 - dy * dy;
1493            if d < 0.0 {
1494                continue;
1495            }
1496            let dx = libm::sqrtf(d);
1497            let x0 = (libm::roundf(cx - dx) as i32).max(0);
1498            let x1 = (libm::roundf(cx + dx) as i32).min(self.width as i32);
1499            for px in x0..x1 {
1500                self.blend_pixel(px, py, argb);
1501            }
1502        }
1503    }
1504}
1505
1506impl Canvas2dContext {
1507    /// 現在のパスを、`lineWidth` を反映して線描する。
1508    pub fn stroke_with_width(&mut self) {
1509        self.sync_clip();
1510        let color = self.apply_alpha(self.state.stroke);
1511        let w = self.state.line_width;
1512        let (cap, join, limit) = (
1513            self.state.line_cap,
1514            self.state.line_join,
1515            self.state.miter_limit,
1516        );
1517        let sub = self.path.finish();
1518        let sub = self.apply_dash(&sub);
1519        self.surface
1520            .stroke_path_styled(&sub, color, w, cap, join, limit);
1521    }
1522}
1523
1524/// 線端の形状(`lineCap`)。
1525#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1526pub enum LineCap {
1527    /// 端点で切り落とす(既定)。
1528    Butt,
1529    /// 端点に半円を付ける。
1530    Round,
1531    /// 端点から線幅の半分だけ四角く伸ばす。
1532    Square,
1533}
1534
1535/// 線の結合形状(`lineJoin`)。
1536#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1537pub enum LineJoin {
1538    /// 外側の辺を延長して尖らせる(既定)。
1539    Miter,
1540    /// 結合点に円を置く。
1541    Round,
1542    /// 外側を直線で切り落とす。
1543    Bevel,
1544}
1545
1546/// `miterLimit` の既定値。仕様どおり 10。
1547pub const DEFAULT_MITER_LIMIT: f32 = 10.0;
1548
1549pub fn parse_line_cap(s: &str) -> Option<LineCap> {
1550    match s.trim() {
1551        "butt" => Some(LineCap::Butt),
1552        "round" => Some(LineCap::Round),
1553        "square" => Some(LineCap::Square),
1554        _ => None,
1555    }
1556}
1557
1558pub fn parse_line_join(s: &str) -> Option<LineJoin> {
1559    match s.trim() {
1560        "miter" => Some(LineJoin::Miter),
1561        "round" => Some(LineJoin::Round),
1562        "bevel" => Some(LineJoin::Bevel),
1563        _ => None,
1564    }
1565}
1566
1567/// 2 直線の交点。`p1` を通り向き `d1` の直線と、`p2` を通り向き `d2` の直線。
1568///
1569/// 平行(または反転)なら交点は無いので `None`。miter を延ばす先を
1570/// 求めるのに使い、`None` のときは bevel へ落とす。
1571pub fn line_intersection(
1572    p1: (f32, f32),
1573    d1: (f32, f32),
1574    p2: (f32, f32),
1575    d2: (f32, f32),
1576) -> Option<(f32, f32)> {
1577    let den = d1.0 * d2.1 - d1.1 * d2.0;
1578    // ほぼ平行なら交点が無限遠へ飛ぶ。閾値は目視で差が出ない範囲。
1579    if libm::fabsf(den) < 1e-6 {
1580        return None;
1581    }
1582    let t = ((p2.0 - p1.0) * d2.1 - (p2.1 - p1.1) * d2.0) / den;
1583    let x = p1.0 + d1.0 * t;
1584    let y = p1.1 + d1.1 * t;
1585    if !x.is_finite() || !y.is_finite() {
1586        return None;
1587    }
1588    Some((x, y))
1589}
1590
1591impl Surface {
1592    /// 線端・結合を指定して折れ線を描く。
1593    ///
1594    /// `stroke_path_width` は端 `Butt`・結合 `Round` でこれを呼ぶ。
1595    pub fn stroke_path_styled(
1596        &mut self,
1597        subpaths: &[SubPath],
1598        argb: u32,
1599        width: f32,
1600        cap: LineCap,
1601        join: LineJoin,
1602        miter_limit: f32,
1603    ) {
1604        mark_canvas_dirty();
1605        // 1 以下は従来の 1px 線で足りる(見た目も速度も有利)。
1606        if !width.is_finite() || width <= 1.0 {
1607            self.stroke_path(subpaths, argb);
1608            return;
1609        }
1610        let half = width / 2.0;
1611        let limit = if miter_limit.is_finite() && miter_limit > 0.0 {
1612            miter_limit
1613        } else {
1614            DEFAULT_MITER_LIMIT
1615        };
1616        for sp in subpaths {
1617            let n = sp.points.len();
1618            if n < 2 {
1619                continue;
1620            }
1621            let seg_end = if sp.closed { n } else { n - 1 };
1622
1623            // 各線分を、線分に垂直な方向へ half ずつ広げた四角形へ展開。
1624            for i in 0..seg_end {
1625                let a = sp.points[i];
1626                let b = sp.points[(i + 1) % n];
1627                let Some((dx, dy, len)) = unit_delta(a, b) else {
1628                    continue;
1629                };
1630                // 端が Square のときは、線分の外側へ half だけ伸ばす。
1631                let (mut ax, mut ay) = a;
1632                let (mut bx, mut by) = b;
1633                if cap == LineCap::Square && !sp.closed {
1634                    if i == 0 {
1635                        ax -= dx * half;
1636                        ay -= dy * half;
1637                    }
1638                    if i == seg_end - 1 {
1639                        bx += dx * half;
1640                        by += dy * half;
1641                    }
1642                }
1643                let _ = len;
1644                let (nx, ny) = (-dy * half, dx * half);
1645                self.fill_quad(
1646                    (ax + nx, ay + ny),
1647                    (bx + nx, by + ny),
1648                    (bx - nx, by - ny),
1649                    (ax - nx, ay - ny),
1650                    argb,
1651                );
1652            }
1653
1654            // 結合部を埋める。線分の四角形だけでは外側の角が欠ける。
1655            let joint_from = if sp.closed { 0 } else { 1 };
1656            let joint_to = if sp.closed { n } else { n - 1 };
1657            for j in joint_from..joint_to {
1658                let prev = sp.points[(j + n - 1) % n];
1659                let cur = sp.points[j];
1660                let next = sp.points[(j + 1) % n];
1661                self.fill_joint(prev, cur, next, half, argb, join, limit);
1662            }
1663
1664            // 端が Round なら両端に半円(円で近似)を置く。
1665            if cap == LineCap::Round && !sp.closed {
1666                let first = sp.points[0];
1667                let last = sp.points[n - 1];
1668                self.fill_disc(first.0, first.1, half, argb);
1669                self.fill_disc(last.0, last.1, half, argb);
1670            }
1671        }
1672    }
1673
1674    /// 結合部 1 箇所を埋める。
1675    #[allow(clippy::too_many_arguments)]
1676    fn fill_joint(
1677        &mut self,
1678        prev: (f32, f32),
1679        cur: (f32, f32),
1680        next: (f32, f32),
1681        half: f32,
1682        argb: u32,
1683        join: LineJoin,
1684        limit: f32,
1685    ) {
1686        if join == LineJoin::Round {
1687            self.fill_disc(cur.0, cur.1, half, argb);
1688            return;
1689        }
1690        let (Some((d1x, d1y, _)), Some((d2x, d2y, _))) =
1691            (unit_delta(prev, cur), unit_delta(cur, next))
1692        else {
1693            return;
1694        };
1695        // 曲がる向き。外側だけを埋めれば足りる(内側は線分の四角形が覆う)。
1696        let cross = d1x * d2y - d1y * d2x;
1697        if libm::fabsf(cross) < 1e-6 {
1698            // まっすぐ、または折り返し。角は生じないので何もしない。
1699            return;
1700        }
1701        // 左法線を (-dy, dx) とすると、左折(cross>0)では外側は右。
1702        let s = if cross > 0.0 { -1.0f32 } else { 1.0f32 };
1703        let p1 = (cur.0 + s * -d1y * half, cur.1 + s * d1x * half);
1704        let p2 = (cur.0 + s * -d2y * half, cur.1 + s * d2x * half);
1705
1706        if join == LineJoin::Miter {
1707            if let Some(mp) = line_intersection(p1, (d1x, d1y), p2, (d2x, d2y)) {
1708                let (mx, my) = (mp.0 - cur.0, mp.1 - cur.1);
1709                let miter_len = libm::sqrtf(mx * mx + my * my);
1710                // 鋭角では尖りが際限なく伸びる。仕様どおり
1711                // miterLength/halfWidth が上限を超えたら bevel へ落とす。
1712                if miter_len / half <= limit {
1713                    self.fill_quad(cur, p1, mp, p2, argb);
1714                    return;
1715                }
1716            }
1717        }
1718        // Bevel(および miter の打ち切り)。外側を直線で結ぶ三角形。
1719        self.fill_tri(cur, p1, p2, argb);
1720    }
1721
1722    fn fill_quad(
1723        &mut self,
1724        a: (f32, f32),
1725        b: (f32, f32),
1726        c: (f32, f32),
1727        d: (f32, f32),
1728        argb: u32,
1729    ) {
1730        for p in [a, b, c, d] {
1731            if !p.0.is_finite() || !p.1.is_finite() {
1732                return;
1733            }
1734        }
1735        let poly = alloc::vec![SubPath {
1736            points: alloc::vec![a, b, c, d],
1737            closed: true,
1738        }];
1739        self.fill_path(&poly, FillRule::NonZero, argb);
1740    }
1741
1742    fn fill_tri(&mut self, a: (f32, f32), b: (f32, f32), c: (f32, f32), argb: u32) {
1743        for p in [a, b, c] {
1744            if !p.0.is_finite() || !p.1.is_finite() {
1745                return;
1746            }
1747        }
1748        let poly = alloc::vec![SubPath {
1749            points: alloc::vec![a, b, c],
1750            closed: true,
1751        }];
1752        self.fill_path(&poly, FillRule::NonZero, argb);
1753    }
1754}
1755
1756/// `a` から `b` への単位ベクトルと距離。長さが 0 なら `None`。
1757pub fn unit_delta(a: (f32, f32), b: (f32, f32)) -> Option<(f32, f32, f32)> {
1758    let (dx, dy) = (b.0 - a.0, b.1 - a.1);
1759    if !dx.is_finite() || !dy.is_finite() {
1760        return None;
1761    }
1762    let len = libm::sqrtf(dx * dx + dy * dy);
1763    if len < 1e-6 {
1764        return None;
1765    }
1766    Some((dx / len, dy / len, len))
1767}
1768
1769/// JS が canvas へ描いたことを示す旗(エッジトリガ再描画用)。
1770///
1771/// canvas への描画はバッキングストア(`Surface`)を書き換えるだけで、
1772/// 画面には出ない。以前は「他の理由で再描画が起きたとき」にだけ
1773/// ついでに反映されていたため、JS が描いても画面が変わらないことがあった。
1774///
1775/// 立てっぱなしにすると毎フレーム全画面を描き直して画面が波打つので、
1776/// 画像完了(`image_results_ready`)と同じく**変化があった一度だけ**
1777/// 再描画させる。描画側が `take_canvas_dirty` で降ろす。
1778static CANVAS_DIRTY: core::sync::atomic::AtomicBool =
1779    core::sync::atomic::AtomicBool::new(false);
1780
1781/// 画素が変わったことを記録する。`Surface` の塗り口から呼ぶ。
1782pub fn mark_canvas_dirty() {
1783    CANVAS_DIRTY.store(true, core::sync::atomic::Ordering::Relaxed);
1784}
1785
1786/// 変化があったかを見る(降ろさない)。再描画するかの判断に使う。
1787pub fn canvas_dirty() -> bool {
1788    CANVAS_DIRTY.load(core::sync::atomic::Ordering::Relaxed)
1789}
1790
1791/// 変化があったかを見て、同時に降ろす。描画を始める側から呼ぶ。
1792pub fn take_canvas_dirty() -> bool {
1793    CANVAS_DIRTY.swap(false, core::sync::atomic::Ordering::Relaxed)
1794}
1795
1796/// 破線のパターンを正規化する(`setLineDash` の引数検査)。
1797///
1798/// 仕様では、負の値や非有限が混じるパターンは**まるごと捨てる**
1799/// (一部だけ直すのではない)。奇数個なら 2 周ぶんに複製して偶数個にする。
1800/// 全部 0 のパターンは「破線なし」と同じ扱い。
1801pub fn normalize_dash(pattern: &[f32]) -> Option<Vec<f32>> {
1802    if pattern.is_empty() {
1803        return None;
1804    }
1805    for v in pattern {
1806        if !v.is_finite() || *v < 0.0 {
1807            return None;
1808        }
1809    }
1810    if pattern.iter().all(|v| *v == 0.0) {
1811        return None;
1812    }
1813    let mut out: Vec<f32> = pattern.to_vec();
1814    // 奇数個なら繰り返して偶数個にする(仕様)。
1815    if out.len() % 2 == 1 {
1816        let dup = out.clone();
1817        out.extend(dup);
1818    }
1819    Some(out)
1820}
1821
1822/// 折れ線を破線に切り分ける。
1823///
1824/// `pattern` は「描く長さ・空ける長さ」の交互。`offset` はパターンの
1825/// 開始位置をずらす量(`lineDashOffset`)。戻り値は描く区間だけを集めた
1826/// 開いた折れ線の並びで、そのまま `stroke_path_styled` へ渡せる。
1827///
1828/// 線分の途中でパターンが切り替わるので、線分を長さで分割しながら進む。
1829pub fn dash_subpaths(subpaths: &[SubPath], pattern: &[f32], offset: f32) -> Vec<SubPath> {
1830    let Some(pat) = normalize_dash(pattern) else {
1831        return subpaths.to_vec();
1832    };
1833    let total: f32 = pat.iter().sum();
1834    if total <= 0.0 {
1835        return subpaths.to_vec();
1836    }
1837    let mut out: Vec<SubPath> = Vec::new();
1838    for sp in subpaths {
1839        let n = sp.points.len();
1840        if n < 2 {
1841            continue;
1842        }
1843        // パターン内の位置。offset は仕様どおり周期で巻き戻す。
1844        let mut phase = if offset.is_finite() {
1845            let m = libm::fmodf(offset, total);
1846            if m < 0.0 {
1847                m + total
1848            } else {
1849                m
1850            }
1851        } else {
1852            0.0
1853        };
1854        // phase がどの区間にいるか(偶数番が「描く」)。
1855        let mut idx = 0usize;
1856        while phase >= pat[idx] {
1857            phase -= pat[idx];
1858            idx = (idx + 1) % pat.len();
1859        }
1860        let mut drawing = idx % 2 == 0;
1861        let mut cur: Vec<(f32, f32)> = Vec::new();
1862        if drawing {
1863            cur.push(sp.points[0]);
1864        }
1865
1866        let seg_end = if sp.closed { n } else { n - 1 };
1867        for i in 0..seg_end {
1868            let a = sp.points[i];
1869            let b = sp.points[(i + 1) % n];
1870            let Some((dx, dy, len)) = unit_delta(a, b) else {
1871                continue;
1872            };
1873            let mut travelled = 0.0f32;
1874            while travelled < len {
1875                // この区間の残り長。
1876                let remain = pat[idx] - phase;
1877                let step = remain.min(len - travelled);
1878                travelled += step;
1879                phase += step;
1880                let p = (a.0 + dx * travelled, a.1 + dy * travelled);
1881                if phase >= pat[idx] - 1e-6 {
1882                    // 区間の切り替わり。描いていたなら、ここで区切る。
1883                    if drawing {
1884                        cur.push(p);
1885                        if cur.len() >= 2 {
1886                            out.push(SubPath {
1887                                points: core::mem::take(&mut cur),
1888                                closed: false,
1889                            });
1890                        } else {
1891                            cur.clear();
1892                        }
1893                    } else {
1894                        cur.clear();
1895                        cur.push(p);
1896                    }
1897                    phase = 0.0;
1898                    idx = (idx + 1) % pat.len();
1899                    drawing = !drawing;
1900                } else if drawing {
1901                    // 線分の終わりで、まだ同じ区間の途中。
1902                    cur.push(p);
1903                }
1904            }
1905        }
1906        if drawing && cur.len() >= 2 {
1907            out.push(SubPath {
1908                points: cur,
1909                closed: false,
1910            });
1911        }
1912    }
1913    out
1914}
1915
1916impl Canvas2dContext {
1917    /// 現在の破線設定をパスへ適用する。実線ならそのまま返す。
1918    pub fn apply_dash(&self, subpaths: &[SubPath]) -> Vec<SubPath> {
1919        if self.state.line_dash.is_empty() {
1920            return subpaths.to_vec();
1921        }
1922        dash_subpaths(subpaths, &self.state.line_dash, self.state.line_dash_offset)
1923    }
1924}
1925
1926// ───────────── クリップ領域 ─────────────
1927
1928/// クリップ領域(`clip()`)。画素ごとに「描いてよいか」を持つ。
1929///
1930/// 走査線ベースの塗りに合わせて、パスをその場でラスタライズして持つ。
1931/// 領域を式のまま持つと、交差のたびに判定が複雑になっていく。
1932#[derive(Debug, Clone)]
1933pub struct ClipMask {
1934    pub width: u32,
1935    pub height: u32,
1936    /// `true` の画素だけ描ける。長さは `width * height`。
1937    pub bits: Vec<bool>,
1938}
1939
1940impl ClipMask {
1941    /// パスからクリップ領域を作る。塗りと同じ走査線・同じ巻き数規則を使う。
1942    ///
1943    /// 塗りとずれた判定をすると、クリップの縁に 1px の隙間や food が出る。
1944    pub fn from_path(width: u32, height: u32, subpaths: &[SubPath], rule: FillRule) -> ClipMask {
1945        let mut bits = alloc::vec![false; (width as usize) * (height as usize)];
1946        for y in 0..height {
1947            // 塗りと同じくピクセル中心で判定する。
1948            let spans = scanline_spans(subpaths, y as f32 + 0.5, rule);
1949            for (x0, x1) in spans {
1950                let a = (libm::ceilf(x0 - 0.5) as i32).max(0);
1951                let b = (libm::ceilf(x1 - 0.5) as i32).min(width as i32);
1952                for x in a..b {
1953                    bits[y as usize * width as usize + x as usize] = true;
1954                }
1955            }
1956        }
1957        ClipMask {
1958            width,
1959            height,
1960            bits,
1961        }
1962    }
1963
1964    /// 2 つのクリップ領域の交わり。`clip()` は既存の領域を**狭める**だけで、
1965    /// 広げることはできない(仕様)。
1966    pub fn intersect(&self, other: &ClipMask) -> ClipMask {
1967        if self.width != other.width || self.height != other.height {
1968            // 大きさが食い違うことは無い想定。安全側(狭い方)へ倒す。
1969            return self.clone();
1970        }
1971        let bits = self
1972            .bits
1973            .iter()
1974            .zip(other.bits.iter())
1975            .map(|(a, b)| *a && *b)
1976            .collect();
1977        ClipMask {
1978            width: self.width,
1979            height: self.height,
1980            bits,
1981        }
1982    }
1983
1984    /// その画素に描いてよいか。範囲外は描けない。
1985    pub fn allows(&self, x: i32, y: i32) -> bool {
1986        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
1987            return false;
1988        }
1989        self.bits[y as usize * self.width as usize + x as usize]
1990    }
1991}
1992
1993impl Canvas2dContext {
1994    /// 現在のパスでクリップ領域を狭める(`clip()`)。
1995    ///
1996    /// `save`/`restore` で元に戻る。状態を複製するだけで復帰できるよう
1997    /// `Rc` で共有しており、色と違って**正しく戻せる**。
1998    pub fn clip(&mut self, rule: FillRule) {
1999        let sub = self.path.finish();
2000        if sub.is_empty() {
2001            return;
2002        }
2003        let mask = ClipMask::from_path(self.surface.width, self.surface.height, &sub, rule);
2004        let merged = match &self.state.clip {
2005            Some(prev) => prev.intersect(&mask),
2006            None => mask,
2007        };
2008        self.state.clip = Some(alloc::sync::Arc::new(merged));
2009    }
2010
2011    /// 描画前に、現在のクリップ領域を `Surface` へ渡す。
2012    pub fn sync_clip(&mut self) {
2013        self.surface.clip = self.state.clip.clone();
2014    }
2015}
2016
2017// ───────────── ImageData ─────────────
2018
2019/// `Surface` の一部を `ImageData` のバイト列(RGBA8・行優先)へ取り出す。
2020///
2021/// 面の外にはみ出した部分は**透明**として埋める(仕様)。
2022/// はみ出しを切り詰めると、返る配列の大きさが要求と食い違ってしまう。
2023pub fn get_image_data(s: &Surface, x: i32, y: i32, w: i32, h: i32) -> Vec<u8> {
2024    if w <= 0 || h <= 0 {
2025        return Vec::new();
2026    }
2027    let mut out = alloc::vec![0u8; (w as usize) * (h as usize) * 4];
2028    for row in 0..h {
2029        for col in 0..w {
2030            let (sx, sy) = (x + col, y + row);
2031            if sx < 0 || sy < 0 || sx as u32 >= s.width || sy as u32 >= s.height {
2032                continue;
2033            }
2034            let argb = s.pixels[sy as usize * s.width as usize + sx as usize];
2035            let o = ((row as usize) * (w as usize) + col as usize) * 4;
2036            out[o] = ((argb >> 16) & 0xFF) as u8;
2037            out[o + 1] = ((argb >> 8) & 0xFF) as u8;
2038            out[o + 2] = (argb & 0xFF) as u8;
2039            out[o + 3] = ((argb >> 24) & 0xFF) as u8;
2040        }
2041    }
2042    out
2043}
2044
2045/// `ImageData` のバイト列を `Surface` へ書き戻す。
2046///
2047/// `putImageData` は**合成しない**(上書きする)。クリップも無視する。
2048/// これは仕様で、`fill` 系とは意味論が違う。
2049pub fn put_image_data(s: &mut Surface, data: &[u8], w: i32, h: i32, x: i32, y: i32) {
2050    if w <= 0 || h <= 0 {
2051        return;
2052    }
2053    // 長さが足りない配列は、足りるぶんだけ書く(途中で落とさない)。
2054    mark_canvas_dirty();
2055    for row in 0..h {
2056        for col in 0..w {
2057            let o = ((row as usize) * (w as usize) + col as usize) * 4;
2058            if o + 3 >= data.len() {
2059                return;
2060            }
2061            let (dx, dy) = (x + col, y + row);
2062            if dx < 0 || dy < 0 || dx as u32 >= s.width || dy as u32 >= s.height {
2063                continue;
2064            }
2065            let argb = ((data[o + 3] as u32) << 24)
2066                | ((data[o] as u32) << 16)
2067                | ((data[o + 1] as u32) << 8)
2068                | (data[o + 2] as u32);
2069            s.pixels[dy as usize * s.width as usize + dx as usize] = argb;
2070        }
2071    }
2072}
2073
2074/// canvas の大きさ変更。`<canvas width>`/`height` への代入で呼ぶ。
2075///
2076/// 仕様では、大きさを代入すると**同じ値でも**内容と描画状態が
2077/// まるごと初期化される。これを省くと、大きさを設定し直す定型句
2078/// (`canvas.width = canvas.width` による消去)が効かなくなる。
2079///
2080/// 大きさが上限を超える等で面が作れなければ何もしない(元の面を残す)。
2081pub fn resize_context(id: u32, width: u32, height: u32) -> bool {
2082    let Some(fresh) = Surface::new(width, height) else {
2083        return false;
2084    };
2085    mark_canvas_dirty();
2086    with_context(id, |c| {
2087        c.surface = fresh;
2088        c.state = DrawState::default();
2089        c.path = PathBuilder::new();
2090        c.stack.clear();
2091    })
2092    .is_some()
2093}
2094
2095// ───────────── 画像の転送 ─────────────
2096
2097/// 転送元の画素(RGBA8・行優先)。
2098pub struct ImageSource<'a> {
2099    pub width: u32,
2100    pub height: u32,
2101    pub rgba: &'a [u8],
2102}
2103
2104impl ImageSource<'_> {
2105    /// 最近傍で 1 画素を読む。範囲外は透明。
2106    pub fn sample(&self, x: i32, y: i32) -> u32 {
2107        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
2108            return 0;
2109        }
2110        let o = (y as usize * self.width as usize + x as usize) * 4;
2111        if o + 3 >= self.rgba.len() {
2112            return 0;
2113        }
2114        ((self.rgba[o + 3] as u32) << 24)
2115            | ((self.rgba[o] as u32) << 16)
2116            | ((self.rgba[o + 1] as u32) << 8)
2117            | (self.rgba[o + 2] as u32)
2118    }
2119
2120    /// バイリニア補間で 1 画素を読む(imageSmoothingEnabled 用)。
2121    pub fn sample_bilinear(&self, sxf: f32, syf: f32) -> u32 {
2122        let u = sxf - 0.5;
2123        let v = syf - 0.5;
2124        let x0 = libm::floorf(u) as i32;
2125        let y0 = libm::floorf(v) as i32;
2126        let x1 = x0 + 1;
2127        let y1 = y0 + 1;
2128
2129        let fx = ((u - x0 as f32) * 256.0).clamp(0.0, 256.0) as u32;
2130        let fy = ((v - y0 as f32) * 256.0).clamp(0.0, 256.0) as u32;
2131
2132        let c00 = self.sample(x0, y0);
2133        let c10 = self.sample(x1, y0);
2134        let c01 = self.sample(x0, y1);
2135        let c11 = self.sample(x1, y1);
2136
2137        if c00 == c10 && c10 == c01 && c01 == c11 {
2138            return c00;
2139        }
2140
2141        let w00 = (256 - fx) * (256 - fy);
2142        let w10 = fx * (256 - fy);
2143        let w01 = (256 - fx) * fy;
2144        let w11 = fx * fy;
2145
2146        let a = (((c00 >> 24) & 0xFF) * w00
2147            + ((c10 >> 24) & 0xFF) * w10
2148            + ((c01 >> 24) & 0xFF) * w01
2149            + ((c11 >> 24) & 0xFF) * w11 + 32768) >> 16;
2150
2151        let r = (((c00 >> 16) & 0xFF) * w00
2152            + ((c10 >> 16) & 0xFF) * w10
2153            + ((c01 >> 16) & 0xFF) * w01
2154            + ((c11 >> 16) & 0xFF) * w11 + 32768) >> 16;
2155
2156        let g = (((c00 >> 8) & 0xFF) * w00
2157            + ((c10 >> 8) & 0xFF) * w10
2158            + ((c01 >> 8) & 0xFF) * w01
2159            + ((c11 >> 8) & 0xFF) * w11 + 32768) >> 16;
2160
2161        let b = ((c00 & 0xFF) * w00
2162            + (c10 & 0xFF) * w10
2163            + (c01 & 0xFF) * w01
2164            + (c11 & 0xFF) * w11 + 32768) >> 16;
2165
2166        (a.min(255) << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255)
2167    }
2168}
2169
2170/// 転送の指定(`drawImage` の 9 引数形に対応)。
2171#[derive(Debug, Clone, Copy)]
2172pub struct BlitSpec {
2173    /// 転送元の矩形。
2174    pub sx: f32,
2175    pub sy: f32,
2176    pub sw: f32,
2177    pub sh: f32,
2178    /// 転送先の矩形(変換前の canvas 座標)。
2179    pub dx: f32,
2180    pub dy: f32,
2181    pub dw: f32,
2182    pub dh: f32,
2183}
2184
2185impl Surface {
2186    /// 画像を現在の変換のもとで転送する(`drawImage`)。
2187    ///
2188    /// # なぜ逆変換で引くか
2189    ///
2190    /// 転送元を 1 画素ずつ変換先へ「押し出す」と、拡大時に隙間が空き
2191    /// 回転時には縞が出る。転送先の画素ごとに**逆変換で元を引く**と、
2192    /// どの倍率・角度でも穴が空かない。
2193    ///
2194    /// 変換が退化している(面積 0 に潰れる)場合は何もしない。
2195    pub fn draw_image(&mut self, src: &ImageSource, spec: &BlitSpec, m: &Matrix, alpha: f32, smooth: bool) {
2196        if spec.dw == 0.0 || spec.dh == 0.0 || spec.sw == 0.0 || spec.sh == 0.0 {
2197            return;
2198        }
2199        for v in [
2200            spec.sx, spec.sy, spec.sw, spec.sh, spec.dx, spec.dy, spec.dw, spec.dh,
2201        ] {
2202            if !v.is_finite() {
2203                return;
2204            }
2205        }
2206        let Some(inv) = m.invert() else {
2207            return;
2208        };
2209        mark_canvas_dirty();
2210
2211        // 転送先の 4 隅を変換して、走査すべき範囲を求める。
2212        let corners = [
2213            m.apply(spec.dx, spec.dy),
2214            m.apply(spec.dx + spec.dw, spec.dy),
2215            m.apply(spec.dx + spec.dw, spec.dy + spec.dh),
2216            m.apply(spec.dx, spec.dy + spec.dh),
2217        ];
2218        let mut x0 = corners[0].0;
2219        let mut x1 = corners[0].0;
2220        let mut y0 = corners[0].1;
2221        let mut y1 = corners[0].1;
2222        for (cx, cy) in corners.iter().skip(1) {
2223            x0 = x0.min(*cx);
2224            x1 = x1.max(*cx);
2225            y0 = y0.min(*cy);
2226            y1 = y1.max(*cy);
2227        }
2228        let px0 = (libm::floorf(x0) as i32).max(0);
2229        let px1 = (libm::ceilf(x1) as i32).min(self.width as i32);
2230        let py0 = (libm::floorf(y0) as i32).max(0);
2231        let py1 = (libm::ceilf(y1) as i32).min(self.height as i32);
2232
2233        let a = alpha.clamp(0.0, 1.0);
2234        for py in py0..py1 {
2235            for px in px0..px1 {
2236                // 画素中心を逆変換して、変換前の canvas 座標へ戻す。
2237                let (ux, uy) = inv.apply(px as f32 + 0.5, py as f32 + 0.5);
2238                // 転送先の矩形の中だけを描く。
2239                let tx = (ux - spec.dx) / spec.dw;
2240                let ty = (uy - spec.dy) / spec.dh;
2241                if !(0.0..1.0).contains(&tx) || !(0.0..1.0).contains(&ty) {
2242                    continue;
2243                }
2244                // 転送元の対応位置(smooth に応じてバイリニア / 最近傍)。
2245                let sxf = spec.sx + tx * spec.sw;
2246                let syf = spec.sy + ty * spec.sh;
2247                let argb = if smooth {
2248                    src.sample_bilinear(sxf, syf)
2249                } else {
2250                    src.sample(libm::floorf(sxf) as i32, libm::floorf(syf) as i32)
2251                };
2252                if argb >> 24 == 0 {
2253                    continue;
2254                }
2255                let argb = if a >= 1.0 {
2256                    argb
2257                } else {
2258                    let na = (((argb >> 24) & 0xFF) as f32 * a) as u32;
2259                    (na << 24) | (argb & 0x00FF_FFFF)
2260                };
2261                self.blend_pixel(px, py, argb);
2262            }
2263        }
2264    }
2265}
2266
2267// ───────────── テキスト ─────────────
2268
2269/// `textAlign`。
2270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2271pub enum TextAlign {
2272    Start,
2273    End,
2274    Left,
2275    Right,
2276    Center,
2277}
2278
2279/// `textBaseline`。
2280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2281pub enum TextBaseline {
2282    Top,
2283    Hanging,
2284    Middle,
2285    Alphabetic,
2286    Ideographic,
2287    Bottom,
2288}
2289
2290pub fn parse_text_align(s: &str) -> Option<TextAlign> {
2291    match s.trim() {
2292        "start" => Some(TextAlign::Start),
2293        "end" => Some(TextAlign::End),
2294        "left" => Some(TextAlign::Left),
2295        "right" => Some(TextAlign::Right),
2296        "center" => Some(TextAlign::Center),
2297        _ => None,
2298    }
2299}
2300
2301pub fn parse_text_baseline(s: &str) -> Option<TextBaseline> {
2302    match s.trim() {
2303        "top" => Some(TextBaseline::Top),
2304        "hanging" => Some(TextBaseline::Hanging),
2305        "middle" => Some(TextBaseline::Middle),
2306        "alphabetic" => Some(TextBaseline::Alphabetic),
2307        "ideographic" => Some(TextBaseline::Ideographic),
2308        "bottom" => Some(TextBaseline::Bottom),
2309        _ => None,
2310    }
2311}
2312
2313/// 指定位置から見た、描き始めの x のずれ。
2314///
2315/// `start`/`end` は書字方向で決まる。この処理系は左から右のみ扱うので
2316/// `start` は `left`、`end` は `right` と同じになる。
2317pub fn align_offset(align: TextAlign, total_advance: f32) -> f32 {
2318    match align {
2319        TextAlign::Start | TextAlign::Left => 0.0,
2320        TextAlign::End | TextAlign::Right => -total_advance,
2321        TextAlign::Center => -total_advance / 2.0,
2322    }
2323}
2324
2325/// 指定位置から見た、ベースラインの y のずれ。
2326///
2327/// 字形はベースライン基準で置かれるので、`top` 指定なら
2328/// ベースラインを下げる(正の向きへずらす)。
2329/// 比率はこの処理系の字形配置(アセンダ 0.85)に合わせた近似で、
2330/// フォントごとの実測値ではない。
2331pub fn baseline_offset(baseline: TextBaseline, size_px: f32) -> f32 {
2332    match baseline {
2333        TextBaseline::Alphabetic => 0.0,
2334        TextBaseline::Top => size_px * 0.85,
2335        TextBaseline::Hanging => size_px * 0.8,
2336        TextBaseline::Middle => size_px * 0.35,
2337        TextBaseline::Ideographic | TextBaseline::Bottom => -size_px * 0.15,
2338    }
2339}
2340
2341/// `font` から文字の大きさ(px)を取り出す。
2342///
2343/// CSS の `font` 一括指定は語順が自由なので、**`px` で終わる数値**を
2344/// 探す。`pt`/`em` 等は換算せず、見つからなければ `None`
2345/// (呼び出し側が既定の 10px を使う)。
2346pub fn parse_font_size(font: &str) -> Option<f32> {
2347    for tok in font.split(|c: char| c.is_whitespace() || c == '/') {
2348        let t = tok.trim();
2349        let Some(num) = t.strip_suffix("px") else {
2350            continue;
2351        };
2352        if let Ok(v) = num.parse::<f32>() {
2353            if v.is_finite() && v > 0.0 {
2354                return Some(v);
2355            }
2356        }
2357    }
2358    None
2359}
2360
2361/// 1 文字ぶんの字形(アルファ値のビットマップ)。
2362///
2363/// フォントの実装(`kernel::vector_font`)に依存しないよう、
2364/// ここでは**値として受け取る**。canvas 側を純粋に保つため。
2365pub struct GlyphBitmap<'a> {
2366    pub width: u32,
2367    pub height: u32,
2368    /// 字形の左上を、ペン位置(ベースライン左端)から見たずれ。
2369    pub x_offset: i32,
2370    pub y_offset: i32,
2371    /// 次の文字までの送り幅。
2372    pub advance: f32,
2373    /// 長さは `width * height`。0 は透明。
2374    pub alpha: &'a [u8],
2375}
2376
2377impl Surface {
2378    /// 字形を 1 つ描く。
2379    ///
2380    /// 字形は軸に沿ったビットマップだが、**現在の変換の下で**置きたい。
2381    /// アルファを色に載せた画像として `draw_image` に通すことで、
2382    /// 回転・拡大も転送と同じ経路で扱える(別実装を増やさない)。
2383    pub fn draw_glyph(&mut self, g: &GlyphBitmap, pen_x: f32, pen_y: f32, argb: u32, m: &Matrix) {
2384        if g.width == 0 || g.height == 0 {
2385            return;
2386        }
2387        let n = (g.width as usize) * (g.height as usize);
2388        if g.alpha.len() < n {
2389            return;
2390        }
2391        let (r, gg, b) = ((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
2392        let base_a = (argb >> 24) & 0xFF;
2393        let mut rgba = alloc::vec![0u8; n * 4];
2394        for i in 0..n {
2395            let a = (g.alpha[i] as u32 * base_a / 255) as u8;
2396            rgba[i * 4] = r as u8;
2397            rgba[i * 4 + 1] = gg as u8;
2398            rgba[i * 4 + 2] = b as u8;
2399            rgba[i * 4 + 3] = a;
2400        }
2401        let src = ImageSource {
2402            width: g.width,
2403            height: g.height,
2404            rgba: &rgba,
2405        };
2406        let spec = BlitSpec {
2407            sx: 0.0,
2408            sy: 0.0,
2409            sw: g.width as f32,
2410            sh: g.height as f32,
2411            dx: pen_x + g.x_offset as f32,
2412            dy: pen_y + g.y_offset as f32,
2413            dw: g.width as f32,
2414            dh: g.height as f32,
2415        };
2416        self.draw_image(&src, &spec, m, 1.0, false);
2417    }
2418
2419    /// 字形の輪郭(ストローク)を描く。
2420    ///
2421    /// 線幅 `line_width` に応じて近傍へ輪郭マスクを膨張(dilation)させて描画する。
2422    pub fn draw_glyph_stroke(
2423        &mut self,
2424        g: &GlyphBitmap,
2425        pen_x: f32,
2426        pen_y: f32,
2427        stroke_argb: u32,
2428        line_width: f32,
2429        m: &Matrix,
2430    ) {
2431        if g.width == 0 || g.height == 0 || line_width <= 0.0 {
2432            return;
2433        }
2434        let n = (g.width as usize) * (g.height as usize);
2435        if g.alpha.len() < n {
2436            return;
2437        }
2438        let r = (line_width / 2.0).clamp(0.5, 4.0);
2439        let ir = libm::ceilf(r) as i32;
2440        let w = g.width as i32;
2441        let h = g.height as i32;
2442
2443        let mut stroke_alpha = alloc::vec![0u8; n];
2444        for y in 0..h {
2445            for x in 0..w {
2446                let idx = (y * w + x) as usize;
2447                let a = g.alpha[idx];
2448                if a > 0 {
2449                    for dy in -ir..=ir {
2450                        for dx in -ir..=ir {
2451                            let dist = libm::sqrtf((dx * dx + dy * dy) as f32);
2452                            if dist <= r {
2453                                let nx = x + dx;
2454                                let ny = y + dy;
2455                                if nx >= 0 && nx < w && ny >= 0 && ny < h {
2456                                    let nidx = (ny * w + nx) as usize;
2457                                    let falloff = if r > 0.0 {
2458                                        (1.0 - (dist / (r + 0.5))).clamp(0.0, 1.0)
2459                                    } else {
2460                                        1.0
2461                                    };
2462                                    let strk_val = (a as f32 * falloff) as u8;
2463                                    if strk_val > stroke_alpha[nidx] {
2464                                        stroke_alpha[nidx] = strk_val;
2465                                    }
2466                                }
2467                            }
2468                        }
2469                    }
2470                }
2471            }
2472        }
2473        let (r_col, g_col, b_col) = (
2474            (stroke_argb >> 16) & 0xFF,
2475            (stroke_argb >> 8) & 0xFF,
2476            stroke_argb & 0xFF,
2477        );
2478        let base_a = (stroke_argb >> 24) & 0xFF;
2479        let mut rgba = alloc::vec![0u8; n * 4];
2480        for i in 0..n {
2481            let a = (stroke_alpha[i] as u32 * base_a / 255) as u8;
2482            rgba[i * 4] = r_col as u8;
2483            rgba[i * 4 + 1] = g_col as u8;
2484            rgba[i * 4 + 2] = b_col as u8;
2485            rgba[i * 4 + 3] = a;
2486        }
2487        let src = ImageSource {
2488            width: g.width,
2489            height: g.height,
2490            rgba: &rgba,
2491        };
2492        let spec = BlitSpec {
2493            sx: 0.0,
2494            sy: 0.0,
2495            sw: g.width as f32,
2496            sh: g.height as f32,
2497            dx: pen_x + g.x_offset as f32,
2498            dy: pen_y + g.y_offset as f32,
2499            dw: g.width as f32,
2500            dh: g.height as f32,
2501        };
2502        self.draw_image(&src, &spec, m, 1.0, false);
2503    }
2504}
2505
2506
2507// ───────────── SVG パス文字列のパース ─────────────
2508
2509/// SVG パス文字列から浮動小数点数を順次読み取るトークナイザ。
2510struct SvgPathTokenizer<'a> {
2511    data: &'a [u8],
2512    pos: usize,
2513}
2514
2515impl<'a> SvgPathTokenizer<'a> {
2516    fn new(data: &'a str) -> Self {
2517        Self {
2518            data: data.as_bytes(),
2519            pos: 0,
2520        }
2521    }
2522
2523    fn skip_whitespace_and_comma(&mut self) {
2524        while self.pos < self.data.len() {
2525            let c = self.data[self.pos];
2526            if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == b',' {
2527                self.pos += 1;
2528            } else {
2529                break;
2530            }
2531        }
2532    }
2533
2534    fn next_cmd(&mut self) -> Option<char> {
2535        self.skip_whitespace_and_comma();
2536        if self.pos >= self.data.len() {
2537            return None;
2538        }
2539        let c = self.data[self.pos] as char;
2540        if c.is_ascii_alphabetic() {
2541            self.pos += 1;
2542            Some(c)
2543        } else {
2544            None
2545        }
2546    }
2547
2548    fn next_f32(&mut self) -> Option<f32> {
2549        self.skip_whitespace_and_comma();
2550        if self.pos >= self.data.len() {
2551            return None;
2552        }
2553        let start = self.pos;
2554        if self.pos < self.data.len() && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+') {
2555            self.pos += 1;
2556        }
2557        let mut has_digit = false;
2558        while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
2559            self.pos += 1;
2560            has_digit = true;
2561        }
2562        if self.pos < self.data.len() && self.data[self.pos] == b'.' {
2563            self.pos += 1;
2564            while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
2565                self.pos += 1;
2566                has_digit = true;
2567            }
2568        }
2569        if has_digit && self.pos < self.data.len() && (self.data[self.pos] == b'e' || self.data[self.pos] == b'E') {
2570            self.pos += 1;
2571            if self.pos < self.data.len() && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+') {
2572                self.pos += 1;
2573            }
2574            while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
2575                self.pos += 1;
2576            }
2577        }
2578        if !has_digit {
2579            self.pos = start;
2580            return None;
2581        }
2582        let s = core::str::from_utf8(&self.data[start..self.pos]).ok()?;
2583        s.parse::<f32>().ok()
2584    }
2585}
2586
2587/// SVG パス文字列 `d` を解析し、`PathBuilder` へ追加する。
2588pub fn parse_svg_path_into(d: &str, p: &mut PathBuilder, m: &Matrix) {
2589    let mut tok = SvgPathTokenizer::new(d);
2590    let mut curr_x = 0.0f32;
2591    let mut curr_y = 0.0f32;
2592    let mut start_x = 0.0f32;
2593    let mut start_y = 0.0f32;
2594    let mut last_cmd = ' ';
2595
2596    while tok.pos < tok.data.len() {
2597        tok.skip_whitespace_and_comma();
2598        if tok.pos >= tok.data.len() {
2599            break;
2600        }
2601        let cmd = if let Some(c) = tok.next_cmd() {
2602            last_cmd = c;
2603            c
2604        } else if last_cmd != ' ' {
2605            if last_cmd == 'M' {
2606                'L'
2607            } else if last_cmd == 'm' {
2608                'l'
2609            } else {
2610                last_cmd
2611            }
2612        } else {
2613            break;
2614        };
2615
2616        match cmd {
2617            'M' => {
2618                if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
2619                    curr_x = x;
2620                    curr_y = y;
2621                    start_x = x;
2622                    start_y = y;
2623                    p.move_to(m, x, y);
2624                }
2625            }
2626            'm' => {
2627                if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
2628                    curr_x += dx;
2629                    curr_y += dy;
2630                    start_x = curr_x;
2631                    start_y = curr_y;
2632                    p.move_to(m, curr_x, curr_y);
2633                }
2634            }
2635            'L' => {
2636                if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
2637                    curr_x = x;
2638                    curr_y = y;
2639                    p.line_to(m, x, y);
2640                }
2641            }
2642            'l' => {
2643                if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
2644                    curr_x += dx;
2645                    curr_y += dy;
2646                    p.line_to(m, curr_x, curr_y);
2647                }
2648            }
2649            'H' => {
2650                if let Some(x) = tok.next_f32() {
2651                    curr_x = x;
2652                    p.line_to(m, curr_x, curr_y);
2653                }
2654            }
2655            'h' => {
2656                if let Some(dx) = tok.next_f32() {
2657                    curr_x += dx;
2658                    p.line_to(m, curr_x, curr_y);
2659                }
2660            }
2661            'V' => {
2662                if let Some(y) = tok.next_f32() {
2663                    curr_y = y;
2664                    p.line_to(m, curr_x, curr_y);
2665                }
2666            }
2667            'v' => {
2668                if let Some(dy) = tok.next_f32() {
2669                    curr_y += dy;
2670                    p.line_to(m, curr_x, curr_y);
2671                }
2672            }
2673            'C' => {
2674                if let (Some(x1), Some(y1), Some(x2), Some(y2), Some(x), Some(y)) = (
2675                    tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2676                ) {
2677                    p.bezier_curve_to(m, x1, y1, x2, y2, x, y);
2678                    curr_x = x;
2679                    curr_y = y;
2680                }
2681            }
2682            'c' => {
2683                if let (Some(dx1), Some(dy1), Some(dx2), Some(dy2), Some(dx), Some(dy)) = (
2684                    tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2685                ) {
2686                    p.bezier_curve_to(m, curr_x + dx1, curr_y + dy1, curr_x + dx2, curr_y + dy2, curr_x + dx, curr_y + dy);
2687                    curr_x += dx;
2688                    curr_y += dy;
2689                }
2690            }
2691            'S' | 's' => {
2692                if cmd == 'S' {
2693                    if let (Some(x2), Some(y2), Some(x), Some(y)) = (
2694                        tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2695                    ) {
2696                        p.bezier_curve_to(m, curr_x, curr_y, x2, y2, x, y);
2697                        curr_x = x;
2698                        curr_y = y;
2699                    }
2700                } else {
2701                    if let (Some(dx2), Some(dy2), Some(dx), Some(dy)) = (
2702                        tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2703                    ) {
2704                        p.bezier_curve_to(m, curr_x, curr_y, curr_x + dx2, curr_y + dy2, curr_x + dx, curr_y + dy);
2705                        curr_x += dx;
2706                        curr_y += dy;
2707                    }
2708                }
2709            }
2710            'Q' => {
2711                if let (Some(x1), Some(y1), Some(x), Some(y)) = (
2712                    tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2713                ) {
2714                    p.quadratic_curve_to(m, x1, y1, x, y);
2715                    curr_x = x;
2716                    curr_y = y;
2717                }
2718            }
2719            'q' => {
2720                if let (Some(dx1), Some(dy1), Some(dx), Some(dy)) = (
2721                    tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2722                ) {
2723                    p.quadratic_curve_to(m, curr_x + dx1, curr_y + dy1, curr_x + dx, curr_y + dy);
2724                    curr_x += dx;
2725                    curr_y += dy;
2726                }
2727            }
2728            'Z' | 'z' => {
2729                p.close_path();
2730                curr_x = start_x;
2731                curr_y = start_y;
2732            }
2733            _ => {
2734                tok.pos += 1;
2735            }
2736        }
2737    }
2738}
2739
2740// ───────────── Path2D 登録表 ─────────────
2741
2742static PATH2D_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, PathBuilder>> =
2743    spin::Mutex::new(alloc::collections::BTreeMap::new());
2744static NEXT_PATH2D_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
2745const MAX_PATH2D: usize = 64;
2746
2747pub fn create_path2d(init_svg: Option<&str>) -> u32 {
2748    let id = NEXT_PATH2D_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2749    let mut builder = PathBuilder::new();
2750    if let Some(svg) = init_svg {
2751        parse_svg_path_into(svg, &mut builder, &Matrix::identity());
2752    }
2753    let mut map = PATH2D_MAP.lock();
2754    while map.len() >= MAX_PATH2D {
2755        if let Some(&oldest) = map.keys().next() {
2756            map.remove(&oldest);
2757        } else {
2758            break;
2759        }
2760    }
2761    map.insert(id, builder);
2762    id
2763}
2764
2765pub fn clone_path2d(src_id: u32) -> u32 {
2766    let new_id = NEXT_PATH2D_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2767    let mut map = PATH2D_MAP.lock();
2768    while map.len() >= MAX_PATH2D {
2769        if let Some(&oldest) = map.keys().next() {
2770            map.remove(&oldest);
2771        } else {
2772            break;
2773        }
2774    }
2775    let cloned = map.get(&src_id).cloned().unwrap_or_else(PathBuilder::new);
2776    map.insert(new_id, cloned);
2777    new_id
2778}
2779
2780pub fn with_path2d<R>(id: u32, f: impl FnOnce(&mut PathBuilder) -> R) -> Option<R> {
2781    let mut map = PATH2D_MAP.lock();
2782    map.get_mut(&id).map(f)
2783}
2784
2785pub fn get_path2d_subpaths(id: u32) -> Option<Vec<SubPath>> {
2786    let map = PATH2D_MAP.lock();
2787    map.get(&id).map(|p| p.finish())
2788}
2789
2790pub fn add_path_to_path2d(dst_id: u32, src_id: u32, transform: Option<&Matrix>) {
2791    let mut map = PATH2D_MAP.lock();
2792    if let Some(src) = map.get(&src_id).cloned() {
2793        if let Some(dst) = map.get_mut(&dst_id) {
2794            dst.add_path(&src, transform);
2795        }
2796    }
2797}
2798
2799impl Canvas2dContext {
2800    /// 外部のパス(Path2D 等)を指定して塗る。
2801    pub fn fill_subpaths(&mut self, subpaths: &[SubPath], rule: FillRule) {
2802        self.sync_clip();
2803        if let Some(ref grad) = self.state.fill_gradient {
2804            self.surface.fill_path_gradient(subpaths, rule, grad, self.state.global_alpha.clamp(0.0, 1.0));
2805        } else {
2806            let color = self.apply_alpha(self.state.fill);
2807            self.surface.fill_path(subpaths, rule, color);
2808        }
2809    }
2810
2811    /// 外部のパス(Path2D 等)を指定して線を描く。
2812    pub fn stroke_subpaths(&mut self, subpaths: &[SubPath]) {
2813        self.sync_clip();
2814        let color = self.apply_alpha(self.state.stroke);
2815        let w = self.state.line_width;
2816        let (cap, join, limit) = (
2817            self.state.line_cap,
2818            self.state.line_join,
2819            self.state.miter_limit,
2820        );
2821        let sub = self.apply_dash(subpaths);
2822        self.surface
2823            .stroke_path_styled(&sub, color, w, cap, join, limit);
2824    }
2825
2826    /// 外部のパス(Path2D 等)を指定してクリップする。
2827    pub fn clip_subpaths(&mut self, subpaths: &[SubPath], rule: FillRule) {
2828        if subpaths.is_empty() {
2829            return;
2830        }
2831        let mask = ClipMask::from_path(self.surface.width, self.surface.height, subpaths, rule);
2832        let merged = match &self.state.clip {
2833            Some(prev) => prev.intersect(&mask),
2834            None => mask,
2835        };
2836        self.state.clip = Some(alloc::sync::Arc::new(merged));
2837    }
2838}
2839
2840
2841// ───────────── CanvasGradient 登録表 ─────────────
2842
2843static GRADIENT_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, CanvasGradient>> =
2844    spin::Mutex::new(alloc::collections::BTreeMap::new());
2845static NEXT_GRADIENT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
2846const MAX_GRADIENTS: usize = 64;
2847
2848pub fn create_linear_gradient(x0: f32, y0: f32, x1: f32, y1: f32) -> u32 {
2849    let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2850    let grad = CanvasGradient::new_linear(x0, y0, x1, y1);
2851    let mut map = GRADIENT_MAP.lock();
2852    while map.len() >= MAX_GRADIENTS {
2853        if let Some(&oldest) = map.keys().next() {
2854            map.remove(&oldest);
2855        } else {
2856            break;
2857        }
2858    }
2859    map.insert(id, grad);
2860    id
2861}
2862
2863pub fn create_radial_gradient(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> u32 {
2864    let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2865    let grad = CanvasGradient::new_radial(x0, y0, r0, x1, y1, r1);
2866    let mut map = GRADIENT_MAP.lock();
2867    while map.len() >= MAX_GRADIENTS {
2868        if let Some(&oldest) = map.keys().next() {
2869            map.remove(&oldest);
2870        } else {
2871            break;
2872        }
2873    }
2874    map.insert(id, grad);
2875    id
2876}
2877
2878pub fn add_color_stop_to_gradient(id: u32, offset: f32, color: u32) {
2879    let mut map = GRADIENT_MAP.lock();
2880    if let Some(grad) = map.get_mut(&id) {
2881        grad.add_color_stop(offset, color);
2882    }
2883}
2884
2885pub fn get_gradient(id: u32) -> Option<CanvasGradient> {
2886    let map = GRADIENT_MAP.lock();
2887    map.get(&id).cloned()
2888}