2023-04-05 18:34:50 +00:00
|
|
|
package pptx
|
2023-04-04 14:13:47 +00:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"archive/zip"
|
|
|
|
|
"bytes"
|
|
|
|
|
_ "embed"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
2023-04-10 18:45:10 +00:00
|
|
|
"text/template"
|
2023-04-04 14:13:47 +00:00
|
|
|
)
|
|
|
|
|
|
2023-04-10 14:45:04 +00:00
|
|
|
// Measurements in OOXML are made in English Metric Units (EMUs) where 1 inch = 914,400 EMUs
|
|
|
|
|
// The intent is to have a measurement unit that doesn't require floating points when dealing with centimeters, inches, points (DPI).
|
2023-04-04 14:13:47 +00:00
|
|
|
// Office Open XML (OOXML) http://officeopenxml.com/prPresentation.php
|
2023-04-10 14:45:04 +00:00
|
|
|
// https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/
|
|
|
|
|
const SLIDE_WIDTH = 9_144_000
|
|
|
|
|
const SLIDE_HEIGHT = 5_143_500
|
|
|
|
|
const HEADER_HEIGHT = 392_471
|
|
|
|
|
|
|
|
|
|
const IMAGE_HEIGHT = SLIDE_HEIGHT - HEADER_HEIGHT
|
|
|
|
|
|
|
|
|
|
// keep the right aspect ratio: SLIDE_WIDTH / SLIDE_HEIGHT = IMAGE_WIDTH / IMAGE_HEIGHT
|
|
|
|
|
const IMAGE_WIDTH = 8_446_273
|
|
|
|
|
const IMAGE_ASPECT_RATIO = float64(IMAGE_WIDTH) / float64(IMAGE_HEIGHT)
|
2023-04-04 14:13:47 +00:00
|
|
|
|
|
|
|
|
//go:embed template.pptx
|
2023-04-10 16:49:30 +00:00
|
|
|
var PPTX_TEMPLATE []byte
|
2023-04-04 14:13:47 +00:00
|
|
|
|
|
|
|
|
func copyPptxTemplateTo(w *zip.Writer) error {
|
2023-04-10 16:49:30 +00:00
|
|
|
reader := bytes.NewReader(PPTX_TEMPLATE)
|
2023-04-04 14:13:47 +00:00
|
|
|
zipReader, err := zip.NewReader(reader, reader.Size())
|
|
|
|
|
if err != nil {
|
2023-04-06 18:16:32 +00:00
|
|
|
fmt.Printf("error creating zip reader: %v", err)
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, f := range zipReader.File {
|
2023-04-06 18:16:32 +00:00
|
|
|
if err := w.Copy(f); err != nil {
|
|
|
|
|
return fmt.Errorf("error copying %s: %v", f.Name, err)
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/slide.xml.rels
|
|
|
|
|
var RELS_SLIDE_XML string
|
2023-04-04 14:13:47 +00:00
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type RelsSlideXmlContent struct {
|
|
|
|
|
FileName string
|
|
|
|
|
RelationshipID string
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/slide.xml
|
|
|
|
|
var SLIDE_XML string
|
2023-04-04 14:13:47 +00:00
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type SlideXmlContent struct {
|
|
|
|
|
Title string
|
|
|
|
|
TitlePrefix string
|
|
|
|
|
Description string
|
|
|
|
|
HeaderHeight int
|
|
|
|
|
ImageID string
|
|
|
|
|
ImageLeft int
|
|
|
|
|
ImageTop int
|
|
|
|
|
ImageWidth int
|
|
|
|
|
ImageHeight int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getSlideXmlContent(imageID string, slide *Slide) SlideXmlContent {
|
|
|
|
|
boardPath := slide.BoardPath
|
2023-04-05 14:40:21 +00:00
|
|
|
boardName := boardPath[len(boardPath)-1]
|
|
|
|
|
prefixPath := boardPath[:len(boardPath)-1]
|
2023-04-10 18:45:10 +00:00
|
|
|
var prefix string
|
2023-04-05 14:40:21 +00:00
|
|
|
if len(prefixPath) > 0 {
|
2023-04-10 18:45:10 +00:00
|
|
|
prefix = strings.Join(prefixPath, " / ") + " / "
|
|
|
|
|
}
|
|
|
|
|
return SlideXmlContent{
|
|
|
|
|
Title: boardName,
|
|
|
|
|
TitlePrefix: prefix,
|
|
|
|
|
Description: strings.Join(boardPath, " / "),
|
|
|
|
|
HeaderHeight: HEADER_HEIGHT,
|
|
|
|
|
ImageID: imageID,
|
|
|
|
|
ImageLeft: slide.ImageLeft,
|
|
|
|
|
ImageTop: slide.ImageTop + HEADER_HEIGHT,
|
|
|
|
|
ImageWidth: slide.ImageWidth,
|
|
|
|
|
ImageHeight: slide.ImageHeight,
|
2023-04-05 14:40:21 +00:00
|
|
|
}
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/rels_presentation.xml
|
|
|
|
|
var RELS_PRESENTATION_XML string
|
|
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type RelsPresentationSlideXmlContent struct {
|
|
|
|
|
RelationshipID string
|
|
|
|
|
FileName string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RelsPresentationXmlContent struct {
|
|
|
|
|
Slides []RelsPresentationSlideXmlContent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getRelsPresentationXmlContent(slideFileNames []string) RelsPresentationXmlContent {
|
|
|
|
|
var content RelsPresentationXmlContent
|
2023-04-04 14:13:47 +00:00
|
|
|
for _, name := range slideFileNames {
|
2023-04-10 18:45:10 +00:00
|
|
|
content.Slides = append(content.Slides, RelsPresentationSlideXmlContent{
|
|
|
|
|
RelationshipID: name,
|
|
|
|
|
FileName: name,
|
|
|
|
|
})
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
return content
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/content_types.xml
|
|
|
|
|
var CONTENT_TYPES_XML string
|
|
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type ContentTypesXmlContent struct {
|
|
|
|
|
FileNames []string
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/presentation.xml
|
|
|
|
|
var PRESENTATION_XML string
|
|
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type PresentationSlideXmlContent struct {
|
|
|
|
|
ID int
|
|
|
|
|
RelationshipID string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type PresentationXmlContent struct {
|
|
|
|
|
SlideWidth int
|
|
|
|
|
SlideHeight int
|
|
|
|
|
Slides []PresentationSlideXmlContent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getPresentationXmlContent(slideFileNames []string) PresentationXmlContent {
|
|
|
|
|
content := PresentationXmlContent{
|
|
|
|
|
SlideWidth: SLIDE_WIDTH,
|
|
|
|
|
SlideHeight: SLIDE_HEIGHT,
|
|
|
|
|
}
|
2023-04-04 14:13:47 +00:00
|
|
|
for i, name := range slideFileNames {
|
2023-04-10 18:45:10 +00:00
|
|
|
content.Slides = append(content.Slides, PresentationSlideXmlContent{
|
|
|
|
|
// in the exported presentation, the first slide ID was 256, so keeping it here for compatibility
|
|
|
|
|
ID: 256 + i,
|
|
|
|
|
RelationshipID: name,
|
|
|
|
|
})
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
2023-04-10 18:45:10 +00:00
|
|
|
return content
|
2023-04-04 14:13:47 +00:00
|
|
|
}
|
2023-04-05 18:22:12 +00:00
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/core.xml
|
|
|
|
|
var CORE_XML string
|
2023-04-05 18:22:12 +00:00
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type CoreXmlContent struct {
|
|
|
|
|
Title string
|
|
|
|
|
Subject string
|
|
|
|
|
Creator string
|
|
|
|
|
Description string
|
|
|
|
|
LastModifiedBy string
|
|
|
|
|
Created string
|
|
|
|
|
Modified string
|
2023-04-05 18:22:12 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-10 16:49:30 +00:00
|
|
|
//go:embed templates/app.xml
|
|
|
|
|
var APP_XML string
|
|
|
|
|
|
2023-04-10 18:45:10 +00:00
|
|
|
type AppXmlContent struct {
|
|
|
|
|
SlideCount int
|
|
|
|
|
TitlesOfPartsCount int
|
|
|
|
|
Titles []string
|
|
|
|
|
D2Version string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func addFileFromTemplate(zipFile *zip.Writer, filePath, templateContent string, templateData interface{}) error {
|
|
|
|
|
w, err := zipFile.Create(filePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmpl, err := template.New(filePath).Parse(templateContent)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2023-04-06 14:01:44 +00:00
|
|
|
}
|
2023-04-10 18:45:10 +00:00
|
|
|
return tmpl.Execute(w, templateData)
|
2023-04-05 18:22:12 +00:00
|
|
|
}
|