diff options
-rw-r--r-- | doc.go | 44 | ||||
-rw-r--r-- | fpdf.go | 2 | ||||
-rw-r--r-- | fpdf_test.go | 132 | ||||
-rw-r--r-- | image/signature.svg | 43 | ||||
-rw-r--r-- | svgbasic.go | 210 |
5 files changed, 393 insertions, 38 deletions
@@ -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 @@ -15,21 +15,8 @@ */ /* -Package gofpdf implements a PDF document generator. - -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 and clipping is -adapted from FPDF scripts by Andreas Würmser. Support for outline bookmarks is -adapted from Olivier Plathey by Manuel Cornes. Support for transformations is -adapted from the FPDF transformation script by Moritz Wagner and Andreas -Würmser. Lawrence Kesteloot provided code to allow an image's extent to be -determined prior to placement. Bruno Michel has provided valuable assistance -with the code. - -The FPDF website is http://www.fpdf.org/. +Package gofpdf implements a PDF document generator with high level support for +text, drawing and images. Features @@ -57,10 +44,31 @@ Features • Clipping +• Basic path-only SVG images + 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 -is derived, this package does not yet support UTF-8 source text. gofpdf is -copyrighted by Kurt Jung and is released under the MIT License. +is derived, this package does not yet support UTF-8 source text. + +Acknowledgments + +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 and clipping is +adapted from FPDF scripts by Andreas Würmser. Support for outline bookmarks is +adapted from Olivier Plathey by Manuel Cornes. Support for transformations is +adapted from the FPDF transformation script by Moritz Wagner and Andreas +Würmser. Lawrence Kesteloot provided code to allow an image's extent to be +determined prior to placement. Bruno Michel has provided valuable assistance +with the code. + +The FPDF website is http://www.fpdf.org/. + +License + +gofpdf is copyrighted by Kurt Jung and is released under the MIT License. Installation @@ -826,7 +826,7 @@ func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) { // 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. +// See tutorials 11 and 20 for examples of this function. func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string) { f.point(x0, y0) f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k, diff --git a/fpdf_test.go b/fpdf_test.go index d70875a..c8bf2e3 100644 --- a/fpdf_test.go +++ b/fpdf_test.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 @@ -88,6 +88,18 @@ func docWriter(pdf *gofpdf.Fpdf, idx int) *pdfWriter { return pw } +func imageFile(fileStr string) string { + return filepath.Join(cnImgDir, fileStr) +} + +func fontFile(fileStr string) string { + return filepath.Join(cnFontDir, fileStr) +} + +func textFile(fileStr string) string { + return filepath.Join(cnTextDir, fileStr) +} + // Convert 'ABCDEFG' to, for example, 'A,BCD,EFG' func strDelimit(str string, sepstr string, sepcount int) string { pos := len(str) - sepcount @@ -187,7 +199,7 @@ func ExampleFpdf_tutorial01() { func ExampleFpdf_tutorial02() { pdf := gofpdf.New("P", "mm", "A4", cnFontDir) pdf.SetHeaderFunc(func() { - pdf.Image(cnImgDir+"/logo.png", 10, 6, 30, 0, false, "", 0, "") + pdf.Image(imageFile("logo.png"), 10, 6, 30, 0, false, "", 0, "") pdf.SetY(5) pdf.SetFont("Arial", "B", 15) pdf.Cell(80, 0, "") @@ -274,8 +286,8 @@ func ExampleFpdf_tutorial03() { chapterTitle(chapNum, titleStr) chapterBody(fileStr) } - printChapter(1, "A RUNAWAY REEF", cnTextDir+"/20k_c1.txt") - printChapter(2, "THE PROS AND CONS", cnTextDir+"/20k_c2.txt") + printChapter(1, "A RUNAWAY REEF", textFile("20k_c1.txt")) + printChapter(2, "THE PROS AND CONS", textFile("20k_c2.txt")) pdf.OutputAndClose(docWriter(pdf, 3)) // Output: // Successfully generated pdf/tutorial03.pdf @@ -374,8 +386,8 @@ func ExampleFpdf_tutorial04() { // Page number pdf.CellFormat(0, 10, fmt.Sprintf("Page %d", pdf.PageNo()), "", 0, "C", false, 0, "") }) - printChapter(1, "A RUNAWAY REEF", cnTextDir+"/20k_c1.txt") - printChapter(2, "THE PROS AND CONS", cnTextDir+"/20k_c2.txt") + printChapter(1, "A RUNAWAY REEF", textFile("20k_c1.txt")) + printChapter(2, "THE PROS AND CONS", textFile("20k_c2.txt")) pdf.OutputAndClose(docWriter(pdf, 4)) // Output: // Successfully generated pdf/tutorial04.pdf @@ -488,7 +500,7 @@ func ExampleFpdf_tutorial05() { } pdf.CellFormat(wSum, 0, "", "T", 0, "", false, 0, "") } - loadData(cnTextDir + "/countries.txt") + loadData(textFile("countries.txt")) pdf.SetFont("Arial", "", 14) pdf.AddPage() basicTable() @@ -583,7 +595,7 @@ func ExampleFpdf_tutorial06() { // Second page pdf.AddPage() pdf.SetLink(link, 0, -1) - pdf.Image(cnImgDir+"/logo.png", 10, 12, 30, 0, false, "", 0, "http://www.fpdf.org") + pdf.Image(imageFile("logo.png"), 10, 12, 30, 0, false, "", 0, "http://www.fpdf.org") pdf.SetLeftMargin(45) pdf.SetFontSize(14) htmlStr := `You can now easily print text mixing different styles: <b>bold</b>, ` + @@ -613,15 +625,15 @@ func ExampleFpdf_tutorial08() { pdf := gofpdf.New("P", "mm", "A4", cnFontDir) pdf.AddPage() pdf.SetFont("Arial", "", 11) - pdf.Image(cnImgDir+"/logo.png", 10, 10, 30, 0, false, "", 0, "") + pdf.Image(imageFile("logo.png"), 10, 10, 30, 0, false, "", 0, "") pdf.Text(50, 20, "logo.png") - pdf.Image(cnImgDir+"/logo.gif", 10, 40, 30, 0, false, "", 0, "") + pdf.Image(imageFile("logo.gif"), 10, 40, 30, 0, false, "", 0, "") pdf.Text(50, 50, "logo.gif") - pdf.Image(cnImgDir+"/logo-gray.png", 10, 70, 30, 0, false, "", 0, "") + pdf.Image(imageFile("logo-gray.png"), 10, 70, 30, 0, false, "", 0, "") pdf.Text(50, 80, "logo-gray.png") - pdf.Image(cnImgDir+"/logo-rgb.png", 10, 100, 30, 0, false, "", 0, "") + pdf.Image(imageFile("logo-rgb.png"), 10, 100, 30, 0, false, "", 0, "") pdf.Text(50, 110, "logo-rgb.png") - pdf.Image(cnImgDir+"/logo.jpg", 10, 130, 30, 0, false, "", 0, "") + pdf.Image(imageFile("logo.jpg"), 10, 130, 30, 0, false, "", 0, "") pdf.Text(50, 140, "logo.jpg") pdf.OutputAndClose(docWriter(pdf, 8)) // Output: @@ -673,9 +685,9 @@ func ExampleFpdf_tutorial09() { pdf.SetFont("Times", "", 12) for j := 0; j < 20; j++ { if j == 1 { - pdf.Image(cnImgDir+"/fpdf.png", -1, 0, colWd, 0, true, "", 0, "") + pdf.Image(imageFile("fpdf.png"), -1, 0, colWd, 0, true, "", 0, "") } else if j == 5 { - pdf.Image(cnImgDir+"/golang-gopher.png", -1, 0, colWd, 0, true, "", 0, "") + pdf.Image(imageFile("golang-gopher.png"), -1, 0, colWd, 0, true, "", 0, "") } pdf.MultiCell(colWd, 5, loremStr, "", "", false) pdf.Ln(-1) @@ -687,7 +699,7 @@ func ExampleFpdf_tutorial09() { // Test the corner cases as reported by the gocov tool func ExampleFpdf_tutorial10() { - gofpdf.MakeFont(cnFontDir+"/calligra.ttf", cnFontDir+"/cp1252.map", cnFontDir, nil, true) + gofpdf.MakeFont(fontFile("calligra.ttf"), fontFile("cp1252.map"), cnFontDir, nil, true) pdf := gofpdf.New("", "", "", "") pdf.SetFontLocation(cnFontDir) pdf.SetTitle("世界", true) @@ -822,7 +834,7 @@ func ExampleFpdf_tutorial12() { pdf.SetXY(x, y+2) pdf.CellFormat(rectW, rectH, "A", "", 0, "C", false, 0, "") pdf.SetAlpha(0.5, modeList[j]) - pdf.Image(cnImgDir+"/golang-gopher.png", x-gapX, y, rectW+2*gapX, 0, false, "", 0, "") + pdf.Image(imageFile("golang-gopher.png"), x-gapX, y, rectW+2*gapX, 0, false, "", 0, "") pdf.SetAlpha(1.0, "Normal") x += rectW + gapX j++ @@ -887,7 +899,7 @@ func ExampleFpdf_tutorial14() { y += 28 pdf.ClipEllipse(26, y+10, 16, 10, true) - pdf.Image(cnImgDir+"/logo.jpg", 10, y, 32, 0, false, "JPG", 0, "") + pdf.Image(imageFile("logo.jpg"), 10, y, 32, 0, false, "JPG", 0, "") pdf.ClipEnd() pdf.ClipCircle(60, y+10, 10, true) @@ -1105,7 +1117,7 @@ func ExampleFpdf_tutorial18() { pdf.SetMargins(10, 10, 10) pdf.SetFont("Helvetica", "", 15) for j, str := range fileList { - fileStr = filepath.Join(cnImgDir, str) + fileStr = imageFile(str) infoPtr = pdf.RegisterImage(fileStr, "") imgWd, imgHt = infoPtr.Extent() switch j { @@ -1158,3 +1170,85 @@ func ExampleFpdf_tutorial19() { // Output: // Successfully generated pdf/tutorial19.pdf } + +// This example demonstrates how to render a simple path-only SVG image of the +// type generated by the jSignature web control. +func ExampleFpdf_tutorial20() { + const ( + fontPtSize = 18.0 + lineHt = fontPtSize * 25.4 / 72.0 + wd = 100.0 + sigFileStr = "signature.svg" + ) + var ( + sig gofpdf.SvgBasicType + 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) + 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) + sig, err = gofpdf.SvgBasicFileParse(imageFile(sigFileStr)) + if err == nil { + scale := 150 / sig.Wd + scaleY := 50 / 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) + } + } + } + } else { + pdf.SetError(err) + } + pdf.OutputAndClose(docWriter(pdf, 20)) + // Output: + // Successfully generated pdf/tutorial20.pdf +} diff --git a/image/signature.svg b/image/signature.svg new file mode 100644 index 0000000..cdbb4af --- /dev/null +++ b/image/signature.svg @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" width="4010" height="570"> + <path + d="m 243.83517,126.31141 c 50.21866,26.50627 126.75595,-3.87395 151.46369,-35.94162 24.70774,-32.067668 10.41159,-51.980992 -16.41089,-58.072772 -26.82248,-6.09178 -42.17661,10.494756 -57.68933,33.13636 -15.51272,22.641602 -22.3395,49.640452 -26.04984,89.319922 -3.71034,39.67947 -2.57151,103.67609 -16.65175,144.69433 -14.08024,41.01824 -40.79012,59.98014 -80.0876,71.50199 -39.29748,11.52185 -96.60749,6.67866 -126.372403,-24.20296 -29.764916,-30.88162 -24.693439,-84.55794 0.62001,-107.05538 25.313453,-22.49744 49.916583,-21.8463 53.386943,18.74456 3.47036,40.59086 -77.347631,106.99944 -102.157291,102.59574 -24.80966,-4.4037 -9.193082,0 -13.789623,0" /> + <path + d="m 544.84659,276.92526 c 16.02735,-13.54645 29.98118,-31.39768 32.51139,-52.84016 1.4974,-10.24434 -1.94415,-22.06454 -11.64632,-27.13714 -22.66026,-14.31032 -51.37591,-9.68663 -75.27088,-1.12303 -29.57925,11.19994 -56.66841,28.48168 -80.87983,48.69857 -15.56749,13.48245 -30.31327,28.65126 -40.17476,46.87113 -10.56334,25.04076 5.93271,58.80187 33.76392,62.35287 20.97698,3.69753 41.31134,-5.15667 59.15595,-15.26739 23.68053,-12.07041 45.37059,-27.68722 65.11773,-45.42392 5.85816,-4.99109 11.66007,-10.04795 17.4228,-15.14884" /> + <path + d="m 614.57518,235.67736 c -39.71057,22.89155 -90.21146,57.33437 -110.62013,81.46882 -20.40867,24.13445 1.74636,32.16167 21.51346,33.96557 19.7671,1.8039 40.22731,-5.81242 63.90833,-18.83825 23.68102,-13.02583 54.88215,-39.05314 73.09016,-60.24005 18.20801,-21.18691 37.84165,-41.69088 43.78312,-57.46575 5.94147,-15.77487 6.81292,-20.38898 -2.3045,-26.03079" /> + <path + d="m 586.09449,351.56431 c 59.46452,-49.53559 110.50457,-110.94455 181.15663,-145.53797 21.55772,-14.53068 69.21456,-30.38164 74.52476,8.12435 2.15457,32.87115 -24.99905,56.36851 -41.3729,81.85669 -11.33394,14.91071 -34.85688,47.22341 -38.29416,51.12061 38.40925,-59.07476 94.0006,-105.23813 152.94098,-142.74425 21.53324,-14.17578 76.3474,-17.732 63.14039,22.44093 -6.44013,30.30909 -29.58192,52.20309 -51.31162,72.13403 -22.35245,20.18703 -11.93986,55.81184 21.21245,48.89199 28.04358,-1.72994 49.86098,-21.80465 73.62848,-34.51546 51.0484,-39.90132 89.3284,-97.12351 150.3008,-123.60116 30.7216,-12.02494 70.5868,-23.14241 98.2954,1.38478 22.9075,21.32517 9.8573,56.2678 -7.0675,76.93906 -18.1931,26.99594 -44.6741,46.96256 -74.8471,58.94304 -34.0583,20.33195 -79.3725,18.48794 -110.8061,-6.03301 -1.0838,-0.69666 -2.2533,-1.23366 -3.4004,-1.81269" /> + <path + d="m 1154.6667,159.26245 c -19.2615,48.72397 -39.2763,98.0821 -71.8076,139.71403 -28.4578,44.00142 -46.4445,94.33551 -79.7245,135.30113 -23.78316,32.93768 -48.80204,65.54924 -80.01549,91.86673 -19.47895,6.1922 -20.04137,-20.7923 -14.66105,-32.91267 12.83153,-36.25521 37.22095,-66.57625 54.90924,-100.3885 22.07476,-38.69659 54.0525,-69.56182 85.7438,-100.24739" /> + <path + d="m 1076.8889,323.15134 c 41.6055,37.74324 106.1656,40.87499 155.2476,16.47425 79.7307,-33.80513 147.627,-88.69981 215.7912,-140.91414 38.5692,-31.19936 75.8068,-66.58379 98.2999,-111.44269 5.2858,-16.340836 11.2358,-38.191353 -2.3042,-51.988823 -24.8579,-9.392422 -44.7459,19.21292 -62.7133,32.466761 -42.206,41.159272 -75.9104,90.348772 -105.7076,140.969902 -18.1332,33.9424 -36.3441,60.40033 -45.1009,105.84746 -4.8684,25.26636 17.3519,40.01019 38.489,36.24378 27.2382,-1.27906 52.1821,-14.06224 77.2642,-23.5167 20.2617,-9.77194 38.052,-23.75443 55.7341,-37.47313" /> + <path + d="m 1518.5556,231.48467 c 45.3671,35.48031 84.7434,37.23796 135.7328,25.17331 14.925,-4.86754 25.7515,-17.97315 30.1445,-32.73996 6.2047,-12.9545 -0.2899,-27.91555 -12.929,-33.8982 -19.5006,-12.36806 -44.6888,-10.55607 -64.9092,-0.92535 -33.4028,14.45512 -59.4353,40.93033 -84.563,66.45243 -13.9248,16.55902 -22.671,38.06079 -22.9956,59.74639 0.7543,12.73146 10.7427,22.65661 22.5942,25.94215 28.6381,11.95988 61.6748,18.05546 91.8527,8.36881 20.1474,-7.27059 39.868,-15.91987 58.8894,-25.73255 22.7573,-13.36221 41.3454,-32.41648 60.072,-50.72036" /> + <path + d="m 1789.3889,357.87356 c 28.6993,3.03127 63.3496,2.62457 82.7159,-22.45172 21.6447,-19.99229 43.1941,-48.10952 35.6329,-79.45221 -4.1368,-22.17497 -33.5802,-38.22486 -52.1616,-22.03003 -31.7488,22.47063 -31.372,70.25063 -12.8813,101.12391 23.6727,38.55114 74.0602,50.51158 116.164,41.35476 54.0203,-10.31179 104.4208,-53.87389 110.9357,-110.27278 0.7888,-32.83995 0.7757,-65.72269 4.9445,-98.36663 3.9298,-42.7466 9.9471,-89.084404 39.2166,-122.675001 17.104,-18.491716 51.2258,-26.923352 69.4545,-5.555968 14.6931,19.194858 3.0296,45.294939 -10.7419,61.486039 -16.3416,25.1385 -48.7197,29.13643 -75.3811,36.23743 -26.4792,5.77658 -58.6276,2.75073 -76.1034,-20.51069 -9.6512,-10.71312 -20.4603,-22.76827 -20.6837,-38.053992" /> + <path + d="m 2254.6667,137.04023 c -3.9166,0.81691 -6.5582,5.39919 -5.219,9.18763 1.2777,3.63173 4.3682,6.64885 8.1357,7.58096 2.108,0.12035 4.1149,-1.67454 4.2375,-3.78089 0.7885,-3.73488 0.2701,-7.91813 -2.1144,-10.99661 -1.2461,-1.37634 -3.1464,-2.37225 -5.0398,-1.99109" /> + <path + d="m 2228.9722,184.9569 c -29.9457,39.95653 -58.8628,80.7409 -86.118,122.56461 -5.4378,11.43956 -6.9218,27.24913 2.6137,36.94004 11.5344,8.50012 26.6093,3.95387 39.1614,0.62975 31.967,-8.46295 63.2786,-21.86206 88.264,-43.97857 15.2095,-12.48824 29.1196,-26.55137 41.4956,-41.85028" /> + <path + d="m 2496.7771,218.06883 c -3.3292,-19.2489 -24.4513,-30.55681 -42.8081,-28.63327 -39.0192,-1.47124 -74.6768,18.22839 -106.1126,39.27122 -27.4601,19.28281 -52.9628,45.12902 -62.0025,78.30592 -2.9108,24.00347 18.676,49.02117 43.417,46.57579 41.4005,-0.49369 78.4941,-23.55071 108.3115,-50.5202 26.5153,-22.3804 52.8121,-49.50782 59.1947,-84.99946" /> + <path + d="m 2524.8056,241.2069 c -48.3542,24.84206 -95.4271,57.89871 -121.9801,106.67545 -38.548,57.86235 -85.0736,109.83278 -130.7587,162.02483 -14.3416,13.79585 -31.8449,28.78802 -52.8206,28.38924 -22.4201,-5.81296 -20.145,-35.44311 -3.7589,-46.92382 18.3472,-20.65863 39.7594,-38.46949 61.3752,-55.55681 37.3089,-27.01631 74.2659,-55.34905 116.5753,-74.2272 45.8819,-21.27356 92.5741,-41.44894 133.4511,-71.77058" /> + <path + d="m 2571.3333,191.2069 c -11.728,39.55572 -31.7885,76.05727 -54.1537,110.51619 -11.367,17.27415 -23.5479,34.0347 -36.8185,49.90047" /> + <path + d="m 2481.0556,349.54023 c 39.4203,-54.93792 84.2673,-108.82208 144.2119,-142.13528 20.4555,-10.53089 43.6781,-13.18946 66.1233,-16.24748 15.4075,-2.13385 35.1588,8.90133 33.4692,26.24623 -2.9508,16.37726 -14.16,29.73489 -22.9962,43.3486 -13.3021,19.00229 -30.5436,36.66009 -36.611,59.58253 -4.3581,18.38699 14.1449,34.91044 31.9547,29.10574 16.8023,-4.83584 31.073,-15.60156 46.3125,-23.86514 23.356,-14.17855 45.5509,-30.69352 63.9244,-51.0352" /> + <path + d="m 2954.6667,291.2069 c 22.3825,-18.04222 43.6601,-40.96167 49.1836,-70.14766 1.6596,-16.7076 -14.1662,-31.70685 -30.5059,-31.31677 -38.4972,-6.9588 -76.9683,9.1156 -108.0208,30.84253 -27.6063,19.08076 -55.1974,41.48489 -69.2262,72.82154 -7.1903,18.94072 -2.0574,44.40278 17.325,53.92146 22.3966,12.66022 50.5757,7.55107 71.8376,-5.02642 25.3952,-13.7463 46.7256,-33.50699 69.4067,-51.09468" /> + <path + d="m 3041.4722,235.65134 c -34.9663,24.39777 -159.9904,94.46573 -91.1404,111.19476 49.5078,12.02932 120.689,-54.55505 178.2367,-101.75128 57.5477,-47.19623 164.1423,-136.34884 176.2724,-205.706174 12.1301,-69.357335 -35.8274,25.038279 -57.6617,62.402284 -21.8343,37.36401 -53.1817,114.5822 -93.4244,175.68115 -55.9422,84.93494 14.2268,97.70016 78.695,54.07139 43.4593,-29.41104 56.0463,-39.23804 79.8558,-64.64213" /> + <path + d="m 3128.9722,178.01245 c 51.3284,2.8018 102.806,4.27265 154.1667,1.38889" /> + <path + d="m 3368.5556,180.79023 c -26.0554,38.94967 -53.443,76.90675 -75.3826,118.80876 -8.2374,15.73252 -12.8945,40.55439 6.2938,47.64921 25.3545,6.43589 34.5616,-2.95338 49.294,-7.34667 48.0292,-19.31164 87.8533,-54.50776 122.854,-93.37814 15.42,-20.923 30.5124,-42.09003 46.9408,-62.26134" /> + <path + d="m 3520.6389,183.56801 c -31.3786,34.96488 -59.3857,72.92099 -84.9995,112.24284 -6.288,13.61689 -16.1644,29.28735 -9.8599,44.58789 7.0942,13.41865 25.2642,10.69636 37.6171,7.93765 31.8778,-9.70921 58.1068,-31.49347 83.8755,-51.77583 20.5616,-16.72078 38.3203,-36.60992 53.5974,-58.18739 14.5478,-17.73424 29.2159,-35.93086 39.6787,-56.3568 4.934,-18.95097 -28.1547,-19.20088 -17.7187,-0.12487 9.5405,17.82935 32.3392,18.53167 50.1021,19.48194 16.3122,-3.37266 23.3898,15.5346 12.9603,26.5127 -6.8718,8.02389 -13.7478,15.769 -19.1588,24.9829 -16.8576,24.69747 -36.847,48.68268 -44.6449,78.10881 -3.0723,19.33042 19.5849,33.62972 36.7818,27.38476 35.6706,-8.34057 67.9833,-26.49985 99.2689,-44.93349" /> + <path + d="m 3772.7222,231.48467 c 33.1817,22.33062 74.828,35.62344 114.8149,27.5851 15.6821,-3.20582 30.2525,-10.71137 43.1524,-19.942 17.3248,-14.2012 16.9209,-44.79145 -2.1675,-57.28603 -13.4925,-8.89012 -30.6833,-5.87659 -45.6599,-3.46265 -33.5763,7.00049 -61.9142,28.29846 -85.8989,51.8761 -25.4017,22.73023 -42.112,57.70516 -36.9155,92.17467 6.3894,18.1743 25.3211,28.36102 43.6991,29.78005 26.397,2.30348 52.8197,-3.96515 77.6246,-12.547 39.816,-14.49663 75.2011,-38.73414 108.0175,-65.12268" /> +</svg> diff --git a/svgbasic.go b/svgbasic.go new file mode 100644 index 0000000..4c58f95 --- /dev/null +++ b/svgbasic.go @@ -0,0 +1,210 @@ +/* + * 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 ( + "encoding/xml" + "fmt" + "io/ioutil" + "strconv" + "strings" +) + +var pathCmdSub *strings.Replacer + +func init() { + // Handle permitted constructions like "100L200,230" + pathCmdSub = strings.NewReplacer(",", " ", + "L", " L ", "l", " l ", + "C", " C ", "c", " c ", + "M", " M ", "m", " m ") +} + +// SvgBasicSegmentType describes a single curve or position segment +type SvgBasicSegmentType struct { + Cmd byte // See http://www.w3.org/TR/SVG/paths.html for path command structure + Arg [6]float64 +} + +func absolutizePath(segs []SvgBasicSegmentType) { + var x, y float64 + var segPtr *SvgBasicSegmentType + adjust := func(pos int, adjX, adjY float64) { + segPtr.Arg[pos] += adjX + segPtr.Arg[pos+1] += adjY + } + for j, seg := range segs { + segPtr = &segs[j] + if j == 0 && seg.Cmd == 'm' { + segPtr.Cmd = 'M' + } + switch segPtr.Cmd { + case 'M': + x = seg.Arg[0] + y = seg.Arg[1] + case 'm': + adjust(0, x, y) + segPtr.Cmd = 'M' + x = segPtr.Arg[0] + y = segPtr.Arg[1] + case 'L': + x = seg.Arg[0] + y = seg.Arg[1] + case 'l': + adjust(0, x, y) + segPtr.Cmd = 'L' + x = segPtr.Arg[0] + y = segPtr.Arg[1] + case 'C': + x = seg.Arg[4] + y = seg.Arg[5] + case 'c': + adjust(0, x, y) + adjust(2, x, y) + adjust(4, x, y) + segPtr.Cmd = 'C' + x = segPtr.Arg[4] + y = segPtr.Arg[5] + } + } +} + +func pathParse(pathStr string) (segs []SvgBasicSegmentType, err error) { + var seg SvgBasicSegmentType + var j, argJ, argCount, prevArgCount int + setup := func(n int) { + // It is not strictly necessary to clear arguments, but result may be clearer + // to caller + for j := 0; j < len(seg.Arg); j++ { + seg.Arg[j] = 0.0 + } + argJ = 0 + argCount = n + prevArgCount = n + } + var str string + var c byte + pathStr = pathCmdSub.Replace(pathStr) + strList := strings.Fields(pathStr) + count := len(strList) + for j = 0; j < count && err == nil; j++ { + str = strList[j] + if argCount == 0 { // Look for path command or argument continuation + c = str[0] + if c == '-' || (c >= '0' && c <= '9') { // More arguments + if j > 0 { + setup(prevArgCount) + // Repeat previous action + if seg.Cmd == 'M' { + seg.Cmd = 'L' + } else if seg.Cmd == 'm' { + seg.Cmd = 'l' + } + } else { + err = fmt.Errorf("expecting SVG path command at first position, got %s", str) + } + } + } + if err == nil { + if argCount == 0 { + seg.Cmd = str[0] + switch seg.Cmd { + case 'M', 'm': // Absolute/relative moveto: x, y + setup(2) + case 'C', 'c': // Absolute/relative Bézier curve: cx0, cy0, cx1, cy1, x1, y1 + setup(6) + case 'L', 'l': // Absolute/relative lineto: x, y + setup(2) + default: + err = fmt.Errorf("expecting SVG path command at position %d, got %s", j, str) + } + } else { + seg.Arg[argJ], err = strconv.ParseFloat(str, 64) + if err == nil { + argJ++ + argCount-- + if argCount == 0 { + segs = append(segs, seg) + } + } + } + } + } + if err == nil { + if argCount == 0 { + absolutizePath(segs) + } else { + err = fmt.Errorf("Expecting additional (%d) numeric arguments", argCount) + } + } + return +} + +// SvgBasicType aggregates the information needed to describe a multi-segment +// basic vector image +type SvgBasicType struct { + Wd, Ht float64 + Segments [][]SvgBasicSegmentType +} + +// SvgBasicParse parses a simple scalable vector graphics (SVG) buffer into a +// descriptor. Only a small subset of the SVG standard, in particular the path +// information generated by jSignature, is supported. The returned path data +// includes only the commands 'M' (absolute moveto: x, y), 'L' (absolute +// lineto: x, y), and 'C' (absolute cubic Bézier curve: cx0, cy0, cx1, cy1, +// x1,y1). +func SvgBasicParse(buf []byte) (sig SvgBasicType, err error) { + type pathType struct { + D string `xml:"d,attr"` + } + type srcType struct { + Wd float64 `xml:"width,attr"` + Ht float64 `xml:"height,attr"` + Paths []pathType `xml:"path"` + } + var src srcType + err = xml.Unmarshal(buf, &src) + if err == nil { + if src.Wd > 0 && src.Ht > 0 { + sig.Wd, sig.Ht = src.Wd, src.Ht + var segs []SvgBasicSegmentType + for _, path := range src.Paths { + if err == nil { + segs, err = pathParse(path.D) + if err == nil { + sig.Segments = append(sig.Segments, segs) + } + } + } + } else { + err = fmt.Errorf("Unacceptable values for basic SVG extent: %.2f x %.2f", + sig.Wd, sig.Ht) + } + } + return +} + +// SvgBasicParse parses a simple scalable vector graphics (SVG) file into a +// basic descriptor. See SvgBasicParse for additional comments. +func SvgBasicFileParse(svgFileStr string) (sig SvgBasicType, err error) { + var buf []byte + buf, err = ioutil.ReadFile(svgFileStr) + if err == nil { + sig, err = SvgBasicParse(buf) + } + return +} |