summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt Jung <kurt.w.jung@code.google.com>2014-01-29 10:45:04 -0500
committerKurt Jung <kurt.w.jung@code.google.com>2014-01-29 10:45:04 -0500
commit557587281695e882c2244eac363312bcac643240 (patch)
treed75e31d7b45e940e7453e4eb30ce2f2cba7250cf
parent85f9ff114702c1a4c9187762d8a7a7779c49f130 (diff)
Factored basic SVG path rendering, basic HTML rendering, and color routines.
-rw-r--r--def.go18
-rw-r--r--fpdf.go134
-rw-r--r--fpdf_test.go222
-rw-r--r--htmlbasic.go196
-rw-r--r--svgwrite.go60
5 files changed, 379 insertions, 251 deletions
diff --git a/def.go b/def.go
index bf78fea..15e6939 100644
--- a/def.go
+++ b/def.go
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 Kurt Jung (Gmail: kurt.w.jung)
+ * Copyright (c) 2013-2014 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
@@ -66,6 +66,14 @@ type ImageInfoType struct {
scale float64 // document scaling factor
}
+// PointConvert returns the value of pt, expressed in points (1/72 inch), as a
+// value expressed in the unit of measure specified in New(). Since font
+// management in Fpdf uses points, this method can help with line height
+// calculations and other methods that require user units.
+func (f *Fpdf) PointConvert(pt float64) float64 {
+ return pt / f.k
+}
+
// Extent returns the width and height of the image in the units of the Fpdf
// object.
func (info *ImageInfoType) Extent() (wd, ht float64) {
@@ -157,10 +165,6 @@ type Fpdf struct {
currentFont fontDefType // current font info
fontSizePt float64 // current font size in points
fontSize float64 // current font size in user unit
- drawColor string // commands for drawing color
- fillColor string // commands for filling color
- textColor string // commands for text color
- colorFlag bool // indicates whether fill and text colors are different
ws float64 // word spacing
images map[string]*ImageInfoType // array of used images
pageLinks [][]linkType // pageLinks[page][link], both 1-based
@@ -192,6 +196,10 @@ type Fpdf struct {
clipNest int // Number of active clipping contexts
transformNest int // Number of active transformation contexts
err error // Set if error occurs during life cycle of instance
+ colorFlag bool // indicates whether fill and text colors are different
+ color struct { // Composite values of colors
+ draw, fill, text clrType
+ }
}
type encType struct {
diff --git a/fpdf.go b/fpdf.go
index c2625d0..8009e73 100644
--- a/fpdf.go
+++ b/fpdf.go
@@ -80,11 +80,11 @@ func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType)
f.lasth = 0
f.fontFamily = ""
f.fontStyle = ""
- f.fontSizePt = 12
+ f.SetFontSize(12)
f.underline = false
- f.drawColor = "0 G"
- f.fillColor = "0 g"
- f.textColor = "0 g"
+ f.SetDrawColor(0, 0, 0) // "0 G"
+ f.SetFillColor(0, 0, 0) // "0 g"
+ f.SetTextColor(0, 0, 0) // "0 g"
f.colorFlag = false
f.ws = 0
f.fontpath = fontDirStr
@@ -512,9 +512,9 @@ func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
}
fontsize := f.fontSizePt
lw := f.lineWidth
- dc := f.drawColor
- fc := f.fillColor
- tc := f.textColor
+ dc := f.color.draw
+ fc := f.color.fill
+ tc := f.color.text
cf := f.colorFlag
if f.page > 0 {
// Page footer
@@ -542,15 +542,15 @@ func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
}
}
// Set colors
- f.drawColor = dc
- if dc != "0 G" {
- f.out(dc)
+ f.color.draw = dc
+ if dc.str != "0 G" {
+ f.out(dc.str)
}
- f.fillColor = fc
- if fc != "0 g" {
- f.out(fc)
+ f.color.fill = fc
+ if fc.str != "0 g" {
+ f.out(fc.str)
}
- f.textColor = tc
+ f.color.text = tc
f.colorFlag = cf
// Page header
if f.headerFnc != nil {
@@ -571,15 +571,15 @@ func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
}
}
// Restore colors
- if f.drawColor != dc {
- f.drawColor = dc
- f.out(dc)
+ if f.color.draw.str != dc.str {
+ f.color.draw = dc
+ f.out(dc.str)
}
- if f.fillColor != fc {
- f.fillColor = fc
- f.out(fc)
+ if f.color.fill.str != fc.str {
+ f.color.fill = fc
+ f.out(fc.str)
}
- f.textColor = tc
+ f.color.text = tc
f.colorFlag = cf
return
}
@@ -613,36 +613,34 @@ func (f *Fpdf) PageNo() int {
}
type clrType struct {
- r, g, b float64
+ r, g, b float64
+ ir, ig, ib int
+ gray bool
+ str string
}
-func colorComp(v int) float64 {
+func colorComp(v int) (int, float64) {
if v < 0 {
v = 0
} else if v > 255 {
v = 255
}
- return float64(v) / 255.0
+ return v, float64(v) / 255.0
}
-func colorValueString(r, g, b int) string {
- clr := colorValue(r, g, b)
- return sprintf("%.3f %.3f %.3f", clr.r, clr.g, clr.b)
-}
-
-func colorValue(r, g, b int) (clr clrType) {
- clr.r = colorComp(r)
- clr.g = colorComp(g)
- clr.b = colorComp(b)
- return
-}
-
-func colorString(r, g, b int, grayStr, fullStr string) (str string) {
- clr := colorValue(r, g, b)
- if r == g && r == b {
- str = sprintf("%.3f %s", clr.r, grayStr)
+func colorValue(r, g, b int, grayStr, fullStr string) (clr clrType) {
+ clr.ir, clr.r = colorComp(r)
+ clr.ig, clr.g = colorComp(g)
+ clr.ib, clr.b = colorComp(b)
+ clr.gray = clr.ir == clr.ig && clr.r == clr.b
+ if len(grayStr) > 0 {
+ if clr.gray {
+ clr.str = sprintf("%.3f %s", clr.r, grayStr)
+ } else {
+ clr.str = sprintf("%.3f %.3f %.3f %s", clr.r, clr.g, clr.b, fullStr)
+ }
} else {
- str = sprintf("%.3f %.3f %.3f %s", clr.r, clr.g, clr.b, fullStr)
+ clr.str = sprintf("%.3f %.3f %.3f", clr.r, clr.g, clr.b)
}
return
}
@@ -652,30 +650,48 @@ func colorString(r, g, b int, grayStr, fullStr string) (str string) {
// The method can be called before the first page is created and the value is
// retained from page to page.
func (f *Fpdf) SetDrawColor(r, g, b int) {
- f.drawColor = colorString(r, g, b, "G", "RG")
+ f.color.draw = colorValue(r, g, b, "G", "RG")
+ // f.drawColor = colorString(f.color.draw, "G", "RG")
if f.page > 0 {
- f.out(f.drawColor)
+ f.out(f.color.draw.str)
}
}
+// GetDrawColor returns the current draw color as RGB components (0 - 255).
+func (f *Fpdf) GetDrawColor() (int, int, int) {
+ return f.color.draw.ir, f.color.draw.ig, f.color.draw.ib
+}
+
// SetFillColor defines the color used for all filling operations (filled
// rectangles and cell backgrounds). It is expressed in RGB components (0
// -255). The method can be called before the first page is created and the
// value is retained from page to page.
func (f *Fpdf) SetFillColor(r, g, b int) {
- f.fillColor = colorString(r, g, b, "g", "rg")
- f.colorFlag = f.fillColor != f.textColor
+ f.color.fill = colorValue(r, g, b, "g", "rg")
+ // f.fillColor = colorString(f.color.fill, "g", "rg")
+ f.colorFlag = f.color.fill.str != f.color.text.str
if f.page > 0 {
- f.out(f.fillColor)
+ f.out(f.color.fill.str)
}
}
+// GetFillColor returns the current fill color as RGB components (0 - 255).
+func (f *Fpdf) GetFillColor() (int, int, int) {
+ return f.color.fill.ir, f.color.fill.ig, f.color.fill.ib
+}
+
// SetTextColor defines the color used for text. It is expressed in RGB
// components (0 - 255). The method can be called before the first page is
// created and the value is retained from page to page.
func (f *Fpdf) SetTextColor(r, g, b int) {
- f.textColor = colorString(r, g, b, "g", "rg")
- f.colorFlag = f.fillColor != f.textColor
+ f.color.text = colorValue(r, g, b, "g", "rg")
+ // f.textColor = colorString(f.color.text, "g", "rg")
+ f.colorFlag = f.color.fill.str != f.color.text.str
+}
+
+// GetTextColor returns the current text color as RGB components (0 - 255).
+func (f *Fpdf) GetTextColor() (int, int, int) {
+ return f.color.text.ir, f.color.text.ig, f.color.text.ib
}
// GetStringWidth returns the length of a string in user units. A font must be
@@ -958,8 +974,10 @@ func (f *Fpdf) gradientClipEnd() {
func (f *Fpdf) gradient(tp int, r1, g1, b1 int, r2, g2, b2 int, x1, y1 float64, x2, y2 float64, r float64) {
pos := len(f.gradientList)
- f.gradientList = append(f.gradientList, gradientType{tp, colorValueString(r1, g1, b1),
- colorValueString(r2, g2, b2), x1, y1, x2, y2, r, 0})
+ clr1 := colorValue(r1, g1, b1, "", "")
+ clr2 := colorValue(r2, g2, b2, "", "")
+ f.gradientList = append(f.gradientList, gradientType{tp, clr1.str, clr2.str,
+ x1, y1, x2, y2, r, 0})
f.outf("/Sh%d sh", pos)
}
@@ -1352,6 +1370,13 @@ func (f *Fpdf) SetFontSize(size float64) {
}
}
+// GetFontSize returns the size of the current font in points followed by the
+// size in the unit of measure specified in New(). The second value can be used
+// as a line height value in drawing operations.
+func (f *Fpdf) GetFontSize() (ptSize, unitSize float64) {
+ return f.fontSizePt, f.fontSize
+}
+
// AddLink creates a new internal link and returns its identifier. An internal
// link is a clickable area which directs to another place within the document.
// The identifier can then be passed to Cell(), Write(), Image() or Link(). The
@@ -1423,7 +1448,7 @@ func (f *Fpdf) Text(x, y float64, txtStr string) {
s += " " + f.dounderline(x, y, txtStr)
}
if f.colorFlag {
- s = sprintf("q %s %s Q", f.textColor, s)
+ s = sprintf("q %s %s Q", f.color.text.str, s)
}
f.out(s)
}
@@ -1555,7 +1580,7 @@ func (f *Fpdf) CellFormat(w, h float64, txtStr string, borderStr string, ln int,
dx = f.cMargin
}
if f.colorFlag {
- s.printf("q %s ", f.textColor)
+ s.printf("q %s ", f.color.text.str)
}
txt2 := strings.Replace(txtStr, "\\", "\\\\", -1)
txt2 = strings.Replace(txt2, "(", "\\(", -1)
@@ -2028,6 +2053,11 @@ func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) {
return info
}
+// GetXY returns the abscissa and ordinate of the current position.
+func (f *Fpdf) GetXY() (float64, float64) {
+ return f.x, f.y
+}
+
// GetX returns the abscissa of the current position.
func (f *Fpdf) GetX() float64 {
return f.x
diff --git a/fpdf_test.go b/fpdf_test.go
index c8bf2e3..32e4237 100644
--- a/fpdf_test.go
+++ b/fpdf_test.go
@@ -23,7 +23,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
- "regexp"
"strings"
)
@@ -110,71 +109,6 @@ func strDelimit(str string, sepstr string, sepcount int) string {
return str
}
-type htmlSegmentType struct {
- cat byte // 'O' open tag, 'C' close tag, 'T' text
- str string // Literal text unchanged, tags are lower case
- attr map[string]string // Attribute keys are lower case
-}
-
-// Returns a list of HTML tags and literal elements. This is done with regular
-// expressions, so the result is only marginally better than useless.
-// Adapted from http://www.fpdf.org/
-func htmlTokenize(htmlStr string) (list []htmlSegmentType) {
- list = make([]htmlSegmentType, 0, 16)
- htmlStr = strings.Replace(htmlStr, "\n", " ", -1)
- htmlStr = strings.Replace(htmlStr, "\r", "", -1)
- tagRe, _ := regexp.Compile(`(?U)<.*>`)
- attrRe, _ := regexp.Compile(`([^=]+)=["']?([^"']+)`)
- capList := tagRe.FindAllStringIndex(htmlStr, -1)
- if capList != nil {
- var seg htmlSegmentType
- var parts []string
- pos := 0
- for _, cap := range capList {
- if pos < cap[0] {
- seg.cat = 'T'
- seg.str = htmlStr[pos:cap[0]]
- seg.attr = nil
- list = append(list, seg)
- }
- if htmlStr[cap[0]+1] == '/' {
- seg.cat = 'C'
- seg.str = strings.ToLower(htmlStr[cap[0]+2 : cap[1]-1])
- seg.attr = nil
- list = append(list, seg)
- } else {
- // Extract attributes
- parts = strings.Split(htmlStr[cap[0]+1:cap[1]-1], " ")
- if len(parts) > 0 {
- for j, part := range parts {
- if j == 0 {
- seg.cat = 'O'
- seg.str = strings.ToLower(parts[0])
- seg.attr = make(map[string]string)
- } else {
- attrList := attrRe.FindAllStringSubmatch(part, -1)
- if attrList != nil {
- for _, attr := range attrList {
- seg.attr[strings.ToLower(attr[1])] = attr[2]
- }
- }
- }
- }
- list = append(list, seg)
- }
- }
- pos = cap[1]
- }
- if len(htmlStr) > pos {
- seg.cat = 'T'
- seg.str = htmlStr[pos:]
- seg.attr = nil
- list = append(list, seg)
- }
- }
- return
-}
-
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 " +
@@ -513,96 +447,32 @@ func ExampleFpdf_tutorial05() {
// Successfully generated pdf/tutorial05.pdf
}
-// Internal and external links
+// This example demonstrates internal and external links with and without basic
+// HTML.
func ExampleFpdf_tutorial06() {
- var boldLvl, italicLvl, underscoreLvl int
- var hrefStr string
pdf := gofpdf.New("P", "mm", "A4", cnFontDir)
- setStyle := func(boldAdj, italicAdj, underscoreAdj int) {
- styleStr := ""
- boldLvl += boldAdj
- if boldLvl > 0 {
- styleStr += "B"
- }
- italicLvl += italicAdj
- if italicLvl > 0 {
- styleStr += "I"
- }
- underscoreLvl += underscoreAdj
- if underscoreLvl > 0 {
- styleStr += "U"
- }
- pdf.SetFont("", styleStr, 0)
- }
- putLink := func(urlStr, txtStr string) {
- // Put a hyperlink
- pdf.SetTextColor(0, 0, 255)
- setStyle(0, 0, 1)
- pdf.WriteLinkString(5, txtStr, urlStr)
- setStyle(0, 0, -1)
- pdf.SetTextColor(0, 0, 0)
- }
-
- writeHTML := func(htmlStr string) {
- list := htmlTokenize(htmlStr)
- var ok bool
- for _, el := range list {
- switch el.cat {
- case 'T':
- if len(hrefStr) > 0 {
- putLink(hrefStr, el.str)
- hrefStr = ""
- } else {
- pdf.Write(5, el.str)
- }
- case 'O':
- switch el.str {
- case "b":
- setStyle(1, 0, 0)
- case "i":
- setStyle(0, 1, 0)
- case "u":
- setStyle(0, 0, 1)
- case "br":
- pdf.Ln(5)
- case "a":
- hrefStr, ok = el.attr["href"]
- if !ok {
- hrefStr = ""
- }
- }
- case 'C':
- switch el.str {
- case "b":
- setStyle(-1, 0, 0)
- case "i":
- setStyle(0, -1, 0)
- case "u":
- setStyle(0, 0, -1)
-
- }
- }
- }
- }
- // First page
+ // First page: manual local link
pdf.AddPage()
- pdf.SetFont("Arial", "", 20)
- pdf.Write(5, "To find out what's new in this tutorial, click ")
+ pdf.SetFont("Helvetica", "", 20)
+ _, lineHt := pdf.GetFontSize()
+ pdf.Write(lineHt, "To find out what's new in this tutorial, click ")
pdf.SetFont("", "U", 0)
link := pdf.AddLink()
- pdf.WriteLinkID(5, "here", link)
+ pdf.WriteLinkID(lineHt, "here", link)
pdf.SetFont("", "", 0)
- // Second page
+ // Second page: image link and basic HTML with link
pdf.AddPage()
pdf.SetLink(link, 0, -1)
pdf.Image(imageFile("logo.png"), 10, 12, 30, 0, false, "", 0, "http://www.fpdf.org")
pdf.SetLeftMargin(45)
pdf.SetFontSize(14)
+ _, lineHt = pdf.GetFontSize()
htmlStr := `You can now easily print text mixing different styles: <b>bold</b>, ` +
`<i>italic</i>, <u>underlined</u>, or <b><i><u>all at once</u></i></b>!<br><br>` +
`You can also insert links on text, such as ` +
`<a href="http://www.fpdf.org">www.fpdf.org</a>, or on an image: click on the logo.`
- writeHTML(htmlStr)
+ html := pdf.HtmlBasicNew()
+ html.Write(lineHt, htmlStr)
pdf.OutputAndClose(docWriter(pdf, 6))
// Output:
// Successfully generated pdf/tutorial06.pdf
@@ -1008,7 +878,7 @@ func ExampleFpdf_tutorial17() {
titleStr := "Transformations"
titlePt := 36.0
- titleHt := titlePt * 25.4 / 72.0
+ titleHt := pdf.PointConvert(titlePt)
pdf.SetFont("Helvetica", "", titlePt)
titleWd := pdf.GetStringWidth(titleStr)
titleX := (210 - titleWd) / 2
@@ -1148,11 +1018,11 @@ func ExampleFpdf_tutorial18() {
func ExampleFpdf_tutorial19() {
const (
fontPtSize = 18.0
- lineHt = fontPtSize * 25.4 / 72.0
wd = 100.0
)
pdf := gofpdf.New("P", "mm", "A4", cnFontDir) // A4 210.0 x 297.0
pdf.SetFont("Times", "", fontPtSize)
+ _, lineHt := pdf.GetFontSize()
pdf.AddPage()
pdf.SetMargins(10, 10, 10)
lines := pdf.SplitLines([]byte(lorem()), wd)
@@ -1175,8 +1045,7 @@ func ExampleFpdf_tutorial19() {
// type generated by the jSignature web control.
func ExampleFpdf_tutorial20() {
const (
- fontPtSize = 18.0
- lineHt = fontPtSize * 25.4 / 72.0
+ fontPtSize = 16.0
wd = 100.0
sigFileStr = "signature.svg"
)
@@ -1185,66 +1054,31 @@ func ExampleFpdf_tutorial20() {
err error
)
pdf := gofpdf.New("P", "mm", "A4", cnFontDir) // A4 210.0 x 297.0
- link := func(showStr, urlStr string) {
- pdf.SetFont("", "U", 0)
- pdf.SetTextColor(0, 0, 128)
- pdf.WriteLinkString(lineHt, showStr, urlStr)
- pdf.SetTextColor(0, 0, 0)
- pdf.SetFont("", "", 0)
- }
pdf.SetFont("Times", "", fontPtSize)
+ lineHt := pdf.PointConvert(fontPtSize)
pdf.AddPage()
pdf.SetMargins(10, 10, 10)
- pdf.Write(lineHt, "This example renders a simple ")
- link("SVG", "http://www.w3.org/TR/SVG/")
- pdf.Write(lineHt, " (scalable vector graphics) image that contains only "+
- "basic path commands without any styling, color fill, reflection or "+
- "endpoint closures. In particular, the type of vector graphic returned from a ")
- link("jSignature", "http://willowsystems.github.io/jSignature/#/demo/")
- pdf.Write(lineHt, " web control is supported and is used in this example.")
- pdf.Ln(3 * lineHt)
+ htmlStr := `This example renders a simple ` +
+ `<a href="http://www.w3.org/TR/SVG/">SVG</a> (scalable vector graphics) ` +
+ `image that contains only basic path commands without any styling, ` +
+ `color fill, reflection or endpoint closures. In particular, the ` +
+ `type of vector graphic returned from a ` +
+ `<a href="http://willowsystems.github.io/jSignature/#/demo/">jSignature</a> ` +
+ `web control is supported and is used in this example.`
+ html := pdf.HtmlBasicNew()
+ html.Write(lineHt, htmlStr)
sig, err = gofpdf.SvgBasicFileParse(imageFile(sigFileStr))
if err == nil {
- scale := 150 / sig.Wd
- scaleY := 50 / sig.Ht
+ scale := 100 / sig.Wd
+ scaleY := 30 / sig.Ht
if scale > scaleY {
scale = scaleY
}
- originX := (210.0 - scale*sig.Wd) / 2.0
- originY := pdf.GetY() + 10
- var x, y, newX, newY float64
- var cx0, cy0, cx1, cy1 float64
- var path []gofpdf.SvgBasicSegmentType
- var seg gofpdf.SvgBasicSegmentType
- val := func(arg int) (float64, float64) {
- return originX + scale*seg.Arg[arg], originY + scale*seg.Arg[arg+1]
- }
pdf.SetLineCapStyle("round")
pdf.SetLineWidth(0.25)
pdf.SetDrawColor(0, 0, 128)
- for j := 0; j < len(sig.Segments) && pdf.Ok(); j++ {
- path = sig.Segments[j]
- for k := 0; k < len(path) && pdf.Ok(); k++ {
- seg = path[k]
- switch seg.Cmd {
- case 'M':
- x, y = val(0)
- pdf.SetXY(x, y)
- case 'L':
- newX, newY = val(0)
- pdf.Line(x, y, newX, newY)
- x, y = newX, newY
- case 'C':
- cx0, cy0 = val(0)
- cx1, cy1 = val(2)
- newX, newY = val(4)
- pdf.CurveCubic(x, y, cx0, cy0, newX, newY, cx1, cy1, "D")
- x, y = newX, newY
- default:
- pdf.SetErrorf("Unexpected path command '%c'", seg.Cmd)
- }
- }
- }
+ pdf.SetXY((210.0-scale*sig.Wd)/2.0, pdf.GetY()+10)
+ pdf.SvgBasicWrite(&sig, scale)
} else {
pdf.SetError(err)
}
diff --git a/htmlbasic.go b/htmlbasic.go
new file mode 100644
index 0000000..b2a05e5
--- /dev/null
+++ b/htmlbasic.go
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2014 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 (
+ "regexp"
+ "strings"
+)
+
+type HtmlBasicSegmentType struct {
+ Cat byte // 'O' open tag, 'C' close tag, 'T' text
+ Str string // Literal text unchanged, tags are lower case
+ Attr map[string]string // Attribute keys are lower case
+}
+
+// HtmlBasicTokenize returns a list of HTML tags and literal elements. This is
+// done with regular expressions, so the result is only marginally better than
+// useless.
+func HtmlBasicTokenize(htmlStr string) (list []HtmlBasicSegmentType) {
+ // This routine is adapted from http://www.fpdf.org/
+ list = make([]HtmlBasicSegmentType, 0, 16)
+ htmlStr = strings.Replace(htmlStr, "\n", " ", -1)
+ htmlStr = strings.Replace(htmlStr, "\r", "", -1)
+ tagRe, _ := regexp.Compile(`(?U)<.*>`)
+ attrRe, _ := regexp.Compile(`([^=]+)=["']?([^"']+)`)
+ capList := tagRe.FindAllStringIndex(htmlStr, -1)
+ if capList != nil {
+ var seg HtmlBasicSegmentType
+ var parts []string
+ pos := 0
+ for _, cap := range capList {
+ if pos < cap[0] {
+ seg.Cat = 'T'
+ seg.Str = htmlStr[pos:cap[0]]
+ seg.Attr = nil
+ list = append(list, seg)
+ }
+ if htmlStr[cap[0]+1] == '/' {
+ seg.Cat = 'C'
+ seg.Str = strings.ToLower(htmlStr[cap[0]+2 : cap[1]-1])
+ seg.Attr = nil
+ list = append(list, seg)
+ } else {
+ // Extract attributes
+ parts = strings.Split(htmlStr[cap[0]+1:cap[1]-1], " ")
+ if len(parts) > 0 {
+ for j, part := range parts {
+ if j == 0 {
+ seg.Cat = 'O'
+ seg.Str = strings.ToLower(parts[0])
+ seg.Attr = make(map[string]string)
+ } else {
+ attrList := attrRe.FindAllStringSubmatch(part, -1)
+ if attrList != nil {
+ for _, attr := range attrList {
+ seg.Attr[strings.ToLower(attr[1])] = attr[2]
+ }
+ }
+ }
+ }
+ list = append(list, seg)
+ }
+ }
+ pos = cap[1]
+ }
+ if len(htmlStr) > pos {
+ seg.Cat = 'T'
+ seg.Str = htmlStr[pos:]
+ seg.Attr = nil
+ list = append(list, seg)
+ }
+ }
+ return
+}
+
+// HtmlBasicType is used for rendering a very basic subset of HTML. It supports
+// only the bold, italic and underscore attributes and hyperlinks. In the Link
+// structure, the ClrR, ClrG and ClrB fields (0 through 255) define the color
+// of hyperlinks. The Bold, Italic and Underscore values (0 for off, 1 for on)
+// define the hyperlink style.
+type HtmlBasicType struct {
+ pdf *Fpdf
+ Link struct {
+ ClrR, ClrG, ClrB int
+ Bold, Italic, Underscore bool
+ }
+}
+
+// HtmlBasicNew returns an instance that facilitates writing basic HTML in the
+// specified PDF file.
+func (f *Fpdf) HtmlBasicNew() (html HtmlBasicType) {
+ html.pdf = f
+ html.Link.ClrR, html.Link.ClrG, html.Link.ClrB = 0, 0, 128
+ html.Link.Bold, html.Link.Italic, html.Link.Underscore = false, false, true
+ return
+}
+
+// Write prints text from the current position using the currently selected
+// font. The text can be encoded with a basic subset of HTML that includes tags
+// for italic (I), bold (B) and underscore (U) attributes and hyperlinks. When
+// the right margin is reached a line break occurs and text continues from the
+// left margin. Upon method exit, the current position is left at the end of
+// the text.
+//
+// lineHt indicates the line height in the unit of measure specified in New().
+func (html *HtmlBasicType) Write(lineHt float64, htmlStr string) {
+ var boldLvl, italicLvl, underscoreLvl, linkBold, linkItalic, linkUnderscore int
+ var textR, textG, textB = html.pdf.GetTextColor()
+ var hrefStr string
+ if html.Link.Bold {
+ linkBold = 1
+ }
+ if html.Link.Italic {
+ linkItalic = 1
+ }
+ if html.Link.Underscore {
+ linkUnderscore = 1
+ }
+ setStyle := func(boldAdj, italicAdj, underscoreAdj int) {
+ styleStr := ""
+ boldLvl += boldAdj
+ if boldLvl > 0 {
+ styleStr += "B"
+ }
+ italicLvl += italicAdj
+ if italicLvl > 0 {
+ styleStr += "I"
+ }
+ underscoreLvl += underscoreAdj
+ if underscoreLvl > 0 {
+ styleStr += "U"
+ }
+ html.pdf.SetFont("", styleStr, 0)
+ }
+ putLink := func(urlStr, txtStr string) {
+ // Put a hyperlink
+ html.pdf.SetTextColor(html.Link.ClrR, html.Link.ClrG, html.Link.ClrB)
+ setStyle(linkBold, linkItalic, linkUnderscore)
+ html.pdf.WriteLinkString(lineHt, txtStr, urlStr)
+ setStyle(-linkBold, -linkItalic, -linkUnderscore)
+ html.pdf.SetTextColor(textR, textG, textB)
+ }
+ list := HtmlBasicTokenize(htmlStr)
+ var ok bool
+ for _, el := range list {
+ switch el.Cat {
+ case 'T':
+ if len(hrefStr) > 0 {
+ putLink(hrefStr, el.Str)
+ hrefStr = ""
+ } else {
+ html.pdf.Write(lineHt, el.Str)
+ }
+ case 'O':
+ switch el.Str {
+ case "b":
+ setStyle(1, 0, 0)
+ case "i":
+ setStyle(0, 1, 0)
+ case "u":
+ setStyle(0, 0, 1)
+ case "br":
+ html.pdf.Ln(lineHt)
+ case "a":
+ hrefStr, ok = el.Attr["href"]
+ if !ok {
+ hrefStr = ""
+ }
+ }
+ case 'C':
+ switch el.Str {
+ case "b":
+ setStyle(-1, 0, 0)
+ case "i":
+ setStyle(0, -1, 0)
+ case "u":
+ setStyle(0, 0, -1)
+
+ }
+ }
+ }
+}
diff --git a/svgwrite.go b/svgwrite.go
new file mode 100644
index 0000000..ea445c1
--- /dev/null
+++ b/svgwrite.go
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2014 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
+
+// SvgBasicWrite renders the paths encoded in the basic SVG image specified by
+// sb. The scale value is used to convert the coordinates in the path to the
+// unit of measure specified in New(). The current position (as set with a call
+// to SetXY()) is used as the origin of the image. The current line cap style
+// (as set with SetLineCapStyle()), line width (as set with SetLineWidth()),
+// and draw color (as set with SetDrawColor()) are used in drawing the image
+// paths.
+//
+// See example 20 for a demonstration of this function.
+func (f *Fpdf) SvgBasicWrite(sb *SvgBasicType, scale float64) {
+ originX, originY := f.GetXY()
+ var x, y, newX, newY float64
+ var cx0, cy0, cx1, cy1 float64
+ var path []SvgBasicSegmentType
+ var seg SvgBasicSegmentType
+ val := func(arg int) (float64, float64) {
+ return originX + scale*seg.Arg[arg], originY + scale*seg.Arg[arg+1]
+ }
+ for j := 0; j < len(sb.Segments) && f.Ok(); j++ {
+ path = sb.Segments[j]
+ for k := 0; k < len(path) && f.Ok(); k++ {
+ seg = path[k]
+ switch seg.Cmd {
+ case 'M':
+ x, y = val(0)
+ f.SetXY(x, y)
+ case 'L':
+ newX, newY = val(0)
+ f.Line(x, y, newX, newY)
+ x, y = newX, newY
+ case 'C':
+ cx0, cy0 = val(0)
+ cx1, cy1 = val(2)
+ newX, newY = val(4)
+ f.CurveCubic(x, y, cx0, cy0, newX, newY, cx1, cy1, "D")
+ x, y = newX, newY
+ default:
+ f.SetErrorf("Unexpected path command '%c'", seg.Cmd)
+ }
+ }
+ }
+}