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

     1  /*
     2   * Copyright (c) 2013 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  	"bufio"
    21  	"bytes"
    22  	"compress/zlib"
    23  	"fmt"
    24  	"io"
    25  	"math"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  )
    30  
    31  func round(f float64) int {
    32  	if f < 0 {
    33  		return -int(math.Floor(-f + 0.5))
    34  	}
    35  	return int(math.Floor(f + 0.5))
    36  }
    37  
    38  func sprintf(fmtStr string, args ...interface{}) string {
    39  	return fmt.Sprintf(fmtStr, args...)
    40  }
    41  
    42  // fileExist returns true if the specified normal file exists
    43  func fileExist(filename string) (ok bool) {
    44  	info, err := os.Stat(filename)
    45  	if err == nil {
    46  		if ^os.ModePerm&info.Mode() == 0 {
    47  			ok = true
    48  		}
    49  	}
    50  	return ok
    51  }
    52  
    53  // fileSize returns the size of the specified file; ok will be false
    54  // if the file does not exist or is not an ordinary file
    55  func fileSize(filename string) (size int64, ok bool) {
    56  	info, err := os.Stat(filename)
    57  	ok = err == nil
    58  	if ok {
    59  		size = info.Size()
    60  	}
    61  	return
    62  }
    63  
    64  // bufferFromReader returns a new buffer populated with the contents of the specified Reader
    65  func bufferFromReader(r io.Reader) (b *bytes.Buffer, err error) {
    66  	b = new(bytes.Buffer)
    67  	_, err = b.ReadFrom(r)
    68  	return
    69  }
    70  
    71  // slicesEqual returns true if the two specified float slices are equal
    72  func slicesEqual(a, b []float64) bool {
    73  	if len(a) != len(b) {
    74  		return false
    75  	}
    76  	for i := range a {
    77  		if a[i] != b[i] {
    78  			return false
    79  		}
    80  	}
    81  	return true
    82  }
    83  
    84  // sliceCompress returns a zlib-compressed copy of the specified byte array
    85  func sliceCompress(data []byte) []byte {
    86  	var buf bytes.Buffer
    87  	cmp, _ := zlib.NewWriterLevel(&buf, zlib.BestSpeed)
    88  	cmp.Write(data)
    89  	cmp.Close()
    90  	return buf.Bytes()
    91  }
    92  
    93  // sliceUncompress returns an uncompressed copy of the specified zlib-compressed byte array
    94  func sliceUncompress(data []byte) (outData []byte, err error) {
    95  	inBuf := bytes.NewReader(data)
    96  	r, err := zlib.NewReader(inBuf)
    97  	defer r.Close()
    98  	if err == nil {
    99  		var outBuf bytes.Buffer
   100  		_, err = outBuf.ReadFrom(r)
   101  		if err == nil {
   102  			outData = outBuf.Bytes()
   103  		}
   104  	}
   105  	return
   106  }
   107  
   108  // utf8toutf16 converts UTF-8 to UTF-16BE; from http://www.fpdf.org/
   109  func utf8toutf16(s string, withBOM ...bool) string {
   110  	bom := true
   111  	if len(withBOM) > 0 {
   112  		bom = withBOM[0]
   113  	}
   114  	res := make([]byte, 0, 8)
   115  	if bom {
   116  		res = append(res, 0xFE, 0xFF)
   117  	}
   118  	nb := len(s)
   119  	i := 0
   120  	for i < nb {
   121  		c1 := byte(s[i])
   122  		i++
   123  		switch {
   124  		case c1 >= 224:
   125  			// 3-byte character
   126  			c2 := byte(s[i])
   127  			i++
   128  			c3 := byte(s[i])
   129  			i++
   130  			res = append(res, ((c1&0x0F)<<4)+((c2&0x3C)>>2),
   131  				((c2&0x03)<<6)+(c3&0x3F))
   132  		case c1 >= 192:
   133  			// 2-byte character
   134  			c2 := byte(s[i])
   135  			i++
   136  			res = append(res, ((c1 & 0x1C) >> 2),
   137  				((c1&0x03)<<6)+(c2&0x3F))
   138  		default:
   139  			// Single-byte character
   140  			res = append(res, 0, c1)
   141  		}
   142  	}
   143  	return string(res)
   144  }
   145  
   146  // intIf returns a if cnd is true, otherwise b
   147  func intIf(cnd bool, a, b int) int {
   148  	if cnd {
   149  		return a
   150  	}
   151  	return b
   152  }
   153  
   154  // strIf returns aStr if cnd is true, otherwise bStr
   155  func strIf(cnd bool, aStr, bStr string) string {
   156  	if cnd {
   157  		return aStr
   158  	}
   159  	return bStr
   160  }
   161  
   162  // doNothing returns the passed string with no translation.
   163  func doNothing(s string) string {
   164  	return s
   165  }
   166  
   167  // Dump the internals of the specified values
   168  // func dump(fileStr string, a ...interface{}) {
   169  // 	fl, err := os.OpenFile(fileStr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
   170  // 	if err == nil {
   171  // 		fmt.Fprintf(fl, "----------------\n")
   172  // 		spew.Fdump(fl, a...)
   173  // 		fl.Close()
   174  // 	}
   175  // }
   176  
   177  func repClosure(m map[rune]byte) func(string) string {
   178  	var buf bytes.Buffer
   179  	return func(str string) string {
   180  		var ch byte
   181  		var ok bool
   182  		buf.Truncate(0)
   183  		for _, r := range str {
   184  			if r < 0x80 {
   185  				ch = byte(r)
   186  			} else {
   187  				ch, ok = m[r]
   188  				if !ok {
   189  					ch = byte('.')
   190  				}
   191  			}
   192  			buf.WriteByte(ch)
   193  		}
   194  		return buf.String()
   195  	}
   196  }
   197  
   198  // UnicodeTranslator returns a function that can be used to translate, where
   199  // possible, utf-8 strings to a form that is compatible with the specified code
   200  // page. The returned function accepts a string and returns a string.
   201  //
   202  // r is a reader that should read a buffer made up of content lines that
   203  // pertain to the code page of interest. Each line is made up of three
   204  // whitespace separated fields. The first begins with "!" and is followed by
   205  // two hexadecimal digits that identify the glyph position in the code page of
   206  // interest. The second field begins with "U+" and is followed by the unicode
   207  // code point value. The third is the glyph name. A number of these code page
   208  // map files are packaged with the gfpdf library in the font directory.
   209  //
   210  // An error occurs only if a line is read that does not conform to the expected
   211  // format. In this case, the returned function is valid but does not perform
   212  // any rune translation.
   213  func UnicodeTranslator(r io.Reader) (f func(string) string, err error) {
   214  	m := make(map[rune]byte)
   215  	var uPos, cPos uint32
   216  	var lineStr, nameStr string
   217  	sc := bufio.NewScanner(r)
   218  	for sc.Scan() {
   219  		lineStr = sc.Text()
   220  		lineStr = strings.TrimSpace(lineStr)
   221  		if len(lineStr) > 0 {
   222  			_, err = fmt.Sscanf(lineStr, "!%2X U+%4X %s", &cPos, &uPos, &nameStr)
   223  			if err == nil {
   224  				if cPos >= 0x80 {
   225  					m[rune(uPos)] = byte(cPos)
   226  				}
   227  			}
   228  		}
   229  	}
   230  	if err == nil {
   231  		f = repClosure(m)
   232  	} else {
   233  		f = doNothing
   234  	}
   235  	return
   236  }
   237  
   238  // UnicodeTranslatorFromFile returns a function that can be used to translate,
   239  // where possible, utf-8 strings to a form that is compatible with the
   240  // specified code page. See UnicodeTranslator for more details.
   241  //
   242  // fileStr identifies a font descriptor file that maps glyph positions to names.
   243  //
   244  // If an error occurs reading the file, the returned function is valid but does
   245  // not perform any rune translation.
   246  func UnicodeTranslatorFromFile(fileStr string) (f func(string) string, err error) {
   247  	var fl *os.File
   248  	fl, err = os.Open(fileStr)
   249  	if err == nil {
   250  		f, err = UnicodeTranslator(fl)
   251  		fl.Close()
   252  	} else {
   253  		f = doNothing
   254  	}
   255  	return
   256  }
   257  
   258  // UnicodeTranslatorFromDescriptor returns a function that can be used to
   259  // translate, where possible, utf-8 strings to a form that is compatible with
   260  // the specified code page. See UnicodeTranslator for more details.
   261  //
   262  // cpStr identifies a code page. A descriptor file in the font directory, set
   263  // with the fontDirStr argument in the call to New(), should have this name
   264  // plus the extension ".map". If cpStr is empty, it will be replaced with
   265  // "cp1252", the gofpdf code page default.
   266  //
   267  // If an error occurs reading the descriptor, the returned function is valid
   268  // but does not perform any rune translation.
   269  //
   270  // The CellFormat_codepage example demonstrates this method.
   271  func (f *Fpdf) UnicodeTranslatorFromDescriptor(cpStr string) (rep func(string) string) {
   272  	var str string
   273  	var ok bool
   274  	if f.err == nil {
   275  		if len(cpStr) == 0 {
   276  			cpStr = "cp1252"
   277  		}
   278  		str, ok = embeddedMapList[cpStr]
   279  		if ok {
   280  			rep, f.err = UnicodeTranslator(strings.NewReader(str))
   281  		} else {
   282  			rep, f.err = UnicodeTranslatorFromFile(filepath.Join(f.fontpath, cpStr) + ".map")
   283  		}
   284  	} else {
   285  		rep = doNothing
   286  	}
   287  	return
   288  }
   289  
   290  // Transform moves a point by given X, Y offset
   291  func (p *PointType) Transform(x, y float64) PointType {
   292  	return PointType{p.X + x, p.Y + y}
   293  }
   294  
   295  // Orientation returns the orientation of a given size:
   296  // "P" for portrait, "L" for landscape
   297  func (s *SizeType) Orientation() string {
   298  	if s == nil || s.Ht == s.Wd {
   299  		return ""
   300  	}
   301  	if s.Wd > s.Ht {
   302  		return "L"
   303  	}
   304  	return "P"
   305  }
   306  
   307  // ScaleBy expands a size by a certain factor
   308  func (s *SizeType) ScaleBy(factor float64) SizeType {
   309  	return SizeType{s.Wd * factor, s.Ht * factor}
   310  }
   311  
   312  // ScaleToWidth adjusts the height of a size to match the given width
   313  func (s *SizeType) ScaleToWidth(width float64) SizeType {
   314  	height := s.Ht * width / s.Wd
   315  	return SizeType{width, height}
   316  }
   317  
   318  // ScaleToHeight adjusts the width of a size to match the given height
   319  func (s *SizeType) ScaleToHeight(height float64) SizeType {
   320  	width := s.Wd * height / s.Ht
   321  	return SizeType{width, height}
   322  }
   323  
   324  //The untypedKeyMap structure and its methods are copyrighted 2019 by Arteom Korotkiy (Gmail: arteomkorotkiy).
   325  //Imitation of untyped Map Array
   326  type untypedKeyMap struct {
   327  	keySet   []interface{}
   328  	valueSet []int
   329  }
   330  
   331  //Get position of key=>value in PHP Array
   332  func (pa *untypedKeyMap) getIndex(key interface{}) int {
   333  	if key != nil {
   334  		for i, mKey := range pa.keySet {
   335  			if mKey == key {
   336  				return i
   337  			}
   338  		}
   339  		return -1
   340  	}
   341  	return -1
   342  }
   343  
   344  //Put key=>value in PHP Array
   345  func (pa *untypedKeyMap) put(key interface{}, value int) {
   346  	if key == nil {
   347  		var i int
   348  		for n := 0; ; n++ {
   349  			i = pa.getIndex(n)
   350  			if i < 0 {
   351  				key = n
   352  				break
   353  			}
   354  		}
   355  		pa.keySet = append(pa.keySet, key)
   356  		pa.valueSet = append(pa.valueSet, value)
   357  	} else {
   358  		i := pa.getIndex(key)
   359  		if i < 0 {
   360  			pa.keySet = append(pa.keySet, key)
   361  			pa.valueSet = append(pa.valueSet, value)
   362  		} else {
   363  			pa.valueSet[i] = value
   364  		}
   365  	}
   366  }
   367  
   368  //Delete value in PHP Array
   369  func (pa *untypedKeyMap) delete(key interface{}) {
   370  	if pa == nil || pa.keySet == nil || pa.valueSet == nil {
   371  		return
   372  	}
   373  	i := pa.getIndex(key)
   374  	if i >= 0 {
   375  		if i == 0 {
   376  			pa.keySet = pa.keySet[1:]
   377  			pa.valueSet = pa.valueSet[1:]
   378  		} else if i == len(pa.keySet)-1 {
   379  			pa.keySet = pa.keySet[:len(pa.keySet)-1]
   380  			pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
   381  		} else {
   382  			pa.keySet = append(pa.keySet[:i], pa.keySet[i+1:]...)
   383  			pa.valueSet = append(pa.valueSet[:i], pa.valueSet[i+1:]...)
   384  		}
   385  	}
   386  }
   387  
   388  //Get value from PHP Array
   389  func (pa *untypedKeyMap) get(key interface{}) int {
   390  	i := pa.getIndex(key)
   391  	if i >= 0 {
   392  		return pa.valueSet[i]
   393  	}
   394  	return 0
   395  }
   396  
   397  //Imitation of PHP function pop()
   398  func (pa *untypedKeyMap) pop() {
   399  	pa.keySet = pa.keySet[:len(pa.keySet)-1]
   400  	pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
   401  }
   402  
   403  //Imitation of PHP function array_merge()
   404  func arrayMerge(arr1, arr2 *untypedKeyMap) *untypedKeyMap {
   405  	answer := untypedKeyMap{}
   406  	if arr1 == nil && arr2 == nil {
   407  		answer = untypedKeyMap{
   408  			make([]interface{}, 0),
   409  			make([]int, 0),
   410  		}
   411  	} else if arr2 == nil {
   412  		answer.keySet = arr1.keySet[:]
   413  		answer.valueSet = arr1.valueSet[:]
   414  	} else if arr1 == nil {
   415  		answer.keySet = arr2.keySet[:]
   416  		answer.valueSet = arr2.valueSet[:]
   417  	} else {
   418  		answer.keySet = arr1.keySet[:]
   419  		answer.valueSet = arr1.valueSet[:]
   420  		for i := 0; i < len(arr2.keySet); i++ {
   421  			if arr2.keySet[i] == "interval" {
   422  				if arr1.getIndex("interval") < 0 {
   423  					answer.put("interval", arr2.valueSet[i])
   424  				}
   425  			} else {
   426  				answer.put(nil, arr2.valueSet[i])
   427  			}
   428  		}
   429  	}
   430  	return &answer
   431  }
   432  
   433  func remove(arr []int, key int) []int {
   434  	n := 0
   435  	for i, mKey := range arr {
   436  		if mKey == key {
   437  			n = i
   438  		}
   439  	}
   440  	if n == 0 {
   441  		return arr[1:]
   442  	} else if n == len(arr)-1 {
   443  		return arr[:len(arr)-1]
   444  	}
   445  	return append(arr[:n], arr[n+1:]...)
   446  }
   447  
   448  func isChinese(rune2 rune) bool {
   449  	// chinese unicode: 4e00-9fa5
   450  	if rune2 >= rune(0x4e00) && rune2 <= rune(0x9fa5) {
   451  		return true
   452  	}
   453  	return false
   454  }
   455  
   456  // Condition font family string to PDF name compliance. See section 5.3 (Names)
   457  // in https://resources.infosecinstitute.com/pdf-file-format-basic-structure/
   458  func fontFamilyEscape(familyStr string) (escStr string) {
   459  	escStr = strings.Replace(familyStr, " ", "#20", -1)
   460  	// Additional replacements can take place here
   461  	return
   462  }