Skip to content

PPTX 프레젠테이션 처음부터 만들기

PptxWriter는 새로운 PowerPoint .pptx 파일을 메모리에서 생성하여 디스크 또는 바이트 버퍼에 기록합니다 — 템플릿 불필요, PowerPoint 설치 불필요, COM 자동화 불필요. 형식 독립적 IR을 통한 매핑 대신, 슬라이드·제목·본문 텍스트·이미지를 명시적으로 제어하고 싶을 때 적합한 도구입니다.

이 Writer는 실제 PowerPoint 파일 806개에서 평균 0.7ms, 98.4% 성공률로 PPTX를 추출하는 Rust 코어와 동일한 엔진으로 구동되며, Python, Rust, Go, C#, JavaScript(네이티브 Node)에서 사용할 수 있습니다.

단위에 대한 참고사항: 모든 위치와 크기는 EMU(English Metric Units) 기준입니다. 914400 EMU = 1인치, 360000 EMU ≈ 1cm. 기본 캔버스는 12192000 × 6858000 EMU — 표준 16:9 와이드스크린(33.87cm × 19.05cm)입니다.

Python에서 PPTX 파일을 처음부터 만들려면?

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는 새 슬라이드의 0 기반 인덱스를 반환합니다 — 이를 다른 모든 호출의 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, 글머리 목록, 자유 위치 텍스트 상자도 지원합니다.

PptxWriter의 핵심 Rust 시그니처:

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<()>

add_slide가 반환하는 &mut SlideData의 메서드:

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")를 연쇄 호출할 수 있습니다. 메모리 출력에는 Cursor<Vec<u8>>과 같은 Write + Seek를 구현하는 임의의 대상에 write_to를 사용하세요. 다른 바인딩과 동일한 플랫 구조를 선호한다면 인덱스 기반 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[]. PptxWriterIDisposable을 구현하므로 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'이며, dataUint8Array 또는 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 생성 프레젠테이션의 기본 구성 요소인 제목, 본문 단락, 이미지를 제공합니다. 더 풍부한 슬라이드 레이아웃이 필요하다면 Rust의 SlideData 빌더가 글머리 목록, 스타일이 적용된 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 기반 인덱스를 반환하며, 이를 set_slide_title, add_slide_text, add_slide_image에 전달합니다.

위치와 크기의 단위는 무엇인가요? EMU(English Metric Units)입니다. 914400 EMU = 1인치, 360000 EMU ≈ 1cm. 기본 캔버스는 12192000 × 6858000(16:9 와이드스크린)입니다. 슬라이드 추가 전에 set_presentation_size(cx, cy)를 호출하여 변경할 수 있습니다 — 예를 들어 4:3은 9144000 × 6858000입니다.

PowerPoint나 Microsoft 런타임을 설치해야 하나요? 아닙니다. PptxWriter는 유효한 OOXML .pptx ZIP을 Rust만으로 생성합니다. COM 자동화, JVM, 시스템 의존성이 전혀 없으며, 806개의 실제 파일에서 평균 0.7ms, 98.4% 성공률로 PPTX를 읽는 엔진과 동일합니다.

디스크에 쓰지 않고 바이트로 얻으려면 어떻게 하나요? to_bytes()(Python/C#/JS) 또는 ToBytes()(Go)를 호출하면 완전한 .pptx 바이트 버퍼가 반환됩니다. Rust에서는 HTTP 응답이나 오브젝트 스토어 업로드에 적합한 Cursor<Vec<u8>>과 같은 Write + Seek 대상과 함께 write_to(writer)를 사용하세요.

어떤 이미지 형식을 삽입할 수 있나요? PNG, JPEG("jpeg" 또는 "jpg"), GIF입니다. 원시 이미지 바이트와 format 문자열, EMU 단위의 위치/크기를 add_slide_image(Rust에서는 ImageFormat 열거형을 받는 add_image)에 전달하세요.

참고 문서