mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
Compare commits
2
Commits
c4f7c448f8
...
78b66f85fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78b66f85fe | ||
|
|
0784a404f8 |
+198
@@ -0,0 +1,198 @@
|
|||||||
|
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ofdgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Action 动作
|
||||||
|
type Action struct {
|
||||||
|
Event string `xml:"Event,attr"`
|
||||||
|
Region *Region `xml:"Region"`
|
||||||
|
Goto *Goto `xml:"Goto"`
|
||||||
|
URI *URI `xml:"URI"`
|
||||||
|
GotoA *GotoA `xml:"GotoA"`
|
||||||
|
Sound *Sound `xml:"Sound"`
|
||||||
|
Movie *Movie `xml:"Movie"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Goto 文档内跳转动作
|
||||||
|
type Goto struct {
|
||||||
|
Dest *Dest `xml:"Dest"`
|
||||||
|
Bookmark *GotoBookmark `xml:"Bookmark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dest 文档内跳转目标
|
||||||
|
type Dest struct {
|
||||||
|
Type string `xml:"Type,attr"`
|
||||||
|
PageID string `xml:"PageID,attr"`
|
||||||
|
Left float64 `xml:"Left"`
|
||||||
|
Right float64 `xml:"Right"`
|
||||||
|
Top float64 `xml:"Top"`
|
||||||
|
Bottom float64 `xml:"Bottom"`
|
||||||
|
Zoom float64 `xml:"Zoom"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GotoBookmark 书签跳转目标
|
||||||
|
type GotoBookmark struct {
|
||||||
|
Name string `xml:"Name,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// URI URI动作
|
||||||
|
type URI struct {
|
||||||
|
URI string `xml:"URI,attr"`
|
||||||
|
Base string `xml:"Base,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GotoA 附件动作
|
||||||
|
type GotoA struct {
|
||||||
|
AttachID string `xml:"AttachID,attr"`
|
||||||
|
NewWindow *bool `xml:"NewWindow,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sound 音频动作
|
||||||
|
type Sound struct {
|
||||||
|
ResourceID string `xml:"ResourceID,attr"`
|
||||||
|
Volume *int `xml:"Volume,attr"`
|
||||||
|
Repeat bool `xml:"Repeat,attr"`
|
||||||
|
Synchronous bool `xml:"Synchronous,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movie 视频动作
|
||||||
|
type Movie struct {
|
||||||
|
ResourceID string `xml:"ResourceID,attr"`
|
||||||
|
Operator string `xml:"Operator,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region 动作区域
|
||||||
|
type Region struct {
|
||||||
|
Area []RegionArea `xml:"Area"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegionArea 动作区域分路径
|
||||||
|
type RegionArea struct {
|
||||||
|
Start string `xml:"Start,attr"`
|
||||||
|
Command []RegionCommand `xml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegionCommand 动作区域绘制指令
|
||||||
|
type RegionCommand struct {
|
||||||
|
Type string
|
||||||
|
Point1 string
|
||||||
|
Point2 string
|
||||||
|
Point3 string
|
||||||
|
EllipseSize string
|
||||||
|
RotationAngle string
|
||||||
|
LargeArc string
|
||||||
|
SweepDirection string
|
||||||
|
EndPoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析跳转目标
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (dest *Dest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
var value struct {
|
||||||
|
Left *float64 `xml:"Left"`
|
||||||
|
Right *float64 `xml:"Right"`
|
||||||
|
Top *float64 `xml:"Top"`
|
||||||
|
Bottom *float64 `xml:"Bottom"`
|
||||||
|
Zoom *float64 `xml:"Zoom"`
|
||||||
|
}
|
||||||
|
dest.Type = attrValue(start, "Type")
|
||||||
|
dest.PageID = attrValue(start, "PageID")
|
||||||
|
dest.Left = actionFloatAttr(start, "Left")
|
||||||
|
dest.Right = actionFloatAttr(start, "Right")
|
||||||
|
dest.Top = actionFloatAttr(start, "Top")
|
||||||
|
dest.Bottom = actionFloatAttr(start, "Bottom")
|
||||||
|
dest.Zoom = actionFloatAttr(start, "Zoom")
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if value.Left != nil {
|
||||||
|
dest.Left = *value.Left
|
||||||
|
}
|
||||||
|
if value.Right != nil {
|
||||||
|
dest.Right = *value.Right
|
||||||
|
}
|
||||||
|
if value.Top != nil {
|
||||||
|
dest.Top = *value.Top
|
||||||
|
}
|
||||||
|
if value.Bottom != nil {
|
||||||
|
dest.Bottom = *value.Bottom
|
||||||
|
}
|
||||||
|
if value.Zoom != nil {
|
||||||
|
dest.Zoom = *value.Zoom
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析视频动作并应用默认值
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (m *Movie) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type movie Movie
|
||||||
|
value := movie{Operator: "Play"}
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = Movie(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析动作区域分路径并保留指令顺序
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (a *RegionArea) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*a = RegionArea{Start: attrValue(start, "Start")}
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch node := tok.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
command := RegionCommand{
|
||||||
|
Type: node.Name.Local,
|
||||||
|
Point1: attrValue(node, "Point1"),
|
||||||
|
Point2: attrValue(node, "Point2"),
|
||||||
|
Point3: attrValue(node, "Point3"),
|
||||||
|
EllipseSize: attrValue(node, "EllipseSize"),
|
||||||
|
RotationAngle: attrValue(node, "RotationAngle"),
|
||||||
|
LargeArc: attrValue(node, "LargeArc"),
|
||||||
|
SweepDirection: attrValue(node, "SweepDirection"),
|
||||||
|
EndPoint: attrValue(node, "EndPoint"),
|
||||||
|
}
|
||||||
|
a.Command = append(a.Command, command)
|
||||||
|
if err := d.Skip(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
if node.Name.Local == start.Name.Local {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actionFloatAttr 获取动作浮点属性
|
||||||
|
// 入参: start 起始节点, name 属性名
|
||||||
|
// 返回: float64 属性值
|
||||||
|
func actionFloatAttr(start xml.StartElement, name string) float64 {
|
||||||
|
value, _ := strconv.ParseFloat(attrValue(start, name), 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
+196
-17
@@ -23,26 +23,32 @@ type Document struct {
|
|||||||
Pages Pages `xml:"Pages"`
|
Pages Pages `xml:"Pages"`
|
||||||
Outlines Outlines `xml:"Outlines"`
|
Outlines Outlines `xml:"Outlines"`
|
||||||
Permissions Permissions `xml:"Permissions"`
|
Permissions Permissions `xml:"Permissions"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
|
Bookmarks Bookmarks `xml:"Bookmarks"`
|
||||||
Annotations string `xml:"Annotations"`
|
Annotations string `xml:"Annotations"`
|
||||||
Signatures string `xml:"Signatures"`
|
Signatures string `xml:"Signatures"`
|
||||||
Attachments Attachments `xml:"Attachments"`
|
Attachments Attachments `xml:"Attachments"`
|
||||||
|
CustomTags CustomTags `xml:"CustomTags"`
|
||||||
Extensions Extensions `xml:"Extensions"`
|
Extensions Extensions `xml:"Extensions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions 扩展集合
|
// Extensions 扩展集合
|
||||||
type Extensions struct {
|
type Extensions struct {
|
||||||
|
XMLName xml.Name `xml:"Extensions"`
|
||||||
|
Path string `xml:",chardata"`
|
||||||
Extension []Extension `xml:"Extension"`
|
Extension []Extension `xml:"Extension"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension 扩展信息
|
// Extension 扩展信息
|
||||||
type Extension struct {
|
type Extension struct {
|
||||||
AppName string `xml:"AppName,attr"`
|
AppName string `xml:"AppName,attr"`
|
||||||
Company string `xml:"Company,attr"`
|
Company string `xml:"Company,attr"`
|
||||||
AppVersion string `xml:"AppVersion,attr"`
|
AppVersion string `xml:"AppVersion,attr"`
|
||||||
Date string `xml:"Date,attr"`
|
Date string `xml:"Date,attr"`
|
||||||
RefID string `xml:"RefId,attr"`
|
RefID string `xml:"RefId,attr"`
|
||||||
Property []Property `xml:"Property"`
|
Property []Property `xml:"Property"`
|
||||||
Data string `xml:"Data"`
|
ExtendData []string `xml:"ExtendData"`
|
||||||
|
Data []ExtensionData `xml:"Data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Property 扩展属性
|
// Property 扩展属性
|
||||||
@@ -52,16 +58,45 @@ type Property struct {
|
|||||||
Type string `xml:"Type,attr"`
|
Type string `xml:"Type,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtensionData 扩展数据
|
||||||
|
type ExtensionData struct {
|
||||||
|
Attr []xml.Attr `xml:",any,attr"`
|
||||||
|
Content string `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
// Attachments 附件集合
|
// Attachments 附件集合
|
||||||
type Attachments struct {
|
type Attachments struct {
|
||||||
|
XMLName xml.Name `xml:"Attachments"`
|
||||||
|
Path string `xml:",chardata"`
|
||||||
Attachment []Attachment `xml:"Attachment"`
|
Attachment []Attachment `xml:"Attachment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachment 附件信息
|
// Attachment 附件信息
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
Name string `xml:"Name,attr"`
|
ID string `xml:"ID,attr"`
|
||||||
File string `xml:"File,attr"`
|
Name string `xml:"Name,attr"`
|
||||||
ID string `xml:"ID,attr"`
|
Format string `xml:"Format,attr"`
|
||||||
|
CreationDate string `xml:"CreationDate,attr"`
|
||||||
|
ModDate string `xml:"ModDate,attr"`
|
||||||
|
Size *float64 `xml:"Size,attr"`
|
||||||
|
Visible bool `xml:"Visible,attr"`
|
||||||
|
Usage string `xml:"Usage,attr"`
|
||||||
|
FileLoc string `xml:"FileLoc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomTags 自定义标引集合
|
||||||
|
type CustomTags struct {
|
||||||
|
XMLName xml.Name `xml:"CustomTags"`
|
||||||
|
Path string `xml:",chardata"`
|
||||||
|
CustomTag []CustomTag `xml:"CustomTag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomTag 自定义标引
|
||||||
|
type CustomTag struct {
|
||||||
|
TypeID string `xml:"TypeID,attr"`
|
||||||
|
NameSpace string `xml:"NameSpace,attr"`
|
||||||
|
SchemaLoc string `xml:"SchemaLoc"`
|
||||||
|
FileLoc string `xml:"FileLoc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonData 文档公共数据
|
// CommonData 文档公共数据
|
||||||
@@ -101,6 +136,17 @@ type TemplatePage struct {
|
|||||||
ZOrder string `xml:"ZOrder,attr"`
|
ZOrder string `xml:"ZOrder,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bookmarks 书签集合
|
||||||
|
type Bookmarks struct {
|
||||||
|
Bookmark []Bookmark `xml:"Bookmark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bookmark 书签
|
||||||
|
type Bookmark struct {
|
||||||
|
Name string `xml:"Name,attr"`
|
||||||
|
Dest Dest `xml:"Dest"`
|
||||||
|
}
|
||||||
|
|
||||||
// Outlines 大纲集合
|
// Outlines 大纲集合
|
||||||
type Outlines struct {
|
type Outlines struct {
|
||||||
OutlineElem []OutlineElem `xml:"OutlineElem"`
|
OutlineElem []OutlineElem `xml:"OutlineElem"`
|
||||||
@@ -108,15 +154,148 @@ type Outlines struct {
|
|||||||
|
|
||||||
// OutlineElem 大纲节点
|
// OutlineElem 大纲节点
|
||||||
type OutlineElem struct {
|
type OutlineElem struct {
|
||||||
Title string `xml:"Title,attr"`
|
Title string `xml:"Title,attr"`
|
||||||
Count int `xml:"Count,attr"`
|
Count int `xml:"Count,attr"`
|
||||||
Actions string `xml:"Actions"`
|
Expanded bool `xml:"Expanded,attr"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
|
OutlineElem []OutlineElem `xml:"OutlineElem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permissions 权限声明
|
// Permissions 权限声明
|
||||||
type Permissions struct {
|
type Permissions struct {
|
||||||
Edit bool `xml:"Edit"`
|
Edit bool `xml:"Edit"`
|
||||||
Print bool `xml:"Print"`
|
Annot bool `xml:"Annot"`
|
||||||
Export bool `xml:"Export"`
|
Export bool `xml:"Export"`
|
||||||
Copy bool `xml:"Copy"`
|
Signature bool `xml:"Signature"`
|
||||||
|
Watermark bool `xml:"Watermark"`
|
||||||
|
PrintScreen bool `xml:"PrintScreen"`
|
||||||
|
Print bool `xml:"-"`
|
||||||
|
Copies int `xml:"-"`
|
||||||
|
Copy bool `xml:"CopyText"`
|
||||||
|
ContentRegist bool `xml:"ContentRegist"`
|
||||||
|
ValidPeriod *ValidPeriod `xml:"ValidPeriod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidPeriod 文档访问有效期
|
||||||
|
type ValidPeriod struct {
|
||||||
|
StartDate string `xml:"StartDate,attr"`
|
||||||
|
EndDate string `xml:"EndDate,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析文档并应用权限默认值
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (doc *Document) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type document Document
|
||||||
|
value := document{Permissions: defaultPermissions()}
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*doc = Document(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析附件并应用默认值
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (a *Attachment) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type attachment Attachment
|
||||||
|
value := attachment{Visible: true, Usage: "none"}
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*a = Attachment(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析大纲节点并应用默认值
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (o *OutlineElem) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type outlineElem OutlineElem
|
||||||
|
value := outlineElem{Expanded: true}
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*o = OutlineElem(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析文档权限并应用默认值
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (p *Permissions) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
var value struct {
|
||||||
|
Edit *bool `xml:"Edit"`
|
||||||
|
Annot *bool `xml:"Annot"`
|
||||||
|
Export *bool `xml:"Export"`
|
||||||
|
Signature *bool `xml:"Signature"`
|
||||||
|
Watermark *bool `xml:"Watermark"`
|
||||||
|
PrintScreen *bool `xml:"PrintScreen"`
|
||||||
|
Copy *bool `xml:"CopyText"`
|
||||||
|
ContentRegist *bool `xml:"ContentRegist"`
|
||||||
|
Print *print `xml:"Print"`
|
||||||
|
ValidPeriod *ValidPeriod `xml:"ValidPeriod"`
|
||||||
|
}
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = defaultPermissions()
|
||||||
|
if value.Edit != nil {
|
||||||
|
p.Edit = *value.Edit
|
||||||
|
}
|
||||||
|
if value.Annot != nil {
|
||||||
|
p.Annot = *value.Annot
|
||||||
|
}
|
||||||
|
if value.Export != nil {
|
||||||
|
p.Export = *value.Export
|
||||||
|
}
|
||||||
|
if value.Signature != nil {
|
||||||
|
p.Signature = *value.Signature
|
||||||
|
}
|
||||||
|
if value.Watermark != nil {
|
||||||
|
p.Watermark = *value.Watermark
|
||||||
|
}
|
||||||
|
if value.PrintScreen != nil {
|
||||||
|
p.PrintScreen = *value.PrintScreen
|
||||||
|
}
|
||||||
|
if value.Copy != nil {
|
||||||
|
p.Copy = *value.Copy
|
||||||
|
}
|
||||||
|
if value.ContentRegist != nil {
|
||||||
|
p.ContentRegist = *value.ContentRegist
|
||||||
|
}
|
||||||
|
if value.Print != nil {
|
||||||
|
if value.Print.Printable != nil {
|
||||||
|
p.Print = *value.Print.Printable
|
||||||
|
}
|
||||||
|
if value.Print.Copies != nil {
|
||||||
|
p.Copies = *value.Print.Copies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.ValidPeriod = value.ValidPeriod
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// print 打印权限节点
|
||||||
|
type print struct {
|
||||||
|
Printable *bool `xml:"Printable,attr"`
|
||||||
|
Copies *int `xml:"Copies,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultPermissions 获取默认文档权限
|
||||||
|
// 返回: Permissions 文档权限
|
||||||
|
func defaultPermissions() Permissions {
|
||||||
|
return Permissions{
|
||||||
|
Edit: true,
|
||||||
|
Annot: true,
|
||||||
|
Export: true,
|
||||||
|
Signature: true,
|
||||||
|
Watermark: true,
|
||||||
|
PrintScreen: true,
|
||||||
|
Print: true,
|
||||||
|
Copies: -1,
|
||||||
|
Copy: true,
|
||||||
|
ContentRegist: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-280
@@ -19,6 +19,76 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// graphicObjectTarget 图形对象集合
|
||||||
|
type graphicObjectTarget struct {
|
||||||
|
objects *[]GraphicObject
|
||||||
|
text *[]TextObject
|
||||||
|
path *[]PathObject
|
||||||
|
image *[]ImageObject
|
||||||
|
composite *[]CompositeGraphicUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeGraphicObject 解析图形对象
|
||||||
|
// 入参: d XML解码器, start 起始节点, target 图形对象集合
|
||||||
|
// 返回: bool 是否为图形对象, error 错误信息
|
||||||
|
func decodeGraphicObject(d *xml.Decoder, start xml.StartElement, target graphicObjectTarget) (bool, error) {
|
||||||
|
switch start.Name.Local {
|
||||||
|
case "TextObject":
|
||||||
|
var obj TextObject
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
*target.text = append(*target.text, obj)
|
||||||
|
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
|
||||||
|
case "PathObject":
|
||||||
|
var obj PathObject
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
*target.path = append(*target.path, obj)
|
||||||
|
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
|
||||||
|
case "ImageObject":
|
||||||
|
var obj ImageObject
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
*target.image = append(*target.image, obj)
|
||||||
|
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
|
||||||
|
case "CompositeGraphicUnit", "CompositeObject":
|
||||||
|
var obj CompositeGraphicUnit
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
*target.composite = append(*target.composite, obj)
|
||||||
|
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeObjectContainer 解析图形对象容器
|
||||||
|
// 入参: d XML解码器, start 起始节点, decode 对象解码函数
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func decodeObjectContainer(d *xml.Decoder, start xml.StartElement, decode func(*xml.Decoder, xml.StartElement) error) error {
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch node := tok.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if err := decode(d, node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
if node.Name.Local == start.Name.Local {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalXML 解析图层并保留对象顺序
|
// UnmarshalXML 解析图层并保留对象顺序
|
||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
@@ -26,85 +96,27 @@ func (l *Layer) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
*l = Layer{}
|
*l = Layer{}
|
||||||
l.ID = attrValue(start, "ID")
|
l.ID = attrValue(start, "ID")
|
||||||
l.DrawParam = attrValue(start, "DrawParam")
|
l.DrawParam = attrValue(start, "DrawParam")
|
||||||
for {
|
return decodeObjectContainer(d, start, l.decodeObject)
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := l.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeObject 解析图层子对象
|
// decodeObject 解析图层子对象
|
||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (l *Layer) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
func (l *Layer) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
||||||
switch start.Name.Local {
|
target := graphicObjectTarget{
|
||||||
case "TextObject":
|
objects: &l.Objects,
|
||||||
var obj TextObject
|
text: &l.TextObject,
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
path: &l.PathObject,
|
||||||
return err
|
image: &l.ImageObject,
|
||||||
}
|
composite: &l.CompositeGraphicUnit,
|
||||||
l.TextObject = append(l.TextObject, obj)
|
|
||||||
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
|
|
||||||
case "PathObject":
|
|
||||||
var obj PathObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.PathObject = append(l.PathObject, obj)
|
|
||||||
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
|
|
||||||
case "ImageObject":
|
|
||||||
var obj ImageObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.ImageObject = append(l.ImageObject, obj)
|
|
||||||
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
|
|
||||||
case "CompositeGraphicUnit", "CompositeObject":
|
|
||||||
var obj CompositeGraphicUnit
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.CompositeGraphicUnit = append(l.CompositeGraphicUnit, obj)
|
|
||||||
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
|
|
||||||
case "PageBlock":
|
|
||||||
return l.decodePageBlock(d, start)
|
|
||||||
default:
|
|
||||||
return d.Skip()
|
|
||||||
}
|
}
|
||||||
return nil
|
if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
// decodePageBlock 解析页块子对象
|
|
||||||
// 入参: d XML解码器, start 起始节点
|
|
||||||
// 返回: error 错误信息
|
|
||||||
func (l *Layer) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
|
|
||||||
for {
|
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := l.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if start.Name.Local == "PageBlock" {
|
||||||
|
return decodeObjectContainer(d, start, l.decodeObject)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalXML 解析复合图元并保留对象顺序
|
// UnmarshalXML 解析复合图元并保留对象顺序
|
||||||
@@ -128,178 +140,73 @@ func (c *CompositeGraphicUnit) UnmarshalXML(d *xml.Decoder, start xml.StartEleme
|
|||||||
c.Visible = &visible
|
c.Visible = &visible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for {
|
return decodeObjectContainer(d, start, c.decodeObject)
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := c.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeObject 解析复合图元子对象
|
// decodeObject 解析复合图元子对象
|
||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (c *CompositeGraphicUnit) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
func (c *CompositeGraphicUnit) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
target := graphicObjectTarget{
|
||||||
|
objects: &c.Objects,
|
||||||
|
text: &c.TextObject,
|
||||||
|
path: &c.PathObject,
|
||||||
|
image: &c.ImageObject,
|
||||||
|
composite: &c.CompositeGraphicUnit,
|
||||||
|
}
|
||||||
|
if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
switch start.Name.Local {
|
switch start.Name.Local {
|
||||||
case "TextObject":
|
|
||||||
var obj TextObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.TextObject = append(c.TextObject, obj)
|
|
||||||
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
|
|
||||||
case "PathObject":
|
|
||||||
var obj PathObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.PathObject = append(c.PathObject, obj)
|
|
||||||
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
|
|
||||||
case "ImageObject":
|
|
||||||
var obj ImageObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.ImageObject = append(c.ImageObject, obj)
|
|
||||||
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
|
|
||||||
case "CompositeGraphicUnit", "CompositeObject":
|
|
||||||
var obj CompositeGraphicUnit
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.CompositeGraphicUnit = append(c.CompositeGraphicUnit, obj)
|
|
||||||
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
|
|
||||||
case "Clips":
|
case "Clips":
|
||||||
var clips Clips
|
var clips Clips
|
||||||
if err := d.DecodeElement(&clips, &start); err != nil {
|
if err := d.DecodeElement(&clips, &start); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.Clips = &clips
|
c.Clips = &clips
|
||||||
|
case "Actions":
|
||||||
|
var actions struct {
|
||||||
|
Action []Action `xml:"Action"`
|
||||||
|
}
|
||||||
|
if err := d.DecodeElement(&actions, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Actions = actions.Action
|
||||||
case "Content", "PageBlock":
|
case "Content", "PageBlock":
|
||||||
return c.decodePageBlock(d, start)
|
return decodeObjectContainer(d, start, c.decodeObject)
|
||||||
default:
|
default:
|
||||||
return d.Skip()
|
return d.Skip()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodePageBlock 解析页块子对象
|
|
||||||
// 入参: d XML解码器, start 起始节点
|
|
||||||
// 返回: error 错误信息
|
|
||||||
func (c *CompositeGraphicUnit) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
|
|
||||||
for {
|
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := c.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalXML 解析注释外观并保留对象顺序
|
// UnmarshalXML 解析注释外观并保留对象顺序
|
||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
*a = Appearance{}
|
*a = Appearance{}
|
||||||
a.Boundary = attrValue(start, "Boundary")
|
a.Boundary = attrValue(start, "Boundary")
|
||||||
for {
|
return decodeObjectContainer(d, start, a.decodeObject)
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := a.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeObject 解析注释外观子对象
|
// decodeObject 解析注释外观子对象
|
||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (a *Appearance) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
func (a *Appearance) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
||||||
switch start.Name.Local {
|
target := graphicObjectTarget{
|
||||||
case "TextObject":
|
objects: &a.Objects,
|
||||||
var obj TextObject
|
text: &a.TextObject,
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
path: &a.PathObject,
|
||||||
return err
|
image: &a.ImageObject,
|
||||||
}
|
composite: &a.CompositeGraphicUnit,
|
||||||
a.TextObject = append(a.TextObject, obj)
|
|
||||||
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
|
|
||||||
case "PathObject":
|
|
||||||
var obj PathObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.PathObject = append(a.PathObject, obj)
|
|
||||||
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
|
|
||||||
case "ImageObject":
|
|
||||||
var obj ImageObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.ImageObject = append(a.ImageObject, obj)
|
|
||||||
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
|
|
||||||
case "CompositeGraphicUnit", "CompositeObject":
|
|
||||||
var obj CompositeGraphicUnit
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.CompositeGraphicUnit = append(a.CompositeGraphicUnit, obj)
|
|
||||||
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
|
|
||||||
case "PageBlock":
|
|
||||||
return a.decodePageBlock(d, start)
|
|
||||||
default:
|
|
||||||
return d.Skip()
|
|
||||||
}
|
}
|
||||||
return nil
|
if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
// decodePageBlock 解析注释外观页块子对象
|
|
||||||
// 入参: d XML解码器, start 起始节点
|
|
||||||
// 返回: error 错误信息
|
|
||||||
func (a *Appearance) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
|
|
||||||
for {
|
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := a.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if start.Name.Local == "PageBlock" {
|
||||||
|
return decodeObjectContainer(d, start, a.decodeObject)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalXML 解析图案单元内容并保留对象顺序
|
// UnmarshalXML 解析图案单元内容并保留对象顺序
|
||||||
@@ -307,85 +214,27 @@ func (a *Appearance) decodePageBlock(d *xml.Decoder, start xml.StartElement) err
|
|||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
*p = PatternContent{}
|
*p = PatternContent{}
|
||||||
for {
|
return decodeObjectContainer(d, start, p.decodeObject)
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := p.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeObject 解析图案单元内容子对象
|
// decodeObject 解析图案单元内容子对象
|
||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (p *PatternContent) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
func (p *PatternContent) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
||||||
switch start.Name.Local {
|
target := graphicObjectTarget{
|
||||||
case "TextObject":
|
objects: &p.Objects,
|
||||||
var obj TextObject
|
text: &p.TextObject,
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
path: &p.PathObject,
|
||||||
return err
|
image: &p.ImageObject,
|
||||||
}
|
composite: &p.CompositeGraphicUnit,
|
||||||
p.TextObject = append(p.TextObject, obj)
|
|
||||||
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
|
|
||||||
case "PathObject":
|
|
||||||
var obj PathObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.PathObject = append(p.PathObject, obj)
|
|
||||||
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
|
|
||||||
case "ImageObject":
|
|
||||||
var obj ImageObject
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.ImageObject = append(p.ImageObject, obj)
|
|
||||||
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
|
|
||||||
case "CompositeGraphicUnit", "CompositeObject":
|
|
||||||
var obj CompositeGraphicUnit
|
|
||||||
if err := d.DecodeElement(&obj, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.CompositeGraphicUnit = append(p.CompositeGraphicUnit, obj)
|
|
||||||
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
|
|
||||||
case "PageBlock":
|
|
||||||
return p.decodePageBlock(d, start)
|
|
||||||
default:
|
|
||||||
return d.Skip()
|
|
||||||
}
|
}
|
||||||
return nil
|
if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
// decodePageBlock 解析图案单元内容页块子对象
|
|
||||||
// 入参: d XML解码器, start 起始节点
|
|
||||||
// 返回: error 错误信息
|
|
||||||
func (p *PatternContent) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
|
|
||||||
for {
|
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch node := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if err := p.decodeObject(d, node); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case xml.EndElement:
|
|
||||||
if node.Name.Local == start.Name.Local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if start.Name.Local == "PageBlock" {
|
||||||
|
return decodeObjectContainer(d, start, p.decodeObject)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
// attrValue 获取XML属性值
|
// attrValue 获取XML属性值
|
||||||
|
|||||||
+12
-8
@@ -23,6 +23,7 @@ type PageContent struct {
|
|||||||
Area PageArea `xml:"Area"`
|
Area PageArea `xml:"Area"`
|
||||||
Template []Template `xml:"Template"`
|
Template []Template `xml:"Template"`
|
||||||
Content Content `xml:"Content"`
|
Content Content `xml:"Content"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template 页面模板引用
|
// Template 页面模板引用
|
||||||
@@ -97,6 +98,7 @@ type TextObject struct {
|
|||||||
CGTransform []CGTransform `xml:"CGTransform"`
|
CGTransform []CGTransform `xml:"CGTransform"`
|
||||||
TextCode []TextCode `xml:"TextCode"`
|
TextCode []TextCode `xml:"TextCode"`
|
||||||
Clips *Clips `xml:"Clips"`
|
Clips *Clips `xml:"Clips"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillColor 填充颜色
|
// FillColor 填充颜色
|
||||||
@@ -165,6 +167,7 @@ type PathObject struct {
|
|||||||
FillColor *FillColor `xml:"FillColor"`
|
FillColor *FillColor `xml:"FillColor"`
|
||||||
AbbreviatedData string `xml:"AbbreviatedData"`
|
AbbreviatedData string `xml:"AbbreviatedData"`
|
||||||
Clips *Clips `xml:"Clips"`
|
Clips *Clips `xml:"Clips"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeColor 勾边颜色
|
// StrokeColor 勾边颜色
|
||||||
@@ -207,12 +210,13 @@ type ShdColor struct {
|
|||||||
|
|
||||||
// ImageObject 图片对象
|
// ImageObject 图片对象
|
||||||
type ImageObject struct {
|
type ImageObject struct {
|
||||||
ID string `xml:"ID,attr"`
|
ID string `xml:"ID,attr"`
|
||||||
Boundary string `xml:"Boundary,attr"`
|
Boundary string `xml:"Boundary,attr"`
|
||||||
ResourceID string `xml:"ResourceID,attr"`
|
ResourceID string `xml:"ResourceID,attr"`
|
||||||
ImageMask string `xml:"ImageMask,attr"`
|
ImageMask string `xml:"ImageMask,attr"`
|
||||||
CTM string `xml:"CTM,attr"`
|
CTM string `xml:"CTM,attr"`
|
||||||
Alpha *int `xml:"Alpha,attr"`
|
Alpha *int `xml:"Alpha,attr"`
|
||||||
Visible *bool `xml:"Visible,attr"`
|
Visible *bool `xml:"Visible,attr"`
|
||||||
Clips *Clips `xml:"Clips"`
|
Clips *Clips `xml:"Clips"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-64
@@ -142,10 +142,10 @@ func parseFillColor(fillColor *FillColor) color.Color {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if fillColor.AxialShd != nil {
|
if fillColor.AxialShd != nil {
|
||||||
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha)
|
return parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
if fillColor.RadialShd != nil {
|
if fillColor.RadialShd != nil {
|
||||||
return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha)
|
return parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(fillColor.Value) != "" {
|
if strings.TrimSpace(fillColor.Value) != "" {
|
||||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||||
@@ -170,10 +170,10 @@ func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64)
|
|||||||
return gradient
|
return gradient
|
||||||
}
|
}
|
||||||
if fillColor.AxialShd != nil {
|
if fillColor.AxialShd != nil {
|
||||||
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha)
|
return parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
if fillColor.RadialShd != nil {
|
if fillColor.RadialShd != nil {
|
||||||
return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha)
|
return parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(fillColor.Value) != "" {
|
if strings.TrimSpace(fillColor.Value) != "" {
|
||||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||||
@@ -189,10 +189,10 @@ func parseStrokeColor(strokeColor *StrokeColor) color.Color {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if strokeColor.AxialShd != nil {
|
if strokeColor.AxialShd != nil {
|
||||||
return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha)
|
return parseShdColor(strokeColor.AxialShd.Segment, strokeColor.Alpha)
|
||||||
}
|
}
|
||||||
if strokeColor.RadialShd != nil {
|
if strokeColor.RadialShd != nil {
|
||||||
return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha)
|
return parseShdColor(strokeColor.RadialShd.Segment, strokeColor.Alpha)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(strokeColor.Value) != "" {
|
if strings.TrimSpace(strokeColor.Value) != "" {
|
||||||
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
|
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
|
||||||
@@ -214,10 +214,10 @@ func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH, originX, originY fl
|
|||||||
return gradient
|
return gradient
|
||||||
}
|
}
|
||||||
if strokeColor.AxialShd != nil {
|
if strokeColor.AxialShd != nil {
|
||||||
return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha)
|
return parseShdColor(strokeColor.AxialShd.Segment, strokeColor.Alpha)
|
||||||
}
|
}
|
||||||
if strokeColor.RadialShd != nil {
|
if strokeColor.RadialShd != nil {
|
||||||
return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha)
|
return parseShdColor(strokeColor.RadialShd.Segment, strokeColor.Alpha)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(strokeColor.Value) != "" {
|
if strings.TrimSpace(strokeColor.Value) != "" {
|
||||||
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
|
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
|
||||||
@@ -235,24 +235,44 @@ func patternColor(fillColor *FillColor) color.Color {
|
|||||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAxialShdColor 解析轴向渐变颜色
|
// parseShdColor 解析渐变颜色
|
||||||
// 入参: axialShd 轴向渐变节点, alpha 透明度
|
// 入参: segments 渐变分段, alpha 透明度
|
||||||
// 返回: color.Color 颜色对象
|
// 返回: color.Color 颜色对象
|
||||||
func parseAxialShdColor(axialShd *AxialShd, alpha *int) color.Color {
|
func parseShdColor(segments []ShdSegment, alpha *int) color.Color {
|
||||||
if axialShd != nil {
|
for _, segment := range segments {
|
||||||
for _, segment := range axialShd.Segment {
|
if strings.TrimSpace(segment.Color.Value) == "" {
|
||||||
if strings.TrimSpace(segment.Color.Value) == "" {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
if alpha == nil {
|
|
||||||
alpha = segment.Color.Alpha
|
|
||||||
}
|
|
||||||
return parseColorWithAlpha(segment.Color.Value, alpha)
|
|
||||||
}
|
}
|
||||||
|
segmentAlpha := alpha
|
||||||
|
if segmentAlpha == nil {
|
||||||
|
segmentAlpha = segment.Color.Alpha
|
||||||
|
}
|
||||||
|
return parseColorWithAlpha(segment.Color.Value, segmentAlpha)
|
||||||
}
|
}
|
||||||
return color.Black
|
return color.Black
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseShdSegments 解析渐变分段
|
||||||
|
// 入参: segments 渐变分段, alpha 透明度
|
||||||
|
// 返回: canvas.Grad 渐变分段
|
||||||
|
func parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad {
|
||||||
|
gradient := canvas.NewGradient()
|
||||||
|
for _, segment := range segments {
|
||||||
|
if strings.TrimSpace(segment.Color.Value) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
segmentAlpha := alpha
|
||||||
|
if segmentAlpha == nil {
|
||||||
|
segmentAlpha = segment.Color.Alpha
|
||||||
|
}
|
||||||
|
gradient.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
|
||||||
|
}
|
||||||
|
if len(gradient) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return gradient
|
||||||
|
}
|
||||||
|
|
||||||
// parseAxialShdGradient 解析轴向渐变
|
// parseAxialShdGradient 解析轴向渐变
|
||||||
// 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
|
// 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
|
||||||
// 返回: canvas.Gradient 渐变对象
|
// 返回: canvas.Gradient 渐变对象
|
||||||
@@ -265,18 +285,8 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX,
|
|||||||
if len(start) < 2 || len(end) < 2 {
|
if len(start) < 2 || len(end) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
grad := canvas.NewGradient()
|
gradient := parseShdSegments(axialShd.Segment, alpha)
|
||||||
for _, segment := range axialShd.Segment {
|
if gradient == nil {
|
||||||
if strings.TrimSpace(segment.Color.Value) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
segmentAlpha := alpha
|
|
||||||
if segmentAlpha == nil {
|
|
||||||
segmentAlpha = segment.Color.Alpha
|
|
||||||
}
|
|
||||||
grad.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
|
|
||||||
}
|
|
||||||
if len(grad) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
|
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
|
||||||
@@ -284,25 +294,7 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX,
|
|||||||
if startPoint.Equals(endPoint) {
|
if startPoint.Equals(endPoint) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return grad.ToLinear(startPoint, endPoint)
|
return gradient.ToLinear(startPoint, endPoint)
|
||||||
}
|
|
||||||
|
|
||||||
// parseRadialShdColor 解析径向渐变颜色
|
|
||||||
// 入参: radialShd 径向渐变节点, alpha 透明度
|
|
||||||
// 返回: color.Color 颜色对象
|
|
||||||
func parseRadialShdColor(radialShd *RadialShd, alpha *int) color.Color {
|
|
||||||
if radialShd != nil {
|
|
||||||
for _, segment := range radialShd.Segment {
|
|
||||||
if strings.TrimSpace(segment.Color.Value) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if alpha == nil {
|
|
||||||
alpha = segment.Color.Alpha
|
|
||||||
}
|
|
||||||
return parseColorWithAlpha(segment.Color.Value, alpha)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color.Black
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseRadialShdGradient 解析径向渐变
|
// parseRadialShdGradient 解析径向渐变
|
||||||
@@ -317,23 +309,13 @@ func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, origi
|
|||||||
if len(start) < 2 || len(end) < 2 {
|
if len(start) < 2 || len(end) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
grad := canvas.NewGradient()
|
gradient := parseShdSegments(radialShd.Segment, alpha)
|
||||||
for _, segment := range radialShd.Segment {
|
if gradient == nil {
|
||||||
if strings.TrimSpace(segment.Color.Value) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
segmentAlpha := alpha
|
|
||||||
if segmentAlpha == nil {
|
|
||||||
segmentAlpha = segment.Color.Alpha
|
|
||||||
}
|
|
||||||
grad.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
|
|
||||||
}
|
|
||||||
if len(grad) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
|
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
|
||||||
endPoint := canvas.Point{X: x + end[0] - originX, Y: pageH - (y + end[1]) - originY}
|
endPoint := canvas.Point{X: x + end[0] - originX, Y: pageH - (y + end[1]) - originY}
|
||||||
return grad.ToRadial(startPoint, radialShd.StartRadius, endPoint, radialShd.EndRadius)
|
return gradient.ToRadial(startPoint, radialShd.StartRadius, endPoint, radialShd.EndRadius)
|
||||||
}
|
}
|
||||||
|
|
||||||
// colorToRGBA 转换颜色对象
|
// colorToRGBA 转换颜色对象
|
||||||
|
|||||||
+94
-1
@@ -279,6 +279,21 @@ func (r *Reader) ResData(resLink string) ([]byte, error) {
|
|||||||
return r.readFile(fullPath)
|
return r.readFile(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readDocumentPart 读取文档关联文件
|
||||||
|
// 入参: loc 文件位置, value 文档结构
|
||||||
|
// 返回: string 文件路径, error 错误信息
|
||||||
|
func (r *Reader) readDocumentPart(loc string, value any) (string, error) {
|
||||||
|
fullPath := r.ResPath(strings.TrimSpace(loc))
|
||||||
|
data, err := r.readFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := xml.Unmarshal(data, value); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to unmarshal %s: %w", path.Base(fullPath), err)
|
||||||
|
}
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Version 获取OFD版本号
|
// Version 获取OFD版本号
|
||||||
// 返回: string 版本号
|
// 返回: string 版本号
|
||||||
func (r *Reader) Version() string {
|
func (r *Reader) Version() string {
|
||||||
@@ -326,6 +341,26 @@ func (r *Reader) Outlines() ([]OutlineElem, error) {
|
|||||||
return doc.Outlines.OutlineElem, nil
|
return doc.Outlines.OutlineElem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actions 获取文档动作
|
||||||
|
// 返回: []Action 动作列表, error 错误信息
|
||||||
|
func (r *Reader) Actions() ([]Action, error) {
|
||||||
|
doc, err := r.Doc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return doc.Actions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bookmarks 获取文档书签
|
||||||
|
// 返回: []Bookmark 书签列表, error 错误信息
|
||||||
|
func (r *Reader) Bookmarks() ([]Bookmark, error) {
|
||||||
|
doc, err := r.Doc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return doc.Bookmarks.Bookmark, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Attachments 获取附件列表
|
// Attachments 获取附件列表
|
||||||
// 返回: []Attachment 附件列表, error 错误信息
|
// 返回: []Attachment 附件列表, error 错误信息
|
||||||
func (r *Reader) Attachments() ([]Attachment, error) {
|
func (r *Reader) Attachments() ([]Attachment, error) {
|
||||||
@@ -333,9 +368,50 @@ func (r *Reader) Attachments() ([]Attachment, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(doc.Attachments.Path) != "" && doc.Attachments.Attachment == nil {
|
||||||
|
var attachments Attachments
|
||||||
|
partPath, err := r.readDocumentPart(doc.Attachments.Path, &attachments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range attachments.Attachment {
|
||||||
|
attachment := &attachments.Attachment[i]
|
||||||
|
attachment.FileLoc = resolveResourcePath(partPath, "", attachment.FileLoc)
|
||||||
|
}
|
||||||
|
if attachments.Attachment == nil {
|
||||||
|
attachments.Attachment = []Attachment{}
|
||||||
|
}
|
||||||
|
doc.Attachments.Attachment = attachments.Attachment
|
||||||
|
}
|
||||||
return doc.Attachments.Attachment, nil
|
return doc.Attachments.Attachment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomTags 获取自定义标引
|
||||||
|
// 返回: []CustomTag 自定义标引列表, error 错误信息
|
||||||
|
func (r *Reader) CustomTags() ([]CustomTag, error) {
|
||||||
|
doc, err := r.Doc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(doc.CustomTags.Path) != "" && doc.CustomTags.CustomTag == nil {
|
||||||
|
var customTags CustomTags
|
||||||
|
partPath, err := r.readDocumentPart(doc.CustomTags.Path, &customTags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range customTags.CustomTag {
|
||||||
|
tag := &customTags.CustomTag[i]
|
||||||
|
tag.SchemaLoc = resolveResourcePath(partPath, "", tag.SchemaLoc)
|
||||||
|
tag.FileLoc = resolveResourcePath(partPath, "", tag.FileLoc)
|
||||||
|
}
|
||||||
|
if customTags.CustomTag == nil {
|
||||||
|
customTags.CustomTag = []CustomTag{}
|
||||||
|
}
|
||||||
|
doc.CustomTags.CustomTag = customTags.CustomTag
|
||||||
|
}
|
||||||
|
return doc.CustomTags.CustomTag, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CustomDatas 获取自定义数据
|
// CustomDatas 获取自定义数据
|
||||||
// 返回: []CustomData 自定义数据列表, error 错误信息
|
// 返回: []CustomData 自定义数据列表, error 错误信息
|
||||||
func (r *Reader) CustomDatas() ([]CustomData, error) {
|
func (r *Reader) CustomDatas() ([]CustomData, error) {
|
||||||
@@ -349,12 +425,29 @@ func (r *Reader) CustomDatas() ([]CustomData, error) {
|
|||||||
return info.CustomDatas.CustomData, nil
|
return info.CustomDatas.CustomData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions 获取分列扩展项
|
// Extensions 获取扩展项
|
||||||
// 返回: []Extension 扩展项列表, error 错误信息
|
// 返回: []Extension 扩展项列表, error 错误信息
|
||||||
func (r *Reader) Extensions() ([]Extension, error) {
|
func (r *Reader) Extensions() ([]Extension, error) {
|
||||||
doc, err := r.Doc()
|
doc, err := r.Doc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(doc.Extensions.Path) != "" && doc.Extensions.Extension == nil {
|
||||||
|
var extensions Extensions
|
||||||
|
partPath, err := r.readDocumentPart(doc.Extensions.Path, &extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range extensions.Extension {
|
||||||
|
extension := &extensions.Extension[i]
|
||||||
|
for j := range extension.ExtendData {
|
||||||
|
extension.ExtendData[j] = resolveResourcePath(partPath, "", extension.ExtendData[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if extensions.Extension == nil {
|
||||||
|
extensions.Extension = []Extension{}
|
||||||
|
}
|
||||||
|
doc.Extensions.Extension = extensions.Extension
|
||||||
|
}
|
||||||
return doc.Extensions.Extension, nil
|
return doc.Extensions.Extension, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-15
@@ -71,9 +71,16 @@ func (r *Renderer) RenderToPDF(page *PageContent, writer io.Writer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
box, err := r.GetPageBox(page)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pages := []pdfPage{{Content: page, Box: box}}
|
||||||
|
navigation := newPDFNavigation(r, r.Reader.doc, pages)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
p := pdf.New(&buf, c.W, c.H, nil)
|
p := pdf.New(&buf, c.W, c.H, nil)
|
||||||
p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
|
p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
|
||||||
|
navigation.apply(p, 0)
|
||||||
c.RenderTo(p)
|
c.RenderTo(p)
|
||||||
if err := p.Close(); err != nil {
|
if err := p.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -104,28 +111,33 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer) error {
|
|||||||
if len(doc.Pages.Page) == 0 {
|
if len(doc.Pages.Page) == 0 {
|
||||||
return fmt.Errorf("no pages found")
|
return fmt.Errorf("no pages found")
|
||||||
}
|
}
|
||||||
|
pages := make([]pdfPage, len(doc.Pages.Page))
|
||||||
|
for i, pageRef := range doc.Pages.Page {
|
||||||
|
page, err := r.Reader.PageContent(pageRef)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read page %d: %w", i+1, err)
|
||||||
|
}
|
||||||
|
box, err := r.GetPageBox(page)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read page %d area: %w", i+1, err)
|
||||||
|
}
|
||||||
|
pages[i] = pdfPage{Content: page, Box: box}
|
||||||
|
}
|
||||||
|
navigation := newPDFNavigation(r, doc, pages)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
var p *pdf.PDF
|
p := pdf.New(&buf, pages[0].Box.W, pages[0].Box.H, nil)
|
||||||
for _, pgRef := range doc.Pages.Page {
|
p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
|
||||||
page, err := r.Reader.PageContent(pgRef)
|
for i, page := range pages {
|
||||||
|
c, err := r.renderPage(page.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
return fmt.Errorf("failed to render page %d: %w", i+1, err)
|
||||||
}
|
}
|
||||||
c, err := r.renderPage(page)
|
if i > 0 {
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if p == nil {
|
|
||||||
p = pdf.New(&buf, c.W, c.H, nil)
|
|
||||||
p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
|
|
||||||
} else {
|
|
||||||
p.NewPage(c.W, c.H)
|
p.NewPage(c.W, c.H)
|
||||||
}
|
}
|
||||||
|
navigation.apply(p, i)
|
||||||
c.RenderTo(p)
|
c.RenderTo(p)
|
||||||
}
|
}
|
||||||
if p == nil {
|
|
||||||
return fmt.Errorf("failed to render any page")
|
|
||||||
}
|
|
||||||
if err := p.Close(); err != nil {
|
if err := p.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,322 @@
|
|||||||
|
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ofdgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/tdewolff/canvas"
|
||||||
|
"github.com/tdewolff/canvas/renderers/pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pdfPage PDF页面数据
|
||||||
|
type pdfPage struct {
|
||||||
|
Content *PageContent
|
||||||
|
Box Box
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfNavigation PDF导航信息
|
||||||
|
type pdfNavigation struct {
|
||||||
|
Anchor map[int][]pdfAnchor
|
||||||
|
Link map[int][]pdfLink
|
||||||
|
Outline map[int][]pdfOutline
|
||||||
|
nextID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfAnchor PDF跳转目标
|
||||||
|
type pdfAnchor struct {
|
||||||
|
Name string
|
||||||
|
Rect canvas.Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfLink PDF链接
|
||||||
|
type pdfLink struct {
|
||||||
|
URI string
|
||||||
|
Rect canvas.Rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfOutline PDF大纲
|
||||||
|
type pdfOutline struct {
|
||||||
|
Name string
|
||||||
|
Level int
|
||||||
|
Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfActionSource PDF动作来源
|
||||||
|
type pdfActionSource struct {
|
||||||
|
Box Box
|
||||||
|
Actions []Action
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPDFNavigation 创建PDF导航信息
|
||||||
|
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
|
||||||
|
// 返回: *pdfNavigation PDF导航信息
|
||||||
|
func newPDFNavigation(renderer *Renderer, doc *Document, pages []pdfPage) *pdfNavigation {
|
||||||
|
navigation := &pdfNavigation{
|
||||||
|
Anchor: make(map[int][]pdfAnchor),
|
||||||
|
Link: make(map[int][]pdfLink),
|
||||||
|
Outline: make(map[int][]pdfOutline),
|
||||||
|
}
|
||||||
|
pageIndex := make(map[string]int, len(pages))
|
||||||
|
for i, page := range pages {
|
||||||
|
pageIndex[page.Content.ID] = i
|
||||||
|
}
|
||||||
|
bookmarks := make(map[string]Dest)
|
||||||
|
if doc != nil {
|
||||||
|
for _, bookmark := range doc.Bookmarks.Bookmark {
|
||||||
|
bookmarks[bookmark.Name] = bookmark.Dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, page := range pages {
|
||||||
|
sources := pageActionSources(page)
|
||||||
|
if renderer.RenderAnnotations {
|
||||||
|
sources = append(sources, annotationActionSources(renderer.Reader.Annots[page.Content.ID])...)
|
||||||
|
}
|
||||||
|
for _, source := range sources {
|
||||||
|
rect := pdfSourceRect(source.Box, page.Box.H)
|
||||||
|
for _, action := range source.Actions {
|
||||||
|
if action.Event != "CLICK" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
navigation.addAction(i, rect, action, bookmarks, pageIndex, pages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if doc != nil {
|
||||||
|
navigation.addOutlines(doc.Outlines.OutlineElem, 0, bookmarks, pageIndex, pages)
|
||||||
|
}
|
||||||
|
return navigation
|
||||||
|
}
|
||||||
|
|
||||||
|
// addAction 添加PDF动作
|
||||||
|
// 入参: page 页面索引, rect 动作区域, action 动作, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
||||||
|
func (n *pdfNavigation) addAction(page int, rect canvas.Rect, action Action, bookmarks map[string]Dest, pageIndex map[string]int, pages []pdfPage) {
|
||||||
|
if action.Goto != nil {
|
||||||
|
dest := gotoDest(action.Goto, bookmarks)
|
||||||
|
if dest == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target, ok := pageIndex[dest.PageID]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetRect, ok := pdfDestRect(*dest, pages[target].Box.H)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("ofdgo-dest-%d", n.nextID)
|
||||||
|
n.nextID++
|
||||||
|
n.Anchor[target] = append(n.Anchor[target], pdfAnchor{Name: name, Rect: targetRect})
|
||||||
|
n.Link[page] = append(n.Link[page], pdfLink{URI: "#" + name, Rect: rect})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if action.URI != nil && action.URI.URI != "" {
|
||||||
|
n.Link[page] = append(n.Link[page], pdfLink{URI: resolveActionURI(*action.URI), Rect: rect})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addOutlines 添加PDF大纲
|
||||||
|
// 入参: outlines 大纲节点, level 节点层级, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
|
||||||
|
func (n *pdfNavigation) addOutlines(outlines []OutlineElem, level int, bookmarks map[string]Dest, pageIndex map[string]int, pages []pdfPage) {
|
||||||
|
for _, outline := range outlines {
|
||||||
|
if dest := outlineDest(outline, bookmarks); dest != nil {
|
||||||
|
if page, ok := pageIndex[dest.PageID]; ok {
|
||||||
|
n.Outline[page] = append(n.Outline[page], pdfOutline{
|
||||||
|
Name: outline.Title,
|
||||||
|
Level: level,
|
||||||
|
Y: pdfDestY(*dest, pages[page].Box.H),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.addOutlines(outline.OutlineElem, level+1, bookmarks, pageIndex, pages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply 应用PDF导航信息
|
||||||
|
// 入参: renderer PDF渲染器, page 页面索引
|
||||||
|
func (n *pdfNavigation) apply(renderer *pdf.PDF, page int) {
|
||||||
|
for _, anchor := range n.Anchor[page] {
|
||||||
|
renderer.AddAnchor(anchor.Name, anchor.Rect)
|
||||||
|
}
|
||||||
|
for _, link := range n.Link[page] {
|
||||||
|
renderer.AddLink(link.URI, link.Rect)
|
||||||
|
}
|
||||||
|
for _, outline := range n.Outline[page] {
|
||||||
|
renderer.AddOutline(outline.Name, outline.Level, outline.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pageActionSources 获取页面动作来源
|
||||||
|
// 入参: page 页面数据
|
||||||
|
// 返回: []pdfActionSource 动作来源
|
||||||
|
func pageActionSources(page pdfPage) []pdfActionSource {
|
||||||
|
sources := make([]pdfActionSource, 0)
|
||||||
|
if len(page.Content.Actions) > 0 {
|
||||||
|
sources = append(sources, pdfActionSource{
|
||||||
|
Box: Box{W: page.Box.W, H: page.Box.H},
|
||||||
|
Actions: page.Content.Actions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, layer := range page.Content.Content.Layer {
|
||||||
|
for _, object := range layer.Objects {
|
||||||
|
sources = appendGraphicActionSources(sources, object, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotationActionSources 获取注释动作来源
|
||||||
|
// 入参: annotations 页面注释
|
||||||
|
// 返回: []pdfActionSource 动作来源
|
||||||
|
func annotationActionSources(annotations []Annotation) []pdfActionSource {
|
||||||
|
sources := make([]pdfActionSource, 0)
|
||||||
|
for _, annotation := range annotations {
|
||||||
|
box, err := ParseBox(annotation.Appearance.Boundary)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, object := range annotation.Appearance.Objects {
|
||||||
|
sources = appendGraphicActionSources(sources, object, &box)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendGraphicActionSources 添加图形对象动作来源
|
||||||
|
// 入参: sources 动作来源, object 图形对象, box 指定动作区域
|
||||||
|
// 返回: []pdfActionSource 动作来源
|
||||||
|
func appendGraphicActionSources(sources []pdfActionSource, object GraphicObject, box *Box) []pdfActionSource {
|
||||||
|
var boundary string
|
||||||
|
var actions []Action
|
||||||
|
var children []GraphicObject
|
||||||
|
switch object.Type {
|
||||||
|
case "TextObject":
|
||||||
|
boundary = object.TextObject.Boundary
|
||||||
|
actions = object.TextObject.Actions
|
||||||
|
case "PathObject":
|
||||||
|
boundary = object.PathObject.Boundary
|
||||||
|
actions = object.PathObject.Actions
|
||||||
|
case "ImageObject":
|
||||||
|
boundary = object.ImageObject.Boundary
|
||||||
|
actions = object.ImageObject.Actions
|
||||||
|
case "CompositeGraphicUnit", "CompositeObject":
|
||||||
|
boundary = object.CompositeGraphicUnit.Boundary
|
||||||
|
actions = object.CompositeGraphicUnit.Actions
|
||||||
|
children = object.CompositeGraphicUnit.Objects
|
||||||
|
}
|
||||||
|
sourceBox := box
|
||||||
|
if sourceBox == nil && boundary != "" {
|
||||||
|
if value, err := ParseBox(boundary); err == nil {
|
||||||
|
sourceBox = &value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sourceBox != nil && len(actions) > 0 {
|
||||||
|
sources = append(sources, pdfActionSource{Box: *sourceBox, Actions: actions})
|
||||||
|
}
|
||||||
|
for _, child := range children {
|
||||||
|
sources = appendGraphicActionSources(sources, child, box)
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
// gotoDest 获取文档内跳转目标
|
||||||
|
// 入参: action 跳转动作, bookmarks 书签
|
||||||
|
// 返回: *Dest 跳转目标
|
||||||
|
func gotoDest(action *Goto, bookmarks map[string]Dest) *Dest {
|
||||||
|
if action.Dest != nil {
|
||||||
|
return action.Dest
|
||||||
|
}
|
||||||
|
if action.Bookmark != nil {
|
||||||
|
if dest, ok := bookmarks[action.Bookmark.Name]; ok {
|
||||||
|
return &dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// outlineDest 获取大纲跳转目标
|
||||||
|
// 入参: outline 大纲节点, bookmarks 书签
|
||||||
|
// 返回: *Dest 跳转目标
|
||||||
|
func outlineDest(outline OutlineElem, bookmarks map[string]Dest) *Dest {
|
||||||
|
for _, action := range outline.Actions {
|
||||||
|
if action.Goto != nil {
|
||||||
|
if dest := gotoDest(action.Goto, bookmarks); dest != nil {
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, child := range outline.OutlineElem {
|
||||||
|
if dest := outlineDest(child, bookmarks); dest != nil {
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveActionURI 解析URI动作地址
|
||||||
|
// 入参: action URI动作
|
||||||
|
// 返回: string URI地址
|
||||||
|
func resolveActionURI(action URI) string {
|
||||||
|
if action.Base == "" {
|
||||||
|
return action.URI
|
||||||
|
}
|
||||||
|
base, err := url.Parse(action.Base)
|
||||||
|
if err != nil {
|
||||||
|
return action.URI
|
||||||
|
}
|
||||||
|
target, err := url.Parse(action.URI)
|
||||||
|
if err != nil {
|
||||||
|
return action.URI
|
||||||
|
}
|
||||||
|
return base.ResolveReference(target).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfSourceRect 转换PDF动作区域
|
||||||
|
// 入参: box OFD区域, pageH 页面高度
|
||||||
|
// 返回: canvas.Rect PDF区域
|
||||||
|
func pdfSourceRect(box Box, pageH float64) canvas.Rect {
|
||||||
|
return canvas.RectFromSize(box.X, pageH-box.Y-box.H, box.W, box.H)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfDestRect 转换PDF跳转目标
|
||||||
|
// 入参: dest OFD跳转目标, pageH 页面高度
|
||||||
|
// 返回: canvas.Rect PDF目标区域, bool 是否支持
|
||||||
|
func pdfDestRect(dest Dest, pageH float64) (canvas.Rect, bool) {
|
||||||
|
switch dest.Type {
|
||||||
|
case "XYZ":
|
||||||
|
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Top, X1: dest.Left, Y1: pageH - dest.Top}, true
|
||||||
|
case "Fit":
|
||||||
|
return canvas.Rect{}, true
|
||||||
|
case "FitH":
|
||||||
|
return canvas.Rect{Y0: pageH - dest.Top, Y1: pageH - dest.Top}, true
|
||||||
|
case "FitV":
|
||||||
|
return canvas.Rect{X0: dest.Left, X1: dest.Left}, true
|
||||||
|
case "FitR":
|
||||||
|
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Bottom, X1: dest.Right, Y1: pageH - dest.Top}, true
|
||||||
|
}
|
||||||
|
return canvas.Rect{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfDestY 获取PDF大纲目标位置
|
||||||
|
// 入参: dest OFD跳转目标, pageH 页面高度
|
||||||
|
// 返回: float64 PDF纵坐标
|
||||||
|
func pdfDestY(dest Dest, pageH float64) float64 {
|
||||||
|
switch dest.Type {
|
||||||
|
case "XYZ", "FitH", "FitR":
|
||||||
|
return pageH - dest.Top
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -104,4 +104,5 @@ type CompositeGraphicUnit struct {
|
|||||||
ImageObject []ImageObject `xml:"ImageObject"`
|
ImageObject []ImageObject `xml:"ImageObject"`
|
||||||
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
||||||
Clips *Clips `xml:"Clips"`
|
Clips *Clips `xml:"Clips"`
|
||||||
|
Actions []Action `xml:"Actions>Action"`
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ const (
|
|||||||
// Signatures 签名列表
|
// Signatures 签名列表
|
||||||
type Signatures struct {
|
type Signatures struct {
|
||||||
XMLName xml.Name `xml:"Signatures"`
|
XMLName xml.Name `xml:"Signatures"`
|
||||||
MaxSignId string `xml:"MaxSignId"`
|
MaxSignID string `xml:"MaxSignId"`
|
||||||
List []Signature `xml:"Signature"`
|
List []Signature `xml:"Signature"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+184
-21
@@ -15,7 +15,6 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -31,17 +30,32 @@ type sm2PublicKey struct {
|
|||||||
Y *big.Int
|
Y *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sm2Curve SM2椭圆曲线
|
||||||
|
type sm2Curve struct {
|
||||||
|
P *big.Int
|
||||||
|
N *big.Int
|
||||||
|
B *big.Int
|
||||||
|
Gx *big.Int
|
||||||
|
Gy *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// sm2Point SM2雅可比坐标点
|
||||||
|
type sm2Point struct {
|
||||||
|
X *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
Z *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
// newSM2P256 创建SM2椭圆曲线
|
// newSM2P256 创建SM2椭圆曲线
|
||||||
// 返回: elliptic.Curve SM2椭圆曲线
|
// 返回: *sm2Curve SM2椭圆曲线
|
||||||
func newSM2P256() elliptic.Curve {
|
func newSM2P256() *sm2Curve {
|
||||||
c := &elliptic.CurveParams{Name: "SM2-P-256"}
|
return &sm2Curve{
|
||||||
c.P = sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF")
|
P: sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"),
|
||||||
c.N = sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123")
|
N: sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"),
|
||||||
c.B = sm2Big("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93")
|
B: sm2Big("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"),
|
||||||
c.Gx = sm2Big("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7")
|
Gx: sm2Big("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7"),
|
||||||
c.Gy = sm2Big("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0")
|
Gy: sm2Big("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"),
|
||||||
c.BitSize = 256
|
}
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sm2Big 解析SM2大整数常量
|
// sm2Big 解析SM2大整数常量
|
||||||
@@ -85,11 +99,11 @@ func parseSM2Signature(sig []byte) (*big.Int, *big.Int, bool) {
|
|||||||
// 入参: pub 公钥, userID 用户标识, msg 原文, r R值, s S值
|
// 入参: pub 公钥, userID 用户标识, msg 原文, r R值, s S值
|
||||||
// 返回: bool 是否验证通过
|
// 返回: bool 是否验证通过
|
||||||
func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool {
|
func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool {
|
||||||
n := sm2P256.Params().N
|
n := sm2P256.N
|
||||||
if r.Sign() <= 0 || s.Sign() <= 0 || r.Cmp(n) >= 0 || s.Cmp(n) >= 0 {
|
if r.Sign() <= 0 || s.Sign() <= 0 || r.Cmp(n) >= 0 || s.Cmp(n) >= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if pub.X == nil || pub.Y == nil || !sm2P256.IsOnCurve(pub.X, pub.Y) {
|
if pub.X == nil || pub.Y == nil || !sm2P256.isOnCurve(pub.X, pub.Y) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
e := new(big.Int).SetBytes(sm2MessageDigest(pub, userID, msg))
|
e := new(big.Int).SetBytes(sm2MessageDigest(pub, userID, msg))
|
||||||
@@ -98,10 +112,8 @@ func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool {
|
|||||||
if t.Sign() == 0 {
|
if t.Sign() == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
x1, y1 := sm2P256.ScalarBaseMult(s.Bytes())
|
x, ok := sm2P256.combinedMult(pub.X, pub.Y, s, t)
|
||||||
x2, y2 := sm2P256.ScalarMult(pub.X, pub.Y, t.Bytes())
|
if !ok {
|
||||||
x, _ := sm2P256.Add(x1, y1, x2, y2)
|
|
||||||
if x == nil {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
v := new(big.Int).Add(e, x)
|
v := new(big.Int).Add(e, x)
|
||||||
@@ -132,9 +144,9 @@ func sm2ZA(pub sm2PublicKey, userID []byte) []byte {
|
|||||||
h.Write(entl[:])
|
h.Write(entl[:])
|
||||||
h.Write(userID)
|
h.Write(userID)
|
||||||
h.Write(sm2Fixed(sm2A()))
|
h.Write(sm2Fixed(sm2A()))
|
||||||
h.Write(sm2Fixed(sm2P256.Params().B))
|
h.Write(sm2Fixed(sm2P256.B))
|
||||||
h.Write(sm2Fixed(sm2P256.Params().Gx))
|
h.Write(sm2Fixed(sm2P256.Gx))
|
||||||
h.Write(sm2Fixed(sm2P256.Params().Gy))
|
h.Write(sm2Fixed(sm2P256.Gy))
|
||||||
h.Write(sm2Fixed(pub.X))
|
h.Write(sm2Fixed(pub.X))
|
||||||
h.Write(sm2Fixed(pub.Y))
|
h.Write(sm2Fixed(pub.Y))
|
||||||
return h.Sum(nil)
|
return h.Sum(nil)
|
||||||
@@ -143,7 +155,158 @@ func sm2ZA(pub sm2PublicKey, userID []byte) []byte {
|
|||||||
// sm2A 获取SM2曲线A参数
|
// sm2A 获取SM2曲线A参数
|
||||||
// 返回: *big.Int 曲线A参数
|
// 返回: *big.Int 曲线A参数
|
||||||
func sm2A() *big.Int {
|
func sm2A() *big.Int {
|
||||||
return new(big.Int).Sub(sm2P256.Params().P, big.NewInt(3))
|
return new(big.Int).Sub(sm2P256.P, big.NewInt(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOnCurve 判断点是否位于SM2曲线
|
||||||
|
// 入参: x X坐标, y Y坐标
|
||||||
|
// 返回: bool 是否位于曲线
|
||||||
|
func (c *sm2Curve) isOnCurve(x, y *big.Int) bool {
|
||||||
|
if x.Sign() < 0 || y.Sign() < 0 || x.Cmp(c.P) >= 0 || y.Cmp(c.P) >= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
left := c.fieldSquare(y)
|
||||||
|
right := c.fieldAdd(c.fieldSub(c.fieldMul(c.fieldSquare(x), x), c.fieldScale(x, 3)), c.B)
|
||||||
|
return left.Cmp(right) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// combinedMult 计算sG+tP
|
||||||
|
// 入参: x 公钥X坐标, y 公钥Y坐标, s 标量S, t 标量T
|
||||||
|
// 返回: *big.Int 结果X坐标, bool 是否计算成功
|
||||||
|
func (c *sm2Curve) combinedMult(x, y, s, t *big.Int) (*big.Int, bool) {
|
||||||
|
base := c.scalarMult(c.Gx, c.Gy, s.Bytes())
|
||||||
|
public := c.scalarMult(x, y, t.Bytes())
|
||||||
|
result := c.add(base, public)
|
||||||
|
affineX, _, ok := c.affine(result)
|
||||||
|
return affineX, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// scalarMult 计算椭圆曲线标量乘法
|
||||||
|
// 入参: x 点X坐标, y 点Y坐标, scalar 标量
|
||||||
|
// 返回: sm2Point 雅可比坐标点
|
||||||
|
func (c *sm2Curve) scalarMult(x, y *big.Int, scalar []byte) sm2Point {
|
||||||
|
result := c.infinity()
|
||||||
|
point := sm2Point{X: new(big.Int).Set(x), Y: new(big.Int).Set(y), Z: big.NewInt(1)}
|
||||||
|
for _, value := range scalar {
|
||||||
|
for bit := 7; bit >= 0; bit-- {
|
||||||
|
result = c.double(result)
|
||||||
|
if value&(1<<uint(bit)) != 0 {
|
||||||
|
result = c.add(result, point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 计算椭圆曲线点加法
|
||||||
|
// 入参: p 点P, q 点Q
|
||||||
|
// 返回: sm2Point 结果点
|
||||||
|
func (c *sm2Curve) add(p, q sm2Point) sm2Point {
|
||||||
|
if p.Z.Sign() == 0 {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if q.Z.Sign() == 0 {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
z1z1 := c.fieldSquare(p.Z)
|
||||||
|
z2z2 := c.fieldSquare(q.Z)
|
||||||
|
u1 := c.fieldMul(p.X, z2z2)
|
||||||
|
u2 := c.fieldMul(q.X, z1z1)
|
||||||
|
s1 := c.fieldMul(p.Y, c.fieldMul(q.Z, z2z2))
|
||||||
|
s2 := c.fieldMul(q.Y, c.fieldMul(p.Z, z1z1))
|
||||||
|
if u1.Cmp(u2) == 0 {
|
||||||
|
if s1.Cmp(s2) != 0 {
|
||||||
|
return c.infinity()
|
||||||
|
}
|
||||||
|
return c.double(p)
|
||||||
|
}
|
||||||
|
h := c.fieldSub(u2, u1)
|
||||||
|
i := c.fieldSquare(c.fieldScale(h, 2))
|
||||||
|
j := c.fieldMul(h, i)
|
||||||
|
r := c.fieldScale(c.fieldSub(s2, s1), 2)
|
||||||
|
v := c.fieldMul(u1, i)
|
||||||
|
x := c.fieldSub(c.fieldSub(c.fieldSquare(r), j), c.fieldScale(v, 2))
|
||||||
|
y := c.fieldSub(c.fieldMul(r, c.fieldSub(v, x)), c.fieldScale(c.fieldMul(s1, j), 2))
|
||||||
|
z := c.fieldMul(c.fieldSub(c.fieldSub(c.fieldSquare(c.fieldAdd(p.Z, q.Z)), z1z1), z2z2), h)
|
||||||
|
return sm2Point{X: x, Y: y, Z: z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// double 计算椭圆曲线点倍加
|
||||||
|
// 入参: p 点P
|
||||||
|
// 返回: sm2Point 结果点
|
||||||
|
func (c *sm2Curve) double(p sm2Point) sm2Point {
|
||||||
|
if p.Z.Sign() == 0 || p.Y.Sign() == 0 {
|
||||||
|
return c.infinity()
|
||||||
|
}
|
||||||
|
delta := c.fieldSquare(p.Z)
|
||||||
|
gamma := c.fieldSquare(p.Y)
|
||||||
|
beta := c.fieldMul(p.X, gamma)
|
||||||
|
alpha := c.fieldScale(c.fieldMul(c.fieldSub(p.X, delta), c.fieldAdd(p.X, delta)), 3)
|
||||||
|
x := c.fieldSub(c.fieldSquare(alpha), c.fieldScale(beta, 8))
|
||||||
|
z := c.fieldSub(c.fieldSub(c.fieldSquare(c.fieldAdd(p.Y, p.Z)), gamma), delta)
|
||||||
|
y := c.fieldSub(c.fieldMul(alpha, c.fieldSub(c.fieldScale(beta, 4), x)), c.fieldScale(c.fieldSquare(gamma), 8))
|
||||||
|
return sm2Point{X: x, Y: y, Z: z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// affine 将雅可比坐标转换为仿射坐标
|
||||||
|
// 入参: p 雅可比坐标点
|
||||||
|
// 返回: *big.Int X坐标, *big.Int Y坐标, bool 是否转换成功
|
||||||
|
func (c *sm2Curve) affine(p sm2Point) (*big.Int, *big.Int, bool) {
|
||||||
|
if p.Z.Sign() == 0 {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
z := new(big.Int).ModInverse(p.Z, c.P)
|
||||||
|
if z == nil {
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
z2 := c.fieldSquare(z)
|
||||||
|
x := c.fieldMul(p.X, z2)
|
||||||
|
y := c.fieldMul(p.Y, c.fieldMul(z2, z))
|
||||||
|
return x, y, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// infinity 获取无穷远点
|
||||||
|
// 返回: sm2Point 无穷远点
|
||||||
|
func (c *sm2Curve) infinity() sm2Point {
|
||||||
|
return sm2Point{X: new(big.Int), Y: new(big.Int), Z: new(big.Int)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldAdd 计算有限域加法
|
||||||
|
// 入参: x 左操作数, y 右操作数
|
||||||
|
// 返回: *big.Int 计算结果
|
||||||
|
func (c *sm2Curve) fieldAdd(x, y *big.Int) *big.Int {
|
||||||
|
value := new(big.Int).Add(x, y)
|
||||||
|
return value.Mod(value, c.P)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldSub 计算有限域减法
|
||||||
|
// 入参: x 左操作数, y 右操作数
|
||||||
|
// 返回: *big.Int 计算结果
|
||||||
|
func (c *sm2Curve) fieldSub(x, y *big.Int) *big.Int {
|
||||||
|
value := new(big.Int).Sub(x, y)
|
||||||
|
return value.Mod(value, c.P)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldMul 计算有限域乘法
|
||||||
|
// 入参: x 左操作数, y 右操作数
|
||||||
|
// 返回: *big.Int 计算结果
|
||||||
|
func (c *sm2Curve) fieldMul(x, y *big.Int) *big.Int {
|
||||||
|
value := new(big.Int).Mul(x, y)
|
||||||
|
return value.Mod(value, c.P)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldSquare 计算有限域平方
|
||||||
|
// 入参: x 操作数
|
||||||
|
// 返回: *big.Int 计算结果
|
||||||
|
func (c *sm2Curve) fieldSquare(x *big.Int) *big.Int {
|
||||||
|
return c.fieldMul(x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldScale 计算有限域整数倍
|
||||||
|
// 入参: x 操作数, scale 倍数
|
||||||
|
// 返回: *big.Int 计算结果
|
||||||
|
func (c *sm2Curve) fieldScale(x *big.Int, scale int64) *big.Int {
|
||||||
|
return c.fieldMul(x, big.NewInt(scale))
|
||||||
}
|
}
|
||||||
|
|
||||||
// sm2Fixed 转换为SM2固定长度字节
|
// sm2Fixed 转换为SM2固定长度字节
|
||||||
|
|||||||
Reference in New Issue
Block a user