123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /*
- * Copyright (c) 2013 Kurt Jung (Gmail: kurt.w.jung)
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- package gofpdf
- import (
- "bufio"
- "bytes"
- "compress/zlib"
- "fmt"
- "io"
- "math"
- "os"
- "path/filepath"
- "strings"
- )
- func round(f float64) int {
- if f < 0 {
- return -int(math.Floor(-f + 0.5))
- }
- return int(math.Floor(f + 0.5))
- }
- func sprintf(fmtStr string, args ...interface{}) string {
- return fmt.Sprintf(fmtStr, args...)
- }
- // fileExist returns true if the specified normal file exists
- func fileExist(filename string) (ok bool) {
- info, err := os.Stat(filename)
- if err == nil {
- if ^os.ModePerm&info.Mode() == 0 {
- ok = true
- }
- }
- return ok
- }
- // fileSize returns the size of the specified file; ok will be false
- // if the file does not exist or is not an ordinary file
- func fileSize(filename string) (size int64, ok bool) {
- info, err := os.Stat(filename)
- ok = err == nil
- if ok {
- size = info.Size()
- }
- return
- }
- // bufferFromReader returns a new buffer populated with the contents of the specified Reader
- func bufferFromReader(r io.Reader) (b *bytes.Buffer, err error) {
- b = new(bytes.Buffer)
- _, err = b.ReadFrom(r)
- return
- }
- // slicesEqual returns true if the two specified float slices are equal
- func slicesEqual(a, b []float64) bool {
- if len(a) != len(b) {
- return false
- }
- for i := range a {
- if a[i] != b[i] {
- return false
- }
- }
- return true
- }
- // sliceCompress returns a zlib-compressed copy of the specified byte array
- func sliceCompress(data []byte) []byte {
- var buf bytes.Buffer
- cmp, _ := zlib.NewWriterLevel(&buf, zlib.BestSpeed)
- cmp.Write(data)
- cmp.Close()
- return buf.Bytes()
- }
- // sliceUncompress returns an uncompressed copy of the specified zlib-compressed byte array
- func sliceUncompress(data []byte) (outData []byte, err error) {
- inBuf := bytes.NewReader(data)
- r, err := zlib.NewReader(inBuf)
- defer r.Close()
- if err == nil {
- var outBuf bytes.Buffer
- _, err = outBuf.ReadFrom(r)
- if err == nil {
- outData = outBuf.Bytes()
- }
- }
- return
- }
- // utf8toutf16 converts UTF-8 to UTF-16BE; from http://www.fpdf.org/
- func utf8toutf16(s string, withBOM ...bool) string {
- bom := true
- if len(withBOM) > 0 {
- bom = withBOM[0]
- }
- res := make([]byte, 0, 8)
- if bom {
- res = append(res, 0xFE, 0xFF)
- }
- nb := len(s)
- i := 0
- for i < nb {
- c1 := byte(s[i])
- i++
- switch {
- case c1 >= 224:
- // 3-byte character
- c2 := byte(s[i])
- i++
- c3 := byte(s[i])
- i++
- res = append(res, ((c1&0x0F)<<4)+((c2&0x3C)>>2),
- ((c2&0x03)<<6)+(c3&0x3F))
- case c1 >= 192:
- // 2-byte character
- c2 := byte(s[i])
- i++
- res = append(res, ((c1 & 0x1C) >> 2),
- ((c1&0x03)<<6)+(c2&0x3F))
- default:
- // Single-byte character
- res = append(res, 0, c1)
- }
- }
- return string(res)
- }
- // intIf returns a if cnd is true, otherwise b
- func intIf(cnd bool, a, b int) int {
- if cnd {
- return a
- }
- return b
- }
- // strIf returns aStr if cnd is true, otherwise bStr
- func strIf(cnd bool, aStr, bStr string) string {
- if cnd {
- return aStr
- }
- return bStr
- }
- // doNothing returns the passed string with no translation.
- func doNothing(s string) string {
- return s
- }
- // Dump the internals of the specified values
- // func dump(fileStr string, a ...interface{}) {
- // fl, err := os.OpenFile(fileStr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
- // if err == nil {
- // fmt.Fprintf(fl, "----------------\n")
- // spew.Fdump(fl, a...)
- // fl.Close()
- // }
- // }
- func repClosure(m map[rune]byte) func(string) string {
- var buf bytes.Buffer
- return func(str string) string {
- var ch byte
- var ok bool
- buf.Truncate(0)
- for _, r := range str {
- if r < 0x80 {
- ch = byte(r)
- } else {
- ch, ok = m[r]
- if !ok {
- ch = byte('.')
- }
- }
- buf.WriteByte(ch)
- }
- return buf.String()
- }
- }
- // UnicodeTranslator returns a function that can be used to translate, where
- // possible, utf-8 strings to a form that is compatible with the specified code
- // page. The returned function accepts a string and returns a string.
- //
- // r is a reader that should read a buffer made up of content lines that
- // pertain to the code page of interest. Each line is made up of three
- // whitespace separated fields. The first begins with "!" and is followed by
- // two hexadecimal digits that identify the glyph position in the code page of
- // interest. The second field begins with "U+" and is followed by the unicode
- // code point value. The third is the glyph name. A number of these code page
- // map files are packaged with the gfpdf library in the font directory.
- //
- // An error occurs only if a line is read that does not conform to the expected
- // format. In this case, the returned function is valid but does not perform
- // any rune translation.
- func UnicodeTranslator(r io.Reader) (f func(string) string, err error) {
- m := make(map[rune]byte)
- var uPos, cPos uint32
- var lineStr, nameStr string
- sc := bufio.NewScanner(r)
- for sc.Scan() {
- lineStr = sc.Text()
- lineStr = strings.TrimSpace(lineStr)
- if len(lineStr) > 0 {
- _, err = fmt.Sscanf(lineStr, "!%2X U+%4X %s", &cPos, &uPos, &nameStr)
- if err == nil {
- if cPos >= 0x80 {
- m[rune(uPos)] = byte(cPos)
- }
- }
- }
- }
- if err == nil {
- f = repClosure(m)
- } else {
- f = doNothing
- }
- return
- }
- // UnicodeTranslatorFromFile returns a function that can be used to translate,
- // where possible, utf-8 strings to a form that is compatible with the
- // specified code page. See UnicodeTranslator for more details.
- //
- // fileStr identifies a font descriptor file that maps glyph positions to names.
- //
- // If an error occurs reading the file, the returned function is valid but does
- // not perform any rune translation.
- func UnicodeTranslatorFromFile(fileStr string) (f func(string) string, err error) {
- var fl *os.File
- fl, err = os.Open(fileStr)
- if err == nil {
- f, err = UnicodeTranslator(fl)
- fl.Close()
- } else {
- f = doNothing
- }
- return
- }
- // UnicodeTranslatorFromDescriptor returns a function that can be used to
- // translate, where possible, utf-8 strings to a form that is compatible with
- // the specified code page. See UnicodeTranslator for more details.
- //
- // cpStr identifies a code page. A descriptor file in the font directory, set
- // with the fontDirStr argument in the call to New(), should have this name
- // plus the extension ".map". If cpStr is empty, it will be replaced with
- // "cp1252", the gofpdf code page default.
- //
- // If an error occurs reading the descriptor, the returned function is valid
- // but does not perform any rune translation.
- //
- // The CellFormat_codepage example demonstrates this method.
- func (f *Fpdf) UnicodeTranslatorFromDescriptor(cpStr string) (rep func(string) string) {
- var str string
- var ok bool
- if f.err == nil {
- if len(cpStr) == 0 {
- cpStr = "cp1252"
- }
- str, ok = embeddedMapList[cpStr]
- if ok {
- rep, f.err = UnicodeTranslator(strings.NewReader(str))
- } else {
- rep, f.err = UnicodeTranslatorFromFile(filepath.Join(f.fontpath, cpStr) + ".map")
- }
- } else {
- rep = doNothing
- }
- return
- }
- // Transform moves a point by given X, Y offset
- func (p *PointType) Transform(x, y float64) PointType {
- return PointType{p.X + x, p.Y + y}
- }
- // Orientation returns the orientation of a given size:
- // "P" for portrait, "L" for landscape
- func (s *SizeType) Orientation() string {
- if s == nil || s.Ht == s.Wd {
- return ""
- }
- if s.Wd > s.Ht {
- return "L"
- }
- return "P"
- }
- // ScaleBy expands a size by a certain factor
- func (s *SizeType) ScaleBy(factor float64) SizeType {
- return SizeType{s.Wd * factor, s.Ht * factor}
- }
- // ScaleToWidth adjusts the height of a size to match the given width
- func (s *SizeType) ScaleToWidth(width float64) SizeType {
- height := s.Ht * width / s.Wd
- return SizeType{width, height}
- }
- // ScaleToHeight adjusts the width of a size to match the given height
- func (s *SizeType) ScaleToHeight(height float64) SizeType {
- width := s.Wd * height / s.Ht
- return SizeType{width, height}
- }
- //The untypedKeyMap structure and its methods are copyrighted 2019 by Arteom Korotkiy (Gmail: arteomkorotkiy).
- //Imitation of untyped Map Array
- type untypedKeyMap struct {
- keySet []interface{}
- valueSet []int
- }
- //Get position of key=>value in PHP Array
- func (pa *untypedKeyMap) getIndex(key interface{}) int {
- if key != nil {
- for i, mKey := range pa.keySet {
- if mKey == key {
- return i
- }
- }
- return -1
- }
- return -1
- }
- //Put key=>value in PHP Array
- func (pa *untypedKeyMap) put(key interface{}, value int) {
- if key == nil {
- var i int
- for n := 0; ; n++ {
- i = pa.getIndex(n)
- if i < 0 {
- key = n
- break
- }
- }
- pa.keySet = append(pa.keySet, key)
- pa.valueSet = append(pa.valueSet, value)
- } else {
- i := pa.getIndex(key)
- if i < 0 {
- pa.keySet = append(pa.keySet, key)
- pa.valueSet = append(pa.valueSet, value)
- } else {
- pa.valueSet[i] = value
- }
- }
- }
- //Delete value in PHP Array
- func (pa *untypedKeyMap) delete(key interface{}) {
- if pa == nil || pa.keySet == nil || pa.valueSet == nil {
- return
- }
- i := pa.getIndex(key)
- if i >= 0 {
- if i == 0 {
- pa.keySet = pa.keySet[1:]
- pa.valueSet = pa.valueSet[1:]
- } else if i == len(pa.keySet)-1 {
- pa.keySet = pa.keySet[:len(pa.keySet)-1]
- pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
- } else {
- pa.keySet = append(pa.keySet[:i], pa.keySet[i+1:]...)
- pa.valueSet = append(pa.valueSet[:i], pa.valueSet[i+1:]...)
- }
- }
- }
- //Get value from PHP Array
- func (pa *untypedKeyMap) get(key interface{}) int {
- i := pa.getIndex(key)
- if i >= 0 {
- return pa.valueSet[i]
- }
- return 0
- }
- //Imitation of PHP function pop()
- func (pa *untypedKeyMap) pop() {
- pa.keySet = pa.keySet[:len(pa.keySet)-1]
- pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
- }
- //Imitation of PHP function array_merge()
- func arrayMerge(arr1, arr2 *untypedKeyMap) *untypedKeyMap {
- answer := untypedKeyMap{}
- if arr1 == nil && arr2 == nil {
- answer = untypedKeyMap{
- make([]interface{}, 0),
- make([]int, 0),
- }
- } else if arr2 == nil {
- answer.keySet = arr1.keySet[:]
- answer.valueSet = arr1.valueSet[:]
- } else if arr1 == nil {
- answer.keySet = arr2.keySet[:]
- answer.valueSet = arr2.valueSet[:]
- } else {
- answer.keySet = arr1.keySet[:]
- answer.valueSet = arr1.valueSet[:]
- for i := 0; i < len(arr2.keySet); i++ {
- if arr2.keySet[i] == "interval" {
- if arr1.getIndex("interval") < 0 {
- answer.put("interval", arr2.valueSet[i])
- }
- } else {
- answer.put(nil, arr2.valueSet[i])
- }
- }
- }
- return &answer
- }
- func remove(arr []int, key int) []int {
- n := 0
- for i, mKey := range arr {
- if mKey == key {
- n = i
- }
- }
- if n == 0 {
- return arr[1:]
- } else if n == len(arr)-1 {
- return arr[:len(arr)-1]
- }
- return append(arr[:n], arr[n+1:]...)
- }
- func isChinese(rune2 rune) bool {
- // chinese unicode: 4e00-9fa5
- if rune2 >= rune(0x4e00) && rune2 <= rune(0x9fa5) {
- return true
- }
- return false
- }
- // Condition font family string to PDF name compliance. See section 5.3 (Names)
- // in https://resources.infosecinstitute.com/pdf-file-format-basic-structure/
- func fontFamilyEscape(familyStr string) (escStr string) {
- escStr = strings.Replace(familyStr, " ", "#20", -1)
- // Additional replacements can take place here
- return
- }
|