#[derive(Debug, Clone, Copy)]
pub struct Matrix {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
pub e: f64,
pub f: f64,
}
impl Matrix {
pub fn identity() -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 0.0,
f: 0.0,
}
}
pub fn translate(tx: f64, ty: f64) -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: tx,
f: ty,
}
}
pub fn multiply(&self, other: &Matrix) -> Matrix {
Matrix {
a: self.a * other.a + self.b * other.c,
b: self.a * other.b + self.b * other.d,
c: self.c * other.a + self.d * other.c,
d: self.c * other.b + self.d * other.d,
e: self.e * other.a + self.f * other.c + other.e,
f: self.e * other.b + self.f * other.d + other.f,
}
}
pub fn transform_point(&self, x: f64, y: f64) -> (f64, f64) {
(
self.a * x + self.c * y + self.e,
self.b * x + self.d * y + self.f,
)
}
pub fn font_size_factor(&self) -> f64 {
(self.b * self.b + self.d * self.d).sqrt()
}
}
impl Default for Matrix {
fn default() -> Self {
Self::identity()
}
}
#[derive(Debug, Clone)]
pub struct TextState {
pub font_name: String,
pub font_size: f64,
pub char_spacing: f64,
pub word_spacing: f64,
pub horizontal_scaling: f64,
pub leading: f64,
pub rise: f64,
pub render_mode: i32,
}
impl Default for TextState {
fn default() -> Self {
Self {
font_name: String::new(),
font_size: 0.0,
char_spacing: 0.0,
word_spacing: 0.0,
horizontal_scaling: 100.0,
leading: 0.0,
rise: 0.0,
render_mode: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct GraphicsState {
pub ctm: Matrix,
pub text_matrix: Matrix,
pub text_line_matrix: Matrix,
pub text_state: TextState,
pub fill_color: Vec<f64>,
pub stroke_color: Vec<f64>,
pub fill_color_space_components: u8,
pub stroke_color_space_components: u8,
}
impl Default for GraphicsState {
fn default() -> Self {
Self {
ctm: Matrix::identity(),
text_matrix: Matrix::identity(),
text_line_matrix: Matrix::identity(),
text_state: TextState::default(),
fill_color: vec![0.0], stroke_color: vec![0.0],
fill_color_space_components: 1, stroke_color_space_components: 1,
}
}
}
impl GraphicsState {
pub fn begin_text(&mut self) {
self.text_matrix = Matrix::identity();
self.text_line_matrix = Matrix::identity();
}
pub fn text_rendering_matrix(&self) -> Matrix {
let font_matrix = Matrix {
a: self.text_state.font_size * (self.text_state.horizontal_scaling / 100.0),
b: 0.0,
c: 0.0,
d: self.text_state.font_size,
e: 0.0,
f: self.text_state.rise,
};
let tm_ctm = self.text_matrix.multiply(&self.ctm);
font_matrix.multiply(&tm_ctm)
}
pub fn text_position(&self) -> (f64, f64) {
let trm = self.text_rendering_matrix();
(trm.e, trm.f)
}
pub fn effective_font_size(&self) -> f64 {
let trm = self.text_rendering_matrix();
trm.font_size_factor()
}
pub fn translate_text(&mut self, tx: f64, ty: f64) {
let translation = Matrix::translate(tx, ty);
self.text_line_matrix = translation.multiply(&self.text_line_matrix);
self.text_matrix = self.text_line_matrix;
}
pub fn set_text_matrix(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
self.text_matrix = Matrix { a, b, c, d, e, f };
self.text_line_matrix = self.text_matrix;
}
pub fn next_line(&mut self) {
self.translate_text(0.0, -self.text_state.leading);
}
pub fn advance_text(&mut self, displacement: f64) {
let scaled = displacement * self.text_state.horizontal_scaling / 100.0;
self.text_matrix.e += scaled * self.text_matrix.a;
self.text_matrix.f += scaled * self.text_matrix.b;
}
}
#[derive(Default)]
pub struct GraphicsStateStack {
stack: Vec<GraphicsState>,
pub current: GraphicsState,
}
impl GraphicsStateStack {
pub fn save(&mut self) {
self.stack.push(self.current.clone());
}
pub fn restore(&mut self) {
if let Some(state) = self.stack.pop() {
self.current = state;
}
}
pub fn concat_ctm(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
let new_matrix = Matrix { a, b, c, d, e, f };
self.current.ctm = new_matrix.multiply(&self.current.ctm);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matrix_identity() {
let m = Matrix::identity();
let (x, y) = m.transform_point(10.0, 20.0);
assert!((x - 10.0).abs() < 1e-10);
assert!((y - 20.0).abs() < 1e-10);
}
#[test]
fn test_matrix_translate() {
let m = Matrix::translate(100.0, 200.0);
let (x, y) = m.transform_point(10.0, 20.0);
assert!((x - 110.0).abs() < 1e-10);
assert!((y - 220.0).abs() < 1e-10);
}
#[test]
fn test_matrix_multiply() {
let a = Matrix::translate(10.0, 20.0);
let b = Matrix::translate(30.0, 40.0);
let c = a.multiply(&b);
let (x, y) = c.transform_point(0.0, 0.0);
assert!((x - 40.0).abs() < 1e-10);
assert!((y - 60.0).abs() < 1e-10);
}
#[test]
fn test_text_translate() {
let mut gs = GraphicsState::default();
gs.text_state.font_size = 12.0;
gs.begin_text();
gs.translate_text(100.0, 700.0);
let (x, y) = gs.text_position();
assert!((x - 100.0 * 12.0).abs() < 1e-6 || (x - 100.0).abs() < 1e-6);
assert!(y.abs() > 0.0 || y.abs() < 1e-6);
}
#[test]
fn test_graphics_state_stack() {
let mut stack = GraphicsStateStack::default();
stack.current.text_state.font_size = 12.0;
stack.save();
stack.current.text_state.font_size = 24.0;
assert!((stack.current.text_state.font_size - 24.0).abs() < 1e-10);
stack.restore();
assert!((stack.current.text_state.font_size - 12.0).abs() < 1e-10);
}
}