summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc.go2
-rw-r--r--fpdf.go164
-rw-r--r--fpdf_test.go44
3 files changed, 148 insertions, 62 deletions
diff --git a/doc.go b/doc.go
index 4e54658..75091e0 100644
--- a/doc.go
+++ b/doc.go
@@ -72,7 +72,7 @@ Reader interface while maintaining backward compatibility. Anthony Starks
provided code for the Polygon function. Robert Lillack provided the Beziergon
function and corrected some naming issues with the internal curve function.
Claudio Felber provided implementations for dashed line drawing and generalized
-font loading. Stanim provided support for multi-segment path drawing with
+font loading. Stani Michiels provided support for multi-segment path drawing with
smooth line joins and line join styles. Bruno Michel has provided valuable
assistance with the code.
diff --git a/fpdf.go b/fpdf.go
index 6c21210..ea4b384 100644
--- a/fpdf.go
+++ b/fpdf.go
@@ -907,7 +907,7 @@ func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) Ellipse(x, y, rx, ry, degRotate float64, styleStr string) {
- f.Arc(x, y, rx, ry, degRotate, 0, 360, styleStr)
+ f.arc(x, y, rx, ry, degRotate, 0, 360, styleStr, false)
}
// Polygon draws a closed figure defined by a series of vertices specified by
@@ -1047,54 +1047,7 @@ func (f *Fpdf) CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styl
//
// See tutorial 11 for an example of this function.
func (f *Fpdf) Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) {
- x *= f.k
- y = (f.h - y) * f.k
- rx *= f.k
- ry *= f.k
- segments := int(degEnd-degStart) / 60
- if segments < 2 {
- segments = 2
- }
- angleStart := degStart * math.Pi / 180
- angleEnd := degEnd * math.Pi / 180
- angleTotal := angleEnd - angleStart
- dt := angleTotal / float64(segments)
- dtm := dt / 3
- if degRotate != 0 {
- a := -degRotate * math.Pi / 180
- f.outf("q %.5f %.5f %.5f %.5f %.5f %.5f cm", math.Cos(a), -1*math.Sin(a),
- math.Sin(a), math.Cos(a), x, y)
- x = 0
- y = 0
- }
- t := angleStart
- a0 := x + rx*math.Cos(t)
- b0 := y + ry*math.Sin(t)
- c0 := -rx * math.Sin(t)
- d0 := ry * math.Cos(t)
- f.point(a0/f.k, f.h-(b0/f.k))
- for j := 1; j <= segments; j++ {
- // Draw this bit of the total curve
- t = (float64(j) * dt) + angleStart
- a1 := x + rx*math.Cos(t)
- b1 := y + ry*math.Sin(t)
- c1 := -rx * math.Sin(t)
- d1 := ry * math.Cos(t)
- f.curve((a0+(c0*dtm))/f.k,
- f.h-((b0+(d0*dtm))/f.k),
- (a1-(c1*dtm))/f.k,
- f.h-((b1-(d1*dtm))/f.k),
- a1/f.k,
- f.h-(b1/f.k))
- a0 = a1
- b0 = b1
- c0 = c1
- d0 = d1
- }
- f.out(fillDrawOp(styleStr))
- if degRotate != 0 {
- f.out("Q")
- }
+ f.arc(x, y, rx, ry, degRotate, degStart, degEnd, styleStr, false)
}
// SetAlpha sets the alpha blending channel. The blending effect applies to
@@ -3487,19 +3440,22 @@ func (f *Fpdf) enddoc() {
}
// Path Drawing
-//
-// Create a "path" by moving a virtual stylus around the page, then draw it or
-// fill it in. The main advantage of using the path drawing routines rather
-// than multiple Fpdf.Line is that PDF creates nice line joins at the angles,
-// rather than just overlaying the lines.
-// MoveTo moves the stylus to (x, y) without drawing the path from the previous
-// point. Paths must start with a MoveTo to set the original stylus location or
-// the result is undefined.
+// MoveTo moves the stylus to (x, y) without drawing the path from the
+// previous point. Paths must start with a MoveTo to set the original
+// stylus location or the result is undefined.
+//
+// Create a "path" by moving a virtual stylus around the page (with
+// MoveTo, LineTo, CurveTo, CurveBezierCubicTo, ArcTo & ClosePath)
+// then draw it or fill it in (with DrawPath). The main advantage of
+// using the path drawing routines rather than multiple Fpdf.Line is
+// that PDF creates nice line joins at the angles, rather than just
+// overlaying the lines.
//
// See tutorial 30 for an example of this function.
func (f *Fpdf) MoveTo(x, y float64) {
- f.point(x, y) // rename?
+ f.point(x, y)
+ f.x, f.y = x, y
}
// LineTo creates a line from the current stylus location to (x, y), which
@@ -3509,6 +3465,7 @@ func (f *Fpdf) MoveTo(x, y float64) {
// See tutorial 30 for an example of this function.
func (f *Fpdf) LineTo(x, y float64) {
f.outf("%.2f %.2f l", x*f.k, (f.h-y)*f.k)
+ f.x, f.y = x, y
}
// CurveTo creates a single-segment quadratic Bézier curve. The curve starts at
@@ -3521,6 +3478,7 @@ func (f *Fpdf) LineTo(x, y float64) {
// See tutorial 30 for an example of this function.
func (f *Fpdf) CurveTo(cx, cy, x, y float64) {
f.outf("%.5f %.5f %.5f %.5f v", cx*f.k, (f.h-cy)*f.k, x*f.k, (f.h-y)*f.k)
+ f.x, f.y = x, y
}
// CurveBezierCubicTo creates a single-segment cubic Bézier curve. The curve
@@ -3533,7 +3491,8 @@ func (f *Fpdf) CurveTo(cx, cy, x, y float64) {
//
// See tutorial 30 for examples of this function.
func (f *Fpdf) CurveBezierCubicTo(cx0, cy0, cx1, cy1, x, y float64) {
- f.curve(cx0, cy0, cx1, cy1, x, y) // rename?
+ f.curve(cx0, cy0, cx1, cy1, x, y)
+ f.x, f.y = x, y
}
// ClosePath creates a line from the current location to the last MoveTo point
@@ -3556,3 +3515,90 @@ func (f *Fpdf) ClosePath() {
func (f *Fpdf) DrawPath(styleStr string) {
f.outf(fillDrawOp(styleStr))
}
+
+// ArcTo draws an elliptical arc centered at point (x, y). rx and ry specify its
+// horizontal and vertical radii. If the start of the arc is not at
+// the current position, a connecting line will be drawn.
+//
+// degRotate specifies the angle that the arc will be rotated. degStart and
+// degEnd specify the starting and ending angle of the arc. All angles are
+// specified in degrees and measured counter-clockwise from the 3 o'clock
+// position.
+//
+// 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 centered on the arc's
+// path. Filling uses the current fill color.
+//
+// See tutorial 30 for an example of this function.
+func (f *Fpdf) ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) {
+ f.arc(x, y, rx, ry, degRotate, degStart, degEnd, "", true)
+}
+
+func (f *Fpdf) arc(x, y, rx, ry, degRotate, degStart, degEnd float64,
+ styleStr string, path bool) {
+ x *= f.k
+ y = (f.h - y) * f.k
+ rx *= f.k
+ ry *= f.k
+ segments := int(degEnd-degStart) / 60
+ if segments < 2 {
+ segments = 2
+ }
+ angleStart := degStart * math.Pi / 180
+ angleEnd := degEnd * math.Pi / 180
+ angleTotal := angleEnd - angleStart
+ dt := angleTotal / float64(segments)
+ dtm := dt / 3
+ if degRotate != 0 {
+ a := -degRotate * math.Pi / 180
+ f.outf("q %.5f %.5f %.5f %.5f %.5f %.5f cm",
+ math.Cos(a), -1*math.Sin(a),
+ math.Sin(a), math.Cos(a), x, y)
+ x = 0
+ y = 0
+ }
+ t := angleStart
+ a0 := x + rx*math.Cos(t)
+ b0 := y + ry*math.Sin(t)
+ c0 := -rx * math.Sin(t)
+ d0 := ry * math.Cos(t)
+ sx := a0 / f.k // start point of arc
+ sy := f.h - (b0 / f.k)
+ if path {
+ if f.x != sx || f.y != sy {
+ // Draw connecting line to start point
+ f.LineTo(sx, sy)
+ }
+ } else {
+ f.point(sx, sy)
+ }
+ for j := 1; j <= segments; j++ {
+ // Draw this bit of the total curve
+ t = (float64(j) * dt) + angleStart
+ a1 := x + rx*math.Cos(t)
+ b1 := y + ry*math.Sin(t)
+ c1 := -rx * math.Sin(t)
+ d1 := ry * math.Cos(t)
+ f.curve((a0+(c0*dtm))/f.k,
+ f.h-((b0+(d0*dtm))/f.k),
+ (a1-(c1*dtm))/f.k,
+ f.h-((b1-(d1*dtm))/f.k),
+ a1/f.k,
+ f.h-(b1/f.k))
+ a0 = a1
+ b0 = b1
+ c0 = c1
+ d0 = d1
+ if path {
+ f.x = a1 / f.k
+ f.y = f.h - (b1 / f.k)
+ }
+ }
+ if !path {
+ f.out(fillDrawOp(styleStr))
+ }
+ if degRotate != 0 {
+ f.out("Q")
+ }
+}
diff --git a/fpdf_test.go b/fpdf_test.go
index 8959ece..9ac1e18 100644
--- a/fpdf_test.go
+++ b/fpdf_test.go
@@ -20,7 +20,6 @@ import (
"bufio"
"bytes"
"fmt"
- "github.com/jung-kurt/gofpdf"
"io"
"io/ioutil"
"math"
@@ -28,6 +27,8 @@ import (
"os"
"path/filepath"
"strings"
+
+ "github.com/jung-kurt/gofpdf"
)
// Absolute path needed for gocov tool; relative OK for test
@@ -1465,7 +1466,8 @@ func ExampleFpdf_tutorial30() {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.MoveTo(20, 20)
- pdf.LineTo(190, 20)
+ pdf.LineTo(170, 20)
+ pdf.ArcTo(170, 40, 20, 20, 0, 90, 0)
pdf.CurveTo(190, 100, 105, 100)
pdf.CurveBezierCubicTo(20, 100, 105, 200, 20, 200)
pdf.ClosePath()
@@ -1476,3 +1478,41 @@ func ExampleFpdf_tutorial30() {
// Output:
// Successfully generated pdf/tutorial30.pdf
}
+
+func ExampleFpdf_tutorial31() {
+ const offset = 75.0
+ pdf := gofpdf.New("L", "mm", "A4", "")
+ pdf.AddPage()
+ var draw = func(cap, join string, x0, y0, x1, y1 float64) {
+ // transform begin & end needed to isolate caps and joins
+ pdf.SetLineCapStyle(cap)
+ pdf.SetLineJoinStyle(join)
+
+ // Draw thick line
+ pdf.SetDrawColor(0x33, 0x33, 0x33)
+ pdf.SetLineWidth(30.0)
+ pdf.MoveTo(x0, y0)
+ pdf.LineTo((x0+x1)/2+offset, (y0+y1)/2)
+ pdf.LineTo(x1, y1)
+ pdf.DrawPath("D")
+
+ // Draw thin helping line
+ pdf.SetDrawColor(0xFF, 0x33, 0x33)
+ pdf.SetLineWidth(2.56)
+ pdf.MoveTo(x0, y0)
+ pdf.LineTo((x0+x1)/2+offset, (y0+y1)/2)
+ pdf.LineTo(x1, y1)
+ pdf.DrawPath("D")
+
+ }
+ x := 35.0
+ caps := []string{"butt", "square", "round"}
+ joins := []string{"bevel", "miter", "round"}
+ for i := range caps {
+ draw(caps[i], joins[i], x, 50, x, 160)
+ x += offset
+ }
+ pdf.OutputAndClose(docWriter(pdf, 31))
+ // Output:
+ // Successfully generated pdf/tutorial31.pdf
+}