use std::fmt::Write as _;
use kurbo::Rect;
use crate::ap::fmt;
use crate::color::Color;
use crate::geom;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Float {
Shortest,
G6,
}
impl Float {
#[must_use]
pub fn write(self, value: f32) -> String {
match self {
Float::Shortest => fmt::shortest(value),
Float::G6 => fmt::g6(value),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaintOp {
Fill,
Stroke,
}
#[derive(Debug, Clone, Default)]
pub struct Content {
bytes: String,
}
impl Content {
#[must_use]
pub fn new() -> Content {
Content::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.bytes
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes.into_bytes()
}
pub fn raw(&mut self, text: &str) {
self.bytes.push_str(text);
}
pub fn num(&mut self, value: f32, how: Float) {
self.bytes.push_str(&how.write(value));
self.bytes.push(' ');
}
pub fn point(&mut self, x: f32, y: f32, how: Float) {
self.num(x, how);
self.num(y, how);
}
pub fn rect(&mut self, r: Rect, how: Float) {
self.num(geom::left(r), how);
self.num(geom::bottom(r), how);
self.num(geom::width(r), how);
self.num(geom::height(r), how);
}
}
#[must_use]
pub fn color_op(color: Color, paint: PaintOp) -> String {
color_op_via(color, paint, Float::Shortest)
}
#[must_use]
pub fn color_op_via(color: Color, paint: PaintOp, how: Float) -> String {
let mut out = String::new();
let (gray, rgb, cmyk) = match paint {
PaintOp::Fill => ("g", "rg", "k"),
PaintOp::Stroke => ("G", "RG", "K"),
};
let mut write = |values: &[f32], op: &str| {
for value in values {
let _ = write!(out, "{} ", how.write(*value));
}
let _ = writeln!(out, "{op}");
};
match color {
Color::Transparent => {}
Color::Gray(g) => write(&[g], gray),
Color::Rgb(r, g, b) => write(&[r, g, b], rgb),
Color::Cmyk(c, m, y, k) => write(&[c, m, y, k], cmyk),
}
out
}
#[must_use]
pub fn color_with_default(
array: Option<&pdfrum_object::Array>,
default: Color,
paint: PaintOp,
) -> String {
match array {
Some(array) => color_op(Color::from_array(array), paint),
None => color_op(default, paint),
}
}
#[must_use]
pub fn paint_operator(stroke: bool, fill: bool) -> &'static str {
match (stroke, fill) {
(true, true) => "b",
(true, false) => "s",
(false, true) => "f",
(false, false) => "n",
}
}
#[cfg(test)]
mod tests {
use super::{Content, Float, PaintOp, color_op, color_with_default, paint_operator};
use crate::color::Color;
use crate::geom;
use pdfrum_object::{Array, Object};
#[test]
fn a_rectangle_is_written_as_operator_operands_not_as_corners() {
let mut out = Content::new();
out.rect(geom::rect(10.0, 20.0, 30.0, 25.0), Float::Shortest);
out.raw("re\n");
assert_eq!(out.as_str(), "10 20 20 5 re\n");
}
#[test]
fn a_negative_extent_survives_into_the_operands() {
let mut out = Content::new();
out.rect(geom::rect(30.0, 20.0, 10.0, 25.0), Float::Shortest);
assert_eq!(out.as_str(), "30 20 -20 5 ");
}
#[test]
fn the_two_writers_produce_different_bytes_for_the_same_number() {
let mut shortest = Content::new();
shortest.num(0.5, Float::Shortest);
let mut ostream = Content::new();
ostream.num(0.5, Float::G6);
assert_eq!(shortest.as_str(), ".5 ");
assert_eq!(ostream.as_str(), "0.5 ");
}
#[test]
fn a_transparent_colour_writes_nothing_at_all() {
assert_eq!(color_op(Color::Transparent, PaintOp::Fill), "");
assert_eq!(color_op(Color::Transparent, PaintOp::Stroke), "");
}
#[test]
fn each_space_writes_its_own_operator_pair() {
assert_eq!(color_op(Color::Gray(0.5), PaintOp::Fill), ".5 g\n");
assert_eq!(color_op(Color::Gray(0.5), PaintOp::Stroke), ".5 G\n");
assert_eq!(
color_op(Color::Rgb(1.0, 0.0, 0.0), PaintOp::Fill),
"1 0 0 rg\n"
);
assert_eq!(
color_op(Color::Cmyk(0.0, 0.25, 0.5, 1.0), PaintOp::Stroke),
"0 .25 .5 1 K\n"
);
}
#[test]
fn a_present_but_unusable_array_beats_the_default() {
let empty = Array::new();
assert_eq!(
color_with_default(Some(&empty), Color::Rgb(1.0, 1.0, 0.0), PaintOp::Fill),
""
);
assert_eq!(
color_with_default(None, Color::Rgb(1.0, 1.0, 0.0), PaintOp::Fill),
"1 1 0 rg\n"
);
let black = Array::of([0.0_f32, 0.0, 0.0].map(Object::from));
assert_eq!(
color_with_default(Some(&black), Color::Rgb(1.0, 1.0, 0.0), PaintOp::Fill),
"0 0 0 rg\n"
);
}
#[test]
fn the_paint_operator_covers_all_four_combinations() {
assert_eq!(paint_operator(true, true), "b");
assert_eq!(paint_operator(true, false), "s");
assert_eq!(paint_operator(false, true), "f");
assert_eq!(paint_operator(false, false), "n");
}
}