picture.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. // Copyright 2016 - 2023 The excelize Authors. All rights reserved. Use of
  2. // this source code is governed by a BSD-style license that can be found in
  3. // the LICENSE file.
  4. //
  5. // Package excelize providing a set of functions that allow you to write to and
  6. // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
  7. // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
  8. // Supports complex components by high compatibility, and provided streaming
  9. // API for generating or reading data from a worksheet with huge amounts of
  10. // data. This library needs Go version 1.16 or later.
  11. package excelize
  12. import (
  13. "bytes"
  14. "encoding/xml"
  15. "image"
  16. "io"
  17. "os"
  18. "path"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. )
  23. // parseGraphicOptions provides a function to parse the format settings of
  24. // the picture with default value.
  25. func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions {
  26. if opts == nil {
  27. return &GraphicOptions{
  28. PrintObject: boolPtr(true),
  29. Locked: boolPtr(true),
  30. ScaleX: defaultPictureScale,
  31. ScaleY: defaultPictureScale,
  32. }
  33. }
  34. if opts.PrintObject == nil {
  35. opts.PrintObject = boolPtr(true)
  36. }
  37. if opts.Locked == nil {
  38. opts.Locked = boolPtr(true)
  39. }
  40. if opts.ScaleX == 0 {
  41. opts.ScaleX = defaultPictureScale
  42. }
  43. if opts.ScaleY == 0 {
  44. opts.ScaleY = defaultPictureScale
  45. }
  46. return opts
  47. }
  48. // AddPicture provides the method to add picture in a sheet by given picture
  49. // format set (such as offset, scale, aspect ratio setting and print settings)
  50. // and file path, supported image types: BMP, EMF, EMZ, GIF, JPEG, JPG, PNG,
  51. // SVG, TIF, TIFF, WMF, and WMZ. This function is concurrency safe. For example:
  52. //
  53. // package main
  54. //
  55. // import (
  56. // "fmt"
  57. // _ "image/gif"
  58. // _ "image/jpeg"
  59. // _ "image/png"
  60. //
  61. // "github.com/xuri/excelize/v2"
  62. // )
  63. //
  64. // func main() {
  65. // f := excelize.NewFile()
  66. // defer func() {
  67. // if err := f.Close(); err != nil {
  68. // fmt.Println(err)
  69. // }
  70. // }()
  71. // // Insert a picture.
  72. // if err := f.AddPicture("Sheet1", "A2", "image.jpg", nil); err != nil {
  73. // fmt.Println(err)
  74. // return
  75. // }
  76. // // Insert a picture scaling in the cell with location hyperlink.
  77. // enable := true
  78. // if err := f.AddPicture("Sheet1", "D2", "image.png",
  79. // &excelize.GraphicOptions{
  80. // ScaleX: 0.5,
  81. // ScaleY: 0.5,
  82. // Hyperlink: "#Sheet2!D8",
  83. // HyperlinkType: "Location",
  84. // },
  85. // ); err != nil {
  86. // fmt.Println(err)
  87. // return
  88. // }
  89. // // Insert a picture offset in the cell with external hyperlink, printing and positioning support.
  90. // if err := f.AddPicture("Sheet1", "H2", "image.gif",
  91. // &excelize.GraphicOptions{
  92. // PrintObject: &enable,
  93. // LockAspectRatio: false,
  94. // OffsetX: 15,
  95. // OffsetY: 10,
  96. // Hyperlink: "https://github.com/xuri/excelize",
  97. // HyperlinkType: "External",
  98. // Positioning: "oneCell",
  99. // },
  100. // ); err != nil {
  101. // fmt.Println(err)
  102. // return
  103. // }
  104. // if err := f.SaveAs("Book1.xlsx"); err != nil {
  105. // fmt.Println(err)
  106. // }
  107. // }
  108. //
  109. // The optional parameter "AutoFit" specifies if you make image size auto-fits the
  110. // cell, the default value of that is 'false'.
  111. //
  112. // The optional parameter "Hyperlink" specifies the hyperlink of the image.
  113. //
  114. // The optional parameter "HyperlinkType" defines two types of
  115. // hyperlink "External" for website or "Location" for moving to one of the
  116. // cells in this workbook. When the "HyperlinkType" is "Location",
  117. // coordinates need to start with "#".
  118. //
  119. // The optional parameter "Positioning" defines two types of the position of an
  120. // image in an Excel spreadsheet, "oneCell" (Move but don't size with
  121. // cells) or "absolute" (Don't move or size with cells). If you don't set this
  122. // parameter, the default positioning is move and size with cells.
  123. //
  124. // The optional parameter "PrintObject" indicates whether the image is printed
  125. // when the worksheet is printed, the default value of that is 'true'.
  126. //
  127. // The optional parameter "LockAspectRatio" indicates whether lock aspect
  128. // ratio for the image, the default value of that is 'false'.
  129. //
  130. // The optional parameter "Locked" indicates whether lock the image. Locking
  131. // an object has no effect unless the sheet is protected.
  132. //
  133. // The optional parameter "OffsetX" specifies the horizontal offset of the
  134. // image with the cell, the default value of that is 0.
  135. //
  136. // The optional parameter "ScaleX" specifies the horizontal scale of images,
  137. // the default value of that is 1.0 which presents 100%.
  138. //
  139. // The optional parameter "OffsetY" specifies the vertical offset of the
  140. // image with the cell, the default value of that is 0.
  141. //
  142. // The optional parameter "ScaleY" specifies the vertical scale of images,
  143. // the default value of that is 1.0 which presents 100%.
  144. func (f *File) AddPicture(sheet, cell, name string, opts *GraphicOptions) error {
  145. var err error
  146. // Check picture exists first.
  147. if _, err = os.Stat(name); os.IsNotExist(err) {
  148. return err
  149. }
  150. ext, ok := supportedImageTypes[strings.ToLower(path.Ext(name))]
  151. if !ok {
  152. return ErrImgExt
  153. }
  154. file, _ := os.ReadFile(filepath.Clean(name))
  155. return f.AddPictureFromBytes(sheet, cell, &Picture{Extension: ext, File: file, Format: opts})
  156. }
  157. // AddPictureFromBytes provides the method to add picture in a sheet by given
  158. // picture format set (such as offset, scale, aspect ratio setting and print
  159. // settings), file base name, extension name and file bytes, supported image
  160. // types: EMF, EMZ, GIF, JPEG, JPG, PNG, SVG, TIF, TIFF, WMF, and WMZ. For
  161. // example:
  162. //
  163. // package main
  164. //
  165. // import (
  166. // "fmt"
  167. // _ "image/jpeg"
  168. // "os"
  169. //
  170. // "github.com/xuri/excelize/v2"
  171. // )
  172. //
  173. // func main() {
  174. // f := excelize.NewFile()
  175. // defer func() {
  176. // if err := f.Close(); err != nil {
  177. // fmt.Println(err)
  178. // }
  179. // }()
  180. // file, err := os.ReadFile("image.jpg")
  181. // if err != nil {
  182. // fmt.Println(err)
  183. // return
  184. // }
  185. // if err := f.AddPictureFromBytes("Sheet1", "A2", &excelize.Picture{
  186. // Extension: ".jpg",
  187. // File: file,
  188. // Format: &excelize.GraphicOptions{AltText: "Excel Logo"},
  189. // }); err != nil {
  190. // fmt.Println(err)
  191. // return
  192. // }
  193. // if err := f.SaveAs("Book1.xlsx"); err != nil {
  194. // fmt.Println(err)
  195. // }
  196. // }
  197. func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
  198. var drawingHyperlinkRID int
  199. var hyperlinkType string
  200. ext, ok := supportedImageTypes[strings.ToLower(pic.Extension)]
  201. if !ok {
  202. return ErrImgExt
  203. }
  204. options := parseGraphicOptions(pic.Format)
  205. img, _, err := image.DecodeConfig(bytes.NewReader(pic.File))
  206. if err != nil {
  207. return err
  208. }
  209. // Read sheet data.
  210. ws, err := f.workSheetReader(sheet)
  211. if err != nil {
  212. return err
  213. }
  214. ws.Lock()
  215. // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
  216. drawingID := f.countDrawings() + 1
  217. drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
  218. drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
  219. drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
  220. mediaStr := ".." + strings.TrimPrefix(f.addMedia(pic.File, ext), "xl")
  221. drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType)
  222. // Add picture with hyperlink.
  223. if options.Hyperlink != "" && options.HyperlinkType != "" {
  224. if options.HyperlinkType == "External" {
  225. hyperlinkType = options.HyperlinkType
  226. }
  227. drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, options.Hyperlink, hyperlinkType)
  228. }
  229. ws.Unlock()
  230. err = f.addDrawingPicture(sheet, drawingXML, cell, ext, drawingRID, drawingHyperlinkRID, img, options)
  231. if err != nil {
  232. return err
  233. }
  234. if err = f.addContentTypePart(drawingID, "drawings"); err != nil {
  235. return err
  236. }
  237. f.addSheetNameSpace(sheet, SourceRelationship)
  238. return err
  239. }
  240. // deleteSheetRelationships provides a function to delete relationships in
  241. // xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
  242. // relationship index.
  243. func (f *File) deleteSheetRelationships(sheet, rID string) {
  244. name, ok := f.getSheetXMLPath(sheet)
  245. if !ok {
  246. name = strings.ToLower(sheet) + ".xml"
  247. }
  248. rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
  249. sheetRels, _ := f.relsReader(rels)
  250. if sheetRels == nil {
  251. sheetRels = &xlsxRelationships{}
  252. }
  253. sheetRels.Lock()
  254. defer sheetRels.Unlock()
  255. for k, v := range sheetRels.Relationships {
  256. if v.ID == rID {
  257. sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...)
  258. }
  259. }
  260. f.Relationships.Store(rels, sheetRels)
  261. }
  262. // addSheetLegacyDrawing provides a function to add legacy drawing element to
  263. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
  264. func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
  265. ws, _ := f.workSheetReader(sheet)
  266. ws.LegacyDrawing = &xlsxLegacyDrawing{
  267. RID: "rId" + strconv.Itoa(rID),
  268. }
  269. }
  270. // addSheetDrawing provides a function to add drawing element to
  271. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
  272. func (f *File) addSheetDrawing(sheet string, rID int) {
  273. ws, _ := f.workSheetReader(sheet)
  274. ws.Drawing = &xlsxDrawing{
  275. RID: "rId" + strconv.Itoa(rID),
  276. }
  277. }
  278. // addSheetPicture provides a function to add picture element to
  279. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
  280. func (f *File) addSheetPicture(sheet string, rID int) error {
  281. ws, err := f.workSheetReader(sheet)
  282. if err != nil {
  283. return err
  284. }
  285. ws.Picture = &xlsxPicture{
  286. RID: "rId" + strconv.Itoa(rID),
  287. }
  288. return err
  289. }
  290. // countDrawings provides a function to get drawing files count storage in the
  291. // folder xl/drawings.
  292. func (f *File) countDrawings() int {
  293. var c1, c2 int
  294. f.Pkg.Range(func(k, v interface{}) bool {
  295. if strings.Contains(k.(string), "xl/drawings/drawing") {
  296. c1++
  297. }
  298. return true
  299. })
  300. f.Drawings.Range(func(rel, value interface{}) bool {
  301. if strings.Contains(rel.(string), "xl/drawings/drawing") {
  302. c2++
  303. }
  304. return true
  305. })
  306. if c1 < c2 {
  307. return c2
  308. }
  309. return c1
  310. }
  311. // addDrawingPicture provides a function to add picture by given sheet,
  312. // drawingXML, cell, file name, width, height relationship index and format
  313. // sets.
  314. func (f *File) addDrawingPicture(sheet, drawingXML, cell, ext string, rID, hyperlinkRID int, img image.Config, opts *GraphicOptions) error {
  315. col, row, err := CellNameToCoordinates(cell)
  316. if err != nil {
  317. return err
  318. }
  319. width, height := img.Width, img.Height
  320. if opts.AutoFit {
  321. width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts)
  322. if err != nil {
  323. return err
  324. }
  325. } else {
  326. width = int(float64(width) * opts.ScaleX)
  327. height = int(float64(height) * opts.ScaleY)
  328. }
  329. col--
  330. row--
  331. colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height)
  332. content, cNvPrID, err := f.drawingParser(drawingXML)
  333. if err != nil {
  334. return err
  335. }
  336. twoCellAnchor := xdrCellAnchor{}
  337. twoCellAnchor.EditAs = opts.Positioning
  338. from := xlsxFrom{}
  339. from.Col = colStart
  340. from.ColOff = opts.OffsetX * EMU
  341. from.Row = rowStart
  342. from.RowOff = opts.OffsetY * EMU
  343. to := xlsxTo{}
  344. to.Col = colEnd
  345. to.ColOff = x2 * EMU
  346. to.Row = rowEnd
  347. to.RowOff = y2 * EMU
  348. twoCellAnchor.From = &from
  349. twoCellAnchor.To = &to
  350. pic := xlsxPic{}
  351. pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio
  352. pic.NvPicPr.CNvPr.ID = cNvPrID
  353. pic.NvPicPr.CNvPr.Descr = opts.AltText
  354. pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID)
  355. if hyperlinkRID != 0 {
  356. pic.NvPicPr.CNvPr.HlinkClick = &xlsxHlinkClick{
  357. R: SourceRelationship.Value,
  358. RID: "rId" + strconv.Itoa(hyperlinkRID),
  359. }
  360. }
  361. pic.BlipFill.Blip.R = SourceRelationship.Value
  362. pic.BlipFill.Blip.Embed = "rId" + strconv.Itoa(rID)
  363. if ext == ".svg" {
  364. pic.BlipFill.Blip.ExtList = &xlsxEGOfficeArtExtensionList{
  365. Ext: []xlsxCTOfficeArtExtension{
  366. {
  367. URI: ExtURISVG,
  368. SVGBlip: xlsxCTSVGBlip{
  369. XMLNSaAVG: NameSpaceDrawing2016SVG.Value,
  370. Embed: pic.BlipFill.Blip.Embed,
  371. },
  372. },
  373. },
  374. }
  375. }
  376. pic.SpPr.PrstGeom.Prst = "rect"
  377. twoCellAnchor.Pic = &pic
  378. twoCellAnchor.ClientData = &xdrClientData{
  379. FLocksWithSheet: *opts.Locked,
  380. FPrintsWithSheet: *opts.PrintObject,
  381. }
  382. content.Lock()
  383. defer content.Unlock()
  384. content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
  385. f.Drawings.Store(drawingXML, content)
  386. return err
  387. }
  388. // countMedia provides a function to get media files count storage in the
  389. // folder xl/media/image.
  390. func (f *File) countMedia() int {
  391. count := 0
  392. f.Pkg.Range(func(k, v interface{}) bool {
  393. if strings.Contains(k.(string), "xl/media/image") {
  394. count++
  395. }
  396. return true
  397. })
  398. return count
  399. }
  400. // addMedia provides a function to add a picture into folder xl/media/image by
  401. // given file and extension name. Duplicate images are only actually stored once
  402. // and drawings that use it will reference the same image.
  403. func (f *File) addMedia(file []byte, ext string) string {
  404. count := f.countMedia()
  405. var name string
  406. f.Pkg.Range(func(k, existing interface{}) bool {
  407. if !strings.HasPrefix(k.(string), "xl/media/image") {
  408. return true
  409. }
  410. if bytes.Equal(file, existing.([]byte)) {
  411. name = k.(string)
  412. return false
  413. }
  414. return true
  415. })
  416. if name != "" {
  417. return name
  418. }
  419. media := "xl/media/image" + strconv.Itoa(count+1) + ext
  420. f.Pkg.Store(media, file)
  421. return media
  422. }
  423. // setContentTypePartImageExtensions provides a function to set the content
  424. // type for relationship parts and the Main Document part.
  425. func (f *File) setContentTypePartImageExtensions() error {
  426. imageTypes := map[string]string{
  427. "bmp": "image/", "jpeg": "image/", "png": "image/", "gif": "image/",
  428. "svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-",
  429. "emz": "image/x-", "wmz": "image/x-",
  430. }
  431. content, err := f.contentTypesReader()
  432. if err != nil {
  433. return err
  434. }
  435. content.Lock()
  436. defer content.Unlock()
  437. for _, file := range content.Defaults {
  438. delete(imageTypes, file.Extension)
  439. }
  440. for extension, prefix := range imageTypes {
  441. content.Defaults = append(content.Defaults, xlsxDefault{
  442. Extension: extension,
  443. ContentType: prefix + extension,
  444. })
  445. }
  446. return err
  447. }
  448. // setContentTypePartVMLExtensions provides a function to set the content type
  449. // for relationship parts and the Main Document part.
  450. func (f *File) setContentTypePartVMLExtensions() error {
  451. var vml bool
  452. content, err := f.contentTypesReader()
  453. if err != nil {
  454. return err
  455. }
  456. content.Lock()
  457. defer content.Unlock()
  458. for _, v := range content.Defaults {
  459. if v.Extension == "vml" {
  460. vml = true
  461. }
  462. }
  463. if !vml {
  464. content.Defaults = append(content.Defaults, xlsxDefault{
  465. Extension: "vml",
  466. ContentType: ContentTypeVML,
  467. })
  468. }
  469. return err
  470. }
  471. // addContentTypePart provides a function to add content type part
  472. // relationships in the file [Content_Types].xml by given index.
  473. func (f *File) addContentTypePart(index int, contentType string) error {
  474. setContentType := map[string]func() error{
  475. "comments": f.setContentTypePartVMLExtensions,
  476. "drawings": f.setContentTypePartImageExtensions,
  477. }
  478. partNames := map[string]string{
  479. "chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
  480. "chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
  481. "comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
  482. "drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
  483. "table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
  484. "pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
  485. "pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
  486. "sharedStrings": "/xl/sharedStrings.xml",
  487. }
  488. contentTypes := map[string]string{
  489. "chart": ContentTypeDrawingML,
  490. "chartsheet": ContentTypeSpreadSheetMLChartsheet,
  491. "comments": ContentTypeSpreadSheetMLComments,
  492. "drawings": ContentTypeDrawing,
  493. "table": ContentTypeSpreadSheetMLTable,
  494. "pivotTable": ContentTypeSpreadSheetMLPivotTable,
  495. "pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
  496. "sharedStrings": ContentTypeSpreadSheetMLSharedStrings,
  497. }
  498. s, ok := setContentType[contentType]
  499. if ok {
  500. if err := s(); err != nil {
  501. return err
  502. }
  503. }
  504. content, err := f.contentTypesReader()
  505. if err != nil {
  506. return err
  507. }
  508. content.Lock()
  509. defer content.Unlock()
  510. for _, v := range content.Overrides {
  511. if v.PartName == partNames[contentType] {
  512. return err
  513. }
  514. }
  515. content.Overrides = append(content.Overrides, xlsxOverride{
  516. PartName: partNames[contentType],
  517. ContentType: contentTypes[contentType],
  518. })
  519. return err
  520. }
  521. // getSheetRelationshipsTargetByID provides a function to get Target attribute
  522. // value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and
  523. // relationship index.
  524. func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
  525. name, ok := f.getSheetXMLPath(sheet)
  526. if !ok {
  527. name = strings.ToLower(sheet) + ".xml"
  528. }
  529. rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
  530. sheetRels, _ := f.relsReader(rels)
  531. if sheetRels == nil {
  532. sheetRels = &xlsxRelationships{}
  533. }
  534. sheetRels.Lock()
  535. defer sheetRels.Unlock()
  536. for _, v := range sheetRels.Relationships {
  537. if v.ID == rID {
  538. return v.Target
  539. }
  540. }
  541. return ""
  542. }
  543. // GetPictures provides a function to get picture meta info and raw content
  544. // embed in spreadsheet by given worksheet and cell name. This function
  545. // returns the image contents as []byte data types. This function is
  546. // concurrency safe. For example:
  547. //
  548. // f, err := excelize.OpenFile("Book1.xlsx")
  549. // if err != nil {
  550. // fmt.Println(err)
  551. // return
  552. // }
  553. // defer func() {
  554. // if err := f.Close(); err != nil {
  555. // fmt.Println(err)
  556. // }
  557. // }()
  558. // pics, err := f.GetPictures("Sheet1", "A2")
  559. // if err != nil {
  560. // fmt.Println(err)
  561. // }
  562. // for idx, pic := range pics {
  563. // name := fmt.Sprintf("image%d%s", idx+1, pic.Extension)
  564. // if err := os.WriteFile(name, pic.File, 0644); err != nil {
  565. // fmt.Println(err)
  566. // }
  567. // }
  568. func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
  569. col, row, err := CellNameToCoordinates(cell)
  570. if err != nil {
  571. return nil, err
  572. }
  573. col--
  574. row--
  575. ws, err := f.workSheetReader(sheet)
  576. if err != nil {
  577. return nil, err
  578. }
  579. if ws.Drawing == nil {
  580. return nil, err
  581. }
  582. target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
  583. drawingXML := strings.ReplaceAll(target, "..", "xl")
  584. drawingRelationships := strings.ReplaceAll(
  585. strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
  586. return f.getPicture(row, col, drawingXML, drawingRelationships)
  587. }
  588. // DeletePicture provides a function to delete all pictures in a cell by given
  589. // worksheet name and cell reference. Note that the image file won't be deleted
  590. // from the document currently.
  591. func (f *File) DeletePicture(sheet, cell string) error {
  592. col, row, err := CellNameToCoordinates(cell)
  593. if err != nil {
  594. return err
  595. }
  596. col--
  597. row--
  598. ws, err := f.workSheetReader(sheet)
  599. if err != nil {
  600. return err
  601. }
  602. if ws.Drawing == nil {
  603. return err
  604. }
  605. drawingXML := strings.ReplaceAll(f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID), "..", "xl")
  606. return f.deleteDrawing(col, row, drawingXML, "Pic")
  607. }
  608. // getPicture provides a function to get picture base name and raw content
  609. // embed in spreadsheet by given coordinates and drawing relationships.
  610. func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (pics []Picture, err error) {
  611. var (
  612. wsDr *xlsxWsDr
  613. ok bool
  614. deWsDr *decodeWsDr
  615. drawRel *xlsxRelationship
  616. deTwoCellAnchor *decodeTwoCellAnchor
  617. )
  618. if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
  619. return
  620. }
  621. if pics = f.getPicturesFromWsDr(row, col, drawingRelationships, wsDr); len(pics) > 0 {
  622. return
  623. }
  624. deWsDr = new(decodeWsDr)
  625. if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))).
  626. Decode(deWsDr); err != nil && err != io.EOF {
  627. return
  628. }
  629. err = nil
  630. for _, anchor := range deWsDr.TwoCellAnchor {
  631. deTwoCellAnchor = new(decodeTwoCellAnchor)
  632. if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>")).
  633. Decode(deTwoCellAnchor); err != nil && err != io.EOF {
  634. return
  635. }
  636. if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil {
  637. if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row {
  638. drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed)
  639. if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
  640. pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
  641. if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
  642. pic.File = buffer.([]byte)
  643. pic.Format.AltText = deTwoCellAnchor.Pic.NvPicPr.CNvPr.Descr
  644. pics = append(pics, pic)
  645. }
  646. return
  647. }
  648. }
  649. }
  650. }
  651. return
  652. }
  653. // getPicturesFromWsDr provides a function to get picture base name and raw
  654. // content in worksheet drawing by given coordinates and drawing
  655. // relationships.
  656. func (f *File) getPicturesFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (pics []Picture) {
  657. var (
  658. ok bool
  659. anchor *xdrCellAnchor
  660. drawRel *xlsxRelationship
  661. )
  662. wsDr.Lock()
  663. defer wsDr.Unlock()
  664. for _, anchor = range wsDr.TwoCellAnchor {
  665. if anchor.From != nil && anchor.Pic != nil {
  666. if anchor.From.Col == col && anchor.From.Row == row {
  667. if drawRel = f.getDrawingRelationships(drawingRelationships,
  668. anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
  669. if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
  670. pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
  671. if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
  672. pic.File = buffer.([]byte)
  673. pic.Format.AltText = anchor.Pic.NvPicPr.CNvPr.Descr
  674. pics = append(pics, pic)
  675. }
  676. }
  677. }
  678. }
  679. }
  680. }
  681. return
  682. }
  683. // getDrawingRelationships provides a function to get drawing relationships
  684. // from xl/drawings/_rels/drawing%s.xml.rels by given file name and
  685. // relationship ID.
  686. func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship {
  687. if drawingRels, _ := f.relsReader(rels); drawingRels != nil {
  688. drawingRels.Lock()
  689. defer drawingRels.Unlock()
  690. for _, v := range drawingRels.Relationships {
  691. if v.ID == rID {
  692. return &v
  693. }
  694. }
  695. }
  696. return nil
  697. }
  698. // drawingsWriter provides a function to save xl/drawings/drawing%d.xml after
  699. // serialize structure.
  700. func (f *File) drawingsWriter() {
  701. f.Drawings.Range(func(path, d interface{}) bool {
  702. if d != nil {
  703. v, _ := xml.Marshal(d.(*xlsxWsDr))
  704. f.saveFileList(path.(string), v)
  705. }
  706. return true
  707. })
  708. }
  709. // drawingResize calculate the height and width after resizing.
  710. func (f *File) drawingResize(sheet, cell string, width, height float64, opts *GraphicOptions) (w, h, c, r int, err error) {
  711. var mergeCells []MergeCell
  712. mergeCells, err = f.GetMergeCells(sheet)
  713. if err != nil {
  714. return
  715. }
  716. var rng []int
  717. var inMergeCell bool
  718. if c, r, err = CellNameToCoordinates(cell); err != nil {
  719. return
  720. }
  721. cellWidth, cellHeight := f.getColWidth(sheet, c), f.getRowHeight(sheet, r)
  722. for _, mergeCell := range mergeCells {
  723. if inMergeCell {
  724. continue
  725. }
  726. if inMergeCell, err = f.checkCellInRangeRef(cell, mergeCell[0]); err != nil {
  727. return
  728. }
  729. if inMergeCell {
  730. rng, _ = cellRefsToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis())
  731. _ = sortCoordinates(rng)
  732. }
  733. }
  734. if inMergeCell {
  735. cellWidth, cellHeight = 0, 0
  736. c, r = rng[0], rng[1]
  737. for col := rng[0]; col <= rng[2]; col++ {
  738. cellWidth += f.getColWidth(sheet, col)
  739. }
  740. for row := rng[1]; row <= rng[3]; row++ {
  741. cellHeight += f.getRowHeight(sheet, row)
  742. }
  743. }
  744. if float64(cellWidth) < width {
  745. asp := float64(cellWidth) / width
  746. width, height = float64(cellWidth), height*asp
  747. }
  748. if float64(cellHeight) < height {
  749. asp := float64(cellHeight) / height
  750. height, width = float64(cellHeight), width*asp
  751. }
  752. width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY)
  753. w, h = int(width*opts.ScaleX), int(height*opts.ScaleY)
  754. return
  755. }