diff --git a/.gitignore b/.gitignore index 0801b70a0d..67f938b4a7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.dll *.so *.dylib +*.swp coverage* **/.DS_Store **/main diff --git a/_examples/document/image/image.docx b/_examples/document/image/image.docx index a9d6021e4c..4e92407232 100644 Binary files a/_examples/document/image/image.docx and b/_examples/document/image/image.docx differ diff --git a/_examples/document/image/main.go b/_examples/document/image/main.go index 3fa1079f15..6a66b740b5 100644 --- a/_examples/document/image/main.go +++ b/_examples/document/image/main.go @@ -2,6 +2,7 @@ package main import ( + "io/ioutil" "log" "baliance.com/gooxml/common" @@ -16,18 +17,30 @@ var lorem = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin lobo func main() { doc := document.New() - img, err := common.ImageFromFile("gophercolor.png") + img1, err := common.ImageFromFile("gophercolor.png") + if err != nil { + log.Fatalf("unable to create image: %s", err) + } + img2data, err := ioutil.ReadFile("gophercolor.png") + if err != nil { + log.Fatalf("unable to read file: %s", err) + } + img2, err := common.ImageFromBytes(img2data) if err != nil { log.Fatalf("unable to create image: %s", err) } - iref, err := doc.AddImage(img) + img1ref, err := doc.AddImage(img1) + if err != nil { + log.Fatalf("unable to add image to document: %s", err) + } + img2ref, err := doc.AddImage(img2) if err != nil { log.Fatalf("unable to add image to document: %s", err) } para := doc.AddParagraph() - anchored, err := para.AddRun().AddDrawingAnchored(iref) + anchored, err := para.AddRun().AddDrawingAnchored(img1ref) if err != nil { log.Fatalf("unable to add anchored image: %s", err) } @@ -44,7 +57,14 @@ func main() { // drop an inline image in if i == 13 { - inl, err := run.AddDrawingInline(iref) + inl, err := run.AddDrawingInline(img1ref) + if err != nil { + log.Fatalf("unable to add inline image: %s", err) + } + inl.SetSize(1*measurement.Inch, 1*measurement.Inch) + } + if i == 15 { + inl, err := run.AddDrawingInline(img2ref) if err != nil { log.Fatalf("unable to add inline image: %s", err) } diff --git a/common/image.go b/common/image.go index 0abc9bec3a..be6207cade 100644 --- a/common/image.go +++ b/common/image.go @@ -8,6 +8,7 @@ package common import ( + "bytes" "fmt" "image" "os" @@ -21,10 +22,12 @@ import ( // Image is a container for image information. It's used as we need format and // and size information to use images. +// It contains either the filesystem path to the image, or the image itself. type Image struct { Size image.Point Format string Path string + Data *[]byte } // ImageRef is a reference to an image within a document. @@ -56,11 +59,16 @@ func (i ImageRef) Format() string { return i.img.Format } -// Path returns the path to an image file +// Path returns the path to an image file, if any. func (i ImageRef) Path() string { return i.img.Path } +// Data returns the data of an image file, if any. +func (i ImageRef) Data() *[]byte { + return i.img.Data +} + // Size returns the size of an image func (i ImageRef) Size() image.Point { return i.img.Size @@ -102,3 +110,18 @@ func ImageFromFile(path string) (Image, error) { r.Size = imgDec.Bounds().Size() return r, nil } + +// ImageFromBytes returns an Image struct for an in-memory image. You can also +// construct an Image directly if the file and size are known. +func ImageFromBytes(data []byte) (Image, error) { + r := Image{} + imgDec, ifmt, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return r, fmt.Errorf("unable to parse image: %s", err) + } + + r.Data = &data + r.Format = ifmt + r.Size = imgDec.Bounds().Size() + return r, nil +} diff --git a/document/document.go b/document/document.go index 7f3a815d24..a0c7ba0e80 100644 --- a/document/document.go +++ b/document/document.go @@ -244,7 +244,11 @@ func (d *Document) Save(w io.Writer) error { for i, img := range d.Images { fn := fmt.Sprintf("word/media/image%d.%s", i+1, strings.ToLower(img.Format())) - if img.Path() != "" { + if img.Data() != nil { + if err := zippkg.AddFileFromBytes(z, fn, *img.Data()); err != nil { + return err + } + } else if img.Path() != "" { if err := zippkg.AddFileFromDisk(z, fn, img.Path()); err != nil { return err } @@ -566,8 +570,8 @@ func (d *Document) validateTableCells() error { // can be used to add the image to a run and place it in the document contents. func (d *Document) AddImage(i common.Image) (common.ImageRef, error) { r := common.MakeImageRef(i, &d.DocBase, d.docRels) - if i.Path == "" { - return r, errors.New("image must have a path") + if i.Data == nil && i.Path == "" { + return r, errors.New("image must have data or a path") } if i.Format == "" { diff --git a/document/document_test.go b/document/document_test.go index 43f2d7a56c..10baa48189 100644 --- a/document/document_test.go +++ b/document/document_test.go @@ -216,39 +216,97 @@ func TestHeaderAndFooterImages(t *testing.T) { if err != nil { t.Fatalf("unable to create image: %s", err) } - dir1, err := doc.AddImage(img1) + png3x3 := []byte{ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, + 0x08, 0x02, 0x00, 0x00, 0x00, 0xd9, 0x4a, 0x22, + 0xe8, 0x00, 0x00, 0x00, 0x1e, 0x49, 0x44, 0x41, + 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xc5, 0x1e, 0xf8, + 0x9d, 0xfd, 0xd7, 0x34, 0xf6, 0x5f, 0x0c, 0x10, + 0x8a, 0x9d, 0xf7, 0x17, 0x03, 0x84, 0x62, 0xf7, + 0xf9, 0x05, 0x00, 0xd2, 0x6f, 0x0d, 0x71, 0x26, + 0x33, 0x2f, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, + } + img3, err := common.ImageFromBytes(png3x3) if err != nil { - t.Fatalf("unable to add image to doc: %s", err) + t.Fatalf("unable to create image: %s", err) } - dir2, err := doc.AddImage(img2) + + dir1, err := doc.AddImage(img1) if err != nil { t.Fatalf("unable to add image to doc: %s", err) } - if dir1.RelID() != "rId4" { t.Errorf("expected rId4 != %s", dir1.RelID()) } + + dir2, err := doc.AddImage(img2) + if err != nil { + t.Fatalf("unable to add image to doc: %s", err) + } if dir2.RelID() != "rId5" { t.Errorf("expected rId5 != %s", dir2.RelID()) } + dir3, err := doc.AddImage(img3) + if err != nil { + t.Fatalf("unable to add image to doc: %s", err) + } + if dir3.RelID() != "rId6" { + t.Errorf("expected rId6 != %s", dir3.RelID()) + } + hdr := doc.AddHeader() ftr := doc.AddFooter() + hir1, err := hdr.AddImage(img1) - fir1, err := ftr.AddImage(img1) - hir2, err := hdr.AddImage(img2) - fir2, err := ftr.AddImage(img2) + if err != nil { + t.Fatalf("unable to add image to header: %s", err) + } if hir1.RelID() != "rId1" { t.Errorf("expected rId1 != %s", hir1.RelID()) } + + hir2, err := hdr.AddImage(img2) + if err != nil { + t.Fatalf("unable to add image to header: %s", err) + } if hir2.RelID() != "rId2" { t.Errorf("expected rId2 != %s", hir2.RelID()) } + + hir3, err := hdr.AddImage(img3) + if err != nil { + t.Fatalf("unable to add image to header: %s", err) + } + if hir3.RelID() != "rId3" { + t.Errorf("expected rId3 != %s", hir3.RelID()) + } + + fir1, err := ftr.AddImage(img1) + if err != nil { + t.Fatalf("unable to add image to footer: %s", err) + } if fir1.RelID() != "rId1" { - t.Errorf("expected rId1 != %s", hir1.RelID()) + t.Errorf("expected rId1 != %s", fir1.RelID()) + } + + fir2, err := ftr.AddImage(img2) + if err != nil { + t.Fatalf("unable to add image to footer: %s", err) } if fir2.RelID() != "rId2" { - t.Errorf("expected rId2 != %s", hir2.RelID()) + t.Errorf("expected rId2 != %s", fir2.RelID()) + } + + fir3, err := ftr.AddImage(img3) + if err != nil { + t.Fatalf("unable to add image to footer: %s", err) + } + if fir3.RelID() != "rId3" { + t.Errorf("expected rId3 != %s", fir3.RelID()) } } diff --git a/document/footer.go b/document/footer.go index 16f1002526..8d172e9a02 100644 --- a/document/footer.go +++ b/document/footer.go @@ -89,8 +89,8 @@ func (f Footer) AddImage(i common.Image) (common.ImageRef, error) { } r := common.MakeImageRef(i, &f.d.DocBase, ftrRels) - if i.Path == "" { - return r, errors.New("image must have a path") + if i.Data == nil && i.Path == "" { + return r, errors.New("image must have data or a path") } if i.Format == "" { diff --git a/document/header.go b/document/header.go index 8ef5c44f5d..7bb8f652ee 100644 --- a/document/header.go +++ b/document/header.go @@ -89,8 +89,8 @@ func (h Header) AddImage(i common.Image) (common.ImageRef, error) { } r := common.MakeImageRef(i, &h.d.DocBase, hdrRels) - if i.Path == "" { - return r, errors.New("image must have a path") + if i.Data == nil && i.Path == "" { + return r, errors.New("image must have data or a path") } if i.Format == "" { diff --git a/presentation/presentation.go b/presentation/presentation.go index 95b9b59bd1..8c68afd6c4 100644 --- a/presentation/presentation.go +++ b/presentation/presentation.go @@ -688,8 +688,8 @@ func (p *Presentation) GetLayoutByName(name string) (SlideLayout, error) { // 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.Path == "" { - return r, errors.New("image must have a path") + if i.Data == nil && i.Path == "" { + return r, errors.New("image must have data or a path") } if i.Format == "" { diff --git a/spreadsheet/workbook.go b/spreadsheet/workbook.go index dbb56cbc02..95133db652 100644 --- a/spreadsheet/workbook.go +++ b/spreadsheet/workbook.go @@ -491,8 +491,8 @@ func (wb *Workbook) RecalculateFormulas() { // can be used to add the image to a drawing. func (wb *Workbook) AddImage(i common.Image) (common.ImageRef, error) { r := common.MakeImageRef(i, &wb.DocBase, wb.wbRels) - if i.Path == "" { - return r, errors.New("image must have a path") + if i.Data == nil && i.Path == "" { + return r, errors.New("image must have data or a path") } if i.Format == "" { diff --git a/zippkg/helpers.go b/zippkg/helpers.go index e104f11462..0a2e8ba1e0 100644 --- a/zippkg/helpers.go +++ b/zippkg/helpers.go @@ -9,6 +9,7 @@ package zippkg import ( "archive/zip" + "bytes" "encoding/xml" "fmt" "io" @@ -69,6 +70,16 @@ func AddFileFromDisk(z *zip.Writer, zipPath, diskPath string) error { return err } +// AddFileFromBytes takes a byte array and adds it at a given path to a zip file. +func AddFileFromBytes(z *zip.Writer, zipPath string, data []byte) error { + w, err := z.Create(zipPath) + if err != nil { + return fmt.Errorf("error creating %s: %s", zipPath, err) + } + _, err = io.Copy(w, bytes.NewReader(data)) + return err +} + // ExtractToDiskTmp extracts a zip file to a temporary file in a given path, // returning the name of the file. func ExtractToDiskTmp(f *zip.File, path string) (string, error) {