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 }