diff options
-rw-r--r-- | def.go | 22 | ||||
-rw-r--r-- | fpdf.go | 216 | ||||
-rw-r--r-- | fpdf_test.go | 45 | ||||
-rw-r--r-- | ttfparser.go | 1 |
4 files changed, 168 insertions, 116 deletions
@@ -36,12 +36,15 @@ type gradientType struct { objNum int } -type sizeType struct { - wd, ht float64 +// Wd and Ht specify the horizontal and vertical extents of a document page. +type SizeType struct { + Wd, Ht float64 } -type pointType struct { - x, y float64 +// X and Y specify the horizontal and vertical coordinates of a point, +// typically used in drawing. +type PointType struct { + X, Y float64 } type imageInfoType struct { @@ -86,10 +89,11 @@ type Fpdf struct { k float64 // scale factor (number of points in user unit) defOrientation string // default orientation curOrientation string // current orientation - stdpageSizes map[string]sizeType // standard page sizes - defPageSize sizeType // default page size - curPageSize sizeType // current page size - pageSizes map[int]sizeType // used for pages with non default sizes or orientations + stdPageSizes map[string]SizeType // standard page sizes + defPageSize SizeType // default page size + curPageSize SizeType // current page size + pageSizes map[int]SizeType // used for pages with non default sizes or orientations + unitStr string // unit of measure for all rendered objects except fonts wPt, hPt float64 // dimensions of current page in points w, h float64 // dimensions of current page in user unit lMargin float64 // left margin @@ -141,7 +145,7 @@ type Fpdf struct { blendList []blendModeType // slice[idx] of alpha transparency modes, 1-based blendMap map[string]int // map into blendList gradientList []gradientType // slice[idx] of gradient records - clipActive bool // clippping operation is underway + clipNest int // Number of active clipping contexts err error // Set if error occurs during life cycle of instance } @@ -45,24 +45,7 @@ func (b *fmtBuffer) printf(fmtStr string, args ...interface{}) { b.Buffer.WriteString(fmt.Sprintf(fmtStr, args...)) } -// New returns a pointer to a new Fpdf instance. Its methods are subsequently -// called to produce a single PDF document. -// -// orientationStr specifies the default page orientation. For portrait mode, -// specify "P" or "Portrait". For landscape mode, specify "L" or "Landscape". -// An empty string will be replaced with "P". -// -// unitStr specifies the unit of length used in size parameters for elements -// other than fonts, which are always measured in points. Specify "pt" for -// point, "mm" for millimeter, "cm" for centimeter, or "in" for inch. An empty -// string will be replaced with "mm". -// -// sizeStr specifies the page size. Acceptable values are "A3", "A4", "A5", -// "Letter", or "Legal". An empty string will be replaced with "A4". -// -// fontDirStr specifies the file system location in which font resources will -// be found. An empty string is replaced with ".". -func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) { +func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) (f *Fpdf) { f = new(Fpdf) if orientationStr == "" { orientationStr = "P" @@ -80,7 +63,7 @@ func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) { f.n = 2 f.pages = make([]*bytes.Buffer, 0, 8) f.pages = append(f.pages, bytes.NewBufferString("")) // pages[0] is unused (1-based) - f.pageSizes = make(map[int]sizeType) + f.pageSizes = make(map[int]SizeType) f.state = 0 f.fonts = make(map[string]fontDefType) f.fontFiles = make(map[string]fontFileType) @@ -125,16 +108,21 @@ func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) { f.err = fmt.Errorf("Incorrect unit %s", unitStr) return } + f.unitStr = unitStr // Page sizes - f.stdpageSizes = make(map[string]sizeType) - f.stdpageSizes["a3"] = sizeType{841.89, 1190.55} - f.stdpageSizes["a4"] = sizeType{595.28, 841.89} - f.stdpageSizes["a5"] = sizeType{420.94, 595.28} - f.stdpageSizes["letter"] = sizeType{612, 792} - f.stdpageSizes["legal"] = sizeType{612, 1008} - f.defPageSize = f.getpagesizestr(sizeStr) - if f.err != nil { - return + f.stdPageSizes = make(map[string]SizeType) + f.stdPageSizes["a3"] = SizeType{841.89, 1190.55} + f.stdPageSizes["a4"] = SizeType{595.28, 841.89} + f.stdPageSizes["a5"] = SizeType{420.94, 595.28} + f.stdPageSizes["letter"] = SizeType{612, 792} + f.stdPageSizes["legal"] = SizeType{612, 1008} + if size.Wd > 0 && size.Ht > 0 { + f.defPageSize = size + } else { + f.defPageSize = f.getpagesizestr(sizeStr) + if f.err != nil { + return + } } f.curPageSize = f.defPageSize // Page orientation @@ -142,13 +130,13 @@ func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) { switch orientationStr { case "p", "portrait": f.defOrientation = "P" - f.w = f.defPageSize.wd - f.h = f.defPageSize.ht + f.w = f.defPageSize.Wd + f.h = f.defPageSize.Ht // dbg("Assign h: %8.2f", f.h) case "l", "landscape": f.defOrientation = "L" - f.w = f.defPageSize.ht - f.h = f.defPageSize.wd + f.w = f.defPageSize.Ht + f.h = f.defPageSize.Wd default: f.err = fmt.Errorf("Incorrect orientation: %s", orientationStr) return @@ -185,6 +173,46 @@ func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) { return } +// InitType is used with NewCustom() to customize an Fpdf instance. +// OrientationStr, UnitStr, SizeStr and FontDirStr correspond to the arguments +// accepted by New(). If the Wd and Ht fields of Size are each greater than +// zero, Size will be used to set the default page size rather than SizeStr. +type InitType struct { + OrientationStr string + UnitStr string + SizeStr string + Size SizeType + FontDirStr string +} + +// NewCustom returns a pointer to a new Fpdf instance. Its methods are +// subsequently called to produce a single PDF document. NewCustom() is an +// alternative to New() that provides additional customization. +func NewCustom(init *InitType) (f *Fpdf) { + return fpdfNew(init.OrientationStr, init.UnitStr, init.SizeStr, init.FontDirStr, init.Size) +} + +// New returns a pointer to a new Fpdf instance. Its methods are subsequently +// called to produce a single PDF document. +// +// orientationStr specifies the default page orientation. For portrait mode, +// specify "P" or "Portrait". For landscape mode, specify "L" or "Landscape". +// An empty string will be replaced with "P". +// +// unitStr specifies the unit of length used in size parameters for elements +// other than fonts, which are always measured in points. Specify "pt" for +// point, "mm" for millimeter, "cm" for centimeter, or "in" for inch. An empty +// string will be replaced with "mm". +// +// sizeStr specifies the page size. Acceptable values are "A3", "A4", "A5", +// "Letter", or "Legal". An empty string will be replaced with "A4". +// +// fontDirStr specifies the file system location in which font resources will +// be found. An empty string is replaced with ".". +func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) { + return fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, SizeType{0, 0}) +} + // Returns true if no processing errors have occurred. func (f *Fpdf) Ok() bool { return f.err == nil @@ -407,7 +435,7 @@ func (f *Fpdf) open() { // an invalid document. func (f *Fpdf) Close() { if f.err == nil { - if f.clipActive { + if f.clipNest > 0 { f.err = fmt.Errorf("Clip procedure must be explicitly ended") } } @@ -436,13 +464,27 @@ func (f *Fpdf) Close() { return } +// Returns the width and height of the specified page in the units established +// in New(). These return values are followed by the unit of measure itself. If +// pageNum is zero or otherwise out of bounds, it returns the default page +// size, that is, the size of the page that would be added by AddPage(). +func (f *Fpdf) PageSize(pageNum int) (wd, ht float64, unitStr string) { + sz, ok := f.pageSizes[pageNum] + if ok { + sz.Wd, sz.Ht = sz.Wd/f.k, sz.Ht/f.k + } else { + sz = f.defPageSize // user units + } + return sz.Wd, sz.Ht, f.unitStr +} + // Adds a new page with non-default orientation or size. See AddPage() for more // details. // // See New() for a description of orientationStr. // // size specifies the size of the new page in the units established in New(). -func (f *Fpdf) AddPageFormat(orientationStr string, size sizeType) { +func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) { if f.err != nil { return } @@ -948,17 +990,6 @@ func (f *Fpdf) RadialGradient(x, y, w, h float64, r1, g1, b1 int, r2, g2, b2 int f.gradientClipEnd() } -func (f *Fpdf) setClipActive() bool { - if f.err == nil { - if f.clipActive { - f.err = fmt.Errorf("Clipping operation already active") - } else { - f.clipActive = true - } - } - return f.err == nil -} - // Begins a rectangular clipping operation. The rectangle is of width w and // height h. Its upper left corner is positioned at point (x, y). outline is // true to draw a border with the current draw color and line width centered on @@ -969,9 +1000,7 @@ func (f *Fpdf) setClipActive() bool { // // See tutorial 14 for an example of this function. func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) { - if !f.setClipActive() { - return - } + f.clipNest++ f.outf("q %.2f %.2f %.2f %.2f re W %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, strIf(outline, "S", "n")) } @@ -986,9 +1015,7 @@ func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) { // // See tutorial 14 for an example of this function. func (f *Fpdf) ClipText(x, y float64, txtStr string, outline bool) { - if !f.setClipActive() { - return - } + f.clipNest++ f.outf("q BT %.2f %.2f Td %d Tr (%s) Tj ET", x*f.k, (f.h-y)*f.k, intIf(outline, 5, 7), f.escape(txtStr)) } @@ -1009,9 +1036,7 @@ func (f *Fpdf) clipArc(x1, y1, x2, y2, x3, y3 float64) { // // See tutorial 14 for an example of this function. func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) { - if !f.setClipActive() { - return - } + f.clipNest++ k := f.k hp := f.h myArc := (4.0 / 3.0) * (math.Sqrt2 - 1.0) @@ -1045,9 +1070,7 @@ func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) { // // See tutorial 14 for an example of this function. func (f *Fpdf) ClipEllipse(x, y, rx, ry float64, outline bool) { - if !f.setClipActive() { - return - } + f.clipNest++ lx := (4.0 / 3.0) * rx * (math.Sqrt2 - 1) ly := (4.0 / 3.0) * ry * (math.Sqrt2 - 1) k := f.k @@ -1095,16 +1118,14 @@ func (f *Fpdf) ClipCircle(x, y, r float64, outline bool) { // ClipEnd() to restore unclipped operations. // // See tutorial 14 for an example of this function. -func (f *Fpdf) ClipPolygon(points []pointType, outline bool) { - if !f.setClipActive() { - return - } +func (f *Fpdf) ClipPolygon(points []PointType, outline bool) { + f.clipNest++ var s fmtBuffer h := f.h k := f.k s.printf("q ") for j, pt := range points { - s.printf("%.2f %.2f %s ", pt.x*k, (h-pt.y)*k, strIf(j == 0, "m", "l")) + s.printf("%.2f %.2f %s ", pt.X*k, (h-pt.Y)*k, strIf(j == 0, "m", "l")) } s.printf("h W %s", strIf(outline, "S", "n")) f.out(s.String()) @@ -1112,14 +1133,14 @@ func (f *Fpdf) ClipPolygon(points []pointType, outline bool) { // Ends a clipping operation that was started with a call to ClipRect(), // ClipRoundedRect(), ClipText(), ClipEllipse(), ClipCircle() or ClipPolygon(). -// Only one clipping operation can be active at a time, and the document cannot -// be successfully output while a clipping operation is active. +// Clipping operations can be nested. The document cannot be successfully +// output while a clipping operation is active. // // See tutorial 14 for an example of this function. func (f *Fpdf) ClipEnd() { if f.err == nil { - if f.clipActive { - f.clipActive = false + if f.clipNest > 0 { + f.clipNest-- f.out("Q") } else { f.err = fmt.Errorf("Error attempting to end clip operation") @@ -1965,45 +1986,40 @@ func (f *Fpdf) Output(w io.Writer) error { if err != nil { f.err = err } - dump("pdf.txt", f.stdpageSizes, + dump("pdf.txt", f.stdPageSizes, f.defPageSize, f.curPageSize, f.pageSizes) return f.err } -func (f *Fpdf) getpagesizestr(sizeStr string) (size sizeType) { +func (f *Fpdf) getpagesizestr(sizeStr string) (size SizeType) { if f.err != nil { return } sizeStr = strings.ToLower(sizeStr) // dbg("Size [%s]", sizeStr) - if sizeStr == "" { - // dbg("not found %s", sizeStr) - size = f.defPageSize - } else { - var ok bool - size, ok = f.stdpageSizes[sizeStr] - if ok { - // dbg("found %s", sizeStr) - size.wd /= f.k - size.ht /= f.k + var ok bool + size, ok = f.stdPageSizes[sizeStr] + if ok { + // dbg("found %s", sizeStr) + size.Wd /= f.k + size.Ht /= f.k - } else { - f.err = fmt.Errorf("Unknown page size %s", sizeStr) - } + } else { + f.err = fmt.Errorf("Unknown page size %s", sizeStr) } return } -func (f *Fpdf) _getpagesize(size sizeType) sizeType { - if size.wd > size.ht { - size.wd, size.ht = size.ht, size.wd +func (f *Fpdf) _getpagesize(size SizeType) SizeType { + if size.Wd > size.Ht { + size.Wd, size.Ht = size.Ht, size.Wd } return size } -func (f *Fpdf) beginpage(orientationStr string, size sizeType) { +func (f *Fpdf) beginpage(orientationStr string, size SizeType) { if f.err != nil { return } @@ -2020,14 +2036,14 @@ func (f *Fpdf) beginpage(orientationStr string, size sizeType) { } else { orientationStr = strings.ToUpper(orientationStr[0:1]) } - if orientationStr != f.curOrientation || size.wd != f.curPageSize.wd || size.ht != f.curPageSize.ht { + if orientationStr != f.curOrientation || size.Wd != f.curPageSize.Wd || size.Ht != f.curPageSize.Ht { // New size or orientation if orientationStr == "P" { - f.w = size.wd - f.h = size.ht + f.w = size.Wd + f.h = size.Ht } else { - f.w = size.ht - f.h = size.wd + f.w = size.Ht + f.h = size.Wd } f.wPt = f.w * f.k f.hPt = f.h * f.k @@ -2035,8 +2051,8 @@ func (f *Fpdf) beginpage(orientationStr string, size sizeType) { f.curOrientation = orientationStr f.curPageSize = size } - if orientationStr != f.defOrientation || size.wd != f.defPageSize.wd || size.ht != f.defPageSize.ht { - f.pageSizes[f.page] = sizeType{f.wPt, f.hPt} + if orientationStr != f.defOrientation || size.Wd != f.defPageSize.Wd || size.Ht != f.defPageSize.Ht { + f.pageSizes[f.page] = SizeType{f.wPt, f.hPt} } return } @@ -2411,7 +2427,7 @@ func (f *Fpdf) outf(fmtStr string, args ...interface{}) { func (f *Fpdf) putpages() { var wPt, hPt float64 - var pageSize sizeType + var pageSize SizeType // var linkList []linkType var ok bool nb := f.page @@ -2428,11 +2444,11 @@ func (f *Fpdf) putpages() { } } if f.defOrientation == "P" { - wPt = f.defPageSize.wd * f.k - hPt = f.defPageSize.ht * f.k + wPt = f.defPageSize.Wd * f.k + hPt = f.defPageSize.Ht * f.k } else { - wPt = f.defPageSize.ht * f.k - hPt = f.defPageSize.wd * f.k + wPt = f.defPageSize.Ht * f.k + hPt = f.defPageSize.Wd * f.k } for n := 1; n <= nb; n++ { // Page @@ -2441,7 +2457,7 @@ func (f *Fpdf) putpages() { f.out("/Parent 1 0 R") pageSize, ok = f.pageSizes[n] if ok { - f.outf("/MediaBox [0 0 %.2f %.2f]", pageSize.wd, pageSize.ht) + f.outf("/MediaBox [0 0 %.2f %.2f]", pageSize.Wd, pageSize.Ht) } f.out("/Resources 2 0 R") // Links @@ -2455,11 +2471,11 @@ func (f *Fpdf) putpages() { annots.printf("/A <</S /URI /URI %s>>>>", f.textstring(pl.linkStr)) } else { l := f.links[pl.link] - var sz sizeType + var sz SizeType var h float64 sz, ok = f.pageSizes[l.page] if ok { - h = sz.ht + h = sz.Ht } else { h = hPt } diff --git a/fpdf_test.go b/fpdf_test.go index c27263e..9113057 100644 --- a/fpdf_test.go +++ b/fpdf_test.go @@ -798,11 +798,15 @@ func ExampleFpdf_tutorial14() { pdf.ClipEnd() y += 55 - pdf.ClipRect(10, y, 160, 20, true) - pdf.SetFillColor(64, 128, 128) - pdf.Circle(40, y+10, 15, "F") - pdf.SetFillColor(128, 64, 64) - pdf.Ellipse(90, y+10, 30, 40, 45, "F") + pdf.ClipRect(10, y, 105, 20, true) + pdf.SetFillColor(255, 255, 255) + pdf.Rect(10, y, 105, 20, "F") + pdf.ClipCircle(40, y+10, 15, false) + pdf.RadialGradient(25, y, 30, 30, 220, 250, 220, 40, 60, 40, 0.3, 0.85, 0.3, 0.85, 0.5) + pdf.ClipEnd() + pdf.ClipEllipse(80, y+10, 20, 15, false) + pdf.RadialGradient(60, y, 40, 30, 250, 220, 220, 60, 40, 40, 0.3, 0.85, 0.3, 0.85, 0.5) + pdf.ClipEnd() pdf.ClipEnd() y += 28 @@ -814,8 +818,8 @@ func ExampleFpdf_tutorial14() { pdf.RadialGradient(50, y, 20, 20, 220, 220, 250, 40, 40, 60, 0.3, 0.7, 0.3, 0.7, 0.5) pdf.ClipEnd() - pdf.ClipPolygon([]pointType{{80, y + 20}, {90, y}, {100, y + 20}}, true) - pdf.LinearGradient(80, y, 20, 20, 240, 240, 250, 80, 80, 220, 0.5, 1, 0.5, 0) + pdf.ClipPolygon([]PointType{{80, y + 20}, {90, y}, {100, y + 20}}, true) + pdf.LinearGradient(80, y, 20, 20, 250, 220, 250, 60, 40, 60, 0.5, 1, 0.5, 0.5) pdf.ClipEnd() y += 30 @@ -832,3 +836,30 @@ func ExampleFpdf_tutorial14() { // Output: // Successfully generated pdf/tutorial14.pdf } + +// Page size example +func ExampleFpdf_tutorial15() { + pdf := NewCustom(&InitType{UnitStr: "in", Size: SizeType{6, 6}, FontDirStr: FONT_DIR}) + pdf.SetMargins(0.5, 1, 0.5) + pdf.SetFont("Times", "", 14) + pdf.AddPageFormat("L", SizeType{3, 12}) + pdf.SetXY(0.5, 1.5) + pdf.CellFormat(11, 0.2, "12 in x 3 in", "", 0, "C", false, 0, "") + pdf.AddPage() // Default size established in NewCustom() + pdf.SetXY(0.5, 3) + pdf.CellFormat(5, 0.2, "6 in x 6 in", "", 0, "C", false, 0, "") + pdf.AddPageFormat("P", SizeType{3, 12}) + pdf.SetXY(0.5, 6) + pdf.CellFormat(2, 0.2, "3 in x 12 in", "", 0, "C", false, 0, "") + for j := 0; j <= 3; j++ { + wd, ht, u := pdf.PageSize(j) + fmt.Printf("%d: %6.2f %s, %6.2f %s\n", j, wd, u, ht, u) + } + pdf.OutputAndClose(docWriter(pdf, 15)) + // Output: + // 0: 6.00 in, 6.00 in + // 1: 12.00 in, 3.00 in + // 2: 6.00 in, 6.00 in + // 3: 3.00 in, 12.00 in + // Successfully generated pdf/tutorial15.pdf +} diff --git a/ttfparser.go b/ttfparser.go index 323f144..41574e2 100644 --- a/ttfparser.go +++ b/ttfparser.go @@ -30,6 +30,7 @@ import ( "strings" ) +// This structure contains metrics of a TrueType font. type TtfType struct { Embeddable bool UnitsPerEm uint16 |