use crate::layout::Color;
use crate::object::Object;
use std::collections::HashMap;
fn key(s: &str) -> String {
s.to_string()
}
#[derive(Debug, Clone, Copy, Default)]
pub enum PatternPaintType {
#[default]
Colored = 1,
Uncolored = 2,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum PatternTilingType {
#[default]
ConstantSpacing = 1,
NoDistortion = 2,
ConstantSpacingFaster = 3,
}
#[derive(Debug, Clone)]
pub struct TilingPatternBuilder {
bbox: (f32, f32, f32, f32),
x_step: f32,
y_step: f32,
paint_type: PatternPaintType,
tiling_type: PatternTilingType,
content: Vec<u8>,
matrix: Option<[f32; 6]>,
resources: HashMap<Vec<u8>, Object>,
}
impl Default for TilingPatternBuilder {
fn default() -> Self {
Self {
bbox: (0.0, 0.0, 10.0, 10.0),
x_step: 10.0,
y_step: 10.0,
paint_type: PatternPaintType::Colored,
tiling_type: PatternTilingType::ConstantSpacing,
content: Vec::new(),
matrix: None,
resources: HashMap::new(),
}
}
}
impl TilingPatternBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn bbox(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
self.bbox = (x, y, width, height);
self
}
pub fn x_step(mut self, step: f32) -> Self {
self.x_step = step;
self
}
pub fn y_step(mut self, step: f32) -> Self {
self.y_step = step;
self
}
pub fn step(self, x: f32, y: f32) -> Self {
self.x_step(x).y_step(y)
}
pub fn colored(mut self) -> Self {
self.paint_type = PatternPaintType::Colored;
self
}
pub fn uncolored(mut self) -> Self {
self.paint_type = PatternPaintType::Uncolored;
self
}
pub fn tiling_type(mut self, tiling: PatternTilingType) -> Self {
self.tiling_type = tiling;
self
}
pub fn matrix(mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
self.matrix = Some([a, b, c, d, e, f]);
self
}
pub fn content_bytes(mut self, content: Vec<u8>) -> Self {
self.content = content;
self
}
pub fn build(&self) -> (Object, Vec<u8>) {
let mut dict: HashMap<String, Object> = HashMap::new();
dict.insert(key("Type"), Object::Name("Pattern".to_string()));
dict.insert(key("PatternType"), Object::Integer(1));
dict.insert(key("PaintType"), Object::Integer(self.paint_type as i64));
dict.insert(key("TilingType"), Object::Integer(self.tiling_type as i64));
dict.insert(
key("BBox"),
Object::Array(vec![
Object::Real(self.bbox.0 as f64),
Object::Real(self.bbox.1 as f64),
Object::Real(self.bbox.2 as f64),
Object::Real(self.bbox.3 as f64),
]),
);
dict.insert(key("XStep"), Object::Real(self.x_step as f64));
dict.insert(key("YStep"), Object::Real(self.y_step as f64));
if let Some(m) = &self.matrix {
dict.insert(
key("Matrix"),
Object::Array(m.iter().map(|&v| Object::Real(v as f64)).collect()),
);
}
if !self.resources.is_empty() {
let converted: HashMap<String, Object> = self
.resources
.iter()
.map(|(k, v)| (String::from_utf8_lossy(k).to_string(), v.clone()))
.collect();
dict.insert(key("Resources"), Object::Dictionary(converted));
}
(Object::Dictionary(dict), self.content.clone())
}
}
#[derive(Debug, Clone, Default)]
pub struct ShadingPatternBuilder {
shading_id: Option<u32>,
matrix: Option<[f32; 6]>,
ext_gstate_id: Option<u32>,
}
impl ShadingPatternBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn shading_id(mut self, id: u32) -> Self {
self.shading_id = Some(id);
self
}
pub fn matrix(mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
self.matrix = Some([a, b, c, d, e, f]);
self
}
pub fn ext_gstate_id(mut self, id: u32) -> Self {
self.ext_gstate_id = Some(id);
self
}
pub fn build(&self) -> Object {
let mut dict: HashMap<String, Object> = HashMap::new();
dict.insert(key("Type"), Object::Name("Pattern".to_string()));
dict.insert(key("PatternType"), Object::Integer(2));
if let Some(id) = self.shading_id {
dict.insert(key("Shading"), Object::Reference(crate::object::ObjectRef::new(id, 0)));
}
if let Some(m) = &self.matrix {
dict.insert(
key("Matrix"),
Object::Array(m.iter().map(|&v| Object::Real(v as f64)).collect()),
);
}
if let Some(id) = self.ext_gstate_id {
dict.insert(key("ExtGState"), Object::Reference(crate::object::ObjectRef::new(id, 0)));
}
Object::Dictionary(dict)
}
}
pub struct PatternPresets;
impl PatternPresets {
pub fn horizontal_stripes(
width: f32,
_height: f32,
stripe_height: f32,
color: Color,
) -> Vec<u8> {
format!(
"{} {} {} rg\n0 0 {} {} re\nf\n",
color.r, color.g, color.b, width, stripe_height
)
.into_bytes()
}
pub fn vertical_stripes(_width: f32, height: f32, stripe_width: f32, color: Color) -> Vec<u8> {
format!(
"{} {} {} rg\n0 0 {} {} re\nf\n",
color.r, color.g, color.b, stripe_width, height
)
.into_bytes()
}
pub fn checkerboard(size: f32, color1: Color, color2: Color) -> Vec<u8> {
format!(
"{} {} {} rg\n0 0 {} {} re\nf\n{} {} {} rg\n{} 0 {} {} re\n0 {} {} {} re\nf\n",
color1.r,
color1.g,
color1.b,
size * 2.0,
size * 2.0,
color2.r,
color2.g,
color2.b,
size,
size,
size,
size,
size,
size
)
.into_bytes()
}
pub fn dots(spacing: f32, radius: f32, color: Color) -> Vec<u8> {
let k = radius * 0.552_284_8;
let cx = spacing / 2.0;
let cy = spacing / 2.0;
format!(
"{} {} {} rg\n\
{} {} m\n\
{} {} {} {} {} {} c\n\
{} {} {} {} {} {} c\n\
{} {} {} {} {} {} c\n\
{} {} {} {} {} {} c\n\
f\n",
color.r,
color.g,
color.b,
cx + radius,
cy,
cx + radius,
cy + k,
cx + k,
cy + radius,
cx,
cy + radius,
cx - k,
cy + radius,
cx - radius,
cy + k,
cx - radius,
cy,
cx - radius,
cy - k,
cx - k,
cy - radius,
cx,
cy - radius,
cx + k,
cy - radius,
cx + radius,
cy - k,
cx + radius,
cy
)
.into_bytes()
}
pub fn diagonal_lines(size: f32, line_width: f32, color: Color) -> Vec<u8> {
format!(
"{} {} {} RG\n{} w\n0 0 m\n{} {} l\nS\n",
color.r, color.g, color.b, line_width, size, size
)
.into_bytes()
}
pub fn crosshatch(size: f32, line_width: f32, color: Color) -> Vec<u8> {
format!(
"{} {} {} RG\n{} w\n\
0 0 m\n{} {} l\nS\n\
{} 0 m\n0 {} l\nS\n",
color.r, color.g, color.b, line_width, size, size, size, size
)
.into_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tiling_pattern_builder() {
let content =
PatternPresets::horizontal_stripes(10.0, 10.0, 5.0, Color::new(1.0, 0.0, 0.0));
let (dict, _content) = TilingPatternBuilder::new()
.bbox(0.0, 0.0, 10.0, 10.0)
.step(10.0, 10.0)
.colored()
.content_bytes(content)
.build();
if let Object::Dictionary(d) = dict {
assert!(d.contains_key("PatternType"));
if let Some(Object::Integer(pt)) = d.get("PatternType") {
assert_eq!(*pt, 1);
}
} else {
panic!("Expected dictionary");
}
}
#[test]
fn test_shading_pattern_builder() {
let dict = ShadingPatternBuilder::new()
.shading_id(5)
.matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
.build();
if let Object::Dictionary(d) = dict {
assert!(d.contains_key("PatternType"));
if let Some(Object::Integer(pt)) = d.get("PatternType") {
assert_eq!(*pt, 2);
}
} else {
panic!("Expected dictionary");
}
}
#[test]
fn test_pattern_presets() {
let _ = PatternPresets::horizontal_stripes(10.0, 10.0, 5.0, Color::new(0.0, 0.0, 1.0));
let _ = PatternPresets::checkerboard(5.0, Color::white(), Color::black());
let _ = PatternPresets::dots(10.0, 2.0, Color::new(1.0, 0.0, 0.0));
let _ = PatternPresets::diagonal_lines(10.0, 0.5, Color::black());
}
}