use crate::annotation_types::AnnotationFlags;
use crate::geometry::Rect;
use crate::object::{Object, ObjectRef};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SoundEncoding {
#[default]
Raw,
Signed,
MuLaw,
ALaw,
}
impl SoundEncoding {
pub fn pdf_name(&self) -> &'static str {
match self {
SoundEncoding::Raw => "Raw",
SoundEncoding::Signed => "Signed",
SoundEncoding::MuLaw => "muLaw",
SoundEncoding::ALaw => "ALaw",
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SoundIcon {
#[default]
Speaker,
Mic,
}
impl SoundIcon {
pub fn pdf_name(&self) -> &'static str {
match self {
SoundIcon::Speaker => "Speaker",
SoundIcon::Mic => "Mic",
}
}
}
#[derive(Debug, Clone)]
pub struct SoundData {
pub data: Vec<u8>,
pub sample_rate: u32,
pub channels: u8,
pub bits_per_sample: u8,
pub encoding: SoundEncoding,
}
impl SoundData {
pub fn new(data: Vec<u8>, sample_rate: u32) -> Self {
Self {
data,
sample_rate,
channels: 1,
bits_per_sample: 8,
encoding: SoundEncoding::default(),
}
}
pub fn stereo(data: Vec<u8>, sample_rate: u32) -> Self {
Self {
data,
sample_rate,
channels: 2,
bits_per_sample: 16,
encoding: SoundEncoding::Signed,
}
}
pub fn with_channels(mut self, channels: u8) -> Self {
self.channels = channels;
self
}
pub fn with_bits(mut self, bits: u8) -> Self {
self.bits_per_sample = bits;
self
}
pub fn with_encoding(mut self, encoding: SoundEncoding) -> Self {
self.encoding = encoding;
self
}
pub fn build_sound_dict(&self) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("Sound".to_string()));
dict.insert("R".to_string(), Object::Integer(self.sample_rate as i64));
if self.channels != 1 {
dict.insert("C".to_string(), Object::Integer(self.channels as i64));
}
if self.bits_per_sample != 8 {
dict.insert("B".to_string(), Object::Integer(self.bits_per_sample as i64));
}
if self.encoding != SoundEncoding::Raw {
dict.insert("E".to_string(), Object::Name(self.encoding.pdf_name().to_string()));
}
dict
}
}
#[derive(Debug, Clone)]
pub struct SoundAnnotation {
pub rect: Rect,
pub sound: SoundData,
pub icon: SoundIcon,
pub contents: Option<String>,
pub flags: AnnotationFlags,
pub author: Option<String>,
pub modification_date: Option<String>,
}
impl SoundAnnotation {
pub fn new(rect: Rect, data: Vec<u8>, sample_rate: u32) -> Self {
Self {
rect,
sound: SoundData::new(data, sample_rate),
icon: SoundIcon::default(),
contents: None,
flags: AnnotationFlags::printable(),
author: None,
modification_date: None,
}
}
pub fn from_sound_data(rect: Rect, sound: SoundData) -> Self {
Self {
rect,
sound,
icon: SoundIcon::default(),
contents: None,
flags: AnnotationFlags::printable(),
author: None,
modification_date: None,
}
}
pub fn with_channels(mut self, channels: u8) -> Self {
self.sound.channels = channels;
self
}
pub fn with_bits(mut self, bits: u8) -> Self {
self.sound.bits_per_sample = bits;
self
}
pub fn with_encoding(mut self, encoding: SoundEncoding) -> Self {
self.sound.encoding = encoding;
self
}
pub fn with_icon(mut self, icon: SoundIcon) -> Self {
self.icon = icon;
self
}
pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
self.contents = Some(contents.into());
self
}
pub fn with_author(mut self, author: impl Into<String>) -> Self {
self.author = Some(author.into());
self
}
pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
self.flags = flags;
self
}
pub fn build(&self, _page_refs: &[ObjectRef]) -> HashMap<String, Object> {
let mut dict = HashMap::new();
dict.insert("Type".to_string(), Object::Name("Annot".to_string()));
dict.insert("Subtype".to_string(), Object::Name("Sound".to_string()));
dict.insert(
"Rect".to_string(),
Object::Array(vec![
Object::Real(self.rect.x as f64),
Object::Real(self.rect.y as f64),
Object::Real((self.rect.x + self.rect.width) as f64),
Object::Real((self.rect.y + self.rect.height) as f64),
]),
);
dict.insert("Name".to_string(), Object::Name(self.icon.pdf_name().to_string()));
if let Some(ref contents) = self.contents {
dict.insert("Contents".to_string(), Object::String(contents.as_bytes().to_vec()));
}
if self.flags.bits() != 0 {
dict.insert("F".to_string(), Object::Integer(self.flags.bits() as i64));
}
if let Some(ref author) = self.author {
dict.insert("T".to_string(), Object::String(author.as_bytes().to_vec()));
}
if let Some(ref date) = self.modification_date {
dict.insert("M".to_string(), Object::String(date.as_bytes().to_vec()));
}
dict
}
pub fn build_sound_stream(&self) -> (HashMap<String, Object>, Vec<u8>) {
(self.sound.build_sound_dict(), self.sound.data.clone())
}
pub fn sound_data(&self) -> &SoundData {
&self.sound
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sound_data_new() {
let data = vec![0u8; 1000];
let sound = SoundData::new(data.clone(), 44100);
assert_eq!(sound.data, data);
assert_eq!(sound.sample_rate, 44100);
assert_eq!(sound.channels, 1);
assert_eq!(sound.bits_per_sample, 8);
assert!(matches!(sound.encoding, SoundEncoding::Raw));
}
#[test]
fn test_sound_data_stereo() {
let data = vec![0u8; 2000];
let sound = SoundData::stereo(data, 48000);
assert_eq!(sound.sample_rate, 48000);
assert_eq!(sound.channels, 2);
assert_eq!(sound.bits_per_sample, 16);
assert!(matches!(sound.encoding, SoundEncoding::Signed));
}
#[test]
fn test_sound_data_builder() {
let sound = SoundData::new(vec![], 22050)
.with_channels(2)
.with_bits(16)
.with_encoding(SoundEncoding::MuLaw);
assert_eq!(sound.channels, 2);
assert_eq!(sound.bits_per_sample, 16);
assert!(matches!(sound.encoding, SoundEncoding::MuLaw));
}
#[test]
fn test_sound_data_build_dict() {
let sound = SoundData::new(vec![], 44100)
.with_channels(2)
.with_bits(16)
.with_encoding(SoundEncoding::Signed);
let dict = sound.build_sound_dict();
assert_eq!(dict.get("Type"), Some(&Object::Name("Sound".to_string())));
assert_eq!(dict.get("R"), Some(&Object::Integer(44100)));
assert_eq!(dict.get("C"), Some(&Object::Integer(2)));
assert_eq!(dict.get("B"), Some(&Object::Integer(16)));
assert_eq!(dict.get("E"), Some(&Object::Name("Signed".to_string())));
}
#[test]
fn test_sound_annotation_new() {
let rect = Rect::new(72.0, 720.0, 24.0, 24.0);
let data = vec![0u8; 500];
let annot = SoundAnnotation::new(rect, data, 44100);
assert_eq!(annot.rect.x, 72.0);
assert_eq!(annot.sound.sample_rate, 44100);
assert!(matches!(annot.icon, SoundIcon::Speaker));
}
#[test]
fn test_sound_annotation_builder() {
let rect = Rect::new(100.0, 500.0, 32.0, 32.0);
let annot = SoundAnnotation::new(rect, vec![], 22050)
.with_channels(2)
.with_bits(16)
.with_encoding(SoundEncoding::Signed)
.with_icon(SoundIcon::Mic)
.with_contents("Audio recording")
.with_author("User");
assert_eq!(annot.sound.channels, 2);
assert_eq!(annot.sound.bits_per_sample, 16);
assert!(matches!(annot.icon, SoundIcon::Mic));
assert_eq!(annot.contents, Some("Audio recording".to_string()));
assert_eq!(annot.author, Some("User".to_string()));
}
#[test]
fn test_sound_annotation_build() {
let rect = Rect::new(72.0, 720.0, 24.0, 24.0);
let annot = SoundAnnotation::new(rect, vec![1, 2, 3], 44100)
.with_contents("Click to play")
.with_icon(SoundIcon::Speaker);
let dict = annot.build(&[]);
assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
assert_eq!(dict.get("Subtype"), Some(&Object::Name("Sound".to_string())));
assert_eq!(dict.get("Name"), Some(&Object::Name("Speaker".to_string())));
assert!(dict.contains_key("Rect"));
assert!(dict.contains_key("Contents"));
}
#[test]
fn test_sound_encoding_names() {
assert_eq!(SoundEncoding::Raw.pdf_name(), "Raw");
assert_eq!(SoundEncoding::Signed.pdf_name(), "Signed");
assert_eq!(SoundEncoding::MuLaw.pdf_name(), "muLaw");
assert_eq!(SoundEncoding::ALaw.pdf_name(), "ALaw");
}
#[test]
fn test_sound_icon_names() {
assert_eq!(SoundIcon::Speaker.pdf_name(), "Speaker");
assert_eq!(SoundIcon::Mic.pdf_name(), "Mic");
}
}