forked from p30928647/excelize
This closes #1204, breaking changes for add comments
- Allowing insert SVG format images - Unit tests updated
This commit is contained in:
parent
a410b22bdd
commit
db2d084ada
|
@ -1,15 +1,15 @@
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
*.json
|
||||||
|
*.out
|
||||||
|
*.test
|
||||||
~$*.xlsx
|
~$*.xlsx
|
||||||
|
test/*.png
|
||||||
|
test/BadWorkbook.SaveAsEmptyStruct.xlsx
|
||||||
|
test/Encryption*.xlsx
|
||||||
|
test/excelize-*
|
||||||
test/Test*.xlam
|
test/Test*.xlam
|
||||||
test/Test*.xlsm
|
test/Test*.xlsm
|
||||||
test/Test*.xlsx
|
test/Test*.xlsx
|
||||||
test/Test*.xltm
|
test/Test*.xltm
|
||||||
test/Test*.xltx
|
test/Test*.xltx
|
||||||
# generated files
|
|
||||||
test/Encryption*.xlsx
|
|
||||||
test/BadWorkbook.SaveAsEmptyStruct.xlsx
|
|
||||||
test/*.png
|
|
||||||
test/excelize-*
|
|
||||||
*.out
|
|
||||||
*.test
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
|
|
55
cell.go
55
cell.go
|
@ -902,31 +902,7 @@ func getCellRichText(si *xlsxSI) (runs []RichTextRun) {
|
||||||
Text: v.T.Val,
|
Text: v.T.Val,
|
||||||
}
|
}
|
||||||
if v.RPr != nil {
|
if v.RPr != nil {
|
||||||
font := Font{Underline: "none"}
|
run.Font = newFont(v.RPr)
|
||||||
font.Bold = v.RPr.B != nil
|
|
||||||
font.Italic = v.RPr.I != nil
|
|
||||||
if v.RPr.U != nil {
|
|
||||||
font.Underline = "single"
|
|
||||||
if v.RPr.U.Val != nil {
|
|
||||||
font.Underline = *v.RPr.U.Val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v.RPr.RFont != nil && v.RPr.RFont.Val != nil {
|
|
||||||
font.Family = *v.RPr.RFont.Val
|
|
||||||
}
|
|
||||||
if v.RPr.Sz != nil && v.RPr.Sz.Val != nil {
|
|
||||||
font.Size = *v.RPr.Sz.Val
|
|
||||||
}
|
|
||||||
font.Strike = v.RPr.Strike != nil
|
|
||||||
if v.RPr.Color != nil {
|
|
||||||
font.Color = strings.TrimPrefix(v.RPr.Color.RGB, "FF")
|
|
||||||
if v.RPr.Color.Theme != nil {
|
|
||||||
font.ColorTheme = v.RPr.Color.Theme
|
|
||||||
}
|
|
||||||
font.ColorIndexed = v.RPr.Color.Indexed
|
|
||||||
font.ColorTint = v.RPr.Color.Tint
|
|
||||||
}
|
|
||||||
run.Font = &font
|
|
||||||
}
|
}
|
||||||
runs = append(runs, run)
|
runs = append(runs, run)
|
||||||
}
|
}
|
||||||
|
@ -985,6 +961,35 @@ func newRpr(fnt *Font) *xlsxRPr {
|
||||||
return &rpr
|
return &rpr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newFont create font format by given run properties for the rich text.
|
||||||
|
func newFont(rPr *xlsxRPr) *Font {
|
||||||
|
font := Font{Underline: "none"}
|
||||||
|
font.Bold = rPr.B != nil
|
||||||
|
font.Italic = rPr.I != nil
|
||||||
|
if rPr.U != nil {
|
||||||
|
font.Underline = "single"
|
||||||
|
if rPr.U.Val != nil {
|
||||||
|
font.Underline = *rPr.U.Val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rPr.RFont != nil && rPr.RFont.Val != nil {
|
||||||
|
font.Family = *rPr.RFont.Val
|
||||||
|
}
|
||||||
|
if rPr.Sz != nil && rPr.Sz.Val != nil {
|
||||||
|
font.Size = *rPr.Sz.Val
|
||||||
|
}
|
||||||
|
font.Strike = rPr.Strike != nil
|
||||||
|
if rPr.Color != nil {
|
||||||
|
font.Color = strings.TrimPrefix(rPr.Color.RGB, "FF")
|
||||||
|
if rPr.Color.Theme != nil {
|
||||||
|
font.ColorTheme = rPr.Color.Theme
|
||||||
|
}
|
||||||
|
font.ColorIndexed = rPr.Color.Indexed
|
||||||
|
font.ColorTint = rPr.Color.Tint
|
||||||
|
}
|
||||||
|
return &font
|
||||||
|
}
|
||||||
|
|
||||||
// setRichText provides a function to set rich text of a cell.
|
// setRichText provides a function to set rich text of a cell.
|
||||||
func setRichText(runs []RichTextRun) ([]xlsxR, error) {
|
func setRichText(runs []RichTextRun) ([]xlsxR, error) {
|
||||||
var (
|
var (
|
||||||
|
|
127
comment.go
127
comment.go
|
@ -13,7 +13,6 @@ package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -23,17 +22,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseCommentOptions provides a function to parse the format settings of
|
|
||||||
// the comment with default value.
|
|
||||||
func parseCommentOptions(opts string) (*commentOptions, error) {
|
|
||||||
options := commentOptions{
|
|
||||||
Author: "Author:",
|
|
||||||
Text: " ",
|
|
||||||
}
|
|
||||||
err := json.Unmarshal([]byte(opts), &options)
|
|
||||||
return &options, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetComments retrieves all comments and returns a map of worksheet name to
|
// GetComments retrieves all comments and returns a map of worksheet name to
|
||||||
// the worksheet comments.
|
// the worksheet comments.
|
||||||
func (f *File) GetComments() (comments map[string][]Comment) {
|
func (f *File) GetComments() (comments map[string][]Comment) {
|
||||||
|
@ -53,14 +41,18 @@ func (f *File) GetComments() (comments map[string][]Comment) {
|
||||||
if comment.AuthorID < len(d.Authors.Author) {
|
if comment.AuthorID < len(d.Authors.Author) {
|
||||||
sheetComment.Author = d.Authors.Author[comment.AuthorID]
|
sheetComment.Author = d.Authors.Author[comment.AuthorID]
|
||||||
}
|
}
|
||||||
sheetComment.Ref = comment.Ref
|
sheetComment.Cell = comment.Ref
|
||||||
sheetComment.AuthorID = comment.AuthorID
|
sheetComment.AuthorID = comment.AuthorID
|
||||||
if comment.Text.T != nil {
|
if comment.Text.T != nil {
|
||||||
sheetComment.Text += *comment.Text.T
|
sheetComment.Text += *comment.Text.T
|
||||||
}
|
}
|
||||||
for _, text := range comment.Text.R {
|
for _, text := range comment.Text.R {
|
||||||
if text.T != nil {
|
if text.T != nil {
|
||||||
sheetComment.Text += text.T.Val
|
run := RichTextRun{Text: text.T.Val}
|
||||||
|
if text.RPr != nil {
|
||||||
|
run.Font = newFont(text.RPr)
|
||||||
|
}
|
||||||
|
sheetComment.Runs = append(sheetComment.Runs, run)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sheetComments = append(sheetComments, sheetComment)
|
sheetComments = append(sheetComments, sheetComment)
|
||||||
|
@ -92,12 +84,15 @@ func (f *File) getSheetComments(sheetFile string) string {
|
||||||
// author length is 255 and the max text length is 32512. For example, add a
|
// author length is 255 and the max text length is 32512. For example, add a
|
||||||
// comment in Sheet1!$A$30:
|
// comment in Sheet1!$A$30:
|
||||||
//
|
//
|
||||||
// err := f.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
|
// err := f.AddComment(sheet, excelize.Comment{
|
||||||
func (f *File) AddComment(sheet, cell, opts string) error {
|
// Cell: "A12",
|
||||||
options, err := parseCommentOptions(opts)
|
// Author: "Excelize",
|
||||||
if err != nil {
|
// Runs: []excelize.RichTextRun{
|
||||||
return err
|
// {Text: "Excelize: ", Font: &excelize.Font{Bold: true}},
|
||||||
}
|
// {Text: "This is a comment."},
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
func (f *File) AddComment(sheet string, comment Comment) error {
|
||||||
// Read sheet data.
|
// Read sheet data.
|
||||||
ws, err := f.workSheetReader(sheet)
|
ws, err := f.workSheetReader(sheet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -122,20 +117,19 @@ func (f *File) AddComment(sheet, cell, opts string) error {
|
||||||
f.addSheetLegacyDrawing(sheet, rID)
|
f.addSheetLegacyDrawing(sheet, rID)
|
||||||
}
|
}
|
||||||
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
|
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
|
||||||
var colCount int
|
var rows, cols int
|
||||||
for i, l := range strings.Split(options.Text, "\n") {
|
for _, runs := range comment.Runs {
|
||||||
if ll := len(l); ll > colCount {
|
for _, subStr := range strings.Split(runs.Text, "\n") {
|
||||||
if i == 0 {
|
rows++
|
||||||
ll += len(options.Author)
|
if chars := len(subStr); chars > cols {
|
||||||
}
|
cols = chars
|
||||||
colCount = ll
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = f.addDrawingVML(commentID, drawingVML, cell, strings.Count(options.Text, "\n")+1, colCount)
|
}
|
||||||
if err != nil {
|
if err = f.addDrawingVML(commentID, drawingVML, comment.Cell, rows+1, cols); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.addComment(commentsXML, cell, options)
|
f.addComment(commentsXML, comment)
|
||||||
f.addContentTypePart(commentID, "comments")
|
f.addContentTypePart(commentID, "comments")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -280,44 +274,42 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount,
|
||||||
|
|
||||||
// addComment provides a function to create chart as xl/comments%d.xml by
|
// addComment provides a function to create chart as xl/comments%d.xml by
|
||||||
// given cell and format sets.
|
// given cell and format sets.
|
||||||
func (f *File) addComment(commentsXML, cell string, opts *commentOptions) {
|
func (f *File) addComment(commentsXML string, comment Comment) {
|
||||||
a := opts.Author
|
if comment.Author == "" {
|
||||||
t := opts.Text
|
comment.Author = "Author"
|
||||||
if len(a) > MaxFieldLength {
|
|
||||||
a = a[:MaxFieldLength]
|
|
||||||
}
|
}
|
||||||
if len(t) > 32512 {
|
if len(comment.Author) > MaxFieldLength {
|
||||||
t = t[:32512]
|
comment.Author = comment.Author[:MaxFieldLength]
|
||||||
}
|
}
|
||||||
comments := f.commentsReader(commentsXML)
|
comments, authorID := f.commentsReader(commentsXML), 0
|
||||||
authorID := 0
|
|
||||||
if comments == nil {
|
if comments == nil {
|
||||||
comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{opts.Author}}}
|
comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}}
|
||||||
}
|
}
|
||||||
if inStrSlice(comments.Authors.Author, opts.Author, true) == -1 {
|
if inStrSlice(comments.Authors.Author, comment.Author, true) == -1 {
|
||||||
comments.Authors.Author = append(comments.Authors.Author, opts.Author)
|
comments.Authors.Author = append(comments.Authors.Author, comment.Author)
|
||||||
authorID = len(comments.Authors.Author) - 1
|
authorID = len(comments.Authors.Author) - 1
|
||||||
}
|
}
|
||||||
defaultFont := f.GetDefaultFont()
|
defaultFont, chars, cmt := f.GetDefaultFont(), 0, xlsxComment{
|
||||||
bold := ""
|
Ref: comment.Cell,
|
||||||
cmt := xlsxComment{
|
|
||||||
Ref: cell,
|
|
||||||
AuthorID: authorID,
|
AuthorID: authorID,
|
||||||
Text: xlsxText{
|
Text: xlsxText{R: []xlsxR{}},
|
||||||
R: []xlsxR{
|
}
|
||||||
{
|
if comment.Text != "" {
|
||||||
RPr: &xlsxRPr{
|
if len(comment.Text) > TotalCellChars {
|
||||||
B: &bold,
|
comment.Text = comment.Text[:TotalCellChars]
|
||||||
Sz: &attrValFloat{Val: float64Ptr(9)},
|
}
|
||||||
Color: &xlsxColor{
|
cmt.Text.T = stringPtr(comment.Text)
|
||||||
Indexed: 81,
|
chars += len(comment.Text)
|
||||||
},
|
}
|
||||||
RFont: &attrValString{Val: stringPtr(defaultFont)},
|
for _, run := range comment.Runs {
|
||||||
Family: &attrValInt{Val: intPtr(2)},
|
if chars == TotalCellChars {
|
||||||
},
|
break
|
||||||
T: &xlsxT{Val: a},
|
}
|
||||||
},
|
if chars+len(run.Text) > TotalCellChars {
|
||||||
{
|
run.Text = run.Text[:TotalCellChars-chars]
|
||||||
|
}
|
||||||
|
chars += len(run.Text)
|
||||||
|
r := xlsxR{
|
||||||
RPr: &xlsxRPr{
|
RPr: &xlsxRPr{
|
||||||
Sz: &attrValFloat{Val: float64Ptr(9)},
|
Sz: &attrValFloat{Val: float64Ptr(9)},
|
||||||
Color: &xlsxColor{
|
Color: &xlsxColor{
|
||||||
|
@ -326,10 +318,15 @@ func (f *File) addComment(commentsXML, cell string, opts *commentOptions) {
|
||||||
RFont: &attrValString{Val: stringPtr(defaultFont)},
|
RFont: &attrValString{Val: stringPtr(defaultFont)},
|
||||||
Family: &attrValInt{Val: intPtr(2)},
|
Family: &attrValInt{Val: intPtr(2)},
|
||||||
},
|
},
|
||||||
T: &xlsxT{Val: t},
|
T: &xlsxT{Val: run.Text, Space: xml.Attr{
|
||||||
},
|
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
|
||||||
},
|
Value: "preserve",
|
||||||
},
|
}},
|
||||||
|
}
|
||||||
|
if run.Font != nil {
|
||||||
|
r.RPr = newRpr(run.Font)
|
||||||
|
}
|
||||||
|
cmt.Text.R = append(cmt.Text.R, r)
|
||||||
}
|
}
|
||||||
comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
|
comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
|
||||||
f.Comments[commentsXML] = comments
|
f.Comments[commentsXML] = comments
|
||||||
|
|
|
@ -26,14 +26,14 @@ func TestAddComments(t *testing.T) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
s := strings.Repeat("c", 32768)
|
s := strings.Repeat("c", TotalCellChars+1)
|
||||||
assert.NoError(t, f.AddComment("Sheet1", "A30", `{"author":"`+s+`","text":"`+s+`"}`))
|
assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A30", Author: s, Text: s, Runs: []RichTextRun{{Text: s}, {Text: s}}}))
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "B7", `{"author":"Excelize: ","text":"This is a comment."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "B7", Author: "Excelize", Text: s[:TotalCellChars-1], Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}))
|
||||||
|
|
||||||
// Test add comment on not exists worksheet.
|
// Test add comment on not exists worksheet.
|
||||||
assert.EqualError(t, f.AddComment("SheetN", "B7", `{"author":"Excelize: ","text":"This is a comment."}`), "sheet SheetN does not exist")
|
assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist")
|
||||||
// Test add comment on with illegal cell reference
|
// Test add comment on with illegal cell reference
|
||||||
assert.EqualError(t, f.AddComment("Sheet1", "A", `{"author":"Excelize: ","text":"This is a comment."}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
assert.EqualError(t, f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
||||||
if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) {
|
if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) {
|
||||||
assert.Len(t, f.GetComments(), 2)
|
assert.Len(t, f.GetComments(), 2)
|
||||||
}
|
}
|
||||||
|
@ -52,12 +52,12 @@ func TestDeleteComment(t *testing.T) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "A40", `{"author":"Excelize: ","text":"This is a comment1."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "A40", Text: "Excelize: This is a comment1."}))
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "A41", `{"author":"Excelize: ","text":"This is a comment2."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "A41", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment2."}}}))
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "C41", `{"author":"Excelize: ","text":"This is a comment3."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C41", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment3."}}}))
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "C41", `{"author":"Excelize: ","text":"This is a comment3-1."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C41", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment3-1."}}}))
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "C42", `{"author":"Excelize: ","text":"This is a comment4."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C42", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment4."}}}))
|
||||||
assert.NoError(t, f.AddComment("Sheet2", "C41", `{"author":"Excelize: ","text":"This is a comment3-2."}`))
|
assert.NoError(t, f.AddComment("Sheet2", Comment{Cell: "C41", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment2."}}}))
|
||||||
|
|
||||||
assert.NoError(t, f.DeleteComment("Sheet2", "A40"))
|
assert.NoError(t, f.DeleteComment("Sheet2", "A40"))
|
||||||
|
|
||||||
|
|
|
@ -946,8 +946,7 @@ func TestSetDeleteSheet(t *testing.T) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
f.DeleteSheet("Sheet1")
|
f.DeleteSheet("Sheet1")
|
||||||
assert.EqualError(t, f.AddComment("Sheet1", "A1", ""), "unexpected end of JSON input")
|
assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A1", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}))
|
||||||
assert.NoError(t, f.AddComment("Sheet1", "A1", `{"author":"Excelize: ","text":"This is a comment."}`))
|
|
||||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook4.xlsx")))
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook4.xlsx")))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
23
picture.go
23
picture.go
|
@ -183,7 +183,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, fi
|
||||||
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, options.Hyperlink, hyperlinkType)
|
drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, options.Hyperlink, hyperlinkType)
|
||||||
}
|
}
|
||||||
ws.Unlock()
|
ws.Unlock()
|
||||||
err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, options)
|
err = f.addDrawingPicture(sheet, drawingXML, cell, name, ext, drawingRID, drawingHyperlinkRID, img, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -263,11 +263,12 @@ func (f *File) countDrawings() (count int) {
|
||||||
// addDrawingPicture provides a function to add picture by given sheet,
|
// addDrawingPicture provides a function to add picture by given sheet,
|
||||||
// drawingXML, cell, file name, width, height relationship index and format
|
// drawingXML, cell, file name, width, height relationship index and format
|
||||||
// sets.
|
// sets.
|
||||||
func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, height, rID, hyperlinkRID int, opts *pictureOptions) error {
|
func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, hyperlinkRID int, img image.Config, opts *pictureOptions) error {
|
||||||
col, row, err := CellNameToCoordinates(cell)
|
col, row, err := CellNameToCoordinates(cell)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
width, height := img.Width, img.Height
|
||||||
if opts.Autofit {
|
if opts.Autofit {
|
||||||
width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts)
|
width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -308,6 +309,19 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
|
||||||
}
|
}
|
||||||
pic.BlipFill.Blip.R = SourceRelationship.Value
|
pic.BlipFill.Blip.R = SourceRelationship.Value
|
||||||
pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
|
pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
|
||||||
|
if ext == ".svg" {
|
||||||
|
pic.BlipFill.Blip.ExtList = &xlsxEGOfficeArtExtensionList{
|
||||||
|
Ext: []xlsxCTOfficeArtExtension{
|
||||||
|
{
|
||||||
|
URI: ExtURISVG,
|
||||||
|
SVGBlip: xlsxCTSVGBlip{
|
||||||
|
XMLNSaAVG: NameSpaceDrawing2016SVG.Value,
|
||||||
|
Embed: pic.BlipFill.Blip.Embed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
pic.SpPr.PrstGeom.Prst = "rect"
|
pic.SpPr.PrstGeom.Prst = "rect"
|
||||||
|
|
||||||
twoCellAnchor.Pic = &pic
|
twoCellAnchor.Pic = &pic
|
||||||
|
@ -362,7 +376,10 @@ func (f *File) addMedia(file []byte, ext string) string {
|
||||||
// setContentTypePartImageExtensions provides a function to set the content
|
// setContentTypePartImageExtensions provides a function to set the content
|
||||||
// type for relationship parts and the Main Document part.
|
// type for relationship parts and the Main Document part.
|
||||||
func (f *File) setContentTypePartImageExtensions() {
|
func (f *File) setContentTypePartImageExtensions() {
|
||||||
imageTypes := map[string]string{"jpeg": "image/", "png": "image/", "gif": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-", "emz": "image/x-", "wmz": "image/x-"}
|
imageTypes := map[string]string{
|
||||||
|
"jpeg": "image/", "png": "image/", "gif": "image/", "svg": "image/", "tiff": "image/",
|
||||||
|
"emf": "image/x-", "wmf": "image/x-", "emz": "image/x-", "wmz": "image/x-",
|
||||||
|
}
|
||||||
content := f.contentTypesReader()
|
content := f.contentTypesReader()
|
||||||
content.Lock()
|
content.Lock()
|
||||||
defer content.Unlock()
|
defer content.Unlock()
|
||||||
|
|
|
@ -90,10 +90,12 @@ func TestAddPictureErrors(t *testing.T) {
|
||||||
image.RegisterFormat("wmf", "", decode, decodeConfig)
|
image.RegisterFormat("wmf", "", decode, decodeConfig)
|
||||||
image.RegisterFormat("emz", "", decode, decodeConfig)
|
image.RegisterFormat("emz", "", decode, decodeConfig)
|
||||||
image.RegisterFormat("wmz", "", decode, decodeConfig)
|
image.RegisterFormat("wmz", "", decode, decodeConfig)
|
||||||
|
image.RegisterFormat("svg", "", decode, decodeConfig)
|
||||||
assert.NoError(t, f.AddPicture("Sheet1", "Q1", filepath.Join("test", "images", "excel.emf"), ""))
|
assert.NoError(t, f.AddPicture("Sheet1", "Q1", filepath.Join("test", "images", "excel.emf"), ""))
|
||||||
assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), ""))
|
assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), ""))
|
||||||
assert.NoError(t, f.AddPicture("Sheet1", "Q13", filepath.Join("test", "images", "excel.emz"), ""))
|
assert.NoError(t, f.AddPicture("Sheet1", "Q13", filepath.Join("test", "images", "excel.emz"), ""))
|
||||||
assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), ""))
|
assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), ""))
|
||||||
|
assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", `{"x_scale": 2.1}`))
|
||||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx")))
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx")))
|
||||||
assert.NoError(t, f.Close())
|
assert.NoError(t, f.Close())
|
||||||
}
|
}
|
||||||
|
@ -175,7 +177,7 @@ func TestGetPicture(t *testing.T) {
|
||||||
func TestAddDrawingPicture(t *testing.T) {
|
func TestAddDrawingPicture(t *testing.T) {
|
||||||
// Test addDrawingPicture with illegal cell reference.
|
// Test addDrawingPicture with illegal cell reference.
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, 0, 0, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddPictureFromBytes(t *testing.T) {
|
func TestAddPictureFromBytes(t *testing.T) {
|
||||||
|
|
|
@ -72,16 +72,11 @@ type xlsxPhoneticRun struct {
|
||||||
T string `xml:"t"`
|
T string `xml:"t"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// commentOptions directly maps the format settings of the comment.
|
|
||||||
type commentOptions struct {
|
|
||||||
Author string `json:"author"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comment directly maps the comment information.
|
// Comment directly maps the comment information.
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
AuthorID int `json:"author_id"`
|
AuthorID int `json:"author_id"`
|
||||||
Ref string `json:"ref"`
|
Cell string `json:"cell"`
|
||||||
Text string `json:"text"`
|
Text string `json:"string"`
|
||||||
|
Runs []RichTextRun `json:"runs"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ var (
|
||||||
NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"}
|
NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"}
|
||||||
NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"}
|
NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"}
|
||||||
NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"}
|
NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"}
|
||||||
|
NameSpaceDrawing2016SVG = xml.Attr{Name: xml.Name{Local: "asvg", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"}
|
||||||
NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"}
|
NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"}
|
||||||
NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"}
|
NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"}
|
||||||
NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"}
|
NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"}
|
||||||
|
@ -95,6 +96,7 @@ const (
|
||||||
ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}"
|
ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}"
|
||||||
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
|
ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}"
|
||||||
ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}"
|
ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}"
|
||||||
|
ExtURISVG = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Excel specifications and limits
|
// Excel specifications and limits
|
||||||
|
@ -163,7 +165,11 @@ var IndexedColorMapping = []string{
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportedImageTypes defined supported image types.
|
// supportedImageTypes defined supported image types.
|
||||||
var supportedImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff", ".emf": ".emf", ".wmf": ".wmf", ".emz": ".emz", ".wmz": ".wmz"}
|
var supportedImageTypes = map[string]string{
|
||||||
|
".emf": ".emf", ".emz": ".emz", ".gif": ".gif", ".jpeg": ".jpeg",
|
||||||
|
".jpg": ".jpeg", ".png": ".png", ".svg": ".svg", ".tif": ".tiff",
|
||||||
|
".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz",
|
||||||
|
}
|
||||||
|
|
||||||
// supportedContentTypes defined supported file format types.
|
// supportedContentTypes defined supported file format types.
|
||||||
var supportedContentTypes = map[string]string{
|
var supportedContentTypes = map[string]string{
|
||||||
|
@ -234,6 +240,7 @@ type xlsxBlip struct {
|
||||||
Embed string `xml:"r:embed,attr"`
|
Embed string `xml:"r:embed,attr"`
|
||||||
Cstate string `xml:"cstate,attr,omitempty"`
|
Cstate string `xml:"cstate,attr,omitempty"`
|
||||||
R string `xml:"xmlns:r,attr"`
|
R string `xml:"xmlns:r,attr"`
|
||||||
|
ExtList *xlsxEGOfficeArtExtensionList `xml:"a:extLst"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxStretch directly maps the stretch element. This element specifies that a
|
// xlsxStretch directly maps the stretch element. This element specifies that a
|
||||||
|
@ -293,6 +300,28 @@ type xlsxNvPicPr struct {
|
||||||
CNvPicPr xlsxCNvPicPr `xml:"xdr:cNvPicPr"`
|
CNvPicPr xlsxCNvPicPr `xml:"xdr:cNvPicPr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// xlsxCTSVGBlip specifies a graphic element in Scalable Vector Graphics (SVG)
|
||||||
|
// format.
|
||||||
|
type xlsxCTSVGBlip struct {
|
||||||
|
XMLNSaAVG string `xml:"xmlns:asvg,attr"`
|
||||||
|
Embed string `xml:"r:embed,attr"`
|
||||||
|
Link string `xml:"r:link,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// xlsxCTOfficeArtExtension used for future extensibility and is seen elsewhere
|
||||||
|
// throughout the drawing area.
|
||||||
|
type xlsxCTOfficeArtExtension struct {
|
||||||
|
XMLName xml.Name `xml:"a:ext"`
|
||||||
|
URI string `xml:"uri,attr"`
|
||||||
|
SVGBlip xlsxCTSVGBlip `xml:"asvg:svgBlip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// xlsxEGOfficeArtExtensionList used for future extensibility and is seen
|
||||||
|
// elsewhere throughout the drawing area.
|
||||||
|
type xlsxEGOfficeArtExtensionList struct {
|
||||||
|
Ext []xlsxCTOfficeArtExtension `xml:"ext"`
|
||||||
|
}
|
||||||
|
|
||||||
// xlsxBlipFill directly maps the blipFill (Picture Fill). This element
|
// xlsxBlipFill directly maps the blipFill (Picture Fill). This element
|
||||||
// specifies the kind of picture fill that the picture object has. Because a
|
// specifies the kind of picture fill that the picture object has. Because a
|
||||||
// picture has a picture fill already by default, it is possible to have two
|
// picture has a picture fill already by default, it is possible to have two
|
||||||
|
|
|
@ -83,6 +83,6 @@ type xlsxRPr struct {
|
||||||
|
|
||||||
// RichTextRun directly maps the settings of the rich text run.
|
// RichTextRun directly maps the settings of the rich text run.
|
||||||
type RichTextRun struct {
|
type RichTextRun struct {
|
||||||
Font *Font
|
Font *Font `json:"font"`
|
||||||
Text string
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue