From b9ce8324a44bc77b164d3f9747cc08ef8b8f6cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Tue, 4 Apr 2023 11:13:47 -0300 Subject: [PATCH] base ppt export --- lib/ppt/pptx.go | 111 ++++++++++++++++++++++++++++++++++++++++ lib/ppt/presentation.go | 100 ++++++++++++++++++++++++++++++++++++ lib/ppt/template.pptx | Bin 0 -> 29492 bytes 3 files changed, 211 insertions(+) create mode 100644 lib/ppt/pptx.go create mode 100644 lib/ppt/presentation.go create mode 100644 lib/ppt/template.pptx 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 0000000000000000000000000000000000000000..cdb16e97f7e01114a3c2f95379a99ae98530cd92 GIT binary patch literal 29492 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 WE`;cvZV-qM{eK6o`*Pi4?bm+^y}Nn< literal 0 HcmV?d00001