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 17package gofpdf 18 19import ( 20 "bufio" 21 "bytes" 22 "compress/zlib" 23 "fmt" 24 "io" 25 "math" 26 "os" 27 "path/filepath" 28 "strings" 29) 30 31func 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 38func sprintf(fmtStr string, args ...interface{}) string { 39 return fmt.Sprintf(fmtStr, args...) 40} 41 42// fileExist returns true if the specified normal file exists 43func 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 55func 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 65func 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 72func 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 85func 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 94func 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/ 109func 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 147func 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 155func 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. 163func 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 177func 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. 213func 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. 246func 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. 271func (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 291func (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 297func (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 308func (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 313func (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 319func (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 326type untypedKeyMap struct { 327 keySet []interface{} 328 valueSet []int 329} 330 331//Get position of key=>value in PHP Array 332func (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 345func (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 369func (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 389func (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() 398func (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() 404func 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 433func 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 448func 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/ 458func fontFamilyEscape(familyStr string) (escStr string) { 459 escStr = strings.Replace(familyStr, " ", "#20", -1) 460 // Additional replacements can take place here 461 return 462} 463