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