Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ No LibreOffice, no Chromium, no Docker — just a single binary powered by [Typs
## Features

- **DOCX** — paragraphs, inline formatting (bold/italic/underline/color), tables, images, lists, headers/footers, page setup
- **PPTX** — slides, text boxes, shapes, images, slide masters, speaker notes, gradient backgrounds, shadow/reflection effects
- **PPTX** — slides, text boxes, shapes, tables (with theme-based table styles), images, slide masters, speaker notes, gradient backgrounds, shadow/reflection effects
- **XLSX** — sheets, cell formatting, merged cells, column widths, row heights, conditional formatting (DataBar, IconSet)
- **PDF/A-2b** — archival-compliant output via `--pdf-a`
- **Embedded font extraction** — fonts embedded in PPTX/DOCX are automatically extracted, deobfuscated, and used during conversion
Expand Down Expand Up @@ -129,7 +129,7 @@ Available functions: `convertToPdf(data, format)`, `convertDocxToPdf(data)`, `co
| Format | Status | Key Features |
|--------|--------|-------------|
| DOCX | Supported | Text, tables, images, lists, headers/footers, page setup |
| PPTX | Supported | Slides, text boxes, shapes, images, masters, gradients, effects |
| PPTX | Supported | Slides, text boxes, shapes, tables, images, masters, gradients, effects |
| XLSX | Supported | Sheets, formatting, merged cells, column/row sizing, conditional formatting |

## License
Expand Down
12 changes: 11 additions & 1 deletion crates/office2pdf/src/parser/pptx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ use crate::parser::Parser;
use crate::parser::smartart;
use crate::parser::units::emu_to_pt;

use self::package::{load_theme, parse_presentation_xml, parse_rels_xml, read_zip_entry};
use self::package::{
load_table_styles, load_theme, parse_presentation_xml, parse_rels_xml, read_zip_entry,
};
#[cfg(test)]
use self::package::{resolve_relative_path, scan_chart_refs};
use self::shapes::{
Expand All @@ -44,6 +46,8 @@ mod package;
mod shapes;
#[path = "pptx_slides.rs"]
mod slides;
#[path = "pptx_table_styles.rs"]
mod table_styles;
#[path = "pptx_tables.rs"]
mod tables;
#[path = "pptx_text.rs"]
Expand Down Expand Up @@ -391,6 +395,11 @@ impl Parser for PptxParser {
// Load theme data (if available)
let theme = load_theme(&rel_map, &mut archive);

// Load table styles (uses theme colors for scheme color resolution)
let master_color_map: ColorMapData = default_color_map();
let table_styles: table_styles::TableStyleMap =
load_table_styles(&mut archive, &theme, &master_color_map);

let mut warnings = Vec::new();

// Parse each slide in order, skipping broken slides with warnings
Expand All @@ -417,6 +426,7 @@ impl Parser for PptxParser {
&slide_label,
slide_size,
&theme,
&table_styles,
&mut archive,
) {
Ok((page, slide_warnings)) => {
Expand Down
13 changes: 13 additions & 0 deletions crates/office2pdf/src/parser/pptx_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,19 @@ pub(super) fn load_theme<R: Read + std::io::Seek>(
parse_theme_xml(&theme_xml)
}

/// Load and parse `ppt/tableStyles.xml` from the archive.
/// Returns an empty map if the file is missing.
pub(super) fn load_table_styles<R: Read + std::io::Seek>(
archive: &mut ZipArchive<R>,
theme: &ThemeData,
color_map: &ColorMapData,
) -> table_styles::TableStyleMap {
let Ok(xml) = read_zip_entry(archive, "ppt/tableStyles.xml") else {
return table_styles::TableStyleMap::new();
};
table_styles::parse_table_styles_xml(&xml, theme, color_map)
}

pub(super) fn resolve_relative_path(base_dir: &str, relative: &str) -> String {
crate::parser::xml_util::resolve_relative_path(base_dir, relative)
}
Expand Down
2 changes: 2 additions & 0 deletions crates/office2pdf/src/parser/pptx_shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub(super) fn parse_group_shape(
color_map: &ColorMapData,
warning_context: &str,
inherited_text_body_defaults: &PptxTextBodyStyleDefaults,
table_styles: &table_styles::TableStyleMap,
) -> Result<(Vec<FixedElement>, Vec<ConvertWarning>), ConvertError> {
let mut transform = GroupTransform::default();
let mut in_xfrm = false;
Expand Down Expand Up @@ -173,6 +174,7 @@ pub(super) fn parse_group_shape(
color_map,
warning_context,
inherited_text_body_defaults,
table_styles,
)?;
for element in &mut child_elements {
transform.apply(element);
Expand Down
14 changes: 13 additions & 1 deletion crates/office2pdf/src/parser/pptx_slides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ fn parse_layer_elements<R: Read + std::io::Seek>(
archive: &mut ZipArchive<R>,
) -> (Vec<FixedElement>, Vec<ConvertWarning>) {
let images: SlideImageMap = load_slide_images(layer_path, archive);
let empty_table_styles: table_styles::TableStyleMap = table_styles::TableStyleMap::new();
parse_slide_xml(
layer_xml,
&images,
theme,
color_map,
label,
text_style_defaults,
&empty_table_styles,
)
.unwrap_or_default()
}
Expand Down Expand Up @@ -192,6 +194,7 @@ pub(super) fn parse_single_slide<R: Read + std::io::Seek>(
slide_label: &str,
slide_size: PageSize,
theme: &ThemeData,
table_styles: &table_styles::TableStyleMap,
archive: &mut ZipArchive<R>,
) -> Result<(Page, Vec<ConvertWarning>), ConvertError> {
let chain: SlideInheritanceChain = resolve_inheritance_chain(slide_path, theme, archive)?;
Expand All @@ -206,6 +209,7 @@ pub(super) fn parse_single_slide<R: Read + std::io::Seek>(
&chain.slide_color_map,
slide_label,
&chain.master_text_style_defaults,
table_styles,
)?;
warnings.extend(slide_warnings);

Expand Down Expand Up @@ -613,6 +617,7 @@ struct SlideXmlParser<'a> {
color_map: &'a ColorMapData,
warning_context: &'a str,
inherited_text_body_defaults: &'a PptxTextBodyStyleDefaults,
table_styles: &'a table_styles::TableStyleMap,

// ── Output accumulators ─────────────────────────────────────────
elements: Vec<FixedElement>,
Expand Down Expand Up @@ -669,6 +674,7 @@ impl<'a> SlideXmlParser<'a> {
color_map: &'a ColorMapData,
warning_context: &'a str,
inherited_text_body_defaults: &'a PptxTextBodyStyleDefaults,
table_styles: &'a table_styles::TableStyleMap,
) -> Self {
Self {
xml,
Expand All @@ -677,6 +683,7 @@ impl<'a> SlideXmlParser<'a> {
color_map,
warning_context,
inherited_text_body_defaults,
table_styles,

elements: Vec::new(),
warnings: Vec::new(),
Expand Down Expand Up @@ -729,7 +736,9 @@ impl<'a> SlideXmlParser<'a> {
self.gf.in_xfrm = true;
}
b"tbl" if self.in_graphic_frame => {
if let Ok(mut table) = parse_pptx_table(reader, self.theme, self.color_map) {
if let Ok(mut table) =
parse_pptx_table(reader, self.theme, self.color_map, self.table_styles)
{
scale_pptx_table_geometry_to_frame(
&mut table,
emu_to_pt(self.gf.cx),
Expand All @@ -753,6 +762,7 @@ impl<'a> SlideXmlParser<'a> {
self.color_map,
self.warning_context,
self.inherited_text_body_defaults,
self.table_styles,
) {
self.elements.extend(group_elems);
self.warnings.extend(group_warnings);
Expand Down Expand Up @@ -1320,6 +1330,7 @@ pub(super) fn parse_slide_xml(
color_map: &ColorMapData,
warning_context: &str,
inherited_text_body_defaults: &PptxTextBodyStyleDefaults,
table_styles: &table_styles::TableStyleMap,
) -> Result<(Vec<FixedElement>, Vec<ConvertWarning>), ConvertError> {
let mut reader = Reader::from_str(xml);
let mut parser = SlideXmlParser::new(
Expand All @@ -1329,6 +1340,7 @@ pub(super) fn parse_slide_xml(
color_map,
warning_context,
inherited_text_body_defaults,
table_styles,
);

loop {
Expand Down
Loading
Loading