d2/lib/pptx/presentation.go

153 lines
4 KiB
Go
Raw Normal View History

2023-04-05 18:34:50 +00:00
// pptx is a package to create slide presentations in pptx (Microsoft Power Point) format.
// A `.pptx` file is just a bunch of zip compressed `.xml` files following the Office Open XML (OOXML) format.
// To see its content, you can just `unzip <path/to/file>.pptx -d <folder>`.
// With this package, it is possible to create a `Presentation` and add `Slide`s to it.
// Then, when saving the presentation, it will generate the required `.xml` files, compress them and write to the disk.
// Note that this isn't a full implementation of the OOXML format, but a wrapper around it.
// There's a base template with common files to the presentation and then when saving, the package generate only the slides and relationships.
// The base template and slide templates were generated using https://python-pptx.readthedocs.io/en/latest/
// For more information about OOXML, check http://officeopenxml.com/index.php
package pptx
2023-04-04 14:13:47 +00:00
import (
"archive/zip"
"bytes"
_ "embed"
"fmt"
"image/png"
"os"
)
2023-04-04 21:00:45 +00:00
type Presentation struct {
2023-04-05 18:22:12 +00:00
Title string
Description string
Subject string
Creator string
D2Version string
2023-04-04 14:13:47 +00:00
Slides []*Slide
}
type Slide struct {
2023-04-05 14:40:21 +00:00
BoardPath []string
2023-04-04 21:47:22 +00:00
Image []byte
ImageWidth int
ImageHeight int
ImageTop int
ImageLeft int
2023-04-04 14:13:47 +00:00
}
2023-04-05 18:22:12 +00:00
func NewPresentation(title, description, subject, creator, d2Version string) *Presentation {
return &Presentation{
Title: title,
Description: description,
Subject: subject,
Creator: creator,
D2Version: d2Version,
}
2023-04-04 14:13:47 +00:00
}
2023-04-05 14:40:21 +00:00
func (p *Presentation) AddSlide(pngContent []byte, boardPath []string) error {
2023-04-04 14:13:47 +00:00
src, err := png.Decode(bytes.NewReader(pngContent))
if err != nil {
return fmt.Errorf("error decoding PNG image: %v", err)
}
2023-04-05 19:09:33 +00:00
var width, height int
2023-04-04 14:13:47 +00:00
srcSize := src.Bounds().Size()
2023-04-04 21:00:45 +00:00
// compute the size and position to fit the slide
if srcSize.X > srcSize.Y {
2023-04-04 21:47:22 +00:00
width = IMAGE_WIDTH
2023-04-05 19:09:33 +00:00
height = int(float64(width) * (float64(srcSize.Y) / float64(srcSize.X)))
2023-04-04 21:00:45 +00:00
} else {
2023-04-04 21:47:22 +00:00
height = IMAGE_HEIGHT
2023-04-04 21:00:45 +00:00
width = int(float64(height) * (float64(srcSize.X) / float64(srcSize.Y)))
}
2023-04-05 19:09:33 +00:00
top := (IMAGE_HEIGHT - height) / 2
left := (SLIDE_WIDTH - width) / 2
2023-04-04 14:13:47 +00:00
2023-04-05 14:40:21 +00:00
slide := &Slide{
BoardPath: make([]string, len(boardPath)),
2023-04-04 21:47:22 +00:00
Image: pngContent,
ImageWidth: width,
ImageHeight: height,
ImageTop: top,
ImageLeft: left,
2023-04-05 14:40:21 +00:00
}
// it must copy the board path to avoid slice reference issues
copy(slide.BoardPath, boardPath)
2023-04-04 14:13:47 +00:00
2023-04-05 14:40:21 +00:00
p.Slides = append(p.Slides, slide)
2023-04-04 14:13:47 +00:00
return nil
}
2023-04-04 21:00:45 +00:00
func (p *Presentation) SaveTo(filePath string) error {
2023-04-04 14:13:47 +00:00
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
zipFile := zip.NewWriter(f)
defer zipFile.Close()
copyPptxTemplateTo(zipFile)
var slideFileNames []string
for i, slide := range p.Slides {
imageId := fmt.Sprintf("slide%dImage", i+1)
slideFileName := fmt.Sprintf("slide%d", i+1)
slideFileNames = append(slideFileNames, slideFileName)
imageWriter, err := zipFile.Create(fmt.Sprintf("ppt/media/%s.png", imageId))
if err != nil {
return err
}
_, err = imageWriter.Write(slide.Image)
if err != nil {
return err
}
err = addFile(zipFile, fmt.Sprintf("ppt/slides/_rels/%s.xml.rels", slideFileName), getRelsSlideXml(imageId))
if err != nil {
return err
}
2023-04-04 21:47:22 +00:00
err = addFile(
zipFile,
fmt.Sprintf("ppt/slides/%s.xml", slideFileName),
2023-04-05 14:40:21 +00:00
getSlideXml(slide.BoardPath, imageId, slide.ImageTop, slide.ImageLeft, slide.ImageWidth, slide.ImageHeight),
2023-04-04 21:47:22 +00:00
)
2023-04-04 14:13:47 +00:00
if err != nil {
return err
}
}
err = addFile(zipFile, "[Content_Types].xml", getContentTypesXml(slideFileNames))
if err != nil {
return err
}
err = addFile(zipFile, "ppt/_rels/presentation.xml.rels", getPresentationXmlRels(slideFileNames))
if err != nil {
return err
}
err = addFile(zipFile, "ppt/presentation.xml", getPresentationXml(slideFileNames))
if err != nil {
return err
}
2023-04-05 18:22:12 +00:00
err = addFile(zipFile, "docProps/core.xml", getCoreXml(p.Title, p.Subject, p.Description, p.Creator))
if err != nil {
return err
}
2023-04-06 14:01:44 +00:00
err = addFile(zipFile, "docProps/app.xml", getAppXml(p.Slides, p.D2Version))
2023-04-05 18:22:12 +00:00
if err != nil {
return err
}
2023-04-04 14:13:47 +00:00
return nil
}