diff --git a/_examples/presentation/simple/main.go b/_examples/presentation/simple/main.go index 93d4fe7ab0..47ed53312a 100644 --- a/_examples/presentation/simple/main.go +++ b/_examples/presentation/simple/main.go @@ -2,13 +2,40 @@ package main import ( + "log" + + "baliance.com/gooxml/color" + "baliance.com/gooxml/measurement" + "baliance.com/gooxml/schema/soo/dml" + "baliance.com/gooxml/presentation" ) func main() { ppt := presentation.New() - slide := ppt.AddSlide() - _ = slide + for i := 0; i < 5; i++ { + slide := ppt.AddSlide() + + tb := slide.AddTextBox() + tb.Properties().SetGeometry(dml.ST_ShapeTypeStar10) + + tb.Properties().SetWidth(3 * measurement.Inch) + pos := measurement.Distance(i) * measurement.Inch + tb.Properties().SetPosition(pos, pos) + + tb.Properties().SetSolidFill(color.AliceBlue) + tb.Properties().LineProperties().SetSolidFill(color.Blue) + + p := tb.AddParagraph() + p.Properties().SetAlign(dml.ST_TextAlignTypeCtr) + + r := p.AddRun() + r.SetText("gooxml") + r.Properties().SetSize(24 * measurement.Point) + } + if err := ppt.Validate(); err != nil { + log.Fatal(err) + } ppt.SaveToFile("simple.pptx") } diff --git a/_examples/presentation/simple/simple.pptx b/_examples/presentation/simple/simple.pptx index f44837c323..3bebddc443 100644 Binary files a/_examples/presentation/simple/simple.pptx and b/_examples/presentation/simple/simple.pptx differ diff --git a/drawing/paragraphproperties.go b/drawing/paragraphproperties.go index d0d428603d..285ca94686 100644 --- a/drawing/paragraphproperties.go +++ b/drawing/paragraphproperties.go @@ -36,3 +36,8 @@ func (p ParagraphProperties) SetNumbered(scheme dml.ST_TextAutonumberScheme) { p.x.BuAutoNum.TypeAttr = scheme } } + +// SetAlign controls the paragraph alignment +func (p ParagraphProperties) SetAlign(a dml.ST_TextAlignType) { + p.x.AlgnAttr = a +} diff --git a/drawing/shapeproperties.go b/drawing/shapeproperties.go index 310330f664..9fbeee2a21 100644 --- a/drawing/shapeproperties.go +++ b/drawing/shapeproperties.go @@ -8,7 +8,9 @@ package drawing import ( + "baliance.com/gooxml" "baliance.com/gooxml/color" + "baliance.com/gooxml/measurement" "baliance.com/gooxml/schema/soo/dml" ) @@ -53,3 +55,50 @@ func (s ShapeProperties) LineProperties() LineProperties { } return LineProperties{s.x.Ln} } + +func (s ShapeProperties) ensureXfrm() { + if s.x.Xfrm == nil { + s.x.Xfrm = dml.NewCT_Transform2D() + } +} + +// SetWidth sets the width of the shape. +func (s ShapeProperties) SetWidth(w measurement.Distance) { + s.ensureXfrm() + if s.x.Xfrm.Ext == nil { + s.x.Xfrm.Ext = dml.NewCT_PositiveSize2D() + } + s.x.Xfrm.Ext.CxAttr = int64(w / measurement.EMU) +} + +// SetHeight sets the height of the shape. +func (s ShapeProperties) SetHeight(h measurement.Distance) { + s.ensureXfrm() + if s.x.Xfrm.Ext == nil { + s.x.Xfrm.Ext = dml.NewCT_PositiveSize2D() + } + s.x.Xfrm.Ext.CyAttr = int64(h / measurement.EMU) +} + +// SetSize sets the width and height of the shape. +func (s ShapeProperties) SetSize(w, h measurement.Distance) { + s.SetWidth(w) + s.SetHeight(h) +} + +// SetPosition sets the position of the shape. +func (s ShapeProperties) SetPosition(x, y measurement.Distance) { + if s.x.Xfrm.Off == nil { + s.x.Xfrm.Off = dml.NewCT_Point2D() + } + s.x.Xfrm.Off.XAttr.ST_CoordinateUnqualified = gooxml.Int64(int64(x / measurement.EMU)) + s.x.Xfrm.Off.YAttr.ST_CoordinateUnqualified = gooxml.Int64(int64(y / measurement.EMU)) +} + +// SetGeometry sets the shape type of the shape +func (s ShapeProperties) SetGeometry(g dml.ST_ShapeType) { + if s.x.PrstGeom == nil { + s.x.PrstGeom = dml.NewCT_PresetGeometry2D() + } + s.x.PrstGeom.PrstAttr = g +} diff --git a/presentation/presentation.go b/presentation/presentation.go index f7836312f3..22e856bf15 100644 --- a/presentation/presentation.go +++ b/presentation/presentation.go @@ -232,10 +232,20 @@ 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. func (p *Presentation) AddSlide() Slide { sd := pml.NewCT_SlideIdListEntry() - sd.IdAttr = 256 + sd.IdAttr = p.nextSlideID() p.x.SldIdLst.SldId = append(p.x.SldIdLst.SldId, sd) slide := pml.NewSld() @@ -250,24 +260,6 @@ func (p *Presentation) AddSlide() Slide { slide.CSld.SpTree.GrpSpPr.Xfrm.ChOff = slide.CSld.SpTree.GrpSpPr.Xfrm.Off slide.CSld.SpTree.GrpSpPr.Xfrm.ChExt = slide.CSld.SpTree.GrpSpPr.Xfrm.Ext - /* - c := pml.NewCT_GroupShapeChoice() - slide.CSld.SpTree.Choice = append(slide.CSld.SpTree.Choice, c) - sp := pml.NewCT_Shape() - c.Sp = append(c.Sp, sp) - - sp.NvSpPr.NvPr.Ph = pml.NewCT_Placeholder() - sp.NvSpPr.NvPr.Ph.TypeAttr = pml.ST_PlaceholderTypeCtrTitle - - sp.TxBody = dml.NewCT_TextBody() - para := dml.NewCT_TextParagraph() - sp.TxBody.P = append(sp.TxBody.P, para) - - run := dml.NewEG_TextRun() - para.EG_TextRun = append(para.EG_TextRun, run) - run.R = dml.NewCT_RegularTextRun() - run.R.T = "testing 123" - */ p.slides = append(p.slides, slide) srelID := p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType, len(p.slides), gooxml.SlideType) @@ -478,11 +470,12 @@ func (p *Presentation) Validate() error { if err := p.x.Validate(); err != nil { return err } - for i, s := range p.slides { + for i, s := range p.Slides() { if err := s.ValidateWithPath(fmt.Sprintf("Slide[%d]", i)); err != nil { return err } } + for i, sm := range p.masters { if err := sm.ValidateWithPath(fmt.Sprintf("SlideMaster[%d]", i)); err != nil { return err diff --git a/presentation/slide.go b/presentation/slide.go index b6a06df55e..516f0cd79f 100644 --- a/presentation/slide.go +++ b/presentation/slide.go @@ -10,6 +10,9 @@ package presentation import ( "errors" + "baliance.com/gooxml/measurement" + "baliance.com/gooxml/schema/soo/dml" + "baliance.com/gooxml/schema/soo/pml" ) @@ -68,3 +71,47 @@ func (s Slide) GetPlaceholderByIndex(idx uint32) (PlaceHolder, error) { } return PlaceHolder{}, errors.New("unable to find placeholder") } + +// ValidateWithPath validates the slide passing path informaton for a better +// error message +func (s Slide) ValidateWithPath(path string) error { + // schema checks + if err := s.x.ValidateWithPath(path); err != nil { + return err + } + + // stuff we've figured out + for _, c := range s.x.CSld.SpTree.Choice { + for _, sp := range c.Sp { + if sp.TxBody != nil { + if len(sp.TxBody.P) == 0 { + return errors.New(path + " : slide shape with a txbody must contain paragraphs") + } + } + } + } + return nil +} + +// AddTextBox adds an empty textbox to a slide. +func (s Slide) AddTextBox() TextBox { + c := pml.NewCT_GroupShapeChoice() + s.x.CSld.SpTree.Choice = append(s.x.CSld.SpTree.Choice, c) + + sp := pml.NewCT_Shape() + c.Sp = append(c.Sp, sp) + sp.SpPr = dml.NewCT_ShapeProperties() + sp.SpPr.Xfrm = dml.NewCT_Transform2D() + sp.SpPr.PrstGeom = dml.NewCT_PresetGeometry2D() + sp.SpPr.PrstGeom.PrstAttr = dml.ST_ShapeTypeRect + sp.TxBody = dml.NewCT_TextBody() + sp.TxBody.BodyPr = dml.NewCT_TextBodyProperties() + sp.TxBody.BodyPr.WrapAttr = dml.ST_TextWrappingTypeSquare + sp.TxBody.BodyPr.SpAutoFit = dml.NewCT_TextShapeAutofit() + + tb := TextBox{sp} + tb.Properties().SetWidth(3 * measurement.Inch) + tb.Properties().SetHeight(1 * measurement.Inch) + tb.Properties().SetPosition(0, 0) + return tb +} diff --git a/presentation/textbox.go b/presentation/textbox.go new file mode 100644 index 0000000000..585951b8fc --- /dev/null +++ b/presentation/textbox.go @@ -0,0 +1,34 @@ +// Copyright 2017 Baliance. All rights reserved. +// +// 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 by contacting sales@baliance.com. + +package presentation + +import ( + "baliance.com/gooxml/drawing" + "baliance.com/gooxml/schema/soo/dml" + "baliance.com/gooxml/schema/soo/pml" +) + +// TextBox is a text box within a slide. +type TextBox struct { + x *pml.CT_Shape +} + +// AddParagraph adds a paragraph to the text box +func (t TextBox) AddParagraph() drawing.Paragraph { + p := dml.NewCT_TextParagraph() + t.x.TxBody.P = append(t.x.TxBody.P, p) + return drawing.MakeParagraph(p) +} + +// Properties returns the properties of the TextBox. +func (t TextBox) Properties() drawing.ShapeProperties { + if t.x.SpPr == nil { + t.x.SpPr = dml.NewCT_ShapeProperties() + } + return drawing.MakeShapeProperties(t.x.SpPr) +}