document_graphics/chess/
render.rs1use super::*;
2use crate::{Raster, RasterOptions};
3use std::fmt::Write;
4#[derive(Debug, Clone, Copy, Default)]
5pub struct RenderOptions {
6 pub dark: bool,
7 pub flipped: bool,
8}
9pub fn to_svg(
12 position: &Position,
13 options: RenderOptions,
14 cancel: &AtomicBool,
15) -> Result<String, Error> {
16 check(cancel)?;
17 let (light, dark, frame, ink) = if options.dark {
18 ("#bfc9b4", "#607b62", "#292e2a", "#d9dfd5")
19 } else {
20 ("#eee8d5", "#829777", "#e3dfd2", "#39463b")
21 };
22 let mut svg = format!(
23 r#"<svg xmlns="http://www.w3.org/2000/svg" width="440" height="440" viewBox="0 0 440 440"><rect width="440" height="440" rx="7" fill="{frame}"/>"#
24 );
25 for row in 0..8 {
26 check(cancel)?;
27 for col in 0..8 {
28 let (file, rank) = if options.flipped {
29 (7 - col, row)
30 } else {
31 (col, 7 - row)
32 };
33 let square = Square::new(cozy_chess::File::index(file), cozy_chess::Rank::index(rank));
34 let x = 28 + col * 48;
35 let y = 28 + row * 48;
36 let fill = if (file + rank) % 2 == 0 { dark } else { light };
37 let _ = write!(
38 svg,
39 r#"<rect x="{x}" y="{y}" width="48" height="48" fill="{fill}"/>"#
40 );
41 if position
42 .last_move
43 .is_some_and(|(from, to)| from == square || to == square)
44 {
45 let _ = write!(
46 svg,
47 r##"<rect x="{x}" y="{y}" width="48" height="48" fill="#e1c754" fill-opacity=".55"/>"##
48 );
49 }
50 if let Some((color, piece)) = position.piece(square) {
51 let (body, edge, detail) = if color == Color::White {
52 ("#faf8ee", "#30382f", "#596451")
53 } else {
54 ("#252e28", "#e1e5d9", "#b8c4ad")
55 };
56 let _ = write!(
57 svg,
58 r#"<g transform="translate({x} {y})" stroke-linejoin="round" stroke-linecap="round" stroke-width="1.5" fill="{body}" stroke="{edge}">"#
59 );
60 match piece {
62 Piece::Pawn => svg.push_str(r#"<path d="M14 38h20l-2-5c-5-2-6-7-4-12a7 7 0 1 0-8 0c2 5 1 10-4 12z"/>"#),
63 Piece::Rook => svg.push_str(r#"<path d="M12 8h6v5h4V8h4v5h4V8h6v12l-5 4v9l4 5H13l4-5v-9l-5-4z"/>"#),
64 Piece::Knight => {
65 svg.push_str(r#"<path d="M13 38h23l-2-9c0-10-3-17-11-21l-5-3 1 7-9 11 4 5 9-5c0 6-6 7-7 10z"/>"#);
66 let _ = write!(svg, r#"<circle cx="22" cy="16" r="1.5" stroke="none" fill="{detail}"/><path d="M13 24l4 1m11-10c4 9 2 14-2 18" fill="none" stroke="{detail}"/>"#);
67 }
68 Piece::Bishop => {
69 svg.push_str(r#"<path d="M13 38h22l-4-6-3-7c8-8 4-13-4-19-8 6-12 11-4 19l-3 7z"/>"#);
70 let _ = write!(svg, r#"<path d="M26 11l-6 8" fill="none" stroke="{detail}"/>"#);
71 }
72 Piece::Queen => svg.push_str(r#"<path d="M13 38h22l-3-7 5-16-9 7-4-12-4 12-9-7 5 16z"/><circle cx="10" cy="12" r="3"/><circle cx="24" cy="7" r="3"/><circle cx="38" cy="12" r="3"/>"#),
73 Piece::King => svg.push_str(r#"<path d="M13 38h22l-3-9c9-11 2-17-8-9-10-8-17-2-8 9z"/><path d="M24 6v12m-5-7h10" fill="none" stroke-width="3"/>"#),
74 }
75 let _ = write!(
76 svg,
77 r#"<path d="M16 33h16" stroke="{detail}" fill="none"/></g>"#
78 );
79 }
80 }
81 let rank = if options.flipped { row + 1 } else { 8 - row };
82 let file = char::from(b'a' + if options.flipped { 7 - row } else { row } as u8);
83 let at = 52 + row * 48;
84 for (x, y, label) in [
85 (14, at + 4, rank.to_string()),
86 (426, at + 4, rank.to_string()),
87 (at, 19, file.to_string()),
88 (at, 430, file.to_string()),
89 ] {
90 let _ = write!(
91 svg,
92 r#"<text x="{x}" y="{y}" font-family="KaTeX_Main" font-size="13" text-anchor="middle" fill="{ink}">{label}</text>"#
93 );
94 }
95 }
96 svg.push_str("</svg>");
97 Ok(svg)
98}
99pub fn rasterize(
100 position: &Position,
101 options: RenderOptions,
102 fitting: RasterOptions,
103 cancel: &AtomicBool,
104) -> Result<Raster, Error> {
105 let svg = to_svg(position, options, cancel)?;
106 let raster = crate::svg::rasterize(&svg, fitting).map_err(|e| Error::Render(e.to_string()))?;
107 check(cancel)?;
108 Ok(raster)
109}