d2/lib/pptx/pptx.go

152 lines
4.4 KiB
Go
Raw Normal View History

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-05 18:22:12 +00:00
"time"
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
}
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
}
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 14:45:04 +00:00
func getRelsSlideXml(imageID string) string {
return fmt.Sprintf(RELS_SLIDE_XML, imageID, imageID)
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 14:45:04 +00:00
func getSlideXml(boardPath []string, imageID string, top, left, width, height int) string {
2023-04-05 14:40:21 +00:00
var slideTitle string
boardName := boardPath[len(boardPath)-1]
prefixPath := boardPath[:len(boardPath)-1]
if len(prefixPath) > 0 {
prefix := strings.Join(prefixPath, " / ") + " / "
slideTitle = fmt.Sprintf(`<a:r><a:t>%s</a:t></a:r><a:r><a:rPr b="1" /><a:t>%s</a:t></a:r>`, prefix, boardName)
} else {
slideTitle = fmt.Sprintf(`<a:r><a:rPr b="1" /><a:t>%s</a:t></a:r>`, boardName)
}
slideDescription := strings.Join(boardPath, " / ")
2023-04-04 21:47:22 +00:00
top += HEADER_HEIGHT
2023-04-10 14:45:04 +00:00
return fmt.Sprintf(SLIDE_XML, slideDescription, slideDescription, imageID, left, top, width, height, slideDescription, HEADER_HEIGHT, slideTitle)
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-04 14:13:47 +00:00
func getPresentationXmlRels(slideFileNames []string) string {
var builder strings.Builder
for _, name := range slideFileNames {
builder.WriteString(fmt.Sprintf(
`<Relationship Id="%s" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/%s.xml" />`, name, name,
))
}
2023-04-10 16:49:30 +00:00
return fmt.Sprintf(RELS_PRESENTATION_XML, builder.String())
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-04 14:13:47 +00:00
func getContentTypesXml(slideFileNames []string) string {
var builder strings.Builder
for _, name := range slideFileNames {
builder.WriteString(fmt.Sprintf(
`<Override PartName="/ppt/slides/%s.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml" />`, name,
))
}
2023-04-10 16:49:30 +00:00
return fmt.Sprintf(CONTENT_TYPES_XML, builder.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-04 14:13:47 +00:00
func getPresentationXml(slideFileNames []string) string {
var builder strings.Builder
for i, name := range slideFileNames {
2023-04-06 18:16:32 +00:00
// in the exported presentation, the first slide ID was 256, so keeping it here for compatibility
2023-04-06 14:01:44 +00:00
builder.WriteString(fmt.Sprintf(`<p:sldId id="%d" r:id="%s" />`, 256+i, name))
2023-04-04 14:13:47 +00:00
}
2023-04-10 16:49:30 +00:00
return fmt.Sprintf(PRESENTATION_XML, builder.String(), SLIDE_WIDTH, SLIDE_HEIGHT)
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 16:49:30 +00:00
func getCoreXml(title, subject, description, creator string) string {
2023-04-06 14:01:44 +00:00
dateTime := time.Now().Format(time.RFC3339)
2023-04-10 16:49:30 +00:00
return fmt.Sprintf(
CORE_XML,
title,
subject,
creator,
description,
creator,
dateTime,
dateTime,
)
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-06 14:01:44 +00:00
func getAppXml(slides []*Slide, d2version string) string {
2023-04-05 18:22:12 +00:00
var builder strings.Builder
2023-04-06 14:01:44 +00:00
for _, slide := range slides {
builder.WriteString(fmt.Sprintf(`<vt:lpstr>%s</vt:lpstr>`, strings.Join(slide.BoardPath, "/")))
}
2023-04-10 16:49:30 +00:00
return fmt.Sprintf(
APP_XML,
len(slides),
len(slides),
len(slides)+3, // number of entries, len(slides) + Office Theme + 2 Fonts
builder.String(),
d2version,
)
2023-04-05 18:22:12 +00:00
}