mirror of
https://github.com/fumiama/go-docx.git
synced 2026-06-04 23:30:25 +08:00
add AddShape api
This commit is contained in:
@@ -35,9 +35,9 @@ func (p *Paragraph) AddInlineDrawing(pic []byte) (*Run, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idn := int(atomic.AddUintptr(&p.file.imageID, 1))
|
||||
id := strconv.Itoa(idn)
|
||||
rid := p.file.addImage(Media{Name: "image" + id + "." + format, Data: pic})
|
||||
idn := int(atomic.AddUintptr(&p.file.docID, 1))
|
||||
id := strconv.Itoa(int(p.file.IncreaseID("图片")))
|
||||
rid := p.file.addImage(format, pic)
|
||||
w, h := int64(sz.Width), int64(sz.Height)
|
||||
if float64(w)/float64(h) > 1.2 {
|
||||
h = A4_EMU_MAX_WIDTH * h / w
|
||||
@@ -61,7 +61,7 @@ func (p *Paragraph) AddInlineDrawing(pic []byte) (*Run, error) {
|
||||
Name: "图片 " + id,
|
||||
},
|
||||
CNvGraphicFramePr: &WPCNvGraphicFramePr{
|
||||
Locks: &AGraphicFrameLocks{
|
||||
Locks: AGraphicFrameLocks{
|
||||
NoChangeAspect: 1,
|
||||
},
|
||||
},
|
||||
@@ -136,9 +136,9 @@ func (p *Paragraph) AddAnchorDrawing(pic []byte) (*Run, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idn := int(atomic.AddUintptr(&p.file.imageID, 1))
|
||||
id := strconv.Itoa(idn)
|
||||
rid := p.file.addImage(Media{Name: "image" + id + "." + format, Data: pic})
|
||||
idn := int(atomic.AddUintptr(&p.file.docID, 1))
|
||||
id := strconv.Itoa(int(p.file.IncreaseID("图片")))
|
||||
rid := p.file.addImage(format, pic)
|
||||
w, h := int64(sz.Width), int64(sz.Height)
|
||||
if float64(w)/float64(h) > 1.2 {
|
||||
h = A4_EMU_MAX_WIDTH * h / w
|
||||
@@ -171,7 +171,7 @@ func (p *Paragraph) AddAnchorDrawing(pic []byte) (*Run, error) {
|
||||
Name: "图片 " + id,
|
||||
},
|
||||
CNvGraphicFramePr: &WPCNvGraphicFramePr{
|
||||
Locks: &AGraphicFrameLocks{
|
||||
Locks: AGraphicFrameLocks{
|
||||
NoChangeAspect: 1,
|
||||
},
|
||||
},
|
||||
|
||||
73
apishape.go
Normal file
73
apishape.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package docx
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// AddShape adds wsp named drawing to paragraph
|
||||
func (p *Paragraph) AddShape(w, h int64, name, bwMode, prst string, elems []interface{}) (*Run, error) {
|
||||
idn := int(atomic.AddUintptr(&p.file.docID, 1))
|
||||
id := strconv.Itoa(int(p.file.IncreaseID(name)))
|
||||
d := &Drawing{
|
||||
Anchor: &WPAnchor{
|
||||
LayoutInCell: 1,
|
||||
AllowOverlap: 1,
|
||||
|
||||
SimplePosXY: &WPSimplePos{},
|
||||
PositionH: &WPPositionH{
|
||||
RelativeFrom: "column",
|
||||
},
|
||||
PositionV: &WPPositionV{
|
||||
RelativeFrom: "paragraph",
|
||||
},
|
||||
|
||||
Extent: &WPExtent{
|
||||
CX: w,
|
||||
CY: h,
|
||||
},
|
||||
EffectExtent: &WPEffectExtent{},
|
||||
WrapNone: &struct{}{},
|
||||
DocPr: &WPDocPr{
|
||||
ID: idn,
|
||||
Name: name + " " + id,
|
||||
},
|
||||
CNvGraphicFramePr: &WPCNvGraphicFramePr{},
|
||||
Graphic: &AGraphic{
|
||||
XMLA: XMLNS_DRAWINGML_MAIN,
|
||||
GraphicData: &AGraphicData{
|
||||
URI: XMLNS_WPS,
|
||||
Shape: &WPSWordprocessingShape{
|
||||
CNvCnPr: &WPSCNvCnPr{
|
||||
ConnShapeLocks: &struct{}{},
|
||||
},
|
||||
SpPr: &WPSSpPr{
|
||||
BWMode: bwMode,
|
||||
|
||||
Xfrm: AXfrm{
|
||||
Ext: AExt{
|
||||
CX: w,
|
||||
CY: h,
|
||||
},
|
||||
},
|
||||
PrstGeom: APrstGeom{
|
||||
Prst: prst,
|
||||
},
|
||||
NoFill: &struct{}{},
|
||||
Elems: elems,
|
||||
},
|
||||
BodyPr: &WPSBodyPr{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := make([]interface{}, 1, 64)
|
||||
c[0] = d
|
||||
run := &Run{
|
||||
RunProperties: &RunProperties{},
|
||||
Children: c,
|
||||
}
|
||||
p.Children = append(p.Children, run)
|
||||
return run, nil
|
||||
}
|
||||
@@ -123,6 +123,18 @@ func main() {
|
||||
}
|
||||
tbl2.TableRows[0].TableCells[0].Shade("clear", "auto", "E7E6E6")
|
||||
|
||||
p := w.AddParagraph().Justification("center")
|
||||
p.AddText("测试 AutoShape w:ln").Size("44")
|
||||
p.AddShape(808355, 238760, "AutoShape", "auto", "straightConnector1", []interface{}{
|
||||
&docx.ALine{
|
||||
W: 9525,
|
||||
SolidFill: &docx.ASolidFill{SrgbClr: &docx.ASrgbClr{Val: "000000"}},
|
||||
Round: &struct{}{},
|
||||
HeadEnd: &struct{}{},
|
||||
TailEnd: &struct{}{},
|
||||
},
|
||||
})
|
||||
|
||||
f, err := os.Create(*fileLocation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
8
docx.go
8
docx.go
@@ -27,6 +27,7 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Docx is the structure that allow to access the internal represntation
|
||||
@@ -39,8 +40,11 @@ type Docx struct {
|
||||
media []Media
|
||||
mediaNameIdx map[string]int
|
||||
|
||||
rID uintptr
|
||||
imageID uintptr
|
||||
rID uintptr
|
||||
imageID uintptr
|
||||
docID uintptr
|
||||
slowIDs map[string]uintptr
|
||||
slowIDsMu sync.Mutex
|
||||
|
||||
template string
|
||||
tmplfs fs.FS
|
||||
|
||||
1
empty.go
1
empty.go
@@ -74,6 +74,7 @@ func newEmptyA4File() *Docx {
|
||||
media: make([]Media, 0, 64),
|
||||
mediaNameIdx: make(map[string]int, 64),
|
||||
rID: 5,
|
||||
slowIDs: make(map[string]uintptr, 64),
|
||||
template: "a4",
|
||||
tmpfslst: []string{
|
||||
"_rels/.rels",
|
||||
|
||||
10
id.go
Normal file
10
id.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package docx
|
||||
|
||||
func (f *Docx) IncreaseID(name string) (n uintptr) {
|
||||
f.slowIDsMu.Lock()
|
||||
n, _ = f.slowIDs[name] //nolint: go-staticcheck
|
||||
n++
|
||||
f.slowIDs[name] = n
|
||||
f.slowIDsMu.Unlock()
|
||||
return
|
||||
}
|
||||
8
image.go
8
image.go
@@ -20,8 +20,14 @@
|
||||
|
||||
package docx
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// addImage add image to docx and return its rId
|
||||
func (f *Docx) addImage(m Media) string {
|
||||
func (f *Docx) addImage(format string, data []byte) string {
|
||||
m := Media{Name: "image" + strconv.Itoa(int(atomic.AddUintptr(&f.imageID, 1))) + "." + format, Data: data}
|
||||
f.addMedia(m)
|
||||
return f.addImageRelation(m)
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ func (r *WPDocPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
// WPCNvGraphicFramePr represents the non-visual properties of a graphic frame.
|
||||
type WPCNvGraphicFramePr struct {
|
||||
XMLName xml.Name `xml:"wp:cNvGraphicFramePr,omitempty"`
|
||||
Locks *AGraphicFrameLocks
|
||||
Locks AGraphicFrameLocks
|
||||
}
|
||||
|
||||
// UnmarshalXML ...
|
||||
@@ -329,20 +329,14 @@ func (w *WPCNvGraphicFramePr) UnmarshalXML(d *xml.Decoder, start xml.StartElemen
|
||||
if tt, ok := t.(xml.StartElement); ok {
|
||||
switch tt.Name.Local {
|
||||
case "graphicFrameLocks":
|
||||
var value AGraphicFrameLocks
|
||||
err = d.DecodeElement(&value, &tt)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "expected") {
|
||||
return err
|
||||
}
|
||||
v := getAtt(tt.Attr, "noChangeAspect")
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
value.NoChangeAspect, err = strconv.Atoi(v)
|
||||
w.Locks.NoChangeAspect, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Locks = &value
|
||||
default:
|
||||
err = d.Skip() // skip unsupported tags
|
||||
if err != nil {
|
||||
@@ -358,7 +352,7 @@ func (w *WPCNvGraphicFramePr) UnmarshalXML(d *xml.Decoder, start xml.StartElemen
|
||||
// AGraphicFrameLocks represents the locks applied to a graphic frame.
|
||||
type AGraphicFrameLocks struct {
|
||||
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main graphicFrameLocks,omitempty"`
|
||||
NoChangeAspect int `xml:"noChangeAspect,attr"`
|
||||
NoChangeAspect int `xml:"noChangeAspect,attr,omitempty"`
|
||||
}
|
||||
|
||||
// AGraphic represents a graphic in a Word document.
|
||||
@@ -414,6 +408,7 @@ type AGraphicData struct {
|
||||
XMLName xml.Name `xml:"a:graphicData,omitempty"`
|
||||
URI string `xml:"uri,attr"`
|
||||
Pic *PICPic
|
||||
Shape *WPSWordprocessingShape
|
||||
}
|
||||
|
||||
// UnmarshalXML ...
|
||||
@@ -437,6 +432,13 @@ func (a *AGraphicData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
|
||||
}
|
||||
value.XMLPIC = getAtt(tt.Attr, "pic")
|
||||
a.Pic = &value
|
||||
case "wsp":
|
||||
var value WPSWordprocessingShape
|
||||
err = d.DecodeElement(&value, &tt)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "expected") {
|
||||
return err
|
||||
}
|
||||
a.Shape = &value
|
||||
default:
|
||||
err = d.Skip() // skip unsupported tags
|
||||
if err != nil {
|
||||
|
||||
@@ -98,7 +98,7 @@ type WPSSpPr struct {
|
||||
Xfrm AXfrm
|
||||
PrstGeom APrstGeom
|
||||
NoFill *struct{} `xml:"a:noFill,omitempty"`
|
||||
Ln *ALine
|
||||
Elems []interface{}
|
||||
}
|
||||
|
||||
// UnmarshalXML ...
|
||||
@@ -132,11 +132,12 @@ func (w *WPSSpPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
case "noFill":
|
||||
w.NoFill = &struct{}{}
|
||||
case "ln":
|
||||
w.Ln = &ALine{}
|
||||
err = d.DecodeElement(&w.Ln, &tt)
|
||||
var ln ALine
|
||||
err = d.DecodeElement(&ln, &tt)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "expected") {
|
||||
return err
|
||||
}
|
||||
w.Elems = append(w.Elems, &ln)
|
||||
default:
|
||||
err = d.Skip() // skip unsupported tags
|
||||
if err != nil {
|
||||
|
||||
77
structshape_test.go
Normal file
77
structshape_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package docx
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShapeStructure(t *testing.T) {
|
||||
w := NewA4()
|
||||
// add new paragraph
|
||||
para1 := w.AddParagraph()
|
||||
// add text
|
||||
para1.AddText("test shape")
|
||||
para1.AddShape(808355, 238760, "AutoShape", "auto", "straightConnector1", []interface{}{
|
||||
&ALine{
|
||||
W: 9525,
|
||||
SolidFill: &ASolidFill{SrgbClr: &ASrgbClr{Val: "000000"}},
|
||||
Round: &struct{}{},
|
||||
HeadEnd: &struct{}{},
|
||||
TailEnd: &struct{}{},
|
||||
},
|
||||
})
|
||||
|
||||
f, err := os.Create("TestMarshalShapeStructure.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = marshaller{data: &w.Document}.WriteTo(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = NewA4()
|
||||
err = xml.NewDecoder(f).Decode(&w.Document)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f1, err := os.Create("TestUnmarshalShapeStructure.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f1.Close()
|
||||
_, err = marshaller{data: &w.Document}.WriteTo(f1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = f1.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
_, err = io.Copy(h, f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
crc1 := h.Sum64()
|
||||
h.Reset()
|
||||
_, err = io.Copy(h, f1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
crc2 := h.Sum64()
|
||||
if crc1 != crc2 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
func unpack(zipReader *zip.Reader) (docx *Docx, err error) {
|
||||
docx = new(Docx)
|
||||
docx.mediaNameIdx = make(map[string]int, 64)
|
||||
docx.slowIDs = make(map[string]uintptr, 64)
|
||||
docx.tmplfs = zipReader
|
||||
docx.tmpfslst = make([]string, 0, 64)
|
||||
for _, f := range zipReader.File {
|
||||
@@ -88,6 +89,7 @@ func (f *Docx) parseDocument(file *zip.File) error {
|
||||
f.Document.XMLName.Local = "document"
|
||||
|
||||
f.Document.Body.file = f
|
||||
//TODO: find last docID
|
||||
err = xml.NewDecoder(zf).Decode(&f.Document)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user