Создание PPTX-презентации с нуля
PptxWriter создаёт новый файл PowerPoint .pptx в памяти и записывает его на диск или в байтовый буфер — без шаблонов, без установки PowerPoint, без COM-автоматизации. Это правильный инструмент, когда нужен явный контроль над слайдами, заголовками, основным текстом и изображениями, а не преобразование через формат-агностический IR.
Writer построен на том же ядре Rust, которое читает PPTX со средним временем 0,7 мс и 98,4% успешных результатов на 806 реальных файлах PowerPoint, и доступен из Python, Rust, Go, C# и JavaScript (нативный Node).
Важное замечание об единицах: все позиции и размеры задаются в EMU (English Metric Units). 914400 EMU = 1 дюйм, 360000 EMU ≈ 1 см. Холст по умолчанию — 12192000 × 6858000 EMU, стандартный широкоэкранный формат 16:9 (33,87 × 19,05 см).
Как создать PPTX-файл с нуля на Python?
Создайте PptxWriter, вызовите add_slide(), чтобы получить индекс слайда (начиная с 0), затем используйте этот индекс, чтобы задать заголовок и добавить основной текст. В конце вызовите save().
Rust
use office_oxide::pptx::write::PptxWriter;
fn main() -> office_oxide::Result<()> {
let mut pres = PptxWriter::new();
let slide = pres.add_slide_get_index(); // -> 0 (the slide index)
pres.slide_set_title(slide, "Quarterly Results");
pres.slide_add_text(slide, "Revenue grew 18% quarter over quarter.");
pres.slide_add_text(slide, "All regions exceeded target.");
let slide2 = pres.add_slide_get_index(); // -> 1
pres.slide_set_title(slide2, "Next Steps");
pres.slide_add_text(slide2, "Expand EU sales team.");
pres.save("results.pptx")?;
Ok(())
}
Python
from office_oxide import PptxWriter
pres = PptxWriter()
slide = pres.add_slide() # -> 0 (the slide index)
pres.set_slide_title(slide, "Quarterly Results")
pres.add_slide_text(slide, "Revenue grew 18% quarter over quarter.")
pres.add_slide_text(slide, "All regions exceeded target.")
slide2 = pres.add_slide() # -> 1
pres.set_slide_title(slide2, "Next Steps")
pres.add_slide_text(slide2, "Expand EU sales team.")
pres.save("results.pptx")
JavaScript
import { PptxWriter } from 'office-oxide';
const pres = new PptxWriter();
const slide = pres.addSlide(); // -> 0 (the slide index)
pres.setSlideTitle(slide, 'Quarterly Results');
pres.addSlideText(slide, 'Revenue grew 18% quarter over quarter.');
pres.addSlideText(slide, 'All regions exceeded target.');
const slide2 = pres.addSlide(); // -> 1
pres.setSlideTitle(slide2, 'Next Steps');
pres.addSlideText(slide2, 'Expand EU sales team.');
pres.save('results.pptx');
pres.close(); // release the native handle
Go
package main
import officeoxide "github.com/yfedoseev/office_oxide/go"
func main() {
pres := officeoxide.NewPptxWriter()
defer pres.Close()
slide := pres.AddSlide() // -> 0 (the slide index)
pres.SetSlideTitle(slide, "Quarterly Results")
pres.AddSlideText(slide, "Revenue grew 18% quarter over quarter.")
pres.AddSlideText(slide, "All regions exceeded target.")
slide2 := pres.AddSlide() // -> 1
pres.SetSlideTitle(slide2, "Next Steps")
pres.AddSlideText(slide2, "Expand EU sales team.")
if err := pres.Save("results.pptx"); err != nil {
panic(err)
}
}
C#
using OfficeOxide;
using var pres = new PptxWriter();
uint slide = pres.AddSlide(); // -> 0 (the slide index)
pres.SetSlideTitle(slide, "Quarterly Results");
pres.AddSlideText(slide, "Revenue grew 18% quarter over quarter.");
pres.AddSlideText(slide, "All regions exceeded target.");
uint slide2 = pres.AddSlide(); // -> 1
pres.SetSlideTitle(slide2, "Next Steps");
pres.AddSlideText(slide2, "Expand EU sales team.");
pres.Save("results.pptx");
C
#include "office_oxide.h"
OfficePptxWriterHandle *p = office_pptx_writer_new();
uint32_t slide = office_pptx_writer_add_slide(p); /* -> 0 (the slide index) */
office_pptx_slide_set_title(p, slide, "Quarterly Results");
office_pptx_slide_add_text(p, slide, "Revenue grew 18% quarter over quarter.");
office_pptx_slide_add_text(p, slide, "All regions exceeded target.");
uint32_t slide2 = office_pptx_writer_add_slide(p); /* -> 1 */
office_pptx_slide_set_title(p, slide2, "Next Steps");
office_pptx_slide_add_text(p, slide2, "Expand EU sales team.");
int err = 0;
office_pptx_writer_save(p, "results.pptx", &err);
office_pptx_writer_free(p);
Чтобы передать байты в веб-ответ или объектное хранилище вместо записи на диск, используйте to_bytes():
Rust
use std::io::Cursor;
let mut buf = Cursor::new(Vec::new());
pres.write_to(&mut buf)?; // a complete .pptx ZIP
let data = buf.into_inner(); // -> Vec<u8>
Python
data = pres.to_bytes() # -> bytes (a complete .pptx ZIP)
with open("results.pptx", "wb") as f:
f.write(data)
JavaScript
const data = pres.toBytes(); // -> Buffer (a complete .pptx ZIP)
writeFileSync('results.pptx', data);
Go
data, err := pres.ToBytes() // -> []byte (a complete .pptx ZIP)
if err != nil {
panic(err)
}
_ = os.WriteFile("results.pptx", data, 0o644)
C#
byte[] data = pres.ToBytes(); // a complete .pptx ZIP
File.WriteAllBytes("results.pptx", data);
C
size_t out_len = 0;
int err = 0;
uint8_t *buf = office_pptx_writer_to_bytes(p, &out_len, &err); /* a complete .pptx ZIP */
if (buf) { /* stream buf[0..out_len] */ office_oxide_free_bytes(buf, out_len); }
Сигнатуры методов (Python)
| Метод | Сигнатура |
|---|---|
| Конструктор | PptxWriter() |
| Размер холста | set_presentation_size(cx: int, cy: int) -> None |
| Добавить слайд | add_slide() -> int |
| Задать заголовок | set_slide_title(slide: int, title: str) -> None |
| Добавить основной текст | add_slide_text(slide: int, text: str) -> None |
| Добавить изображение | add_slide_image(slide: int, data: bytes, format: str, x: int, y: int, cx: int, cy: int) -> None |
| Сохранить | save(path) -> None |
| Экспортировать | to_bytes() -> bytes |
add_slide возвращает индекс нового слайда, начиная с нуля — передавайте его как аргумент slide во все остальные вызовы. Каждый вызов add_slide_text добавляет один абзац в текстовый плейсхолдер слайда.
Как задать размер слайда и добавить изображение?
Вызовите set_presentation_size(cx, cy) до добавления слайдов, чтобы изменить холст — например, перейти к формату 4:3 (9144000 × 6858000). add_slide_image встраивает сырые байты изображения в абсолютную позицию; format — это "png", "jpeg"/"jpg" или "gif".
Rust
use office_oxide::pptx::write::PptxWriter;
use office_oxide::ir::ImageFormat;
fn main() -> office_oxide::Result<()> {
let mut pres = PptxWriter::new();
pres.set_presentation_size(9_144_000, 6_858_000); // 4:3, in EMU
let logo = std::fs::read("logo.png")?;
pres.add_slide()
.set_title("Logo")
// Place the image 1 inch from the top-left, sized 3in × 2in
.add_image(logo, ImageFormat::Png, 914_400, 914_400, 2_743_200, 1_828_800);
pres.save("deck.pptx")?;
Ok(())
}
Python
from office_oxide import PptxWriter
pres = PptxWriter()
pres.set_presentation_size(9_144_000, 6_858_000) # 4:3, in EMU
slide = pres.add_slide()
pres.set_slide_title(slide, "Logo")
with open("logo.png", "rb") as f:
logo = f.read()
# Place the image 1 inch from the top-left, sized 3in × 2in
pres.add_slide_image(slide, logo, "png",
x=914_400, y=914_400,
cx=2_743_200, cy=1_828_800)
pres.save("deck.pptx")
JavaScript
import { readFileSync } from 'node:fs';
import { PptxWriter } from 'office-oxide';
const pres = new PptxWriter();
pres.setPresentationSize(9144000, 6858000); // 4:3, in EMU
const slide = pres.addSlide();
pres.setSlideTitle(slide, 'Logo');
const logo = readFileSync('logo.png');
// Place the image 1 inch from the top-left, sized 3in × 2in
pres.addSlideImage(slide, logo, 'png', 914400, 914400, 2743200, 1828800);
pres.save('deck.pptx');
pres.close();
Go
package main
import (
"os"
officeoxide "github.com/yfedoseev/office_oxide/go"
)
func main() {
pres := officeoxide.NewPptxWriter()
defer pres.Close()
pres.SetPresentationSize(9144000, 6858000) // 4:3, in EMU
slide := pres.AddSlide()
pres.SetSlideTitle(slide, "Logo")
logo, _ := os.ReadFile("logo.png")
// Place the image 1 inch from the top-left, sized 3in × 2in
pres.AddSlideImage(slide, logo, "png", 914400, 914400, 2743200, 1828800)
if err := pres.Save("deck.pptx"); err != nil {
panic(err)
}
}
C#
using OfficeOxide;
using var pres = new PptxWriter();
pres.SetPresentationSize(9144000, 6858000); // 4:3, in EMU
uint slide = pres.AddSlide();
pres.SetSlideTitle(slide, "Logo");
byte[] logo = File.ReadAllBytes("logo.png");
// Place the image 1 inch from the top-left, sized 3in × 2in
pres.AddSlideImage(slide, logo, "png", 914400, 914400, 2743200, 1828800);
pres.Save("deck.pptx");
C
#include "office_oxide.h"
OfficePptxWriterHandle *p = office_pptx_writer_new();
office_pptx_writer_set_presentation_size(p, 9144000, 6858000); /* 4:3, in EMU */
uint32_t slide = office_pptx_writer_add_slide(p);
office_pptx_slide_set_title(p, slide, "Logo");
/* read logo.png into img_data / img_len yourself, then: */
/* Place the image 1 inch from the top-left, sized 3in × 2in */
office_pptx_slide_add_image(p, slide, img_data, img_len, "png",
914400, 914400, 2743200, 1828800);
int err = 0;
office_pptx_writer_save(p, "deck.pptx", &err);
office_pptx_writer_free(p);
Полный цикл создания и экспорта по языкам
Следующий пример создаёт два слайда, встраивает изображение, сохраняет на диск и экспортирует в байты — одна и та же операция для каждой языковой привязки.
Rust
use office_oxide::pptx::write::{PptxWriter, Run};
use office_oxide::ir::ImageFormat;
fn main() -> office_oxide::Result<()> {
let mut pres = PptxWriter::new();
// Slide 1: title, body text, a bullet list, and a styled paragraph
pres.add_slide()
.set_title("Quarterly Results")
.add_text("Revenue grew 18% quarter over quarter.")
.add_bullet_list(&["North America: +22%", "EU: +14%", "APAC: +9%"])
.add_rich_text(&[
Run::new("Highlight: ").bold(),
Run::new("record EU quarter").italic().color("0070C0"),
]);
// Slide 2: a title plus a positioned image
let logo = std::fs::read("logo.png")?;
pres.add_slide()
.set_title("Logo")
.add_image(logo, ImageFormat::Png, 914_400, 914_400, 2_743_200, 1_828_800);
pres.save("results.pptx")?;
Ok(())
}
Go
package main
import (
"os"
officeoxide "github.com/yfedoseev/office_oxide/go"
)
func main() {
pres := officeoxide.NewPptxWriter()
defer pres.Close()
slide := pres.AddSlide() // uint32, 0-based index
pres.SetSlideTitle(slide, "Quarterly Results")
pres.AddSlideText(slide, "Revenue grew 18% quarter over quarter.")
pres.AddSlideText(slide, "All regions exceeded target.")
// Embed an image. format is "png", "jpeg"/"jpg", or "gif".
// x, y, cx, cy are in EMU (914400 = 1 inch).
logo, _ := os.ReadFile("logo.png")
pres.AddSlideImage(slide, logo, "png", 914400, 914400, 2743200, 1828800)
if err := pres.Save("results.pptx"); err != nil {
panic(err)
}
// Or export to bytes:
data, err := pres.ToBytes()
if err != nil {
panic(err)
}
_ = os.WriteFile("results-copy.pptx", data, 0o644)
}
C#
using OfficeOxide;
using var pres = new PptxWriter();
uint slide = pres.AddSlide(); // 0-based index
pres.SetSlideTitle(slide, "Quarterly Results");
pres.AddSlideText(slide, "Revenue grew 18% quarter over quarter.");
pres.AddSlideText(slide, "All regions exceeded target.");
// Embed an image. format is "png", "jpeg"/"jpg", or "gif".
// x, y, cx, cy are in EMU (914400 = 1 inch).
byte[] logo = File.ReadAllBytes("logo.png");
pres.AddSlideImage(slide, logo, "png", 914400, 914400, 2743200, 1828800);
pres.Save("results.pptx");
// Or export to a byte[]:
byte[] data = pres.ToBytes();
File.WriteAllBytes("results-copy.pptx", data);
JavaScript
import { readFileSync, writeFileSync } from 'node:fs';
import { PptxWriter } from 'office-oxide';
const pres = new PptxWriter();
const slide = pres.addSlide(); // 0-based index
pres.setSlideTitle(slide, 'Quarterly Results');
pres.addSlideText(slide, 'Revenue grew 18% quarter over quarter.');
pres.addSlideText(slide, 'All regions exceeded target.');
// Embed an image. format is 'png', 'jpeg'/'jpg', or 'gif'.
// x, y, cx, cy are in EMU (914400 = 1 inch).
const logo = readFileSync('logo.png');
pres.addSlideImage(slide, logo, 'png', 914400, 914400, 2743200, 1828800);
pres.save('results.pptx');
// Or export to a Buffer:
const data = pres.toBytes();
writeFileSync('results-copy.pptx', data);
pres.close(); // release the native handle
C
#include "office_oxide.h"
OfficePptxWriterHandle *p = office_pptx_writer_new();
uint32_t slide = office_pptx_writer_add_slide(p); /* 0-based index */
office_pptx_slide_set_title(p, slide, "Quarterly Results");
office_pptx_slide_add_text(p, slide, "Revenue grew 18% quarter over quarter.");
office_pptx_slide_add_text(p, slide, "All regions exceeded target.");
/* Embed an image: raw png/jpeg/gif bytes; x,y,cx,cy in EMU (914400 = 1 inch). */
/* read logo.png into img_data / img_len yourself, then: */
office_pptx_slide_add_image(p, slide, img_data, img_len, "png",
914400, 914400, 2743200, 1828800);
int err = 0;
office_pptx_writer_save(p, "results.pptx", &err);
/* Or export to bytes: */
size_t out_len = 0;
uint8_t *buf = office_pptx_writer_to_bytes(p, &out_len, &err);
if (buf) { /* stream buf[0..out_len] */ office_oxide_free_bytes(buf, out_len); }
office_pptx_writer_free(p);
Сигнатуры по языковым привязкам
Крейт Rust предоставляет более богатый fluent-строитель. add_slide() возвращает заимствованный дескриптор &mut SlideData, методы которого можно цепочкой вызывать — так весь слайд настраивается в одном выражении. Помимо простого текста поддерживаются стилизованные Run, маркированные списки и свободно позиционируемые текстовые блоки.
Ключевые сигнатуры Rust на PptxWriter:
pub fn new() -> Self
pub fn set_presentation_size(&mut self, cx: u64, cy: u64) -> &mut Self
pub fn add_slide(&mut self) -> &mut SlideData
pub fn save(&self, path: impl AsRef<Path>) -> Result<()>
pub fn write_to<W: Write + Seek>(&self, writer: W) -> Result<()>
и на &mut SlideData, возвращаемом add_slide:
pub fn set_title(&mut self, title: &str) -> &mut Self
pub fn add_text(&mut self, text: &str) -> &mut Self
pub fn add_rich_text(&mut self, runs: &[Run]) -> &mut Self
pub fn add_bullet_list(&mut self, items: &[&str]) -> &mut Self
pub fn add_text_box(&mut self, text: &str, x: i64, y: i64, cx: i64, cy: i64) -> &mut Self
pub fn add_image(&mut self, data: Vec<u8>, format: ImageFormat, x: i64, y: i64, cx: u64, cy: u64) -> &mut Self
Run — это строитель: Run::new(text), после которого можно цепочкой вызывать .bold(), .italic(), .underline(), .strikethrough(), .color("FF0000") (6-символьный hex без #), .font_size(18.0) и .font("Calibri"). Для вывода в память используйте write_to с любым Write + Seek (например, Cursor<Vec<u8>>). Также доступен индексный API (add_slide_get_index, slide_set_title, slide_add_text, slide_add_image) для тех, кто предпочитает единообразный плоский стиль остальных привязок.
Сигнатуры Go: NewPptxWriter() *PptxWriter, SetPresentationSize(cx, cy uint64), AddSlide() uint32, SetSlideTitle(slide uint32, title string), AddSlideText(slide uint32, text string), AddSlideImage(slide uint32, data []byte, format string, x, y int64, cx, cy uint64), Save(path string) error, ToBytes() ([]byte, error), Close(). Всегда используйте defer pres.Close() для освобождения нативного дескриптора.
Сигнатуры C#: PptxWriter(), SetPresentationSize(ulong cx, ulong cy), AddSlide() -> uint, SetSlideTitle(uint slide, string title), AddSlideText(uint slide, string text), AddSlideImage(uint slide, byte[] data, string format, long x, long y, ulong cx, ulong cy), Save(string path), ToBytes() -> byte[]. PptxWriter реализует IDisposable, поэтому оборачивайте его в using.
Сигнатуры JavaScript: new PptxWriter(), setPresentationSize(cx, cy), addSlide(), setSlideTitle(slide, title), addSlideText(slide, text), addSlideImage(slide, data, format, x, y, cx, cy), save(path), toBytes(), close(). Аргумент format — 'png' | 'jpeg' | 'jpg' | 'gif', data — Uint8Array или Buffer.
Сигнатуры C: office_pptx_writer_new(), office_pptx_writer_set_presentation_size(p, cx, cy), office_pptx_writer_add_slide(p) -> uint32_t, office_pptx_slide_set_title(p, slide, title), office_pptx_slide_add_text(p, slide, text), office_pptx_slide_add_image(p, slide, data, len, format, x, y, cx, cy), office_pptx_writer_save(p, path, &err), office_pptx_writer_to_bytes(p, &out_len, &err) -> uint8_t*, office_pptx_writer_free(p). Освобождайте возвращённые байтовые буферы через office_oxide_free_bytes(ptr, len).
Стилизованные фрагменты, маркеры и текстовые блоки (Rust)
Плоские привязки (Python/Go/C#/JS) предоставляют заголовки, абзацы основного текста и изображения — строительные блоки для AI-генерируемых презентаций. Для более богатого макета отдельных слайдов строитель SlideData в Rust дополнительно поддерживает маркированные списки, стилизованные Run и абсолютно позиционированные текстовые блоки:
use office_oxide::pptx::write::{PptxWriter, Run};
fn main() -> office_oxide::Result<()> {
let mut pres = PptxWriter::new();
pres.add_slide()
.set_title("Free-floating text boxes")
.add_text("The body placeholder holds regular paragraphs.")
// A text box at 1in × 4in, sized 6in × 0.75in (EMU)
.add_text_box("Pinned note", 914_400, 3_657_600, 5_486_400, 685_800)
// A bullet list rendered in the body placeholder
.add_bullet_list(&["Fast", "Safe", "Dependency-free"]);
pres.write_to(std::io::Cursor::new(Vec::new()))?; // in-memory
Ok(())
}
Часто задаваемые вопросы
Как адресовать слайды — по дескриптору или по индексу?
Обоими способами. В Rust add_slide() возвращает дескриптор &mut SlideData, методы которого (set_title, add_text и т.д.) можно цепочкой вызывать. В Python, Go, C# и JavaScript add_slide() возвращает 0-based индекс слайда, который передаётся в set_slide_title, add_slide_text и add_slide_image.
В каких единицах задаются позиции и размеры?
В EMU (English Metric Units). 914400 EMU = 1 дюйм, 360000 EMU ≈ 1 см. Холст по умолчанию — 12192000 × 6858000 (16:9 широкоэкранный). Вызовите set_presentation_size(cx, cy) до добавления слайдов, чтобы изменить формат — например, 9144000 × 6858000 для 4:3.
Нужно ли устанавливать PowerPoint или какой-либо Microsoft runtime?
Нет. PptxWriter генерирует корректный OOXML-файл .pptx ZIP целиком на Rust. Никакой COM-автоматизации, никакой JVM, никаких системных зависимостей — тот же движок, что читает PPTX со средним временем 0,7 мс и 98,4% успешных результатов на 806 реальных файлах.
Как получить файл как байты, а не записать его на диск?
Вызовите to_bytes() (Python/C#/JS) или ToBytes() (Go) — вернётся полный байтовый буфер .pptx. В Rust используйте write_to(writer) с любым Write + Seek (например, Cursor<Vec<u8>>). Это идеально подходит для HTTP-ответов и загрузки в объектные хранилища.
Какие форматы изображений можно встраивать?
PNG, JPEG ("jpeg" или "jpg") и GIF. Передайте сырые байты изображения, строку format и позицию/размер в EMU в add_slide_image (или add_image в Rust, принимающий перечисление ImageFormat).
Смотрите также
- Создание документов из IR — одна схема, три целевых формата (DOCX/XLSX/PPTX)
- Создание XLSX-книги с нуля — родственный строитель
XlsxWriter - Извлечение текста из PPTX — чтение презентации в простой текст или структурированный IR