diff options
author | Kurt Jung <kurt.w.jung@gmail.com> | 2015-10-20 12:26:10 -0400 |
---|---|---|
committer | Kurt Jung <kurt.w.jung@gmail.com> | 2015-10-20 12:26:10 -0400 |
commit | e2d935b7215daa9476f1ca385a15227f5f5a7011 (patch) | |
tree | 56b837702a1d3b036e081506513d79913389875f | |
parent | 26baf7d305543d6afe39d38f0b2c30054c9fa4e3 (diff) | |
parent | f45e5e9196bb313883f36e0ccc72f052f46aa37e (diff) |
Merge pull request #49 from jung-kurt/compare-reference-pdf
Compare example PDFs with reference copies
56 files changed, 551 insertions, 134 deletions
diff --git a/.gitattribute b/.gitattribute new file mode 100644 index 0000000..d72fd52 --- /dev/null +++ b/.gitattribute @@ -0,0 +1 @@ +*.pdf binary @@ -1,6 +1,5 @@ makefont/makefont pdf/*.pdf -pdf/reference look open pdf.txt @@ -99,7 +99,20 @@ complete. Please note that these examples run in the context of a test. In order run an example as a standalone application, you'll need to examine [fpdf_test.go](https://github.com/jung-kurt/gofpdf/blob/master/fpdf_test.go) for -some helper routines, for example exampleFilename and summary. +some helper routines, for example exampleFilename() and summary(). + +Example PDFs can be compared with reference copies in order to verify that they +have been generated as expected. This comparison will be performed if a PDF +with the same name as the example PDF is placed in the gofpdf/pdf/reference +directory. The routine that summarizes an example will look for this file and, +if found, will call ComparePDFFiles() to check the example PDF for equality +with its reference PDF. If differences exist between the two files they will be +printed to standard output and the test will fail. If the reference file is +missing, the comparison is considered to succeed. In order to successfully +compare two PDFs, the placement of internal resources must be consistent and +the internal creation timestamps must be the same. To do this, the methods +SetCatalogSort() and SetCreationDate() need to be called for both files. This +is done automatically for all examples. ##Nonstandard Fonts diff --git a/compare.go b/compare.go new file mode 100644 index 0000000..64cad1f --- /dev/null +++ b/compare.go @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung) + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package gofpdf + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "sort" +) + +type sortType struct { + length int + less func(int, int) bool + swap func(int, int) +} + +func (s *sortType) Len() int { + return s.length +} + +func (s *sortType) Less(i, j int) bool { + return s.less(i, j) +} + +func (s *sortType) Swap(i, j int) { + s.swap(i, j) +} + +func gensort(Len int, Less func(int, int) bool, Swap func(int, int)) { + sort.Sort(&sortType{length: Len, less: Less, swap: Swap}) +} + +func writeBytes(leadStr string, startPos int, sl []byte) { + var pos, max int + var b byte + fmt.Printf("%s %07x", leadStr, startPos) + max = len(sl) + for pos < max { + fmt.Printf(" ") + for k := 0; k < 8; k++ { + if pos < max { + fmt.Printf(" %02x", sl[pos]) + } else { + fmt.Printf(" ") + } + pos++ + } + } + fmt.Printf(" |") + pos = 0 + for pos < max { + b = sl[pos] + if b < 32 || b >= 128 { + b = '.' + } + fmt.Printf("%c", b) + pos++ + } + fmt.Printf("|\n") +} + +func checkBytes(pos int, sl1, sl2 []byte) (eq bool) { + eq = bytes.Equal(sl1, sl2) + if !eq { + writeBytes("<", pos, sl1) + writeBytes(">", pos, sl2) + } + return +} + +// compareBytes compares the bytes referred to by sl1 with those referred to by +// sl2. Nil is returned if the buffers are equal, otherwise an error. +func compareBytes(sl1, sl2 []byte) (err error) { + var posStart, posEnd, len1, len2, length int + var diffs bool + + len1 = len(sl1) + len2 = len(sl2) + length = len1 + if length > len2 { + length = len2 + } + for posStart < length-1 { + posEnd = posStart + 16 + if posEnd > length { + posEnd = length + } + if !checkBytes(posStart, sl1[posStart:posEnd], sl2[posStart:posEnd]) { + diffs = true + } + posStart = posEnd + } + if diffs { + err = fmt.Errorf("documents are different") + } + return +} + +// ComparePDFs reads and compares the full contents of the two specified +// readers. The comparison is done byte-for-byte with the exception of the +// CreationDate fields which are effectively ignored. Nil is returned if the +// buffers are equal, otherwise an error. +func ComparePDFs(rdr1, rdr2 io.Reader) (err error) { + var b1, b2 *bytes.Buffer + _, err = b1.ReadFrom(rdr1) + if err == nil { + _, err = b2.ReadFrom(rdr2) + if err == nil { + err = compareBytes(b1.Bytes(), b2.Bytes()) + } + } + return +} + +// ComparePDFFiles reads and compares the full contents of the two specified +// files. The comparison is done byte-for-byte with the exception of the +// CreationDate fields which are effectively ignored. Nil is returned if the +// file contents are equal, or if the second file is missing, otherwise an +// error. +func ComparePDFFiles(file1Str, file2Str string) (err error) { + var sl1, sl2 []byte + sl1, err = ioutil.ReadFile(file1Str) + if err == nil { + sl2, err = ioutil.ReadFile(file2Str) + if err == nil { + err = compareBytes(sl1, sl2) + } else { + // Second file is missing; treat this as success + err = nil + } + } + return +} @@ -19,6 +19,7 @@ package gofpdf import ( "bytes" "io" + "time" ) // Version of FPDF from which this package is derived @@ -150,7 +151,7 @@ type InitType struct { // from arbitrary locations (e.g. files, zip files, embedded font resources). // // Open provides an io.Reader for the specified font file (.json or .z). The file name -// does never include a path. Open returns an error if the specified file cannot be opened. +// never includes a path. Open returns an error if the specified file cannot be opened. type FontLoader interface { Open(name string) (io.Reader, error) } @@ -161,7 +162,7 @@ type Fpdf struct { n int // current object number offsets []int // array of object offsets templates map[int64]Template // templates used in this document - templateObjects map[int64]int //template object IDs within this document + templateObjects map[int64]int // template object IDs within this document buffer fmtBuffer // buffer holding in-memory PDF pages []*bytes.Buffer // slice[page] of page content; 1-based state int // current document state @@ -216,6 +217,7 @@ type Fpdf struct { author string // author keywords string // keywords creator string // creator + creationDate time.Time // override for dcoument CreationDate value aliasNbPagesStr string // alias for total number of pages pdfVersion string // PDF version number fontDirStr string // location of font definition files @@ -233,6 +235,7 @@ type Fpdf struct { err error // Set if error occurs during life cycle of instance protect protectType // document protection structure layer layerRecType // manages optional layers in document + catalogSort bool // sort resource catalogs in document colorFlag bool // indicates whether fill and text colors are different color struct { // Composite values of colors draw, fill, text clrType @@ -118,7 +118,20 @@ complete. Please note that these examples run in the context of a test. In order run an example as a standalone application, you'll need to examine fpdf_test.go for -some helper routines, for example exampleFilename and summary. +some helper routines, for example exampleFilename() and summary(). + +Example PDFs can be compared with reference copies in order to verify that they +have been generated as expected. This comparison will be performed if a PDF +with the same name as the example PDF is placed in the gofpdf/pdf/reference +directory. The routine that summarizes an example will look for this file and, +if found, will call ComparePDFFiles() to check the example PDF for equality +with its reference PDF. If differences exist between the two files they will be +printed to standard output and the test will fail. If the reference file is +missing, the comparison is considered to succeed. In order to successfully +compare two PDFs, the placement of internal resources must be consistent and +the internal creation timestamps must be the same. To do this, the methods +SetCatalogSort() and SetCreationDate() need to be called for both files. This +is done automatically for all examples. Nonstandard Fonts @@ -36,11 +36,17 @@ import ( "math" "os" "path" + "sort" "strconv" "strings" "time" ) +var gl struct { + catalogSort bool + creationDate time.Time +} + type fmtBuffer struct { bytes.Buffer } @@ -179,6 +185,8 @@ func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) // Set default PDF version number f.pdfVersion = "1.3" f.layerInit() + f.catalogSort = gl.catalogSort + f.creationDate = gl.creationDate return } @@ -2986,6 +2994,35 @@ func (f *Fpdf) outf(fmtStr string, args ...interface{}) { f.out(sprintf(fmtStr, args...)) } +// SetDefaultCatalogSort sets the default value of the catalog sort flag that +// will be used when initializing a new Fpdf instance. See SetCatalogSort() for +// more details. +func SetDefaultCatalogSort(flag bool) { + gl.catalogSort = flag +} + +// SetCatalogSort sets a flag that will be used, if true, to consistently order +// the document's internal resource catalogs. This method is typically only +// used for test purposes to facilitate PDF comparison. +func (f *Fpdf) SetCatalogSort(flag bool) { + f.catalogSort = flag +} + +// SetDefaultCreationDate sets the default value of the document creation date +// that will be used when initializing a new Fpdf instance. See +// SetCreationDate() for more details. +func SetDefaultCreationDate(tm time.Time) { + gl.creationDate = tm +} + +// SetCreationDate fixes the document's internal CreationDate value. By +// default, the time when the document is generated is used for this value. +// This method is typically only used for testing purposes to facilitate PDF +// comparsion. Specify a zero-value time to revert to the default behavior. +func (f *Fpdf) SetCreationDate(tm time.Time) { + f.creationDate = tm +} + func (f *Fpdf) putpages() { var wPt, hPt float64 var pageSize SizeType @@ -3092,107 +3129,130 @@ func (f *Fpdf) putfonts() { f.outf("<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [%s]>>", diff) f.out("endobj") } - for file, info := range f.fontFiles { - // foreach($this->fontFiles as $file=>$info) - // Font file embedding - f.newobj() - info.n = f.n - f.fontFiles[file] = info - font, err := f.loadFontFile(file) - if err != nil { - f.err = err - return - } - // dbg("font file [%s], ext [%s]", file, file[len(file)-2:]) - compressed := file[len(file)-2:] == ".z" - if !compressed && info.length2 > 0 { - buf := font[6:info.length1] - buf = append(buf, font[6+info.length1+6:info.length2]...) - font = buf + { + var fileList []string + var info fontFileType + var file string + for file = range f.fontFiles { + fileList = append(fileList, file) } - f.outf("<</Length %d", len(font)) - if compressed { - f.out("/Filter /FlateDecode") + if f.catalogSort { + sort.Strings(fileList) } - f.outf("/Length1 %d", info.length1) - if info.length2 > 0 { - f.outf("/Length2 %d /Length3 0", info.length2) - } - f.out(">>") - f.putstream(font) - f.out("endobj") - } - for k, font := range f.fonts { - // Font objects - font.N = f.n + 1 - f.fonts[k] = font - tp := font.Tp - name := font.Name - if tp == "Core" { - // Core font + for _, file = range fileList { + info = f.fontFiles[file] + // Font file embedding f.newobj() - f.out("<</Type /Font") - f.outf("/BaseFont /%s", name) - f.out("/Subtype /Type1") - if name != "Symbol" && name != "ZapfDingbats" { - f.out("/Encoding /WinAnsiEncoding") + info.n = f.n + f.fontFiles[file] = info + font, err := f.loadFontFile(file) + if err != nil { + f.err = err + return } - f.out(">>") - f.out("endobj") - } else if tp == "Type1" || tp == "TrueType" { - // Additional Type1 or TrueType/OpenType font - f.newobj() - f.out("<</Type /Font") - f.outf("/BaseFont /%s", name) - f.outf("/Subtype /%s", tp) - f.out("/FirstChar 32 /LastChar 255") - f.outf("/Widths %d 0 R", f.n+1) - f.outf("/FontDescriptor %d 0 R", f.n+2) - if font.DiffN > 0 { - f.outf("/Encoding %d 0 R", nf+font.DiffN) - } else { - f.out("/Encoding /WinAnsiEncoding") + // dbg("font file [%s], ext [%s]", file, file[len(file)-2:]) + compressed := file[len(file)-2:] == ".z" + if !compressed && info.length2 > 0 { + buf := font[6:info.length1] + buf = append(buf, font[6+info.length1+6:info.length2]...) + font = buf } - f.out(">>") - f.out("endobj") - // Widths - f.newobj() - var s fmtBuffer - s.WriteString("[") - for j := 32; j < 256; j++ { - s.printf("%d ", font.Cw[j]) + f.outf("<</Length %d", len(font)) + if compressed { + f.out("/Filter /FlateDecode") } - s.WriteString("]") - f.out(s.String()) - f.out("endobj") - // Descriptor - f.newobj() - s.Truncate(0) - s.printf("<</Type /FontDescriptor /FontName /%s ", name) - s.printf("/Ascent %d ", font.Desc.Ascent) - s.printf("/Descent %d ", font.Desc.Descent) - s.printf("/CapHeight %d ", font.Desc.CapHeight) - s.printf("/Flags %d ", font.Desc.Flags) - s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin, - font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax) - s.printf("/ItalicAngle %d ", font.Desc.ItalicAngle) - s.printf("/StemV %d ", font.Desc.StemV) - s.printf("/MissingWidth %d ", font.Desc.MissingWidth) - var suffix string - if tp != "Type1" { - suffix = "2" + f.outf("/Length1 %d", info.length1) + if info.length2 > 0 { + f.outf("/Length2 %d /Length3 0", info.length2) } - s.printf("/FontFile%s %d 0 R>>", suffix, f.fontFiles[font.File].n) - f.out(s.String()) + f.out(">>") + f.putstream(font) f.out("endobj") - } else { - f.err = fmt.Errorf("unsupported font type: %s", tp) - return - // Allow for additional types - // $mtd = 'put'.strtolower($type); - // if(!method_exists($this,$mtd)) - // $this->Error('Unsupported font type: '.$type); - // $this->$mtd($font); + } + } + { + var keyList []string + var font fontDefType + var key string + for key = range f.fonts { + keyList = append(keyList, key) + } + if f.catalogSort { + sort.Strings(keyList) + } + for _, key = range keyList { + font = f.fonts[key] + // Font objects + font.N = f.n + 1 + f.fonts[key] = font + tp := font.Tp + name := font.Name + if tp == "Core" { + // Core font + f.newobj() + f.out("<</Type /Font") + f.outf("/BaseFont /%s", name) + f.out("/Subtype /Type1") + if name != "Symbol" && name != "ZapfDingbats" { + f.out("/Encoding /WinAnsiEncoding") + } + f.out(">>") + f.out("endobj") + } else if tp == "Type1" || tp == "TrueType" { + // Additional Type1 or TrueType/OpenType font + f.newobj() + f.out("<</Type /Font") + f.outf("/BaseFont /%s", name) + f.outf("/Subtype /%s", tp) + f.out("/FirstChar 32 /LastChar 255") + f.outf("/Widths %d 0 R", f.n+1) + f.outf("/FontDescriptor %d 0 R", f.n+2) + if font.DiffN > 0 { + f.outf("/Encoding %d 0 R", nf+font.DiffN) + } else { + f.out("/Encoding /WinAnsiEncoding") + } + f.out(">>") + f.out("endobj") + // Widths + f.newobj() + var s fmtBuffer + s.WriteString("[") + for j := 32; j < 256; j++ { + s.printf("%d ", font.Cw[j]) + } + s.WriteString("]") + f.out(s.String()) + f.out("endobj") + // Descriptor + f.newobj() + s.Truncate(0) + s.printf("<</Type /FontDescriptor /FontName /%s ", name) + s.printf("/Ascent %d ", font.Desc.Ascent) + s.printf("/Descent %d ", font.Desc.Descent) + s.printf("/CapHeight %d ", font.Desc.CapHeight) + s.printf("/Flags %d ", font.Desc.Flags) + s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin, + font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax) + s.printf("/ItalicAngle %d ", font.Desc.ItalicAngle) + s.printf("/StemV %d ", font.Desc.StemV) + s.printf("/MissingWidth %d ", font.Desc.MissingWidth) + var suffix string + if tp != "Type1" { + suffix = "2" + } + s.printf("/FontFile%s %d 0 R>>", suffix, f.fontFiles[font.File].n) + f.out(s.String()) + f.out("endobj") + } else { + f.err = fmt.Errorf("unsupported font type: %s", tp) + return + // Allow for additional types + // $mtd = 'put'.strtolower($type); + // if(!method_exists($this,$mtd)) + // $this->Error('Unsupported font type: '.$type); + // $this->$mtd($font); + } } } return @@ -3213,8 +3273,16 @@ func (f *Fpdf) loadFontFile(name string) ([]byte, error) { } func (f *Fpdf) putimages() { - for _, img := range f.images { - f.putimage(img) + var keyList []string + var key string + for key = range f.images { + keyList = append(keyList, key) + } + if f.catalogSort { + sort.Strings(keyList) + } + for _, key = range keyList { + f.putimage(f.images[key]) } } @@ -3283,14 +3351,33 @@ func (f *Fpdf) putimage(info *ImageInfoType) { } func (f *Fpdf) putxobjectdict() { - for _, image := range f.images { - // foreach($this->images as $image) - f.outf("/I%d %d 0 R", image.i, image.n) - } - for _, tpl := range f.templates { - id := tpl.ID() - if objID, ok := f.templateObjects[id]; ok { - f.outf("/TPL%d %d 0 R", id, objID) + { + var image *ImageInfoType + var key string + var keyList []string + for key = range f.images { + keyList = append(keyList, key) + } + if f.catalogSort { + sort.Strings(keyList) + } + for _, key = range keyList { + image = f.images[key] + f.outf("/I%d %d 0 R", image.i, image.n) + } + } + { + var keyList []int64 + var key int64 + var tpl Template + keyList = templateKeyList(f.templates, f.catalogSort) + for _, key = range keyList { + tpl = f.templates[key] + // for _, tpl := range f.templates { + id := tpl.ID() + if objID, ok := f.templateObjects[id]; ok { + f.outf("/TPL%d %d 0 R", id, objID) + } } } } @@ -3298,9 +3385,20 @@ func (f *Fpdf) putxobjectdict() { func (f *Fpdf) putresourcedict() { f.out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]") f.out("/Font <<") - for _, font := range f.fonts { - // foreach($this->fonts as $font) - f.outf("/F%d %d 0 R", font.I, font.N) + { + var keyList []string + var font fontDefType + var key string + for key = range f.fonts { + keyList = append(keyList, key) + } + if f.catalogSort { + sort.Strings(keyList) + } + for _, key = range keyList { + font = f.fonts[key] + f.outf("/F%d %d 0 R", font.I, font.N) + } } f.out(">>") f.out("/XObject <<") @@ -3400,6 +3498,7 @@ func (f *Fpdf) putresources() { } func (f *Fpdf) putinfo() { + var tm time.Time f.outf("/Producer %s", f.textstring("FPDF "+cnFpdfVersion)) if len(f.title) > 0 { f.outf("/Title %s", f.textstring(f.title)) @@ -3416,7 +3515,12 @@ func (f *Fpdf) putinfo() { if len(f.creator) > 0 { f.outf("/Creator %s", f.textstring(f.creator)) } - f.outf("/CreationDate %s", f.textstring("D:"+time.Now().Format("20060102150405"))) + if f.creationDate.IsZero() { + tm = time.Now() + } else { + tm = f.creationDate + } + f.outf("/CreationDate %s", f.textstring("D:"+tm.Format("20060102150405"))) } func (f *Fpdf) putcatalog() { diff --git a/fpdf_test.go b/fpdf_test.go index 966767f..596816c 100644 --- a/fpdf_test.go +++ b/fpdf_test.go @@ -40,9 +40,14 @@ func init() { func cleanup() { filepath.Walk(example.PdfDir(), func(path string, info os.FileInfo, err error) (reterr error) { - if len(path) > 3 { - if path[len(path)-4:] == ".pdf" { - os.Remove(path) + if info.Mode().IsRegular() { + dir, _ := filepath.Split(path) + if "reference" != filepath.Base(dir) { + if len(path) > 3 { + if path[len(path)-4:] == ".pdf" { + os.Remove(path) + } + } } } return @@ -1728,8 +1733,8 @@ func ExampleFpdf_CreateTemplate() { pdf.AddPage() pdf.UseTemplate(template) - pdf.UseTemplateScaled(template, gofpdf.PointType{0, 30}, tplSize) - pdf.UseTemplateScaled(template, gofpdf.PointType{0, 60}, tplSize.ScaleBy(1.4)) + pdf.UseTemplateScaled(template, gofpdf.PointType{X: 0, Y: 30}, tplSize) + pdf.UseTemplateScaled(template, gofpdf.PointType{X: 0, Y: 60}, tplSize.ScaleBy(1.4)) pdf.Line(40, 210, 60, 210) pdf.Text(40, 200, "Template example page 1") diff --git a/internal/example/example.go b/internal/example/example.go index d6a114c..56ffb23 100644 --- a/internal/example/example.go +++ b/internal/example/example.go @@ -22,12 +22,17 @@ import ( "os" "path/filepath" "strings" + "time" + + "github.com/jung-kurt/gofpdf" ) var gofpdfDir string func init() { setRoot() + gofpdf.SetDefaultCatalogSort(true) + gofpdf.SetDefaultCreationDate(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) } // Assign the relative path to the gofpdfDir directory based on current working @@ -36,8 +41,7 @@ func setRoot() { wdStr, err := os.Getwd() if err == nil { gofpdfDir = "" - sepStr := string(os.PathSeparator) - list := strings.Split(wdStr, sepStr) + list := strings.Split(filepath.ToSlash(wdStr), "/") for j := len(list) - 1; j >= 0 && list[j] != "gofpdf"; j-- { gofpdfDir = filepath.Join(gofpdfDir, "..") } @@ -87,12 +91,32 @@ func Filename(baseStr string) string { return PdfFile(baseStr + ".pdf") } +// referenceCompare compares the specified file with the file's reference copy +// located in the 'reference' subdirectory. All bytes of the two files are +// compared except for the value of the /CreationDate field in the PDF. This +// function succeeds if both files are equivalent except for their +// /CreationDate values or if the reference file does not exist. +func referenceCompare(fileStr string) (err error) { + var refFileStr, refDirStr, dirStr, baseFileStr string + dirStr, baseFileStr = filepath.Split(fileStr) + refDirStr = filepath.Join(dirStr, "reference") + err = os.MkdirAll(refDirStr, 0755) + if err == nil { + refFileStr = filepath.Join(refDirStr, baseFileStr) + err = gofpdf.ComparePDFFiles(fileStr, refFileStr) + } + return +} + // Summary generates a predictable report for use by test examples. If the // specified error is nil, the filename delimiters are normalized and the // filename printed to standard output with a success message. If the specified // error is not nil, its String() value is printed to standard output. func Summary(err error, fileStr string) { if err == nil { + err = referenceCompare(fileStr) + } + if err == nil { fileStr = filepath.ToSlash(fileStr) fmt.Printf("Successfully generated %s\n", fileStr) } else { diff --git a/list/list.go b/list/list.go new file mode 100644 index 0000000..8099404 --- /dev/null +++ b/list/list.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +func matchTail(str, tailStr string) (match bool, headStr string) { + sln := len(str) + ln := len(tailStr) + if sln > ln { + match = str[sln-ln:] == tailStr + if match { + headStr = str[:sln-ln] + } + } + return +} + +func matchHead(str, headStr string) (match bool, tailStr string) { + ln := len(headStr) + if len(str) > ln { + match = str[:ln] == headStr + if match { + tailStr = str[ln:] + } + } + return +} + +func main() { + var err error + var ok bool + var showStr, name string + err = filepath.Walk("pdf/reference", func(path string, info os.FileInfo, err error) error { + if info.Mode().IsRegular() { + name = filepath.Base(path) + ok, name = matchTail(name, ".pdf") + if ok { + name = strings.Replace(name, "_", " ", -1) + ok, showStr = matchHead(name, "Fpdf ") + if ok { + fmt.Printf("[%s](%s)\n", showStr, path) + } else { + ok, showStr = matchHead(name, "contrib ") + if ok { + fmt.Printf("[%s](%s)\n", showStr, path) + } + } + } + } + return nil + }) + if err != nil { + fmt.Println(err) + } +} @@ -1,3 +1,5 @@ +#!/bin/bash + # https://github.com/jimmyfrasche/autoreadme autoreadme -f -template README.md.template # Improve the appearance of the markdown document with features unavailable in godoc @@ -16,5 +18,5 @@ cat README.md | tr '\n' '\v' | sed \ -e 's/test.coverage.(\(https:\/\/blog\.golang\.org\/cover\))/[test coverage](\1)/g' \ -e 's/Pull.requests.(\(https:\/\/help\.github\.com\/articles\/using\-pull\-requests\/\))/[Pull requests](\1)/g' \ -e 's/Your change should\v/Your change should\v\v/g' \ - | tr '\v' '\n' > 0 -mv 0 README.md + | tr '\v' '\n' > _0 +mv _0 README.md diff --git a/pdf/reference/Fpdf_AddFont.pdf b/pdf/reference/Fpdf_AddFont.pdf Binary files differnew file mode 100644 index 0000000..e5c8266 --- /dev/null +++ b/pdf/reference/Fpdf_AddFont.pdf diff --git a/pdf/reference/Fpdf_AddLayer.pdf b/pdf/reference/Fpdf_AddLayer.pdf Binary files differnew file mode 100644 index 0000000..16b4b5b --- /dev/null +++ b/pdf/reference/Fpdf_AddLayer.pdf diff --git a/pdf/reference/Fpdf_AddPage.pdf b/pdf/reference/Fpdf_AddPage.pdf Binary files differnew file mode 100644 index 0000000..72c59eb --- /dev/null +++ b/pdf/reference/Fpdf_AddPage.pdf diff --git a/pdf/reference/Fpdf_Beziergon.pdf b/pdf/reference/Fpdf_Beziergon.pdf Binary files differnew file mode 100644 index 0000000..b33e2ee --- /dev/null +++ b/pdf/reference/Fpdf_Beziergon.pdf diff --git a/pdf/reference/Fpdf_Bookmark.pdf b/pdf/reference/Fpdf_Bookmark.pdf Binary files differnew file mode 100644 index 0000000..463d49e --- /dev/null +++ b/pdf/reference/Fpdf_Bookmark.pdf diff --git a/pdf/reference/Fpdf_CellFormat_1_tables.pdf b/pdf/reference/Fpdf_CellFormat_1_tables.pdf Binary files differnew file mode 100644 index 0000000..ef0a880 --- /dev/null +++ b/pdf/reference/Fpdf_CellFormat_1_tables.pdf diff --git a/pdf/reference/Fpdf_CellFormat_2_align.pdf b/pdf/reference/Fpdf_CellFormat_2_align.pdf Binary files differnew file mode 100644 index 0000000..5074357 --- /dev/null +++ b/pdf/reference/Fpdf_CellFormat_2_align.pdf diff --git a/pdf/reference/Fpdf_CellFormat_3_codepageescape.pdf b/pdf/reference/Fpdf_CellFormat_3_codepageescape.pdf Binary files differnew file mode 100644 index 0000000..1d31153 --- /dev/null +++ b/pdf/reference/Fpdf_CellFormat_3_codepageescape.pdf diff --git a/pdf/reference/Fpdf_CellFormat_4_codepage.pdf b/pdf/reference/Fpdf_CellFormat_4_codepage.pdf Binary files differnew file mode 100644 index 0000000..6c11be0 --- /dev/null +++ b/pdf/reference/Fpdf_CellFormat_4_codepage.pdf diff --git a/pdf/reference/Fpdf_Circle_figures.pdf b/pdf/reference/Fpdf_Circle_figures.pdf Binary files differnew file mode 100644 index 0000000..097d53c --- /dev/null +++ b/pdf/reference/Fpdf_Circle_figures.pdf diff --git a/pdf/reference/Fpdf_ClipText.pdf b/pdf/reference/Fpdf_ClipText.pdf Binary files differnew file mode 100644 index 0000000..258ea1f --- /dev/null +++ b/pdf/reference/Fpdf_ClipText.pdf diff --git a/pdf/reference/Fpdf_CreateTemplate.pdf b/pdf/reference/Fpdf_CreateTemplate.pdf Binary files differnew file mode 100644 index 0000000..0223da9 --- /dev/null +++ b/pdf/reference/Fpdf_CreateTemplate.pdf diff --git a/pdf/reference/Fpdf_DrawPath_fill.pdf b/pdf/reference/Fpdf_DrawPath_fill.pdf Binary files differnew file mode 100644 index 0000000..fdde12c --- /dev/null +++ b/pdf/reference/Fpdf_DrawPath_fill.pdf diff --git a/pdf/reference/Fpdf_HTMLBasicNew.pdf b/pdf/reference/Fpdf_HTMLBasicNew.pdf Binary files differnew file mode 100644 index 0000000..fb5cbf4 --- /dev/null +++ b/pdf/reference/Fpdf_HTMLBasicNew.pdf diff --git a/pdf/reference/Fpdf_Image.pdf b/pdf/reference/Fpdf_Image.pdf Binary files differnew file mode 100644 index 0000000..838dd4b --- /dev/null +++ b/pdf/reference/Fpdf_Image.pdf diff --git a/pdf/reference/Fpdf_LinearGradient_gradient.pdf b/pdf/reference/Fpdf_LinearGradient_gradient.pdf Binary files differnew file mode 100644 index 0000000..cb1e3bd --- /dev/null +++ b/pdf/reference/Fpdf_LinearGradient_gradient.pdf diff --git a/pdf/reference/Fpdf_MoveTo_path.pdf b/pdf/reference/Fpdf_MoveTo_path.pdf Binary files differnew file mode 100644 index 0000000..b5dcca0 --- /dev/null +++ b/pdf/reference/Fpdf_MoveTo_path.pdf diff --git a/pdf/reference/Fpdf_MultiCell.pdf b/pdf/reference/Fpdf_MultiCell.pdf Binary files differnew file mode 100644 index 0000000..a073d75 --- /dev/null +++ b/pdf/reference/Fpdf_MultiCell.pdf diff --git a/pdf/reference/Fpdf_PageSize.pdf b/pdf/reference/Fpdf_PageSize.pdf Binary files differnew file mode 100644 index 0000000..f523782 --- /dev/null +++ b/pdf/reference/Fpdf_PageSize.pdf diff --git a/pdf/reference/Fpdf_Polygon.pdf b/pdf/reference/Fpdf_Polygon.pdf Binary files differnew file mode 100644 index 0000000..a3c04ed --- /dev/null +++ b/pdf/reference/Fpdf_Polygon.pdf diff --git a/pdf/reference/Fpdf_RegisterImage.pdf b/pdf/reference/Fpdf_RegisterImage.pdf Binary files differnew file mode 100644 index 0000000..80a14d9 --- /dev/null +++ b/pdf/reference/Fpdf_RegisterImage.pdf diff --git a/pdf/reference/Fpdf_RegisterImageReader_url.pdf b/pdf/reference/Fpdf_RegisterImageReader_url.pdf Binary files differnew file mode 100644 index 0000000..171ad32 --- /dev/null +++ b/pdf/reference/Fpdf_RegisterImageReader_url.pdf diff --git a/pdf/reference/Fpdf_SVGBasicWrite.pdf b/pdf/reference/Fpdf_SVGBasicWrite.pdf Binary files differnew file mode 100644 index 0000000..bdefa65 --- /dev/null +++ b/pdf/reference/Fpdf_SVGBasicWrite.pdf diff --git a/pdf/reference/Fpdf_SetAcceptPageBreakFunc_landscape.pdf b/pdf/reference/Fpdf_SetAcceptPageBreakFunc_landscape.pdf Binary files differnew file mode 100644 index 0000000..7997df9 --- /dev/null +++ b/pdf/reference/Fpdf_SetAcceptPageBreakFunc_landscape.pdf diff --git a/pdf/reference/Fpdf_SetAlpha_transparency.pdf b/pdf/reference/Fpdf_SetAlpha_transparency.pdf Binary files differnew file mode 100644 index 0000000..49dde6a --- /dev/null +++ b/pdf/reference/Fpdf_SetAlpha_transparency.pdf diff --git a/pdf/reference/Fpdf_SetFontLoader.pdf b/pdf/reference/Fpdf_SetFontLoader.pdf Binary files differnew file mode 100644 index 0000000..1aa2f6f --- /dev/null +++ b/pdf/reference/Fpdf_SetFontLoader.pdf diff --git a/pdf/reference/Fpdf_SetKeywords.pdf b/pdf/reference/Fpdf_SetKeywords.pdf Binary files differnew file mode 100644 index 0000000..3d3d3a5 --- /dev/null +++ b/pdf/reference/Fpdf_SetKeywords.pdf diff --git a/pdf/reference/Fpdf_SetLeftMargin_multicolumn.pdf b/pdf/reference/Fpdf_SetLeftMargin_multicolumn.pdf Binary files differnew file mode 100644 index 0000000..91b6d59 --- /dev/null +++ b/pdf/reference/Fpdf_SetLeftMargin_multicolumn.pdf diff --git a/pdf/reference/Fpdf_SetLineJoinStyle_caps.pdf b/pdf/reference/Fpdf_SetLineJoinStyle_caps.pdf Binary files differnew file mode 100644 index 0000000..9b9badb --- /dev/null +++ b/pdf/reference/Fpdf_SetLineJoinStyle_caps.pdf diff --git a/pdf/reference/Fpdf_SetProtection.pdf b/pdf/reference/Fpdf_SetProtection.pdf Binary files differnew file mode 100644 index 0000000..8d007c7 --- /dev/null +++ b/pdf/reference/Fpdf_SetProtection.pdf diff --git a/pdf/reference/Fpdf_Splitlines.pdf b/pdf/reference/Fpdf_Splitlines.pdf Binary files differnew file mode 100644 index 0000000..d8064be --- /dev/null +++ b/pdf/reference/Fpdf_Splitlines.pdf diff --git a/pdf/reference/Fpdf_TransformBegin.pdf b/pdf/reference/Fpdf_TransformBegin.pdf Binary files differnew file mode 100644 index 0000000..d1ffe34 --- /dev/null +++ b/pdf/reference/Fpdf_TransformBegin.pdf diff --git a/pdf/reference/Fpdf_WriteAligned.pdf b/pdf/reference/Fpdf_WriteAligned.pdf Binary files differnew file mode 100644 index 0000000..c173146 --- /dev/null +++ b/pdf/reference/Fpdf_WriteAligned.pdf diff --git a/pdf/reference/basic.pdf b/pdf/reference/basic.pdf Binary files differnew file mode 100644 index 0000000..80a7fbf --- /dev/null +++ b/pdf/reference/basic.pdf diff --git a/pdf/reference/contrib_barcode_Register.pdf b/pdf/reference/contrib_barcode_Register.pdf Binary files differnew file mode 100644 index 0000000..5ad12e7 --- /dev/null +++ b/pdf/reference/contrib_barcode_Register.pdf diff --git a/pdf/reference/contrib_barcode_RegisterCodabar.pdf b/pdf/reference/contrib_barcode_RegisterCodabar.pdf Binary files differnew file mode 100644 index 0000000..4ed247b --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterCodabar.pdf diff --git a/pdf/reference/contrib_barcode_RegisterCode128.pdf b/pdf/reference/contrib_barcode_RegisterCode128.pdf Binary files differnew file mode 100644 index 0000000..f76ae86 --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterCode128.pdf diff --git a/pdf/reference/contrib_barcode_RegisterCode39.pdf b/pdf/reference/contrib_barcode_RegisterCode39.pdf Binary files differnew file mode 100644 index 0000000..fee2c99 --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterCode39.pdf diff --git a/pdf/reference/contrib_barcode_RegisterDataMatrix.pdf b/pdf/reference/contrib_barcode_RegisterDataMatrix.pdf Binary files differnew file mode 100644 index 0000000..5ea011d --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterDataMatrix.pdf diff --git a/pdf/reference/contrib_barcode_RegisterEAN.pdf b/pdf/reference/contrib_barcode_RegisterEAN.pdf Binary files differnew file mode 100644 index 0000000..166090d --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterEAN.pdf diff --git a/pdf/reference/contrib_barcode_RegisterQR.pdf b/pdf/reference/contrib_barcode_RegisterQR.pdf Binary files differnew file mode 100644 index 0000000..6e989e3 --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterQR.pdf diff --git a/pdf/reference/contrib_barcode_RegisterTwoOfFive.pdf b/pdf/reference/contrib_barcode_RegisterTwoOfFive.pdf Binary files differnew file mode 100644 index 0000000..0078dc1 --- /dev/null +++ b/pdf/reference/contrib_barcode_RegisterTwoOfFive.pdf diff --git a/pdf/reference/contrib_httpimg_Register.pdf b/pdf/reference/contrib_httpimg_Register.pdf Binary files differnew file mode 100644 index 0000000..212ca93 --- /dev/null +++ b/pdf/reference/contrib_httpimg_Register.pdf diff --git a/template.go b/template.go index 7561ae1..c9a33d5 100644 --- a/template.go +++ b/template.go @@ -17,6 +17,10 @@ package gofpdf * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +import ( + "sort" +) + // CreateTemplate defines a new template using the current page size. func (f *Fpdf) CreateTemplate(fn func(*Tpl)) Template { return newTpl(PointType{0, 0}, f.curPageSize, f.unitStr, f.fontDirStr, fn, f) @@ -36,7 +40,7 @@ func CreateTemplate(corner PointType, size SizeType, unitStr, fontDirStr string, // using the size and position at which it was originally written. func (f *Fpdf) UseTemplate(t Template) { if t == nil { - f.SetErrorf("Template is nil") + f.SetErrorf("template is nil") return } corner, size := t.Size() @@ -47,13 +51,13 @@ func (f *Fpdf) UseTemplate(t Template) { // using the given page coordinates. func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) { if t == nil { - f.SetErrorf("Template is nil") + f.SetErrorf("template is nil") return } // You have to add at least a page first if f.page <= 0 { - f.SetErrorf("Cannot use a template without first adding a page") + f.SetErrorf("cannot use a template without first adding a page") return } @@ -75,7 +79,7 @@ func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) { tx := corner.X * f.k ty := (f.curPageSize.Ht - corner.Y - size.Ht) * f.k - f.outf("q %.4F 0 0 %.4F %.4F %.4F cm", scaleX, scaleY, tx, ty) // Translate + f.outf("q %.4f 0 0 %.4f %.4f %.4f cm", scaleX, scaleY, tx, ty) // Translate f.outf("/TPL%d Do Q", t.ID()) } @@ -112,7 +116,7 @@ func (f *Fpdf) putTemplates() { filter = "/Filter /FlateDecode " } - templates := sortTemplates(f.templates) + templates := sortTemplates(f.templates, f.catalogSort) var t Template for _, t = range templates { corner, size := t.Size() @@ -122,9 +126,9 @@ func (f *Fpdf) putTemplates() { f.outf("<<%s/Type /XObject", filter) f.out("/Subtype /Form") f.out("/Formtype 1") - f.outf("/BBox [%.2F %.2F %.2F %.2F]", corner.X*f.k, corner.Y*f.k, (corner.X+size.Wd)*f.k, (corner.Y+size.Ht)*f.k) + f.outf("/BBox [%.2f %.2f %.2f %.2f]", corner.X*f.k, corner.Y*f.k, (corner.X+size.Wd)*f.k, (corner.Y+size.Ht)*f.k) if corner.X != 0 || corner.Y != 0 { - f.outf("/Matrix [1 0 0 1 %.5F %.5F]", -corner.X*f.k*2, corner.Y*f.k*2) + f.outf("/Matrix [1 0 0 1 %.5f %.5f]", -corner.X*f.k*2, corner.Y*f.k*2) } // Template's resource dictionary @@ -135,8 +139,21 @@ func (f *Fpdf) putTemplates() { tTemplates := t.Templates() if len(tImages) > 0 || len(tTemplates) > 0 { f.out("/XObject <<") - for _, ti := range tImages { - f.outf("/I%d %d 0 R", ti.i, ti.n) + { + var key string + var keyList []string + var ti *ImageInfoType + for key = range tImages { + keyList = append(keyList, key) + } + if gl.catalogSort { + sort.Strings(keyList) + } + for _, key = range keyList { + // for _, ti := range tImages { + ti = tImages[key] + f.outf("/I%d %d 0 R", ti.i, ti.n) + } } for _, tt := range tTemplates { id := tt.ID() @@ -161,12 +178,34 @@ func (f *Fpdf) putTemplates() { } } +func templateKeyList(mp map[int64]Template, sort bool) (keyList []int64) { + var key int64 + for key = range mp { + keyList = append(keyList, key) + } + if sort { + gensort(len(keyList), + func(a, b int) bool { + return keyList[a] < keyList[b] + }, + func(a, b int) { + keyList[a], keyList[b] = keyList[b], keyList[a] + }) + } + return +} + // sortTemplates puts templates in a suitable order based on dependices -func sortTemplates(templates map[int64]Template) []Template { +func sortTemplates(templates map[int64]Template, catalogSort bool) []Template { chain := make([]Template, 0, len(templates)*2) // build a full set of dependency chains - for _, t := range templates { + var keyList []int64 + var key int64 + var t Template + keyList = templateKeyList(templates, catalogSort) + for _, key = range keyList { + t = templates[key] tlist := templateChainDependencies(t) for _, tt := range tlist { if tt != nil { @@ -202,3 +241,9 @@ func templateChainDependencies(template Template) []Template { chain = append(chain, template) return chain } + +// < 0002640 31 20 31 32 20 30 20 52 0a 2f 54 50 4c 32 20 31 |1 12 0 R./TPL2 1| +// < 0002650 35 20 30 20 52 0a 2f 54 50 4c 31 20 31 34 20 30 |5 0 R./TPL1 14 0| + +// > 0002640 31 20 31 32 20 30 20 52 0a 2f 54 50 4c 31 20 31 |1 12 0 R./TPL1 1| +// > 0002650 34 20 30 20 52 0a 2f 54 50 4c 32 20 31 35 20 30 |4 0 R./TPL2 15 0| diff --git a/template_impl.go b/template_impl.go index 093e1fd..c94f880 100644 --- a/template_impl.go +++ b/template_impl.go @@ -35,8 +35,8 @@ func newTpl(corner PointType, size SizeType, unitStr, fontDirStr string, fn func fn(&tpl) bytes := tpl.Fpdf.pages[tpl.Fpdf.page].Bytes() templates := make([]Template, 0, len(tpl.Fpdf.templates)) - for _, t := range tpl.Fpdf.templates { - templates = append(templates, t) + for _, key := range templateKeyList(tpl.Fpdf.templates, true) { + templates = append(templates, tpl.Fpdf.templates[key]) } images := tpl.Fpdf.images |