use kurbo::Affine;
use crate::color::Argb;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColorMode {
#[default]
Normal,
Gray,
Alpha,
Forced(ColorScheme),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorScheme {
pub path_fill: Argb,
pub path_stroke: Argb,
pub text_fill: Argb,
pub text_stroke: Argb,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextAa {
#[default]
Grayscale,
None,
LcdSubpixel,
}
#[derive(Debug, PartialEq)]
#[expect(
clippy::struct_excessive_bools,
reason = "these are `CPDF_RenderOptions::Options`' independent bit flags \
one for one; grouping them into sub-structs or an enum would \
hide which upstream flag each is and make the port unreviewable"
)]
pub struct RenderOptions {
pub transform: Affine,
pub color_mode: ColorMode,
pub text_aa: TextAa,
pub text_aa_override: Option<TextAa>,
pub no_path_smooth: bool,
pub no_image_smooth: bool,
pub force_halftone: bool,
pub rect_aa: bool,
pub convert_fill_to_stroke: bool,
pub subpixel_text_positioning: bool,
pub background: Option<peniko::Color>,
}
impl Clone for RenderOptions {
fn clone(&self) -> Self {
crate::walkprofile::alloc_items(
crate::walkprofile::Site::OptionsClone,
1,
core::mem::size_of::<Self>(),
);
Self { ..*self }
}
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
transform: Affine::IDENTITY,
color_mode: ColorMode::Normal,
text_aa: TextAa::Grayscale,
text_aa_override: None,
no_path_smooth: false,
no_image_smooth: false,
force_halftone: false,
rect_aa: false,
convert_fill_to_stroke: false,
subpixel_text_positioning: false,
background: None,
}
}
}
impl RenderOptions {
#[must_use]
pub fn background_for(&self, has_transparency: bool) -> peniko::Color {
self.background.unwrap_or(if has_transparency {
peniko::Color::TRANSPARENT
} else {
peniko::Color::WHITE
})
}
#[must_use]
pub fn path_aa(&self) -> crate::device::AntiAlias {
if self.no_path_smooth {
crate::device::AntiAlias::Off
} else {
crate::device::AntiAlias::On
}
}
#[must_use]
pub fn effective_text_aa(&self) -> TextAa {
self.text_aa_override.unwrap_or(self.text_aa)
}
#[must_use]
pub fn for_text_run(&self, aa: TextAa) -> Self {
Self {
text_aa_override: Some(aa),
..self.clone()
}
}
#[must_use]
pub fn text_antialias(&self) -> crate::device::AntiAlias {
match self.effective_text_aa() {
TextAa::Grayscale | TextAa::LcdSubpixel => crate::device::AntiAlias::On,
TextAa::None => crate::device::AntiAlias::Off,
}
}
#[must_use]
pub fn for_type3_char_proc(&self) -> Self {
Self {
force_halftone: true,
rect_aa: true,
..self.clone()
}
}
#[must_use]
pub fn for_uncolored_tile(&self) -> Self {
Self {
color_mode: ColorMode::Alpha,
force_halftone: true,
..self.clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn oracle_defaults() {
let o = RenderOptions::default();
assert_eq!(o.text_aa, TextAa::Grayscale);
assert_eq!(o.color_mode, ColorMode::Normal);
assert!(!o.rect_aa);
assert!(!o.force_halftone);
}
#[test]
fn background_follows_page_transparency() {
let o = RenderOptions::default();
assert_eq!(o.background_for(false), peniko::Color::WHITE);
assert_eq!(o.background_for(true), peniko::Color::TRANSPARENT);
let forced = RenderOptions {
background: Some(peniko::Color::BLACK),
..RenderOptions::default()
};
assert_eq!(forced.background_for(true), peniko::Color::BLACK);
}
#[test]
fn type3_forces_rect_aa_and_halftone() {
let inner = RenderOptions::default().for_type3_char_proc();
assert!(inner.rect_aa);
assert!(inner.force_halftone);
}
#[test]
fn a_per_draw_override_wins_over_the_render_wide_setting() {
let page = RenderOptions::default();
assert_eq!(page.effective_text_aa(), TextAa::Grayscale);
let run = page.for_text_run(TextAa::LcdSubpixel);
assert_eq!(run.effective_text_aa(), TextAa::LcdSubpixel);
assert_eq!(page.effective_text_aa(), TextAa::Grayscale);
assert_eq!(run.text_aa, TextAa::Grayscale);
}
#[test]
fn the_subpixel_mode_still_antialiases_outlines() {
let lcd = RenderOptions::default().for_text_run(TextAa::LcdSubpixel);
assert_eq!(lcd.text_antialias(), crate::device::AntiAlias::On);
let off = RenderOptions::default().for_text_run(TextAa::None);
assert_eq!(off.text_antialias(), crate::device::AntiAlias::Off);
}
#[test]
fn uncolored_tile_renders_in_alpha_mode() {
let inner = RenderOptions::default().for_uncolored_tile();
assert_eq!(inner.color_mode, ColorMode::Alpha);
assert!(inner.force_halftone);
}
}