use std::rc::Rc;
use crate::model::dimension::{Dimension, SixtieThousandthDeg};
use crate::render::dimension::Pt;
use crate::render::geometry::{PtLineSegment, PtOffset, PtRect, PtSize};
use crate::render::resolve::color::RgbColor;
use crate::render::resolve::drawing_color::Rgba;
use crate::render::resolve::images::MediaEntry;
use crate::render::resolve::shape_geometry::SubPath;
#[derive(Debug, Clone)]
pub enum DrawCommand {
Text {
position: PtOffset,
text: Rc<str>,
font_family: Rc<str>,
char_spacing: Pt,
font_size: Pt,
bold: bool,
italic: bool,
color: RgbColor,
},
Underline {
line: PtLineSegment,
color: RgbColor,
width: Pt,
},
Line {
line: PtLineSegment,
color: RgbColor,
width: Pt,
},
Image {
rect: PtRect,
image_data: MediaEntry,
},
Rect {
rect: PtRect,
color: RgbColor,
},
LinkAnnotation {
rect: PtRect,
url: String,
},
InternalLink {
rect: PtRect,
destination: String,
},
NamedDestination {
position: PtOffset,
name: String,
},
Path {
origin: PtOffset,
rotation: Dimension<SixtieThousandthDeg>,
flip_h: bool,
flip_v: bool,
extent: PtSize,
paths: Vec<SubPath>,
fill: ResolvedFill,
stroke: Option<ResolvedStroke>,
effects: Vec<ResolvedEffect>,
},
}
#[derive(Clone, Debug)]
pub enum ResolvedFill {
None,
Solid(Rgba),
Gradient(ResolvedGradient),
Blip(ResolvedBlip),
Pattern(ResolvedPattern),
}
#[derive(Clone, Debug)]
pub struct ResolvedStroke {
pub width: Pt,
pub color: Rgba,
pub dash: ResolvedDashPattern,
pub cap: ResolvedLineCap,
pub join: ResolvedLineJoin,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResolvedLineCap {
Butt,
Round,
Square,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ResolvedLineJoin {
Round,
Bevel,
Miter,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ResolvedDashPattern {
Solid,
Dashes(Vec<Pt>),
}
#[derive(Clone, Debug)]
pub enum ResolvedEffect {
OuterShadow {
blur_radius: Pt,
offset: PtOffset,
color: Rgba,
},
}
#[derive(Clone, Debug)]
pub struct ResolvedGradient {
pub stops: Vec<GradientStopRgba>,
pub kind: ResolvedGradientKind,
}
#[derive(Clone, Copy, Debug)]
pub struct GradientStopRgba {
pub position: f32,
pub color: Rgba,
}
#[derive(Clone, Copy, Debug)]
pub enum ResolvedGradientKind {
Linear { angle_deg: f32 },
Radial,
}
#[derive(Clone, Debug)]
pub struct ResolvedBlip {
pub data: Rc<[u8]>,
pub format: crate::model::ImageFormat,
pub src_rect: Option<PtRect>,
}
#[derive(Clone, Debug)]
pub struct ResolvedPattern {
pub preset: crate::model::PresetPatternVal,
pub fg: Rgba,
pub bg: Rgba,
}
impl DrawCommand {
pub fn shift(&mut self, dx: Pt, dy: Pt) {
match self {
DrawCommand::Text { position, .. } => {
position.x += dx;
position.y += dy;
}
DrawCommand::Underline { line, .. } | DrawCommand::Line { line, .. } => {
line.start.x += dx;
line.start.y += dy;
line.end.x += dx;
line.end.y += dy;
}
DrawCommand::Image { rect, .. }
| DrawCommand::Rect { rect, .. }
| DrawCommand::LinkAnnotation { rect, .. }
| DrawCommand::InternalLink { rect, .. } => {
rect.origin.x += dx;
rect.origin.y += dy;
}
DrawCommand::NamedDestination { position, .. } => {
position.x += dx;
position.y += dy;
}
DrawCommand::Path { origin, .. } => {
origin.x += dx;
origin.y += dy;
}
}
}
pub fn shift_y(&mut self, dy: Pt) {
self.shift(Pt::ZERO, dy);
}
pub fn shift_x(&mut self, dx: Pt) {
self.shift(dx, Pt::ZERO);
}
}
#[derive(Debug, Clone)]
pub struct LayoutedPage {
pub commands: Vec<DrawCommand>,
pub page_size: PtSize,
}
impl LayoutedPage {
pub fn new(page_size: PtSize) -> Self {
Self {
commands: Vec::new(),
page_size,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shift_y_moves_text() {
let mut cmd = DrawCommand::Text {
position: PtOffset::new(Pt::new(10.0), Pt::new(20.0)),
text: "hi".into(),
font_family: Rc::from("Arial"),
char_spacing: Pt::ZERO,
font_size: Pt::new(12.0),
bold: false,
italic: false,
color: RgbColor::BLACK,
};
cmd.shift_y(Pt::new(5.0));
if let DrawCommand::Text { position, .. } = cmd {
assert_eq!(position.y.raw(), 25.0);
assert_eq!(position.x.raw(), 10.0); }
}
#[test]
fn shift_y_moves_line() {
let mut cmd = DrawCommand::Line {
line: PtLineSegment::new(
PtOffset::new(Pt::new(0.0), Pt::new(10.0)),
PtOffset::new(Pt::new(100.0), Pt::new(10.0)),
),
color: RgbColor::BLACK,
width: Pt::new(1.0),
};
cmd.shift_y(Pt::new(50.0));
if let DrawCommand::Line { line, .. } = cmd {
assert_eq!(line.start.y.raw(), 60.0, "Line y shifted");
}
}
#[test]
fn shift_y_moves_rect() {
let mut cmd = DrawCommand::Rect {
rect: PtRect::from_xywh(Pt::new(0.0), Pt::new(10.0), Pt::new(50.0), Pt::new(20.0)),
color: RgbColor::BLACK,
};
cmd.shift_y(Pt::new(100.0));
if let DrawCommand::Rect { rect, .. } = cmd {
assert_eq!(rect.origin.y.raw(), 110.0);
}
}
#[test]
fn layouted_page_new() {
let page = LayoutedPage::new(PtSize::new(Pt::new(612.0), Pt::new(792.0)));
assert!(page.commands.is_empty());
assert_eq!(page.page_size.width.raw(), 612.0);
}
#[test]
fn shift_y_moves_path_origin() {
use crate::model::dimension::Dimension;
let mut cmd = DrawCommand::Path {
origin: PtOffset::new(Pt::new(10.0), Pt::new(20.0)),
rotation: Dimension::new(0),
flip_h: false,
flip_v: false,
extent: PtSize::new(Pt::new(100.0), Pt::new(50.0)),
paths: vec![],
fill: ResolvedFill::None,
stroke: None,
effects: vec![],
};
cmd.shift_y(Pt::new(7.5));
if let DrawCommand::Path { origin, .. } = cmd {
assert_eq!(origin.y.raw(), 27.5);
assert_eq!(origin.x.raw(), 10.0);
} else {
panic!();
}
}
}