diff --git a/d2cli/main.go b/d2cli/main.go index 9b7339f8a..d2998e0b5 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -364,8 +364,9 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende if user, err := user.Current(); err == nil { username = user.Username } - description := "Presentation auto-generated by D2 - https://d2lang.com/" + description := "Presentation generated with D2 - https://d2lang.com/" rootName := getFileName(outputPath) + // version must be only numbers to avoid issues with PowerPoint p := pptx.NewPresentation(rootName, description, rootName, username, version.OnlyNumbers()) err := renderPPTX(ctx, ms, p, plugin, renderOpts, outputPath, page, diagram, nil) if err != nil { @@ -838,9 +839,7 @@ func renameExt(fp string, newExt string) string { func getFileName(path string) string { ext := filepath.Ext(path) - trimmedPath := strings.TrimSuffix(path, ext) - splitPath := strings.Split(trimmedPath, "/") - return splitPath[len(splitPath)-1] + return strings.TrimSuffix(filepath.Base(path), ext) } // TODO: remove after removing slog diff --git a/lib/pptx/pptx.go b/lib/pptx/pptx.go index 1d0c0f44c..08a23d188 100644 --- a/lib/pptx/pptx.go +++ b/lib/pptx/pptx.go @@ -9,7 +9,19 @@ import ( "time" ) +// 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). // Office Open XML (OOXML) http://officeopenxml.com/prPresentation.php +// 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) //go:embed template.pptx var pptx_template []byte @@ -38,26 +50,15 @@ func addFile(zipFile *zip.Writer, filePath, content string) error { 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) - const RELS_SLIDE_XML = `` -func getRelsSlideXml(imageId string) string { - return fmt.Sprintf(RELS_SLIDE_XML, imageId, imageId) +func getRelsSlideXml(imageID string) string { + return fmt.Sprintf(RELS_SLIDE_XML, imageID, imageID) } const SLIDE_XML = `%s` -func getSlideXml(boardPath []string, imageId string, top, left, width, height int) string { +func getSlideXml(boardPath []string, imageID string, top, left, width, height int) string { var slideTitle string boardName := boardPath[len(boardPath)-1] prefixPath := boardPath[:len(boardPath)-1] @@ -69,7 +70,7 @@ func getSlideXml(boardPath []string, imageId string, top, left, width, height in } slideDescription := strings.Join(boardPath, " / ") top += HEADER_HEIGHT - return fmt.Sprintf(SLIDE_XML, slideDescription, slideDescription, imageId, left, top, width, height, slideDescription, HEADER_HEIGHT, slideTitle) + return fmt.Sprintf(SLIDE_XML, slideDescription, slideDescription, imageID, left, top, width, height, slideDescription, HEADER_HEIGHT, slideTitle) } func getPresentationXmlRels(slideFileNames []string) string { diff --git a/lib/pptx/presentation.go b/lib/pptx/presentation.go index a79e6253d..fea845fd5 100644 --- a/lib/pptx/presentation.go +++ b/lib/pptx/presentation.go @@ -23,7 +23,9 @@ type Presentation struct { Description string Subject string Creator string - D2Version string + // D2Version can't have letters, only numbers (`[0-9]`) and `.` + // Otherwise, it may fail to open in PowerPoint + D2Version string Slides []*Slide } @@ -60,16 +62,37 @@ func (p *Presentation) AddSlide(pngContent []byte, boardPath []string) error { // compute the size and position to fit the slide // if the image is wider than taller and its aspect ratio is, at least, the same as the available image space aspect ratio // then, set the image width to the available space and compute the height + // ┌──────────────────────────────────────────────────┐ ─┬─ + // │ HEADER │ │ + // ├──┬────────────────────────────────────────────┬──┤ │ ─┬─ + // │ │ │ │ │ │ + // │ │ │ │ SLIDE │ + // │ │ │ │ HEIGHT │ + // │ │ │ │ │ IMAGE + // │ │ │ │ │ HEIGHT + // │ │ │ │ │ │ + // │ │ │ │ │ │ + // │ │ │ │ │ │ + // │ │ │ │ │ │ + // └──┴────────────────────────────────────────────┴──┘ ─┴─ ─┴─ + // ├────────────────────SLIDE WIDTH───────────────────┤ + // ├─────────────────IMAGE WIDTH────────────────┤ if srcWidth/srcHeight >= IMAGE_ASPECT_RATIO { + // here, the image aspect ratio is, at least, equal to the slide aspect ratio + // so, it makes sense to expand the image horizontally to use as much as space as possible width = SLIDE_WIDTH height = int(float64(width) * (srcHeight / srcWidth)) + // first, try to make the image as wide as the slide + // but, if this results in a tall image, use only the + // image adjusted width to avoid overlapping with the header if height > IMAGE_HEIGHT { - // this would overflow with the title, so we need to adjust to use only IMAGE_WIDTH width = IMAGE_WIDTH height = int(float64(width) * (srcHeight / srcWidth)) } } else { - // otherwise, this image could overflow the slide height/header + // here, the aspect ratio could be 4x3, in which the image is still wider than taller, + // but expanding horizontally would result in an overflow + // so, we expand to make it fit the available vertical space height = IMAGE_HEIGHT width = int(float64(height) * (srcWidth / srcHeight)) } @@ -106,11 +129,11 @@ func (p *Presentation) SaveTo(filePath string) error { var slideFileNames []string for i, slide := range p.Slides { - imageId := fmt.Sprintf("slide%dImage", i+1) + imageID := fmt.Sprintf("slide%dImage", i+1) slideFileName := fmt.Sprintf("slide%d", i+1) slideFileNames = append(slideFileNames, slideFileName) - imageWriter, err := zipWriter.Create(fmt.Sprintf("ppt/media/%s.png", imageId)) + imageWriter, err := zipWriter.Create(fmt.Sprintf("ppt/media/%s.png", imageID)) if err != nil { return err } @@ -119,7 +142,7 @@ func (p *Presentation) SaveTo(filePath string) error { return err } - err = addFile(zipWriter, fmt.Sprintf("ppt/slides/_rels/%s.xml.rels", slideFileName), getRelsSlideXml(imageId)) + err = addFile(zipWriter, fmt.Sprintf("ppt/slides/_rels/%s.xml.rels", slideFileName), getRelsSlideXml(imageID)) if err != nil { return err } @@ -127,7 +150,7 @@ func (p *Presentation) SaveTo(filePath string) error { err = addFile( zipWriter, fmt.Sprintf("ppt/slides/%s.xml", slideFileName), - getSlideXml(slide.BoardPath, imageId, slide.ImageTop, slide.ImageLeft, slide.ImageWidth, slide.ImageHeight), + getSlideXml(slide.BoardPath, imageID, slide.ImageTop, slide.ImageLeft, slide.ImageWidth, slide.ImageHeight), ) if err != nil { return err