codeberg.org/go-pdf/fpdf@v0.11.1/util.go (about)

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