unioffice/presentation/presentation.go

909 lines
32 KiB
Go
Raw Normal View History

// Copyright 2017 FoxyUtils ehf. All rights reserved.
2017-08-28 20:56:18 -05:00
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased on https://unidoc.io.
2017-08-28 20:56:18 -05:00
package presentation
import (
"archive/zip"
"bytes"
"encoding/xml"
"errors"
"flag"
2017-08-28 20:56:18 -05:00
"fmt"
"image"
"image/jpeg"
2017-08-28 20:56:18 -05:00
"io"
"log"
2017-08-28 20:56:18 -05:00
"os"
"path"
2017-08-28 20:56:18 -05:00
"github.com/unidoc/unioffice"
"github.com/unidoc/unioffice/color"
"github.com/unidoc/unioffice/common"
"github.com/unidoc/unioffice/common/license"
"github.com/unidoc/unioffice/common/tempstorage"
"github.com/unidoc/unioffice/measurement"
"github.com/unidoc/unioffice/schema/soo/dml"
crt "github.com/unidoc/unioffice/schema/soo/dml/chart"
"github.com/unidoc/unioffice/schema/soo/ofc/sharedTypes"
"github.com/unidoc/unioffice/schema/soo/pkg/relationships"
"github.com/unidoc/unioffice/schema/soo/pml"
"github.com/unidoc/unioffice/zippkg"
2017-08-28 20:56:18 -05:00
)
// Presentation is the a presentation base document.
type Presentation struct {
common.DocBase
x *pml.Presentation
prels common.Relationships
slides []*pml.Sld
slideRels []common.Relationships
masters []*pml.SldMaster
masterRels []common.Relationships
layouts []*pml.SldLayout
layoutRels []common.Relationships
themes []*dml.Theme
themeRels []common.Relationships
tableStyles common.TableStyles
presentationProperties PresentationProperties
viewProperties ViewProperties
hyperlinks []*dml.CT_Hyperlink
charts []*crt.ChartSpace
handoutMaster []*pml.HandoutMaster
notesMaster []*pml.NotesMaster
customXML []*unioffice.XSDAny
imagesMap map[string]string // mapping input images paths to output ones
2017-08-28 20:56:18 -05:00
}
func newEmpty() *Presentation {
2017-08-28 20:56:18 -05:00
p := &Presentation{x: pml.NewPresentation()}
p.x.SldIdLst = pml.NewCT_SlideIdList()
p.x.ConformanceAttr = sharedTypes.ST_ConformanceClassTransitional
p.AppProperties = common.NewAppProperties()
p.CoreProperties = common.NewCoreProperties()
p.tableStyles = common.NewTableStyles()
2017-08-28 20:56:18 -05:00
p.ContentTypes = common.NewContentTypes()
p.Rels = common.NewRelationships()
p.prels = common.NewRelationships()
p.presentationProperties = NewPresentationProperties()
p.viewProperties = NewViewProperties()
p.imagesMap = map[string]string{}
return p
}
2017-08-28 20:56:18 -05:00
// New initializes and reurns a new presentation
func New() *Presentation {
p := newEmpty()
2017-08-28 20:56:18 -05:00
p.ContentTypes.AddOverride("/ppt/presentation.xml", "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml")
2017-08-28 20:56:18 -05:00
p.Rels.AddRelationship("docProps/core.xml", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties")
p.Rels.AddRelationship("docProps/app.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties")
p.Rels.AddRelationship("ppt/presentation.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument")
p.Rels.AddRelationship("ppt/presProps.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps")
p.Rels.AddRelationship("ppt/viewProps.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps")
p.Rels.AddRelationship("ppt/tableStyles.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles")
2017-08-28 20:56:18 -05:00
p.x.SldMasterIdLst = pml.NewCT_SlideMasterIdList()
m := pml.NewSldMaster()
2017-09-23 08:39:08 -05:00
m.ClrMap.Bg1Attr = dml.ST_ColorSchemeIndexLt1
m.ClrMap.Bg2Attr = dml.ST_ColorSchemeIndexLt2
m.ClrMap.Tx1Attr = dml.ST_ColorSchemeIndexDk1
m.ClrMap.Tx2Attr = dml.ST_ColorSchemeIndexDk2
m.ClrMap.Accent1Attr = dml.ST_ColorSchemeIndexAccent1
m.ClrMap.Accent2Attr = dml.ST_ColorSchemeIndexAccent2
m.ClrMap.Accent3Attr = dml.ST_ColorSchemeIndexAccent3
m.ClrMap.Accent4Attr = dml.ST_ColorSchemeIndexAccent4
m.ClrMap.Accent5Attr = dml.ST_ColorSchemeIndexAccent5
m.ClrMap.Accent6Attr = dml.ST_ColorSchemeIndexAccent6
m.ClrMap.HlinkAttr = dml.ST_ColorSchemeIndexHlink
m.ClrMap.FolHlinkAttr = dml.ST_ColorSchemeIndexFolHlink
2017-08-28 20:56:18 -05:00
p.masters = append(p.masters, m)
smFn := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideMasterType, 1)
p.ContentTypes.AddOverride(smFn, unioffice.SlideMasterContentType)
mrelID := p.prels.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.OfficeDocumentType,
1, unioffice.SlideMasterType)
2017-08-28 20:56:18 -05:00
smid := pml.NewCT_SlideMasterIdListEntry()
smid.IdAttr = unioffice.Uint32(2147483648)
2017-08-28 20:56:18 -05:00
smid.RIdAttr = mrelID.ID()
p.x.SldMasterIdLst.SldMasterId = append(p.x.SldMasterIdLst.SldMasterId, smid)
mrel := common.NewRelationships()
p.masterRels = append(p.masterRels, mrel)
ls := pml.NewSldLayout()
lrid := mrel.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.SlideMasterType, 1, unioffice.SlideLayoutType)
slfn := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideLayoutType, 1)
p.ContentTypes.AddOverride(slfn, unioffice.SlideLayoutContentType)
mrel.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.SlideMasterType, 1, unioffice.ThemeType)
2017-08-28 20:56:18 -05:00
p.layouts = append(p.layouts, ls)
m.SldLayoutIdLst = pml.NewCT_SlideLayoutIdList()
lid := pml.NewCT_SlideLayoutIdListEntry()
lid.IdAttr = unioffice.Uint32(2147483649)
2017-08-28 20:56:18 -05:00
lid.RIdAttr = lrid.ID()
m.SldLayoutIdLst.SldLayoutId = append(m.SldLayoutIdLst.SldLayoutId, lid)
lrel := common.NewRelationships()
p.layoutRels = append(p.layoutRels, lrel)
lrel.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.SlideType, 1, unioffice.SlideMasterType)
2017-08-28 20:56:18 -05:00
p.x.NotesSz.CxAttr = 6858000
p.x.NotesSz.CyAttr = 9144000
thm := dml.NewTheme()
thm.NameAttr = unioffice.String("unioffice Theme")
2017-08-28 20:56:18 -05:00
thm.ThemeElements.ClrScheme.NameAttr = "Office"
thm.ThemeElements.ClrScheme.Dk1.SysClr = dml.NewCT_SystemColor()
thm.ThemeElements.ClrScheme.Dk1.SysClr.LastClrAttr = unioffice.String("000000")
2017-08-28 20:56:18 -05:00
thm.ThemeElements.ClrScheme.Dk1.SysClr.ValAttr = dml.ST_SystemColorValWindowText
thm.ThemeElements.ClrScheme.Lt1.SysClr = dml.NewCT_SystemColor()
thm.ThemeElements.ClrScheme.Lt1.SysClr.LastClrAttr = unioffice.String("ffffff")
2017-08-28 20:56:18 -05:00
thm.ThemeElements.ClrScheme.Lt1.SysClr.ValAttr = dml.ST_SystemColorValWindow
thm.ThemeElements.ClrScheme.Dk2.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Dk2.SrgbClr.ValAttr = "44546a"
thm.ThemeElements.ClrScheme.Lt2.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Lt2.SrgbClr.ValAttr = "e7e7e6"
thm.ThemeElements.ClrScheme.Accent1.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Accent1.SrgbClr.ValAttr = "4472c4"
thm.ThemeElements.ClrScheme.Accent2.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Accent2.SrgbClr.ValAttr = "ed7d31"
thm.ThemeElements.ClrScheme.Accent3.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Accent3.SrgbClr.ValAttr = "a5a5a5"
thm.ThemeElements.ClrScheme.Accent4.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Accent4.SrgbClr.ValAttr = "ffc000"
thm.ThemeElements.ClrScheme.Accent5.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Accent5.SrgbClr.ValAttr = "5b9bd5"
thm.ThemeElements.ClrScheme.Accent6.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Accent6.SrgbClr.ValAttr = "70ad47"
thm.ThemeElements.ClrScheme.Hlink.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.Hlink.SrgbClr.ValAttr = "0563c1"
thm.ThemeElements.ClrScheme.FolHlink.SrgbClr = dml.NewCT_SRgbColor()
thm.ThemeElements.ClrScheme.FolHlink.SrgbClr.ValAttr = "954f72"
thm.ThemeElements.FontScheme.NameAttr = "Office"
thm.ThemeElements.FontScheme.MajorFont.Latin.TypefaceAttr = "Calibri Light"
thm.ThemeElements.FontScheme.MinorFont.Latin.TypefaceAttr = "Calibri"
thm.ThemeElements.FmtScheme.NameAttr = unioffice.String("Office")
2017-08-28 20:56:18 -05:00
// fills
fp := dml.NewEG_FillProperties()
thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties, fp)
fp.SolidFill = &dml.CT_SolidColorFillProperties{
SchemeClr: &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr},
}
// rot fill 0
fp = dml.NewEG_FillProperties()
thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties, fp)
// add it twice so OSX word doesn't choke
thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties, fp)
fp.GradFill = &dml.CT_GradientFillProperties{RotWithShapeAttr: unioffice.Bool(true),
2017-08-28 20:56:18 -05:00
GsLst: &dml.CT_GradientStopList{},
Lin: &dml.CT_LinearShadeProperties{}}
fp.GradFill.Lin.AngAttr = unioffice.Int32(5400000)
fp.GradFill.Lin.ScaledAttr = unioffice.Bool(false)
2017-08-28 20:56:18 -05:00
gs := dml.NewCT_GradientStop()
gs.PosAttr.ST_PositiveFixedPercentageDecimal = unioffice.Int32(0)
2017-08-28 20:56:18 -05:00
gs.SchemeClr = &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr}
fp.GradFill.GsLst.Gs = append(fp.GradFill.GsLst.Gs, gs)
gs = dml.NewCT_GradientStop()
gs.PosAttr.ST_PositiveFixedPercentageDecimal = unioffice.Int32(50000)
2017-08-28 20:56:18 -05:00
gs.SchemeClr = &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr}
fp.GradFill.GsLst.Gs = append(fp.GradFill.GsLst.Gs, gs)
thm.ThemeElements.FmtScheme.LnStyleLst = dml.NewCT_LineStyleList()
for i := 0; i < 3; i++ {
lp := dml.NewCT_LineProperties()
lp.WAttr = unioffice.Int32(int32(6350 * (i + 1)))
2017-08-28 20:56:18 -05:00
lp.CapAttr = dml.ST_LineCapFlat
lp.CmpdAttr = dml.ST_CompoundLineSng
lp.AlgnAttr = dml.ST_PenAlignmentCtr
thm.ThemeElements.FmtScheme.LnStyleLst.Ln = append(thm.ThemeElements.FmtScheme.LnStyleLst.Ln, lp)
}
thm.ThemeElements.FmtScheme.EffectStyleLst = dml.NewCT_EffectStyleList()
for i := 0; i < 3; i++ {
ef := dml.NewCT_EffectStyleItem()
ef.EffectLst = dml.NewCT_EffectList()
thm.ThemeElements.FmtScheme.EffectStyleLst.EffectStyle = append(thm.ThemeElements.FmtScheme.EffectStyleLst.EffectStyle, ef)
}
sf := dml.NewEG_FillProperties()
sf.SolidFill = &dml.CT_SolidColorFillProperties{
SchemeClr: &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr},
}
thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties,
sf)
thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties,
sf)
thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties,
fp)
p.themes = append(p.themes, thm)
themeFn := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.ThemeType, 1)
p.ContentTypes.AddOverride(themeFn, unioffice.ThemeContentType)
p.prels.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.OfficeDocumentType, 1, unioffice.ThemeType)
thmRel := common.NewRelationships()
p.themeRels = append(p.themeRels, thmRel)
2017-08-28 20:56:18 -05:00
return p
}
// X returns the inner wrapped XML type.
func (p *Presentation) X() *pml.Presentation {
return p.x
}
func (p *Presentation) nextSlideID() uint32 {
id := uint32(256)
for _, s := range p.x.SldIdLst.SldId {
if s.IdAttr >= id {
id = s.IdAttr + 1
}
}
return id
}
// AddSlide adds a new slide to the presentation.
2017-08-28 20:56:18 -05:00
func (p *Presentation) AddSlide() Slide {
sd := pml.NewCT_SlideIdListEntry()
sd.IdAttr = p.nextSlideID()
2017-08-28 20:56:18 -05:00
p.x.SldIdLst.SldId = append(p.x.SldIdLst.SldId, sd)
slide := pml.NewSld()
slide.CSld.SpTree.NvGrpSpPr.CNvPr.IdAttr = 1
slide.CSld.SpTree.GrpSpPr.Xfrm = dml.NewCT_GroupTransform2D()
slide.CSld.SpTree.GrpSpPr.Xfrm.Off = dml.NewCT_Point2D()
slide.CSld.SpTree.GrpSpPr.Xfrm.Off.XAttr.ST_CoordinateUnqualified = unioffice.Int64(0)
slide.CSld.SpTree.GrpSpPr.Xfrm.Off.YAttr.ST_CoordinateUnqualified = unioffice.Int64(0)
2017-08-28 20:56:18 -05:00
slide.CSld.SpTree.GrpSpPr.Xfrm.Ext = dml.NewCT_PositiveSize2D()
slide.CSld.SpTree.GrpSpPr.Xfrm.Ext.CxAttr = int64(0 * measurement.Point)
slide.CSld.SpTree.GrpSpPr.Xfrm.Ext.CyAttr = int64(0 * measurement.Point)
slide.CSld.SpTree.GrpSpPr.Xfrm.ChOff = slide.CSld.SpTree.GrpSpPr.Xfrm.Off
slide.CSld.SpTree.GrpSpPr.Xfrm.ChExt = slide.CSld.SpTree.GrpSpPr.Xfrm.Ext
p.slides = append(p.slides, slide)
srelID := p.prels.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.OfficeDocumentType,
len(p.slides), unioffice.SlideType)
2017-08-28 20:56:18 -05:00
sd.RIdAttr = srelID.ID()
slidefn := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideType, len(p.slides))
p.ContentTypes.AddOverride(slidefn, unioffice.SlideContentType)
2017-08-28 20:56:18 -05:00
srel := common.NewRelationships()
p.slideRels = append(p.slideRels, srel)
// TODO: make the slide layout configurable
srel.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.SlideType,
len(p.layouts), unioffice.SlideLayoutType)
2017-08-28 20:56:18 -05:00
2017-11-20 18:24:12 -06:00
return Slide{sd, slide, p}
2017-08-28 20:56:18 -05:00
}
// AddSlideWithLayout adds a new slide with content copied from a layout. Normally you should
// use AddDefaultSlideWithLayout as it will do some post processing similar to PowerPoint to
// clear place holder text, etc.
func (p *Presentation) AddSlideWithLayout(l SlideLayout) (Slide, error) {
sd := pml.NewCT_SlideIdListEntry()
sd.IdAttr = 256
for _, id := range p.x.SldIdLst.SldId {
if id.IdAttr >= sd.IdAttr {
sd.IdAttr = id.IdAttr + 1
}
}
p.x.SldIdLst.SldId = append(p.x.SldIdLst.SldId, sd)
slide := pml.NewSld()
buf := bytes.Buffer{}
enc := xml.NewEncoder(&buf)
start := xml.StartElement{Name: xml.Name{Local: "slide"}}
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/presentationml/2006/main"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:a"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:p"}, Value: "http://schemas.openxmlformats.org/presentationml/2006/main"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:r"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:sh"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:xml"}, Value: "http://www.w3.org/XML/1998/namespace"})
if err := l.x.CSld.MarshalXML(enc, start); err != nil {
return Slide{}, err
}
enc.Flush()
dec := xml.NewDecoder(&buf)
slide.CSld = pml.NewCT_CommonSlideData()
if err := dec.Decode(slide.CSld); err != nil {
return Slide{}, err
}
// clear layout name on the slide
slide.CSld.NameAttr = nil
//, chc := range slide.CSld.SpTree.Choice {
slide.CSld.SpTree.Choice = removeChoicesWithPics(slide.CSld.SpTree.Choice)
p.slides = append(p.slides, slide)
srelID := p.prels.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.OfficeDocumentType,
len(p.slides), unioffice.SlideType)
sd.RIdAttr = srelID.ID()
slidefn := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideType, len(p.slides))
p.ContentTypes.AddOverride(slidefn, unioffice.SlideContentType)
srel := common.NewRelationships()
p.slideRels = append(p.slideRels, srel)
slrLen := len(p.slideRels) - 1
for i, lout := range p.layouts {
if lout == l.X() {
lrels := p.layoutRels[i]
for _, lrel := range lrels.X().Relationship {
if lrel.TypeAttr != unioffice.SlideMasterType {
p.slideRels[slrLen].X().Relationship = append(p.slideRels[slrLen].X().Relationship, lrel)
}
}
srel.AddAutoRelationship(unioffice.DocTypePresentation, unioffice.SlideType,
i+1, unioffice.SlideLayoutType)
}
}
2017-11-20 18:24:12 -06:00
csld := Slide{sd, slide, p}
return csld, nil
}
func removeChoicesWithPics(choices []*pml.CT_GroupShapeChoice) []*pml.CT_GroupShapeChoice {
var newChoices []*pml.CT_GroupShapeChoice
for _, aChoice := range choices {
if len(aChoice.Pic) == 0 {
newChoices = append(newChoices, aChoice)
}
}
return newChoices
}
// AddDefaultSlideWithLayout tries to replicate what PowerPoint does when
// inserting a slide with a new style by clearing placeholder content and removing
// some placeholders. Use AddSlideWithLayout if you need more control.
func (p *Presentation) AddDefaultSlideWithLayout(l SlideLayout) (Slide, error) {
sld, err := p.AddSlideWithLayout(l)
for _, ph := range sld.PlaceHolders() {
// clear all placeholder content
ph.Clear()
// and drop some of the placeholders (footer, slide date/time, slide number)
switch ph.Type() {
case pml.ST_PlaceholderTypeFtr, pml.ST_PlaceholderTypeDt, pml.ST_PlaceholderTypeSldNum:
ph.Remove()
}
}
return sld, err
}
2017-08-28 20:56:18 -05:00
// Save writes the presentation out to a writer in the Zip package format
func (p *Presentation) Save(w io.Writer) error {
return p.save(w, false)
}
// SaveAsTemplate writes the presentation out to a writer in the Zip package format as a template
func (p *Presentation) SaveAsTemplate(w io.Writer) error {
return p.save(w, true)
}
func (p *Presentation) save(w io.Writer, isTemplate bool) error {
if err := p.x.Validate(); err != nil {
log.Printf("validation error in document: %s", err)
}
if !license.GetLicenseKey().IsLicensed() && flag.Lookup("test.v") == nil {
fmt.Println("Unlicensed version of UniOffice")
fmt.Println("- Get a license on https://unidoc.io")
slide := p.Slides()[0]
tb := slide.AddTextBox()
tb.Properties().SetPosition(0, 0)
tb.Properties().LineProperties().SetWidth(2 * measurement.Inch)
p := tb.AddParagraph()
r := p.AddRun()
r.SetText("Unlicensed version of UniOffice - Get a license on https://unidoc.io")
r.Properties().SetSize(12 * measurement.Point)
r.Properties().SetBold(true)
r.Properties().SetSolidFill(color.Red)
}
if isTemplate {
p.ContentTypes.RemoveOverride("application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml")
p.ContentTypes.EnsureOverride("/ppt/presentation.xml", "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml")
} else {
p.ContentTypes.RemoveOverride("application/vnd.openxmlformats-officedocument.presentationml.template.main+xml")
p.ContentTypes.EnsureOverride("/ppt/presentation.xml", "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml")
}
dt := unioffice.DocTypePresentation
2017-08-28 20:56:18 -05:00
z := zip.NewWriter(w)
defer z.Close()
if err := zippkg.MarshalXML(z, unioffice.BaseRelsFilename, p.Rels.X()); err != nil {
2017-08-28 20:56:18 -05:00
return err
}
if err := zippkg.MarshalXMLByType(z, dt, unioffice.ExtendedPropertiesType, p.AppProperties.X()); err != nil {
2017-08-28 20:56:18 -05:00
return err
}
if err := zippkg.MarshalXMLByType(z, dt, unioffice.CorePropertiesType, p.CoreProperties.X()); err != nil {
2017-08-28 20:56:18 -05:00
return err
}
if err := zippkg.MarshalXMLByType(z, dt, unioffice.PresentationPropertiesType, p.presentationProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXMLByType(z, dt, unioffice.ViewPropertiesType, p.viewProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXMLByType(z, dt, unioffice.TableStylesType, p.tableStyles.X()); err != nil {
return err
}
if p.CustomProperties.X() != nil {
if err := zippkg.MarshalXMLByType(z, dt, unioffice.CustomPropertiesType, p.CustomProperties.X()); err != nil {
return err
}
}
if p.Thumbnail != nil {
tn, err := z.Create("docProps/thumbnail.jpeg")
if err != nil {
return err
}
if err := jpeg.Encode(tn, p.Thumbnail, nil); err != nil {
return err
}
2017-08-28 20:56:18 -05:00
}
documentFn := unioffice.AbsoluteFilename(dt, unioffice.OfficeDocumentType, 0)
if err := zippkg.MarshalXML(z, documentFn, p.x); err != nil {
2017-08-28 20:56:18 -05:00
return err
}
if err := zippkg.MarshalXML(z, zippkg.RelationsPathFor(documentFn), p.prels.X()); err != nil {
2017-08-28 20:56:18 -05:00
return err
}
2017-08-28 20:56:18 -05:00
for i, slide := range p.slides {
spath := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideType, i+1)
2017-08-28 20:56:18 -05:00
zippkg.MarshalXML(z, spath, slide)
if !p.slideRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(spath)
zippkg.MarshalXML(z, rpath, p.slideRels[i].X())
}
2017-08-28 20:56:18 -05:00
}
for i, m := range p.masters {
mpath := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideMasterType, i+1)
2017-08-28 20:56:18 -05:00
zippkg.MarshalXML(z, mpath, m)
if !p.masterRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.masterRels[i].X())
}
2017-08-28 20:56:18 -05:00
}
for i, l := range p.layouts {
mpath := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideLayoutType, i+1)
2017-08-28 20:56:18 -05:00
zippkg.MarshalXML(z, mpath, l)
if !p.layoutRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.layoutRels[i].X())
}
2017-08-28 20:56:18 -05:00
}
for i, l := range p.themes {
mpath := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.ThemeType, i+1)
zippkg.MarshalXML(z, mpath, l)
if !p.themeRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.themeRels[i].X())
}
}
for i, chart := range p.charts {
fn := unioffice.AbsoluteFilename(dt, unioffice.ChartType, i+1)
zippkg.MarshalXML(z, fn, chart)
}
for i, hm := range p.handoutMaster {
fn := unioffice.AbsoluteFilename(dt, unioffice.HandoutMasterType, i+1)
zippkg.MarshalXML(z, fn, hm)
}
for i, nm := range p.notesMaster {
fn := unioffice.AbsoluteFilename(dt, unioffice.NotesMasterType, i+1)
zippkg.MarshalXML(z, fn, nm)
}
for i, cx := range p.customXML {
fn := unioffice.AbsoluteFilename(dt, unioffice.CustomXMLType, i+1)
zippkg.MarshalXML(z, fn, cx)
}
for i, img := range p.Images {
if err := common.AddImageToZip(z, img, i+1, unioffice.DocTypePresentation); err != nil {
return err
}
}
p.ContentTypes.EnsureDefault("png", "image/png")
p.ContentTypes.EnsureDefault("jpeg", "image/jpeg")
p.ContentTypes.EnsureDefault("jpg", "image/jpeg")
p.ContentTypes.EnsureDefault("wmf", "image/x-wmf")
if err := zippkg.MarshalXML(z, unioffice.ContentTypesFilename, p.ContentTypes.X()); err != nil {
return err
}
if err := p.WriteExtraFiles(z); err != nil {
return err
2017-08-28 20:56:18 -05:00
}
return nil
}
// SaveToFile writes the Presentation out to a file.
func (p *Presentation) SaveToFile(path string) error {
return p.saveToFile(path, false)
}
// SaveToFileAsTemplate writes the Presentation out to a file as a template.
func (p *Presentation) SaveToFileAsTemplate(path string) error {
return p.saveToFile(path, true)
}
func (p *Presentation) saveToFile(path string, isTemplate bool) error {
2017-08-28 20:56:18 -05:00
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return p.save(f, isTemplate)
2017-08-28 20:56:18 -05:00
}
func (p *Presentation) Validate() error {
if err := p.x.Validate(); err != nil {
return err
}
for i, s := range p.Slides() {
2017-08-28 20:56:18 -05:00
if err := s.ValidateWithPath(fmt.Sprintf("Slide[%d]", i)); err != nil {
return err
}
}
2017-08-28 20:56:18 -05:00
for i, sm := range p.masters {
if err := sm.ValidateWithPath(fmt.Sprintf("SlideMaster[%d]", i)); err != nil {
return err
}
}
for i, sl := range p.layouts {
if err := sl.ValidateWithPath(fmt.Sprintf("SlideLayout[%d]", i)); err != nil {
2017-08-28 20:56:18 -05:00
return err
}
}
return nil
}
// SlideMasters returns the slide masters defined in the presentation.
func (p *Presentation) SlideMasters() []SlideMaster {
ret := []SlideMaster{}
for i, m := range p.masters {
ret = append(ret, SlideMaster{p, p.masterRels[i], m})
}
return ret
}
// SlideLayouts returns the slide layouts defined in the presentation.
func (p *Presentation) SlideLayouts() []SlideLayout {
ret := []SlideLayout{}
for _, l := range p.layouts {
ret = append(ret, SlideLayout{l})
}
return ret
}
func (p *Presentation) onNewRelationship(decMap *zippkg.DecodeMap, target, typ string, files []*zip.File, rel *relationships.Relationship, src zippkg.Target) error {
dt := unioffice.DocTypePresentation
switch typ {
case unioffice.OfficeDocumentType:
p.x = pml.NewPresentation()
decMap.AddTarget(target, p.x, typ, 0)
decMap.AddTarget(zippkg.RelationsPathFor(target), p.prels.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.CorePropertiesType:
decMap.AddTarget(target, p.CoreProperties.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.CustomPropertiesType:
decMap.AddTarget(target, p.CustomProperties.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.PresentationPropertiesType:
decMap.AddTarget(target, p.presentationProperties.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.ViewPropertiesType:
decMap.AddTarget(target, p.viewProperties.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.TableStylesType:
decMap.AddTarget(target, p.tableStyles.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.HyperLinkType:
hl := dml.NewCT_Hyperlink()
idx := uint32(len(p.hyperlinks))
decMap.AddTarget(target, hl, typ, idx)
p.hyperlinks = append(p.hyperlinks, hl)
case unioffice.CustomXMLType:
cx := &unioffice.XSDAny{}
idx := uint32(len(p.customXML))
decMap.AddTarget(target, cx, typ, idx)
p.customXML = append(p.customXML, cx)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.customXML))
case unioffice.ChartType:
chart := crt.NewChartSpace()
idx := uint32(len(p.charts))
decMap.AddTarget(target, chart, typ, idx)
p.charts = append(p.charts, chart)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.charts))
case unioffice.HandoutMasterType:
nm := pml.NewHandoutMaster()
idx := uint32(len(p.handoutMaster))
decMap.AddTarget(target, nm, typ, idx)
p.handoutMaster = append(p.handoutMaster, nm)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.handoutMaster))
case unioffice.NotesMasterType:
nm := pml.NewNotesMaster()
idx := uint32(len(p.notesMaster))
decMap.AddTarget(target, nm, typ, idx)
p.notesMaster = append(p.notesMaster, nm)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.notesMaster))
case unioffice.ExtendedPropertiesType:
decMap.AddTarget(target, p.AppProperties.X(), typ, 0)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
case unioffice.SlideType:
sld := pml.NewSld()
p.slides = append(p.slides, sld)
decMap.AddTarget(target, sld, typ, uint32(len(p.slides)))
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.slides))
slRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), slRel.X(), typ, 0)
p.slideRels = append(p.slideRels, slRel)
case unioffice.SlideMasterType:
sm := pml.NewSldMaster()
if !decMap.AddTarget(target, sm, typ, uint32(len(p.masters)+1)) {
return nil
}
p.masters = append(p.masters, sm)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.masters))
// look for master rels
smRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), smRel.X(), typ, 0)
p.masterRels = append(p.masterRels, smRel)
case unioffice.SlideLayoutType:
sl := pml.NewSldLayout()
if !decMap.AddTarget(target, sl, typ, uint32(len(p.layouts)+1)) {
return nil
}
p.layouts = append(p.layouts, sl)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.layouts))
// look for layout rels
slRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), slRel.X(), typ, 0)
p.layoutRels = append(p.layoutRels, slRel)
case unioffice.ThumbnailType:
// read our thumbnail
for i, f := range files {
if f == nil {
continue
}
if f.Name == target {
rc, err := f.Open()
if err != nil {
return fmt.Errorf("error reading thumbnail: %s", err)
}
p.Thumbnail, _, err = image.Decode(rc)
rc.Close()
if err != nil {
return fmt.Errorf("error decoding thumbnail: %s", err)
}
files[i] = nil
}
}
case unioffice.ThemeType:
thm := dml.NewTheme()
if !decMap.AddTarget(target, thm, typ, uint32(len(p.themes)+1)) {
return nil
}
p.themes = append(p.themes, thm)
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(p.themes))
// look for theme rels
thmRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), thmRel.X(), typ, 0)
p.themeRels = append(p.themeRels, thmRel)
case unioffice.ImageType:
// we use path.Clean instead of filepath.Clean to ensure we
// end up with forward separators
target = path.Clean(target)
if targetAttr, ok := p.imagesMap[target]; ok {
rel.TargetAttr = targetAttr
return nil
}
format := ""
for i, f := range files {
if f == nil {
continue
}
if f.Name == target {
path, err := zippkg.ExtractToDiskTmp(f, p.TmpPath)
if err != nil {
return err
}
img, err := common.ImageFromStorage(path)
if err != nil {
return err
}
format = img.Format
iref := common.MakeImageRef(img, &p.DocBase, p.prels)
p.Images = append(p.Images, iref)
files[i] = nil
decMap.RecordIndex(target, len(p.Images))
break
}
}
idx := decMap.IndexFor(target)
rel.TargetAttr = unioffice.RelativeImageFilename(dt, src.Typ, typ, idx, format)
p.imagesMap[target] = rel.TargetAttr
default:
unioffice.Log("unsupported relationship type: %s tgt: %s", typ, target)
}
return nil
}
// Slides returns the slides in the presentation.
func (p *Presentation) Slides() []Slide {
ret := []Slide{}
for i, v := range p.slides {
2017-11-20 18:24:12 -06:00
ret = append(ret, Slide{p.x.SldIdLst.SldId[i], v, p})
}
return ret
}
// RemoveSlide removes a slide from a presentation.
func (p *Presentation) RemoveSlide(s Slide) error {
removed := false
slideIdx := 0
for i, v := range p.slides {
if v == s.x {
if p.x.SldIdLst.SldId[i] != s.sid {
return errors.New("inconsistency in slides and ID list")
}
copy(p.slides[i:], p.slides[i+1:])
p.slides = p.slides[0 : len(p.slides)-1]
copy(p.slideRels[i:], p.slideRels[i+1:])
p.slideRels = p.slideRels[0 : len(p.slideRels)-1]
copy(p.x.SldIdLst.SldId[i:], p.x.SldIdLst.SldId[i+1:])
p.x.SldIdLst.SldId = p.x.SldIdLst.SldId[0 : len(p.x.SldIdLst.SldId)-1]
removed = true
slideIdx = i
}
}
if !removed {
return errors.New("unable to find slide")
}
// remove it from content types
fn := unioffice.AbsoluteFilename(unioffice.DocTypePresentation, unioffice.SlideType, 0)
return p.ContentTypes.RemoveOverrideByIndex(fn, slideIdx)
}
// GetLayoutByName retrieves a slide layout given a layout name.
func (p *Presentation) GetLayoutByName(name string) (SlideLayout, error) {
for _, l := range p.layouts {
if l.CSld.NameAttr != nil && name == *l.CSld.NameAttr {
return SlideLayout{l}, nil
}
}
return SlideLayout{}, errors.New("unable to find layout with that name")
}
2017-11-20 18:24:12 -06:00
// AddImage adds an image to the document package, returning a reference that
// can be used to add the image to a run and place it in the document contents.
func (p *Presentation) AddImage(i common.Image) (common.ImageRef, error) {
r := common.MakeImageRef(i, &p.DocBase, p.prels)
if i.Data == nil && i.Path == "" {
return r, errors.New("image must have data or a path")
2017-11-20 18:24:12 -06:00
}
if i.Format == "" {
return r, errors.New("image must have a valid format")
}
if i.Size.X == 0 || i.Size.Y == 0 {
return r, errors.New("image must have a valid size")
}
if i.Path != "" {
err := tempstorage.Add(i.Path)
if err != nil {
return r, err
}
}
2017-11-20 18:24:12 -06:00
p.Images = append(p.Images, r)
p.ContentTypes.EnsureDefault("png", "image/png")
p.ContentTypes.EnsureDefault("jpeg", "image/jpeg")
p.ContentTypes.EnsureDefault("jpg", "image/jpeg")
p.ContentTypes.EnsureDefault("wmf", "image/x-wmf")
p.ContentTypes.EnsureDefault(i.Format, "image/"+i.Format)
2017-11-20 18:24:12 -06:00
return r, nil
}
// GetImageByRelID returns an ImageRef with the associated relation ID in the
// document.
func (p *Presentation) GetImageByRelID(relID string) (common.ImageRef, bool) {
for _, img := range p.Images {
if img.RelID() == relID {
return img, true
}
}
return common.ImageRef{}, false
}
// GetOrCreateCustomProperties returns the custom properties of the document (and if they not exist yet, creating them first)
func (p *Presentation) GetOrCreateCustomProperties() common.CustomProperties {
if p.CustomProperties.X() == nil {
p.createCustomProperties()
}
return p.CustomProperties
}
func (p *Presentation) createCustomProperties() {
p.CustomProperties = common.NewCustomProperties()
p.addCustomRelationships()
}
func (p *Presentation) addCustomRelationships() {
p.ContentTypes.AddOverride("/docProps/custom.xml", "application/vnd.openxmlformats-officedocument.custom-properties+xml")
p.Rels.AddRelationship("docProps/custom.xml", unioffice.CustomPropertiesType)
}
// Close closes the presentation, removing any temporary files that might have been
// created when opening a document.
func (p *Presentation) Close() error {
if p.TmpPath != "" {
return tempstorage.RemoveAll(p.TmpPath)
}
return nil
}