From 557587281695e882c2244eac363312bcac643240 Mon Sep 17 00:00:00 2001 From: Kurt Jung Date: Wed, 29 Jan 2014 10:45:04 -0500 Subject: Factored basic SVG path rendering, basic HTML rendering, and color routines. --- def.go | 18 +++-- fpdf.go | 134 ++++++++++++++++++++++-------------- fpdf_test.go | 222 ++++++++--------------------------------------------------- htmlbasic.go | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++ svgwrite.go | 60 ++++++++++++++++ 5 files changed, 379 insertions(+), 251 deletions(-) create mode 100644 htmlbasic.go create mode 100644 svgwrite.go 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: bold, ` + `italic, underlined, or all at once!

` + `You can also insert links on text, such as ` + `www.fpdf.org, 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 ` + + `SVG (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 ` + + `jSignature ` + + `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) + } + } + } +} -- cgit v1.2.1-24-ge1ad