1 Star 0 Fork 0

广州捷仓科技有限公司/gofpdi

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
writer.go 12.68 KB
一键复制 编辑 原始数据 按行查看 历史
jiecang 提交于 2024-10-08 09:27 +08:00 . 开放GetPageRotation函数
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
package gofpdi
import (
"bufio"
"bytes"
"compress/zlib"
"crypto/sha1"
"encoding/hex"
"fmt"
"math"
"os"
"github.com/pkg/errors"
)
type PdfWriter struct {
f *os.File
w *bufio.Writer
r *PdfReader
k float64
tpls []*PdfTemplate
m int
n int
offsets map[int]int
offset int
result map[int]string
// Keep track of which objects have already been written
obj_stack map[int]*PdfValue
don_obj_stack map[int]*PdfValue
written_objs map[*PdfObjectId][]byte
written_obj_pos map[*PdfObjectId]map[int]string
current_obj *PdfObject
current_obj_id int
tpl_id_offset int
use_hash bool
}
type PdfObjectId struct {
id int
hash string
}
type PdfObject struct {
id *PdfObjectId
buffer *bytes.Buffer
}
func (this *PdfWriter) SetTplIdOffset(n int) {
this.tpl_id_offset = n
}
func (this *PdfWriter) Init() {
this.k = 1
this.obj_stack = make(map[int]*PdfValue, 0)
this.don_obj_stack = make(map[int]*PdfValue, 0)
this.tpls = make([]*PdfTemplate, 0)
this.written_objs = make(map[*PdfObjectId][]byte, 0)
this.written_obj_pos = make(map[*PdfObjectId]map[int]string, 0)
this.current_obj = new(PdfObject)
}
func (this *PdfWriter) SetUseHash(b bool) {
this.use_hash = b
}
func (this *PdfWriter) SetNextObjectID(id int) {
this.n = id - 1
}
func NewPdfWriter(filename string) (*PdfWriter, error) {
writer := &PdfWriter{}
writer.Init()
if filename != "" {
var err error
f, err := os.Create(filename)
if err != nil {
return nil, errors.Wrap(err, "Unable to create filename: "+filename)
}
writer.f = f
writer.w = bufio.NewWriter(f)
}
return writer, nil
}
// Done with parsing. Now, create templates.
type PdfTemplate struct {
Id int
Reader *PdfReader
Resources *PdfValue
Buffer string
Box map[string]float64
Boxes map[string]map[string]float64
X float64
Y float64
W float64
H float64
Rotation int
N int
}
func (this *PdfWriter) GetImportedObjects() map[*PdfObjectId][]byte {
return this.written_objs
}
// For each object (uniquely identified by a sha1 hash), return the positions
// of each hash within the object, to be replaced with pdf object ids (integers)
func (this *PdfWriter) GetImportedObjHashPos() map[*PdfObjectId]map[int]string {
return this.written_obj_pos
}
func (this *PdfWriter) ClearImportedObjects() {
this.written_objs = make(map[*PdfObjectId][]byte, 0)
}
// Create a PdfTemplate object from a page number (e.g. 1) and a boxName (e.g. MediaBox)
func (this *PdfWriter) ImportPage(reader *PdfReader, pageno int, boxName string) (int, error) {
var err error
var ascii85Err error
// Set default scale to 1
this.k = 1
// Get all page boxes
pageBoxes, err := reader.getPageBoxes(1, this.k)
if err != nil {
return -1, errors.Wrap(err, "Failed to get page boxes")
}
// If requested box name does not exist for this page, use an alternate box
if _, ok := pageBoxes[boxName]; !ok {
if boxName == "/BleedBox" || boxName == "/TrimBox" || boxName == "ArtBox" {
boxName = "/CropBox"
} else if boxName == "/CropBox" {
boxName = "/MediaBox"
}
}
// If the requested box name or an alternate box name cannot be found, trigger an error
// TODO: Improve error handling
if _, ok := pageBoxes[boxName]; !ok {
return -1, errors.New("Box not found: " + boxName)
}
pageResources, err := reader.getPageResources(pageno)
if err != nil {
return -1, errors.Wrap(err, "Failed to get page resources")
}
content, err := reader.getContent(pageno)
if err != nil {
switch err.(type) {
case *Ascii85DecodeError:
ascii85Err = err
default:
return -1, errors.Wrap(err, "Failed to get content")
}
}
// Set template values
tpl := &PdfTemplate{}
tpl.Reader = reader
tpl.Resources = pageResources
tpl.Buffer = content
tpl.Box = pageBoxes[boxName]
tpl.Boxes = pageBoxes
tpl.X = 0
tpl.Y = 0
tpl.W = tpl.Box["w"]
tpl.H = tpl.Box["h"]
// Set template rotation
rotation, err := reader.GetPageRotation(pageno)
if err != nil {
return -1, errors.Wrap(err, "Failed to get page rotation")
}
angle := rotation.Int % 360
// Normalize angle
if angle != 0 {
steps := angle / 90
w := tpl.W
h := tpl.H
if steps%2 == 0 {
tpl.W = w
tpl.H = h
} else {
tpl.W = h
tpl.H = w
}
if angle < 0 {
angle += 360
}
tpl.Rotation = angle * -1
}
this.tpls = append(this.tpls, tpl)
// Return last template id
if ascii85Err != nil {
return len(this.tpls) - 1, ascii85Err
}
return len(this.tpls) - 1, nil
}
// Create a new object and keep track of the offset for the xref table
func (this *PdfWriter) newObj(objId int, onlyNewObj bool) {
if objId < 0 {
this.n++
objId = this.n
}
if !onlyNewObj {
// set current object id integer
this.current_obj_id = objId
// Create new PdfObject and PdfObjectId
this.current_obj = new(PdfObject)
this.current_obj.buffer = new(bytes.Buffer)
this.current_obj.id = new(PdfObjectId)
this.current_obj.id.id = objId
this.current_obj.id.hash = this.shaOfInt(objId)
this.written_obj_pos[this.current_obj.id] = make(map[int]string, 0)
}
}
func (this *PdfWriter) endObj() {
this.out("endobj")
this.written_objs[this.current_obj.id] = this.current_obj.buffer.Bytes()
this.current_obj_id = -1
}
func (this *PdfWriter) shaOfInt(i int) string {
hasher := sha1.New()
hasher.Write([]byte(fmt.Sprintf("%d-%d-%s", this.tpl_id_offset, i, this.r.sourceFile)))
sha := hex.EncodeToString(hasher.Sum(nil))
return sha
}
func (this *PdfWriter) outObjRef(objId int) {
sha := this.shaOfInt(objId)
// Keep track of object hash and position - to be replaced with actual object id (integer)
this.written_obj_pos[this.current_obj.id][this.current_obj.buffer.Len()] = sha
if this.use_hash {
this.current_obj.buffer.WriteString(sha)
} else {
this.current_obj.buffer.WriteString(fmt.Sprintf("%d", objId))
}
this.current_obj.buffer.WriteString(" 0 R ")
}
// Output PDF data with a newline
func (this *PdfWriter) out(s string) {
this.current_obj.buffer.WriteString(s)
this.current_obj.buffer.WriteString("\n")
}
// Output PDF data
func (this *PdfWriter) straightOut(s string) {
this.current_obj.buffer.WriteString(s)
}
// Output a PdfValue
func (this *PdfWriter) writeValue(value *PdfValue) {
switch value.Type {
case PDF_TYPE_TOKEN:
this.straightOut(value.Token + " ")
break
case PDF_TYPE_NUMERIC:
this.straightOut(fmt.Sprintf("%d", value.Int) + " ")
break
case PDF_TYPE_REAL:
this.straightOut(fmt.Sprintf("%F", value.Real) + " ")
break
case PDF_TYPE_ARRAY:
this.straightOut("[")
for i := 0; i < len(value.Array); i++ {
this.writeValue(value.Array[i])
}
this.out("]")
break
case PDF_TYPE_DICTIONARY:
this.straightOut("<<")
for k, v := range value.Dictionary {
this.straightOut(k + " ")
this.writeValue(v)
}
this.straightOut(">>")
break
case PDF_TYPE_OBJREF:
// An indirect object reference. Fill the object stack if needed.
// Check to see if object already exists on the don_obj_stack.
if _, ok := this.don_obj_stack[value.Id]; !ok {
this.newObj(-1, true)
this.obj_stack[value.Id] = &PdfValue{Type: PDF_TYPE_OBJREF, Gen: value.Gen, Id: value.Id, NewId: this.n}
this.don_obj_stack[value.Id] = &PdfValue{Type: PDF_TYPE_OBJREF, Gen: value.Gen, Id: value.Id, NewId: this.n}
}
// Get object ID from don_obj_stack
objId := this.don_obj_stack[value.Id].NewId
this.outObjRef(objId)
//this.out(fmt.Sprintf("%d 0 R", objId))
break
case PDF_TYPE_STRING:
// A string
this.straightOut("(" + value.String + ")")
break
case PDF_TYPE_STREAM:
// A stream. First, output the stream dictionary, then the stream data itself.
this.writeValue(value.Value)
this.out("stream")
this.out(string(value.Stream.Bytes))
this.out("endstream")
break
case PDF_TYPE_HEX:
this.straightOut("<" + value.String + ">")
break
case PDF_TYPE_BOOLEAN:
if value.Bool {
this.straightOut("true")
} else {
this.straightOut("false")
}
break
case PDF_TYPE_NULL:
// The null object
this.straightOut("null ")
break
}
}
// Output Form XObjects (1 for each template)
// returns a map of template names (e.g. /GOFPDITPL1) to PdfObjectId
func (this *PdfWriter) PutFormXobjects(reader *PdfReader) (map[string]*PdfObjectId, error) {
// Set current reader
this.r = reader
var err error
var result = make(map[string]*PdfObjectId, 0)
compress := true
filter := ""
if compress {
filter = "/Filter /FlateDecode "
}
for i := 0; i < len(this.tpls); i++ {
tpl := this.tpls[i]
if tpl == nil {
return nil, errors.New("Template is nil")
}
var p string
if compress {
var b bytes.Buffer
w := zlib.NewWriter(&b)
w.Write([]byte(tpl.Buffer))
w.Close()
p = b.String()
} else {
p = tpl.Buffer
}
// Create new PDF object
this.newObj(-1, false)
cN := this.n // remember current "n"
tpl.N = this.n
// Return xobject form name and object position
pdfObjId := new(PdfObjectId)
pdfObjId.id = cN
pdfObjId.hash = this.shaOfInt(cN)
result[fmt.Sprintf("/GOFPDITPL%d", i+this.tpl_id_offset)] = pdfObjId
this.out("<<" + filter + "/Type /XObject")
this.out("/Subtype /Form")
this.out("/FormType 1")
this.out(fmt.Sprintf("/BBox [%.2F %.2F %.2F %.2F]", tpl.Box["llx"]*this.k, tpl.Box["lly"]*this.k, (tpl.Box["urx"]+tpl.X)*this.k, (tpl.Box["ury"]-tpl.Y)*this.k))
var c, s, tx, ty float64
c = 1
// Handle rotated pages
if tpl.Box != nil {
tx = -tpl.Box["llx"]
ty = -tpl.Box["lly"]
if tpl.Rotation != 0 {
angle := float64(tpl.Rotation) * math.Pi / 180.0
c = math.Cos(float64(angle))
s = math.Sin(float64(angle))
switch tpl.Rotation {
case -90:
tx = -tpl.Box["lly"]
ty = tpl.Box["urx"]
break
case -180:
tx = tpl.Box["urx"]
ty = tpl.Box["ury"]
break
case -270:
tx = tpl.Box["ury"]
ty = -tpl.Box["llx"]
}
}
} else {
tx = -tpl.Box["x"] * 2
ty = tpl.Box["y"] * 2
}
tx *= this.k
ty *= this.k
if c != 1 || s != 0 || tx != 0 || ty != 0 {
this.out(fmt.Sprintf("/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]", c, s, -s, c, tx, ty))
}
// Now write resources
this.out("/Resources ")
if tpl.Resources != nil {
this.writeValue(tpl.Resources) // "n" will be changed
} else {
return nil, errors.New("Template resources are empty")
}
nN := this.n // remember new "n"
this.n = cN // reset to current "n"
this.out("/Length " + fmt.Sprintf("%d", len(p)) + " >>")
this.out("stream")
this.out(p)
this.out("endstream")
this.endObj()
this.n = nN // reset to new "n"
// Put imported objects, starting with the ones from the XObject's Resources,
// then from dependencies of those resources).
err = this.putImportedObjects(reader)
if err != nil {
return nil, errors.Wrap(err, "Failed to put imported objects")
}
}
return result, nil
}
func (this *PdfWriter) putImportedObjects(reader *PdfReader) error {
var err error
var nObj *PdfValue
// obj_stack will have new items added to it in the inner loop, so do another loop to check for extras
// TODO make the order of this the same every time
for {
atLeastOne := false
// FIXME: How to determine number of objects before this loop?
for i := 0; i < 9999; i++ {
k := i
v := this.obj_stack[i]
if v == nil {
continue
}
atLeastOne = true
nObj, err = reader.resolveObject(v)
if err != nil {
return errors.Wrap(err, "Unable to resolve object")
}
// New object with "NewId" field
this.newObj(v.NewId, false)
if nObj.Type == PDF_TYPE_STREAM {
this.writeValue(nObj)
} else {
this.writeValue(nObj.Value)
}
this.endObj()
// Remove from stack
this.obj_stack[k] = nil
}
if !atLeastOne {
break
}
}
return nil
}
// Get the calculated size of a template
// If one size is given, this method calculates the other one
func (this *PdfWriter) getTemplateSize(tplid int, _w float64, _h float64) map[string]float64 {
result := make(map[string]float64, 2)
tpl := this.tpls[tplid]
w := tpl.W
h := tpl.H
if _w == 0 && _h == 0 {
_w = w
_h = h
}
if _w == 0 {
_w = _h * w / h
}
if _h == 0 {
_h = _w * h / w
}
result["w"] = _w
result["h"] = _h
return result
}
func (this *PdfWriter) UseTemplate(tplid int, _x float64, _y float64, _w float64, _h float64) (string, float64, float64, float64, float64) {
tpl := this.tpls[tplid]
w := tpl.W
h := tpl.H
_x += tpl.X
_y += tpl.Y
wh := this.getTemplateSize(0, _w, _h)
_w = wh["w"]
_h = wh["h"]
tData := make(map[string]float64, 9)
tData["x"] = 0.0
tData["y"] = 0.0
tData["w"] = _w
tData["h"] = _h
tData["scaleX"] = (_w / w)
tData["scaleY"] = (_h / h)
tData["tx"] = _x
tData["ty"] = (0 - _y - _h)
tData["lty"] = (0 - _y - _h) - (0-h)*(_h/h)
return fmt.Sprintf("/GOFPDITPL%d", tplid+this.tpl_id_offset), tData["scaleX"], tData["scaleY"], tData["tx"] * this.k, tData["ty"] * this.k
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/jiecang-tech/gofpdi.git
git@gitee.com:jiecang-tech/gofpdi.git
jiecang-tech
gofpdi
gofpdi
v1.2.3

搜索帮助