add links to pptx
This commit is contained in:
parent
ff7438fc2a
commit
da14037466
5 changed files with 214 additions and 71 deletions
|
|
@ -21,6 +21,7 @@ import (
|
|||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2lib"
|
||||
"oss.terrastruct.com/d2/d2parser"
|
||||
"oss.terrastruct.com/d2/d2plugin"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2animate"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||
|
|
@ -32,7 +33,6 @@ import (
|
|||
"oss.terrastruct.com/d2/lib/background"
|
||||
"oss.terrastruct.com/d2/lib/imgbundler"
|
||||
ctxlog "oss.terrastruct.com/d2/lib/log"
|
||||
"oss.terrastruct.com/d2/lib/pdf"
|
||||
pdflib "oss.terrastruct.com/d2/lib/pdf"
|
||||
"oss.terrastruct.com/d2/lib/png"
|
||||
"oss.terrastruct.com/d2/lib/pptx"
|
||||
|
|
@ -189,7 +189,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
outputPath = ms.AbsPath(outputPath)
|
||||
if *animateIntervalFlag > 0 {
|
||||
// Not checking for extension == "svg", because users may want to write SVG data to a non-svg-extension file
|
||||
if filepath.Ext(outputPath) == ".png" || filepath.Ext(outputPath) == ".pdf" {
|
||||
if filepath.Ext(outputPath) == ".png" || filepath.Ext(outputPath) == ".pdf" || filepath.Ext(outputPath) == ".pptx" {
|
||||
return xmain.UsageErrorf("-animate-interval can only be used when exporting to SVG.\nYou provided: %s", filepath.Ext(outputPath))
|
||||
}
|
||||
}
|
||||
|
|
@ -351,7 +351,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
|
|||
|
||||
switch filepath.Ext(outputPath) {
|
||||
case ".pdf":
|
||||
pageMap := pdf.BuildPDFPageMap(diagram, nil, nil)
|
||||
pageMap := buildBoardIdToIndex(diagram, nil, nil)
|
||||
pdf, err := renderPDF(ctx, ms, plugin, renderOpts, outputPath, page, ruler, diagram, nil, nil, pageMap)
|
||||
if err != nil {
|
||||
return pdf, false, err
|
||||
|
|
@ -367,7 +367,9 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
|
|||
description := "Presentation auto-generated by D2 - https://d2lang.com/"
|
||||
rootName := getFileName(outputPath)
|
||||
p := pptx.NewPresentation(rootName, description, rootName, username, version.OnlyNumbers())
|
||||
err := renderPPTX(ctx, ms, p, plugin, renderOpts, outputPath, page, diagram, nil)
|
||||
|
||||
boardIdToIndex := buildBoardIdToIndex(diagram, nil, nil)
|
||||
err := renderPPTX(ctx, ms, p, plugin, renderOpts, ruler, outputPath, page, diagram, nil, boardIdToIndex)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
@ -756,7 +758,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
|
|||
return svg, nil
|
||||
}
|
||||
|
||||
func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []string) error {
|
||||
func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []string, boardIdToIndex map[string]int) error {
|
||||
var currBoardPath []string
|
||||
// Root board doesn't have a name, so we use the output filename
|
||||
if diagram.Name == "" {
|
||||
|
|
@ -792,31 +794,73 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
|
|||
return bundleErr
|
||||
}
|
||||
|
||||
svg = appendix.Append(diagram, ruler, svg)
|
||||
|
||||
// png.ConvertSVG scales the image by 2x
|
||||
pngScale := 2.
|
||||
pngImg, err := png.ConvertSVG(ms, page, svg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = presentation.AddSlide(pngImg, currBoardPath)
|
||||
slide, err := presentation.AddSlide(pngImg, diagram, currBoardPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
viewboxSlice := appendix.FindViewboxSlice(svg)
|
||||
viewboxX, err := strconv.ParseFloat(viewboxSlice[0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
viewboxY, err := strconv.ParseFloat(viewboxSlice[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Draw links
|
||||
for _, shape := range diagram.Shapes {
|
||||
if shape.Link == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
linkX := pngScale * (float64(shape.Pos.X) - viewboxX - float64(shape.StrokeWidth))
|
||||
linkY := pngScale * (float64(shape.Pos.Y) - viewboxY - float64(shape.StrokeWidth))
|
||||
linkWidth := pngScale * (float64(shape.Width) + float64(shape.StrokeWidth*2))
|
||||
linkHeight := pngScale * (float64(shape.Height) + float64(shape.StrokeWidth*2))
|
||||
link := &pptx.Link{
|
||||
Left: int(linkX),
|
||||
Top: int(linkY),
|
||||
Width: int(linkWidth),
|
||||
Height: int(linkHeight),
|
||||
Tooltip: shape.Link,
|
||||
}
|
||||
slide.AddLink(link)
|
||||
key, err := d2parser.ParseKey(shape.Link)
|
||||
if err != nil || key.Path[0].Unbox().ScalarString() != "root" {
|
||||
// External link
|
||||
link.ExternalUrl = shape.Link
|
||||
} else if pageNum, ok := boardIdToIndex[shape.Link]; ok {
|
||||
// Internal link
|
||||
link.SlideIndex = pageNum + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, dl := range diagram.Layers {
|
||||
err := renderPPTX(ctx, ms, presentation, plugin, opts, "", page, dl, currBoardPath)
|
||||
err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, currBoardPath, boardIdToIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, dl := range diagram.Scenarios {
|
||||
err := renderPPTX(ctx, ms, presentation, plugin, opts, "", page, dl, currBoardPath)
|
||||
err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, currBoardPath, boardIdToIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, dl := range diagram.Steps {
|
||||
err := renderPPTX(ctx, ms, presentation, plugin, opts, "", page, dl, currBoardPath)
|
||||
err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, currBoardPath, boardIdToIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -914,3 +958,28 @@ func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold string)
|
|||
|
||||
return d2fonts.AddFontFamily("custom", regularTTF, italicTTF, boldTTF)
|
||||
}
|
||||
|
||||
// buildBoardIdToIndex returns a map from board path to page int
|
||||
// To map correctly, it must follow the same traversal of PDF building
|
||||
func buildBoardIdToIndex(diagram *d2target.Diagram, dictionary map[string]int, path []string) map[string]int {
|
||||
newPath := append(path, diagram.Name)
|
||||
if dictionary == nil {
|
||||
dictionary = map[string]int{}
|
||||
newPath[0] = "root"
|
||||
}
|
||||
|
||||
key := strings.Join(newPath, ".")
|
||||
dictionary[key] = len(dictionary)
|
||||
|
||||
for _, dl := range diagram.Layers {
|
||||
buildBoardIdToIndex(dl, dictionary, append(newPath, "layers"))
|
||||
}
|
||||
for _, dl := range diagram.Scenarios {
|
||||
buildBoardIdToIndex(dl, dictionary, append(newPath, "scenarios"))
|
||||
}
|
||||
for _, dl := range diagram.Steps {
|
||||
buildBoardIdToIndex(dl, dictionary, append(newPath, "steps"))
|
||||
}
|
||||
|
||||
return dictionary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,28 +164,3 @@ func (g *GoFPDF) AddPDFPage(png []byte, boardPath []string, themeID int64, fill
|
|||
func (g *GoFPDF) Export(outputPath string) error {
|
||||
return g.pdf.OutputFileAndClose(outputPath)
|
||||
}
|
||||
|
||||
// BuildPDFPageMap returns a map from board path to page int
|
||||
// To map correctly, it must follow the same traversal of PDF building
|
||||
func BuildPDFPageMap(diagram *d2target.Diagram, dictionary map[string]int, path []string) map[string]int {
|
||||
newPath := append(path, diagram.Name)
|
||||
if dictionary == nil {
|
||||
dictionary = map[string]int{}
|
||||
newPath[0] = "root"
|
||||
}
|
||||
|
||||
key := strings.Join(newPath, ".")
|
||||
dictionary[key] = len(dictionary)
|
||||
|
||||
for _, dl := range diagram.Layers {
|
||||
BuildPDFPageMap(dl, dictionary, append(newPath, "layers"))
|
||||
}
|
||||
for _, dl := range diagram.Scenarios {
|
||||
BuildPDFPageMap(dl, dictionary, append(newPath, "scenarios"))
|
||||
}
|
||||
for _, dl := range diagram.Steps {
|
||||
BuildPDFPageMap(dl, dictionary, append(newPath, "steps"))
|
||||
}
|
||||
|
||||
return dictionary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ var genPNGScript string
|
|||
|
||||
const pngPrefix = "data:image/png;base64,"
|
||||
|
||||
// ConvertSVG converts the given SVG into a PNG.
|
||||
// Note that the resulting PNG has 2x the size (width and height) of the original SVG (see generate_png.js)
|
||||
func ConvertSVG(ms *xmain.State, page playwright.Page, svg []byte) ([]byte, error) {
|
||||
cancel := background.Repeat(func() {
|
||||
ms.Log.Info.Printf("converting to PNG...")
|
||||
|
|
|
|||
|
|
@ -49,27 +49,69 @@ const IMAGE_HEIGHT = SLIDE_HEIGHT - HEADER_HEIGHT
|
|||
const IMAGE_WIDTH = 8_446_273
|
||||
const IMAGE_ASPECT_RATIO = float64(IMAGE_WIDTH) / float64(IMAGE_HEIGHT)
|
||||
|
||||
const RELS_SLIDE_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout7.xml" /><Relationship Id="%s" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/%s.png" /></Relationships>`
|
||||
func getRelsSlideXml(slide *Slide) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout7.xml" />`)
|
||||
builder.WriteString(
|
||||
fmt.Sprintf(
|
||||
`<Relationship Id="%s" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/%s.png" />`,
|
||||
slide.ImageId,
|
||||
slide.ImageId,
|
||||
),
|
||||
)
|
||||
for _, link := range slide.Links {
|
||||
if link.isExternal() {
|
||||
builder.WriteString(
|
||||
fmt.Sprintf(
|
||||
`<Relationship Id="%s" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="%s" TargetMode="External" />`,
|
||||
link.Id,
|
||||
link.ExternalUrl,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
builder.WriteString(
|
||||
fmt.Sprintf(
|
||||
`<Relationship Id="%s" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slide%d.xml" />`,
|
||||
link.Id,
|
||||
link.SlideIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func getRelsSlideXml(imageId string) string {
|
||||
return fmt.Sprintf(RELS_SLIDE_XML, imageId, imageId)
|
||||
builder.WriteString(`</Relationships>`)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
const SLIDE_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name="" /><p:cNvGrpSpPr /><p:nvPr /></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0" /><a:ext cx="0" cy="0" /><a:chOff x="0" y="0" /><a:chExt cx="0" cy="0" /></a:xfrm></p:grpSpPr><p:pic><p:nvPicPr><p:cNvPr id="2" name="%s" descr="%s" /><p:cNvPicPr><a:picLocks noChangeAspect="1" /></p:cNvPicPr><p:nvPr /></p:nvPicPr><p:blipFill><a:blip r:embed="%s" /><a:stretch><a:fillRect /></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="%d" y="%d" /><a:ext cx="%d" cy="%d" /></a:xfrm><a:prstGeom prst="rect"><a:avLst /></a:prstGeom></p:spPr></p:pic><p:sp><p:nvSpPr><p:cNvPr id="95" name="%s" /><p:cNvSpPr txBox="1" /><p:nvPr /></p:nvSpPr><p:spPr><a:xfrm><a:off x="4001" y="6239" /><a:ext cx="9135998" cy="%d" /></a:xfrm><a:prstGeom prst="rect"><a:avLst /></a:prstGeom><a:ln w="12700"><a:miter lim="400000" /></a:ln><a:extLst><a:ext uri="{C572A759-6A51-4108-AA02-DFA0A04FC94B}"><ma14:wrappingTextBoxFlag xmlns:ma14="http://schemas.microsoft.com/office/mac/drawingml/2011/main" xmlns="" val="1" /></a:ext></a:extLst></p:spPr><p:txBody><a:bodyPr lIns="45719" rIns="45719"><a:spAutoFit /></a:bodyPr><a:lstStyle><a:lvl1pPr><a:defRPr sz="2400" /></a:lvl1pPr></a:lstStyle><a:p>%s</a:p></p:txBody></p:sp></p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping /></p:clrMapOvr></p:sld>`
|
||||
|
||||
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]
|
||||
func getSlideXml(slide *Slide) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name="" /><p:cNvGrpSpPr /><p:nvPr /></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0" /><a:ext cx="0" cy="0" /><a:chOff x="0" y="0" /><a:chExt cx="0" cy="0" /></a:xfrm></p:grpSpPr><p:pic><p:nvPicPr>`)
|
||||
slideDescription := strings.Join(slide.BoardPath, " / ")
|
||||
builder.WriteString(fmt.Sprintf(`<p:cNvPr id="2" name="%s" descr="%s" />`, slideDescription, slideDescription))
|
||||
builder.WriteString(`<p:cNvPicPr><a:picLocks noChangeAspect="1" /></p:cNvPicPr><p:nvPr /></p:nvPicPr><p:blipFill>`)
|
||||
builder.WriteString(fmt.Sprintf(`<a:blip r:embed="%s" />`, slide.ImageId))
|
||||
builder.WriteString(`<a:stretch><a:fillRect /></a:stretch></p:blipFill><p:spPr><a:xfrm>`)
|
||||
builder.WriteString(fmt.Sprintf(`<a:off x="%d" y="%d" />`, slide.ImageLeft, slide.ImageTop))
|
||||
builder.WriteString(fmt.Sprintf(`<a:ext cx="%d" cy="%d" />`, slide.ImageWidth, slide.ImageHeight))
|
||||
builder.WriteString(`</a:xfrm><a:prstGeom prst="rect"><a:avLst /></a:prstGeom></p:spPr></p:pic><p:sp><p:nvSpPr>`)
|
||||
builder.WriteString(fmt.Sprintf(`<p:cNvPr id="95" name="%s" />`, slideDescription))
|
||||
builder.WriteString(`<p:cNvSpPr txBox="1" /><p:nvPr /></p:nvSpPr><p:spPr><a:xfrm>`)
|
||||
builder.WriteString(`<a:off x="4001" y="6239" />`)
|
||||
builder.WriteString(fmt.Sprintf(`<a:ext cx="9135998" cy="%d" />`, HEADER_HEIGHT))
|
||||
builder.WriteString(`</a:xfrm><a:prstGeom prst="rect"><a:avLst /></a:prstGeom><a:ln w="12700"><a:miter lim="400000" /></a:ln><a:extLst><a:ext uri="{C572A759-6A51-4108-AA02-DFA0A04FC94B}"><ma14:wrappingTextBoxFlag xmlns:ma14="http://schemas.microsoft.com/office/mac/drawingml/2011/main" xmlns="" val="1" /></a:ext></a:extLst></p:spPr><p:txBody><a:bodyPr lIns="45719" rIns="45719"><a:spAutoFit /></a:bodyPr><a:lstStyle><a:lvl1pPr><a:defRPr sz="2400" /></a:lvl1pPr></a:lstStyle>`)
|
||||
boardName := slide.BoardPath[len(slide.BoardPath)-1]
|
||||
prefixPath := slide.BoardPath[:len(slide.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)
|
||||
builder.WriteString(fmt.Sprintf(`<a:p><a:r><a:t>%s</a:t></a:r><a:r><a:rPr b="1" /><a:t>%s</a:t></a:r></a:p></p:txBody></p:sp>`, prefix, boardName))
|
||||
} else {
|
||||
slideTitle = fmt.Sprintf(`<a:r><a:rPr b="1" /><a:t>%s</a:t></a:r>`, boardName)
|
||||
builder.WriteString(fmt.Sprintf(`<a:p><a:r><a:rPr b="1" /><a:t>%s</a:t></a:r></a:p></p:txBody></p:sp>`, boardName))
|
||||
}
|
||||
slideDescription := strings.Join(boardPath, " / ")
|
||||
top += HEADER_HEIGHT
|
||||
return fmt.Sprintf(SLIDE_XML, slideDescription, slideDescription, imageId, left, top, width, height, slideDescription, HEADER_HEIGHT, slideTitle)
|
||||
for _, link := range slide.Links {
|
||||
builder.WriteString(getLinkXml(link))
|
||||
}
|
||||
builder.WriteString(`</p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping /></p:clrMapOvr></p:sld>`)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func getPresentationXmlRels(slideFileNames []string) string {
|
||||
|
|
@ -113,7 +155,7 @@ func getPresentationXml(slideFileNames []string) string {
|
|||
builder.WriteString("</p:sldIdLst>")
|
||||
|
||||
builder.WriteString(fmt.Sprintf(
|
||||
`<p:sldSz cx="%d" cy="%d" type="screen4x3" /><p:notesSz cx="6858000" cy="9144000" /><p:defaultTextStyle><a:defPPr><a:defRPr lang="en-US" /></a:defPPr><a:lvl1pPr marL="0" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl1pPr><a:lvl2pPr marL="457200" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl2pPr><a:lvl3pPr marL="914400" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl3pPr><a:lvl4pPr marL="1371600" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl4pPr><a:lvl5pPr marL="1828800" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl5pPr><a:lvl6pPr marL="2286000" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl6pPr><a:lvl7pPr marL="2743200" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl7pPr><a:lvl8pPr marL="3200400" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl8pPr><a:lvl9pPr marL="3657600" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl9pPr></p:defaultTextStyle><p:extLst><p:ext uri="{EFAFB233-063F-42B5-8137-9DF3F51BA10A}"><p15:sldGuideLst xmlns:p15="http://schemas.microsoft.com/office/powerpoint/2012/main"><p15:guide id="1" orient="horz" pos="2160"><p15:clr><a:srgbClr val="A4A3A4" /></p15:clr></p15:guide><p15:guide id="2" pos="2880"><p15:clr><a:srgbClr val="A4A3A4" /></p15:clr></p15:guide></p15:sldGuideLst></p:ext></p:extLst></p:presentation>`,
|
||||
`<p:sldSz cx="%d" cy="%d" /><p:notesSz cx="6858000" cy="9144000" /><p:defaultTextStyle><a:defPPr><a:defRPr lang="en-US" /></a:defPPr><a:lvl1pPr marL="0" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl1pPr><a:lvl2pPr marL="457200" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl2pPr><a:lvl3pPr marL="914400" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl3pPr><a:lvl4pPr marL="1371600" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl4pPr><a:lvl5pPr marL="1828800" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl5pPr><a:lvl6pPr marL="2286000" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl6pPr><a:lvl7pPr marL="2743200" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl7pPr><a:lvl8pPr marL="3200400" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl8pPr><a:lvl9pPr marL="3657600" algn="l" defTabSz="457200" rtl="0" eaLnBrk="1" latinLnBrk="0" hangingPunct="1"><a:defRPr sz="1800" kern="1200"><a:solidFill><a:schemeClr val="tx1" /></a:solidFill><a:latin typeface="+mn-lt" /><a:ea typeface="+mn-ea" /><a:cs typeface="+mn-cs" /></a:defRPr></a:lvl9pPr></p:defaultTextStyle><p:extLst><p:ext uri="{EFAFB233-063F-42B5-8137-9DF3F51BA10A}"><p15:sldGuideLst xmlns:p15="http://schemas.microsoft.com/office/powerpoint/2012/main"><p15:guide id="1" orient="horz" pos="2160"><p15:clr><a:srgbClr val="A4A3A4" /></p15:clr></p15:guide><p15:guide id="2" pos="2880"><p15:clr><a:srgbClr val="A4A3A4" /></p15:clr></p15:guide></p15:sldGuideLst></p:ext></p:extLst></p:presentation>`,
|
||||
SLIDE_WIDTH,
|
||||
SLIDE_HEIGHT,
|
||||
))
|
||||
|
|
@ -198,3 +240,26 @@ func getAppXml(slides []*Slide, d2version string) string {
|
|||
builder.WriteString(`</Properties>`)
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func getLinkXml(link *Link) string {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString("<p:sp><p:nvSpPr>")
|
||||
builder.WriteString(fmt.Sprintf(`<p:cNvPr id="%d" name="%s">`, link.Index, link.Tooltip))
|
||||
var linkAction string
|
||||
if !link.isExternal() {
|
||||
linkAction = "ppaction://hlinksldjump"
|
||||
}
|
||||
builder.WriteString(
|
||||
fmt.Sprintf(`<a:hlinkClick r:id="%s" action="%s" tooltip="%s" history="1" />`,
|
||||
link.Id,
|
||||
linkAction,
|
||||
link.Tooltip,
|
||||
),
|
||||
)
|
||||
builder.WriteString("</p:cNvPr><p:cNvSpPr /><p:nvPr /></p:nvSpPr><p:spPr><a:xfrm>")
|
||||
builder.WriteString(fmt.Sprintf(`<a:off x="%d" y="%d" />`, link.Left, link.Top))
|
||||
builder.WriteString(fmt.Sprintf(`<a:ext cx="%d" cy="%d" />`, link.Width, link.Height))
|
||||
builder.WriteString(`</a:xfrm><a:prstGeom prst="rect"><a:avLst /></a:prstGeom><a:noFill /><a:ln><a:noFill /></a:ln></p:spPr></p:sp>`)
|
||||
return builder.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import (
|
|||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
)
|
||||
|
||||
type Presentation struct {
|
||||
|
|
@ -30,11 +32,40 @@ type Presentation struct {
|
|||
|
||||
type Slide struct {
|
||||
BoardPath []string
|
||||
Links []*Link
|
||||
Image []byte
|
||||
ImageId string
|
||||
ImageWidth int
|
||||
ImageHeight int
|
||||
ImageTop int
|
||||
ImageLeft int
|
||||
ImageScaleFactor float64
|
||||
}
|
||||
|
||||
func (s *Slide) AddLink(link *Link) {
|
||||
link.Index = len(s.Links)
|
||||
s.Links = append(s.Links, link)
|
||||
link.Id = fmt.Sprintf("link%d", len(s.Links))
|
||||
link.Height *= int(s.ImageScaleFactor)
|
||||
link.Width *= int(s.ImageScaleFactor)
|
||||
link.Top = s.ImageTop + int(float64(link.Top)*s.ImageScaleFactor)
|
||||
link.Left = s.ImageLeft + int(float64(link.Left)*s.ImageScaleFactor)
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Id string
|
||||
Index int
|
||||
Top int
|
||||
Left int
|
||||
Width int
|
||||
Height int
|
||||
SlideIndex int
|
||||
ExternalUrl string
|
||||
Tooltip string
|
||||
}
|
||||
|
||||
func (l *Link) isExternal() bool {
|
||||
return l.ExternalUrl != ""
|
||||
}
|
||||
|
||||
func NewPresentation(title, description, subject, creator, d2Version string) *Presentation {
|
||||
|
|
@ -47,16 +78,16 @@ func NewPresentation(title, description, subject, creator, d2Version string) *Pr
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Presentation) AddSlide(pngContent []byte, boardPath []string) error {
|
||||
func (p *Presentation) AddSlide(pngContent []byte, diagram *d2target.Diagram, boardPath []string) (*Slide, error) {
|
||||
src, err := png.Decode(bytes.NewReader(pngContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding PNG image: %v", err)
|
||||
return nil, fmt.Errorf("error decoding PNG image: %v", err)
|
||||
}
|
||||
|
||||
var width, height int
|
||||
srcSize := src.Bounds().Size()
|
||||
srcWidth, srcHeight := float64(srcSize.X), float64(srcSize.Y)
|
||||
|
||||
var width, height int
|
||||
|
||||
// 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
|
||||
|
|
@ -73,22 +104,24 @@ func (p *Presentation) AddSlide(pngContent []byte, boardPath []string) error {
|
|||
height = IMAGE_HEIGHT
|
||||
width = int(float64(height) * (srcWidth / srcHeight))
|
||||
}
|
||||
top := (IMAGE_HEIGHT - height) / 2
|
||||
top := HEADER_HEIGHT + ((IMAGE_HEIGHT - height) / 2)
|
||||
left := (SLIDE_WIDTH - width) / 2
|
||||
|
||||
slide := &Slide{
|
||||
BoardPath: make([]string, len(boardPath)),
|
||||
ImageId: fmt.Sprintf("slide%dImage", len(p.Slides)+1),
|
||||
Image: pngContent,
|
||||
ImageWidth: width,
|
||||
ImageHeight: height,
|
||||
ImageTop: top,
|
||||
ImageLeft: left,
|
||||
ImageScaleFactor: float64(width) / srcWidth,
|
||||
}
|
||||
// it must copy the board path to avoid slice reference issues
|
||||
copy(slide.BoardPath, boardPath)
|
||||
|
||||
p.Slides = append(p.Slides, slide)
|
||||
return nil
|
||||
return slide, nil
|
||||
}
|
||||
|
||||
func (p *Presentation) SaveTo(filePath string) error {
|
||||
|
|
@ -106,11 +139,10 @@ func (p *Presentation) SaveTo(filePath string) error {
|
|||
|
||||
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 := zipWriter.Create(fmt.Sprintf("ppt/media/%s.png", imageId))
|
||||
imageWriter, err := zipWriter.Create(fmt.Sprintf("ppt/media/%s.png", slide.ImageId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -119,7 +151,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(slide))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -127,7 +159,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),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
Loading…
Reference in a new issue