diff --git a/lib/ppt/pptx.go b/lib/ppt/pptx.go new file mode 100644 index 000000000..04f30b03e --- /dev/null +++ b/lib/ppt/pptx.go @@ -0,0 +1,111 @@ +package ppt + +import ( + "archive/zip" + "bytes" + _ "embed" + "fmt" + "io" + "strings" +) + +// Office Open XML (OOXML) http://officeopenxml.com/prPresentation.php + +//go:embed template.pptx +var pptx_template []byte + +func copyPptxTemplateTo(w *zip.Writer) error { + reader := bytes.NewReader(pptx_template) + zipReader, err := zip.NewReader(reader, reader.Size()) + if err != nil { + fmt.Printf("%v", err) + } + + for _, f := range zipReader.File { + fw, err := w.Create(f.Name) + if err != nil { + return err + } + fr, err := f.Open() + if err != nil { + return err + } + _, err = io.Copy(fw, fr) + if err != nil { + return err + } + } + return nil +} + +func addFile(zipFile *zip.Writer, filePath, content string) error { + w, err := zipFile.Create(filePath) + if err != nil { + return err + } + w.Write([]byte(content)) + return nil +} + +// https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/ +const SLIDE_WIDTH = 9144000 +const SLIDE_HEIGHT = 5143500 + +const RELS_SLIDE_XML = `` + +func getRelsSlideXml(imageId string) string { + return fmt.Sprintf(RELS_SLIDE_XML, imageId, imageId) +} + +const SLIDE_XML = `` + +func getSlideXml(imageId, imageName string, top, left, width, height int) string { + return fmt.Sprintf(SLIDE_XML, imageName, imageName, imageId, left, top, width, height) +} + +func getPresentationXmlRels(slideFileNames []string) string { + var builder strings.Builder + builder.WriteString(``) + + for _, name := range slideFileNames { + builder.WriteString(fmt.Sprintf( + ``, name, name, + )) + } + + builder.WriteString("") + + return builder.String() +} + +func getContentTypesXml(slideFileNames []string) string { + var builder strings.Builder + builder.WriteString(``) + + for _, name := range slideFileNames { + builder.WriteString(fmt.Sprintf( + ``, name, + )) + } + + builder.WriteString(``) + return builder.String() +} + +func getPresentationXml(slideFileNames []string) string { + var builder strings.Builder + builder.WriteString(``) + + builder.WriteString("") + for i, name := range slideFileNames { + builder.WriteString(fmt.Sprintf(``, i, name)) + } + builder.WriteString("") + + builder.WriteString(fmt.Sprintf( + ``, + SLIDE_WIDTH, + SLIDE_HEIGHT, + )) + return builder.String() +} diff --git a/lib/ppt/presentation.go b/lib/ppt/presentation.go new file mode 100644 index 000000000..256bcafa7 --- /dev/null +++ b/lib/ppt/presentation.go @@ -0,0 +1,100 @@ +package ppt + +import ( + "archive/zip" + "bytes" + _ "embed" + "fmt" + "image/png" + "os" +) + +type Pptx struct { + Slides []*Slide +} + +type Slide struct { + Image []byte + Width int + Height int +} + +func NewPresentation() *Pptx { + return &Pptx{} +} + +func (p *Pptx) AddSlide(pngContent []byte) error { + src, err := png.Decode(bytes.NewReader(pngContent)) + if err != nil { + return fmt.Errorf("error decoding PNG image: %v", err) + } + + srcSize := src.Bounds().Size() + height := int(float64(SLIDE_WIDTH) * (float64(srcSize.X) / float64(srcSize.Y))) + + p.Slides = append(p.Slides, &Slide{ + Image: pngContent, + Width: SLIDE_WIDTH, + Height: height, + }) + + return nil +} + +func (p *Pptx) SaveTo(filePath string) error { + // TODO: update core files with metadata + + 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 + } + + // TODO: center the image? + err = addFile(zipFile, fmt.Sprintf("ppt/slides/%s.xml", slideFileName), getSlideXml(imageId, imageId, 0, 0, slide.Width, slide.Height)) + 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 + } + + return nil +} diff --git a/lib/ppt/template.pptx b/lib/ppt/template.pptx new file mode 100644 index 000000000..cdb16e97f Binary files /dev/null and b/lib/ppt/template.pptx differ