github.com/phpdave11/gofpdf@v1.4.2/svgbasic.go (about)

     1  /*
     2   * Copyright (c) 2014 Kurt Jung (Gmail: kurt.w.jung)
     3   *
     4   * Permission to use, copy, modify, and distribute this software for any
     5   * purpose with or without fee is hereby granted, provided that the above
     6   * copyright notice and this permission notice appear in all copies.
     7   *
     8   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     9   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    10   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    11   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    12   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    13   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    14   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    15   */
    16  
    17  package gofpdf
    18  
    19  import (
    20  	"encoding/xml"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  var pathCmdSub *strings.Replacer
    28  
    29  func init() {
    30  	// Handle permitted constructions like "100L200,230"
    31  	pathCmdSub = strings.NewReplacer(",", " ",
    32  		"L", " L ", "l", " l ",
    33  		"C", " C ", "c", " c ",
    34  		"M", " M ", "m", " m ",
    35  		"H", " H ", "h", " h ",
    36  		"V", " V ", "v", " v ",
    37  		"Q", " Q ", "q", " q ",
    38  		"Z", " Z ", "z", " z ")
    39  }
    40  
    41  // SVGBasicSegmentType describes a single curve or position segment
    42  type SVGBasicSegmentType struct {
    43  	Cmd byte // See http://www.w3.org/TR/SVG/paths.html for path command structure
    44  	Arg [6]float64
    45  }
    46  
    47  func absolutizePath(segs []SVGBasicSegmentType) {
    48  	var x, y float64
    49  	var segPtr *SVGBasicSegmentType
    50  	adjust := func(pos int, adjX, adjY float64) {
    51  		segPtr.Arg[pos] += adjX
    52  		segPtr.Arg[pos+1] += adjY
    53  	}
    54  	for j, seg := range segs {
    55  		segPtr = &segs[j]
    56  		if j == 0 && seg.Cmd == 'm' {
    57  			segPtr.Cmd = 'M'
    58  		}
    59  		switch segPtr.Cmd {
    60  		case 'M':
    61  			x = seg.Arg[0]
    62  			y = seg.Arg[1]
    63  		case 'm':
    64  			adjust(0, x, y)
    65  			segPtr.Cmd = 'M'
    66  			x = segPtr.Arg[0]
    67  			y = segPtr.Arg[1]
    68  		case 'L':
    69  			x = seg.Arg[0]
    70  			y = seg.Arg[1]
    71  		case 'l':
    72  			adjust(0, x, y)
    73  			segPtr.Cmd = 'L'
    74  			x = segPtr.Arg[0]
    75  			y = segPtr.Arg[1]
    76  		case 'C':
    77  			x = seg.Arg[4]
    78  			y = seg.Arg[5]
    79  		case 'c':
    80  			adjust(0, x, y)
    81  			adjust(2, x, y)
    82  			adjust(4, x, y)
    83  			segPtr.Cmd = 'C'
    84  			x = segPtr.Arg[4]
    85  			y = segPtr.Arg[5]
    86  		case 'Q':
    87  			x = seg.Arg[2]
    88  			y = seg.Arg[3]
    89  		case 'q':
    90  			adjust(0, x, y)
    91  			adjust(2, x, y)
    92  			segPtr.Cmd = 'Q'
    93  			x = segPtr.Arg[2]
    94  			y = segPtr.Arg[3]
    95  		case 'H':
    96  			x = seg.Arg[0]
    97  		case 'h':
    98  			segPtr.Arg[0] += x
    99  			segPtr.Cmd = 'H'
   100  			x += seg.Arg[0]
   101  		case 'V':
   102  			y = seg.Arg[0]
   103  		case 'v':
   104  			segPtr.Arg[0] += y
   105  			segPtr.Cmd = 'V'
   106  			y += seg.Arg[0]
   107  		case 'z':
   108  			segPtr.Cmd = 'Z'
   109  		}
   110  	}
   111  }
   112  
   113  func pathParse(pathStr string) (segs []SVGBasicSegmentType, err error) {
   114  	var seg SVGBasicSegmentType
   115  	var j, argJ, argCount, prevArgCount int
   116  	setup := func(n int) {
   117  		// It is not strictly necessary to clear arguments, but result may be clearer
   118  		// to caller
   119  		for j := 0; j < len(seg.Arg); j++ {
   120  			seg.Arg[j] = 0.0
   121  		}
   122  		argJ = 0
   123  		argCount = n
   124  		prevArgCount = n
   125  	}
   126  	var str string
   127  	var c byte
   128  	pathStr = pathCmdSub.Replace(pathStr)
   129  	strList := strings.Fields(pathStr)
   130  	count := len(strList)
   131  	for j = 0; j < count && err == nil; j++ {
   132  		str = strList[j]
   133  		if argCount == 0 { // Look for path command or argument continuation
   134  			c = str[0]
   135  			if c == '-' || (c >= '0' && c <= '9') { // More arguments
   136  				if j > 0 {
   137  					setup(prevArgCount)
   138  					// Repeat previous action
   139  					if seg.Cmd == 'M' {
   140  						seg.Cmd = 'L'
   141  					} else if seg.Cmd == 'm' {
   142  						seg.Cmd = 'l'
   143  					}
   144  				} else {
   145  					err = fmt.Errorf("expecting SVG path command at first position, got %s", str)
   146  				}
   147  			}
   148  		}
   149  		if err == nil {
   150  			if argCount == 0 {
   151  				seg.Cmd = str[0]
   152  				switch seg.Cmd {
   153  				case 'M', 'm': // Absolute/relative moveto: x, y
   154  					setup(2)
   155  				case 'C', 'c': // Absolute/relative Bézier curve: cx0, cy0, cx1, cy1, x1, y1
   156  					setup(6)
   157  				case 'H', 'h': // Absolute/relative horizontal line to: x
   158  					setup(1)
   159  				case 'L', 'l': // Absolute/relative lineto: x, y
   160  					setup(2)
   161  				case 'Q', 'q': // Absolute/relative quadratic curve: x0, y0, x1, y1
   162  					setup(4)
   163  				case 'V', 'v': // Absolute/relative vertical line to: y
   164  					setup(1)
   165  				case 'Z', 'z': // closepath instruction (takes no arguments)
   166  					segs = append(segs, seg)
   167  				default:
   168  					err = fmt.Errorf("expecting SVG path command at position %d, got %s", j, str)
   169  				}
   170  			} else {
   171  				seg.Arg[argJ], err = strconv.ParseFloat(str, 64)
   172  				if err == nil {
   173  					argJ++
   174  					argCount--
   175  					if argCount == 0 {
   176  						segs = append(segs, seg)
   177  					}
   178  				}
   179  			}
   180  		}
   181  	}
   182  	if err == nil {
   183  		if argCount == 0 {
   184  			absolutizePath(segs)
   185  		} else {
   186  			err = fmt.Errorf("expecting additional (%d) numeric arguments", argCount)
   187  		}
   188  	}
   189  	return
   190  }
   191  
   192  // SVGBasicType aggregates the information needed to describe a multi-segment
   193  // basic vector image
   194  type SVGBasicType struct {
   195  	Wd, Ht   float64
   196  	Segments [][]SVGBasicSegmentType
   197  }
   198  
   199  // SVGBasicParse parses a simple scalable vector graphics (SVG) buffer into a
   200  // descriptor. Only a small subset of the SVG standard, in particular the path
   201  // information generated by jSignature, is supported. The returned path data
   202  // includes only the commands 'M' (absolute moveto: x, y), 'L' (absolute
   203  // lineto: x, y), 'C' (absolute cubic Bézier curve: cx0, cy0, cx1, cy1,
   204  // x1,y1), 'Q' (absolute quadratic Bézier curve: x0, y0, x1, y1) and 'Z'
   205  // (closepath).
   206  func SVGBasicParse(buf []byte) (sig SVGBasicType, err error) {
   207  	type pathType struct {
   208  		D string `xml:"d,attr"`
   209  	}
   210  	type srcType struct {
   211  		Wd    float64    `xml:"width,attr"`
   212  		Ht    float64    `xml:"height,attr"`
   213  		Paths []pathType `xml:"path"`
   214  	}
   215  	var src srcType
   216  	err = xml.Unmarshal(buf, &src)
   217  	if err == nil {
   218  		if src.Wd > 0 && src.Ht > 0 {
   219  			sig.Wd, sig.Ht = src.Wd, src.Ht
   220  			var segs []SVGBasicSegmentType
   221  			for _, path := range src.Paths {
   222  				if err == nil {
   223  					segs, err = pathParse(path.D)
   224  					if err == nil {
   225  						sig.Segments = append(sig.Segments, segs)
   226  					}
   227  				}
   228  			}
   229  		} else {
   230  			err = fmt.Errorf("unacceptable values for basic SVG extent: %.2f x %.2f",
   231  				sig.Wd, sig.Ht)
   232  		}
   233  	}
   234  	return
   235  }
   236  
   237  // SVGBasicFileParse parses a simple scalable vector graphics (SVG) file into a
   238  // basic descriptor. The SVGBasicWrite() example demonstrates this method.
   239  func SVGBasicFileParse(svgFileStr string) (sig SVGBasicType, err error) {
   240  	var buf []byte
   241  	buf, err = ioutil.ReadFile(svgFileStr)
   242  	if err == nil {
   243  		sig, err = SVGBasicParse(buf)
   244  	}
   245  	return
   246  }