use crate::ooxml::error::Result;
use crate::ooxml::pptx::shapes::textframe::TextFrame;
use quick_xml::events::Event;
use quick_xml::Reader;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShapeType {
Shape,
Picture,
GraphicFrame,
GroupShape,
Connector,
Unknown,
}
#[derive(Debug, Clone)]
pub struct BaseShape {
xml_bytes: Vec<u8>,
shape_type: ShapeType,
name: Option<String>,
geometry: Option<ShapeGeometry>,
}
#[derive(Debug, Clone, Copy)]
struct ShapeGeometry {
x: i64,
y: i64,
cx: i64,
cy: i64,
}
impl BaseShape {
pub fn new(xml_bytes: Vec<u8>, shape_type: ShapeType) -> Self {
Self {
xml_bytes,
shape_type,
name: None,
geometry: None,
}
}
#[inline]
pub fn shape_type(&self) -> &ShapeType {
&self.shape_type
}
pub fn name(&mut self) -> Result<String> {
if let Some(ref name) = self.name {
return Ok(name.clone());
}
let mut reader = Reader::from_reader(&self.xml_bytes[..]);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(e)) | Ok(Event::Start(e)) => {
if e.local_name().as_ref() == b"cNvPr" {
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"name" {
let name = std::str::from_utf8(&attr.value)
.unwrap_or("")
.to_string();
self.name = Some(name.clone());
return Ok(name);
}
}
}
}
Ok(Event::Eof) => break,
Err(_) => break,
_ => {}
}
buf.clear();
}
Ok(String::new())
}
pub fn left(&mut self) -> Result<i64> {
self.ensure_geometry()?;
Ok(self.geometry.unwrap().x)
}
pub fn top(&mut self) -> Result<i64> {
self.ensure_geometry()?;
Ok(self.geometry.unwrap().y)
}
pub fn width(&mut self) -> Result<i64> {
self.ensure_geometry()?;
Ok(self.geometry.unwrap().cx)
}
pub fn height(&mut self) -> Result<i64> {
self.ensure_geometry()?;
Ok(self.geometry.unwrap().cy)
}
pub fn is_placeholder(&self) -> bool {
let mut reader = Reader::from_reader(&self.xml_bytes[..]);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(e)) | Ok(Event::Start(e)) => {
if e.local_name().as_ref() == b"ph" {
return true;
}
}
Ok(Event::Eof) => break,
Err(_) => break,
_ => {}
}
buf.clear();
}
false
}
pub fn has_text_frame(&self) -> bool {
self.shape_type == ShapeType::Shape
}
pub fn has_table(&self) -> bool {
self.shape_type == ShapeType::GraphicFrame && self.contains_table_marker()
}
fn contains_table_marker(&self) -> bool {
let xml_str = String::from_utf8_lossy(&self.xml_bytes);
xml_str.contains("a:tbl") || xml_str.contains("<a:tbl")
}
fn ensure_geometry(&mut self) -> Result<()> {
if self.geometry.is_some() {
return Ok(());
}
let mut reader = Reader::from_reader(&self.xml_bytes[..]);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut x = 0;
let mut y = 0;
let mut cx = 0;
let mut cy = 0;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(ref e)) | Ok(Event::Start(ref e)) => {
let tag_name = e.local_name();
if tag_name.as_ref() == b"off" {
for attr in e.attributes().flatten() {
match attr.key.as_ref() {
b"x" => {
x = std::str::from_utf8(&attr.value)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
b"y" => {
y = std::str::from_utf8(&attr.value)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
_ => {}
}
}
} else if tag_name.as_ref() == b"ext" {
for attr in e.attributes().flatten() {
match attr.key.as_ref() {
b"cx" => {
cx = std::str::from_utf8(&attr.value)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
b"cy" => {
cy = std::str::from_utf8(&attr.value)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
_ => {}
}
}
}
}
Ok(Event::Eof) => break,
Err(_) => break,
_ => {}
}
buf.clear();
}
self.geometry = Some(ShapeGeometry { x, y, cx, cy });
Ok(())
}
#[inline]
pub fn xml_bytes(&self) -> &[u8] {
&self.xml_bytes
}
}
#[derive(Debug, Clone)]
pub struct Shape {
base: BaseShape,
}
impl Shape {
pub fn new(xml_bytes: Vec<u8>) -> Self {
Self {
base: BaseShape::new(xml_bytes, ShapeType::Shape),
}
}
#[inline]
pub fn base(&mut self) -> &mut BaseShape {
&mut self.base
}
pub fn text_frame(&self) -> Result<TextFrame> {
TextFrame::from_xml(&self.base.xml_bytes)
}
pub fn text(&self) -> Result<String> {
let tf = self.text_frame()?;
tf.text()
}
}