summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt Jung <kurt.w.jung@code.google.com>2013-08-23 11:50:17 -0400
committerKurt Jung <kurt.w.jung@code.google.com>2013-08-23 11:50:17 -0400
commitf3b514a3763d2f5127e2a36cfdb662c6eb4924ac (patch)
tree4c0ebb4d8dc944bde3a88c90401ef38e70ba360d
parentc6a09fef08ef94067a714feea492e2f86fc41428 (diff)
Added clipping support adapted from script by Andreas Würmser
-rw-r--r--def.go5
-rw-r--r--doc.go6
-rw-r--r--fpdf.go213
-rw-r--r--fpdf_test.go69
-rw-r--r--util.go18
5 files changed, 283 insertions, 28 deletions
diff --git a/def.go b/def.go
index 146a705..7272f73 100644
--- a/def.go
+++ b/def.go
@@ -40,6 +40,10 @@ type sizeType struct {
wd, ht float64
}
+type pointType struct {
+ x, y float64
+}
+
type imageInfoType struct {
data []byte
smask []byte
@@ -137,6 +141,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
err error // Set if error occurs during life cycle of instance
}
diff --git a/doc.go b/doc.go
index 3e5949f..ebc0f1c 100644
--- a/doc.go
+++ b/doc.go
@@ -21,8 +21,8 @@ This package's code and documentation are closely derived from the FPDF library
created by Olivier Plathey, and a number of font and image resources are copied
directly from it. Drawing support is adapted from the FPDF geometric figures
script by David Hernández Sanz. Transparency support is adapted from the FPDF
-transparency script by Martin Hall-May. Support for gradients is adapted from
-the FPDF gradients script by Andreas Würmser.
+transparency script by Martin Hall-May. Support for gradients and clipping is
+adapted from FPDF scripts by Andreas Würmser.
The FPDF website is http://www.fpdf.org/.
@@ -46,7 +46,7 @@ Features
• Page compression
-• Drawing support (lines, Bézier curves, arcs, ellipses)
+• Drawing support (lines, Bézier curves, arcs, ellipses) and clipping
gofpdf has no dependencies other than the Go standard library. All tests pass
on Linux, Mac and Windows platforms. Like FPDF version 1.7, from which gofpdf
diff --git a/fpdf.go b/fpdf.go
index f526821..3c3f709 100644
--- a/fpdf.go
+++ b/fpdf.go
@@ -406,6 +406,11 @@ func (f *Fpdf) open() {
// document contains no page, AddPage() is called to prevent the generation of
// an invalid document.
func (f *Fpdf) Close() {
+ if f.err == nil {
+ if f.clipActive {
+ f.err = fmt.Errorf("Clip procedure must be explicitly ended")
+ }
+ }
if f.err != nil {
return
}
@@ -682,7 +687,8 @@ func fillDrawOp(styleStr string) (opStr string) {
// It can be drawn (border only), filled (with no border) or both. styleStr can
// be "F" for filled, "D" for outlined only, or "DF" or "FD" for outlined and
// filled. An empty string will be replaced with "D". Drawing uses the current
-// draw color and line width. Filling uses the current fill color.
+// draw color and line width centered on the rectangle's perimeter. Filling
+// uses the current fill color.
func (f *Fpdf) Rect(x, y, w, h float64, styleStr string) {
f.outf("%.2f %.2f %.2f %.2f re %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, fillDrawOp(styleStr))
}
@@ -691,7 +697,8 @@ func (f *Fpdf) Rect(x, y, w, h float64, styleStr string) {
//
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
// outlined and filled. An empty string will be replaced with "D". Drawing uses
-// the current draw color and line width. Filling uses the current fill color.
+// the current draw color and line width centered on the circle's perimeter.
+// Filling uses the current fill color.
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
@@ -706,7 +713,8 @@ func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
//
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
// outlined and filled. An empty string will be replaced with "D". Drawing uses
-// the current draw color and line width. Filling uses the current fill color.
+// the current draw color and line width centered on the ellipse's perimeter.
+// Filling uses the current fill color.
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) Ellipse(x, y, rx, ry, degRotate float64, styleStr string) {
@@ -733,8 +741,8 @@ func (f *Fpdf) curve(cx0, cy0, x1, y1, cx1, cy1 float64) {
//
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
// outlined and filled. An empty string will be replaced with "D". Drawing uses
-// the current draw color, line width, and cap style. Filling uses the current
-// fill color.
+// the current draw color, line width, and cap style centered on the curve's
+// path. Filling uses the current fill color.
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) {
@@ -752,8 +760,8 @@ func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) {
//
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
// outlined and filled. An empty string will be replaced with "D". Drawing uses
-// the current draw color, line width, and cap style. Filling uses the current
-// fill color.
+// the current draw color, line width, and cap style centered on the curve's
+// path. Filling uses the current fill color.
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string) {
@@ -772,8 +780,8 @@ func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr s
//
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
// outlined and filled. An empty string will be replaced with "D". Drawing uses
-// the current draw color, line width, and cap style. Filling uses the current
-// fill color.
+// the current draw color, line width, and cap style centered on the arc's
+// path. Filling uses the current fill color.
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) {
@@ -873,14 +881,14 @@ func (f *Fpdf) SetAlpha(alpha float64, blendModeStr string) {
f.outf("/GS%d gs", pos)
}
-func (f *Fpdf) clipStart(x, y, w, h float64) {
+func (f *Fpdf) gradientClipStart(x, y, w, h float64) {
// Save current graphic state and set clipping area
f.outf("q %.2f %.2f %.2f %.2f re W n", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k)
// Set up transformation matrix for gradient
f.outf("%.3f 0 0 %.3f %.3f %.3f cm", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k)
}
-func (f *Fpdf) clipEnd() {
+func (f *Fpdf) gradientClipEnd() {
// Restore previous graphic state
f.out("Q")
}
@@ -911,9 +919,9 @@ func (f *Fpdf) gradient(tp int, r1, g1, b1 int, r2, g2, b2 int, x1, y1 float64,
//
// See tutorial 13 for an example of this function.
func (f *Fpdf) LinearGradient(x, y, w, h float64, r1, g1, b1 int, r2, g2, b2 int, x1, y1, x2, y2 float64) {
- f.clipStart(x, y, w, h)
+ f.gradientClipStart(x, y, w, h)
f.gradient(2, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, 0)
- f.clipEnd()
+ f.gradientClipEnd()
}
// Draws a rectangular area with a blending of one color to another. The
@@ -935,9 +943,176 @@ func (f *Fpdf) LinearGradient(x, y, w, h float64, r1, g1, b1 int, r2, g2, b2 int
//
// See tutorial 13 for an example of this function.
func (f *Fpdf) RadialGradient(x, y, w, h float64, r1, g1, b1 int, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
- f.clipStart(x, y, w, h)
+ f.gradientClipStart(x, y, w, h)
f.gradient(3, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, r)
- f.clipEnd()
+ 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
+// the rectangle's perimeter. After calling this method, all rendering
+// operations (for example, Image(), LinearGradient(), etc) will be clipped by
+// the specified rectangle. Call ClipEnd() to restore unclipped operations.
+
+func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) {
+ if !f.setClipActive() {
+ return
+ }
+ 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"))
+}
+
+// Begins a clipping operation in which rendering is confined to the character
+// string specified by txtStr. The origin (x, y) is on the left of the first
+// character at the baseline. The current font is used. outline is true to draw
+// a border with the current draw color and line width centered on the
+// perimeters of the text characters. After calling this method, all rendering
+// operations (for example, Image(), LinearGradient(), etc) will be clipped.
+// Call ClipEnd() to restore unclipped operations.
+
+func (f *Fpdf) ClipText(x, y float64, txtStr string, outline bool) {
+ if !f.setClipActive() {
+ return
+ }
+ 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))
+}
+
+func (f *Fpdf) clipArc(x1, y1, x2, y2, x3, y3 float64) {
+ h := f.h
+ f.outf("%.2f %.2f %.2f %.2f %.2f %.2f c ", x1*f.k, (h-y1)*f.k,
+ x2*f.k, (h-y2)*f.k, x3*f.k, (h-y3)*f.k)
+}
+
+// Begins a rectangular clipping operation. The rectangle is of width w and
+// height h. Its upper left corner is positioned at point (x, y). The rounded
+// corners of the rectangle are specified by radius r. outline is true to draw
+// a border with the current draw color and line width centered on the
+// rectangle's perimeter. After calling this method, all rendering operations
+// (for example, Image(), LinearGradient(), etc) will be clipped by the
+// specified rectangle. Call ClipEnd() to restore unclipped operations.
+
+func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) {
+ if !f.setClipActive() {
+ return
+ }
+ k := f.k
+ hp := f.h
+ myArc := (4.0 / 3.0) * (math.Sqrt2 - 1.0)
+ f.outf("q %.2f %.2f m", (x+r)*k, (hp-y)*k)
+ xc := x + w - r
+ yc := y + r
+ f.outf("%.2f %.2f l", xc*k, (hp-y)*k)
+ f.clipArc(xc+r*myArc, yc-r, xc+r, yc-r*myArc, xc+r, yc)
+ xc = x + w - r
+ yc = y + h - r
+ f.outf("%.2f %.2f l", (x+w)*k, (hp-yc)*k)
+ f.clipArc(xc+r, yc+r*myArc, xc+r*myArc, yc+r, xc, yc+r)
+ xc = x + r
+ yc = y + h - r
+ f.outf("%.2f %.2f l", xc*k, (hp-(y+h))*k)
+ f.clipArc(xc-r*myArc, yc+r, xc-r, yc+r*myArc, xc-r, yc)
+ xc = x + r
+ yc = y + r
+ f.outf("%.2f %.2f l", x*k, (hp-yc)*k)
+ f.clipArc(xc-r, yc-r*myArc, xc-r*myArc, yc-r, xc, yc-r)
+ f.outf(" W %s", StrIf(outline, "S", "n"))
+}
+
+// Begins an elliptical clipping operation. The ellipse is centered at (x, y).
+// Its horizontal and vertical radii are specified by rx and ry. outline is
+// true to draw a border with the current draw color and line width centered on
+// the ellipse's perimeter. After calling this method, all rendering operations
+// (for example, Image(), LinearGradient(), etc) will be clipped by the
+// specified ellipse. Call ClipEnd() to restore unclipped operations.
+
+func (f *Fpdf) ClipEllipse(x, y, rx, ry float64, outline bool) {
+ if !f.setClipActive() {
+ return
+ }
+ lx := (4.0 / 3.0) * rx * (math.Sqrt2 - 1)
+ ly := (4.0 / 3.0) * ry * (math.Sqrt2 - 1)
+ k := f.k
+ h := f.h
+ f.outf("q %.2f %.2f m %.2f %.2f %.2f %.2f %.2f %.2f c",
+ (x+rx)*k, (h-y)*k,
+ (x+rx)*k, (h-(y-ly))*k,
+ (x+lx)*k, (h-(y-ry))*k,
+ x*k, (h-(y-ry))*k)
+ f.outf("%.2f %.2f %.2f %.2f %.2f %.2f c",
+ (x-lx)*k, (h-(y-ry))*k,
+ (x-rx)*k, (h-(y-ly))*k,
+ (x-rx)*k, (h-y)*k)
+ f.outf("%.2f %.2f %.2f %.2f %.2f %.2f c",
+ (x-rx)*k, (h-(y+ly))*k,
+ (x-lx)*k, (h-(y+ry))*k,
+ x*k, (h-(y+ry))*k)
+ f.outf("%.2f %.2f %.2f %.2f %.2f %.2f c W %s",
+ (x+lx)*k, (h-(y+ry))*k,
+ (x+rx)*k, (h-(y+ly))*k,
+ (x+rx)*k, (h-y)*k,
+ StrIf(outline, "S", "n"))
+}
+
+// Begins a circular clipping operation. The circle is centered at (x, y) and
+// has radius r. outline is true to draw a border with the current draw color
+// and line width centered on the circle's perimeter. After calling this
+// method, all rendering operations (for example, Image(), LinearGradient(),
+// etc) will be clipped by the specified circle. Call ClipEnd() to restore
+// unclipped operations.
+
+func (f *Fpdf) ClipCircle(x, y, r float64, outline bool) {
+ f.ClipEllipse(x, y, r, r, outline)
+}
+
+// Begins a clipping operation within a polygon. The figure is defined by a
+// series of vertices specified by points. The x and y fields of the points use
+// the units established in New(). The last point in the slice will be
+// implicitly joined to the first to close the polygon. outline is true to draw
+// a border with the current draw color and line width centered on the
+// polygon's perimeter. After calling this method, all rendering operations
+// (for example, Image(), LinearGradient(), etc) will be clipped by the
+// specified polygon. Call ClipEnd() to restore unclipped operations.
+
+func (f *Fpdf) ClipPolygon(points []pointType, outline bool) {
+ if !f.setClipActive() {
+ return
+ }
+ 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("h W %s", StrIf(outline, "S", "n"))
+ f.out(s.String())
+}
+
+// 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.
+
+func (f *Fpdf) ClipEnd() {
+ if f.err == nil {
+ if f.clipActive {
+ f.clipActive = false
+ f.out("Q")
+ } else {
+ f.err = fmt.Errorf("Error attempting to end clip operation")
+ }
+ }
}
// Imports a TrueType, OpenType or Type1 font and makes it available. It is
@@ -1170,10 +1345,10 @@ func (f *Fpdf) LinkString(x, y, w, h float64, linkStr string) {
f.newLink(x, y, w, h, 0, linkStr)
}
-// Prints a character string. The origin is on the left of the first character,
-// on the baseline. This method allows to place a string precisely on the page,
-// but it is usually easier to use Cell(), MultiCell() or Write() which are the
-// standard methods to print text.
+// Prints a character string. The origin (x, y) is on the left of the first
+// character at the baseline. This method allows to place a string precisely on
+// the page, but it is usually easier to use Cell(), MultiCell() or Write()
+// which are the standard methods to print text.
func (f *Fpdf) Text(x, y float64, txtStr string) {
s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, f.escape(txtStr))
if f.underline && txtStr != "" {
diff --git a/fpdf_test.go b/fpdf_test.go
index 5e2e7a8..2ac32f5 100644
--- a/fpdf_test.go
+++ b/fpdf_test.go
@@ -85,6 +85,15 @@ func docWriter(pdf *Fpdf, idx int) *pdfWriter {
return pw
}
+func lorem() string {
+ return "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " +
+ "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " +
+ "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis " +
+ "aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat " +
+ "nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
+ "officia deserunt mollit anim id est laborum."
+}
+
// Hello, world
func ExampleFpdf_tutorial01() {
pdf := New("P", "mm", "A4", FONT_DIR)
@@ -546,12 +555,7 @@ func ExampleFpdf_tutorial08() {
func ExampleFpdf_tutorial09() {
var y0 float64
var crrntCol int
- loremStr := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " +
- "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " +
- "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis " +
- "aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat " +
- "nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
- "officia deserunt mollit anim id est laborum."
+ loremStr := lorem()
pdf := New("L", "mm", "A4", FONT_DIR)
const (
pageWd = 297.0 // A4 210.0 x 297.0
@@ -772,3 +776,56 @@ func ExampleFpdf_tutorial13() {
// Output:
// Successfully generated pdf/tutorial13.pdf
}
+
+// Clipping
+func ExampleFpdf_tutorial14() {
+ pdf := New("", "", "", FONT_DIR)
+ y := 10.0
+ pdf.AddPage()
+
+ pdf.SetFont("Helvetica", "", 24)
+ pdf.SetXY(0, y)
+ pdf.ClipText(10, y+12, "Clipping operations", false)
+ pdf.LinearGradient(0, y, 100, y+20, 0, 0, 0, 220, 220, 220, 0, 0, 1, 1)
+ pdf.ClipEnd()
+
+ y += 12
+ pdf.SetFont("Helvetica", "B", 120)
+ pdf.SetDrawColor(64, 80, 80)
+ pdf.SetLineWidth(.5)
+ pdf.ClipText(10, y+40, pdf.String(), true)
+ pdf.RadialGradient(10, y, 200, 50, 220, 220, 250, 80, 80, 220, 0.25, 0.5, 0.25, 0.5, 1)
+ 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.ClipEnd()
+
+ y += 28
+ pdf.ClipEllipse(26, y+10, 16, 10, true)
+ pdf.Image(IMG_DIR+"/logo.jpg", 10, y, 32, 0, false, "JPG", 0, "")
+ pdf.ClipEnd()
+
+ pdf.ClipCircle(60, y+10, 10, true)
+ pdf.RadialGradient(50, y, 20, 20, 220, 220, 250, 80, 80, 220, 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.ClipEnd()
+
+ y += 30
+ pdf.ClipRoundedRect(10, y, 120, 20, 5, true)
+ pdf.SetXY(5, y-5)
+ pdf.SetFont("Times", "", 12)
+ pdf.MultiCell(130, 5, lorem(), "", "", false)
+ pdf.ClipEnd()
+
+ pdf.OutputAndClose(docWriter(pdf, 14))
+ // Output:
+ // Successfully generated pdf/tutorial14.pdf
+}
diff --git a/util.go b/util.go
index 5043601..b2202ca 100644
--- a/util.go
+++ b/util.go
@@ -202,3 +202,21 @@ func utf8toutf16(s string) string {
}
return string(res)
}
+
+// Return a if cnd is true, otherwise b
+func IntIf(cnd bool, a, b int) int {
+ if cnd {
+ return a
+ } else {
+ return b
+ }
+}
+
+// Return aStr if cnd is true, otherwise bStr
+func StrIf(cnd bool, aStr, bStr string) string {
+ if cnd {
+ return aStr
+ } else {
+ return bStr
+ }
+}