use crate::types::Handle;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PlotPaperUnits {
#[default]
Inches = 0,
Millimeters = 1,
Pixels = 2,
}
impl PlotPaperUnits {
pub fn from_code(code: i16) -> Self {
match code {
1 => PlotPaperUnits::Millimeters,
2 => PlotPaperUnits::Pixels,
_ => PlotPaperUnits::Inches,
}
}
pub fn to_code(self) -> i16 {
self as i16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PlotRotation {
#[default]
None = 0,
Degrees90 = 1,
Degrees180 = 2,
Degrees270 = 3,
}
impl PlotRotation {
pub fn from_code(code: i16) -> Self {
match code {
1 => PlotRotation::Degrees90,
2 => PlotRotation::Degrees180,
3 => PlotRotation::Degrees270,
_ => PlotRotation::None,
}
}
pub fn to_code(self) -> i16 {
self as i16
}
pub fn to_degrees(self) -> f64 {
match self {
PlotRotation::None => 0.0,
PlotRotation::Degrees90 => 90.0,
PlotRotation::Degrees180 => 180.0,
PlotRotation::Degrees270 => 270.0,
}
}
pub fn to_radians(self) -> f64 {
self.to_degrees().to_radians()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PlotType {
LastScreenDisplay = 0,
Extents = 1,
Limits = 2,
View = 3,
#[default]
Window = 4,
Layout = 5,
}
impl PlotType {
pub fn from_code(code: i16) -> Self {
match code {
0 => PlotType::LastScreenDisplay,
1 => PlotType::Extents,
2 => PlotType::Limits,
3 => PlotType::View,
5 => PlotType::Layout,
_ => PlotType::Window,
}
}
pub fn to_code(self) -> i16 {
self as i16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ScaledType {
#[default]
ScaleToFit = 0,
CustomScale = 1,
OneToOne = 16,
OneToTwo = 17,
OneToFour = 18,
OneToEight = 19,
OneToTen = 20,
OneToSixteen = 21,
OneToTwenty = 22,
OneToThirty = 23,
OneToForty = 24,
OneToFifty = 25,
OneToHundred = 26,
TwoToOne = 27,
FourToOne = 28,
EightToOne = 29,
TenToOne = 30,
HundredToOne = 31,
}
impl ScaledType {
pub fn from_code(code: i16) -> Self {
match code {
0 => ScaledType::ScaleToFit,
1 => ScaledType::CustomScale,
16 => ScaledType::OneToOne,
17 => ScaledType::OneToTwo,
18 => ScaledType::OneToFour,
19 => ScaledType::OneToEight,
20 => ScaledType::OneToTen,
21 => ScaledType::OneToSixteen,
22 => ScaledType::OneToTwenty,
23 => ScaledType::OneToThirty,
24 => ScaledType::OneToForty,
25 => ScaledType::OneToFifty,
26 => ScaledType::OneToHundred,
27 => ScaledType::TwoToOne,
28 => ScaledType::FourToOne,
29 => ScaledType::EightToOne,
30 => ScaledType::TenToOne,
31 => ScaledType::HundredToOne,
_ => ScaledType::CustomScale,
}
}
pub fn to_code(self) -> i16 {
self as i16
}
pub fn scale_factor(&self) -> f64 {
match self {
ScaledType::ScaleToFit => 0.0, ScaledType::CustomScale => 1.0,
ScaledType::OneToOne => 1.0,
ScaledType::OneToTwo => 0.5,
ScaledType::OneToFour => 0.25,
ScaledType::OneToEight => 0.125,
ScaledType::OneToTen => 0.1,
ScaledType::OneToSixteen => 0.0625,
ScaledType::OneToTwenty => 0.05,
ScaledType::OneToThirty => 1.0 / 30.0,
ScaledType::OneToForty => 0.025,
ScaledType::OneToFifty => 0.02,
ScaledType::OneToHundred => 0.01,
ScaledType::TwoToOne => 2.0,
ScaledType::FourToOne => 4.0,
ScaledType::EightToOne => 8.0,
ScaledType::TenToOne => 10.0,
ScaledType::HundredToOne => 100.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ShadePlotMode {
#[default]
AsDisplayed = 0,
Wireframe = 1,
Hidden = 2,
Rendered = 3,
}
impl ShadePlotMode {
pub fn from_code(code: i16) -> Self {
match code {
1 => ShadePlotMode::Wireframe,
2 => ShadePlotMode::Hidden,
3 => ShadePlotMode::Rendered,
_ => ShadePlotMode::AsDisplayed,
}
}
pub fn to_code(self) -> i16 {
self as i16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ShadePlotResolutionLevel {
Draft = 0,
Preview = 1,
#[default]
Normal = 2,
Presentation = 3,
Maximum = 4,
Custom = 5,
}
impl ShadePlotResolutionLevel {
pub fn from_code(code: i16) -> Self {
match code {
0 => ShadePlotResolutionLevel::Draft,
1 => ShadePlotResolutionLevel::Preview,
3 => ShadePlotResolutionLevel::Presentation,
4 => ShadePlotResolutionLevel::Maximum,
5 => ShadePlotResolutionLevel::Custom,
_ => ShadePlotResolutionLevel::Normal,
}
}
pub fn to_code(self) -> i16 {
self as i16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PlotFlags {
pub plot_viewport_borders: bool,
pub show_plot_styles: bool,
pub plot_centered: bool,
pub plot_hidden: bool,
pub use_standard_scale: bool,
pub plot_plot_styles: bool,
pub scale_lineweights: bool,
pub print_lineweights: bool,
pub draw_viewports_first: bool,
pub model_type: bool,
pub update_paper: bool,
pub zoom_to_paper_on_update: bool,
pub initializing: bool,
pub prev_plot_init: bool,
}
impl PlotFlags {
pub fn from_bits(bits: i32) -> Self {
Self {
plot_viewport_borders: (bits & 1) != 0,
show_plot_styles: (bits & 2) != 0,
plot_centered: (bits & 4) != 0,
plot_hidden: (bits & 8) != 0,
use_standard_scale: (bits & 16) != 0,
plot_plot_styles: (bits & 32) != 0,
scale_lineweights: (bits & 64) != 0,
print_lineweights: (bits & 128) != 0,
draw_viewports_first: (bits & 512) != 0,
model_type: (bits & 1024) != 0,
update_paper: (bits & 2048) != 0,
zoom_to_paper_on_update: (bits & 4096) != 0,
initializing: (bits & 8192) != 0,
prev_plot_init: (bits & 16384) != 0,
}
}
pub fn to_bits(&self) -> i32 {
let mut bits = 0;
if self.plot_viewport_borders { bits |= 1; }
if self.show_plot_styles { bits |= 2; }
if self.plot_centered { bits |= 4; }
if self.plot_hidden { bits |= 8; }
if self.use_standard_scale { bits |= 16; }
if self.plot_plot_styles { bits |= 32; }
if self.scale_lineweights { bits |= 64; }
if self.print_lineweights { bits |= 128; }
if self.draw_viewports_first { bits |= 512; }
if self.model_type { bits |= 1024; }
if self.update_paper { bits |= 2048; }
if self.zoom_to_paper_on_update { bits |= 4096; }
if self.initializing { bits |= 8192; }
if self.prev_plot_init { bits |= 16384; }
bits
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PaperMargin {
pub left: f64,
pub bottom: f64,
pub right: f64,
pub top: f64,
}
impl PaperMargin {
pub fn uniform(margin: f64) -> Self {
Self {
left: margin,
bottom: margin,
right: margin,
top: margin,
}
}
pub fn new(left: f64, bottom: f64, right: f64, top: f64) -> Self {
Self { left, bottom, right, top }
}
pub fn is_zero(&self) -> bool {
self.left == 0.0 && self.bottom == 0.0 && self.right == 0.0 && self.top == 0.0
}
pub fn horizontal_total(&self) -> f64 {
self.left + self.right
}
pub fn vertical_total(&self) -> f64 {
self.top + self.bottom
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PlotWindow {
pub lower_left_x: f64,
pub lower_left_y: f64,
pub upper_right_x: f64,
pub upper_right_y: f64,
}
impl PlotWindow {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
Self {
lower_left_x: x1.min(x2),
lower_left_y: y1.min(y2),
upper_right_x: x1.max(x2),
upper_right_y: y1.max(y2),
}
}
pub fn width(&self) -> f64 {
self.upper_right_x - self.lower_left_x
}
pub fn height(&self) -> f64 {
self.upper_right_y - self.lower_left_y
}
pub fn is_empty(&self) -> bool {
self.width() <= 0.0 || self.height() <= 0.0
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PlotSettings {
pub handle: Handle,
pub owner: Handle,
pub page_name: String,
pub printer_name: String,
pub paper_size: String,
pub plot_view_name: String,
pub current_style_sheet: String,
pub paper_width: f64,
pub paper_height: f64,
pub margins: PaperMargin,
pub origin_x: f64,
pub origin_y: f64,
pub plot_window: PlotWindow,
pub scale_numerator: f64,
pub scale_denominator: f64,
pub paper_units: PlotPaperUnits,
pub rotation: PlotRotation,
pub plot_type: PlotType,
pub scale_type: ScaledType,
pub shade_plot_mode: ShadePlotMode,
pub shade_plot_resolution: ShadePlotResolutionLevel,
pub shade_plot_dpi: i16,
pub flags: PlotFlags,
cached_scale: Option<f64>,
}
impl PlotSettings {
pub const OBJECT_TYPE: &'static str = "PLOTSETTINGS";
pub fn new(page_name: impl Into<String>) -> Self {
Self {
handle: Handle::NULL,
owner: Handle::NULL,
page_name: page_name.into(),
printer_name: String::new(),
paper_size: String::new(),
plot_view_name: String::new(),
current_style_sheet: String::new(),
paper_width: 0.0,
paper_height: 0.0,
margins: PaperMargin::default(),
origin_x: 0.0,
origin_y: 0.0,
plot_window: PlotWindow::default(),
scale_numerator: 1.0,
scale_denominator: 1.0,
paper_units: PlotPaperUnits::default(),
rotation: PlotRotation::default(),
plot_type: PlotType::default(),
scale_type: ScaledType::default(),
shade_plot_mode: ShadePlotMode::default(),
shade_plot_resolution: ShadePlotResolutionLevel::default(),
shade_plot_dpi: 300,
flags: PlotFlags::default(),
cached_scale: None,
}
}
pub fn with_paper(page_name: impl Into<String>, paper_size: impl Into<String>) -> Self {
let mut settings = Self::new(page_name);
settings.paper_size = paper_size.into();
settings
}
pub fn scale_factor(&self) -> f64 {
if let Some(cached) = self.cached_scale {
return cached;
}
if self.scale_denominator == 0.0 {
1.0
} else {
self.scale_numerator / self.scale_denominator
}
}
pub fn set_custom_scale(&mut self, numerator: f64, denominator: f64) {
self.scale_numerator = numerator;
self.scale_denominator = denominator;
self.scale_type = ScaledType::CustomScale;
self.cached_scale = None;
}
pub fn set_scale_to_fit(&mut self) {
self.scale_type = ScaledType::ScaleToFit;
self.flags.use_standard_scale = false;
}
pub fn set_standard_scale(&mut self, scale: ScaledType) {
self.scale_type = scale;
self.flags.use_standard_scale = true;
match scale {
ScaledType::OneToOne => {
self.scale_numerator = 1.0;
self.scale_denominator = 1.0;
}
ScaledType::OneToTwo => {
self.scale_numerator = 1.0;
self.scale_denominator = 2.0;
}
ScaledType::TwoToOne => {
self.scale_numerator = 2.0;
self.scale_denominator = 1.0;
}
ScaledType::OneToTen => {
self.scale_numerator = 1.0;
self.scale_denominator = 10.0;
}
ScaledType::TenToOne => {
self.scale_numerator = 10.0;
self.scale_denominator = 1.0;
}
_ => {}
}
self.cached_scale = None;
}
pub fn printable_width(&self) -> f64 {
self.paper_width - self.margins.horizontal_total()
}
pub fn printable_height(&self) -> f64 {
self.paper_height - self.margins.vertical_total()
}
pub fn set_paper_size(&mut self, width: f64, height: f64) {
self.paper_width = width;
self.paper_height = height;
}
pub fn set_paper_with_margins(
&mut self,
width: f64,
height: f64,
margins: PaperMargin,
) {
self.paper_width = width;
self.paper_height = height;
self.margins = margins;
}
pub fn set_plot_window(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
self.plot_window = PlotWindow::new(x1, y1, x2, y2);
self.plot_type = PlotType::Window;
}
pub fn set_origin(&mut self, x: f64, y: f64) {
self.origin_x = x;
self.origin_y = y;
}
pub fn center_plot(&mut self) {
self.flags.plot_centered = true;
}
pub fn is_scale_to_fit(&self) -> bool {
matches!(self.scale_type, ScaledType::ScaleToFit)
}
pub fn is_custom_scale(&self) -> bool {
matches!(self.scale_type, ScaledType::CustomScale)
}
pub fn set_printer(&mut self, name: impl Into<String>) {
self.printer_name = name.into();
}
pub fn set_style_sheet(&mut self, name: impl Into<String>) {
self.current_style_sheet = name.into();
}
pub fn with_paper_size(mut self, size: impl Into<String>) -> Self {
self.paper_size = size.into();
self
}
pub fn with_printer(mut self, printer: impl Into<String>) -> Self {
self.printer_name = printer.into();
self
}
pub fn with_rotation(mut self, rotation: PlotRotation) -> Self {
self.rotation = rotation;
self
}
pub fn with_units(mut self, units: PlotPaperUnits) -> Self {
self.paper_units = units;
self
}
pub fn with_scale(mut self, numerator: f64, denominator: f64) -> Self {
self.set_custom_scale(numerator, denominator);
self
}
}
impl Default for PlotSettings {
fn default() -> Self {
Self::new("")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plot_settings_creation() {
let settings = PlotSettings::new("Layout1");
assert_eq!(settings.page_name, "Layout1");
assert_eq!(settings.scale_factor(), 1.0);
}
#[test]
fn test_plot_settings_with_paper() {
let settings = PlotSettings::with_paper("Layout1", "A4");
assert_eq!(settings.page_name, "Layout1");
assert_eq!(settings.paper_size, "A4");
}
#[test]
fn test_plot_settings_scale() {
let mut settings = PlotSettings::new("Test");
settings.set_custom_scale(1.0, 2.0);
assert!((settings.scale_factor() - 0.5).abs() < 1e-10);
assert!(settings.is_custom_scale());
settings.set_standard_scale(ScaledType::TwoToOne);
assert!((settings.scale_factor() - 2.0).abs() < 1e-10);
settings.set_scale_to_fit();
assert!(settings.is_scale_to_fit());
}
#[test]
fn test_paper_margin() {
let margin = PaperMargin::uniform(10.0);
assert_eq!(margin.left, 10.0);
assert_eq!(margin.horizontal_total(), 20.0);
assert_eq!(margin.vertical_total(), 20.0);
let margin2 = PaperMargin::new(5.0, 10.0, 5.0, 10.0);
assert_eq!(margin2.horizontal_total(), 10.0);
assert_eq!(margin2.vertical_total(), 20.0);
}
#[test]
fn test_plot_window() {
let window = PlotWindow::new(10.0, 20.0, 110.0, 120.0);
assert!((window.width() - 100.0).abs() < 1e-10);
assert!((window.height() - 100.0).abs() < 1e-10);
assert!(!window.is_empty());
let window2 = PlotWindow::new(110.0, 120.0, 10.0, 20.0);
assert!((window2.width() - 100.0).abs() < 1e-10);
}
#[test]
fn test_printable_area() {
let mut settings = PlotSettings::new("Test");
settings.set_paper_with_margins(
210.0,
297.0,
PaperMargin::uniform(5.0),
);
assert!((settings.printable_width() - 200.0).abs() < 1e-10);
assert!((settings.printable_height() - 287.0).abs() < 1e-10);
}
#[test]
fn test_plot_paper_units() {
assert_eq!(PlotPaperUnits::from_code(0), PlotPaperUnits::Inches);
assert_eq!(PlotPaperUnits::from_code(1), PlotPaperUnits::Millimeters);
assert_eq!(PlotPaperUnits::from_code(2), PlotPaperUnits::Pixels);
assert_eq!(PlotPaperUnits::Millimeters.to_code(), 1);
}
#[test]
fn test_plot_rotation() {
assert_eq!(PlotRotation::None.to_degrees(), 0.0);
assert_eq!(PlotRotation::Degrees90.to_degrees(), 90.0);
assert_eq!(PlotRotation::Degrees180.to_degrees(), 180.0);
assert_eq!(PlotRotation::Degrees270.to_degrees(), 270.0);
assert_eq!(PlotRotation::from_code(1), PlotRotation::Degrees90);
}
#[test]
fn test_plot_type() {
assert_eq!(PlotType::from_code(0), PlotType::LastScreenDisplay);
assert_eq!(PlotType::from_code(1), PlotType::Extents);
assert_eq!(PlotType::from_code(5), PlotType::Layout);
}
#[test]
fn test_scaled_type() {
assert!((ScaledType::OneToOne.scale_factor() - 1.0).abs() < 1e-10);
assert!((ScaledType::OneToTwo.scale_factor() - 0.5).abs() < 1e-10);
assert!((ScaledType::TwoToOne.scale_factor() - 2.0).abs() < 1e-10);
assert!((ScaledType::OneToHundred.scale_factor() - 0.01).abs() < 1e-10);
}
#[test]
fn test_shade_plot_mode() {
assert_eq!(ShadePlotMode::from_code(0), ShadePlotMode::AsDisplayed);
assert_eq!(ShadePlotMode::from_code(1), ShadePlotMode::Wireframe);
assert_eq!(ShadePlotMode::from_code(2), ShadePlotMode::Hidden);
assert_eq!(ShadePlotMode::from_code(3), ShadePlotMode::Rendered);
}
#[test]
fn test_plot_flags() {
let flags = PlotFlags::from_bits(1 | 4 | 16);
assert!(flags.plot_viewport_borders);
assert!(flags.plot_centered);
assert!(flags.use_standard_scale);
assert!(!flags.show_plot_styles);
assert_eq!(flags.to_bits(), 1 | 4 | 16);
}
#[test]
fn test_plot_settings_builder() {
let settings = PlotSettings::new("Layout1")
.with_paper_size("A3")
.with_printer("PDF Printer")
.with_rotation(PlotRotation::Degrees90)
.with_units(PlotPaperUnits::Millimeters)
.with_scale(1.0, 100.0);
assert_eq!(settings.paper_size, "A3");
assert_eq!(settings.printer_name, "PDF Printer");
assert_eq!(settings.rotation, PlotRotation::Degrees90);
assert_eq!(settings.paper_units, PlotPaperUnits::Millimeters);
assert!((settings.scale_factor() - 0.01).abs() < 1e-10);
}
#[test]
fn test_set_plot_window() {
let mut settings = PlotSettings::new("Test");
settings.set_plot_window(0.0, 0.0, 100.0, 100.0);
assert_eq!(settings.plot_type, PlotType::Window);
assert!((settings.plot_window.width() - 100.0).abs() < 1e-10);
}
#[test]
fn test_center_plot() {
let mut settings = PlotSettings::new("Test");
settings.center_plot();
assert!(settings.flags.plot_centered);
}
}