package pptx import ( "archive/zip" "bytes" _ "embed" "fmt" "strings" "time" ) // 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("error creating zip reader: %v", err) } for _, f := range zipReader.File { if err := w.Copy(f); err != nil { return fmt.Errorf("error copying %s: %v", f.Name, 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 = 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) func getRelsSlideXml(slide *Slide) string { var builder strings.Builder builder.WriteString(``) builder.WriteString( fmt.Sprintf( ``, slide.ImageId, slide.ImageId, ), ) for _, link := range slide.Links { if link.isExternal() { builder.WriteString( fmt.Sprintf( ``, link.Id, link.ExternalUrl, ), ) } else { builder.WriteString( fmt.Sprintf( ``, link.Id, link.SlideIndex, ), ) } } builder.WriteString(``) return builder.String() } func getSlideXml(slide *Slide) string { var builder strings.Builder builder.WriteString(``) slideDescription := strings.Join(slide.BoardPath, " / ") builder.WriteString(fmt.Sprintf(``, slideDescription, slideDescription)) builder.WriteString(``) builder.WriteString(fmt.Sprintf(``, slide.ImageId)) builder.WriteString(``) builder.WriteString(fmt.Sprintf(``, slide.ImageLeft, slide.ImageTop)) builder.WriteString(fmt.Sprintf(``, slide.ImageWidth, slide.ImageHeight)) builder.WriteString(``) builder.WriteString(fmt.Sprintf(``, slideDescription)) builder.WriteString(``) builder.WriteString(``) builder.WriteString(fmt.Sprintf(``, HEADER_HEIGHT)) builder.WriteString(``) boardName := slide.BoardPath[len(slide.BoardPath)-1] prefixPath := slide.BoardPath[:len(slide.BoardPath)-1] if len(prefixPath) > 0 { prefix := strings.Join(prefixPath, " / ") + " / " builder.WriteString(fmt.Sprintf(`%s%s`, prefix, boardName)) } else { builder.WriteString(fmt.Sprintf(`%s`, boardName)) } for _, link := range slide.Links { builder.WriteString(getLinkXml(link)) } builder.WriteString(``) return builder.String() } 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 { // in the exported presentation, the first slide ID was 256, so keeping it here for compatibility builder.WriteString(fmt.Sprintf(``, 256+i, name)) } builder.WriteString("") builder.WriteString(fmt.Sprintf( ``, SLIDE_WIDTH, SLIDE_HEIGHT, )) return builder.String() } func getCoreXml(title, subject, description, creator string) string { var builder strings.Builder builder.WriteString(``) builder.WriteString(``) builder.WriteString(fmt.Sprintf(`%s`, title)) builder.WriteString(fmt.Sprintf(`%s`, subject)) builder.WriteString(fmt.Sprintf(`%s`, creator)) builder.WriteString(``) builder.WriteString(fmt.Sprintf(`%s`, description)) builder.WriteString(fmt.Sprintf(`%s`, creator)) builder.WriteString(`1`) dateTime := time.Now().Format(time.RFC3339) builder.WriteString(fmt.Sprintf(`%s`, dateTime)) builder.WriteString(fmt.Sprintf(`%s`, dateTime)) builder.WriteString(``) builder.WriteString(``) return builder.String() } func getAppXml(slides []*Slide, d2version string) string { var builder strings.Builder builder.WriteString(``) builder.WriteString(``) builder.WriteString(`1`) builder.WriteString(`0`) builder.WriteString(`D2`) builder.WriteString(`On-screen Show (4:3)`) builder.WriteString(`0`) builder.WriteString(fmt.Sprintf(`%d`, len(slides))) builder.WriteString(`0`) builder.WriteString(`0`) builder.WriteString(`0`) builder.WriteString(`false`) builder.WriteString(``) builder.WriteString(``) builder.WriteString(``) builder.WriteString(`Fonts`) builder.WriteString(``) builder.WriteString(``) builder.WriteString(`2`) builder.WriteString(``) builder.WriteString(``) builder.WriteString(`Theme`) builder.WriteString(``) builder.WriteString(``) builder.WriteString(`1`) builder.WriteString(``) builder.WriteString(``) builder.WriteString(`Slide Titles`) builder.WriteString(``) builder.WriteString(``) builder.WriteString(fmt.Sprintf(`%d`, len(slides))) builder.WriteString(``) builder.WriteString(``) builder.WriteString(``) builder.WriteString(``) // number of entries, len(slides) + Office Theme + 2 Fonts builder.WriteString(fmt.Sprintf(``, len(slides)+3)) builder.WriteString(`Arial`) builder.WriteString(`Calibri`) builder.WriteString(`Office Theme`) for _, slide := range slides { builder.WriteString(fmt.Sprintf(`%s`, strings.Join(slide.BoardPath, "/"))) } builder.WriteString(``) builder.WriteString(``) builder.WriteString(``) builder.WriteString(``) builder.WriteString(`false`) builder.WriteString(`false`) builder.WriteString(``) builder.WriteString(`false`) builder.WriteString(fmt.Sprintf(`%s`, d2version)) builder.WriteString(``) return builder.String() } //go:embed xmlTemplates/link.xml var linkTemplate string func getLinkXml(link *Link) string { var linkAction string if !link.isExternal() { linkAction = "ppaction://hlinksldjump" } return fmt.Sprintf( linkTemplate, link.Index, link.Tooltip, link.Id, linkAction, link.Tooltip, link.Left, link.Top, link.Width, link.Height, ) }