1
0
mirror of https://github.com/fumiama/go-docx.git synced 2026-06-05 07:40:24 +08:00

add AddShape api

This commit is contained in:
源文雨
2023-02-27 16:12:56 +08:00
parent c8aae913f2
commit d19032acad
11 changed files with 211 additions and 23 deletions

View File

@@ -35,9 +35,9 @@ func (p *Paragraph) AddInlineDrawing(pic []byte) (*Run, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
idn := int(atomic.AddUintptr(&p.file.imageID, 1)) idn := int(atomic.AddUintptr(&p.file.docID, 1))
id := strconv.Itoa(idn) id := strconv.Itoa(int(p.file.IncreaseID("图片")))
rid := p.file.addImage(Media{Name: "image" + id + "." + format, Data: pic}) rid := p.file.addImage(format, pic)
w, h := int64(sz.Width), int64(sz.Height) w, h := int64(sz.Width), int64(sz.Height)
if float64(w)/float64(h) > 1.2 { if float64(w)/float64(h) > 1.2 {
h = A4_EMU_MAX_WIDTH * h / w h = A4_EMU_MAX_WIDTH * h / w
@@ -61,7 +61,7 @@ func (p *Paragraph) AddInlineDrawing(pic []byte) (*Run, error) {
Name: "图片 " + id, Name: "图片 " + id,
}, },
CNvGraphicFramePr: &WPCNvGraphicFramePr{ CNvGraphicFramePr: &WPCNvGraphicFramePr{
Locks: &AGraphicFrameLocks{ Locks: AGraphicFrameLocks{
NoChangeAspect: 1, NoChangeAspect: 1,
}, },
}, },
@@ -136,9 +136,9 @@ func (p *Paragraph) AddAnchorDrawing(pic []byte) (*Run, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
idn := int(atomic.AddUintptr(&p.file.imageID, 1)) idn := int(atomic.AddUintptr(&p.file.docID, 1))
id := strconv.Itoa(idn) id := strconv.Itoa(int(p.file.IncreaseID("图片")))
rid := p.file.addImage(Media{Name: "image" + id + "." + format, Data: pic}) rid := p.file.addImage(format, pic)
w, h := int64(sz.Width), int64(sz.Height) w, h := int64(sz.Width), int64(sz.Height)
if float64(w)/float64(h) > 1.2 { if float64(w)/float64(h) > 1.2 {
h = A4_EMU_MAX_WIDTH * h / w h = A4_EMU_MAX_WIDTH * h / w
@@ -171,7 +171,7 @@ func (p *Paragraph) AddAnchorDrawing(pic []byte) (*Run, error) {
Name: "图片 " + id, Name: "图片 " + id,
}, },
CNvGraphicFramePr: &WPCNvGraphicFramePr{ CNvGraphicFramePr: &WPCNvGraphicFramePr{
Locks: &AGraphicFrameLocks{ Locks: AGraphicFrameLocks{
NoChangeAspect: 1, NoChangeAspect: 1,
}, },
}, },

73
apishape.go Normal file
View 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
}

View File

@@ -123,6 +123,18 @@ func main() {
} }
tbl2.TableRows[0].TableCells[0].Shade("clear", "auto", "E7E6E6") 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) f, err := os.Create(*fileLocation)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@@ -27,6 +27,7 @@ import (
"bytes" "bytes"
"io" "io"
"io/fs" "io/fs"
"sync"
) )
// Docx is the structure that allow to access the internal represntation // Docx is the structure that allow to access the internal represntation
@@ -39,8 +40,11 @@ type Docx struct {
media []Media media []Media
mediaNameIdx map[string]int mediaNameIdx map[string]int
rID uintptr rID uintptr
imageID uintptr imageID uintptr
docID uintptr
slowIDs map[string]uintptr
slowIDsMu sync.Mutex
template string template string
tmplfs fs.FS tmplfs fs.FS

View File

@@ -74,6 +74,7 @@ func newEmptyA4File() *Docx {
media: make([]Media, 0, 64), media: make([]Media, 0, 64),
mediaNameIdx: make(map[string]int, 64), mediaNameIdx: make(map[string]int, 64),
rID: 5, rID: 5,
slowIDs: make(map[string]uintptr, 64),
template: "a4", template: "a4",
tmpfslst: []string{ tmpfslst: []string{
"_rels/.rels", "_rels/.rels",

10
id.go Normal file
View 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
}

View File

@@ -20,8 +20,14 @@
package docx package docx
import (
"strconv"
"sync/atomic"
)
// addImage add image to docx and return its rId // 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) f.addMedia(m)
return f.addImageRelation(m) return f.addImageRelation(m)
} }

View File

@@ -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. // WPCNvGraphicFramePr represents the non-visual properties of a graphic frame.
type WPCNvGraphicFramePr struct { type WPCNvGraphicFramePr struct {
XMLName xml.Name `xml:"wp:cNvGraphicFramePr,omitempty"` XMLName xml.Name `xml:"wp:cNvGraphicFramePr,omitempty"`
Locks *AGraphicFrameLocks Locks AGraphicFrameLocks
} }
// UnmarshalXML ... // UnmarshalXML ...
@@ -329,20 +329,14 @@ func (w *WPCNvGraphicFramePr) UnmarshalXML(d *xml.Decoder, start xml.StartElemen
if tt, ok := t.(xml.StartElement); ok { if tt, ok := t.(xml.StartElement); ok {
switch tt.Name.Local { switch tt.Name.Local {
case "graphicFrameLocks": 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") v := getAtt(tt.Attr, "noChangeAspect")
if v == "" { if v == "" {
continue continue
} }
value.NoChangeAspect, err = strconv.Atoi(v) w.Locks.NoChangeAspect, err = strconv.Atoi(v)
if err != nil { if err != nil {
return err return err
} }
w.Locks = &value
default: default:
err = d.Skip() // skip unsupported tags err = d.Skip() // skip unsupported tags
if err != nil { 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. // AGraphicFrameLocks represents the locks applied to a graphic frame.
type AGraphicFrameLocks struct { type AGraphicFrameLocks struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/main graphicFrameLocks,omitempty"` 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. // AGraphic represents a graphic in a Word document.
@@ -414,6 +408,7 @@ type AGraphicData struct {
XMLName xml.Name `xml:"a:graphicData,omitempty"` XMLName xml.Name `xml:"a:graphicData,omitempty"`
URI string `xml:"uri,attr"` URI string `xml:"uri,attr"`
Pic *PICPic Pic *PICPic
Shape *WPSWordprocessingShape
} }
// UnmarshalXML ... // UnmarshalXML ...
@@ -437,6 +432,13 @@ func (a *AGraphicData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
} }
value.XMLPIC = getAtt(tt.Attr, "pic") value.XMLPIC = getAtt(tt.Attr, "pic")
a.Pic = &value 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: default:
err = d.Skip() // skip unsupported tags err = d.Skip() // skip unsupported tags
if err != nil { if err != nil {

View File

@@ -98,7 +98,7 @@ type WPSSpPr struct {
Xfrm AXfrm Xfrm AXfrm
PrstGeom APrstGeom PrstGeom APrstGeom
NoFill *struct{} `xml:"a:noFill,omitempty"` NoFill *struct{} `xml:"a:noFill,omitempty"`
Ln *ALine Elems []interface{}
} }
// UnmarshalXML ... // UnmarshalXML ...
@@ -132,11 +132,12 @@ func (w *WPSSpPr) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
case "noFill": case "noFill":
w.NoFill = &struct{}{} w.NoFill = &struct{}{}
case "ln": case "ln":
w.Ln = &ALine{} var ln ALine
err = d.DecodeElement(&w.Ln, &tt) err = d.DecodeElement(&ln, &tt)
if err != nil && !strings.HasPrefix(err.Error(), "expected") { if err != nil && !strings.HasPrefix(err.Error(), "expected") {
return err return err
} }
w.Elems = append(w.Elems, &ln)
default: default:
err = d.Skip() // skip unsupported tags err = d.Skip() // skip unsupported tags
if err != nil { if err != nil {

77
structshape_test.go Normal file
View 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()
}
}

View File

@@ -39,6 +39,7 @@ import (
func unpack(zipReader *zip.Reader) (docx *Docx, err error) { func unpack(zipReader *zip.Reader) (docx *Docx, err error) {
docx = new(Docx) docx = new(Docx)
docx.mediaNameIdx = make(map[string]int, 64) docx.mediaNameIdx = make(map[string]int, 64)
docx.slowIDs = make(map[string]uintptr, 64)
docx.tmplfs = zipReader docx.tmplfs = zipReader
docx.tmpfslst = make([]string, 0, 64) docx.tmpfslst = make([]string, 0, 64)
for _, f := range zipReader.File { for _, f := range zipReader.File {
@@ -88,6 +89,7 @@ func (f *Docx) parseDocument(file *zip.File) error {
f.Document.XMLName.Local = "document" f.Document.XMLName.Local = "document"
f.Document.Body.file = f f.Document.Body.file = f
//TODO: find last docID
err = xml.NewDecoder(zf).Decode(&f.Document) err = xml.NewDecoder(zf).Decode(&f.Document)
return err return err
} }