Repository: sopov/resumeio2pdf Branch: main Commit: 528274d3c22e Files: 108 Total size: 300.1 KB Directory structure: gitextract_c0w8xc36/ ├── .github/ │ └── workflows/ │ ├── codeql-analysis.yml │ ├── go.yml │ └── golangci-lint.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── main.go └── vendor/ ├── github.com/ │ ├── phpdave11/ │ │ └── gofpdi/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── const.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── gofpdi.go │ │ ├── helper.go │ │ ├── importer.go │ │ ├── reader.go │ │ └── writer.go │ ├── pkg/ │ │ └── errors/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── appveyor.yml │ │ ├── errors.go │ │ └── stack.go │ └── signintech/ │ └── gopdf/ │ ├── .gitignore │ ├── Changelog.md │ ├── LICENSE │ ├── README.md │ ├── box.go │ ├── buff.go │ ├── buff_write.go │ ├── buffer_pool.go │ ├── cache_contact_color.go │ ├── cache_content_gray.go │ ├── cache_content_image.go │ ├── cache_content_imported_object.go │ ├── cache_content_line.go │ ├── cache_content_line_type.go │ ├── cache_content_line_width.go │ ├── cache_content_oval.go │ ├── cache_content_polygon.go │ ├── cache_content_rectangle.go │ ├── cache_content_rotate.go │ ├── cache_content_text.go │ ├── cache_contnent_curve.go │ ├── catalog_obj.go │ ├── cell_option.go │ ├── cid_font_obj.go │ ├── config.go │ ├── content_obj.go │ ├── current.go │ ├── device_rgb_obj.go │ ├── embedfont_obj.go │ ├── encoding_obj.go │ ├── encryption_obj.go │ ├── ext_g_state_obj.go │ ├── font_obj.go │ ├── font_option.go │ ├── fontconverthelper.go │ ├── fontdescriptor_obj.go │ ├── fontmaker/ │ │ └── core/ │ │ ├── fontmaker.go │ │ ├── fontmap.go │ │ ├── kern_table.go │ │ ├── math.go │ │ ├── table_directory_entry.go │ │ ├── ttf_info.go │ │ ├── ttfparser.go │ │ ├── ttfparser_cmap_other_format.go │ │ └── ttfparser_kern.go │ ├── func_kern_override.go │ ├── go.mod │ ├── go.sum │ ├── gopdf.go │ ├── i_cache_contneter.go │ ├── ifont.go │ ├── image_holder.go │ ├── image_obj.go │ ├── image_obj_parse.go │ ├── img_info.go │ ├── imported_obj.go │ ├── iobj.go │ ├── link_option.go │ ├── list_cache_content.go │ ├── map_of_character_To_glyph_index.go │ ├── margin.go │ ├── outlines_obj.go │ ├── page_obj.go │ ├── page_option.go │ ├── page_sizes.go │ ├── pages_obj.go │ ├── pdf_dictionary_obj.go │ ├── pdf_info_obj.go │ ├── pdf_protection.go │ ├── point.go │ ├── procset_obj.go │ ├── rect.go │ ├── smask_obj.go │ ├── strhelper.go │ ├── style.go │ ├── subfont_descriptor_obj.go │ ├── subset_font_obj.go │ ├── transparency.go │ ├── transparency_xobject_group.go │ ├── ttf_option.go │ └── unicode_map.go └── modules.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ main ] pull_request: # The branches below must be a subset of the branches above branches: [ main ] schedule: - cron: '43 20 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 ================================================ FILE: .github/workflows/go.yml ================================================ name: Go on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17 - name: Build run: go build -v ./... - name: Test run: go test -v ./... ================================================ FILE: .github/workflows/golangci-lint.yml ================================================ name: golangci-lint on: push: branches: [ main ] pull_request: branches: [ main ] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: latest ================================================ FILE: .gitignore ================================================ # PDF files *.pdf # Test binary, built with `go test -c` *.test .idea/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Leonid Sopov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # resumeio2pdf > ⚠️ This project is no longer maintained. > Resume.io made changes on their side, so the tool no longer works. A progam that allows users to download their resumes from [resume.io](https://resume.io/) as PDFs, including links. ## Usage ```bash ./resumeio2pdf [options] [ID or URL] ./resumeio2pdf https://resume.io/r/SecureID ``` Options: * `-pdf` (string) name of pdf file (default: `SecureID` + `.pdf`) * `-sid` (string) SecureID of resume * `-url` (string) link to resume of the format: https://resume.io/r/SecureID * `-verbose` show detail information * `-version` show version * `-y` overwrite PDF file ## Quick Instructions 1. Run `go build` to generate the executable. 2. Run `./resumeio2pdf https://resume.io/r/[SecureID]` where the provided URL is that generated by the **Share Link** on resume.io. ## Step-by-Step Instructions 1. Download the application by clicking the **<> Code** button and choosing **Download ZIP**. 2. Once the download has been completed, extract the files. You will end up with a **resumeio2pdf-main/** folder. 3. Open your terminal and navigate to the **resumeio2pdf-main/** folder that you have just extracted. - You may have the option to skip this by simply choosing the **New Terminal at Folder** after right clicking on the folder. 4. With your terminal still open, type the command `go version` to see if you already have Go installed. - If you get a message with version information that looks something like `go version go1.17.6 …` then you can skip to Step 6. - Otherwise, continue on to the next step. 5. Open your web browser and navigate to the [Go Downloads Page](https://go.dev/dl/) and choose your operating system from the **Featured Downloads** section. Download the package and follow the installation instructions. - Upon completion, restart your terminal and navigate back to the **resumeio2pdf-main/** folder. You should now see version information after typing in the `go version` command. If you have issues with this step, try visiting the [Go Download and Install Page](https://go.dev/doc/install) for the official instructions. 6. With Go installed, you can now run the `go build` command to build the executable file that will download your PDF. - A successful build will not display any confirmation in the terminal. However, you can check the files in the **resumeio2pdf-main/** folder to confirm that a **resumeio2pdf** executable file has been generated by the command. - If you received an error in the terminal, make sure you are still in the **resumeio2pdf-main/** folder before running the command. Otherwise, trace back through the above steps and give it another shot. 7. Finally, you can run the `./resumeio2pdf https://resume.io/r/[SecureID]` where the provided URL is that generated by the **Share Link** on resume.io. When the process is complete, you will receive a message in the terminal that your file has been stored as **\[SecureID].pdf**. Check the **resumeio2pdf-main/** folder for your file. ## Alternative Methods Repository with binary files: https://github.com/sopov/resumeio2pdf.bin ## Other Questions and Concerns * I don't understand how to install and/or use Go. * Can you download my resume for me? * Can you make a video tutorial? Please visit the [pricing page on Resume.io](https://resume.io/pricing) which provides fair and affordable prices for this service, including an easy method of downloading your resume without the use of this software. ================================================ FILE: go.mod ================================================ module github.com/sopov/resumeio2pdf go 1.16 require github.com/signintech/gopdf v0.9.20 ================================================ FILE: go.sum ================================================ github.com/phpdave11/gofpdi v1.0.11 h1:wsBNx+3S0wy1dEp6fzv281S74ogZGgIdYWV2PugWgho= github.com/phpdave11/gofpdi v1.0.11/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/signintech/gopdf v0.9.20 h1:UJInJStc5NpkSCSZCmkSrUy7JR/BqRWpoH50c7hJJ5w= github.com/signintech/gopdf v0.9.20/go.mod h1:PXwitUSeFWEWs+wHVjSS3cUmD4PTXB686ozqfDIQQoQ= ================================================ FILE: main.go ================================================ package main import ( "encoding/json" "errors" "flag" "fmt" "io" "log" "net/http" "os" "path/filepath" "regexp" "time" "github.com/signintech/gopdf" ) const ( Version = "1.0" NameOfProgram = "resumeio2pdf" Copy = "Copyright (c) 2021, Leonid Sopov " CopyURL = "https://github.com/sopov/resumeio2pdf/" resumePage = "https://resume.io/r/%s" resumeMeta = "https://ssr.resume.tools/meta/ssid-%s?cache=%s" resumeExt = "png" // png, jpeg resumeIMG = "https://ssr.resume.tools/to-image/ssid-%s-%d.%s?cache=%s&size=%d" resumeSize = 1800 Timeout = 60 * time.Second exitCodeMisuseArgs = 2 ) var ( url = flag.String("url", "", "link to resume of the format: https://resume.io/r/SecureID") sid = flag.String("sid", "", "SecureID of resume") showVersion = flag.Bool("version", false, "show version") verbose = flag.Bool("verbose", false, "show detail information") overWrite = flag.Bool("y", false, "overwrite PDF file") pdfFileName = flag.String("pdf", "", "name of pdf file (default: SecureID + .pdf)") httpClient = &http.Client{Timeout: Timeout} reSID = regexp.MustCompile(`^[[:alnum:]]+$`) reID = regexp.MustCompile(`^\d+$`) reURL = regexp.MustCompile(`^https://resume[.]io/r/([[:alnum:]]+)`) reIDURL = regexp.MustCompile(`^https://resume[.]io/(?:app|api)/.*?/(\d+)`) ) type metaLink struct { URL string `json:"url"` Left float64 `json:"left"` Top float64 `json:"top"` Height float64 `json:"height"` Width float64 `json:"width"` } type metaViewPort struct { Height float64 `json:"height"` Width float64 `json:"width"` } type metaPageInfo struct { Links []metaLink `json:"links"` ViewPort metaViewPort `json:"viewport"` } type metaInfo struct { Pages []metaPageInfo `json:"pages"` } func main() { if !readFlags() || *sid == "" { os.Exit(exitCodeMisuseArgs) } loggerf("SecureID: %s", *sid) loggerf("URL: %s", *url) loggerf("PDF: %s", *pdfFileName) meta, err := getMeta() if err != nil { log.Fatalln(err) } images, err := getResumeImages(len(meta.Pages)) if err != nil { log.Fatalln(err) } err = generatePDF(meta, images) if err != nil { log.Fatalln(err) } cleanup(images) fmt.Printf("Resume stored to %s\n", *pdfFileName) } func cleanup(images []string) { for _, file := range images { if _, err := os.Stat(file); os.IsNotExist(err) { continue } if err := os.Remove(file); err != nil { fmt.Printf("Error on remove `%s': %s", file, err.Error()) } else { loggerf("Image `%s' successfully deleted.", file) } } } func generatePDF(info *metaInfo, images []string) error { pdf := gopdf.GoPdf{} logger("Start Generate PDF") pageSize := gopdf.Rect{ W: info.Pages[0].ViewPort.Width, H: info.Pages[0].ViewPort.Height, } pdf.Start(gopdf.Config{PageSize: pageSize}) for i, image := range images { loggerf("Add page #%d", i+1) pageSize := gopdf.Rect{ W: info.Pages[i].ViewPort.Width, H: info.Pages[i].ViewPort.Height, } opt := gopdf.PageOption{ PageSize: &pageSize, } pdf.AddPageWithOption(opt) err := pdf.Image(image, 0, 0, &pageSize) if err != nil { return err } for _, link := range info.Pages[i].Links { loggerf("Add link to %s", link.URL) x := link.Left y := pageSize.H - link.Top - link.Height pdf.AddExternalLink(link.URL, x, y, link.Width, link.Height) } } loggerf("Store PDF to `%s'", *pdfFileName) return pdf.WritePdf(*pdfFileName) } func getResumeImages(p int) (pages []string, err error) { if p < 1 { return nil, errors.New("required one or more pages") } for pID := 1; pID <= p; pID++ { pageFile := fmt.Sprintf("%s-%d.%s", *sid, pID, resumeExt) if _, err := os.Stat(pageFile); os.IsNotExist(err) { loggerf("Download image #%d/%d", pID, p) imgURL := fmt.Sprintf(resumeIMG, *sid, pID, resumeExt, time.Now().UTC().Format(time.RFC3339), resumeSize) if err := downloadPage(imgURL, pageFile); err != nil { return pages, err } } pages = append(pages, pageFile) } loggerf("Total %d pages", len(pages)) return pages, nil } func downloadPage(imgURL, imgFile string) error { r, err := httpClient.Get(imgURL) if err != nil { return err } defer r.Body.Close() if r.StatusCode != http.StatusOK { return errors.New(r.Status) } file, err := os.Create(imgFile) if err != nil { return err } defer file.Close() _, err = io.Copy(file, r.Body) if err != nil { return err } return nil } func getJSON(url string, target interface{}) error { logger("Download meta information") r, err := httpClient.Get(url) if err != nil { return err } defer r.Body.Close() if r.StatusCode != http.StatusOK { return fmt.Errorf("Can't download information from the site resume.io. Please, check URL.\n\nError: %s", r.Status) } decoder := json.NewDecoder(r.Body) err = decoder.Decode(&target) if err != nil { return err } return nil } func getMeta() (meta *metaInfo, err error) { metaURL := fmt.Sprintf(resumeMeta, *sid, time.Now().UTC().Format(time.RFC3339)) meta = &metaInfo{} err = getJSON(metaURL, meta) return meta, err } func readFlags() bool { flag.Parse() if *showVersion { fmt.Printf("Version: %s\n", Version) return false } if !extractArg() { return false } if *sid == "" && *url == "" { usages() return false } if *sid != "" { if !reSID.MatchString(*sid) { fmt.Println("The ID must be as alphanumeric") return false } *url = fmt.Sprintf(resumePage, *sid) } if reIDURL.MatchString(*url) { usageID() return false } if !reURL.MatchString(*url) { msg := fmt.Sprintf("The URL must be in the format %s\n", resumePage) fmt.Printf(msg, "SecureID") return false } if *sid == "" { m := reURL.FindSubmatch([]byte(*url)) *sid = string(m[1]) } if *pdfFileName == "" { *pdfFileName = fmt.Sprintf("%s.pdf", *sid) } rePDF := regexp.MustCompile(`(?i)[.]pdf$`) if !rePDF.MatchString(*pdfFileName) { *pdfFileName = fmt.Sprintf("%s.pdf", *pdfFileName) } if _, err := os.Stat(*pdfFileName); !*overWrite && !os.IsNotExist(err) { fmt.Printf("File `%s' already exists.\n\nFor overwrite run with `-y' flag\n", *pdfFileName) return false } return true } func extractArg() bool { arg := flag.Arg(0) if arg == "" { return true } if reID.MatchString(arg) { usageID() return false } if reIDURL.MatchString(arg) { usageID() return false } if reSID.MatchString(arg) { *sid = arg return true } if reURL.MatchString(arg) { *url = arg return true } return true } func usages() { fileExec, err := os.Executable() if err == nil { fileExec = filepath.Base(fileExec) } if fileExec == "" { fileExec = NameOfProgram } fmt.Println("Syntax:") fmt.Println(" ", fileExec, "[options] [ID or URL]") fmt.Println() fmt.Println("Options:") flag.PrintDefaults() fmt.Println() fmt.Println(Copy) fmt.Println(CopyURL) } func usageID() { fmt.Println("Open in browser: https://resume.io/app/resumes") fmt.Println("Click on `...More' / `Share a link', and lunch with private URL.") } func logger(v ...interface{}) { if !*verbose { return } log.Println(v...) } func loggerf(format string, a ...interface{}) { if !*verbose { return } log.Printf(format, a...) } ================================================ FILE: vendor/github.com/phpdave11/gofpdi/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2019-2020 David Barnes Copyright (c) 2017 Setasign - Jan Slabon, https://www.setasign.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/phpdave11/gofpdi/README.md ================================================ # gofpdi [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/phpdave11/gofpdi/master/LICENSE) [![Report](https://goreportcard.com/badge/github.com/phpdave11/gofpdi)](https://goreportcard.com/report/github.com/phpdave11/gofpdi) [![GoDoc](https://img.shields.io/badge/godoc-gofpdi-blue.svg)](https://godoc.org/github.com/phpdave11/gofpdi) ## Go Free PDF Document Importer gofpdi allows you to import an existing PDF into a new PDF. The following PDF generation libraries are supported: - [gopdf](https://github.com/signintech/gopdf) - [gofpdf](https://github.com/phpdave11/gofpdf) ## Acknowledgments This package’s code is derived from the [fpdi](https://github.com/Setasign/FPDI/tree/1.6.x-legacy) library created by [Jan Slabon](https://github.com/JanSlabon). [mrtsbt](https://github.com/mrtsbt) added support for reading a PDF from an `io.ReadSeeker` stream and also added support for using gofpdi concurrently. [Asher Tuggle](https://github.com/awesomeunleashed) added support for reading PDFs that have split xref tables. ## Examples ### gopdf example ```go package main import ( "github.com/signintech/gopdf" "io" "net/http" "os" ) func main() { var err error // Download a Font fontUrl := "https://github.com/google/fonts/raw/master/ofl/daysone/DaysOne-Regular.ttf" if err = DownloadFile("example-font.ttf", fontUrl); err != nil { panic(err) } // Download a PDF fileUrl := "https://tcpdf.org/files/examples/example_012.pdf" if err = DownloadFile("example-pdf.pdf", fileUrl); err != nil { panic(err) } pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{PageSize: gopdf.Rect{W: 595.28, H: 841.89}}) //595.28, 841.89 = A4 pdf.AddPage() err = pdf.AddTTFFont("daysone", "example-font.ttf") if err != nil { panic(err) } err = pdf.SetFont("daysone", "", 20) if err != nil { panic(err) } // Color the page pdf.SetLineWidth(0.1) pdf.SetFillColor(124, 252, 0) //setup fill color pdf.RectFromUpperLeftWithStyle(50, 100, 400, 600, "FD") pdf.SetFillColor(0, 0, 0) pdf.SetX(50) pdf.SetY(50) pdf.Cell(nil, "Import existing PDF into GoPDF Document") // Import page 1 tpl1 := pdf.ImportPage("example-pdf.pdf", 1, "/MediaBox") // Draw pdf onto page pdf.UseImportedTemplate(tpl1, 50, 100, 400, 0) pdf.WritePdf("example.pdf") } // DownloadFile will download a url to a local file. It's efficient because it will // write as it downloads and not load the whole file into memory. func DownloadFile(filepath string, url string) error { // Get the data resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() // Create the file out, err := os.Create(filepath) if err != nil { return err } defer out.Close() // Write the body to file _, err = io.Copy(out, resp.Body) return err } ``` Generated PDF: [example.pdf](https://github.com/signintech/gopdf/files/3144466/example.pdf) Screenshot of PDF: ![example](https://user-images.githubusercontent.com/9421180/57180557-4c1dbd80-6e4f-11e9-8f47-9d40217805be.jpg) ### gofpdf example #1 - import PDF from file ```go package main import ( "github.com/phpdave11/gofpdf" "github.com/phpdave11/gofpdf/contrib/gofpdi" "io" "net/http" "os" ) func main() { var err error pdf := gofpdf.New("P", "mm", "A4", "") // Download a PDF fileUrl := "https://tcpdf.org/files/examples/example_026.pdf" if err = DownloadFile("example-pdf.pdf", fileUrl); err != nil { panic(err) } // Import example-pdf.pdf with gofpdi free pdf document importer tpl1 := gofpdi.ImportPage(pdf, "example-pdf.pdf", 1, "/MediaBox") pdf.AddPage() pdf.SetFillColor(200, 700, 220) pdf.Rect(20, 50, 150, 215, "F") // Draw imported template onto page gofpdi.UseImportedTemplate(pdf, tpl1, 20, 50, 150, 0) pdf.SetFont("Helvetica", "", 20) pdf.Cell(0, 0, "Import existing PDF into gofpdf document with gofpdi") err = pdf.OutputFileAndClose("example.pdf") if err != nil { panic(err) } } // DownloadFile will download a url to a local file. It's efficient because it will // write as it downloads and not load the whole file into memory. func DownloadFile(filepath string, url string) error { // Get the data resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() // Create the file out, err := os.Create(filepath) if err != nil { return err } defer out.Close() // Write the body to file _, err = io.Copy(out, resp.Body) return err } ``` Generated PDF: [example.pdf](https://github.com/phpdave11/gofpdf/files/3178770/example.pdf) Screenshot of PDF: ![example](https://user-images.githubusercontent.com/9421180/57713804-ca8d1300-7638-11e9-9f8e-e3f803374803.jpg) ### gofpdf example #2 - import PDF from stream ```go package main import ( "bytes" "github.com/phpdave11/gofpdf" "github.com/phpdave11/gofpdf/contrib/gofpdi" "io" "io/ioutil" "net/http" ) func main() { var err error pdf := gofpdf.New("P", "mm", "A4", "") // Download a PDF into memory res, err := http.Get("https://tcpdf.org/files/examples/example_038.pdf") if err != nil { panic(err) } pdfBytes, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { panic(err) } // convert []byte to io.ReadSeeker rs := io.ReadSeeker(bytes.NewReader(pdfBytes)) // Import in-memory PDF stream with gofpdi free pdf document importer tpl1 := gofpdi.ImportPageFromStream(pdf, &rs, 1, "/TrimBox") pdf.AddPage() pdf.SetFillColor(200, 700, 220) pdf.Rect(20, 50, 150, 215, "F") // Draw imported template onto page gofpdi.UseImportedTemplate(pdf, tpl1, 20, 50, 150, 0) pdf.SetFont("Helvetica", "", 20) pdf.Cell(0, 0, "Import PDF stream into gofpdf document with gofpdi") err = pdf.OutputFileAndClose("example.pdf") if err != nil { panic(err) } } ``` Generated PDF: [example.pdf](https://github.com/phpdave11/gofpdi/files/3483219/example.pdf) Screenshot of PDF: ![example.jpg](https://user-images.githubusercontent.com/9421180/62728726-18b87500-b9e2-11e9-885c-7c68b7ac6222.jpg) ================================================ FILE: vendor/github.com/phpdave11/gofpdi/const.go ================================================ package gofpdi const ( PDF_TYPE_NULL = iota PDF_TYPE_NUMERIC PDF_TYPE_TOKEN PDF_TYPE_HEX PDF_TYPE_STRING PDF_TYPE_DICTIONARY PDF_TYPE_ARRAY PDF_TYPE_OBJDEC PDF_TYPE_OBJREF PDF_TYPE_OBJECT PDF_TYPE_STREAM PDF_TYPE_BOOLEAN PDF_TYPE_REAL ) ================================================ FILE: vendor/github.com/phpdave11/gofpdi/go.mod ================================================ module github.com/phpdave11/gofpdi go 1.12 require github.com/pkg/errors v0.8.1 ================================================ FILE: vendor/github.com/phpdave11/gofpdi/go.sum ================================================ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= ================================================ FILE: vendor/github.com/phpdave11/gofpdi/gofpdi.go ================================================ package gofpdi ================================================ FILE: vendor/github.com/phpdave11/gofpdi/helper.go ================================================ package gofpdi import ( "strings" ) // Determine if a value is numeric // Courtesy of https://github.com/syyongx/php2go/blob/master/php.go func is_numeric(val interface{}) bool { switch val.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: case float32, float64, complex64, complex128: return true case string: str := val.(string) if str == "" { return false } // Trim any whitespace str = strings.TrimSpace(str) //fmt.Println(str) if str[0] == '-' || str[0] == '+' { if len(str) == 1 { return false } str = str[1:] } // hex if len(str) > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') { for _, h := range str[2:] { if !((h >= '0' && h <= '9') || (h >= 'a' && h <= 'f') || (h >= 'A' && h <= 'F')) { return false } } return true } // 0-9,Point,Scientific p, s, l := 0, 0, len(str) for i, v := range str { if v == '.' { // Point if p > 0 || s > 0 || i+1 == l { return false } p = i } else if v == 'e' || v == 'E' { // Scientific if i == 0 || s > 0 || i+1 == l { return false } s = i } else if v < '0' || v > '9' { return false } } return true } return false } func in_array(needle interface{}, hystack interface{}) bool { switch key := needle.(type) { case string: for _, item := range hystack.([]string) { if key == item { return true } } case int: for _, item := range hystack.([]int) { if key == item { return true } } case int64: for _, item := range hystack.([]int64) { if key == item { return true } } default: return false } return false } // Taken from png library // intSize is either 32 or 64. const intSize = 32 << (^uint(0) >> 63) func abs(x int) int { // m := -1 if x < 0. m := 0 otherwise. m := x >> (intSize - 1) // In two's complement representation, the negative number // of any number (except the smallest one) can be computed // by flipping all the bits and add 1. This is faster than // code with a branch. // See Hacker's Delight, section 2-4. return (x ^ m) - m } // filterPaeth applies the Paeth filter to the cdat slice. // cdat is the current row's data, pdat is the previous row's data. func filterPaeth(cdat, pdat []byte, bytesPerPixel int) { var a, b, c, pa, pb, pc int for i := 0; i < bytesPerPixel; i++ { a, c = 0, 0 for j := i; j < len(cdat); j += bytesPerPixel { b = int(pdat[j]) pa = b - c pb = a - c pc = abs(pa + pb) pa = abs(pa) pb = abs(pb) if pa <= pb && pa <= pc { // No-op. } else if pb <= pc { a = b } else { a = c } a += int(cdat[j]) a &= 0xff cdat[j] = uint8(a) c = b } } } ================================================ FILE: vendor/github.com/phpdave11/gofpdi/importer.go ================================================ package gofpdi import ( "fmt" "io" ) // The Importer class to be used by a pdf generation library type Importer struct { sourceFile string readers map[string]*PdfReader writers map[string]*PdfWriter tplMap map[int]*TplInfo tplN int writer *PdfWriter } type TplInfo struct { SourceFile string Writer *PdfWriter TemplateId int } func (this *Importer) GetReader() *PdfReader { return this.GetReaderForFile(this.sourceFile) } func (this *Importer) GetWriter() *PdfWriter { return this.GetWriterForFile(this.sourceFile) } func (this *Importer) GetReaderForFile(file string) *PdfReader { if _, ok := this.readers[file]; ok { return this.readers[file] } return nil } func (this *Importer) GetWriterForFile(file string) *PdfWriter { if _, ok := this.writers[file]; ok { return this.writers[file] } return nil } func NewImporter() *Importer { importer := &Importer{} importer.init() return importer } func (this *Importer) init() { this.readers = make(map[string]*PdfReader, 0) this.writers = make(map[string]*PdfWriter, 0) this.tplMap = make(map[int]*TplInfo, 0) this.writer, _ = NewPdfWriter("") } func (this *Importer) SetSourceFile(f string) { this.sourceFile = f // If reader hasn't been instantiated, do that now if _, ok := this.readers[this.sourceFile]; !ok { reader, err := NewPdfReader(this.sourceFile) if err != nil { panic(err) } this.readers[this.sourceFile] = reader } // If writer hasn't been instantiated, do that now if _, ok := this.writers[this.sourceFile]; !ok { writer, err := NewPdfWriter("") if err != nil { panic(err) } // Make the next writer start template numbers at this.tplN writer.SetTplIdOffset(this.tplN) this.writers[this.sourceFile] = writer } } func (this *Importer) SetSourceStream(rs *io.ReadSeeker) { this.sourceFile = fmt.Sprintf("%v", rs) if _, ok := this.readers[this.sourceFile]; !ok { reader, err := NewPdfReaderFromStream(*rs) if err != nil { panic(err) } this.readers[this.sourceFile] = reader } // If writer hasn't been instantiated, do that now if _, ok := this.writers[this.sourceFile]; !ok { writer, err := NewPdfWriter("") if err != nil { panic(err) } // Make the next writer start template numbers at this.tplN writer.SetTplIdOffset(this.tplN) this.writers[this.sourceFile] = writer } } func (this *Importer) GetPageSizes() map[int]map[string]map[string]float64 { result, err := this.GetReader().getAllPageBoxes(1.0) if err != nil { panic(err) } return result } func (this *Importer) ImportPage(pageno int, box string) int { res, err := this.GetWriter().ImportPage(this.GetReader(), pageno, box) if err != nil { panic(err) } // Get current template id tplN := this.tplN // Set tpl info this.tplMap[tplN] = &TplInfo{SourceFile: this.sourceFile, TemplateId: res, Writer: this.GetWriter()} // Increment template id this.tplN++ return tplN } func (this *Importer) SetNextObjectID(objId int) { this.GetWriter().SetNextObjectID(objId) } // Put form xobjects and get back a map of template names (e.g. /GOFPDITPL1) and their object ids (int) func (this *Importer) PutFormXobjects() map[string]int { res := make(map[string]int, 0) tplNamesIds, err := this.GetWriter().PutFormXobjects(this.GetReader()) if err != nil { panic(err) } for tplName, pdfObjId := range tplNamesIds { res[tplName] = pdfObjId.id } return res } // Put form xobjects and get back a map of template names (e.g. /GOFPDITPL1) and their object ids (sha1 hash) func (this *Importer) PutFormXobjectsUnordered() map[string]string { this.GetWriter().SetUseHash(true) res := make(map[string]string, 0) tplNamesIds, err := this.GetWriter().PutFormXobjects(this.GetReader()) if err != nil { panic(err) } for tplName, pdfObjId := range tplNamesIds { res[tplName] = pdfObjId.hash } return res } // Get object ids (int) and their contents (string) func (this *Importer) GetImportedObjects() map[int]string { res := make(map[int]string, 0) pdfObjIdBytes := this.GetWriter().GetImportedObjects() for pdfObjId, bytes := range pdfObjIdBytes { res[pdfObjId.id] = string(bytes) } return res } // Get object ids (sha1 hash) and their contents ([]byte) // The contents may have references to other object hashes which will need to be replaced by the pdf generator library // The positions of the hashes (sha1 - 40 characters) can be obtained by calling GetImportedObjHashPos() func (this *Importer) GetImportedObjectsUnordered() map[string][]byte { res := make(map[string][]byte, 0) pdfObjIdBytes := this.GetWriter().GetImportedObjects() for pdfObjId, bytes := range pdfObjIdBytes { res[pdfObjId.hash] = bytes } return res } // Get the positions of the hashes (sha1 - 40 characters) within each object, to be replaced with // actual objects ids by the pdf generator library func (this *Importer) GetImportedObjHashPos() map[string]map[int]string { res := make(map[string]map[int]string, 0) pdfObjIdPosHash := this.GetWriter().GetImportedObjHashPos() for pdfObjId, posHashMap := range pdfObjIdPosHash { res[pdfObjId.hash] = posHashMap } return res } // For a given template id (returned from ImportPage), get the template name (e.g. /GOFPDITPL1) and // the 4 float64 values necessary to draw the template a x,y for a given width and height. func (this *Importer) UseTemplate(tplid int, _x float64, _y float64, _w float64, _h float64) (string, float64, float64, float64, float64) { // Look up template id in importer tpl map tplInfo := this.tplMap[tplid] return tplInfo.Writer.UseTemplate(tplInfo.TemplateId, _x, _y, _w, _h) } ================================================ FILE: vendor/github.com/phpdave11/gofpdi/reader.go ================================================ package gofpdi import ( "bufio" "bytes" "compress/zlib" "encoding/binary" "fmt" "github.com/pkg/errors" "io" "io/ioutil" "math" "os" "strconv" ) type PdfReader struct { availableBoxes []string stack []string trailer *PdfValue catalog *PdfValue pages []*PdfValue xrefPos int xref map[int]map[int]int xrefStream map[int][2]int f io.ReadSeeker nBytes int64 sourceFile string } func NewPdfReaderFromStream(rs io.ReadSeeker) (*PdfReader, error) { length, err := rs.Seek(0, 2) if err != nil { return nil, errors.Wrapf(err, "Failed to determine stream length") } parser := &PdfReader{f: rs, nBytes: length} if err := parser.init(); err != nil { return nil, errors.Wrap(err, "Failed to initialize parser") } if err := parser.read(); err != nil { return nil, errors.Wrap(err, "Failed to read pdf from stream") } return parser, nil } func NewPdfReader(filename string) (*PdfReader, error) { var err error f, err := os.Open(filename) if err != nil { return nil, errors.Wrap(err, "Failed to open file") } info, err := f.Stat() if err != nil { return nil, errors.Wrap(err, "Failed to obtain file information") } parser := &PdfReader{f: f, sourceFile: filename, nBytes: info.Size()} if err = parser.init(); err != nil { return nil, errors.Wrap(err, "Failed to initialize parser") } if err = parser.read(); err != nil { return nil, errors.Wrap(err, "Failed to read pdf") } return parser, nil } func (this *PdfReader) init() error { this.availableBoxes = []string{"/MediaBox", "/CropBox", "/BleedBox", "/TrimBox", "/ArtBox"} this.xref = make(map[int]map[int]int, 0) this.xrefStream = make(map[int][2]int, 0) err := this.read() if err != nil { return errors.Wrap(err, "Failed to read pdf") } return nil } type PdfValue struct { Type int String string Token string Int int Real float64 Bool bool Dictionary map[string]*PdfValue Array []*PdfValue Id int NewId int Gen int Value *PdfValue Stream *PdfValue Bytes []byte } // Jump over comments func (this *PdfReader) skipComments(r *bufio.Reader) error { var err error var b byte for { b, err = r.ReadByte() if err != nil { return errors.Wrap(err, "Failed to ReadByte while skipping comments") } if b == '\n' || b == '\r' { if b == '\r' { // Peek and see if next char is \n b2, err := r.ReadByte() if err != nil { return errors.Wrap(err, "Failed to read byte") } if b2 != '\n' { r.UnreadByte() } } break } } return nil } // Advance reader so that whitespace is ignored func (this *PdfReader) skipWhitespace(r *bufio.Reader) error { var err error var b byte for { b, err = r.ReadByte() if err != nil { if err == io.EOF { break } return errors.Wrap(err, "Failed to read byte") } if b == ' ' || b == '\n' || b == '\r' || b == '\t' { continue } else { r.UnreadByte() break } } return nil } // Read a token func (this *PdfReader) readToken(r *bufio.Reader) (string, error) { var err error // If there is a token available on the stack, pop it out and return it. if len(this.stack) > 0 { var popped string popped, this.stack = this.stack[len(this.stack)-1], this.stack[:len(this.stack)-1] return popped, nil } err = this.skipWhitespace(r) if err != nil { return "", errors.Wrap(err, "Failed to skip whitespace") } b, err := r.ReadByte() if err != nil { if err == io.EOF { return "", nil } return "", errors.Wrap(err, "Failed to read byte") } switch b { case '[', ']', '(', ')': // This is either an array or literal string delimeter, return it. return string(b), nil case '<', '>': // This could either be a hex string or a dictionary delimiter. // Determine the appropriate case and return the token. nb, err := r.ReadByte() if err != nil { return "", errors.Wrap(err, "Failed to read byte") } if nb == b { return string(b) + string(nb), nil } else { r.UnreadByte() return string(b), nil } case '%': err = this.skipComments(r) if err != nil { return "", errors.Wrap(err, "Failed to skip comments") } return this.readToken(r) default: // FIXME this may not be performant to create new strings for each byte // Is it probably better to create a buffer and then convert to a string at the end. str := string(b) loop: for { b, err := r.ReadByte() if err != nil { return "", errors.Wrap(err, "Failed to read byte") } switch b { case ' ', '%', '[', ']', '<', '>', '(', ')', '\r', '\n', '\t', '/': r.UnreadByte() break loop default: str += string(b) } } return str, nil } return "", nil } // Read a value based on a token func (this *PdfReader) readValue(r *bufio.Reader, t string) (*PdfValue, error) { var err error var b byte result := &PdfValue{} result.Type = -1 result.Token = t result.Dictionary = make(map[string]*PdfValue, 0) result.Array = make([]*PdfValue, 0) switch t { case "<": // This is a hex string // Read bytes until '>' is found var s string for { b, err = r.ReadByte() if err != nil { return nil, errors.Wrap(err, "Failed to read byte") } if b != '>' { s += string(b) } else { break } } result.Type = PDF_TYPE_HEX result.String = s case "<<": // This is a dictionary // Recurse into this function until we reach the end of the dictionary. for { key, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } if key == "" { return nil, errors.New("Token is empty") } if key == ">>" { break } // read next token newKey, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } value, err := this.readValue(r, newKey) if err != nil { return nil, errors.Wrap(err, "Failed to read value for token: "+newKey) } if value.Type == -1 { return result, nil } // Catch missing value if value.Type == PDF_TYPE_TOKEN && value.String == ">>" { result.Type = PDF_TYPE_NULL result.Dictionary[key] = value break } // Set value in dictionary result.Dictionary[key] = value } result.Type = PDF_TYPE_DICTIONARY return result, nil case "[": // This is an array tmpResult := make([]*PdfValue, 0) // Recurse into this function until we reach the end of the array for { key, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } if key == "" { return nil, errors.New("Token is empty") } if key == "]" { break } value, err := this.readValue(r, key) if err != nil { return nil, errors.Wrap(err, "Failed to read value for token: "+key) } if value.Type == -1 { return result, nil } tmpResult = append(tmpResult, value) } result.Type = PDF_TYPE_ARRAY result.Array = tmpResult case "(": // This is a string openBrackets := 1 // Create new buffer var buf bytes.Buffer // Read bytes until brackets are balanced for openBrackets > 0 { b, err := r.ReadByte() if err != nil { return nil, errors.Wrap(err, "Failed to read byte") } switch b { case '(': openBrackets++ case ')': openBrackets-- case '\\': nb, err := r.ReadByte() if err != nil { return nil, errors.Wrap(err, "Failed to read byte") } buf.WriteByte(b) buf.WriteByte(nb) continue } if openBrackets > 0 { buf.WriteByte(b) } } result.Type = PDF_TYPE_STRING result.String = buf.String() case "stream": return nil, errors.New("Stream not implemented") default: result.Type = PDF_TYPE_TOKEN result.Token = t if is_numeric(t) { // A numeric token. Make sure that it is not part of something else t2, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } if t2 != "" { if is_numeric(t2) { // Two numeric tokens in a row. // In this case, we're probably in front of either an object reference // or an object specification. // Determine the case and return the data. t3, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } if t3 != "" { switch t3 { case "obj": result.Type = PDF_TYPE_OBJDEC result.Id, _ = strconv.Atoi(t) result.Gen, _ = strconv.Atoi(t2) return result, nil case "R": result.Type = PDF_TYPE_OBJREF result.Id, _ = strconv.Atoi(t) result.Gen, _ = strconv.Atoi(t2) return result, nil } // If we get to this point, that numeric value up there was just a numeric value. // Push the extra tokens back into the stack and return the value. this.stack = append(this.stack, t3) } } this.stack = append(this.stack, t2) } if n, err := strconv.Atoi(t); err == nil { result.Type = PDF_TYPE_NUMERIC result.Int = n result.Real = float64(n) // Also assign Real value here to fix page box parsing bugs } else { result.Type = PDF_TYPE_REAL result.Real, _ = strconv.ParseFloat(t, 64) } } else if t == "true" || t == "false" { result.Type = PDF_TYPE_BOOLEAN result.Bool = t == "true" } else if t == "null" { result.Type = PDF_TYPE_NULL } else { result.Type = PDF_TYPE_TOKEN result.Token = t } } return result, nil } // Resolve a compressed object (PDF 1.5) func (this *PdfReader) resolveCompressedObject(objSpec *PdfValue) (*PdfValue, error) { var err error // Make sure object reference exists in xrefStream if _, ok := this.xrefStream[objSpec.Id]; !ok { return nil, errors.New(fmt.Sprintf("Could not find object ID %d in xref stream or xref table.", objSpec.Id)) } // Get object id and index objectId := this.xrefStream[objSpec.Id][0] objectIndex := this.xrefStream[objSpec.Id][1] // Read compressed object compressedObjSpec := &PdfValue{Type: PDF_TYPE_OBJREF, Id: objectId, Gen: 0} // Resolve compressed object compressedObj, err := this.resolveObject(compressedObjSpec) if err != nil { return nil, errors.Wrap(err, "Failed to resolve compressed object") } // Verify object type is /ObjStm if _, ok := compressedObj.Value.Dictionary["/Type"]; ok { if compressedObj.Value.Dictionary["/Type"].Token != "/ObjStm" { return nil, errors.New("Expected compressed object type to be /ObjStm") } } else { return nil, errors.New("Could not determine compressed object type.") } // Get number of sub-objects in compressed object n := compressedObj.Value.Dictionary["/N"].Int if n <= 0 { return nil, errors.New("No sub objects in compressed object") } // Get offset of first object first := compressedObj.Value.Dictionary["/First"].Int // Get length //length := compressedObj.Value.Dictionary["/Length"].Int // Check for filter filter := "" if _, ok := compressedObj.Value.Dictionary["/Filter"]; ok { filter = compressedObj.Value.Dictionary["/Filter"].Token if filter != "/FlateDecode" { return nil, errors.New("Unsupported filter - expected /FlateDecode, got: " + filter) } } if filter == "/FlateDecode" { // Decompress if filter is /FlateDecode // Uncompress zlib compressed data var out bytes.Buffer zlibReader, _ := zlib.NewReader(bytes.NewBuffer(compressedObj.Stream.Bytes)) defer zlibReader.Close() io.Copy(&out, zlibReader) // Set stream to uncompressed data compressedObj.Stream.Bytes = out.Bytes() } // Get io.Reader for bytes r := bufio.NewReader(bytes.NewBuffer(compressedObj.Stream.Bytes)) subObjId := 0 subObjPos := 0 // Read sub-object indeces and their positions within the (un)compressed object for i := 0; i < n; i++ { var token string var _objidx int var _objpos int // Read first token (object index) token, err = this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } // Convert line (string) into int _objidx, err = strconv.Atoi(token) if err != nil { return nil, errors.Wrap(err, "Failed to convert token into integer: "+token) } // Read first token (object index) token, err = this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } // Convert line (string) into int _objpos, err = strconv.Atoi(token) if err != nil { return nil, errors.Wrap(err, "Failed to convert token into integer: "+token) } if i == objectIndex { subObjId = _objidx subObjPos = _objpos } } // Now create an io.ReadSeeker rs := io.ReadSeeker(bytes.NewReader(compressedObj.Stream.Bytes)) // Determine where to seek to (sub-object position + /First) seekTo := int64(subObjPos + first) // Fast forward to the object rs.Seek(seekTo, 0) // Create a new io.Reader r = bufio.NewReader(rs) // Read token token, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } // Read object obj, err := this.readValue(r, token) if err != nil { return nil, errors.Wrap(err, "Failed to read value for token: "+token) } result := &PdfValue{} result.Id = subObjId result.Gen = 0 result.Type = PDF_TYPE_OBJECT result.Value = obj return result, nil } func (this *PdfReader) resolveObject(objSpec *PdfValue) (*PdfValue, error) { var err error var old_pos int64 // Create new bufio.Reader r := bufio.NewReader(this.f) if objSpec.Type == PDF_TYPE_OBJREF { // This is a reference, resolve it. offset := this.xref[objSpec.Id][objSpec.Gen] if _, ok := this.xref[objSpec.Id]; !ok { // This may be a compressed object return this.resolveCompressedObject(objSpec) } // Save current file position // This is needed if you want to resolve reference while you're reading another object. // (e.g.: if you need to determine the length of a stream) old_pos, err = this.f.Seek(0, os.SEEK_CUR) if err != nil { return nil, errors.Wrap(err, "Failed to get current position of file") } // Reposition the file pointer and load the object header _, err = this.f.Seek(int64(offset), 0) if err != nil { return nil, errors.Wrap(err, "Failed to set position of file") } token, err := this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } obj, err := this.readValue(r, token) if err != nil { return nil, errors.Wrap(err, "Failed to read value for token: "+token) } if obj.Type != PDF_TYPE_OBJDEC { return nil, errors.New(fmt.Sprintf("Expected type to be PDF_TYPE_OBJDEC, got: %d", obj.Type)) } if obj.Id != objSpec.Id { return nil, errors.New(fmt.Sprintf("Object ID (%d) does not match ObjSpec ID (%d)", obj.Id, objSpec.Id)) } if obj.Gen != objSpec.Gen { return nil, errors.New("Object Gen does not match ObjSpec Gen") } // Read next token token, err = this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } // Read actual object value value, err := this.readValue(r, token) if err != nil { return nil, errors.Wrap(err, "Failed to read value for token: "+token) } // Read next token token, err = this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } result := &PdfValue{} result.Id = obj.Id result.Gen = obj.Gen result.Type = PDF_TYPE_OBJECT result.Value = value if token == "stream" { result.Type = PDF_TYPE_STREAM err = this.skipWhitespace(r) if err != nil { return nil, errors.Wrap(err, "Failed to skip whitespace") } // Get stream length dictionary lengthDict := value.Dictionary["/Length"] // Get number of bytes of stream length := lengthDict.Int // If lengthDict is an object reference, resolve the object and set length if lengthDict.Type == PDF_TYPE_OBJREF { lengthDict, err = this.resolveObject(lengthDict) if err != nil { return nil, errors.Wrap(err, "Failed to resolve length object of stream") } // Set length to resolved object value length = lengthDict.Value.Int } // Read length bytes bytes := make([]byte, length) // Cannot use reader.Read() because that may not read all the bytes _, err := io.ReadFull(r, bytes) if err != nil { return nil, errors.Wrap(err, "Failed to read bytes from buffer") } token, err = this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } if token != "endstream" { return nil, errors.New("Expected next token to be: endstream, got: " + token) } token, err = this.readToken(r) if err != nil { return nil, errors.Wrap(err, "Failed to read token") } streamObj := &PdfValue{} streamObj.Type = PDF_TYPE_STREAM streamObj.Bytes = bytes result.Stream = streamObj } if token != "endobj" { return nil, errors.New("Expected next token to be: endobj, got: " + token) } // Reposition the file pointer to previous position _, err = this.f.Seek(old_pos, 0) if err != nil { return nil, errors.Wrap(err, "Failed to set position of file") } return result, nil } else { return objSpec, nil } return &PdfValue{}, nil } // Find the xref offset (should be at the end of the PDF) func (this *PdfReader) findXref() error { var result int var err error var toRead int64 toRead = 1500 // If PDF is smaller than 1500 bytes, be sure to only read the number of bytes that are in the file fileSize := this.nBytes if fileSize < toRead { toRead = fileSize } // 0 means relative to the origin of the file, // 1 means relative to the current offset, // and 2 means relative to the end. whence := 2 // Perform seek operation _, err = this.f.Seek(-toRead, whence) if err != nil { return errors.Wrap(err, "Failed to set position of file") } // Create new bufio.Reader r := bufio.NewReader(this.f) for { // Read all tokens until "startxref" is found token, err := this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } if token == "startxref" { token, err = this.readToken(r) // Probably EOF before finding startxref if err != nil { return errors.Wrap(err, "Failed to find startxref token") } // Convert line (string) into int result, err = strconv.Atoi(token) if err != nil { return errors.Wrap(err, "Failed to convert xref position into integer: "+token) } // Successfully read the xref position this.xrefPos = result break } } // Rewind file pointer whence = 0 _, err = this.f.Seek(0, whence) if err != nil { return errors.Wrap(err, "Failed to set position of file") } this.xrefPos = result return nil } // Read and parse the xref table func (this *PdfReader) readXref() error { var err error // Create new bufio.Reader r := bufio.NewReader(this.f) // Set file pointer to xref start _, err = this.f.Seek(int64(this.xrefPos), 0) if err != nil { return errors.Wrap(err, "Failed to set position of file") } // Xref should start with 'xref' t, err := this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } if t != "xref" { // Maybe this is an XRef stream ... v, err := this.readValue(r, t) if err != nil { return errors.Wrap(err, "Failed to read XRef stream") } if v.Type == PDF_TYPE_OBJDEC { // Read next token t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } // Read actual object value v, err := this.readValue(r, t) if err != nil { return errors.Wrap(err, "Failed to read value for token: "+t) } // If /Type is set, check to see if it is XRef if _, ok := v.Dictionary["/Type"]; ok { if v.Dictionary["/Type"].Token == "/XRef" { // Continue reading xref stream data now that it is confirmed that it is an xref stream // Check for /DecodeParms paethDecode := false if _, ok := v.Dictionary["/DecodeParms"]; ok { columns := 0 predictor := 0 if _, ok2 := v.Dictionary["/DecodeParms"].Dictionary["/Columns"]; ok2 { columns = v.Dictionary["/DecodeParms"].Dictionary["/Columns"].Int } if _, ok2 := v.Dictionary["/DecodeParms"].Dictionary["/Predictor"]; ok2 { predictor = v.Dictionary["/DecodeParms"].Dictionary["/Predictor"].Int } if columns != 4 || predictor != 12 { return errors.New("Unsupported /DecodeParms - only tested with /Columns 4 /Predictor 12") } paethDecode = true } // Check to make sure field size is [1 2 1] - not yet tested with other field sizes if v.Dictionary["/W"].Array[0].Int != 1 || v.Dictionary["/W"].Array[1].Int > 4 || v.Dictionary["/W"].Array[2].Int != 1 { return errors.New(fmt.Sprintf("Unsupported field sizes in cross-reference stream dictionary: /W [%d %d %d]", v.Dictionary["/W"].Array[0].Int, v.Dictionary["/W"].Array[1].Int, v.Dictionary["/W"].Array[2].Int)) } index := make([]int, 2) // If /Index is not set, this is an error if _, ok := v.Dictionary["/Index"]; ok { if len(v.Dictionary["/Index"].Array) < 2 { return errors.Wrap(err, "Index array does not contain 2 elements") } index[0] = v.Dictionary["/Index"].Array[0].Int index[1] = v.Dictionary["/Index"].Array[1].Int } else { index[0] = 0 } prevXref := 0 // Check for previous xref stream if _, ok := v.Dictionary["/Prev"]; ok { prevXref = v.Dictionary["/Prev"].Int } // Set root object if _, ok := v.Dictionary["/Root"]; ok { // Just set the whole dictionary with /Root key to keep compatibiltiy with existing code this.trailer = v } else { // Don't return an error here. The trailer could be in another XRef stream. //return errors.New("Did not set root object") } startObject := index[0] err = this.skipWhitespace(r) if err != nil { return errors.Wrap(err, "Failed to skip whitespace") } // Get stream length dictionary lengthDict := v.Dictionary["/Length"] // Get number of bytes of stream length := lengthDict.Int // If lengthDict is an object reference, resolve the object and set length if lengthDict.Type == PDF_TYPE_OBJREF { lengthDict, err = this.resolveObject(lengthDict) if err != nil { return errors.Wrap(err, "Failed to resolve length object of stream") } // Set length to resolved object value length = lengthDict.Value.Int } t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } if t != "stream" { return errors.New("Expected next token to be: stream, got: " + t) } err = this.skipWhitespace(r) if err != nil { return errors.Wrap(err, "Failed to skip whitespace") } // Read length bytes data := make([]byte, length) // Cannot use reader.Read() because that may not read all the bytes _, err := io.ReadFull(r, data) if err != nil { return errors.Wrap(err, "Failed to read bytes from buffer") } // Look for endstream token t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } if t != "endstream" { return errors.New("Expected next token to be: endstream, got: " + t) } // Look for endobj token t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } if t != "endobj" { return errors.New("Expected next token to be: endobj, got: " + t) } // Now decode zlib data b := bytes.NewReader(data) z, err := zlib.NewReader(b) if err != nil { return errors.Wrap(err, "zlib.NewReader error") } defer z.Close() p, err := ioutil.ReadAll(z) if err != nil { return errors.Wrap(err, "ioutil.ReadAll error") } objPos := 0 objGen := 0 i := startObject // Decode result with paeth algorithm var result []byte b = bytes.NewReader(p) firstFieldSize := v.Dictionary["/W"].Array[0].Int middleFieldSize := v.Dictionary["/W"].Array[1].Int lastFieldSize := v.Dictionary["/W"].Array[2].Int fieldSize := firstFieldSize + middleFieldSize + lastFieldSize if paethDecode { fieldSize++ } prevRow := make([]byte, fieldSize) for { result = make([]byte, fieldSize) _, err := io.ReadFull(b, result) if err != nil { if err == io.EOF { break } else { return errors.Wrap(err, "io.ReadFull error") } } if paethDecode { filterPaeth(result, prevRow, fieldSize) copy(prevRow, result) } objectData := make([]byte, fieldSize) if paethDecode { copy(objectData, result[1:fieldSize]) } else { copy(objectData, result[0:fieldSize]) } if objectData[0] == 1 { // Regular objects b := make([]byte, 4) copy(b[4-middleFieldSize:], objectData[1:1+middleFieldSize]) objPos = int(binary.BigEndian.Uint32(b)) objGen = int(objectData[firstFieldSize+middleFieldSize]) // Append map[int]int this.xref[i] = make(map[int]int, 1) // Set object id, generation, and position this.xref[i][objGen] = objPos } else if objectData[0] == 2 { // Compressed objects b := make([]byte, 4) copy(b[4-middleFieldSize:], objectData[1:1+middleFieldSize]) objId := int(binary.BigEndian.Uint32(b)) objIdx := int(objectData[firstFieldSize+middleFieldSize]) // object id (i) is located in StmObj (objId) at index (objIdx) this.xrefStream[i] = [2]int{objId, objIdx} } i++ } // Check for previous xref stream if prevXref > 0 { // Set xrefPos to /Prev xref this.xrefPos = prevXref // Read preivous xref xrefErr := this.readXref() if xrefErr != nil { return errors.Wrap(xrefErr, "Failed to read prev xref") } } } } return nil } return errors.New("Expected xref to start with 'xref'. Got: " + t) } for { // Next value will be the starting object id (usually 0, but not always) or the trailer t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } // Check for trailer if t == "trailer" { break } // Convert token to int startObject, err := strconv.Atoi(t) if err != nil { return errors.Wrap(err, "Failed to convert start object to integer: "+t) } // Determine how many objects there are t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } // Convert token to int numObject, err := strconv.Atoi(t) if err != nil { return errors.Wrap(err, "Failed to convert num object to integer: "+t) } // For all objects in xref, read object position, object generation, and status (free or new) for i := startObject; i < startObject+numObject; i++ { t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } // Get object position as int objPos, err := strconv.Atoi(t) if err != nil { return errors.Wrap(err, "Failed to convert object position to integer: "+t) } t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } // Get object generation as int objGen, err := strconv.Atoi(t) if err != nil { return errors.Wrap(err, "Failed to convert object generation to integer: "+t) } // Get object status (free or new) objStatus, err := this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } if objStatus != "f" && objStatus != "n" { return errors.New("Expected objStatus to be 'n' or 'f', got: " + objStatus) } // Append map[int]int this.xref[i] = make(map[int]int, 1) // Set object id, generation, and position this.xref[i][objGen] = objPos } } // Read trailer dictionary t, err = this.readToken(r) if err != nil { return errors.Wrap(err, "Failed to read token") } trailer, err := this.readValue(r, t) if err != nil { return errors.Wrap(err, "Failed to read value for token: "+t) } // If /Root is set, then set trailer object so that /Root can be read later if _, ok := trailer.Dictionary["/Root"]; ok { this.trailer = trailer } // If a /Prev xref trailer is specified, parse that if tr, ok := trailer.Dictionary["/Prev"]; ok { // Resolve parent xref table this.xrefPos = tr.Int return this.readXref() } return nil } // Read root (catalog object) func (this *PdfReader) readRoot() error { var err error rootObjSpec := this.trailer.Dictionary["/Root"] // Read root (catalog) this.catalog, err = this.resolveObject(rootObjSpec) if err != nil { return errors.Wrap(err, "Failed to resolve root object") } return nil } // Read all pages in PDF func (this *PdfReader) readPages() error { var err error // resolve_pages_dict pagesDict, err := this.resolveObject(this.catalog.Value.Dictionary["/Pages"]) if err != nil { return errors.Wrap(err, "Failed to resolve pages object") } // This will normally return itself kids, err := this.resolveObject(pagesDict.Value.Dictionary["/Kids"]) if err != nil { return errors.Wrap(err, "Failed to resolve kids object") } // Allocate pages this.pages = make([]*PdfValue, len(kids.Array)) // Loop through pages and add to result for i := 0; i < len(kids.Array); i++ { page, err := this.resolveObject(kids.Array[i]) if err != nil { return errors.Wrap(err, "Failed to resolve kid object") } // Set page this.pages[i] = page } return nil } // Get references to page resources for a given page number func (this *PdfReader) getPageResources(pageno int) (*PdfValue, error) { var err error // Check to make sure page exists in pages slice if len(this.pages) < pageno { return nil, errors.New(fmt.Sprintf("Page %d does not exist!!", pageno)) } // Resolve page object page, err := this.resolveObject(this.pages[pageno-1]) if err != nil { return nil, errors.Wrap(err, "Failed to resolve page object") } // Check to see if /Resources exists in Dictionary if _, ok := page.Value.Dictionary["/Resources"]; ok { // Resolve /Resources object res, err := this.resolveObject(page.Value.Dictionary["/Resources"]) if err != nil { return nil, errors.Wrap(err, "Failed to resolve resources object") } // If type is PDF_TYPE_OBJECT, return its Value if res.Type == PDF_TYPE_OBJECT { return res.Value, nil } // Otherwise, returned the resolved object return res, nil } else { // If /Resources does not exist, check to see if /Parent exists and return that if _, ok := page.Value.Dictionary["/Parent"]; ok { // Resolve parent object res, err := this.resolveObject(page.Value.Dictionary["/Parent"]) if err != nil { return nil, errors.Wrap(err, "Failed to resolve parent object") } // If /Parent object type is PDF_TYPE_OBJECT, return its Value if res.Type == PDF_TYPE_OBJECT { return res.Value, nil } // Otherwise, return the resolved parent object return res, nil } } // Return an empty PdfValue if we got here // TODO: Improve error handling return &PdfValue{}, nil } // Get page content and return a slice of PdfValue objects func (this *PdfReader) getPageContent(objSpec *PdfValue) ([]*PdfValue, error) { var err error var content *PdfValue // Allocate slice contents := make([]*PdfValue, 0) if objSpec.Type == PDF_TYPE_OBJREF { // If objSpec is an object reference, resolve the object and append it to contents content, err = this.resolveObject(objSpec) if err != nil { return nil, errors.Wrap(err, "Failed to resolve object") } contents = append(contents, content) } else if objSpec.Type == PDF_TYPE_ARRAY { // If objSpec is an array, loop through the array and recursively get page content and append to contents for i := 0; i < len(objSpec.Array); i++ { tmpContents, err := this.getPageContent(objSpec.Array[i]) if err != nil { return nil, errors.Wrap(err, "Failed to get page content") } for j := 0; j < len(tmpContents); j++ { contents = append(contents, tmpContents[j]) } } } return contents, nil } // Get content (i.e. PDF drawing instructions) func (this *PdfReader) getContent(pageno int) (string, error) { var err error var contents []*PdfValue // Check to make sure page exists in pages slice if len(this.pages) < pageno { return "", errors.New(fmt.Sprintf("Page %d does not exist.", pageno)) } // Get page page := this.pages[pageno-1] // FIXME: This could be slow, converting []byte to string and appending many times buffer := "" // Check to make sure /Contents exists in page dictionary if _, ok := page.Value.Dictionary["/Contents"]; ok { // Get an array of page content contents, err = this.getPageContent(page.Value.Dictionary["/Contents"]) if err != nil { return "", errors.Wrap(err, "Failed to get page content") } for i := 0; i < len(contents); i++ { // Decode content if one or more /Filter is specified. // Most common filter is FlateDecode which can be uncompressed with zlib tmpBuffer, err := this.rebuildContentStream(contents[i]) if err != nil { return "", errors.Wrap(err, "Failed to rebuild content stream") } // FIXME: This is probably slow buffer += string(tmpBuffer) } } return buffer, nil } // Rebuild content stream // This will decode content if one or more /Filter (such as FlateDecode) is specified. // If there are multiple filters, they will be decoded in the order in which they were specified. func (this *PdfReader) rebuildContentStream(content *PdfValue) ([]byte, error) { var err error var tmpFilter *PdfValue // Allocate slice of PdfValue filters := make([]*PdfValue, 0) // If content has a /Filter, append it to filters slice if _, ok := content.Value.Dictionary["/Filter"]; ok { filter := content.Value.Dictionary["/Filter"] // If filter type is a reference, resolve it if filter.Type == PDF_TYPE_OBJREF { tmpFilter, err = this.resolveObject(filter) if err != nil { return nil, errors.Wrap(err, "Failed to resolve object") } filter = tmpFilter.Value } if filter.Type == PDF_TYPE_TOKEN { // If filter type is a token (e.g. FlateDecode), appent it to filters slice filters = append(filters, filter) } else if filter.Type == PDF_TYPE_ARRAY { // If filter type is an array, then there are multiple filters. Set filters variable to array value. filters = filter.Array } } // Set stream variable to content bytes stream := content.Stream.Bytes // Loop through filters and apply each filter to stream for i := 0; i < len(filters); i++ { switch filters[i].Token { case "/FlateDecode": // Uncompress zlib compressed data var out bytes.Buffer zlibReader, _ := zlib.NewReader(bytes.NewBuffer(stream)) defer zlibReader.Close() io.Copy(&out, zlibReader) // Set stream to uncompressed data stream = out.Bytes() default: return nil, errors.New("Unspported filter: " + filters[i].Token) } } return stream, nil } func (this *PdfReader) getAllPageBoxes(k float64) (map[int]map[string]map[string]float64, error) { var err error // Allocate result with the number of available boxes result := make(map[int]map[string]map[string]float64, len(this.pages)) for i := 1; i <= len(this.pages); i++ { result[i], err = this.getPageBoxes(i, k) if result[i] == nil { return nil, errors.Wrap(err, "Unable to get page box") } } return result, nil } // Get all page box data func (this *PdfReader) getPageBoxes(pageno int, k float64) (map[string]map[string]float64, error) { var err error // Allocate result with the number of available boxes result := make(map[string]map[string]float64, len(this.availableBoxes)) // Check to make sure page exists in pages slice if len(this.pages) < pageno { return nil, errors.New(fmt.Sprintf("Page %d does not exist?", pageno)) } // Resolve page object page, err := this.resolveObject(this.pages[pageno-1]) if err != nil { return nil, errors.New("Failed to resolve page object") } // Loop through available boxes and add to result for i := 0; i < len(this.availableBoxes); i++ { box, err := this.getPageBox(page, this.availableBoxes[i], k) if err != nil { return nil, errors.New("Failed to get page box") } result[this.availableBoxes[i]] = box } return result, nil } // Get a specific page box value (e.g. MediaBox) and return its values func (this *PdfReader) getPageBox(page *PdfValue, box_index string, k float64) (map[string]float64, error) { var err error var tmpBox *PdfValue // Allocate 8 fields in result result := make(map[string]float64, 8) // Check to make sure box_index (e.g. MediaBox) exists in page dictionary if _, ok := page.Value.Dictionary[box_index]; ok { box := page.Value.Dictionary[box_index] // If the box type is a reference, resolve it if box.Type == PDF_TYPE_OBJREF { tmpBox, err = this.resolveObject(box) if err != nil { return nil, errors.New("Failed to resolve object") } box = tmpBox.Value } if box.Type == PDF_TYPE_ARRAY { // If the box type is an array, calculate scaled value based on k result["x"] = box.Array[0].Real / k result["y"] = box.Array[1].Real / k result["w"] = math.Abs(box.Array[0].Real-box.Array[2].Real) / k result["h"] = math.Abs(box.Array[1].Real-box.Array[3].Real) / k result["llx"] = math.Min(box.Array[0].Real, box.Array[2].Real) result["lly"] = math.Min(box.Array[1].Real, box.Array[3].Real) result["urx"] = math.Max(box.Array[0].Real, box.Array[2].Real) result["ury"] = math.Max(box.Array[1].Real, box.Array[3].Real) } else { // TODO: Improve error handling return nil, errors.New("Could not get page box") } } else if _, ok := page.Value.Dictionary["/Parent"]; ok { parentObj, err := this.resolveObject(page.Value.Dictionary["/Parent"]) if err != nil { return nil, errors.Wrap(err, "Could not resolve parent object") } // If the page box is inherited from /Parent, recursively return page box of parent return this.getPageBox(parentObj, box_index, k) } return result, nil } // Get page rotation for a page number func (this *PdfReader) getPageRotation(pageno int) (*PdfValue, error) { // Check to make sure page exists in pages slice if len(this.pages) < pageno { return nil, errors.New(fmt.Sprintf("Page %d does not exist!!!!", pageno)) } return this._getPageRotation(this.pages[pageno-1]) } // Get page rotation for a page object spec func (this *PdfReader) _getPageRotation(page *PdfValue) (*PdfValue, error) { var err error // Resolve page object page, err = this.resolveObject(page) if err != nil { return nil, errors.New("Failed to resolve page object") } // Check to make sure /Rotate exists in page dictionary if _, ok := page.Value.Dictionary["/Rotate"]; ok { res, err := this.resolveObject(page.Value.Dictionary["/Rotate"]) if err != nil { return nil, errors.New("Failed to resolve rotate object") } // If the type is PDF_TYPE_OBJECT, return its value if res.Type == PDF_TYPE_OBJECT { return res.Value, nil } // Otherwise, return the object return res, nil } else { // Check to see if parent has a rotation if _, ok := page.Value.Dictionary["/Parent"]; ok { // Recursively return /Parent page rotation res, err := this._getPageRotation(page.Value.Dictionary["/Parent"]) if err != nil { return nil, errors.Wrap(err, "Failed to get page rotation for parent") } // If the type is PDF_TYPE_OBJECT, return its value if res.Type == PDF_TYPE_OBJECT { return res.Value, nil } // Otherwise, return the object return res, nil } } return &PdfValue{Int: 0}, nil } func (this *PdfReader) read() error { var err error // Find xref position err = this.findXref() if err != nil { return errors.Wrap(err, "Failed to find xref position") } // Parse xref table err = this.readXref() if err != nil { return errors.Wrap(err, "Failed to read xref table") } // Read catalog err = this.readRoot() if err != nil { return errors.Wrap(err, "Failed to read root") } // Read pages err = this.readPages() if err != nil { return errors.Wrap(err, "Failed to to read pages") } return nil } ================================================ FILE: vendor/github.com/phpdave11/gofpdi/writer.go ================================================ package gofpdi import ( "bufio" "bytes" "compress/zlib" "crypto/sha1" "encoding/hex" "fmt" "github.com/pkg/errors" "math" "os" ) type PdfWriter struct { f *os.File w *bufio.Writer r *PdfReader k float64 tpls []*PdfTemplate m int n int offsets map[int]int offset int result map[int]string // Keep track of which objects have already been written obj_stack map[int]*PdfValue don_obj_stack map[int]*PdfValue written_objs map[*PdfObjectId][]byte written_obj_pos map[*PdfObjectId]map[int]string current_obj *PdfObject current_obj_id int tpl_id_offset int use_hash bool } type PdfObjectId struct { id int hash string } type PdfObject struct { id *PdfObjectId buffer *bytes.Buffer } func (this *PdfWriter) SetTplIdOffset(n int) { this.tpl_id_offset = n } func (this *PdfWriter) Init() { this.k = 1 this.obj_stack = make(map[int]*PdfValue, 0) this.don_obj_stack = make(map[int]*PdfValue, 0) this.tpls = make([]*PdfTemplate, 0) this.written_objs = make(map[*PdfObjectId][]byte, 0) this.written_obj_pos = make(map[*PdfObjectId]map[int]string, 0) this.current_obj = new(PdfObject) } func (this *PdfWriter) SetUseHash(b bool) { this.use_hash = b } func (this *PdfWriter) SetNextObjectID(id int) { this.n = id - 1 } func NewPdfWriter(filename string) (*PdfWriter, error) { writer := &PdfWriter{} writer.Init() if filename != "" { var err error f, err := os.Create(filename) if err != nil { return nil, errors.Wrap(err, "Unable to create filename: "+filename) } writer.f = f writer.w = bufio.NewWriter(f) } return writer, nil } // Done with parsing. Now, create templates. type PdfTemplate struct { Id int Reader *PdfReader Resources *PdfValue Buffer string Box map[string]float64 Boxes map[string]map[string]float64 X float64 Y float64 W float64 H float64 Rotation int N int } func (this *PdfWriter) GetImportedObjects() map[*PdfObjectId][]byte { return this.written_objs } // For each object (uniquely identified by a sha1 hash), return the positions // of each hash within the object, to be replaced with pdf object ids (integers) func (this *PdfWriter) GetImportedObjHashPos() map[*PdfObjectId]map[int]string { return this.written_obj_pos } func (this *PdfWriter) ClearImportedObjects() { this.written_objs = make(map[*PdfObjectId][]byte, 0) } // Create a PdfTemplate object from a page number (e.g. 1) and a boxName (e.g. MediaBox) func (this *PdfWriter) ImportPage(reader *PdfReader, pageno int, boxName string) (int, error) { var err error // Set default scale to 1 this.k = 1 // Get all page boxes pageBoxes, err := reader.getPageBoxes(1, this.k) if err != nil { return -1, errors.Wrap(err, "Failed to get page boxes") } // If requested box name does not exist for this page, use an alternate box if _, ok := pageBoxes[boxName]; !ok { if boxName == "/BleedBox" || boxName == "/TrimBox" || boxName == "ArtBox" { boxName = "/CropBox" } else if boxName == "/CropBox" { boxName = "/MediaBox" } } // If the requested box name or an alternate box name cannot be found, trigger an error // TODO: Improve error handling if _, ok := pageBoxes[boxName]; !ok { return -1, errors.New("Box not found: " + boxName) } pageResources, err := reader.getPageResources(pageno) if err != nil { return -1, errors.Wrap(err, "Failed to get page resources") } content, err := reader.getContent(pageno) if err != nil { return -1, errors.Wrap(err, "Failed to get content") } // Set template values tpl := &PdfTemplate{} tpl.Reader = reader tpl.Resources = pageResources tpl.Buffer = content tpl.Box = pageBoxes[boxName] tpl.Boxes = pageBoxes tpl.X = 0 tpl.Y = 0 tpl.W = tpl.Box["w"] tpl.H = tpl.Box["h"] // Set template rotation rotation, err := reader.getPageRotation(pageno) if err != nil { return -1, errors.Wrap(err, "Failed to get page rotation") } angle := rotation.Int % 360 // Normalize angle if angle != 0 { steps := angle / 90 w := tpl.W h := tpl.H if steps%2 == 0 { tpl.W = w tpl.H = h } else { tpl.W = h tpl.H = w } if angle < 0 { angle += 360 } tpl.Rotation = angle * -1 } this.tpls = append(this.tpls, tpl) // Return last template id return len(this.tpls) - 1, nil } // Create a new object and keep track of the offset for the xref table func (this *PdfWriter) newObj(objId int, onlyNewObj bool) { if objId < 0 { this.n++ objId = this.n } if !onlyNewObj { // set current object id integer this.current_obj_id = objId // Create new PdfObject and PdfObjectId this.current_obj = new(PdfObject) this.current_obj.buffer = new(bytes.Buffer) this.current_obj.id = new(PdfObjectId) this.current_obj.id.id = objId this.current_obj.id.hash = this.shaOfInt(objId) this.written_obj_pos[this.current_obj.id] = make(map[int]string, 0) } } func (this *PdfWriter) endObj() { this.out("endobj") this.written_objs[this.current_obj.id] = this.current_obj.buffer.Bytes() this.current_obj_id = -1 } func (this *PdfWriter) shaOfInt(i int) string { hasher := sha1.New() hasher.Write([]byte(fmt.Sprintf("%s-%s", i, this.r.sourceFile))) sha := hex.EncodeToString(hasher.Sum(nil)) return sha } func (this *PdfWriter) outObjRef(objId int) { sha := this.shaOfInt(objId) // Keep track of object hash and position - to be replaced with actual object id (integer) this.written_obj_pos[this.current_obj.id][this.current_obj.buffer.Len()] = sha if this.use_hash { this.current_obj.buffer.WriteString(sha) } else { this.current_obj.buffer.WriteString(fmt.Sprintf("%d", objId)) } this.current_obj.buffer.WriteString(" 0 R ") } // Output PDF data with a newline func (this *PdfWriter) out(s string) { this.current_obj.buffer.WriteString(s) this.current_obj.buffer.WriteString("\n") } // Output PDF data func (this *PdfWriter) straightOut(s string) { this.current_obj.buffer.WriteString(s) } // Output a PdfValue func (this *PdfWriter) writeValue(value *PdfValue) { switch value.Type { case PDF_TYPE_TOKEN: this.straightOut(value.Token + " ") break case PDF_TYPE_NUMERIC: this.straightOut(fmt.Sprintf("%d", value.Int) + " ") break case PDF_TYPE_REAL: this.straightOut(fmt.Sprintf("%F", value.Real) + " ") break case PDF_TYPE_ARRAY: this.straightOut("[") for i := 0; i < len(value.Array); i++ { this.writeValue(value.Array[i]) } this.out("]") break case PDF_TYPE_DICTIONARY: this.straightOut("<<") for k, v := range value.Dictionary { this.straightOut(k + " ") this.writeValue(v) } this.straightOut(">>") break case PDF_TYPE_OBJREF: // An indirect object reference. Fill the object stack if needed. // Check to see if object already exists on the don_obj_stack. if _, ok := this.don_obj_stack[value.Id]; !ok { this.newObj(-1, true) this.obj_stack[value.Id] = &PdfValue{Type: PDF_TYPE_OBJREF, Gen: value.Gen, Id: value.Id, NewId: this.n} this.don_obj_stack[value.Id] = &PdfValue{Type: PDF_TYPE_OBJREF, Gen: value.Gen, Id: value.Id, NewId: this.n} } // Get object ID from don_obj_stack objId := this.don_obj_stack[value.Id].NewId this.outObjRef(objId) //this.out(fmt.Sprintf("%d 0 R", objId)) break case PDF_TYPE_STRING: // A string this.straightOut("(" + value.String + ")") break case PDF_TYPE_STREAM: // A stream. First, output the stream dictionary, then the stream data itself. this.writeValue(value.Value) this.out("stream") this.out(string(value.Stream.Bytes)) this.out("endstream") break case PDF_TYPE_HEX: this.straightOut("<" + value.String + ">") break case PDF_TYPE_BOOLEAN: if value.Bool { this.straightOut("true") } else { this.straightOut("false") } break case PDF_TYPE_NULL: // The null object this.straightOut("null ") break } } // Output Form XObjects (1 for each template) // returns a map of template names (e.g. /GOFPDITPL1) to PdfObjectId func (this *PdfWriter) PutFormXobjects(reader *PdfReader) (map[string]*PdfObjectId, error) { // Set current reader this.r = reader var err error var result = make(map[string]*PdfObjectId, 0) compress := true filter := "" if compress { filter = "/Filter /FlateDecode " } for i := 0; i < len(this.tpls); i++ { tpl := this.tpls[i] if tpl == nil { return nil, errors.New("Template is nil") } var p string if compress { var b bytes.Buffer w := zlib.NewWriter(&b) w.Write([]byte(tpl.Buffer)) w.Close() p = b.String() } else { p = tpl.Buffer } // Create new PDF object this.newObj(-1, false) cN := this.n // remember current "n" tpl.N = this.n // Return xobject form name and object position pdfObjId := new(PdfObjectId) pdfObjId.id = cN pdfObjId.hash = this.shaOfInt(cN) result[fmt.Sprintf("/GOFPDITPL%d", i+this.tpl_id_offset)] = pdfObjId this.out("<<" + filter + "/Type /XObject") this.out("/Subtype /Form") this.out("/FormType 1") this.out(fmt.Sprintf("/BBox [%.2F %.2F %.2F %.2F]", tpl.Box["llx"]*this.k, tpl.Box["lly"]*this.k, (tpl.Box["urx"]+tpl.X)*this.k, (tpl.Box["ury"]-tpl.Y)*this.k)) var c, s, tx, ty float64 c = 1 // Handle rotated pages if tpl.Box != nil { tx = -tpl.Box["llx"] ty = -tpl.Box["lly"] if tpl.Rotation != 0 { angle := float64(tpl.Rotation) * math.Pi / 180.0 c = math.Cos(float64(angle)) s = math.Sin(float64(angle)) switch tpl.Rotation { case -90: tx = -tpl.Box["lly"] ty = tpl.Box["urx"] break case -180: tx = tpl.Box["urx"] ty = tpl.Box["ury"] break case -270: tx = tpl.Box["ury"] ty = -tpl.Box["llx"] } } } else { tx = -tpl.Box["x"] * 2 ty = tpl.Box["y"] * 2 } tx *= this.k ty *= this.k if c != 1 || s != 0 || tx != 0 || ty != 0 { this.out(fmt.Sprintf("/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]", c, s, -s, c, tx, ty)) } // Now write resources this.out("/Resources ") if tpl.Resources != nil { this.writeValue(tpl.Resources) // "n" will be changed } else { return nil, errors.New("Template resources are empty") } nN := this.n // remember new "n" this.n = cN // reset to current "n" this.out("/Length " + fmt.Sprintf("%d", len(p)) + " >>") this.out("stream") this.out(p) this.out("endstream") this.endObj() this.n = nN // reset to new "n" // Put imported objects, starting with the ones from the XObject's Resources, // then from dependencies of those resources). err = this.putImportedObjects(reader) if err != nil { return nil, errors.Wrap(err, "Failed to put imported objects") } } return result, nil } func (this *PdfWriter) putImportedObjects(reader *PdfReader) error { var err error var nObj *PdfValue // obj_stack will have new items added to it in the inner loop, so do another loop to check for extras // TODO make the order of this the same every time for { atLeastOne := false // FIXME: How to determine number of objects before this loop? for i := 0; i < 9999; i++ { k := i v := this.obj_stack[i] if v == nil { continue } atLeastOne = true nObj, err = reader.resolveObject(v) if err != nil { return errors.Wrap(err, "Unable to resolve object") } // New object with "NewId" field this.newObj(v.NewId, false) if nObj.Type == PDF_TYPE_STREAM { this.writeValue(nObj) } else { this.writeValue(nObj.Value) } this.endObj() // Remove from stack this.obj_stack[k] = nil } if !atLeastOne { break } } return nil } // Get the calculated size of a template // If one size is given, this method calculates the other one func (this *PdfWriter) getTemplateSize(tplid int, _w float64, _h float64) map[string]float64 { result := make(map[string]float64, 2) tpl := this.tpls[tplid] w := tpl.W h := tpl.H if _w == 0 && _h == 0 { _w = w _h = h } if _w == 0 { _w = _h * w / h } if _h == 0 { _h = _w * h / w } result["w"] = _w result["h"] = _h return result } func (this *PdfWriter) UseTemplate(tplid int, _x float64, _y float64, _w float64, _h float64) (string, float64, float64, float64, float64) { tpl := this.tpls[tplid] w := tpl.W h := tpl.H _x += tpl.X _y += tpl.Y wh := this.getTemplateSize(0, _w, _h) _w = wh["w"] _h = wh["h"] tData := make(map[string]float64, 9) tData["x"] = 0.0 tData["y"] = 0.0 tData["w"] = _w tData["h"] = _h tData["scaleX"] = (_w / w) tData["scaleY"] = (_h / h) tData["tx"] = _x tData["ty"] = (0 - _y - _h) tData["lty"] = (0 - _y - _h) - (0-h)*(_h/h) return fmt.Sprintf("/GOFPDITPL%d", tplid+this.tpl_id_offset), tData["scaleX"], tData["scaleY"], tData["tx"] * this.k, tData["ty"] * this.k } ================================================ FILE: vendor/github.com/pkg/errors/.gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof ================================================ FILE: vendor/github.com/pkg/errors/.travis.yml ================================================ language: go go_import_path: github.com/pkg/errors go: - 1.4.x - 1.5.x - 1.6.x - 1.7.x - 1.8.x - 1.9.x - 1.10.x - 1.11.x - tip script: - go test -v ./... ================================================ FILE: vendor/github.com/pkg/errors/LICENSE ================================================ Copyright (c) 2015, Dave Cheney All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: vendor/github.com/pkg/errors/README.md ================================================ # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) Package errors provides simple error handling primitives. `go get github.com/pkg/errors` The traditional error handling idiom in Go is roughly akin to ```go if err != nil { return err } ``` which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. ## Adding context to an error The errors.Wrap function returns a new error that adds context to the original error. For example ```go _, err := ioutil.ReadAll(r) if err != nil { return errors.Wrap(err, "read failed") } ``` ## Retrieving the cause of an error Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. ```go type causer interface { Cause() error } ``` `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: ```go switch err := errors.Cause(err).(type) { case *MyError: // handle specifically default: // unknown error } ``` [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). ## Contributing We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. Before proposing a change, please discuss your change by raising an issue. ## License BSD-2-Clause ================================================ FILE: vendor/github.com/pkg/errors/appveyor.yml ================================================ version: build-{build}.{branch} clone_folder: C:\gopath\src\github.com\pkg\errors shallow_clone: true # for startup speed environment: GOPATH: C:\gopath platform: - x64 # http://www.appveyor.com/docs/installed-software install: # some helpful output for debugging builds - go version - go env # pre-installed MinGW at C:\MinGW is 32bit only # but MSYS2 at C:\msys64 has mingw64 - set PATH=C:\msys64\mingw64\bin;%PATH% - gcc --version - g++ --version build_script: - go install -v ./... test_script: - set PATH=C:\gopath\bin;%PATH% - go test -v ./... #artifacts: # - path: '%GOPATH%\bin\*.exe' deploy: off ================================================ FILE: vendor/github.com/pkg/errors/errors.go ================================================ // Package errors provides simple error handling primitives. // // The traditional error handling idiom in Go is roughly akin to // // if err != nil { // return err // } // // which when applied recursively up the call stack results in error reports // without context or debugging information. The errors package allows // programmers to add context to the failure path in their code in a way // that does not destroy the original value of the error. // // Adding context to an error // // The errors.Wrap function returns a new error that adds context to the // original error by recording a stack trace at the point Wrap is called, // together with the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { // return errors.Wrap(err, "read failed") // } // // If additional control is required, the errors.WithStack and // errors.WithMessage functions destructure errors.Wrap into its component // operations: annotating an error with a stack trace and with a message, // respectively. // // Retrieving the cause of an error // // Using errors.Wrap constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary // to reverse the operation of errors.Wrap to retrieve the original error // for inspection. Any error value which implements this interface // // type causer interface { // Cause() error // } // // can be inspected by errors.Cause. errors.Cause will recursively retrieve // the topmost error that does not implement causer, which is assumed to be // the original cause. For example: // // switch err := errors.Cause(err).(type) { // case *MyError: // // handle specifically // default: // // unknown error // } // // Although the causer interface is not exported by this package, it is // considered a part of its stable public interface. // // Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can // be formatted by the fmt package. The following verbs are supported: // // %s print the error. If the error has a Cause it will be // printed recursively. // %v see %s // %+v extended format. Each Frame of the error's StackTrace will // be printed in detail. // // Retrieving the stack trace of an error or wrapper // // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are // invoked. This information can be retrieved with the following interface: // // type stackTracer interface { // StackTrace() errors.StackTrace // } // // The returned errors.StackTrace type is defined as // // type StackTrace []Frame // // The Frame type represents a call site in the stack trace. Frame supports // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { // fmt.Printf("%+s:%d", f) // } // } // // Although the stackTracer interface is not exported by this package, it is // considered a part of its stable public interface. // // See the documentation for Frame.Format for more details. package errors import ( "fmt" "io" ) // New returns an error with the supplied message. // New also records the stack trace at the point it was called. func New(message string) error { return &fundamental{ msg: message, stack: callers(), } } // Errorf formats according to a format specifier and returns the string // as a value that satisfies error. // Errorf also records the stack trace at the point it was called. func Errorf(format string, args ...interface{}) error { return &fundamental{ msg: fmt.Sprintf(format, args...), stack: callers(), } } // fundamental is an error that has a message and a stack, but no caller. type fundamental struct { msg string *stack } func (f *fundamental) Error() string { return f.msg } func (f *fundamental) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { io.WriteString(s, f.msg) f.stack.Format(s, verb) return } fallthrough case 's': io.WriteString(s, f.msg) case 'q': fmt.Fprintf(s, "%q", f.msg) } } // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. func WithStack(err error) error { if err == nil { return nil } return &withStack{ err, callers(), } } type withStack struct { error *stack } func (w *withStack) Cause() error { return w.error } func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { fmt.Fprintf(s, "%+v", w.Cause()) w.stack.Format(s, verb) return } fallthrough case 's': io.WriteString(s, w.Error()) case 'q': fmt.Fprintf(s, "%q", w.Error()) } } // Wrap returns an error annotating err with a stack trace // at the point Wrap is called, and the supplied message. // If err is nil, Wrap returns nil. func Wrap(err error, message string) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: message, } return &withStack{ err, callers(), } } // Wrapf returns an error annotating err with a stack trace // at the point Wrapf is called, and the format specifier. // If err is nil, Wrapf returns nil. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: fmt.Sprintf(format, args...), } return &withStack{ err, callers(), } } // WithMessage annotates err with a new message. // If err is nil, WithMessage returns nil. func WithMessage(err error, message string) error { if err == nil { return nil } return &withMessage{ cause: err, msg: message, } } // WithMessagef annotates err with the format specifier. // If err is nil, WithMessagef returns nil. func WithMessagef(err error, format string, args ...interface{}) error { if err == nil { return nil } return &withMessage{ cause: err, msg: fmt.Sprintf(format, args...), } } type withMessage struct { cause error msg string } func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { fmt.Fprintf(s, "%+v\n", w.Cause()) io.WriteString(s, w.msg) return } fallthrough case 's', 'q': io.WriteString(s, w.Error()) } } // Cause returns the underlying cause of the error, if possible. // An error value has a cause if it implements the following // interface: // // type causer interface { // Cause() error // } // // If the error does not implement Cause, the original error will // be returned. If the error is nil, nil will be returned without further // investigation. func Cause(err error) error { type causer interface { Cause() error } for err != nil { cause, ok := err.(causer) if !ok { break } err = cause.Cause() } return err } ================================================ FILE: vendor/github.com/pkg/errors/stack.go ================================================ package errors import ( "fmt" "io" "path" "runtime" "strings" ) // Frame represents a program counter inside a stack frame. type Frame uintptr // pc returns the program counter for this frame; // multiple frames may have the same PC value. func (f Frame) pc() uintptr { return uintptr(f) - 1 } // file returns the full path to the file that contains the // function for this Frame's pc. func (f Frame) file() string { fn := runtime.FuncForPC(f.pc()) if fn == nil { return "unknown" } file, _ := fn.FileLine(f.pc()) return file } // line returns the line number of source code of the // function for this Frame's pc. func (f Frame) line() int { fn := runtime.FuncForPC(f.pc()) if fn == nil { return 0 } _, line := fn.FileLine(f.pc()) return line } // Format formats the frame according to the fmt.Formatter interface. // // %s source file // %d source line // %n function name // %v equivalent to %s:%d // // Format accepts flags that alter the printing of some verbs, as follows: // // %+s function name and path of source file relative to the compile time // GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { switch verb { case 's': switch { case s.Flag('+'): pc := f.pc() fn := runtime.FuncForPC(pc) if fn == nil { io.WriteString(s, "unknown") } else { file, _ := fn.FileLine(pc) fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) } default: io.WriteString(s, path.Base(f.file())) } case 'd': fmt.Fprintf(s, "%d", f.line()) case 'n': name := runtime.FuncForPC(f.pc()).Name() io.WriteString(s, funcname(name)) case 'v': f.Format(s, 's') io.WriteString(s, ":") f.Format(s, 'd') } } // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame // Format formats the stack of Frames according to the fmt.Formatter interface. // // %s lists source files for each Frame in the stack // %v lists the source file and line number for each Frame in the stack // // Format accepts flags that alter the printing of some verbs, as follows: // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': switch { case s.Flag('+'): for _, f := range st { fmt.Fprintf(s, "\n%+v", f) } case s.Flag('#'): fmt.Fprintf(s, "%#v", []Frame(st)) default: fmt.Fprintf(s, "%v", []Frame(st)) } case 's': fmt.Fprintf(s, "%s", []Frame(st)) } } // stack represents a stack of program counters. type stack []uintptr func (s *stack) Format(st fmt.State, verb rune) { switch verb { case 'v': switch { case st.Flag('+'): for _, pc := range *s { f := Frame(pc) fmt.Fprintf(st, "\n%+v", f) } } } } func (s *stack) StackTrace() StackTrace { f := make([]Frame, len(*s)) for i := 0; i < len(f); i++ { f[i] = Frame((*s)[i]) } return f } func callers() *stack { const depth = 32 var pcs [depth]uintptr n := runtime.Callers(3, pcs[:]) var st stack = pcs[0:n] return &st } // funcname removes the path prefix component of a function's name reported by func.Name(). func funcname(name string) string { i := strings.LastIndex(name, "/") name = name[i+1:] i = strings.Index(name, ".") return name[i+1:] } ================================================ FILE: vendor/github.com/signintech/gopdf/.gitignore ================================================ ##android # built application files *.apk *.ap_ # files for the dex VM *.dex # Java class files *.class # generated files bin/ gen/ pkg/ *.jar # Local configuration file (sdk path, etc) local.properties ##eclipse *.pydevproject .project .metadata bin/** tmp/** tmp/**/* *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ##tmp *~ *.bak proguard-project.txt /project.properties lint.xml .bzr fontmaker/tmp/ fontmaker/fontmaker fontmaker/ttf/ *.orig .vscode test/out/ test/debug.test *.test *.out .DS_Store .idea .scannerwork ================================================ FILE: vendor/github.com/signintech/gopdf/Changelog.md ================================================ ### May 2016 Remove old function - ```GoPdf.AddFont(family string, ifont IFont, zfontpath string)```. - Remove all font map file. ================================================ FILE: vendor/github.com/signintech/gopdf/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 signintech Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/github.com/signintech/gopdf/README.md ================================================ gopdf ==== gopdf is a simple library for generating PDF document written in Go lang. #### Features - Unicode subfont embedding. (Chinese, Japanese, Korean, etc.) - Draw line, oval, rect, curve - Draw image ( jpg, png ) - Set image mask - Password protection - Font [kerning](https://en.wikipedia.org/wiki/Kerning) ## Installation ``` go get -u github.com/signintech/gopdf ``` ### Print text ```go package main import ( "log" "github.com/signintech/gopdf" ) func main() { pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{ PageSize: *gopdf.PageSizeA4 }) pdf.AddPage() err := pdf.AddTTFFont("wts11", "../ttf/wts11.ttf") if err != nil { log.Print(err.Error()) return } err = pdf.SetFont("wts11", "", 14) if err != nil { log.Print(err.Error()) return } pdf.Cell(nil, "您好") pdf.WritePdf("hello.pdf") } ``` ### Image ```go package main import ( "log" "github.com/signintech/gopdf" ) func main() { pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4 }) pdf.AddPage() var err error err = pdf.AddTTFFont("loma", "../ttf/Loma.ttf") if err != nil { log.Print(err.Error()) return } pdf.Image("../imgs/gopher.jpg", 200, 50, nil) //print image err = pdf.SetFont("loma", "", 14) if err != nil { log.Print(err.Error()) return } pdf.SetX(250) //move current location pdf.SetY(200) pdf.Cell(nil, "gopher and gopher") //print text pdf.WritePdf("image.pdf") } ``` ### Links ```go package main import ( "log" "github.com/signintech/gopdf" ) func main() { pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{ PageSize: *gopdf.PageSizeA4 }) //595.28, 841.89 = A4 pdf.AddPage() err := pdf.AddTTFFont("times", "./test/res/times.ttf") if err != nil { log.Print(err.Error()) return } err = pdf.SetFont("times", "", 14) if err != nil { log.Print(err.Error()) return } pdf.SetX(30) pdf.SetY(40) pdf.Text("Link to example.com") pdf.AddExternalLink("http://example.com/", 27.5, 28, 125, 15) pdf.SetX(30) pdf.SetY(70) pdf.Text("Link to second page") pdf.AddInternalLink("anchor", 27.5, 58, 120, 15) pdf.AddPage() pdf.SetX(30) pdf.SetY(100) pdf.SetAnchor("anchor") pdf.Text("Anchor position") pdf.WritePdf("hello.tmp.pdf") } ``` ### Draw line ```go pdf.SetLineWidth(2) pdf.SetLineType("dashed") pdf.Line(10, 30, 585, 30) ``` ### Draw oval ```go pdf.SetLineWidth(1) pdf.Oval(100, 200, 500, 500) ``` ### Draw polygon ```go pdf.SetStrokeColor(255, 0, 0) pdf.SetLineWidth(2) pdf.SetFillColor(0, 255, 0) pdf.Polygon([]gopdf.Point{{X: 10, Y: 30}, {X: 585, Y: 200}, {X: 585, Y: 250}}, "DF") ``` ### Rotation text or image ```go pdf.SetX(100) pdf.SetY(100) pdf.Rotate(270.0, 100.0, 100.0) pdf.Text("Hello...") pdf.RotateReset() //reset ``` ### Set transparency Read about [transparency in pdf](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf) `(page 320, section 11)` ```go // alpha - value of transparency, can be between `0` and `1` // blendMode - default value is `/Normal` - read about [blendMode and kinds of its](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf) `(page 325, section 11.3.5)` transparency := Transparency{ Alpha: 0.5, BlendModeType: "", } pdf.SetTransparency(transparency Transparency) ``` ### Password protection ```go package main import ( "log" "github.com/signintech/gopdf" ) func main() { pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{ PageSize: *gopdf.PageSizeA4, //595.28, 841.89 = A4 Protection: gopdf.PDFProtectionConfig{ UseProtection: true, Permissions: gopdf.PermissionsPrint | gopdf.PermissionsCopy | gopdf.PermissionsModify, OwnerPass: []byte("123456"), UserPass: []byte("123456789")}, }) pdf.AddPage() pdf.AddTTFFont("loma", "../ttf/loma.ttf") pdf.Cell(nil,"Hi") pdf.WritePdf("protect.pdf") } ``` ### Import existing PDF Import existing PDF power by package [gofpdi](https://github.com/phpdave11/gofpdi) created by @phpdave11 (thank you :smile:) ```go package main import ( "github.com/signintech/gopdf" "io" "net/http" "os" ) func main() { var err error // Download a Font fontUrl := "https://github.com/google/fonts/raw/master/ofl/daysone/DaysOne-Regular.ttf" if err = DownloadFile("example-font.ttf", fontUrl); err != nil { panic(err) } // Download a PDF fileUrl := "https://tcpdf.org/files/examples/example_012.pdf" if err = DownloadFile("example-pdf.pdf", fileUrl); err != nil { panic(err) } pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{PageSize: gopdf.Rect{W: 595.28, H: 841.89}}) //595.28, 841.89 = A4 pdf.AddPage() err = pdf.AddTTFFont("daysone", "example-font.ttf") if err != nil { panic(err) } err = pdf.SetFont("daysone", "", 20) if err != nil { panic(err) } // Color the page pdf.SetLineWidth(0.1) pdf.SetFillColor(124, 252, 0) //setup fill color pdf.RectFromUpperLeftWithStyle(50, 100, 400, 600, "FD") pdf.SetFillColor(0, 0, 0) pdf.SetX(50) pdf.SetY(50) pdf.Cell(nil, "Import existing PDF into GoPDF Document") // Import page 1 tpl1 := pdf.ImportPage("example-pdf.pdf", 1, "/MediaBox") // Draw pdf onto page pdf.UseImportedTemplate(tpl1, 50, 100, 400, 0) pdf.WritePdf("example.pdf") } // DownloadFile will download a url to a local file. It's efficient because it will // write as it downloads and not load the whole file into memory. func DownloadFile(filepath string, url string) error { // Get the data resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() // Create the file out, err := os.Create(filepath) if err != nil { return err } defer out.Close() // Write the body to file _, err = io.Copy(out, resp.Body) return err } ``` ### Possible to set [Trim-box](https://wiki.scribus.net/canvas/PDF_Boxes_:_mediabox,_cropbox,_bleedbox,_trimbox,_artbox) ```go package main import ( "log" "github.com/signintech/gopdf" ) func main() { pdf := gopdf.GoPdf{} mm6ToPx := 22.68 // Base trim-box pdf.Start(gopdf.Config{ PageSize: *gopdf.PageSizeA4, //595.28, 841.89 = A4 TrimBox: gopdf.Box{Left: mm6ToPx, Top: mm6ToPx, Right: 595 - mm6ToPx, Bottom: 842 - mm6ToPx}, }) // Page trim-box opt := gopdf.PageOption{ PageSize: gopdf.PageSizeA4, //595.28, 841.89 = A4 TrimBox: &gopdf.Box{Left: mm6ToPx, Top: mm6ToPx, Right: 595 - mm6ToPx, Bottom: 842 - mm6ToPx}, } pdf.AddPageWithOption(opt) if err := pdf.AddTTFFont("wts11", "../ttf/wts11.ttf"); err != nil { log.Print(err.Error()) return } if err := pdf.SetFont("wts11", "", 14); err != nil { log.Print(err.Error()) return } pdf.Cell(nil,"Hi") pdf.WritePdf("hello.pdf") } ``` visit https://github.com/oneplus1000/gopdfsample for more samples. ================================================ FILE: vendor/github.com/signintech/gopdf/box.go ================================================ package gopdf type Box struct { Left, Top, Right, Bottom float64 unitOverride int } // UnitsToPoints converts the box coordinates to Points. When this is called it is assumed the values of the box are in Units func (box *Box) UnitsToPoints(t int) (b *Box) { if box == nil { return } if box.unitOverride != UnitUnset { t = box.unitOverride } b = &Box{ Left: box.Left, Top: box.Top, Right: box.Right, Bottom: box.Bottom, } UnitsToPointsVar(t, &b.Left, &b.Top, &b.Right, &b.Bottom) return } ================================================ FILE: vendor/github.com/signintech/gopdf/buff.go ================================================ package gopdf //Buff for pdf content type Buff struct { position int datas []byte } //Write : write []byte to buffer func (b *Buff) Write(p []byte) (int, error) { for len(b.datas) < b.position+len(p) { b.datas = append(b.datas, 0) } i := 0 max := len(p) for i < max { b.datas[i+b.position] = p[i] i++ } b.position += i return 0, nil } //Len : len of buffer func (b *Buff) Len() int { return len(b.datas) } //Bytes : get bytes func (b *Buff) Bytes() []byte { return b.datas } //Position : get current postion func (b *Buff) Position() int { return b.position } //SetPosition : set current postion func (b *Buff) SetPosition(pos int) { b.position = pos } ================================================ FILE: vendor/github.com/signintech/gopdf/buff_write.go ================================================ package gopdf import "io" //WriteUInt32 writes a 32-bit unsigned integer value to w io.Writer func WriteUInt32(w io.Writer, v uint) error { a := byte(v >> 24) b := byte(v >> 16) c := byte(v >> 8) d := byte(v) _, err := w.Write([]byte{a, b, c, d}) if err != nil { return err } return nil } //WriteUInt16 writes a 16-bit unsigned integer value to w io.Writer func WriteUInt16(w io.Writer, v uint) error { a := byte(v >> 8) b := byte(v) _, err := w.Write([]byte{a, b}) if err != nil { return err } return nil } //WriteTag writes string value to w io.Writer func WriteTag(w io.Writer, tag string) error { b := []byte(tag) _, err := w.Write(b) if err != nil { return err } return nil } //WriteBytes writes []byte value to w io.Writer func WriteBytes(w io.Writer, data []byte, offset int, count int) error { _, err := w.Write(data[offset : offset+count]) if err != nil { return err } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/buffer_pool.go ================================================ package gopdf import ( "bytes" "sync" ) // buffer pool to reduce GC var buffers = sync.Pool{ // New is called when a new instance is needed New: func() interface{} { return new(bytes.Buffer) }, } // GetBuffer fetches a buffer from the pool func GetBuffer() *bytes.Buffer { return buffers.Get().(*bytes.Buffer) } // PutBuffer returns a buffer to the pool func PutBuffer(buf *bytes.Buffer) { buf.Reset() buffers.Put(buf) } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_contact_color.go ================================================ package gopdf import ( "fmt" "io" ) const colorTypeStroke = "RG" const colorTypeFill = "rg" type cacheContentColor struct { colorType string r, g, b uint8 } func (c *cacheContentColor) write(w io.Writer, protection *PDFProtection) error { fmt.Fprintf(w, "%.3f %.3f %.3f %s\n", float64(c.r)/255, float64(c.g)/255, float64(c.b)/255, c.colorType) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_gray.go ================================================ package gopdf import ( "fmt" "io" ) const grayTypeFill = "g" const grayTypeStroke = "G" type cacheContentGray struct { grayType string scale float64 } func (c *cacheContentGray) write(w io.Writer, protection *PDFProtection) error { fmt.Fprintf(w, "%.2f %s\n", c.scale, c.grayType) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_image.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentImage struct { verticalFlip bool horizontalFlip bool index int x float64 y float64 pageHeight float64 rect Rect crop *CropOptions extGStateIndexes []int } func (c *cacheContentImage) write(w io.Writer, protection *PDFProtection) error { width := c.rect.W height := c.rect.H contentStream := "q\n" for _, extGStateIndex := range c.extGStateIndexes { contentStream += fmt.Sprintf("/GS%d gs\n", extGStateIndex) } if c.horizontalFlip || c.verticalFlip { fh := "1" if c.horizontalFlip { fh = "-1" } fv := "1" if c.verticalFlip { fv = "-1" } contentStream += fmt.Sprintf("%s 0 0 %s 0 0 cm\n", fh, fv) } if c.crop != nil { clippingX := c.x if c.horizontalFlip { clippingX = -clippingX - c.crop.Width } clippingY := c.pageHeight - (c.y + c.crop.Height) if c.verticalFlip { clippingY = -clippingY - c.crop.Height } startX := c.x - c.crop.X if c.horizontalFlip { startX = -startX - width } startY := c.pageHeight - (c.y - c.crop.Y + c.rect.H) if c.verticalFlip { startY = -startY - height } contentStream += fmt.Sprintf("%0.2f %0.2f %0.2f %0.2f re W* n\n", clippingX, clippingY, c.crop.Width, c.crop.Height) contentStream += fmt.Sprintf("q %0.2f 0 0 %0.2f %0.2f %0.2f cm /I%d Do Q\n", width, height, startX, startY, c.index+1) } else { x := c.x y := c.pageHeight - (c.y + height) if c.horizontalFlip { x = -x - width } if c.verticalFlip { y = -y - height } contentStream += fmt.Sprintf("q %0.2f 0 0 %0.2f %0.2f %0.2f cm /I%d Do Q\n", width, height, x, y, c.index+1) } contentStream += "Q\n" if _, err := io.WriteString(w, contentStream); err != nil { return err } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_imported_object.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentImportedTemplate struct { pageHeight float64 tplName string scaleX float64 scaleY float64 tX float64 tY float64 } func (c *cacheContentImportedTemplate) write(w io.Writer, protection *PDFProtection) error { c.tY += c.pageHeight fmt.Fprintf(w, "q 0 J 1 w 0 j 0 G 0 g q %.4F 0 0 %.4F %.4F %.4F cm %s Do Q Q\n", c.scaleX, c.scaleY, c.tX, c.tY, c.tplName) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_line.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentLine struct { pageHeight float64 x1 float64 y1 float64 x2 float64 y2 float64 } func (c *cacheContentLine) write(w io.Writer, protection *PDFProtection) error { fmt.Fprintf(w, "%0.2f %0.2f m %0.2f %0.2f l S\n", c.x1, c.pageHeight-c.y1, c.x2, c.pageHeight-c.y2) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_line_type.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentLineType struct { lineType string } func (c *cacheContentLineType) write(w io.Writer, protection *PDFProtection) error { switch c.lineType { case "dashed": fmt.Fprint(w, "[5] 2 d\n") case "dotted": fmt.Fprint(w, "[2 3] 11 d\n") default: fmt.Fprint(w, "[] 0 d\n") } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_line_width.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentLineWidth struct { width float64 } func (c *cacheContentLineWidth) write(w io.Writer, protection *PDFProtection) error { fmt.Fprintf(w, "%.2f w\n", c.width) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_oval.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentOval struct { pageHeight float64 x1 float64 y1 float64 x2 float64 y2 float64 } func (c *cacheContentOval) write(w io.Writer, protection *PDFProtection) error { h := c.pageHeight x1 := c.x1 y1 := c.y1 x2 := c.x2 y2 := c.y2 cp := 0.55228 // Magnification of the control point v1 := [2]float64{x1 + (x2-x1)/2, h - y2} // Vertex of the lower v2 := [2]float64{x2, h - (y1 + (y2-y1)/2)} // .. Right v3 := [2]float64{x1 + (x2-x1)/2, h - y1} // .. Upper v4 := [2]float64{x1, h - (y1 + (y2-y1)/2)} // .. Left fmt.Fprintf(w, "%0.2f %0.2f m\n", v1[0], v1[1]) fmt.Fprintf(w, "%0.2f %0.2f %0.2f %0.2f %0.2f %0.2f c\n", v1[0]+(x2-x1)/2*cp, v1[1], v2[0], v2[1]-(y2-y1)/2*cp, v2[0], v2[1], ) fmt.Fprintf(w, "%0.2f %0.2f %0.2f %0.2f %0.2f %0.2f c\n", v2[0], v2[1]+(y2-y1)/2*cp, v3[0]+(x2-x1)/2*cp, v3[1], v3[0], v3[1], ) fmt.Fprintf(w, "%0.2f %0.2f %0.2f %0.2f %0.2f %0.2f c\n", v3[0]-(x2-x1)/2*cp, v3[1], v4[0], v4[1]+(y2-y1)/2*cp, v4[0], v4[1], ) fmt.Fprintf(w, "%0.2f %0.2f %0.2f %0.2f %0.2f %0.2f c S\n", v4[0], v4[1]-(y2-y1)/2*cp, v1[0]-(x2-x1)/2*cp, v1[1], v1[0], v1[1], ) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_polygon.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentPolygon struct { pageHeight float64 style string points []Point } func (c *cacheContentPolygon) write(w io.Writer, protection *PDFProtection) error { for i, point := range c.points { fmt.Fprintf(w, "%.2f %.2f", point.X, c.pageHeight-point.Y) if i == 0 { fmt.Fprintf(w, " m ") } else { fmt.Fprintf(w, " l ") } } if c.style == "F" { fmt.Fprintf(w, " f\n") } else if c.style == "FD" || c.style == "DF" { fmt.Fprintf(w, " b\n") } else { fmt.Fprintf(w, " s\n") } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_rectangle.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentRectangle struct { pageHeight float64 x float64 y float64 width float64 height float64 style PaintStyle extGStateIndexes []int } func NewCacheContentRectangle(pageHeight float64, rectOpts DrawableRectOptions) ICacheContent { if rectOpts.PaintStyle == "" { rectOpts.PaintStyle = DrawPaintStyle } return cacheContentRectangle{ x: rectOpts.X, y: rectOpts.Y, width: rectOpts.W, height: rectOpts.H, pageHeight: pageHeight, style: rectOpts.PaintStyle, extGStateIndexes: rectOpts.extGStateIndexes, } } func (c cacheContentRectangle) write(w io.Writer, protection *PDFProtection) error { stream := "q\n" for _, extGStateIndex := range c.extGStateIndexes { stream += fmt.Sprintf("/GS%d gs\n", extGStateIndex) } stream += fmt.Sprintf("%0.2f %0.2f %0.2f %0.2f re %s\n", c.x, c.pageHeight-c.y, c.width, c.height, c.style) stream += "Q\n" if _, err := io.WriteString(w, stream); err != nil { return err } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_rotate.go ================================================ package gopdf import ( "fmt" "io" "math" ) type cacheContentRotate struct { isReset bool pageHeight float64 angle, x, y float64 } func (cc *cacheContentRotate) write(w io.Writer, protection *PDFProtection) error { if cc.isReset == true { fmt.Fprintf(w, "Q\n") return nil } angle := (cc.angle * 22.0) / (180.0 * 7.0) c := math.Cos(angle) s := math.Sin(angle) cy := cc.pageHeight - cc.y fmt.Fprintf(w, "q %.5f %.5f %.5f %.5f %.2f %.2f cm 1 0 0 1 %.2f %.2f cm\n", c, s, -s, c, cc.x, cy, -cc.x, -cy) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_content_text.go ================================================ package gopdf import ( "errors" "fmt" "io" ) //ContentTypeCell cell const ContentTypeCell = 0 //ContentTypeText text const ContentTypeText = 1 type cacheContentText struct { //---setup--- rectangle *Rect textColor Rgb grayFill float64 txtColorMode string fontCountIndex int //Curr.FontFontCount+1 fontSize int fontStyle int setXCount int //จำนวนครั้งที่ใช้ setX x, y float64 fontSubset *SubsetFontObj pageheight float64 contentType int cellOpt CellOption lineWidth float64 text string //---result--- cellWidthPdfUnit, textWidthPdfUnit float64 cellHeightPdfUnit float64 } func (c *cacheContentText) isSame(cache cacheContentText) bool { if c.rectangle != nil { //if rectangle != nil we assumes this is not same content return false } if c.textColor.equal(cache.textColor) && c.grayFill == cache.grayFill && c.fontCountIndex == cache.fontCountIndex && c.fontSize == cache.fontSize && c.fontStyle == cache.fontStyle && c.setXCount == cache.setXCount && c.y == cache.y { return true } return false } func (c *cacheContentText) setPageHeight(pageheight float64) { c.pageheight = pageheight } func (c *cacheContentText) pageHeight() float64 { return c.pageheight //841.89 } func convertTypoUnit(val float64, unitsPerEm uint, fontSize float64) float64 { val = val * 1000.00 / float64(unitsPerEm) return val * fontSize / 1000.0 } func (c *cacheContentText) calTypoAscender() float64 { return convertTypoUnit(float64(c.fontSubset.ttfp.TypoAscender()), c.fontSubset.ttfp.UnitsPerEm(), float64(c.fontSize)) } func (c *cacheContentText) calTypoDescender() float64 { return convertTypoUnit(float64(c.fontSubset.ttfp.TypoDescender()), c.fontSubset.ttfp.UnitsPerEm(), float64(c.fontSize)) } func (c *cacheContentText) calY() (float64, error) { pageHeight := c.pageHeight() if c.contentType == ContentTypeText { return pageHeight - c.y, nil } else if c.contentType == ContentTypeCell { y := float64(0.0) if c.cellOpt.Align&Bottom == Bottom { y = pageHeight - c.y - c.cellHeightPdfUnit - c.calTypoDescender() } else if c.cellOpt.Align&Middle == Middle { y = pageHeight - c.y - c.cellHeightPdfUnit*0.5 - (c.calTypoDescender()+c.calTypoAscender())*0.5 } else { //top y = pageHeight - c.y - c.calTypoAscender() } return y, nil } return 0.0, errors.New("contentType not found") } func (c *cacheContentText) calX() (float64, error) { if c.contentType == ContentTypeText { return c.x, nil } else if c.contentType == ContentTypeCell { x := float64(0.0) if c.cellOpt.Align&Right == Right { x = c.x + c.cellWidthPdfUnit - c.textWidthPdfUnit } else if c.cellOpt.Align&Center == Center { x = c.x + c.cellWidthPdfUnit*0.5 - c.textWidthPdfUnit*0.5 } else { x = c.x } return x, nil } return 0.0, errors.New("contentType not found") } func (c *cacheContentText) write(w io.Writer, protection *PDFProtection) error { r := c.textColor.r g := c.textColor.g b := c.textColor.b x, err := c.calX() if err != nil { return err } y, err := c.calY() if err != nil { return err } for _, extGStateIndex := range c.cellOpt.extGStateIndexes { linkToGSObj := fmt.Sprintf("/GS%d gs\n", extGStateIndex) if _, err := io.WriteString(w, linkToGSObj); err != nil { return err } } if _, err := io.WriteString(w, "BT\n"); err != nil { return err } fmt.Fprintf(w, "%0.2f %0.2f TD\n", x, y) fmt.Fprintf(w, "/F%d %d Tf\n", c.fontCountIndex, c.fontSize) if c.txtColorMode == "color" { fmt.Fprintf(w, "%0.3f %0.3f %0.3f rg\n", float64(r)/255, float64(g)/255, float64(b)/255) } io.WriteString(w, "[<") unitsPerEm := int(c.fontSubset.ttfp.UnitsPerEm()) var leftRune rune var leftRuneIndex uint for i, r := range c.text { glyphindex, err := c.fontSubset.CharIndex(r) if err != nil { return err } pairvalPdfUnit := 0 if i > 0 && c.fontSubset.ttfFontOption.UseKerning { //kerning pairval := kern(c.fontSubset, leftRune, r, leftRuneIndex, glyphindex) pairvalPdfUnit = convertTTFUnit2PDFUnit(int(pairval), unitsPerEm) if pairvalPdfUnit != 0 { fmt.Fprintf(w, ">%d<", (-1)*pairvalPdfUnit) } } fmt.Fprintf(w, "%04X", glyphindex) leftRune = r leftRuneIndex = glyphindex } io.WriteString(w, ">] TJ\n") io.WriteString(w, "ET\n") if c.fontStyle&Underline == Underline { err := c.underline(w, c.x, c.y, c.x+c.cellWidthPdfUnit, c.y) if err != nil { return err } } c.drawBorder(w) return nil } func (c *cacheContentText) drawBorder(w io.Writer) error { //stream.WriteString(fmt.Sprintf("%.2f w\n", 0.1)) lineOffset := c.lineWidth * 0.5 if c.cellOpt.Border&Top == Top { startX := c.x - lineOffset startY := c.pageHeight() - c.y endX := c.x + c.cellWidthPdfUnit + lineOffset endY := startY _, err := fmt.Fprintf(w, "%0.2f %0.2f m %0.2f %0.2f l s\n", startX, startY, endX, endY) if err != nil { return err } } if c.cellOpt.Border&Left == Left { startX := c.x startY := c.pageHeight() - c.y endX := c.x endY := startY - c.cellHeightPdfUnit _, err := fmt.Fprintf(w, "%0.2f %0.2f m %0.2f %0.2f l s\n", startX, startY, endX, endY) if err != nil { return err } } if c.cellOpt.Border&Right == Right { startX := c.x + c.cellWidthPdfUnit startY := c.pageHeight() - c.y endX := c.x + c.cellWidthPdfUnit endY := startY - c.cellHeightPdfUnit _, err := fmt.Fprintf(w, "%0.2f %0.2f m %0.2f %0.2f l s\n", startX, startY, endX, endY) if err != nil { return err } } if c.cellOpt.Border&Bottom == Bottom { startX := c.x - lineOffset startY := c.pageHeight() - c.y - c.cellHeightPdfUnit endX := c.x + c.cellWidthPdfUnit + lineOffset endY := startY _, err := fmt.Fprintf(w, "%0.2f %0.2f m %0.2f %0.2f l s\n", startX, startY, endX, endY) if err != nil { return err } } return nil } func (c *cacheContentText) underline(w io.Writer, startX float64, startY float64, endX float64, endY float64) error { if c.fontSubset == nil { return errors.New("error AppendUnderline not found font") } unitsPerEm := float64(c.fontSubset.ttfp.UnitsPerEm()) h := c.pageHeight() ut := float64(c.fontSubset.GetUt()) up := float64(c.fontSubset.GetUp()) textH := ContentObjCalTextHeight(c.fontSize) arg3 := float64(h) - (float64(startY) - ((up / unitsPerEm) * float64(c.fontSize))) - textH arg4 := (ut / unitsPerEm) * float64(c.fontSize) fmt.Fprintf(w, "%0.2f %0.2f %0.2f -%0.2f re f\n", startX, arg3, endX-startX, arg4) //fmt.Printf("arg3=%f arg4=%f\n", arg3, arg4) return nil } func (c *cacheContentText) createContent() (float64, float64, error) { cellWidthPdfUnit, cellHeightPdfUnit, textWidthPdfUnit, err := createContent(c.fontSubset, c.text, c.fontSize, c.rectangle) if err != nil { return 0, 0, err } c.cellWidthPdfUnit = cellWidthPdfUnit c.cellHeightPdfUnit = cellHeightPdfUnit c.textWidthPdfUnit = textWidthPdfUnit return cellWidthPdfUnit, cellHeightPdfUnit, nil } func createContent(f *SubsetFontObj, text string, fontSize int, rectangle *Rect) (float64, float64, float64, error) { unitsPerEm := int(f.ttfp.UnitsPerEm()) var leftRune rune var leftRuneIndex uint sumWidth := int(0) //fmt.Printf("unitsPerEm = %d", unitsPerEm) for i, r := range text { glyphindex, err := f.CharIndex(r) if err != nil { return 0, 0, 0, err } pairvalPdfUnit := 0 if i > 0 && f.ttfFontOption.UseKerning { //kerning pairval := kern(f, leftRune, r, leftRuneIndex, glyphindex) pairvalPdfUnit = convertTTFUnit2PDFUnit(int(pairval), unitsPerEm) } width, err := f.CharWidth(r) if err != nil { return 0, 0, 0, err } sumWidth += int(width) + int(pairvalPdfUnit) leftRune = r leftRuneIndex = glyphindex } cellWidthPdfUnit := float64(0) cellHeightPdfUnit := float64(0) if rectangle == nil { cellWidthPdfUnit = float64(sumWidth) * (float64(fontSize) / 1000.0) typoAscender := convertTypoUnit(float64(f.ttfp.TypoAscender()), f.ttfp.UnitsPerEm(), float64(fontSize)) typoDescender := convertTypoUnit(float64(f.ttfp.TypoDescender()), f.ttfp.UnitsPerEm(), float64(fontSize)) cellHeightPdfUnit = typoAscender - typoDescender } else { cellWidthPdfUnit = rectangle.W cellHeightPdfUnit = rectangle.H } textWidthPdfUnit := float64(sumWidth) * (float64(fontSize) / 1000.0) return cellWidthPdfUnit, cellHeightPdfUnit, textWidthPdfUnit, nil } func kern(f *SubsetFontObj, leftRune rune, rightRune rune, leftIndex uint, rightIndex uint) int16 { pairVal := int16(0) if haveKerning, kval := f.KernValueByLeft(leftIndex); haveKerning { if ok, v := kval.ValueByRight(rightIndex); ok { pairVal = v } } if f.funcKernOverride != nil { pairVal = f.funcKernOverride( leftRune, rightRune, leftIndex, rightIndex, pairVal, ) } return pairVal } //CacheContent Export cacheContent type CacheContent struct { cacheContentText } //Setup setup all information for cacheContent func (c *CacheContent) Setup(rectangle *Rect, textColor Rgb, grayFill float64, fontCountIndex int, //Curr.FontFontCount+1 fontSize int, fontStyle int, setXCount int, //จำนวนครั้งที่ใช้ setX x, y float64, fontSubset *SubsetFontObj, pageheight float64, contentType int, cellOpt CellOption, lineWidth float64, ) { c.cacheContentText = cacheContentText{ fontSubset: fontSubset, rectangle: rectangle, textColor: textColor, grayFill: grayFill, fontCountIndex: fontCountIndex, fontSize: fontSize, fontStyle: fontStyle, setXCount: setXCount, x: x, y: y, pageheight: pageheight, contentType: ContentTypeCell, cellOpt: cellOpt, lineWidth: lineWidth, } } //WriteTextToContent write text to content func (c *CacheContent) WriteTextToContent(text string) { c.cacheContentText.text += text } ================================================ FILE: vendor/github.com/signintech/gopdf/cache_contnent_curve.go ================================================ package gopdf import ( "fmt" "io" ) type cacheContentCurve struct { pageHeight float64 x0 float64 y0 float64 x1 float64 y1 float64 x2 float64 y2 float64 x3 float64 y3 float64 style string } func (c *cacheContentCurve) write(w io.Writer, protection *PDFProtection) error { h := c.pageHeight x0 := c.x0 y0 := c.y0 x1 := c.x1 y1 := c.y1 x2 := c.x2 y2 := c.y2 x3 := c.x3 y3 := c.y3 style := c.style //cp := 0.55228 fmt.Fprintf(w, "%0.2f %0.2f m\n", x0, h-y0) fmt.Fprintf(w, "%0.2f %0.2f %0.2f %0.2f %0.2f %0.2f c", x1, h-y1, x2, h-y2, x3, h-y3, ) op := "S" if style == "F" { op = "f" } else if style == "FD" || style == "DF" { op = "B" } fmt.Fprintf(w, " %s\n", op) return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/catalog_obj.go ================================================ package gopdf import ( "fmt" "io" ) //CatalogObj : catalog dictionary type CatalogObj struct { //impl IObj outlinesObjID int } func (c *CatalogObj) init(funcGetRoot func() *GoPdf) { c.outlinesObjID = -1 } func (c *CatalogObj) getType() string { return "Catalog" } func (c *CatalogObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") fmt.Fprintf(w, " /Type /%s\n", c.getType()) io.WriteString(w, " /Pages 2 0 R\n") if c.outlinesObjID >= 0 { io.WriteString(w, " /PageMode /UseOutlines\n") fmt.Fprintf(w, " /Outlines %d 0 R\n", c.outlinesObjID) } io.WriteString(w, ">>\n") return nil } func (c *CatalogObj) SetIndexObjOutlines(index int) { c.outlinesObjID = index + 1 } ================================================ FILE: vendor/github.com/signintech/gopdf/cell_option.go ================================================ package gopdf //Left left const Left = 8 //001000 //Top top const Top = 4 //000100 //Right right const Right = 2 //000010 //Bottom bottom const Bottom = 1 //000001 //Center center const Center = 16 //010000 //Middle middle const Middle = 32 //100000 //AllBorders allborders const AllBorders = 15 //001111 //CellOption cell option type CellOption struct { Align int //Allows to align the text. Possible values are: Left,Center,Right,Top,Bottom,Middle Border int //Indicates if borders must be drawn around the cell. Possible values are: Left, Top, Right, Bottom, ALL Float int //Indicates where the current position should go after the call. Possible values are: Right, Bottom Transparency *Transparency extGStateIndexes []int } ================================================ FILE: vendor/github.com/signintech/gopdf/cid_font_obj.go ================================================ package gopdf import ( "fmt" "io" ) // CIDFontObj is a CID-keyed font. // cf. https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5014.CIDFont_Spec.pdf type CIDFontObj struct { PtrToSubsetFontObj *SubsetFontObj indexObjSubfontDescriptor int } func (ci *CIDFontObj) init(funcGetRoot func() *GoPdf) { } //SetIndexObjSubfontDescriptor set indexObjSubfontDescriptor func (ci *CIDFontObj) SetIndexObjSubfontDescriptor(index int) { ci.indexObjSubfontDescriptor = index } func (ci *CIDFontObj) getType() string { return "CIDFont" } func (ci *CIDFontObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") fmt.Fprintf(w, "/BaseFont /%s\n", CreateEmbeddedFontSubsetName(ci.PtrToSubsetFontObj.GetFamily())) io.WriteString(w, "/CIDSystemInfo\n") io.WriteString(w, "<<\n") io.WriteString(w, " /Ordering (Identity)\n") io.WriteString(w, " /Registry (Adobe)\n") io.WriteString(w, " /Supplement 0\n") io.WriteString(w, ">>\n") fmt.Fprintf(w, "/FontDescriptor %d 0 R\n", ci.indexObjSubfontDescriptor+1) //TODO fix io.WriteString(w, "/Subtype /CIDFontType2\n") io.WriteString(w, "/Type /Font\n") glyphIndexs := ci.PtrToSubsetFontObj.CharacterToGlyphIndex.AllVals() io.WriteString(w, "/W [") for _, v := range glyphIndexs { width := ci.PtrToSubsetFontObj.GlyphIndexToPdfWidth(v) fmt.Fprintf(w, "%d[%d]", v, width) } io.WriteString(w, "]\n") io.WriteString(w, ">>\n") return nil } //SetPtrToSubsetFontObj set PtrToSubsetFontObj func (ci *CIDFontObj) SetPtrToSubsetFontObj(ptr *SubsetFontObj) { ci.PtrToSubsetFontObj = ptr } ================================================ FILE: vendor/github.com/signintech/gopdf/config.go ================================================ package gopdf // The units that can be used in the document const ( UnitUnset = iota // No units were set, when conversion is called on nothing will happen UnitPT // Points UnitMM // Millimeters UnitCM // centimeters UnitIN // inches // The math needed to convert units to points conversionUnitPT = 1.0 conversionUnitMM = 72.0 / 25.4 conversionUnitCM = 72.0 / 2.54 conversionUnitIN = 72.0 ) // The units that can be used in the document (for backward compatibility) // Deprecated: Use UnitUnset,UnitPT,UnitMM,UnitCM,UnitIN instead const ( Unit_Unset = UnitUnset // No units were set, when conversion is called on nothing will happen Unit_PT = UnitPT // Points Unit_MM = UnitMM // Millimeters Unit_CM = UnitCM // centimeters Unit_IN = UnitIN // inches ) //Config static config type Config struct { Unit int // The unit type to use when composing the document. TrimBox Box // The default trim box for all pages in the document PageSize Rect // The default page size for all pages in the document K float64 // Not sure Protection PDFProtectionConfig // Protection settings } //PDFProtectionConfig config of pdf protection type PDFProtectionConfig struct { UseProtection bool Permissions int UserPass []byte OwnerPass []byte } // UnitsToPoints converts units of the provided type to points func UnitsToPoints(t int, u float64) float64 { switch t { case UnitPT: return u * conversionUnitPT case UnitMM: return u * conversionUnitMM case UnitCM: return u * conversionUnitCM case UnitIN: return u * conversionUnitIN default: return u } } // PointsToUnits converts points to the provided units func PointsToUnits(t int, u float64) float64 { switch t { case UnitPT: return u / conversionUnitPT case UnitMM: return u / conversionUnitMM case UnitCM: return u / conversionUnitCM case UnitIN: return u / conversionUnitIN default: return u } } // UnitsToPointsVar converts units of the provided type to points for all variables supplied func UnitsToPointsVar(t int, u ...*float64) { for x := 0; x < len(u); x++ { *u[x] = UnitsToPoints(t, *u[x]) } } // PointsToUnitsVar converts points to the provided units for all variables supplied func PointsToUnitsVar(t int, u ...*float64) { for x := 0; x < len(u); x++ { *u[x] = PointsToUnits(t, *u[x]) } } ================================================ FILE: vendor/github.com/signintech/gopdf/content_obj.go ================================================ package gopdf import ( "compress/zlib" "fmt" "io" "strings" ) //ContentObj content object type ContentObj struct { //impl IObj listCache listCacheContent //text bytes.Buffer getRoot func() *GoPdf } func (c *ContentObj) protection() *PDFProtection { return c.getRoot().protection() } func (c *ContentObj) init(funcGetRoot func() *GoPdf) { c.getRoot = funcGetRoot } func (c *ContentObj) write(w io.Writer, objID int) error { buff := GetBuffer() defer PutBuffer(buff) isFlate := (c.getRoot().compressLevel != zlib.NoCompression) if isFlate { ww, err := zlib.NewWriterLevel(buff, c.getRoot().compressLevel) if err != nil { // should never happen... return err } if err := c.listCache.write(ww, c.protection()); err != nil { return err } if err := ww.Close(); err != nil { return err } } else { if err := c.listCache.write(buff, c.protection()); err != nil { return err } } if _, err := io.WriteString(w, "<<\n"); err != nil { return err } if isFlate { if _, err := io.WriteString(w, "/Filter/FlateDecode"); err != nil { return err } } if _, err := fmt.Fprintf(w, "/Length %d\n", buff.Len()); err != nil { return err } if _, err := io.WriteString(w, ">>\n"); err != nil { return err } if _, err := io.WriteString(w, "stream\n"); err != nil { return err } if c.protection() != nil { tmp, err := rc4Cip(c.protection().objectkey(objID), buff.Bytes()) if err != nil { return err } if _, err := w.Write(tmp); err != nil { return err } if _, err := io.WriteString(w, "\n"); err != nil { return err } } else { if _, err := buff.WriteTo(w); err != nil { return err } if isFlate { if _, err := io.WriteString(w, "\n"); err != nil { return err } } } if _, err := io.WriteString(w, "endstream\n"); err != nil { return err } return nil } func (c *ContentObj) getType() string { return "Content" } //AppendStreamText append text func (c *ContentObj) AppendStreamText(text string) error { //support only CURRENT_FONT_TYPE_SUBSET textColor := c.getRoot().curr.textColor() grayFill := c.getRoot().curr.grayFill fontCountIndex := c.getRoot().curr.FontFontCount + 1 fontSize := c.getRoot().curr.FontSize fontStyle := c.getRoot().curr.FontStyle x := c.getRoot().curr.X y := c.getRoot().curr.Y setXCount := c.getRoot().curr.setXCount fontSubset := c.getRoot().curr.FontISubset cellOption := CellOption{Transparency: c.getRoot().curr.transparency} cache := cacheContentText{ fontSubset: fontSubset, rectangle: nil, textColor: textColor, grayFill: grayFill, fontCountIndex: fontCountIndex, fontSize: fontSize, fontStyle: fontStyle, setXCount: setXCount, x: x, y: y, cellOpt: cellOption, pageheight: c.getRoot().curr.pageSize.H, contentType: ContentTypeText, lineWidth: c.getRoot().curr.lineWidth, txtColorMode: c.getRoot().curr.txtColorMode, } var err error c.getRoot().curr.X, c.getRoot().curr.Y, err = c.listCache.appendContentText(cache, text) if err != nil { return err } return nil } //AppendStreamSubsetFont add stream of text func (c *ContentObj) AppendStreamSubsetFont(rectangle *Rect, text string, cellOpt CellOption) error { textColor := c.getRoot().curr.textColor() grayFill := c.getRoot().curr.grayFill fontCountIndex := c.getRoot().curr.FontFontCount + 1 fontSize := c.getRoot().curr.FontSize fontStyle := c.getRoot().curr.FontStyle x := c.getRoot().curr.X y := c.getRoot().curr.Y setXCount := c.getRoot().curr.setXCount fontSubset := c.getRoot().curr.FontISubset cache := cacheContentText{ fontSubset: fontSubset, rectangle: rectangle, textColor: textColor, grayFill: grayFill, fontCountIndex: fontCountIndex, fontSize: fontSize, fontStyle: fontStyle, setXCount: setXCount, x: x, y: y, pageheight: c.getRoot().curr.pageSize.H, contentType: ContentTypeCell, cellOpt: cellOpt, lineWidth: c.getRoot().curr.lineWidth, txtColorMode: c.getRoot().curr.txtColorMode, } var err error c.getRoot().curr.X, c.getRoot().curr.Y, err = c.listCache.appendContentText(cache, text) if err != nil { return err } return nil } //AppendStreamLine append line func (c *ContentObj) AppendStreamLine(x1 float64, y1 float64, x2 float64, y2 float64) { //h := c.getRoot().config.PageSize.H //c.stream.WriteString(fmt.Sprintf("%0.2f %0.2f m %0.2f %0.2f l s\n", x1, h-y1, x2, h-y2)) var cache cacheContentLine cache.pageHeight = c.getRoot().curr.pageSize.H cache.x1 = x1 cache.y1 = y1 cache.x2 = x2 cache.y2 = y2 c.listCache.append(&cache) } //AppendStreamImportedTemplate append imported template func (c *ContentObj) AppendStreamImportedTemplate(tplName string, scaleX float64, scaleY float64, tX float64, tY float64) { var cache cacheContentImportedTemplate cache.pageHeight = c.getRoot().curr.pageSize.H cache.tplName = tplName cache.scaleX = scaleX cache.scaleY = scaleY cache.tX = tX cache.tY = tY c.listCache.append(&cache) } func (c *ContentObj) AppendStreamRectangle(opts DrawableRectOptions) { cache := NewCacheContentRectangle(c.getRoot().curr.pageSize.H, opts) c.listCache.append(cache) } //AppendStreamOval append oval func (c *ContentObj) AppendStreamOval(x1 float64, y1 float64, x2 float64, y2 float64) { var cache cacheContentOval cache.pageHeight = c.getRoot().curr.pageSize.H cache.x1 = x1 cache.y1 = y1 cache.x2 = x2 cache.y2 = y2 c.listCache.append(&cache) } //AppendStreamCurve draw curve // - x0, y0: Start point // - x1, y1: Control point 1 // - x2, y2: Control point 2 // - x3, y3: End point // - style: Style of rectangule (draw and/or fill: D, F, DF, FD) // D or empty string: draw. This is the default value. // F: fill // DF or FD: draw and fill func (c *ContentObj) AppendStreamCurve(x0 float64, y0 float64, x1 float64, y1 float64, x2 float64, y2 float64, x3 float64, y3 float64, style string) { var cache cacheContentCurve cache.pageHeight = c.getRoot().curr.pageSize.H cache.x0 = x0 cache.y0 = y0 cache.x1 = x1 cache.y1 = y1 cache.x2 = x2 cache.y2 = y2 cache.x3 = x3 cache.y3 = y3 cache.style = strings.ToUpper(strings.TrimSpace(style)) c.listCache.append(&cache) } //AppendStreamSetLineWidth : set line width func (c *ContentObj) AppendStreamSetLineWidth(w float64) { var cache cacheContentLineWidth cache.width = w c.listCache.append(&cache) } //AppendStreamSetLineType : Set linetype [solid, dashed, dotted] func (c *ContentObj) AppendStreamSetLineType(t string) { var cache cacheContentLineType cache.lineType = t c.listCache.append(&cache) } //AppendStreamSetGrayFill set the grayscale fills func (c *ContentObj) AppendStreamSetGrayFill(w float64) { w = fixRange10(w) var cache cacheContentGray cache.grayType = grayTypeFill cache.scale = w c.listCache.append(&cache) } //AppendStreamSetGrayStroke set the grayscale stroke func (c *ContentObj) AppendStreamSetGrayStroke(w float64) { w = fixRange10(w) var cache cacheContentGray cache.grayType = grayTypeStroke cache.scale = w c.listCache.append(&cache) } //AppendStreamSetColorStroke set the color stroke func (c *ContentObj) AppendStreamSetColorStroke(r uint8, g uint8, b uint8) { var cache cacheContentColor cache.colorType = colorTypeStroke cache.r = r cache.g = g cache.b = b c.listCache.append(&cache) } //AppendStreamSetColorFill set the color fill func (c *ContentObj) AppendStreamSetColorFill(r uint8, g uint8, b uint8) { var cache cacheContentColor cache.colorType = colorTypeFill cache.r = r cache.g = g cache.b = b c.listCache.append(&cache) } func (c *ContentObj) GetCacheContentImage(index int, opts ImageOptions) *cacheContentImage { h := c.getRoot().curr.pageSize.H return &cacheContentImage{ pageHeight: h, index: index, x: opts.X, y: opts.Y, rect: *opts.Rect, crop: opts.Crop, verticalFlip: opts.VerticalFlip, horizontalFlip: opts.HorizontalFlip, extGStateIndexes: opts.extGStateIndexes, } } //AppendStreamImage append image func (c *ContentObj) AppendStreamImage(index int, opts ImageOptions) { cache := c.GetCacheContentImage(index, opts) c.listCache.append(cache) } //AppendStreamPolygon append polygon func (c *ContentObj) AppendStreamPolygon(points []Point, style string) { var cache cacheContentPolygon cache.points = points cache.style = style cache.pageHeight = c.getRoot().curr.pageSize.H c.listCache.append(&cache) } func (c *ContentObj) appendRotate(angle, x, y float64) { var cache cacheContentRotate cache.isReset = false cache.pageHeight = c.getRoot().curr.pageSize.H cache.angle = angle cache.x = x cache.y = y c.listCache.append(&cache) } func (c *ContentObj) appendRotateReset() { var cache cacheContentRotate cache.isReset = true c.listCache.append(&cache) } //ContentObjCalTextHeight calculates height of text. func ContentObjCalTextHeight(fontsize int) float64 { return (float64(fontsize) * 0.7) } // When setting colour and grayscales the value has to be between 0.00 and 1.00 // This function takes a float64 and returns 0.0 if it is less than 0.0 and 1.0 if it // is more than 1.0 func fixRange10(val float64) float64 { if val < 0.0 { return 0.0 } if val > 1.0 { return 1.0 } return val } func convertTTFUnit2PDFUnit(n int, upem int) int { var ret int if n < 0 { rest1 := n % upem storrest := 1000 * rest1 //ledd2 := (storrest != 0 ? rest1 / storrest : 0); ledd2 := 0 if storrest != 0 { ledd2 = rest1 / storrest } else { ledd2 = 0 } ret = -((-1000*n)/upem - int(ledd2)) } else { ret = (n/upem)*1000 + ((n%upem)*1000)/upem } return ret } ================================================ FILE: vendor/github.com/signintech/gopdf/current.go ================================================ package gopdf //Current current state type Current struct { setXCount int //many times we go func SetX() X float64 Y float64 //font IndexOfFontObj int CountOfFont int CountOfL int FontSize int FontStyle int // Regular|Bold|Italic|Underline FontFontCount int FontType int // CURRENT_FONT_TYPE_IFONT or CURRENT_FONT_TYPE_SUBSET FontISubset *SubsetFontObj // FontType == CURRENT_FONT_TYPE_SUBSET //page IndexOfPageObj int //img CountOfImg int //cache of image in pdf file ImgCaches map[int]ImageCache //text color mode txtColorMode string //color, gray //text color txtColor Rgb //text grayscale grayFill float64 //draw grayscale grayStroke float64 lineWidth float64 //current page size pageSize *Rect //current trim box trimBox *Box sMasksMap SMaskMap extGStatesMap ExtGStatesMap transparency *Transparency transparencyMap TransparencyMap } func (c *Current) setTextColor(rgb Rgb) { c.txtColor = rgb } func (c *Current) textColor() Rgb { return c.txtColor } // ImageCache is metadata for caching images. type ImageCache struct { Path string //ID or Path Index int Rect *Rect } //Rgb rgb color type Rgb struct { r uint8 g uint8 b uint8 } //SetR set red func (rgb *Rgb) SetR(r uint8) { rgb.r = r } //SetG set green func (rgb *Rgb) SetG(g uint8) { rgb.g = g } //SetB set blue func (rgb *Rgb) SetB(b uint8) { rgb.b = b } func (rgb Rgb) equal(obj Rgb) bool { if rgb.r == obj.r && rgb.g == obj.g && rgb.b == obj.b { return true } return false } ================================================ FILE: vendor/github.com/signintech/gopdf/device_rgb_obj.go ================================================ package gopdf import ( "fmt" "io" ) //DeviceRGBObj DeviceRGB type DeviceRGBObj struct { data []byte getRoot func() *GoPdf } func (d *DeviceRGBObj) init(funcGetRoot func() *GoPdf) { d.getRoot = funcGetRoot } func (d *DeviceRGBObj) protection() *PDFProtection { return d.getRoot().protection() } func (d *DeviceRGBObj) getType() string { return "devicergb" } //สร้าง ข้อมูลใน pdf func (d *DeviceRGBObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") fmt.Fprintf(w, "/Length %d\n", len(d.data)) io.WriteString(w, ">>\n") io.WriteString(w, "stream\n") if d.protection() != nil { tmp, err := rc4Cip(d.protection().objectkey(objID), d.data) if err != nil { return err } w.Write(tmp) io.WriteString(w, "\n") } else { w.Write(d.data) } io.WriteString(w, "endstream\n") return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/embedfont_obj.go ================================================ package gopdf import ( "fmt" "io" "io/ioutil" ) // EmbedFontObj is an embedded font object. type EmbedFontObj struct { Data string zfontpath string font IFont getRoot func() *GoPdf } func (e *EmbedFontObj) init(funcGetRoot func() *GoPdf) { e.getRoot = funcGetRoot } func (e *EmbedFontObj) protection() *PDFProtection { return e.getRoot().protection() } func (e *EmbedFontObj) write(w io.Writer, objID int) error { b, err := ioutil.ReadFile(e.zfontpath) if err != nil { return err } fmt.Fprintf(w, "<>\n") io.WriteString(w, "stream\n") if e.protection() != nil { tmp, err := rc4Cip(e.protection().objectkey(objID), b) if err != nil { return err } w.Write(tmp) io.WriteString(w, "\n") } else { w.Write(b) } io.WriteString(w, "\nendstream\n") return nil } func (e *EmbedFontObj) getType() string { return "EmbedFont" } // SetFont sets the font of an embedded font object. func (e *EmbedFontObj) SetFont(font IFont, zfontpath string) { e.font = font e.zfontpath = zfontpath } ================================================ FILE: vendor/github.com/signintech/gopdf/encoding_obj.go ================================================ package gopdf import ( "io" ) // EncodingObj is a font object. type EncodingObj struct { font IFont } func (e *EncodingObj) init(funcGetRoot func() *GoPdf) { } func (e *EncodingObj) getType() string { return "Encoding" } func (e *EncodingObj) write(w io.Writer, objID int) error { io.WriteString(w, "<>\n") return nil } // SetFont sets the font of an encoding object. func (e *EncodingObj) SetFont(font IFont) { e.font = font } // GetFont gets the font from an encoding object. func (e *EncodingObj) GetFont() IFont { return e.font } ================================================ FILE: vendor/github.com/signintech/gopdf/encryption_obj.go ================================================ package gopdf import ( "fmt" "io" "strings" ) //EncryptionObj encryption object res type EncryptionObj struct { uValue []byte //U entry in pdf document oValue []byte //O entry in pdf document pValue int //P entry in pdf document } func (e *EncryptionObj) init(func() *GoPdf) { } func (e *EncryptionObj) getType() string { return "Encryption" } func (e *EncryptionObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") io.WriteString(w, "/Filter /Standard\n") io.WriteString(w, "/V 1\n") io.WriteString(w, "/R 2\n") fmt.Fprintf(w, "/O (%s)\n", e.escape(e.oValue)) fmt.Fprintf(w, "/U (%s)\n", e.escape(e.uValue)) fmt.Fprintf(w, "/P %d\n", e.pValue) io.WriteString(w, ">>\n") return nil } func (e *EncryptionObj) escape(b []byte) string { s := string(b) s = strings.Replace(s, "\\", "\\\\", -1) s = strings.Replace(s, "(", "\\(", -1) s = strings.Replace(s, ")", "\\)", -1) s = strings.Replace(s, "\r", "\\r", -1) return s } ================================================ FILE: vendor/github.com/signintech/gopdf/ext_g_state_obj.go ================================================ package gopdf import ( "fmt" "io" "sync" "github.com/pkg/errors" ) // TODO: add all fields https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf 8.4.5 page 128 type ExtGState struct { Index int ca *float64 CA *float64 BM *BlendModeType SMaskIndex *int } type ExtGStateOptions struct { StrokingCA *float64 NonStrokingCa *float64 BlendMode *BlendModeType SMaskIndex *int } func (extOpt ExtGStateOptions) GetId() string { id := "" if extOpt.StrokingCA != nil { id += fmt.Sprintf("CA_%.3f;", *extOpt.StrokingCA) } if extOpt.NonStrokingCa != nil { id += fmt.Sprintf("ca_%.3f;", *extOpt.NonStrokingCa) } if extOpt.BlendMode != nil { id += fmt.Sprintf("BM_%s;", *extOpt.BlendMode) } if extOpt.SMaskIndex != nil { id += fmt.Sprintf("SMask_%d_0_R;", *extOpt.SMaskIndex) } return id } func GetCachedExtGState(opts ExtGStateOptions, gp *GoPdf) (ExtGState, error) { extGState, ok := gp.curr.extGStatesMap.Find(opts) if !ok { extGState = ExtGState{ BM: opts.BlendMode, CA: opts.StrokingCA, ca: opts.NonStrokingCa, SMaskIndex: opts.SMaskIndex, } extGState.Index = gp.addObj(extGState) pdfObj := gp.pdfObjs[gp.indexOfProcSet] procset, ok := pdfObj.(*ProcSetObj) if !ok { return ExtGState{}, errors.New("can't convert pdfobject to procsetobj") } procset.ExtGStates = append(procset.ExtGStates, ExtGS{Index: extGState.Index}) gp.curr.extGStatesMap.Save(opts.GetId(), extGState) //extGState = extGState } return extGState, nil } func (egs ExtGState) init(func() *GoPdf) {} func (egs ExtGState) getType() string { return "ExtGState" } func (egs ExtGState) write(w io.Writer, objID int) error { content := "<<\n" content += "\t/Type /ExtGState\n" if egs.ca != nil { content += fmt.Sprintf("\t/ca %.3F\n", *egs.ca) } if egs.CA != nil { content += fmt.Sprintf("\t/CA %.3F\n", *egs.CA) } if egs.BM != nil { content += fmt.Sprintf("\t/BM %s\n", *egs.BM) } if egs.SMaskIndex != nil { content += fmt.Sprintf("\t/SMask %d 0 R\n", *egs.SMaskIndex+1) } content += ">>\n" if _, err := io.WriteString(w, content); err != nil { return err } return nil } type ExtGStatesMap struct { syncer sync.Mutex table map[string]ExtGState } func NewExtGStatesMap() ExtGStatesMap { return ExtGStatesMap{ syncer: sync.Mutex{}, table: make(map[string]ExtGState), } } func (extm *ExtGStatesMap) Find(extGState ExtGStateOptions) (ExtGState, bool) { key := extGState.GetId() extm.syncer.Lock() defer extm.syncer.Unlock() t, ok := extm.table[key] if !ok { return ExtGState{}, false } return t, ok } func (tm *ExtGStatesMap) Save(id string, extGState ExtGState) ExtGState { tm.syncer.Lock() defer tm.syncer.Unlock() tm.table[id] = extGState return extGState } ================================================ FILE: vendor/github.com/signintech/gopdf/font_obj.go ================================================ package gopdf import ( "fmt" "io" ) //FontObj font obj type FontObj struct { Family string //Style string //Size int IsEmbedFont bool indexObjWidth int indexObjFontDescriptor int indexObjEncoding int Font IFont CountOfFont int } func (f *FontObj) init(funcGetRoot func() *GoPdf) { f.IsEmbedFont = false //me.CountOfFont = -1 } func (f *FontObj) write(w io.Writer, objID int) error { baseFont := f.Family if f.Font != nil { baseFont = f.Font.GetName() } io.WriteString(w, "<<\n") fmt.Fprintf(w, " /Type /%s\n", f.getType()) io.WriteString(w, " /Subtype /TrueType\n") fmt.Fprintf(w, " /BaseFont /%s\n", baseFont) if f.IsEmbedFont { io.WriteString(w, " /FirstChar 32 /LastChar 255\n") fmt.Fprintf(w, " /Widths %d 0 R\n", f.indexObjWidth) fmt.Fprintf(w, " /FontDescriptor %d 0 R\n", f.indexObjFontDescriptor) fmt.Fprintf(w, " /Encoding %d 0 R\n", f.indexObjEncoding) } io.WriteString(w, ">>\n") return nil } func (f *FontObj) getType() string { return "Font" } // SetIndexObjWidth sets the width of a font object. func (f *FontObj) SetIndexObjWidth(index int) { f.indexObjWidth = index } // SetIndexObjFontDescriptor sets the font descriptor. func (f *FontObj) SetIndexObjFontDescriptor(index int) { f.indexObjFontDescriptor = index } // SetIndexObjEncoding sets the encoding. func (f *FontObj) SetIndexObjEncoding(index int) { f.indexObjEncoding = index } ================================================ FILE: vendor/github.com/signintech/gopdf/font_option.go ================================================ package gopdf import ( "strings" ) //Regular - font style regular const Regular = 0 //000000 //Italic - font style italic const Italic = 1 //000001 //Bold - font style bold const Bold = 2 //000010 //Underline - font style underline const Underline = 4 //000100 func getConvertedStyle(fontStyle string) (style int) { fontStyle = strings.ToUpper(fontStyle) if strings.Contains(fontStyle, "B") { style = style | Bold } if strings.Contains(fontStyle, "I") { style = style | Italic } if strings.Contains(fontStyle, "U") { style = style | Underline } return } ================================================ FILE: vendor/github.com/signintech/gopdf/fontconverthelper.go ================================================ package gopdf import ( "strconv" //"fmt" "bytes" ) // FontConvertHelperCw2Str converts main ASCII characters of a FontCW to a string. func FontConvertHelperCw2Str(cw FontCw) string { buff := new(bytes.Buffer) buff.WriteString(" ") i := 32 for i <= 255 { buff.WriteString(strconv.Itoa(cw[byte(i)]) + " ") i++ } return buff.String() } // FontConvertHelper_Cw2Str converts main ASCII characters of a FontCW to a string. (for backward compatibility) // Deprecated: Use FontConvertHelperCw2Str(cw FontCw) instead func FontConvertHelper_Cw2Str(cw FontCw) string { return FontConvertHelperCw2Str(cw) } ================================================ FILE: vendor/github.com/signintech/gopdf/fontdescriptor_obj.go ================================================ package gopdf import ( "fmt" "io" ) // FontDescriptorObj is a font descriptor object. type FontDescriptorObj struct { font IFont fontFileObjRelate string } func (f *FontDescriptorObj) init(funcGetRoot func() *GoPdf) { } func (f *FontDescriptorObj) write(w io.Writer, objID int) error { fmt.Fprintf(w, "<>\n") return nil } func (f *FontDescriptorObj) getType() string { return "FontDescriptor" } // SetFont sets the font in descriptor. func (f *FontDescriptorObj) SetFont(font IFont) { f.font = font } // GetFont gets font from descriptor. func (f *FontDescriptorObj) GetFont() IFont { return f.font } // SetFontFileObjRelate ??? func (f *FontDescriptorObj) SetFontFileObjRelate(relate string) { f.fontFileObjRelate = relate } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/fontmaker.go ================================================ package core import ( "bufio" "bytes" "compress/zlib" "errors" "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" ) //ErrFontLicenseDoesNotAllowEmbedding Font license does not allow embedding var ErrFontLicenseDoesNotAllowEmbedding = errors.New("Font license does not allow embedding") //FontMaker font maker type FontMaker struct { results []string } //GetResults get result func (f *FontMaker) GetResults() []string { return f.results } //NewFontMaker new FontMaker func NewFontMaker() *FontMaker { return new(FontMaker) } func (f *FontMaker) MakeFont(fontpath string, mappath string, encode string, outfolderpath string) error { encodingpath := mappath + "/" + encode + ".map" //read font file if _, err := os.Stat(fontpath); os.IsNotExist(err) { return err } fileext := filepath.Ext(fontpath) if strings.ToLower(fileext) != ".ttf" { //now support only ttf :-P return errors.New("support only ttf ") } fontmaps, err := f.LoadMap(encodingpath) if err != nil { return err } info, err := f.GetInfoFromTrueType(fontpath, fontmaps) if err != nil { return err } //zip basename := filepath.Base(fontpath) tmp := strings.Split(basename, ".") basename = strings.Replace(tmp[0], " ", "_", -1) gzfilename := basename + ".z" var buff bytes.Buffer gzipwriter := zlib.NewWriter(&buff) fontbytes, err := ioutil.ReadFile(fontpath) if err != nil { return err } _, err = gzipwriter.Write(fontbytes) if err != nil { return err } gzipwriter.Close() err = ioutil.WriteFile(outfolderpath+"/"+gzfilename, buff.Bytes(), 0644) if err != nil { return err } info.PushString("File", gzfilename) f.results = append(f.results, fmt.Sprintf("Save Z file at %s.", outfolderpath+"/"+gzfilename)) //Definition File _, err = f.MakeDefinitionFile(f.GoStructName(basename), mappath, outfolderpath+"/"+basename+".font.go", encode, fontmaps, info) if err != nil { return err } return nil } func (f *FontMaker) GoStructName(name string) string { goname := strings.ToUpper(name[0:1]) + name[1:] return goname } func (f *FontMaker) MakeDefinitionFile(gofontname string, mappath string, exportfile string, encode string, fontmaps []FontMap, info TtfInfo) (string, error) { fonttype := "TrueType" str := "" str += "package fonts //change this\n" str += "import (\n" str += " \"github.com/signintech/gopdf\"\n" str += ")\n" str += "type " + gofontname + " struct {\n" str += "\tfamily string\n" str += "\tfonttype string\n" str += "\tname string\n" str += "\tdesc []gopdf.FontDescItem\n" str += "\tup int\n" str += "\tut int\n" str += "\tcw gopdf.FontCw\n" str += "\tenc string\n" str += "\tdiff string\n" str += "}\n" str += "func (me * " + gofontname + ") Init(){\n" widths, err := info.GetMapIntInt64("Widths") if err != nil { return "", err } tmpStr, err := f.MakeWidthArray(widths) if err != nil { return "", err } str += tmpStr tmpInt64, err := info.GetInt64("UnderlinePosition") if err != nil { return "", err } str += fmt.Sprintf("\tme.up = %d\n", tmpInt64) tmpInt64, err = info.GetInt64("UnderlineThickness") if err != nil { return "", err } str += fmt.Sprintf("\tme.ut = %d\n", tmpInt64) str += "\tme.fonttype = \"" + fonttype + "\"\n" tmpStr, err = info.GetString("FontName") if err != nil { return "", err } str += fmt.Sprintf("\tme.name = \"%s\"\n", tmpStr) str += "\tme.enc = \"" + encode + "\"\n" diff, err := f.MakeFontEncoding(mappath, fontmaps) if err != nil { return "", err } if diff != "" { str += "\tme.diff = \"" + diff + "\"\n" } fd, err := f.MakeFontDescriptor(info) if err != nil { return "", err } str += fd str += "}\n" str += "func (me * " + gofontname + ")GetType() string{\n" str += "\treturn me.fonttype\n" str += "}\n" str += "func (me * " + gofontname + ")GetName() string{\n" str += "\treturn me.name\n" str += "} \n" str += "func (me * " + gofontname + ")GetDesc() []gopdf.FontDescItem{\n" str += "\treturn me.desc\n" str += "}\n" str += "func (me * " + gofontname + ")GetUp() int{\n" str += "\treturn me.up\n" str += "}\n" str += "func (me * " + gofontname + ")GetUt() int{\n" str += "\treturn me.ut\n" str += "}\n" str += "func (me * " + gofontname + ")GetCw() gopdf.FontCw{\n" str += "\treturn me.cw\n" str += "}\n" str += "func (me * " + gofontname + ")GetEnc() string{\n" str += "\treturn me.enc\n" str += "}\n" str += "func (me * " + gofontname + ")GetDiff() string {\n" str += "\treturn me.diff\n" str += "}\n" str += "func (me * " + gofontname + ") GetOriginalsize() int{\n" str += "\treturn 98764\n" str += "}\n" str += "func (me * " + gofontname + ") SetFamily(family string){\n" str += "\tme.family = family\n" str += "}\n" str += "func (me * " + gofontname + ") GetFamily() string{\n" str += "\treturn me.family\n" str += "}\n" err = ioutil.WriteFile(exportfile, []byte(str), 0666) if err != nil { return "", err } f.results = append(f.results, fmt.Sprintf("Save GO file at %s.", exportfile)) return str, nil } func (f *FontMaker) MakeFontDescriptor(info TtfInfo) (string, error) { fd := "" fd = "\tme.desc = make([]gopdf.FontDescItem,8)\n" // Ascent ascender, err := info.GetInt64("Ascender") if err != nil { return "", err } fd += fmt.Sprintf("\tme.desc[0] = gopdf.FontDescItem{ Key:\"Ascent\",Val : \"%d\" }\n", ascender) // Descent descender, err := info.GetInt64("Descender") if err != nil { return "", err } fd += fmt.Sprintf("\tme.desc[1] = gopdf.FontDescItem{ Key: \"Descent\", Val : \"%d\" }\n", descender) // CapHeight capHeight, err := info.GetInt64("CapHeight") if err == nil { fd += fmt.Sprintf("\tme.desc[2] = gopdf.FontDescItem{ Key:\"CapHeight\", Val : \"%d\" }\n", capHeight) } else if err == ERROR_NO_KEY_FOUND { fd += fmt.Sprintf("\tme.desc[2] = gopdf.FontDescItem{ Key:\"CapHeight\", Val : \"%d\" }\n", ascender) } else { return "", err } // Flags flags := 0 isFixedPitch, err := info.GetBool("IsFixedPitch") if err != nil { return "", err } if isFixedPitch { flags += 1 << 0 } flags += 1 << 5 italicAngle, err := info.GetInt64("ItalicAngle") if italicAngle != 0 { flags += 1 << 6 } fd += fmt.Sprintf("\tme.desc[3] = gopdf.FontDescItem{ Key: \"Flags\", Val : \"%d\" }\n", flags) //fmt.Printf("\n----\n") // FontBBox fbb, err := info.GetInt64s("FontBBox") if err != nil { return "", err } fd += fmt.Sprintf("\tme.desc[4] = gopdf.FontDescItem{ Key:\"FontBBox\", Val : \"[%d %d %d %d]\" }\n", fbb[0], fbb[1], fbb[2], fbb[3]) // ItalicAngle fd += fmt.Sprintf("\tme.desc[5] = gopdf.FontDescItem{ Key:\"ItalicAngle\", Val : \"%d\" }\n", italicAngle) // StemV stdVW, err := info.GetInt64("StdVW") issetStdVW := false if err == nil { issetStdVW = true } else if err == ERROR_NO_KEY_FOUND { issetStdVW = false } else { return "", err } bold, err := info.GetBool("Bold") if err != nil { return "", err } stemv := int(0) if issetStdVW { stemv = stdVW } else if bold { stemv = 120 } else { stemv = 70 } fd += fmt.Sprintf("\tme.desc[6] = gopdf.FontDescItem{ Key:\"StemV\", Val : \"%d\" }\n ", stemv) // MissingWidth missingWidth, err := info.GetInt64("MissingWidth") if err != nil { return "", err } fd += fmt.Sprintf("\tme.desc[7] = gopdf.FontDescItem{ Key:\"MissingWidth\", Val : \"%d\" } \n ", missingWidth) return fd, nil } func (f *FontMaker) MakeFontEncoding(mappath string, fontmaps []FontMap) (string, error) { refpath := mappath + "/cp1252.map" ref, err := f.LoadMap(refpath) if err != nil { return "", err } s := "" last := 0 for c := 0; c <= 255; c++ { if fontmaps[c].Name != ref[c].Name { if c != last+1 { s += fmt.Sprintf("%d ", c) } last = c s += "/" + fontmaps[c].Name + " " } } return strings.TrimSpace(s), nil } func (f *FontMaker) MakeWidthArray(widths map[int]int) (string, error) { str := "\tme.cw = make(gopdf.FontCw)\n" for c := 0; c <= 255; c++ { str += "\tme.cw[" chr := string(c) if chr == "\"" { str += "gopdf.ToByte(\"\\\"\")" } else if chr == "\\" { str += "gopdf.ToByte(\"\\\\\")" } else if c >= 32 && c <= 126 { str += "gopdf.ToByte(\"" + chr + "\")" } else { str += fmt.Sprintf("gopdf.Chr(%d)", c) } str += fmt.Sprintf("]=%d\n", widths[c]) } return str, nil } func (f *FontMaker) FileSize(path string) (int64, error) { file, err := os.Open(path) if err != nil { return -1, err } defer file.Close() // get the file size stat, err := file.Stat() if err != nil { return -1, err } return stat.Size(), nil } func (f *FontMaker) GetInfoFromTrueType(fontpath string, fontmaps []FontMap) (TtfInfo, error) { var parser TTFParser err := parser.Parse(fontpath) if err != nil { return nil, err } if !parser.Embeddable { return nil, ErrFontLicenseDoesNotAllowEmbedding } info := NewTtfInfo() fileContent, err := ioutil.ReadFile(fontpath) if err != nil { return nil, err } info.PushBytes("Data", fileContent) size, err := f.FileSize(fontpath) if err != nil { return nil, err } info.PushInt64("OriginalSize", size) k := float64(1000.0 / float64(parser.unitsPerEm)) info.PushString("FontName", parser.postScriptName) info.PushBool("Bold", parser.Bold) info.PushInt("ItalicAngle", parser.italicAngle) info.PushBool("IsFixedPitch", parser.isFixedPitch) info.PushInt("Ascender", f.MultiplyAndRound(k, parser.typoAscender)) info.PushInt("Descender", f.MultiplyAndRound(k, parser.typoDescender)) info.PushInt("UnderlineThickness", f.MultiplyAndRound(k, parser.underlineThickness)) info.PushInt("UnderlinePosition", f.MultiplyAndRound(k, parser.underlinePosition)) fontBBoxs := []int{ f.MultiplyAndRound(k, parser.xMin), f.MultiplyAndRound(k, parser.yMin), f.MultiplyAndRound(k, parser.xMax), f.MultiplyAndRound(k, parser.yMax), } info.PushInt64s("FontBBox", fontBBoxs) info.PushInt("CapHeight", f.MultiplyAndRound(k, parser.capHeight)) missingWidth := f.MultiplyAndRoundWithUInt64(k, parser.widths[0]) info.PushInt("MissingWidth", missingWidth) widths := make(map[int]int) max := 256 c := 0 for c < max { widths[c] = missingWidth c++ } c = 0 //reset for c < max { if fontmaps[c].Name != ".notdef" { uv := fontmaps[c].Uv if val, ok := parser.chars[int(uv)]; ok { w := parser.widths[val] widths[c] = f.MultiplyAndRoundWithUInt64(k, w) } else { f.results = append(f.results, fmt.Sprintf("Warning: Character %s (%d) is missing", fontmaps[c].Name, fontmaps[c].Uv)) } } c++ } info.PushMapIntInt64("Widths", widths) return info, nil } func (f *FontMaker) MultiplyAndRoundWithUInt64(k float64, v uint) int { r := k * float64(v) return f.Round(r) } func (f *FontMaker) MultiplyAndRound(k float64, v int) int { r := k * float64(v) return f.Round(r) } func (f *FontMaker) Round(value float64) int { return Round(value) } func (f *FontMaker) LoadMap(encodingpath string) ([]FontMap, error) { if _, err := os.Stat(encodingpath); os.IsNotExist(err) { return nil, err } var fontmaps []FontMap i := 0 max := 256 for i < max { fontmaps = append(fontmaps, FontMap{Uv: -1, Name: ".notdef"}) i++ } file, err := os.Open(encodingpath) if err != nil { return nil, err } defer file.Close() scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) e := strings.Split(line, " ") strC := (e[0])[1:] strUv := (e[1])[2:] c, err := strconv.ParseInt(strC, 16, 0) if err != nil { return nil, err } uv, err := strconv.ParseInt(strUv, 16, 0) if err != nil { return nil, err } name := e[2] //fmt.Println("strC = "+strC+"strUv = "+strUv+" c=%d , uv= %d", c, uv) fontmaps[c].Name = name fontmaps[c].Uv = int(uv) } return fontmaps, nil } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/fontmap.go ================================================ package core type FontMap struct { Uv int Name string } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/kern_table.go ================================================ package core //KernTable https://www.microsoft.com/typography/otspec/kern.htm type KernTable struct { Version uint //for debug NTables uint //for debug Kerning KernMap } //KernMap kerning map map[left]KernValue type KernMap map[uint]KernValue //KernValue kerning values map[right]value type KernValue map[uint]int16 //ValueByRight get value by right func (k KernValue) ValueByRight(right uint) (bool, int16) { if val, ok := k[uint(right)]; ok { return true, val } return false, 0 } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/math.go ================================================ package core func Round(value float64) int { if value < 0.0 { value -= 0.5 } else { value += 0.5 } return int(value) } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/table_directory_entry.go ================================================ package core type TableDirectoryEntry struct { CheckSum uint Offset uint Length uint } func (t TableDirectoryEntry) PaddedLength() int { l := int(t.Length) return (l + 3) & ^3 } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/ttf_info.go ================================================ package core import ( "errors" ) var ERROR_NO_KEY_FOUND = errors.New("no key found") var ERROR_NO_GET_WRONG_TYPE = errors.New("get wrong type") type TtfInfo map[string]interface{} func (t TtfInfo) PushString(key string, val string) { t[key] = val } func (t TtfInfo) PushBytes(key string, val []byte) { t[key] = val } func (t TtfInfo) PushInt64(key string, val int64) { t[key] = val } func (t TtfInfo) PushInt(key string, val int) { t[key] = val } func (t TtfInfo) PushUInt64(key string, val uint) { t[key] = val } func (t TtfInfo) PushBool(key string, val bool) { t[key] = val } func (t TtfInfo) PushInt64s(key string, val []int) { t[key] = val } func (t TtfInfo) PushMapIntInt64(key string, val map[int]int) { t[key] = val } func (t TtfInfo) GetBool(key string) (bool, error) { if val, ok := t[key]; ok { if m, ok := val.(bool); ok { /* act on str */ return m, nil } else { return false, ERROR_NO_GET_WRONG_TYPE } } else { return false, ERROR_NO_KEY_FOUND } } func (t TtfInfo) GetString(key string) (string, error) { if val, ok := t[key]; ok { if m, ok := val.(string); ok { /* act on str */ return m, nil } else { return "", ERROR_NO_GET_WRONG_TYPE } } else { return "", ERROR_NO_KEY_FOUND } } func (t TtfInfo) GetInt64(key string) (int, error) { if val, ok := t[key]; ok { if m, ok := val.(int); ok { /* act on str */ return m, nil } else { return 0, ERROR_NO_GET_WRONG_TYPE } } else { return 0, ERROR_NO_KEY_FOUND } } func (t TtfInfo) GetInt64s(key string) ([]int, error) { if val, ok := t[key]; ok { if m, ok := val.([]int); ok { /* act on str */ return m, nil } else { return nil, ERROR_NO_GET_WRONG_TYPE } } else { return nil, ERROR_NO_KEY_FOUND } } func (t TtfInfo) GetMapIntInt64(key string) (map[int]int, error) { if val, ok := t[key]; ok { if m, ok := val.(map[int]int); ok { /* act on str */ return m, nil } else { return nil, ERROR_NO_GET_WRONG_TYPE } } else { return nil, ERROR_NO_KEY_FOUND } } func NewTtfInfo() TtfInfo { info := make(TtfInfo) return info } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/ttfparser.go ================================================ package core import ( //"encoding/binary" //"encoding/hex" "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "os" "regexp" "strconv" "strings" ) var ERROR_NO_UNICODE_ENCODING_FOUND = errors.New("No Unicode encoding found") var ERROR_UNEXPECTED_SUBTABLE_FORMAT = errors.New("Unexpected subtable format") var ERROR_INCORRECT_MAGIC_NUMBER = errors.New("Incorrect magic number") var ERROR_POSTSCRIPT_NAME_NOT_FOUND = errors.New("PostScript name not found") //TTFParser true type font parser type TTFParser struct { tables map[string]TableDirectoryEntry //head unitsPerEm uint xMin int yMin int xMax int yMax int indexToLocFormat int //Hhea numberOfHMetrics uint ascender int descender int //end Hhea numGlyphs uint widths []uint chars map[int]uint postScriptName string //os2 os2Version uint Embeddable bool Bold bool typoAscender int typoDescender int capHeight int sxHeight int //post italicAngle int underlinePosition int underlineThickness int isFixedPitch bool sTypoLineGap int usWinAscent uint usWinDescent uint //cmap IsShortIndex bool LocaTable []uint SegCount uint StartCount []uint EndCount []uint IdRangeOffset []uint IdDelta []uint GlyphIdArray []uint symbol bool //cmap format 12 groupingTables []CmapFormat12GroupingTable //data of font cachedFontData []byte //kerning useKerning bool //user config for use or not use kerning kern *KernTable } var Symbolic = 1 << 2 var Nonsymbolic = (1 << 5) //Kern get KernTable func (t *TTFParser) Kern() *KernTable { return t.kern } //UnderlinePosition postion of underline func (t *TTFParser) UnderlinePosition() int { return t.underlinePosition } //GroupingTables get cmap format12 grouping table func (t *TTFParser) GroupingTables() []CmapFormat12GroupingTable { return t.groupingTables } //UnderlineThickness thickness of underline func (t *TTFParser) UnderlineThickness() int { return t.underlineThickness } func (t *TTFParser) XHeight() int { if t.os2Version >= 2 && t.sxHeight != 0 { return t.sxHeight } else { return int((0.66) * float64(t.ascender)) } } func (t *TTFParser) XMin() int { return t.xMin } func (t *TTFParser) YMin() int { return t.yMin } func (t *TTFParser) XMax() int { return t.xMax } func (t *TTFParser) YMax() int { return t.yMax } func (t *TTFParser) ItalicAngle() int { return t.italicAngle } func (t *TTFParser) Flag() int { flag := 0 if t.symbol { flag |= Symbolic } else { flag |= Nonsymbolic } return flag } func (t *TTFParser) Ascender() int { if t.typoAscender == 0 { return t.ascender } return int(t.usWinAscent) } func (t *TTFParser) Descender() int { if t.typoDescender == 0 { return t.descender } descender := int(t.usWinDescent) if t.descender < 0 { descender = descender * (-1) } return descender } func (t *TTFParser) TypoAscender() int { return t.typoAscender } func (t *TTFParser) TypoDescender() int { return t.typoDescender } //CapHeight https://en.wikipedia.org/wiki/Cap_height func (t *TTFParser) CapHeight() int { return t.capHeight } //NumGlyphs number of glyph func (t *TTFParser) NumGlyphs() uint { return t.numGlyphs } func (t *TTFParser) UnitsPerEm() uint { return t.unitsPerEm } func (t *TTFParser) NumberOfHMetrics() uint { return t.numberOfHMetrics } func (t *TTFParser) Widths() []uint { return t.widths } func (t *TTFParser) Chars() map[int]uint { return t.chars } func (t *TTFParser) GetTables() map[string]TableDirectoryEntry { return t.tables } //SetUseKerning set useKerning must set before Parse func (t *TTFParser) SetUseKerning(use bool) { t.useKerning = use } //Parse parse func (t *TTFParser) Parse(filepath string) error { data, err := ioutil.ReadFile(filepath) if err != nil { return err } return t.ParseFontData(data) } //ParseByReader parse by io.reader func (t *TTFParser) ParseByReader(rd io.Reader) error { fontData, err := ioutil.ReadAll(rd) if err != nil { return err } return t.ParseFontData(fontData) } // ParseFontData parses font data. func (t *TTFParser) ParseFontData(fontData []byte) error { fd := bytes.NewReader(fontData) version, err := t.Read(fd, 4) if err != nil { return err } if !bytes.Equal(version, []byte{0x00, 0x01, 0x00, 0x00}) { return errors.New("Unrecognized file (font) format") } i := uint(0) numTables, err := t.ReadUShort(fd) if err != nil { return err } t.Skip(fd, 3*2) //searchRange, entrySelector, rangeShift t.tables = make(map[string]TableDirectoryEntry) for i < numTables { tag, err := t.Read(fd, 4) if err != nil { return err } checksum, err := t.ReadULong(fd) if err != nil { return err } offset, err := t.ReadULong(fd) if err != nil { return err } length, err := t.ReadULong(fd) if err != nil { return err } //fmt.Printf("\n\ntag=%s \nOffset = %d\n", tag, offset) var table TableDirectoryEntry table.Offset = uint(offset) table.CheckSum = checksum table.Length = length //fmt.Printf("\n\ntag=%s \nOffset = %d\nPaddedLength =%d\n\n ", tag, table.Offset, table.PaddedLength()) t.tables[t.BytesToString(tag)] = table i++ } err = t.ParseHead(fd) if err != nil { return err } err = t.ParseHhea(fd) if err != nil { return err } err = t.ParseMaxp(fd) if err != nil { return err } err = t.ParseHmtx(fd) if err != nil { return err } err = t.ParseCmap(fd) if err != nil { return err } err = t.ParseName(fd) if err != nil { return err } err = t.ParseOS2(fd) if err != nil { return err } err = t.ParsePost(fd) if err != nil { return err } err = t.ParseLoca(fd) if err != nil { return err } if t.useKerning { err = t.Parsekern(fd) if err != nil { return err } } t.cachedFontData = fontData return nil } func (t *TTFParser) FontData() []byte { return t.cachedFontData } //ParseLoca parse loca table https://www.microsoft.com/typography/otspec/loca.htm func (t *TTFParser) ParseLoca(fd *bytes.Reader) error { t.IsShortIndex = false if t.indexToLocFormat == 0 { t.IsShortIndex = true } //fmt.Printf("indexToLocFormat = %d\n", me.indexToLocFormat) err := t.Seek(fd, "loca") if err != nil { return err } var locaTable []uint table := t.tables["loca"] if t.IsShortIndex { //do ShortIndex entries := table.Length / 2 i := uint(0) for i < entries { item, err := t.ReadUShort(fd) if err != nil { return err } locaTable = append(locaTable, item*2) i++ } } else { entries := table.Length / 4 i := uint(0) for i < entries { item, err := t.ReadULong(fd) if err != nil { return err } locaTable = append(locaTable, item) i++ } } t.LocaTable = locaTable return nil } //ParsePost parse post table https://www.microsoft.com/typography/otspec/post.htm func (t *TTFParser) ParsePost(fd *bytes.Reader) error { err := t.Seek(fd, "post") if err != nil { return err } err = t.Skip(fd, 4) // version if err != nil { return err } t.italicAngle, err = t.ReadShort(fd) if err != nil { return err } err = t.Skip(fd, 2) // Skip decimal part if err != nil { return err } t.underlinePosition, err = t.ReadShort(fd) if err != nil { return err } //fmt.Printf("start>>>>>>>\n") t.underlineThickness, err = t.ReadShort(fd) if err != nil { return err } //fmt.Printf("underlineThickness=%d\n", t.underlineThickness) //fmt.Printf(">>>>>>>%d\n", me.underlineThickness) isFixedPitch, err := t.ReadULong(fd) if err != nil { return err } t.isFixedPitch = (isFixedPitch != 0) return nil } //ParseOS2 parse OS2 table https://www.microsoft.com/typography/otspec/OS2.htm func (t *TTFParser) ParseOS2(fd *bytes.Reader) error { err := t.Seek(fd, "OS/2") if err != nil { return err } version, err := t.ReadUShort(fd) if err != nil { return err } t.os2Version = version err = t.Skip(fd, 3*2) // xAvgCharWidth, usWeightClass, usWidthClass if err != nil { return err } fsType, err := t.ReadUShort(fd) if err != nil { return err } t.Embeddable = (fsType != 2) && ((fsType & 0x200) == 0) err = t.Skip(fd, (11*2)+10+(4*4)+4) if err != nil { return err } fsSelection, err := t.ReadUShort(fd) if err != nil { return err } t.Bold = ((fsSelection & 32) != 0) err = t.Skip(fd, 2*2) // usFirstCharIndex, usLastCharIndex if err != nil { return err } t.typoAscender, err = t.ReadShort(fd) if err != nil { return err } t.typoDescender, err = t.ReadShort(fd) if err != nil { return err } t.sTypoLineGap, err = t.ReadShort(fd) if err != nil { return err } t.usWinAscent, err = t.ReadUShort(fd) if err != nil { return err } t.usWinDescent, err = t.ReadUShort(fd) if err != nil { return err } if version >= 2 { err = t.Skip(fd, 2*4) if err != nil { return err } t.sxHeight, err = t.ReadShort(fd) if err != nil { return err } t.capHeight, err = t.ReadShort(fd) if err != nil { return err } } else { t.capHeight = t.ascender } return nil } //ParseName parse name table https://www.microsoft.com/typography/otspec/name.htm func (t *TTFParser) ParseName(fd *bytes.Reader) error { //$this->Seek('name'); err := t.Seek(fd, "name") if err != nil { return err } tableOffset, err := t.FTell(fd) if err != nil { return err } t.postScriptName = "" err = t.Skip(fd, 2) // format if err != nil { return err } count, err := t.ReadUShort(fd) if err != nil { return err } stringOffset, err := t.ReadUShort(fd) if err != nil { return err } for i := 0; i < int(count); i++ { err = t.Skip(fd, 3*2) // platformID, encodingID, languageID if err != nil { return err } nameID, err := t.ReadUShort(fd) if err != nil { return err } length, err := t.ReadUShort(fd) if err != nil { return err } offset, err := t.ReadUShort(fd) if err != nil { return err } if nameID == 6 { // PostScript name _, err = fd.Seek(int64(tableOffset+stringOffset+offset), 0) if err != nil { return err } stmp, err := t.Read(fd, int(length)) if err != nil { return err } var tmpStmp []byte for _, v := range stmp { if v != 0 { tmpStmp = append(tmpStmp, v) } } s := fmt.Sprintf("%s", string(tmpStmp)) //strings(stmp) s = strings.Replace(s, strconv.Itoa(0), "", -1) s, err = t.PregReplace("|[ \\[\\](){}<>/%]|", "", s) if err != nil { return err } t.postScriptName = s break } } if t.postScriptName == "" { return ERROR_POSTSCRIPT_NAME_NOT_FOUND } //fmt.Printf("%s\n", me.postScriptName) return nil } func (t *TTFParser) PregReplace(pattern string, replacement string, subject string) (string, error) { reg, err := regexp.Compile(pattern) if err != nil { return "", err } str := reg.ReplaceAllString(subject, replacement) return str, nil } //ParseCmap parse cmap table format 4 https://www.microsoft.com/typography/otspec/cmap.htm func (t *TTFParser) ParseCmap(fd *bytes.Reader) error { t.Seek(fd, "cmap") t.Skip(fd, 2) // version numTables, err := t.ReadUShort(fd) if err != nil { return err } offset31 := uint(0) for i := 0; i < int(numTables); i++ { platformID, err := t.ReadUShort(fd) if err != nil { return err } encodingID, err := t.ReadUShort(fd) if err != nil { return err } offset, err := t.ReadULong(fd) if err != nil { return err } t.symbol = false //init if platformID == 3 && encodingID == 1 { if encodingID == 0 { t.symbol = true } offset31 = offset } //fmt.Printf("me.symbol=%d\n", me.symbol) } //end for if offset31 == 0 { //No Unicode encoding found return ERROR_NO_UNICODE_ENCODING_FOUND } var startCount, endCount, idDelta, idRangeOffset, glyphIDArray []uint _, err = fd.Seek(int64(t.tables["cmap"].Offset+offset31), 0) if err != nil { return err } format, err := t.ReadUShort(fd) if err != nil { return err } if format != 4 { //Unexpected subtable format return ERROR_UNEXPECTED_SUBTABLE_FORMAT } length, err := t.ReadUShort(fd) if err != nil { return err } //fmt.Printf("\nlength=%d\n", length) err = t.Skip(fd, 2) // language if err != nil { return err } segCount, err := t.ReadUShort(fd) if err != nil { return err } segCount = segCount / 2 t.SegCount = segCount err = t.Skip(fd, 3*2) // searchRange, entrySelector, rangeShift if err != nil { return err } glyphCount := (length - (16 + 8*segCount)) / 2 //fmt.Printf("\nglyphCount=%d\n", glyphCount) for i := 0; i < int(segCount); i++ { tmp, err := t.ReadUShort(fd) if err != nil { return err } endCount = append(endCount, tmp) } t.EndCount = endCount err = t.Skip(fd, 2) // reservedPad if err != nil { return err } for i := 0; i < int(segCount); i++ { tmp, err := t.ReadUShort(fd) if err != nil { return err } startCount = append(startCount, tmp) } t.StartCount = startCount for i := 0; i < int(segCount); i++ { tmp, err := t.ReadUShort(fd) if err != nil { return err } idDelta = append(idDelta, tmp) } t.IdDelta = idDelta offset, err := t.FTell(fd) if err != nil { return err } for i := 0; i < int(segCount); i++ { tmp, err := t.ReadUShort(fd) if err != nil { return err } idRangeOffset = append(idRangeOffset, tmp) } t.IdRangeOffset = idRangeOffset //_ = glyphIdArray for i := 0; i < int(glyphCount); i++ { tmp, err := t.ReadUShort(fd) if err != nil { return err } glyphIDArray = append(glyphIDArray, tmp) } t.GlyphIdArray = glyphIDArray t.chars = make(map[int]uint) for i := 0; i < int(segCount); i++ { c1 := startCount[i] c2 := endCount[i] d := idDelta[i] ro := idRangeOffset[i] if ro > 0 { _, err = fd.Seek(int64(offset+uint(2*i)+ro), 0) if err != nil { return err } } for c := c1; c <= c2; c++ { var gid uint if c == 0xFFFF { break } if ro > 0 { gid, err = t.ReadUShort(fd) if err != nil { return err } if gid > 0 { gid += d } } else { gid = c + d } if gid >= 65536 { gid -= 65536 } if gid > 0 { //fmt.Printf("%d gid = %d, ", int(c), gid) t.chars[int(c)] = gid } } } _, err = t.ParseCmapFormat12(fd) if err != nil { return err } return nil } func (t *TTFParser) FTell(fd *bytes.Reader) (uint, error) { offset, err := fd.Seek(0, os.SEEK_CUR) return uint(offset), err } //ParseHmtx parse hmtx table https://www.microsoft.com/typography/otspec/hmtx.htm func (t *TTFParser) ParseHmtx(fd *bytes.Reader) error { t.Seek(fd, "hmtx") i := uint(0) for i < t.numberOfHMetrics { advanceWidth, err := t.ReadUShort(fd) if err != nil { return err } err = t.Skip(fd, 2) if err != nil { return err } t.widths = append(t.widths, advanceWidth) i++ } if t.numberOfHMetrics < t.numGlyphs { var err error lastWidth := t.widths[t.numberOfHMetrics-1] t.widths, err = t.ArrayPadUint(t.widths, t.numGlyphs, lastWidth) if err != nil { return err } } return nil } func (t *TTFParser) ArrayPadUint(arr []uint, size uint, val uint) ([]uint, error) { var result []uint i := uint(0) for i < size { if int(i) < len(arr) { result = append(result, arr[i]) } else { result = append(result, val) } i++ } return result, nil } //ParseHead parse head table https://www.microsoft.com/typography/otspec/Head.htm func (t *TTFParser) ParseHead(fd *bytes.Reader) error { //fmt.Printf("\nParseHead\n") err := t.Seek(fd, "head") if err != nil { return err } err = t.Skip(fd, 3*4) // version, fontRevision, checkSumAdjustment if err != nil { return err } magicNumber, err := t.ReadULong(fd) if err != nil { return err } //fmt.Printf("\nmagicNumber = %d\n", magicNumber) if magicNumber != 0x5F0F3CF5 { return ERROR_INCORRECT_MAGIC_NUMBER } err = t.Skip(fd, 2) if err != nil { return err } t.unitsPerEm, err = t.ReadUShort(fd) if err != nil { return err } err = t.Skip(fd, 2*8) // created, modified if err != nil { return err } t.xMin, err = t.ReadShort(fd) if err != nil { return err } t.yMin, err = t.ReadShort(fd) if err != nil { return err } t.xMax, err = t.ReadShort(fd) if err != nil { return err } t.yMax, err = t.ReadShort(fd) if err != nil { return err } err = t.Skip(fd, 2*3) //skip macStyle,lowestRecPPEM,fontDirectionHint if err != nil { return err } t.indexToLocFormat, err = t.ReadShort(fd) if err != nil { return err } return nil } //ParseHhea parse hhea table https://www.microsoft.com/typography/otspec/hhea.htm func (t *TTFParser) ParseHhea(fd *bytes.Reader) error { err := t.Seek(fd, "hhea") if err != nil { return err } err = t.Skip(fd, 4) //skip version if err != nil { return err } t.ascender, err = t.ReadShort(fd) if err != nil { return err } t.descender, err = t.ReadShort(fd) if err != nil { return err } err = t.Skip(fd, 13*2) if err != nil { return err } t.numberOfHMetrics, err = t.ReadUShort(fd) if err != nil { return err } return nil } //ParseMaxp parse maxp table https://www.microsoft.com/typography/otspec/Maxp.htm func (t *TTFParser) ParseMaxp(fd *bytes.Reader) error { err := t.Seek(fd, "maxp") if err != nil { return err } err = t.Skip(fd, 4) if err != nil { return err } t.numGlyphs, err = t.ReadUShort(fd) if err != nil { return err } return nil } //ErrTableNotFound error table not found var ErrTableNotFound = errors.New("table not found") //Seek seek by tag func (t *TTFParser) Seek(fd *bytes.Reader, tag string) error { table, ok := t.tables[tag] if !ok { return ErrTableNotFound } val := table.Offset _, err := fd.Seek(int64(val), 0) if err != nil { return err } return nil } //BytesToString convert bytes to string func (t *TTFParser) BytesToString(b []byte) string { return string(b) //strings.TrimSpace(string(b)) } //ReadUShort read ushort func (t *TTFParser) ReadUShort(fd *bytes.Reader) (uint, error) { buff, err := t.Read(fd, 2) if err != nil { return 0, err } n := binary.BigEndian.Uint16(buff) return uint(n), nil } //ReadShort read short func (t *TTFParser) ReadShort(fd *bytes.Reader) (int, error) { u, err := t.ReadUShort(fd) if err != nil { return 0, err } //fmt.Printf("%#v\n", buff) var v int if u >= 0x8000 { v = int(u) - 65536 } else { v = int(u) } return v, nil } //ReadShortInt16 read short return int16 func (t *TTFParser) ReadShortInt16(fd *bytes.Reader) (int16, error) { n, err := t.ReadShort(fd) if err != nil { return 0, err } return int16(n), nil } //ReadULong read ulong func (t *TTFParser) ReadULong(fd *bytes.Reader) (uint, error) { buff, err := t.Read(fd, 4) //fmt.Printf("%#v\n", buff) if err != nil { return 0, err } n := binary.BigEndian.Uint32(buff) return uint(n), nil } //Skip skip func (t *TTFParser) Skip(fd *bytes.Reader, length int) error { _, err := fd.Seek(int64(length), 1) if err != nil { return err } return nil } //Read read func (t *TTFParser) Read(fd *bytes.Reader, length int) ([]byte, error) { buff := make([]byte, length) readlength, err := fd.Read(buff) if err != nil { return nil, err } if readlength != length { return nil, errors.New("file out of length") } //fmt.Printf("%d,%s\n", readlength, string(buff)) return buff, nil } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/ttfparser_cmap_other_format.go ================================================ package core import ( "bytes" "errors" ) //ParseCmapFormat12 parse cmap table format 12 https://www.microsoft.com/typography/otspec/cmap.htm func (t *TTFParser) ParseCmapFormat12(fd *bytes.Reader) (bool, error) { t.Seek(fd, "cmap") t.Skip(fd, 2) //skip version numTables, err := t.ReadUShort(fd) if err != nil { return false, err } var cEncodingSubtables []cmapFormat12EncodingSubtable for i := 0; i < int(numTables); i++ { platformID, err := t.ReadUShort(fd) if err != nil { return false, err } encodingID, err := t.ReadUShort(fd) if err != nil { return false, err } offset, err := t.ReadULong(fd) if err != nil { return false, err } var ce cmapFormat12EncodingSubtable ce.platformID = platformID ce.encodingID = encodingID ce.offset = offset cEncodingSubtables = append(cEncodingSubtables, ce) } isFound := false offset := uint(0) for _, ce := range cEncodingSubtables { if ce.platformID == 3 && ce.encodingID == 10 { offset = ce.offset isFound = true break } } if !isFound { return false, nil } _, err = fd.Seek(int64(t.tables["cmap"].Offset+offset), 0) if err != nil { return false, err } format, err := t.ReadUShort(fd) if err != nil { return false, err } if format != 12 { return false, errors.New("format != 12") } reserved, err := t.ReadUShort(fd) if err != nil { return false, err } if reserved != 0 { return false, errors.New("reserved != 0") } err = t.Skip(fd, 4) //skip length if err != nil { return false, err } err = t.Skip(fd, 4) //skip language if err != nil { return false, err } nGroups, err := t.ReadULong(fd) if err != nil { return false, err } g := uint(0) for g < nGroups { startCharCode, err := t.ReadULong(fd) if err != nil { return false, err } endCharCode, err := t.ReadULong(fd) if err != nil { return false, err } glyphID, err := t.ReadULong(fd) if err != nil { return false, err } var gTb CmapFormat12GroupingTable gTb.StartCharCode = startCharCode gTb.EndCharCode = endCharCode gTb.GlyphID = glyphID t.groupingTables = append(t.groupingTables, gTb) g++ } return true, nil } type cmapFormat12EncodingSubtable struct { platformID uint encodingID uint offset uint } type CmapFormat12GroupingTable struct { StartCharCode, EndCharCode, GlyphID uint } ================================================ FILE: vendor/github.com/signintech/gopdf/fontmaker/core/ttfparser_kern.go ================================================ package core import ( "bytes" "fmt" ) //Parsekern parse kerning table https://www.microsoft.com/typography/otspec/kern.htm func (t *TTFParser) Parsekern(fd *bytes.Reader) error { t.kern = nil //clear err := t.Seek(fd, "kern") if err == ErrTableNotFound { return nil } else if err != nil { return err } t.kern = new(KernTable) //init version, err := t.ReadUShort(fd) if err != nil { return err } t.kern.Version = version nTables, err := t.ReadUShort(fd) if err != nil { return err } t.kern.NTables = nTables i := uint(0) for i < nTables { err = t.parsekernSubTable(fd) if err != nil { return err } i++ } return nil } func (t *TTFParser) parsekernSubTable(fd *bytes.Reader) error { t.Skip(fd, 2+2) //skip version and length coverage, err := t.ReadUShort(fd) if err != nil { return err } format := coverage & 0xf0 //fmt.Printf("format = %d\n", format) //debug t.kern.Kerning = make(KernMap) //init if format == 0 { t.parsekernSubTableFormat0(fd) } else { //not support other format yet return fmt.Errorf("not support kerning format %d", format) } return nil } func (t *TTFParser) parsekernSubTableFormat0(fd *bytes.Reader) error { nPairs, err := t.ReadUShort(fd) if err != nil { return err } t.Skip(fd, 2+2+2) //skip searchRange , entrySelector , rangeShift i := uint(0) for i < nPairs { left, err := t.ReadUShort(fd) if err != nil { return err } right, err := t.ReadUShort(fd) if err != nil { return err } value, err := t.ReadShortInt16(fd) if err != nil { return err } if _, ok := t.kern.Kerning[left]; !ok { kval := make(KernValue) kval[right] = value t.kern.Kerning[left] = kval } else { (t.kern.Kerning[left])[right] = value } //_ = fmt.Sprintf("nPairs %d left %d right %d value %d\n", nPairs, left, right, value) //debug i++ } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/func_kern_override.go ================================================ package gopdf //FuncKernOverride return your custome pair value type FuncKernOverride func( leftRune rune, rightRune rune, leftPair uint, rightPair uint, pairVal int16, ) int16 ================================================ FILE: vendor/github.com/signintech/gopdf/go.mod ================================================ module github.com/signintech/gopdf go 1.11 require ( github.com/phpdave11/gofpdi v1.0.11 github.com/pkg/errors v0.8.1 ) ================================================ FILE: vendor/github.com/signintech/gopdf/go.sum ================================================ github.com/phpdave11/gofpdi v1.0.11 h1:wsBNx+3S0wy1dEp6fzv281S74ogZGgIdYWV2PugWgho= github.com/phpdave11/gofpdi v1.0.11/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= ================================================ FILE: vendor/github.com/signintech/gopdf/gopdf.go ================================================ package gopdf import ( "bufio" "bytes" "compress/zlib" // for constants "fmt" "image" "image/jpeg" "io" "io/ioutil" "log" "os" "strconv" "time" "github.com/phpdave11/gofpdi" "github.com/pkg/errors" ) const subsetFont = "SubsetFont" // the default margin if no margins are set const defaultMargin = 10.0 //for backward compatible //GoPdf : A simple library for generating PDF written in Go lang type GoPdf struct { //page Margin //leftMargin float64 //topMargin float64 margins Margins pdfObjs []IObj config Config anchors map[string]anchorOption indexOfCatalogObj int /*---index ของ obj สำคัญๆ เก็บเพื่อลด loop ตอนค้นหา---*/ //index ของ obj pages indexOfPagesObj int //number of pages numOfPagesObj int //index ของ obj page อันแรก indexOfFirstPageObj int //ต่ำแหน่งปัจจุบัน curr Current indexEncodingObjFonts []int indexOfContent int //index ของ procset ซึ่งควรจะมีอันเดียว indexOfProcSet int //IsUnderline bool // Buffer for io.Reader compliance buf bytes.Buffer //pdf PProtection pdfProtection *PDFProtection encryptionObjID int // content streams only compressLevel int //info isUseInfo bool info *PdfInfo //outlines outlines *OutlinesObj indexOfOutlinesObj int // gofpdi free pdf document importer fpdi *gofpdi.Importer } type DrawableRectOptions struct { Rect X float64 Y float64 PaintStyle PaintStyle Transparency *Transparency extGStateIndexes []int } type CropOptions struct { X float64 Y float64 Width float64 Height float64 } type ImageOptions struct { VerticalFlip bool HorizontalFlip bool X float64 Y float64 Rect *Rect Mask *MaskOptions Crop *CropOptions Transparency *Transparency extGStateIndexes []int } type MaskOptions struct { ImageOptions Holder ImageHolder } //SetLineWidth : set line width func (gp *GoPdf) SetLineWidth(width float64) { gp.curr.lineWidth = gp.UnitsToPoints(width) gp.getContent().AppendStreamSetLineWidth(gp.UnitsToPoints(width)) } //SetCompressLevel : set compress Level for content streams // Possible values for level: // -2 HuffmanOnly, -1 DefaultCompression (which is level 6) // 0 No compression, // 1 fastest compression, but not very good ratio // 9 best compression, but slowest func (gp *GoPdf) SetCompressLevel(level int) { errfmt := "compress level too %s, using %s instead\n" if level < -2 { //-2 = zlib.HuffmanOnly fmt.Fprintf(os.Stderr, errfmt, "small", "DefaultCompression") level = zlib.DefaultCompression } else if level > zlib.BestCompression { fmt.Fprintf(os.Stderr, errfmt, "big", "BestCompression") level = zlib.BestCompression return } // sanity check complete gp.compressLevel = level } //SetNoCompression : compressLevel = 0 func (gp *GoPdf) SetNoCompression() { gp.compressLevel = zlib.NoCompression } //SetLineType : set line type ("dashed" ,"dotted") // Usage: // pdf.SetLineType("dashed") // pdf.Line(50, 200, 550, 200) // pdf.SetLineType("dotted") // pdf.Line(50, 400, 550, 400) func (gp *GoPdf) SetLineType(linetype string) { gp.getContent().AppendStreamSetLineType(linetype) } //Line : draw line func (gp *GoPdf) Line(x1 float64, y1 float64, x2 float64, y2 float64) { gp.UnitsToPointsVar(&x1, &y1, &x2, &y2) gp.getContent().AppendStreamLine(x1, y1, x2, y2) } //RectFromLowerLeft : draw rectangle from lower-left corner (x, y) func (gp *GoPdf) RectFromLowerLeft(x float64, y float64, wdth float64, hght float64) { gp.UnitsToPointsVar(&x, &y, &wdth, &hght) opts := DrawableRectOptions{ X: x, Y: y, PaintStyle: DrawPaintStyle, Rect: Rect{W: wdth, H: hght}, } gp.getContent().AppendStreamRectangle(opts) } //RectFromUpperLeft : draw rectangle from upper-left corner (x, y) func (gp *GoPdf) RectFromUpperLeft(x float64, y float64, wdth float64, hght float64) { gp.UnitsToPointsVar(&x, &y, &wdth, &hght) opts := DrawableRectOptions{ X: x, Y: y + hght, PaintStyle: DrawPaintStyle, Rect: Rect{W: wdth, H: hght}, } gp.getContent().AppendStreamRectangle(opts) } //RectFromLowerLeftWithStyle : draw rectangle from lower-left corner (x, y) // - style: Style of rectangule (draw and/or fill: D, F, DF, FD) // D or empty string: draw. This is the default value. // F: fill // DF or FD: draw and fill func (gp *GoPdf) RectFromLowerLeftWithStyle(x float64, y float64, wdth float64, hght float64, style string) { opts := DrawableRectOptions{ X: x, Y: y, Rect: Rect{ H: hght, W: wdth, }, PaintStyle: parseStyle(style), } gp.RectFromLowerLeftWithOpts(opts) } func (gp *GoPdf) RectFromLowerLeftWithOpts(opts DrawableRectOptions) error { gp.UnitsToPointsVar(&opts.X, &opts.Y, &opts.W, &opts.H) imageTransparency, err := gp.getCachedTransparency(opts.Transparency) if err != nil { return err } if imageTransparency != nil { opts.extGStateIndexes = append(opts.extGStateIndexes, imageTransparency.extGStateIndex) } gp.getContent().AppendStreamRectangle(opts) return nil } //RectFromUpperLeftWithStyle : draw rectangle from upper-left corner (x, y) // - style: Style of rectangule (draw and/or fill: D, F, DF, FD) // D or empty string: draw. This is the default value. // F: fill // DF or FD: draw and fill func (gp *GoPdf) RectFromUpperLeftWithStyle(x float64, y float64, wdth float64, hght float64, style string) { opts := DrawableRectOptions{ X: x, Y: y, Rect: Rect{ H: hght, W: wdth, }, PaintStyle: parseStyle(style), } gp.RectFromUpperLeftWithOpts(opts) } func (gp *GoPdf) RectFromUpperLeftWithOpts(opts DrawableRectOptions) error { gp.UnitsToPointsVar(&opts.X, &opts.Y, &opts.W, &opts.H) opts.Y += opts.H imageTransparency, err := gp.getCachedTransparency(opts.Transparency) if err != nil { return err } if imageTransparency != nil { opts.extGStateIndexes = append(opts.extGStateIndexes, imageTransparency.extGStateIndex) } gp.getContent().AppendStreamRectangle(opts) return nil } //Oval : draw oval func (gp *GoPdf) Oval(x1 float64, y1 float64, x2 float64, y2 float64) { gp.UnitsToPointsVar(&x1, &y1, &x2, &y2) gp.getContent().AppendStreamOval(x1, y1, x2, y2) } //Br : new line func (gp *GoPdf) Br(h float64) { gp.UnitsToPointsVar(&h) gp.curr.Y += h gp.curr.X = gp.margins.Left } //SetGrayFill set the grayscale for the fill, takes a float64 between 0.0 and 1.0 func (gp *GoPdf) SetGrayFill(grayScale float64) { gp.curr.txtColorMode = "gray" gp.curr.grayFill = grayScale gp.getContent().AppendStreamSetGrayFill(grayScale) } //SetGrayStroke set the grayscale for the stroke, takes a float64 between 0.0 and 1.0 func (gp *GoPdf) SetGrayStroke(grayScale float64) { gp.curr.grayStroke = grayScale gp.getContent().AppendStreamSetGrayStroke(grayScale) } //SetX : set current position X func (gp *GoPdf) SetX(x float64) { gp.UnitsToPointsVar(&x) gp.curr.setXCount++ gp.curr.X = x } //GetX : get current position X func (gp *GoPdf) GetX() float64 { return gp.PointsToUnits(gp.curr.X) } //SetY : set current position y func (gp *GoPdf) SetY(y float64) { gp.UnitsToPointsVar(&y) gp.curr.Y = y } //GetY : get current position y func (gp *GoPdf) GetY() float64 { return gp.PointsToUnits(gp.curr.Y) } //ImageByHolder : draw image by ImageHolder func (gp *GoPdf) ImageByHolder(img ImageHolder, x float64, y float64, rect *Rect) error { gp.UnitsToPointsVar(&x, &y) rect = rect.UnitsToPoints(gp.config.Unit) imageOptions := ImageOptions{ X: x, Y: y, Rect: rect, } return gp.imageByHolder(img, imageOptions) } func (gp *GoPdf) ImageByHolderWithOptions(img ImageHolder, opts ImageOptions) error { gp.UnitsToPointsVar(&opts.X, &opts.Y) opts.Rect = opts.Rect.UnitsToPoints(gp.config.Unit) imageTransparency, err := gp.getCachedTransparency(opts.Transparency) if err != nil { return err } if imageTransparency != nil { opts.extGStateIndexes = append(opts.extGStateIndexes, imageTransparency.extGStateIndex) } if opts.Mask != nil { maskTransparency, err := gp.getCachedTransparency(opts.Mask.ImageOptions.Transparency) if err != nil { return err } if maskTransparency != nil { opts.Mask.ImageOptions.extGStateIndexes = append(opts.Mask.ImageOptions.extGStateIndexes, maskTransparency.extGStateIndex) } gp.UnitsToPointsVar(&opts.Mask.ImageOptions.X, &opts.Mask.ImageOptions.Y) opts.Mask.ImageOptions.Rect = opts.Mask.ImageOptions.Rect.UnitsToPoints(gp.config.Unit) extGStateIndex, err := gp.maskHolder(opts.Mask.Holder, opts.Mask.ImageOptions) if err != nil { return err } opts.extGStateIndexes = append(opts.extGStateIndexes, extGStateIndex) } return gp.imageByHolder(img, opts) } func (gp *GoPdf) maskHolder(img ImageHolder, opts ImageOptions) (int, error) { var cacheImage *ImageCache var cacheContentImage *cacheContentImage for _, imgcache := range gp.curr.ImgCaches { if img.ID() == imgcache.Path { cacheImage = &imgcache break } } if cacheImage == nil { maskImgobj := &ImageObj{IsMask: true} maskImgobj.init(func() *GoPdf { return gp }) maskImgobj.setProtection(gp.protection()) err := maskImgobj.SetImage(img) if err != nil { return 0, err } if opts.Rect == nil { if opts.Rect, err = maskImgobj.getRect(); err != nil { return 0, err } } if err := maskImgobj.parse(); err != nil { return 0, err } if gp.indexOfProcSet != -1 { index := gp.addObj(maskImgobj) cacheContentImage = gp.getContent().GetCacheContentImage(index, opts) procset := gp.pdfObjs[gp.indexOfProcSet].(*ProcSetObj) procset.RelateXobjs = append(procset.RelateXobjs, RelateXobject{IndexOfObj: index}) imgcache := ImageCache{ Index: index, Path: img.ID(), Rect: opts.Rect, } gp.curr.ImgCaches[index] = imgcache gp.curr.CountOfImg++ } } else { if opts.Rect == nil { opts.Rect = gp.curr.ImgCaches[cacheImage.Index].Rect } cacheContentImage = gp.getContent().GetCacheContentImage(cacheImage.Index, opts) } if cacheContentImage != nil { extGStateInd, err := gp.createTransparencyXObjectGroup(cacheContentImage, opts) if err != nil { return 0, err } return extGStateInd, nil } return 0, errors.New("cacheContentImage is undefined") } func (gp *GoPdf) createTransparencyXObjectGroup(image *cacheContentImage, opts ImageOptions) (int, error) { groupOpts := TransparencyXObjectGroupOptions{ ExtGStateIndexes: opts.extGStateIndexes, XObjects: []cacheContentImage{*image}, BBox: [4]float64{ // correct BBox values is [opts.X, gp.curr.pageSize.H - opts.Y - opts.Rect.H, opts.X + opts.Rect.W, gp.curr.pageSize.H - opts.Y] // but if compress pdf through ghostscript result file can't open correctly in mac viewer, because mac viewer can't parse BBox value correctly // all other viewers parse BBox correctly (like Adobe Acrobat Reader, Chrome, even Internet Explorer) // that's why we need to set [0, 0, gp.curr.pageSize.W, gp.curr.pageSize.H] 0, 0, gp.curr.pageSize.W, gp.curr.pageSize.H, }, } transparencyXObjectGroup, err := GetCachedTransparencyXObjectGroup(groupOpts, gp) if err != nil { return 0, err } sMaskOptions := SMaskOptions{ Subtype: SMaskLuminositySubtype, TransparencyXObjectGroupIndex: transparencyXObjectGroup.Index, } sMask := GetCachedMask(sMaskOptions, gp) extGStateOpts := ExtGStateOptions{SMaskIndex: &sMask.Index} extGState, err := GetCachedExtGState(extGStateOpts, gp) if err != nil { return 0, err } return extGState.Index + 1, nil } func (gp *GoPdf) imageByHolder(img ImageHolder, opts ImageOptions) error { cacheImageIndex := -1 for _, imgcache := range gp.curr.ImgCaches { if img.ID() == imgcache.Path { cacheImageIndex = imgcache.Index break } } if cacheImageIndex == -1 { //new image //create img object imgobj := new(ImageObj) if opts.Mask != nil { imgobj.SplittedMask = true } imgobj.init(func() *GoPdf { return gp }) imgobj.setProtection(gp.protection()) err := imgobj.SetImage(img) if err != nil { return err } if opts.Rect == nil { if opts.Rect, err = imgobj.getRect(); err != nil { return err } } err = imgobj.parse() if err != nil { return err } index := gp.addObj(imgobj) if gp.indexOfProcSet != -1 { //ยัดรูป procset := gp.pdfObjs[gp.indexOfProcSet].(*ProcSetObj) gp.getContent().AppendStreamImage(index, opts) procset.RelateXobjs = append(procset.RelateXobjs, RelateXobject{IndexOfObj: index}) //เก็บข้อมูลรูปเอาไว้ var imgcache ImageCache imgcache.Index = index imgcache.Path = img.ID() imgcache.Rect = opts.Rect gp.curr.ImgCaches[index] = imgcache gp.curr.CountOfImg++ } if imgobj.haveSMask() { smaskObj, err := imgobj.createSMask() if err != nil { return err } imgobj.imginfo.smarkObjID = gp.addObj(smaskObj) } if imgobj.isColspaceIndexed() { dRGB, err := imgobj.createDeviceRGB() if err != nil { return err } dRGB.getRoot = func() *GoPdf { return gp } imgobj.imginfo.deviceRGBObjID = gp.addObj(dRGB) } } else { //same img if opts.Rect == nil { opts.Rect = gp.curr.ImgCaches[cacheImageIndex].Rect } gp.getContent().AppendStreamImage(cacheImageIndex, opts) } return nil } //Image : draw image func (gp *GoPdf) Image(picPath string, x float64, y float64, rect *Rect) error { gp.UnitsToPointsVar(&x, &y) rect = rect.UnitsToPoints(gp.config.Unit) imgh, err := ImageHolderByPath(picPath) if err != nil { return err } imageOptions := ImageOptions{ X: x, Y: y, Rect: rect, } return gp.imageByHolder(imgh, imageOptions) } func (gp *GoPdf) ImageFrom(img image.Image, x float64, y float64, rect *Rect) error { gp.UnitsToPointsVar(&x, &y) rect = rect.UnitsToPoints(gp.config.Unit) r, w := io.Pipe() go func() { bw := bufio.NewWriter(w) err := jpeg.Encode(bw, img, nil) bw.Flush() if err != nil { w.CloseWithError(err) } else { w.Close() } }() imgh, err := ImageHolderByReader(bufio.NewReader(r)) if err != nil { return err } imageOptions := ImageOptions{ X: x, Y: y, Rect: rect, } return gp.imageByHolder(imgh, imageOptions) } //AddPage : add new page func (gp *GoPdf) AddPage() { emptyOpt := PageOption{} gp.AddPageWithOption(emptyOpt) } //AddPageWithOption : add new page with option func (gp *GoPdf) AddPageWithOption(opt PageOption) { opt.TrimBox = opt.TrimBox.UnitsToPoints(gp.config.Unit) opt.PageSize = opt.PageSize.UnitsToPoints(gp.config.Unit) page := new(PageObj) page.init(func() *GoPdf { return gp }) if !opt.isEmpty() { //use page option page.setOption(opt) gp.curr.pageSize = opt.PageSize if opt.isTrimBoxSet() { gp.curr.trimBox = opt.TrimBox } } else { //use default gp.curr.pageSize = &gp.config.PageSize gp.curr.trimBox = &gp.config.TrimBox } page.ResourcesRelate = strconv.Itoa(gp.indexOfProcSet+1) + " 0 R" index := gp.addObj(page) if gp.indexOfFirstPageObj == -1 { gp.indexOfFirstPageObj = index } gp.curr.IndexOfPageObj = index gp.numOfPagesObj++ //reset gp.indexOfContent = -1 gp.resetCurrXY() } func (gp *GoPdf) AddOutline(title string) { gp.outlines.AddOutline(gp.curr.IndexOfPageObj+1, title) } //Start : init gopdf func (gp *GoPdf) Start(config Config) { gp.config = config gp.init() //สร้าง obj พื้นฐาน catalog := new(CatalogObj) catalog.init(func() *GoPdf { return gp }) pages := new(PagesObj) pages.init(func() *GoPdf { return gp }) gp.outlines = new(OutlinesObj) gp.outlines.init(func() *GoPdf { return gp }) gp.indexOfCatalogObj = gp.addObj(catalog) gp.indexOfPagesObj = gp.addObj(pages) gp.indexOfOutlinesObj = gp.addObj(gp.outlines) gp.outlines.SetIndexObjOutlines(gp.indexOfOutlinesObj) //indexOfProcSet procset := new(ProcSetObj) procset.init(func() *GoPdf { return gp }) gp.indexOfProcSet = gp.addObj(procset) if gp.isUseProtection() { gp.pdfProtection = gp.createProtection() } } // SetFontWithStyle : set font style support Regular or Underline // for Bold|Italic should be loaded apropriate fonts with same styles defined func (gp *GoPdf) SetFontWithStyle(family string, style int, size int) error { found := false i := 0 max := len(gp.pdfObjs) for i < max { if gp.pdfObjs[i].getType() == subsetFont { obj := gp.pdfObjs[i] sub, ok := obj.(*SubsetFontObj) if ok { if sub.GetFamily() == family && sub.GetTtfFontOption().Style == style&^Underline { gp.curr.FontSize = size gp.curr.FontStyle = style gp.curr.FontFontCount = sub.CountOfFont gp.curr.FontISubset = sub found = true break } } } i++ } if !found { return errors.New("not found font family") } return nil } //SetFont : set font style support "" or "U" // for "B" and "I" should be loaded apropriate fonts with same styles defined func (gp *GoPdf) SetFont(family string, style string, size int) error { return gp.SetFontWithStyle(family, getConvertedStyle(style), size) } //WritePdf : wirte pdf file func (gp *GoPdf) WritePdf(pdfPath string) error { return ioutil.WriteFile(pdfPath, gp.GetBytesPdf(), 0644) } func (gp *GoPdf) Write(w io.Writer) error { return gp.compilePdf(w) } func (gp *GoPdf) Read(p []byte) (int, error) { if gp.buf.Len() == 0 && gp.buf.Cap() == 0 { if err := gp.compilePdf(&gp.buf); err != nil { return 0, err } } return gp.buf.Read(p) } // Close clears the gopdf buffer. func (gp *GoPdf) Close() error { gp.buf = bytes.Buffer{} return nil } func (gp *GoPdf) compilePdf(w io.Writer) error { gp.prepare() err := gp.Close() if err != nil { return err } max := len(gp.pdfObjs) writer := newCountingWriter(w) //io.WriteString(w, "%PDF-1.7\n\n") fmt.Fprint(writer, "%PDF-1.7\n\n") linelens := make([]int, max) i := 0 for i < max { objID := i + 1 linelens[i] = writer.offset pdfObj := gp.pdfObjs[i] fmt.Fprintf(writer, "%d 0 obj\n", objID) pdfObj.write(writer, objID) io.WriteString(writer, "endobj\n\n") i++ } gp.xref(writer, writer.offset, linelens, i) return nil } type ( countingWriter struct { offset int writer io.Writer } ) func newCountingWriter(w io.Writer) *countingWriter { return &countingWriter{writer: w} } func (cw *countingWriter) Write(b []byte) (int, error) { n, err := cw.writer.Write(b) cw.offset += n return n, err } //GetBytesPdfReturnErr : get bytes of pdf file func (gp *GoPdf) GetBytesPdfReturnErr() ([]byte, error) { err := gp.Close() if err != nil { return nil, err } err = gp.compilePdf(&gp.buf) return gp.buf.Bytes(), err } //GetBytesPdf : get bytes of pdf file func (gp *GoPdf) GetBytesPdf() []byte { b, err := gp.GetBytesPdfReturnErr() if err != nil { log.Fatalf("%s", err.Error()) } return b } //Text write text start at current x,y ( current y is the baseline of text ) func (gp *GoPdf) Text(text string) error { err := gp.curr.FontISubset.AddChars(text) if err != nil { return err } err = gp.getContent().AppendStreamText(text) if err != nil { return err } return nil } //CellWithOption create cell of text ( use current x,y is upper-left corner of cell) func (gp *GoPdf) CellWithOption(rectangle *Rect, text string, opt CellOption) error { transparency, err := gp.getCachedTransparency(opt.Transparency) if err != nil { return err } if transparency != nil { opt.extGStateIndexes = append(opt.extGStateIndexes, transparency.extGStateIndex) } rectangle = rectangle.UnitsToPoints(gp.config.Unit) if err := gp.curr.FontISubset.AddChars(text); err != nil { return err } if err := gp.getContent().AppendStreamSubsetFont(rectangle, text, opt); err != nil { return err } return nil } //Cell : create cell of text ( use current x,y is upper-left corner of cell) //Note that this has no effect on Rect.H pdf (now). Fix later :-) func (gp *GoPdf) Cell(rectangle *Rect, text string) error { rectangle = rectangle.UnitsToPoints(gp.config.Unit) defaultopt := CellOption{ Align: Left | Top, Border: 0, Float: Right, } err := gp.curr.FontISubset.AddChars(text) if err != nil { return err } err = gp.getContent().AppendStreamSubsetFont(rectangle, text, defaultopt) if err != nil { return err } return nil } //MultiCell : create of text with line breaks ( use current x,y is upper-left corner of cell) func (gp *GoPdf) MultiCell(rectangle *Rect, text string) error { var line []rune x := gp.GetX() var totalLineHeight float64 length := len([]rune(text)) // get lineHeight if err := gp.curr.FontISubset.AddChars(text); err != nil { return err } _, lineHeight, _, err := createContent(gp.curr.FontISubset, text, gp.curr.FontSize, nil) if err != nil { return err } for i, v := range []rune(text) { if totalLineHeight+lineHeight > rectangle.H { break } lineWidth, _ := gp.MeasureTextWidth(string(line)) runeWidth, _ := gp.MeasureTextWidth(string(v)) if lineWidth+runeWidth > rectangle.W { gp.Cell(&Rect{W: rectangle.W, H: lineHeight}, string(line)) gp.Br(lineHeight) gp.SetX(x) totalLineHeight = totalLineHeight + lineHeight line = nil } line = append(line, v) if i == length-1 { gp.Cell(&Rect{W: rectangle.W, H: lineHeight}, string(line)) gp.Br(lineHeight) gp.SetX(x) } } return nil } // SplitText splits text into multiple lines based on width. func (gp *GoPdf) SplitText(text string, width float64) ([]string, error) { var lineText []rune var lineTexts []string utf8Texts := []rune(text) utf8TextsLen := len(utf8Texts) // utf8 string quantity if utf8TextsLen == 0 { return lineTexts, errors.New("empty string") } for i := 0; i < utf8TextsLen; i++ { lineWidth, err := gp.MeasureTextWidth(string(lineText)) if err != nil { return nil, err } runeWidth, err := gp.MeasureTextWidth(string(utf8Texts[i])) if err != nil { return nil, err } if lineWidth+runeWidth > width && utf8Texts[i] != '\n' { lineTexts = append(lineTexts, string(lineText)) lineText = lineText[0:0] i-- continue } if utf8Texts[i] == '\n' { lineTexts = append(lineTexts, string(lineText)) lineText = lineText[0:0] continue } if i == utf8TextsLen-1 { lineText = append(lineText, utf8Texts[i]) lineTexts = append(lineTexts, string(lineText)) } lineText = append(lineText, utf8Texts[i]) } return lineTexts, nil } // ImportPage imports a page and return template id. // gofpdi code func (gp *GoPdf) ImportPage(sourceFile string, pageno int, box string) int { // Set source file for fpdi gp.fpdi.SetSourceFile(sourceFile) // gofpdi needs to know where to start the object id at. // By default, it starts at 1, but gopdf adds a few objects initially. startObjID := gp.GetNextObjectID() // Set gofpdi next object ID to whatever the value of startObjID is gp.fpdi.SetNextObjectID(startObjID) // Import page tpl := gp.fpdi.ImportPage(pageno, box) // Import objects into current pdf document tplObjIDs := gp.fpdi.PutFormXobjects() // Set template names and ids in gopdf gp.ImportTemplates(tplObjIDs) // Get a map[int]string of the imported objects. // The map keys will be the ID of each object. imported := gp.fpdi.GetImportedObjects() // Import gofpdi objects into gopdf, starting at whatever the value of startObjID is gp.ImportObjects(imported, startObjID) // Return template ID return tpl } // ImportPageStream imports page using a stream. // Return template id after importing. // gofpdi code func (gp *GoPdf) ImportPageStream(sourceStream *io.ReadSeeker, pageno int, box string) int { // Set source file for fpdi gp.fpdi.SetSourceStream(sourceStream) // gofpdi needs to know where to start the object id at. // By default, it starts at 1, but gopdf adds a few objects initially. startObjID := gp.GetNextObjectID() // Set gofpdi next object ID to whatever the value of startObjID is gp.fpdi.SetNextObjectID(startObjID) // Import page tpl := gp.fpdi.ImportPage(pageno, box) // Import objects into current pdf document tplObjIDs := gp.fpdi.PutFormXobjects() // Set template names and ids in gopdf gp.ImportTemplates(tplObjIDs) // Get a map[int]string of the imported objects. // The map keys will be the ID of each object. imported := gp.fpdi.GetImportedObjects() // Import gofpdi objects into gopdf, starting at whatever the value of startObjID is gp.ImportObjects(imported, startObjID) // Return template ID return tpl } // UseImportedTemplate draws an imported PDF page. func (gp *GoPdf) UseImportedTemplate(tplid int, x float64, y float64, w float64, h float64) { gp.UnitsToPointsVar(&x, &y, &w, &h) // Get template values to draw tplName, scaleX, scaleY, tX, tY := gp.fpdi.UseTemplate(tplid, x, y, w, h) gp.getContent().AppendStreamImportedTemplate(tplName, scaleX, scaleY, tX, tY) } // GetNextObjectID gets the next object ID so that gofpdi knows where to start the object IDs. func (gp *GoPdf) GetNextObjectID() int { return len(gp.pdfObjs) + 1 } // GetNumberOfPages gets the number of pages from the PDF. func (gp *GoPdf) GetNumberOfPages() int { return gp.numOfPagesObj } // ImportObjects imports objects from gofpdi into current document. func (gp *GoPdf) ImportObjects(objs map[int]string, startObjID int) { for i := startObjID; i < len(objs)+startObjID; i++ { if objs[i] != "" { gp.addObj(&ImportedObj{Data: objs[i]}) } } } // ImportTemplates names into procset dictionary. func (gp *GoPdf) ImportTemplates(tpls map[string]int) { procset := gp.pdfObjs[gp.indexOfProcSet].(*ProcSetObj) for tplName, tplID := range tpls { procset.ImportedTemplateIds[tplName] = tplID } } // AddExternalLink adds a new external link. func (gp *GoPdf) AddExternalLink(url string, x, y, w, h float64) { gp.UnitsToPointsVar(&x, &y, &w, &h) page := gp.pdfObjs[gp.curr.IndexOfPageObj].(*PageObj) page.Links = append(page.Links, linkOption{x, gp.config.PageSize.H - y, w, h, url, ""}) } // AddInternalLink adds a new internal link. func (gp *GoPdf) AddInternalLink(anchor string, x, y, w, h float64) { gp.UnitsToPointsVar(&x, &y, &w, &h) page := gp.pdfObjs[gp.curr.IndexOfPageObj].(*PageObj) page.Links = append(page.Links, linkOption{x, gp.config.PageSize.H - y, w, h, "", anchor}) } // SetAnchor creates a new anchor. func (gp *GoPdf) SetAnchor(name string) { y := gp.config.PageSize.H - gp.curr.Y + float64(gp.curr.FontSize) gp.anchors[name] = anchorOption{gp.curr.IndexOfPageObj, y} } // AddTTFFontByReader adds font data by reader. func (gp *GoPdf) AddTTFFontData(family string, fontData []byte) error { return gp.AddTTFFontDataWithOption(family, fontData, defaultTtfFontOption()) } // AddTTFFontDataWithOption adds font data with option. func (gp *GoPdf) AddTTFFontDataWithOption(family string, fontData []byte, option TtfOption) error { subsetFont := new(SubsetFontObj) subsetFont.init(func() *GoPdf { return gp }) subsetFont.SetTtfFontOption(option) subsetFont.SetFamily(family) err := subsetFont.SetTTFData(fontData) if err != nil { return err } return gp.setSubsetFontObject(subsetFont, family, option) } // AddTTFFontByReader adds font file by reader. func (gp *GoPdf) AddTTFFontByReader(family string, rd io.Reader) error { return gp.AddTTFFontByReaderWithOption(family, rd, defaultTtfFontOption()) } // AddTTFFontByReaderWithOption adds font file by reader with option. func (gp *GoPdf) AddTTFFontByReaderWithOption(family string, rd io.Reader, option TtfOption) error { subsetFont := new(SubsetFontObj) subsetFont.init(func() *GoPdf { return gp }) subsetFont.SetTtfFontOption(option) subsetFont.SetFamily(family) err := subsetFont.SetTTFByReader(rd) if err != nil { return err } return gp.setSubsetFontObject(subsetFont, family, option) } // setSubsetFontObject sets SubsetFontObj. // The given SubsetFontObj is expected to be configured in advance. func (gp *GoPdf) setSubsetFontObject(subsetFont *SubsetFontObj, family string, option TtfOption) error { unicodemap := new(UnicodeMap) unicodemap.init(func() *GoPdf { return gp }) unicodemap.setProtection(gp.protection()) unicodemap.SetPtrToSubsetFontObj(subsetFont) unicodeindex := gp.addObj(unicodemap) pdfdic := new(PdfDictionaryObj) pdfdic.init(func() *GoPdf { return gp }) pdfdic.setProtection(gp.protection()) pdfdic.SetPtrToSubsetFontObj(subsetFont) pdfdicindex := gp.addObj(pdfdic) subfontdesc := new(SubfontDescriptorObj) subfontdesc.init(func() *GoPdf { return gp }) subfontdesc.SetPtrToSubsetFontObj(subsetFont) subfontdesc.SetIndexObjPdfDictionary(pdfdicindex) subfontdescindex := gp.addObj(subfontdesc) cidfont := new(CIDFontObj) cidfont.init(func() *GoPdf { return gp }) cidfont.SetPtrToSubsetFontObj(subsetFont) cidfont.SetIndexObjSubfontDescriptor(subfontdescindex) cidindex := gp.addObj(cidfont) subsetFont.SetIndexObjCIDFont(cidindex) subsetFont.SetIndexObjUnicodeMap(unicodeindex) index := gp.addObj(subsetFont) //add หลังสุด if gp.indexOfProcSet != -1 { procset := gp.pdfObjs[gp.indexOfProcSet].(*ProcSetObj) if !procset.Relates.IsContainsFamilyAndStyle(family, option.Style&^Underline) { procset.Relates = append(procset.Relates, RelateFont{Family: family, IndexOfObj: index, CountOfFont: gp.curr.CountOfFont, Style: option.Style &^ Underline}) subsetFont.CountOfFont = gp.curr.CountOfFont gp.curr.CountOfFont++ } } return nil } //AddTTFFontWithOption : add font file func (gp *GoPdf) AddTTFFontWithOption(family string, ttfpath string, option TtfOption) error { if _, err := os.Stat(ttfpath); os.IsNotExist(err) { return err } data, err := ioutil.ReadFile(ttfpath) if err != nil { return err } rd := bytes.NewReader(data) return gp.AddTTFFontByReaderWithOption(family, rd, option) } //AddTTFFont : add font file func (gp *GoPdf) AddTTFFont(family string, ttfpath string) error { return gp.AddTTFFontWithOption(family, ttfpath, defaultTtfFontOption()) } //KernOverride override kern value func (gp *GoPdf) KernOverride(family string, fn FuncKernOverride) error { i := 0 max := len(gp.pdfObjs) for i < max { if gp.pdfObjs[i].getType() == subsetFont { obj := gp.pdfObjs[i] sub, ok := obj.(*SubsetFontObj) if ok { if sub.GetFamily() == family { sub.funcKernOverride = fn return nil } } } i++ } return errors.New("font family not found") } //SetTextColor : function sets the text color func (gp *GoPdf) SetTextColor(r uint8, g uint8, b uint8) { gp.curr.txtColorMode = "color" rgb := Rgb{ r: r, g: g, b: b, } gp.curr.setTextColor(rgb) } //SetStrokeColor set the color for the stroke func (gp *GoPdf) SetStrokeColor(r uint8, g uint8, b uint8) { gp.getContent().AppendStreamSetColorStroke(r, g, b) } //SetFillColor set the color for the stroke func (gp *GoPdf) SetFillColor(r uint8, g uint8, b uint8) { gp.getContent().AppendStreamSetColorFill(r, g, b) } //MeasureTextWidth : measure Width of text (use current font) func (gp *GoPdf) MeasureTextWidth(text string) (float64, error) { err := gp.curr.FontISubset.AddChars(text) //AddChars for create CharacterToGlyphIndex if err != nil { return 0, err } _, _, textWidthPdfUnit, err := createContent(gp.curr.FontISubset, text, gp.curr.FontSize, nil) if err != nil { return 0, err } return PointsToUnits(gp.config.Unit, textWidthPdfUnit), nil } //Curve Draws a Bézier curve (the Bézier curve is tangent to the line between the control points at either end of the curve) // Parameters: // - x0, y0: Start point // - x1, y1: Control point 1 // - x2, y2: Control point 2 // - x3, y3: End point // - style: Style of rectangule (draw and/or fill: D, F, DF, FD) func (gp *GoPdf) Curve(x0 float64, y0 float64, x1 float64, y1 float64, x2 float64, y2 float64, x3 float64, y3 float64, style string) { gp.UnitsToPointsVar(&x0, &y0, &x1, &y1, &x2, &y2, &x3, &y3) gp.getContent().AppendStreamCurve(x0, y0, x1, y1, x2, y2, x3, y3, style) } /* //SetProtection set permissions as well as user and owner passwords func (gp *GoPdf) SetProtection(permissions int, userPass []byte, ownerPass []byte) { gp.pdfProtection = new(PDFProtection) gp.pdfProtection.setProtection(permissions, userPass, ownerPass) }*/ //SetInfo set Document Information Dictionary func (gp *GoPdf) SetInfo(info PdfInfo) { gp.info = &info gp.isUseInfo = true } //Rotate rotate text or image // angle is angle in degrees. // x, y is rotation center func (gp *GoPdf) Rotate(angle, x, y float64) { gp.UnitsToPointsVar(&x, &y) gp.getContent().appendRotate(angle, x, y) } //RotateReset reset rotate func (gp *GoPdf) RotateReset() { gp.getContent().appendRotateReset() } //Polygon : draw polygon // - style: Style of polygon (draw and/or fill: D, F, DF, FD) // D or empty string: draw. This is the default value. // F: fill // DF or FD: draw and fill // Usage: // pdf.SetStrokeColor(255, 0, 0) // pdf.SetLineWidth(2) // pdf.SetFillColor(0, 255, 0) // pdf.Polygon([]gopdf.Point{{X: 10, Y: 30}, {X: 585, Y: 200}, {X: 585, Y: 250}}, "DF") func (gp *GoPdf) Polygon(points []Point, style string) { var pointReals []Point for _, p := range points { x := p.X y := p.Y gp.UnitsToPointsVar(&x, &y) pointReals = append(pointReals, Point{X: x, Y: y}) } gp.getContent().AppendStreamPolygon(pointReals, style) } /*---private---*/ //init func (gp *GoPdf) init() { //default gp.margins = Margins{ Left: defaultMargin, Top: defaultMargin, Right: defaultMargin, Bottom: defaultMargin, } //init curr gp.resetCurrXY() gp.curr.IndexOfPageObj = -1 gp.curr.CountOfFont = 0 gp.curr.CountOfL = 0 gp.curr.CountOfImg = 0 //img gp.curr.ImgCaches = make(map[int]ImageCache) //= *new([]ImageCache) gp.curr.sMasksMap = NewSMaskMap() gp.curr.extGStatesMap = NewExtGStatesMap() gp.curr.transparencyMap = NewTransparencyMap() gp.anchors = make(map[string]anchorOption) gp.curr.txtColorMode = "gray" //init index gp.indexOfPagesObj = -1 gp.indexOfFirstPageObj = -1 gp.indexOfContent = -1 //No underline //gp.IsUnderline = false gp.curr.lineWidth = 1 // default to zlib.DefaultCompression gp.compressLevel = zlib.DefaultCompression // change the unit type gp.config.PageSize = *gp.config.PageSize.UnitsToPoints(gp.config.Unit) gp.config.TrimBox = *gp.config.TrimBox.UnitsToPoints(gp.config.Unit) // init gofpdi free pdf document importer gp.fpdi = gofpdi.NewImporter() } func (gp *GoPdf) resetCurrXY() { gp.curr.X = gp.margins.Left gp.curr.Y = gp.margins.Top } // UnitsToPoints converts the units to the documents unit type func (gp *GoPdf) UnitsToPoints(u float64) float64 { return UnitsToPoints(gp.config.Unit, u) } // UnitsToPointsVar converts the units to the documents unit type for all variables passed in func (gp *GoPdf) UnitsToPointsVar(u ...*float64) { UnitsToPointsVar(gp.config.Unit, u...) } // PointsToUnits converts the points to the documents unit type func (gp *GoPdf) PointsToUnits(u float64) float64 { return PointsToUnits(gp.config.Unit, u) } //PointsToUnitsVar converts the points to the documents unit type for all variables passed in func (gp *GoPdf) PointsToUnitsVar(u ...*float64) { PointsToUnitsVar(gp.config.Unit, u...) } func (gp *GoPdf) isUseProtection() bool { return gp.config.Protection.UseProtection } func (gp *GoPdf) createProtection() *PDFProtection { var prot PDFProtection prot.setProtection( gp.config.Protection.Permissions, gp.config.Protection.UserPass, gp.config.Protection.OwnerPass, ) return &prot } func (gp *GoPdf) protection() *PDFProtection { return gp.pdfProtection } func (gp *GoPdf) prepare() { if gp.isUseProtection() { encObj := gp.pdfProtection.encryptionObj() gp.addObj(encObj) } if gp.outlines.Count() > 0 { catalogObj := gp.pdfObjs[gp.indexOfCatalogObj].(*CatalogObj) catalogObj.SetIndexObjOutlines(gp.indexOfOutlinesObj) } if gp.indexOfPagesObj != -1 { indexCurrPage := -1 var pagesObj *PagesObj pagesObj = gp.pdfObjs[gp.indexOfPagesObj].(*PagesObj) i := 0 //gp.indexOfFirstPageObj max := len(gp.pdfObjs) for i < max { objtype := gp.pdfObjs[i].getType() //fmt.Printf(" objtype = %s , %d \n", objtype , i) switch objtype { case "Page": pagesObj.Kids = fmt.Sprintf("%s %d 0 R ", pagesObj.Kids, i+1) pagesObj.PageCount++ indexCurrPage = i case "Content": if indexCurrPage != -1 { gp.pdfObjs[indexCurrPage].(*PageObj).Contents = fmt.Sprintf("%s %d 0 R ", gp.pdfObjs[indexCurrPage].(*PageObj).Contents, i+1) } case "Font": tmpfont := gp.pdfObjs[i].(*FontObj) j := 0 jmax := len(gp.indexEncodingObjFonts) for j < jmax { tmpencoding := gp.pdfObjs[gp.indexEncodingObjFonts[j]].(*EncodingObj).GetFont() if tmpfont.Family == tmpencoding.GetFamily() { //ใส่ ข้อมูลของ embed font tmpfont.IsEmbedFont = true tmpfont.SetIndexObjEncoding(gp.indexEncodingObjFonts[j] + 1) tmpfont.SetIndexObjWidth(gp.indexEncodingObjFonts[j] + 2) tmpfont.SetIndexObjFontDescriptor(gp.indexEncodingObjFonts[j] + 3) break } j++ } case "Encryption": gp.encryptionObjID = i + 1 } i++ } } } func (gp *GoPdf) xref(w io.Writer, xrefbyteoffset int, linelens []int, i int) error { io.WriteString(w, "xref\n") fmt.Fprintf(w, "0 %d\n", i+1) io.WriteString(w, "0000000000 65535 f \n") j := 0 max := len(linelens) for j < max { linelen := linelens[j] fmt.Fprintf(w, "%s 00000 n \n", gp.formatXrefline(linelen)) j++ } io.WriteString(w, "trailer\n") io.WriteString(w, "<<\n") fmt.Fprintf(w, "/Size %d\n", max+1) io.WriteString(w, "/Root 1 0 R\n") if gp.isUseProtection() { fmt.Fprintf(w, "/Encrypt %d 0 R\n", gp.encryptionObjID) io.WriteString(w, "/ID [()()]\n") } if gp.isUseInfo { gp.writeInfo(w) } io.WriteString(w, ">>\n") io.WriteString(w, "startxref\n") fmt.Fprintf(w, "%d", xrefbyteoffset) io.WriteString(w, "\n%%EOF\n") return nil } func (gp *GoPdf) writeInfo(w io.Writer) { var zerotime time.Time io.WriteString(w, "/Info <<\n") if gp.info.Author != "" { fmt.Fprintf(w, "/Author \n", encodeUtf8(gp.info.Author)) } if gp.info.Title != "" { fmt.Fprintf(w, "/Title \n", encodeUtf8(gp.info.Title)) } if gp.info.Subject != "" { fmt.Fprintf(w, "/Subject \n", encodeUtf8(gp.info.Subject)) } if gp.info.Creator != "" { fmt.Fprintf(w, "/Creator \n", encodeUtf8(gp.info.Creator)) } if gp.info.Producer != "" { fmt.Fprintf(w, "/Producer \n", encodeUtf8(gp.info.Producer)) } if !zerotime.Equal(gp.info.CreationDate) { fmt.Fprintf(w, "/CreationDate(D:%s)>>\n", infodate(gp.info.CreationDate)) } io.WriteString(w, " >>\n") } //ปรับ xref ให้เป็น 10 หลัก func (gp *GoPdf) formatXrefline(n int) string { str := strconv.Itoa(n) for len(str) < 10 { str = "0" + str } return str } func (gp *GoPdf) addObj(iobj IObj) int { index := len(gp.pdfObjs) gp.pdfObjs = append(gp.pdfObjs, iobj) return index } func (gp *GoPdf) getContent() *ContentObj { var content *ContentObj if gp.indexOfContent <= -1 { content = new(ContentObj) content.init(func() *GoPdf { return gp }) gp.indexOfContent = gp.addObj(content) } else { content = gp.pdfObjs[gp.indexOfContent].(*ContentObj) } return content } func encodeUtf8(str string) string { var buff bytes.Buffer for _, r := range str { c := fmt.Sprintf("%X", r) for len(c) < 4 { c = "0" + c } buff.WriteString(c) } return buff.String() } func infodate(t time.Time) string { ft := t.Format("20060102150405-07'00'") return ft } // SetTransparency sets transparency. // alpha: value from 0 (transparent) to 1 (opaque) // blendMode: blend mode, one of the following: // Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, // HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity func (gp *GoPdf) SetTransparency(transparency Transparency) error { t, err := gp.saveTransparency(&transparency) if err != nil { return err } gp.curr.transparency = t return nil } func (gp *GoPdf) getCachedTransparency(transparency *Transparency) (*Transparency, error) { if transparency == nil { transparency = gp.curr.transparency } else { cached, err := gp.saveTransparency(transparency) if err != nil { return nil, err } transparency = cached } return transparency, nil } func (gp *GoPdf) saveTransparency(transparency *Transparency) (*Transparency, error) { cached, ok := gp.curr.transparencyMap.Find(*transparency) if ok { return &cached, nil } else if transparency.Alpha != DefaultAplhaValue { bm := transparency.BlendModeType opts := ExtGStateOptions{ BlendMode: &bm, StrokingCA: &transparency.Alpha, NonStrokingCa: &transparency.Alpha, } extGState, err := GetCachedExtGState(opts, gp) if err != nil { return nil, err } transparency.extGStateIndex = extGState.Index + 1 gp.curr.transparencyMap.Save(*transparency) return transparency, nil } return nil, nil } // IsCurrFontContainGlyph defines is current font contains to a glyph // r: any rune func (gp *GoPdf) IsCurrFontContainGlyph(r rune) (bool, error) { fontISubset := gp.curr.FontISubset if fontISubset == nil { return false, nil } glyphIndex, err := fontISubset.CharCodeToGlyphIndex(r) if err != nil { return false, err } if glyphIndex == 0 { return false, nil } return true, nil } //tool for validate pdf https://www.pdf-online.com/osa/validate.aspx ================================================ FILE: vendor/github.com/signintech/gopdf/i_cache_contneter.go ================================================ package gopdf import ( "io" ) type ICacheContent interface { write(w io.Writer, protection *PDFProtection) error } ================================================ FILE: vendor/github.com/signintech/gopdf/ifont.go ================================================ package gopdf // IFont represents a font interface. type IFont interface { Init() GetType() string GetName() string GetDesc() []FontDescItem GetUp() int GetUt() int GetCw() FontCw GetEnc() string GetDiff() string GetOriginalsize() int SetFamily(family string) GetFamily() string } // FontCw maps characters to integers. type FontCw map[byte]int // FontDescItem is a (key, value) pair. type FontDescItem struct { Key string Val string } // // Chr // func Chr(n int) byte { // return byte(n) //ToByte(fmt.Sprintf("%c", n )) // } // ToByte returns the first byte of a string. func ToByte(chr string) byte { return []byte(chr)[0] } ================================================ FILE: vendor/github.com/signintech/gopdf/image_holder.go ================================================ package gopdf import ( "bytes" "crypto/md5" "fmt" "io" "io/ioutil" ) //ImageHolder hold image data type ImageHolder interface { ID() string io.Reader } //ImageHolderByBytes create ImageHolder by []byte func ImageHolderByBytes(b []byte) (ImageHolder, error) { return newImageBuff(b) } //ImageHolderByPath create ImageHolder by image path func ImageHolderByPath(path string) (ImageHolder, error) { return newImageBuffByPath(path) } //ImageHolderByReader create ImageHolder by io.Reader func ImageHolderByReader(r io.Reader) (ImageHolder, error) { return newImageBuffByReader(r) } //imageBuff image holder (impl ImageHolder) type imageBuff struct { id string bytes.Buffer } func newImageBuff(b []byte) (*imageBuff, error) { h := md5.New() _, err := h.Write(b) if err != nil { return nil, err } var i imageBuff i.id = fmt.Sprintf("%x", h.Sum(nil)) i.Write(b) return &i, nil } func newImageBuffByPath(path string) (*imageBuff, error) { var i imageBuff i.id = path b, err := ioutil.ReadFile(path) if err != nil { return nil, err } i.Write(b) return &i, nil } func newImageBuffByReader(r io.Reader) (*imageBuff, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err } h := md5.New() _, err = h.Write(b) if err != nil { return nil, err } var i imageBuff i.id = fmt.Sprintf("%x", h.Sum(nil)) i.Write(b) return &i, nil } func (i *imageBuff) ID() string { return i.id } ================================================ FILE: vendor/github.com/signintech/gopdf/image_obj.go ================================================ package gopdf import ( "bytes" "fmt" "image" // Packages image/jpeg and image/png are not used explicitly in the code below, // but are imported for their initialization side-effect, which allows // image.Decode to understand JPEG formatted images. _ "image/jpeg" _ "image/png" "io" "io/ioutil" "log" "os" ) //ImageObj image object type ImageObj struct { //imagepath string IsMask bool SplittedMask bool rawImgReader *bytes.Reader imginfo imgInfo pdfProtection *PDFProtection //getRoot func() *GoPdf } func (i *ImageObj) init(funcGetRoot func() *GoPdf) { } func (i *ImageObj) setProtection(p *PDFProtection) { i.pdfProtection = p } func (i *ImageObj) protection() *PDFProtection { return i.pdfProtection } func (i *ImageObj) write(w io.Writer, objID int) error { data := i.imginfo.data if i.IsMask { data = i.imginfo.smask if err := writeMaskImgProps(w, i.imginfo); err != nil { return err } } else { if err := writeImgProps(w, i.imginfo, i.SplittedMask); err != nil { return err } } if _, err := fmt.Fprintf(w, "\t/Length %d\n>>\n", len(data)); err != nil { return err } if _, err := io.WriteString(w, "stream\n"); err != nil { return err } if i.protection() != nil { tmp, err := rc4Cip(i.protection().objectkey(objID), data) if err != nil { return err } if _, err := w.Write(tmp); err != nil { return err } if _, err := io.WriteString(w, "\n"); err != nil { return err } } else { if _, err := w.Write(data); err != nil { return err } } if _, err := io.WriteString(w, "\nendstream\n"); err != nil { return err } return nil } func (i *ImageObj) isColspaceIndexed() bool { return isColspaceIndexed(i.imginfo) } func (i *ImageObj) haveSMask() bool { return haveSMask(i.imginfo) } func (i *ImageObj) createSMask() (*SMask, error) { var smk SMask smk.setProtection(i.protection()) smk.w = i.imginfo.w smk.h = i.imginfo.h smk.colspace = "DeviceGray" smk.bitsPerComponent = "8" smk.filter = i.imginfo.filter smk.data = i.imginfo.smask smk.decodeParms = fmt.Sprintf("/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns %d", i.imginfo.w) return &smk, nil } func (i *ImageObj) createDeviceRGB() (*DeviceRGBObj, error) { var dRGB DeviceRGBObj dRGB.data = i.imginfo.pal return &dRGB, nil } func (i *ImageObj) getType() string { return "Image" } //SetImagePath set image path func (i *ImageObj) SetImagePath(path string) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() err = i.SetImage(file) if err != nil { return err } return nil } //SetImage set image func (i *ImageObj) SetImage(r io.Reader) error { data, err := ioutil.ReadAll(r) if err != nil { return err } i.rawImgReader = bytes.NewReader(data) return nil } //GetRect get rect of img func (i *ImageObj) GetRect() *Rect { rect, err := i.getRect() if err != nil { log.Fatalf("%+v", err) } return rect } //GetRect get rect of img func (i *ImageObj) getRect() (*Rect, error) { i.rawImgReader.Seek(0, 0) m, _, err := image.Decode(i.rawImgReader) if err != nil { return nil, err } imageRect := m.Bounds() k := 1 w := -128 //init h := -128 //init if w < 0 { w = -imageRect.Dx() * 72 / w / k } if h < 0 { h = -imageRect.Dy() * 72 / h / k } if w == 0 { w = h * imageRect.Dx() / imageRect.Dy() } if h == 0 { h = w * imageRect.Dy() / imageRect.Dx() } var rect = new(Rect) rect.H = float64(h) rect.W = float64(w) return rect, nil } func (i *ImageObj) parse() error { i.rawImgReader.Seek(0, 0) imginfo, err := parseImg(i.rawImgReader) if err != nil { return err } i.imginfo = imginfo return nil } //Parse parse img func (i *ImageObj) Parse() error { return i.parse() } ================================================ FILE: vendor/github.com/signintech/gopdf/image_obj_parse.go ================================================ package gopdf import ( "bytes" "compress/zlib" "encoding/binary" "errors" "fmt" "image" "image/color" "io" "io/ioutil" "strings" ) type ColorSpaces string const ( DeviceGray = "DeviceGray" ) func writeMaskImgProps(w io.Writer, imginfo imgInfo) error { if err := writeBaseImgProps(w, imginfo, DeviceGray); err != nil { return err } decode := "\t/DecodeParms <<\n" decode += "\t\t/Predictor 15\n" decode += "\t\t/Colors 1\n" decode += "\t\t/BitsPerComponent 8\n" decode += fmt.Sprintf("\t\t/Columns %d\n", imginfo.w) decode += "\t>>\n" if _, err := io.WriteString(w, decode); err != nil { return err } return nil } func writeImgProps(w io.Writer, imginfo imgInfo, splittedMask bool) error { if err := writeBaseImgProps(w, imginfo, imginfo.colspace); err != nil { return err } if strings.TrimSpace(imginfo.decodeParms) != "" { if _, err := fmt.Fprintf(w, "\t/DecodeParms <<%s>>\n", imginfo.decodeParms); err != nil { return err } } if splittedMask { return nil } if imginfo.trns != nil && len(imginfo.trns) > 0 { j := 0 content := "\t/Mask [" max := len(imginfo.trns) for j < max { content += fmt.Sprintf("\t\t%d ", imginfo.trns[j]) content += fmt.Sprintf("\t\t%d ", imginfo.trns[j]) j++ } content += "\t]\n" if _, err := io.WriteString(w, content); err != nil { return err } } if haveSMask(imginfo) { if _, err := fmt.Fprintf(w, "\t/SMask %d 0 R\n", imginfo.smarkObjID+1); err != nil { return err } } return nil } func writeBaseImgProps(w io.Writer, imginfo imgInfo, colorSpace string) error { content := "<<\n" content += "\t/Type /XObject\n" content += "\t/Subtype /Image\n" content += fmt.Sprintf("\t/Width %d\n", imginfo.w) content += fmt.Sprintf("\t/Height %d\n", imginfo.h) if isColspaceIndexed(imginfo) { size := len(imginfo.pal)/3 - 1 content += fmt.Sprintf("\t/ColorSpace [/Indexed /DeviceRGB %d %d 0 R]\n", size, imginfo.deviceRGBObjID+1) } else { content += fmt.Sprintf("\t/ColorSpace /%s\n", colorSpace) if imginfo.colspace == "DeviceCMYK" { content += "\t/Decode [1 0 1 0 1 0 1 0]\n" } } content += fmt.Sprintf("\t/BitsPerComponent %s\n", imginfo.bitsPerComponent) if strings.TrimSpace(imginfo.filter) != "" { content += fmt.Sprintf("\t/Filter /%s\n", imginfo.filter) } if _, err := io.WriteString(w, content); err != nil { return err } return nil } func isColspaceIndexed(imginfo imgInfo) bool { if imginfo.colspace == "Indexed" { return true } return false } func haveSMask(imginfo imgInfo) bool { if imginfo.smask != nil && len(imginfo.smask) > 0 { return true } return false } func parseImgByPath(path string) (imgInfo, error) { data, err := ioutil.ReadFile(path) if err != nil { return imgInfo{}, err } return parseImg(bytes.NewReader(data)) } func parseImg(raw *bytes.Reader) (imgInfo, error) { //fmt.Printf("----------\n") var info imgInfo raw.Seek(0, 0) imgConfig, formatname, err := image.DecodeConfig(raw) if err != nil { return info, err } info.formatName = formatname if formatname == "jpeg" { err = parseImgJpg(&info, imgConfig) if err != nil { return info, err } raw.Seek(0, 0) info.data, err = ioutil.ReadAll(raw) if err != nil { return info, err } } else if formatname == "png" { err = parsePng(raw, &info, imgConfig) if err != nil { return info, err } } //fmt.Printf("%#v\n", info) return info, nil } func parseImgJpg(info *imgInfo, imgConfig image.Config) error { switch imgConfig.ColorModel { case color.YCbCrModel: info.colspace = "DeviceRGB" case color.GrayModel: info.colspace = "DeviceGray" case color.CMYKModel: info.colspace = "DeviceCMYK" default: return errors.New("color model not support") } info.bitsPerComponent = "8" info.filter = "DCTDecode" info.h = imgConfig.Height info.w = imgConfig.Width return nil } var pngMagicNumber = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a} var pngIHDR = []byte{0x49, 0x48, 0x44, 0x52} func parsePng(f *bytes.Reader, info *imgInfo, imgConfig image.Config) error { //f := bytes.NewReader(raw) f.Seek(0, 0) b, err := readBytes(f, 8) if err != nil { return err } if !bytes.Equal(b, pngMagicNumber) { return errors.New("Not a PNG file") } f.Seek(4, 1) //skip header chunk b, err = readBytes(f, 4) if err != nil { return err } if !bytes.Equal(b, pngIHDR) { return errors.New("Incorrect PNG file") } w, err := readInt(f) if err != nil { return err } h, err := readInt(f) if err != nil { return err } //fmt.Printf("w=%d h=%d\n", w, h) bpc, err := readBytes(f, 1) if err != nil { return err } if bpc[0] > 8 { return errors.New("16-bit depth not supported") } ct, err := readBytes(f, 1) if err != nil { return err } var colspace string switch ct[0] { case 0, 4: colspace = "DeviceGray" case 2, 6: colspace = "DeviceRGB" case 3: colspace = "Indexed" default: return errors.New("Unknown color type") } compressionMethod, err := readBytes(f, 1) if err != nil { return err } if compressionMethod[0] != 0 { return errors.New("Unknown compression method") } filterMethod, err := readBytes(f, 1) if err != nil { return err } if filterMethod[0] != 0 { return errors.New("Unknown filter method") } interlacing, err := readBytes(f, 1) if err != nil { return err } if interlacing[0] != 0 { return errors.New("Interlacing not supported") } _, err = f.Seek(4, 1) //skip if err != nil { return err } //decodeParms := "/Predictor 15 /Colors '.($colspace=='DeviceRGB' ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w; var pal []byte var trns []byte var data []byte for { un, err := readUInt(f) if err != nil { return err } n := int(un) typ, err := readBytes(f, 4) //fmt.Printf(">>>>%+v-%s-%d\n", typ, string(typ), n) if err != nil { return err } if string(typ) == "PLTE" { pal, err = readBytes(f, n) if err != nil { return err } _, err = f.Seek(int64(4), 1) //skip if err != nil { return err } } else if string(typ) == "tRNS" { var t []byte t, err = readBytes(f, n) if err != nil { return err } if ct[0] == 0 { trns = []byte{(t[1])} } else if ct[0] == 2 { trns = []byte{t[1], t[3], t[5]} } else { pos := strings.Index(string(t), "\x00") if pos >= 0 { trns = []byte{byte(pos)} } } _, err = f.Seek(int64(4), 1) //skip if err != nil { return err } } else if string(typ) == "IDAT" { //fmt.Printf("n=%d\n\n", n) var d []byte d, err = readBytes(f, n) if err != nil { return err } data = append(data, d...) _, err = f.Seek(int64(4), 1) //skip if err != nil { return err } } else if string(typ) == "IEND" { break } else { _, err = f.Seek(int64(n+4), 1) //skip if err != nil { return err } } if n <= 0 { break } } //end for //info.data = data //ok info.trns = trns info.pal = pal //fmt.Printf("data= %x", md5.Sum(data)) if colspace == "Indexed" && strings.TrimSpace(string(pal)) == "" { return errors.New("Missing palette") } info.w = w info.h = h info.colspace = colspace info.bitsPerComponent = fmt.Sprintf("%d", int(bpc[0])) info.filter = "FlateDecode" colors := 1 if colspace == "DeviceRGB" { colors = 3 } info.decodeParms = fmt.Sprintf("/Predictor 15 /Colors %d /BitsPerComponent %s /Columns %d", colors, info.bitsPerComponent, w) //fmt.Printf("%d = ct[0]\n", ct[0]) //fmt.Printf("%x\n", md5.Sum(data)) if ct[0] >= 4 { zipReader, err := zlib.NewReader(bytes.NewReader(data)) if err != nil { return err } defer zipReader.Close() afterZipData, err := ioutil.ReadAll(zipReader) if err != nil { return err } var color []byte var alpha []byte if ct[0] == 4 { // Gray image length := 2 * w i := 0 for i < h { pos := (1 + length) * i color = append(color, afterZipData[pos]) alpha = append(alpha, afterZipData[pos]) line := afterZipData[pos+1 : pos+length+1] j := 0 max := len(line) for j < max { color = append(color, line[j]) j++ alpha = append(alpha, line[j]) j++ } i++ } //fmt.Print("aaaaa") } else { // RGB image length := 4 * w i := 0 for i < h { pos := (1 + length) * i color = append(color, afterZipData[pos]) alpha = append(alpha, afterZipData[pos]) line := afterZipData[pos+1 : pos+length+1] j := 0 max := len(line) for j < max { color = append(color, line[j:j+3]...) alpha = append(alpha, line[j+3]) j = j + 4 } i++ } info.smask, err = compress(alpha) if err != nil { return err } info.data, err = compress(color) if err != nil { return err } } } else { info.data = data } return nil } func compress(data []byte) ([]byte, error) { var results []byte var buff bytes.Buffer zwr, err := zlib.NewWriterLevel(&buff, zlib.BestSpeed) if err != nil { return results, err } _, err = zwr.Write(data) if err != nil { return nil, err } zwr.Close() return buff.Bytes(), nil } func readUInt(f *bytes.Reader) (uint, error) { buff, err := readBytes(f, 4) //fmt.Printf("%#v\n\n", buff) if err != nil { return 0, err } n := binary.BigEndian.Uint32(buff) return uint(n), nil } func readInt(f *bytes.Reader) (int, error) { u, err := readUInt(f) if err != nil { return 0, err } var v int if u >= 0x8000 { v = int(u) - 65536 } else { v = int(u) } return v, nil } func readBytes(f *bytes.Reader, len int) ([]byte, error) { b := make([]byte, len) _, err := f.Read(b) if err != nil { return nil, err } return b, nil } func isDeviceRGB(formatname string, img *image.Image) bool { if _, ok := (*img).(*image.YCbCr); ok { return true } else if _, ok := (*img).(*image.NRGBA); ok { return true } return false } //ImgReactagleToWH Rectangle to W and H func ImgReactagleToWH(imageRect image.Rectangle) (float64, float64) { k := 1 w := -128 //init h := -128 //init if w < 0 { w = -imageRect.Dx() * 72 / w / k } if h < 0 { h = -imageRect.Dy() * 72 / h / k } if w == 0 { w = h * imageRect.Dx() / imageRect.Dy() } if h == 0 { h = w * imageRect.Dy() / imageRect.Dx() } return float64(w), float64(h) } ================================================ FILE: vendor/github.com/signintech/gopdf/img_info.go ================================================ package gopdf type imgInfo struct { w, h int //src string formatName string colspace string bitsPerComponent string filter string decodeParms string trns []byte smask []byte smarkObjID int pal []byte deviceRGBObjID int data []byte } ================================================ FILE: vendor/github.com/signintech/gopdf/imported_obj.go ================================================ package gopdf import ( "io" ) //ImportedObj : imported object type ImportedObj struct { //impl IObj Data string } func (c *ImportedObj) init(funcGetRoot func() *GoPdf) { } func (c *ImportedObj) getType() string { return "Imported" } func (c *ImportedObj) write(w io.Writer, objID int) error { if c != nil { io.WriteString(w, c.Data) } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/iobj.go ================================================ package gopdf import ( "io" ) //IObj inteface for all pdf object type IObj interface { init(func() *GoPdf) getType() string write(w io.Writer, objID int) error } ================================================ FILE: vendor/github.com/signintech/gopdf/link_option.go ================================================ package gopdf type anchorOption struct { page int y float64 } type linkOption struct { x, y, w, h float64 url string anchor string } ================================================ FILE: vendor/github.com/signintech/gopdf/list_cache_content.go ================================================ package gopdf import "io" type listCacheContent struct { caches []ICacheContent } func (l *listCacheContent) last() ICacheContent { max := len(l.caches) if max > 0 { return l.caches[max-1] } return nil } func (l *listCacheContent) append(cache ICacheContent) { l.caches = append(l.caches, cache) } func (l *listCacheContent) appendContentText(cache cacheContentText, text string) (float64, float64, error) { x := cache.x y := cache.y mustMakeNewCache := true var cacheFont *cacheContentText var ok bool last := l.last() if cacheFont, ok = last.(*cacheContentText); ok { if cacheFont != nil { if cacheFont.isSame(cache) { mustMakeNewCache = false } } } if mustMakeNewCache { //make new cell l.caches = append(l.caches, &cache) cacheFont = &cache } //start add text cacheFont.text += text //re-create contnet textWidthPdfUnit, textHeightPdfUnit, err := cacheFont.createContent() if err != nil { return x, y, err } if cacheFont.cellOpt.Float == 0 || cacheFont.cellOpt.Float&Right == Right || cacheFont.contentType == ContentTypeText { x += textWidthPdfUnit } if cacheFont.cellOpt.Float&Bottom == Bottom { y += textHeightPdfUnit } return x, y, nil } func (l *listCacheContent) write(w io.Writer, protection *PDFProtection) error { for _, cache := range l.caches { if err := cache.write(w, protection); err != nil { return err } } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/map_of_character_To_glyph_index.go ================================================ package gopdf //MapOfCharacterToGlyphIndex map of CharacterToGlyphIndex type MapOfCharacterToGlyphIndex struct { keyIndexs map[rune]int //for search index in keys Keys []rune Vals []uint } //NewMapOfCharacterToGlyphIndex new CharacterToGlyphIndex func NewMapOfCharacterToGlyphIndex() *MapOfCharacterToGlyphIndex { var m MapOfCharacterToGlyphIndex m.keyIndexs = make(map[rune]int) return &m } //KeyExists key is exists? func (m *MapOfCharacterToGlyphIndex) KeyExists(k rune) bool { /*for _, key := range m.Keys { if k == key { return true } }*/ if _, ok := m.keyIndexs[k]; ok { return true } return false } //Set set key and value to map func (m *MapOfCharacterToGlyphIndex) Set(k rune, v uint) { m.keyIndexs[k] = len(m.Keys) m.Keys = append(m.Keys, k) m.Vals = append(m.Vals, v) } //Index get index by key func (m *MapOfCharacterToGlyphIndex) Index(k rune) (int, bool) { /*for i, key := range m.Keys { if k == key { return i, true } }*/ if index, ok := m.keyIndexs[k]; ok { return index, true } return -1, false } //Val get value by Key func (m *MapOfCharacterToGlyphIndex) Val(k rune) (uint, bool) { i, ok := m.Index(k) if !ok { return 0, false } return m.Vals[i], true } //AllKeys get keys func (m *MapOfCharacterToGlyphIndex) AllKeys() []rune { return m.Keys } //AllVals get all values func (m *MapOfCharacterToGlyphIndex) AllVals() []uint { return m.Vals } ================================================ FILE: vendor/github.com/signintech/gopdf/margin.go ================================================ package gopdf // Margins type. type Margins struct { Left, Top, Right, Bottom float64 } // SetLeftMargin sets left margin. func (gp *GoPdf) SetLeftMargin(margin float64) { gp.UnitsToPointsVar(&margin) gp.margins.Left = margin } // SetTopMargin sets top margin. func (gp *GoPdf) SetTopMargin(margin float64) { gp.UnitsToPointsVar(&margin) gp.margins.Top = margin } // SetMargins defines the left, top, right and bottom margins. By default, they equal 1 cm. Call this method to change them. func (gp *GoPdf) SetMargins(left, top, right, bottom float64) { gp.UnitsToPointsVar(&left, &top, &right, &bottom) gp.margins = Margins{left, top, right, bottom} } // SetMarginLeft sets the left margin func (gp *GoPdf) SetMarginLeft(margin float64) { gp.margins.Left = gp.UnitsToPoints(margin) } // SetMarginTop sets the top margin func (gp *GoPdf) SetMarginTop(margin float64) { gp.margins.Top = gp.UnitsToPoints(margin) } // SetMarginRight sets the right margin func (gp *GoPdf) SetMarginRight(margin float64) { gp.margins.Right = gp.UnitsToPoints(margin) } // SetMarginBottom set the bottom margin func (gp *GoPdf) SetMarginBottom(margin float64) { gp.margins.Bottom = gp.UnitsToPoints(margin) } // Margins gets the current margins, The margins will be converted back to the documents units. Returned values will be in the following order Left, Top, Right, Bottom func (gp *GoPdf) Margins() (float64, float64, float64, float64) { return gp.PointsToUnits(gp.margins.Left), gp.PointsToUnits(gp.margins.Top), gp.PointsToUnits(gp.margins.Right), gp.PointsToUnits(gp.margins.Bottom) } // MarginLeft returns the left margin. func (gp *GoPdf) MarginLeft() float64 { return gp.PointsToUnits(gp.margins.Left) } // MarginTop returns the top margin. func (gp *GoPdf) MarginTop() float64 { return gp.PointsToUnits(gp.margins.Top) } // MarginRight returns the right margin. func (gp *GoPdf) MarginRight() float64 { return gp.PointsToUnits(gp.margins.Right) } // MarginBottom returns the bottom margin. func (gp *GoPdf) MarginBottom() float64 { return gp.PointsToUnits(gp.margins.Bottom) } ================================================ FILE: vendor/github.com/signintech/gopdf/outlines_obj.go ================================================ package gopdf import ( "fmt" "io" ) //OutlinesObj : outlines dictionary type OutlinesObj struct { //impl IObj getRoot func() *GoPdf index int first int last int count int lastObj *OutlineObj } func (o *OutlinesObj) init(funcGetRoot func() *GoPdf) { o.getRoot = funcGetRoot o.first = -1 o.last = -1 } func (o *OutlinesObj) getType() string { return "Outlines" } func (o *OutlinesObj) write(w io.Writer, objID int) error { content := "<<\n" content += fmt.Sprintf("\t/Type /%s\n", o.getType()) content += fmt.Sprintf("\t/Count %d\n", o.count) if o.first >= 0 { content += fmt.Sprintf("\t/First %d 0 R\n", o.first) } if o.last >= 0 { content += fmt.Sprintf("\t/Last %d 0 R\n", o.last) } content += ">>\n" if _, err := io.WriteString(w, content); err != nil { return err } return nil } func (o *OutlinesObj) SetIndexObjOutlines(index int) { o.index = index } func (o *OutlinesObj) AddOutline(dest int, title string) { oo := &OutlineObj{title: title, dest: dest, parent: o.index, prev: o.last, next: -1} o.last = o.getRoot().addObj(oo) + 1 if o.first <= 0 { o.first = o.last } if o.lastObj != nil { o.lastObj.next = o.last } o.lastObj = oo o.count++ } func (o *OutlinesObj) Count() int { return o.count } type OutlineObj struct { //impl IObj title string dest int parent int prev int next int } func (o *OutlineObj) init(funcGetRoot func() *GoPdf) { } func (o *OutlineObj) getType() string { return "Outline" } func (o *OutlineObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") fmt.Fprintf(w, " /Parent %d 0 R\n", o.parent+1) if o.prev >= 0 { fmt.Fprintf(w, " /Prev %d 0 R\n", o.prev) } if o.next >= 0 { fmt.Fprintf(w, " /Next %d 0 R\n", o.next) } fmt.Fprintf(w, " /Dest [ %d 0 R /XYZ null null null ]\n", o.dest) fmt.Fprintf(w, " /Title \n", encodeUtf8(o.title)) io.WriteString(w, ">>\n") return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/page_obj.go ================================================ package gopdf import ( "fmt" "io" "strings" ) //PageObj pdf page object type PageObj struct { //impl IObj Contents string ResourcesRelate string pageOption PageOption Links []linkOption getRoot func() *GoPdf } func (p *PageObj) init(funcGetRoot func() *GoPdf) { p.getRoot = funcGetRoot p.Links = make([]linkOption, 0) } func (p *PageObj) setOption(opt PageOption) { p.pageOption = opt } func (p *PageObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") fmt.Fprintf(w, " /Type /%s\n", p.getType()) io.WriteString(w, " /Parent 2 0 R\n") fmt.Fprintf(w, " /Resources %s\n", p.ResourcesRelate) var err error gp := p.getRoot() if len(p.Links) > 0 { io.WriteString(w, " /Annots [") for _, l := range p.Links { if l.url != "" { err = p.writeExternalLink(w, l, objID) } else { err = p.writeInternalLink(w, l, gp.anchors) } if err != nil { return err } } io.WriteString(w, "]\n") } /*me.buffer.WriteString(" /Font <<\n") i := 0 max := len(me.Realtes) for i < max { realte := me.Realtes[i] me.buffer.WriteString(fmt.Sprintf(" /F%d %d 0 R\n",realte.CountOfFont + 1, realte.IndexOfObj + 1)) i++ } me.buffer.WriteString(" >>\n")*/ //me.buffer.WriteString(" >>\n") fmt.Fprintf(w, " /Contents %s\n", p.Contents) //sample Contents 8 0 R if !p.pageOption.isEmpty() { fmt.Fprintf(w, " /MediaBox [ 0 0 %0.2f %0.2f ]\n", p.pageOption.PageSize.W, p.pageOption.PageSize.H) } if p.pageOption.isTrimBoxSet() { trimBox := p.pageOption.TrimBox fmt.Fprintf(w, " /TrimBox [ %0.2f %0.2f %0.2f %0.2f ]\n", trimBox.Left, trimBox.Top, trimBox.Right, trimBox.Bottom) } io.WriteString(w, ">>\n") return nil } func (p *PageObj) writeExternalLink(w io.Writer, l linkOption, objID int) error { protection := p.getRoot().protection() url := l.url if protection != nil { tmp, err := rc4Cip(protection.objectkey(objID), []byte(url)) if err != nil { return err } url = string(tmp) } url = strings.Replace(url, "\\", "\\\\", -1) url = strings.Replace(url, "(", "\\(", -1) url = strings.Replace(url, ")", "\\)", -1) url = strings.Replace(url, "\r", "\\r", -1) _, err := fmt.Fprintf(w, "<>>>", l.x, l.y, l.x+l.w, l.y-l.h, url) return err } func (p *PageObj) writeInternalLink(w io.Writer, l linkOption, anchors map[string]anchorOption) error { a, ok := anchors[l.anchor] if !ok { return nil } _, err := fmt.Fprintf(w, "<>", l.x, l.y, l.x+l.w, l.y-l.h, a.page+1, a.y) return err } func (p *PageObj) getType() string { return "Page" } ================================================ FILE: vendor/github.com/signintech/gopdf/page_option.go ================================================ package gopdf //PageOption option of page type PageOption struct { TrimBox *Box PageSize *Rect } func (p PageOption) isEmpty() bool { if p.PageSize == nil { return true } return false } func (p PageOption) isTrimBoxSet() bool { if p.TrimBox == nil { return false } if p.TrimBox.Top == 0 && p.TrimBox.Left == 0 && p.TrimBox.Bottom == 0 && p.TrimBox.Right == 0 { return false } return true } ================================================ FILE: vendor/github.com/signintech/gopdf/page_sizes.go ================================================ package gopdf // PageSizeLetter page format var PageSizeLetter = &Rect{W: 612, H: 792, unitOverride: UnitPT} // PageSizeLetterSmall page format var PageSizeLetterSmall = &Rect{W: 612, H: 792, unitOverride: UnitPT} // PageSizeTabloid page format var PageSizeTabloid = &Rect{W: 792, H: 1224, unitOverride: UnitPT} // PageSizeLedger page format var PageSizeLedger = &Rect{W: 1224, H: 792, unitOverride: UnitPT} // PageSizeLegal page format var PageSizeLegal = &Rect{W: 612, H: 1008, unitOverride: UnitPT} // PageSizeStatement page format var PageSizeStatement = &Rect{W: 396, H: 612, unitOverride: UnitPT} // PageSizeExecutive page format var PageSizeExecutive = &Rect{W: 540, H: 720, unitOverride: UnitPT} // PageSizeA0 page format var PageSizeA0 = &Rect{W: 2384, H: 3371, unitOverride: UnitPT} // PageSizeA1 page format var PageSizeA1 = &Rect{W: 1685, H: 2384, unitOverride: UnitPT} // PageSizeA2 page format var PageSizeA2 = &Rect{W: 1190, H: 1684, unitOverride: UnitPT} // PageSizeA3 page format var PageSizeA3 = &Rect{W: 842, H: 1190, unitOverride: UnitPT} // PageSizeA4 page format var PageSizeA4 = &Rect{W: 595, H: 842, unitOverride: UnitPT} // PageSizeA4Small page format var PageSizeA4Small = &Rect{W: 595, H: 842, unitOverride: UnitPT} // PageSizeA5 page format var PageSizeA5 = &Rect{W: 420, H: 595, unitOverride: UnitPT} // PageSizeB4 page format var PageSizeB4 = &Rect{W: 729, H: 1032, unitOverride: UnitPT} // PageSizeB5 page format var PageSizeB5 = &Rect{W: 516, H: 729, unitOverride: UnitPT} // PageSizeFolio page format var PageSizeFolio = &Rect{W: 612, H: 936, unitOverride: UnitPT} // PageSizeQuarto page format var PageSizeQuarto = &Rect{W: 610, H: 780, unitOverride: UnitPT} // PageSize10x14 page format var PageSize10x14 = &Rect{W: 720, H: 1008, unitOverride: UnitPT} // // PageSizeEnvelope page format // var PageSizeEnvelope = Rect{W:???,H:???} ================================================ FILE: vendor/github.com/signintech/gopdf/pages_obj.go ================================================ package gopdf import ( "fmt" "io" ) //PagesObj pdf pages object type PagesObj struct { //impl IObj PageCount int Kids string getRoot func() *GoPdf } func (p *PagesObj) init(funcGetRoot func() *GoPdf) { p.PageCount = 0 p.getRoot = funcGetRoot } func (p *PagesObj) write(w io.Writer, objID int) error { io.WriteString(w, "<<\n") fmt.Fprintf(w, " /Type /%s\n", p.getType()) rootConfig := p.getRoot().config fmt.Fprintf(w, " /MediaBox [ 0 0 %0.2f %0.2f ]\n", rootConfig.PageSize.W, rootConfig.PageSize.H) fmt.Fprintf(w, " /Count %d\n", p.PageCount) fmt.Fprintf(w, " /Kids [ %s ]\n", p.Kids) //sample Kids [ 3 0 R ] io.WriteString(w, ">>\n") return nil } func (p *PagesObj) getType() string { return "Pages" } func (p *PagesObj) test() { fmt.Print(p.getType() + "\n") } ================================================ FILE: vendor/github.com/signintech/gopdf/pdf_dictionary_obj.go ================================================ package gopdf import ( "compress/zlib" "errors" "fmt" "io" "sort" "github.com/signintech/gopdf/fontmaker/core" ) //EntrySelectors entry selectors var EntrySelectors = []int{ 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, } //ErrNotSupportShortIndexYet not suport none short index yet var ErrNotSupportShortIndexYet = errors.New("not suport none short index yet") //PdfDictionaryObj pdf dictionary object type PdfDictionaryObj struct { PtrToSubsetFontObj *SubsetFontObj //getRoot func() *GoPdf pdfProtection *PDFProtection } func (p *PdfDictionaryObj) init(funcGetRoot func() *GoPdf) { //p.getRoot = funcGetRoot } func (p *PdfDictionaryObj) setProtection(pr *PDFProtection) { p.pdfProtection = pr } func (p *PdfDictionaryObj) protection() *PDFProtection { return p.pdfProtection } func (p *PdfDictionaryObj) write(w io.Writer, objID int) error { b, err := p.makeFont() if err != nil { //log.Panicf("%s", err.Error()) return err } //zipvar buff bytes.Buffer zbuff := GetBuffer() defer PutBuffer(zbuff) gzipwriter := zlib.NewWriter(zbuff) _, err = gzipwriter.Write(b) if err != nil { return err } gzipwriter.Close() fmt.Fprintf(w, "<>\n") io.WriteString(w, "stream\n") if p.protection() != nil { tmp, err := rc4Cip(p.protection().objectkey(objID), zbuff.Bytes()) if err != nil { return err } w.Write(tmp) //p.buffer.WriteString("\n") } else { w.Write(zbuff.Bytes()) } io.WriteString(w, "\nendstream\n") return nil } func (p *PdfDictionaryObj) getType() string { return "PdfDictionary" } //SetPtrToSubsetFontObj set subsetFontObj pointer func (p *PdfDictionaryObj) SetPtrToSubsetFontObj(ptr *SubsetFontObj) { p.PtrToSubsetFontObj = ptr } func (p *PdfDictionaryObj) makeGlyfAndLocaTable() ([]byte, []int, error) { ttfp := p.PtrToSubsetFontObj.GetTTFParser() var glyf core.TableDirectoryEntry numGlyphs := int(ttfp.NumGlyphs()) glyphArray := p.completeGlyphClosure(p.PtrToSubsetFontObj.CharacterToGlyphIndex) sort.Ints(glyphArray) glyphCount := len(glyphArray) size := 0 for idx := 0; idx < glyphCount; idx++ { size += p.getGlyphSize(glyphArray[idx]) } glyf.Length = uint(size) glyphTable := make([]byte, glyf.PaddedLength()) locaTable := make([]int, numGlyphs+1) glyphOffset := 0 glyphIndex := 0 oldglyph := -1 for idx := 0; idx < numGlyphs; idx++ { locaTable[idx] = glyphOffset if glyphIndex < glyphCount { if glyphArray[glyphIndex] == idx { oldglyph = glyphArray[glyphIndex] glyphIndex++ bytes := p.getGlyphData(idx) length := len(bytes) if length > 0 { for i := 0; i < length; i++ { glyphTable[glyphOffset+i] = bytes[i] } glyphOffset += length } } else if oldglyph == glyphArray[glyphIndex] { glyphIndex++ bytes := p.getGlyphData(idx) length := len(bytes) if length > 0 { for i := 0; i < length; i++ { glyphTable[glyphOffset+i] = bytes[i] } glyphOffset += length } } } } //end for locaTable[numGlyphs] = glyphOffset return glyphTable, locaTable, nil } func (p *PdfDictionaryObj) getGlyphSize(glyph int) int { ttfp := p.PtrToSubsetFontObj.GetTTFParser() glyf := ttfp.GetTables()["glyf"] start := int(glyf.Offset + ttfp.LocaTable[glyph]) next := int(glyf.Offset + ttfp.LocaTable[glyph+1]) return next - start } func (p *PdfDictionaryObj) getGlyphData(glyph int) []byte { ttfp := p.PtrToSubsetFontObj.GetTTFParser() glyf := ttfp.GetTables()["glyf"] start := int(glyf.Offset + ttfp.LocaTable[glyph]) next := int(glyf.Offset + ttfp.LocaTable[glyph+1]) count := next - start var data []byte i := 0 for i < count { data = append(data, ttfp.FontData()[start+i]) i++ } return data } func (p *PdfDictionaryObj) makeFont() ([]byte, error) { var buff Buff ttfp := p.PtrToSubsetFontObj.GetTTFParser() tables := make(map[string]core.TableDirectoryEntry) tables["cvt "] = ttfp.GetTables()["cvt "] //มีช่องว่างด้วยนะ tables["fpgm"] = ttfp.GetTables()["fpgm"] tables["glyf"] = ttfp.GetTables()["glyf"] tables["head"] = ttfp.GetTables()["head"] tables["hhea"] = ttfp.GetTables()["hhea"] tables["hmtx"] = ttfp.GetTables()["hmtx"] tables["loca"] = ttfp.GetTables()["loca"] tables["maxp"] = ttfp.GetTables()["maxp"] tables["prep"] = ttfp.GetTables()["prep"] tableCount := len(tables) selector := EntrySelectors[tableCount] glyphTable, locaTable, err := p.makeGlyfAndLocaTable() if err != nil { return nil, err } WriteUInt32(&buff, 0x00010000) WriteUInt16(&buff, uint(tableCount)) WriteUInt16(&buff, ((1 << uint(selector)) * 16)) WriteUInt16(&buff, uint(selector)) WriteUInt16(&buff, (uint(tableCount)-(1<> 8) byteIdx++ data[byteIdx] = byte(val) byteIdx++ } } else { for idx := 0; idx < length; idx++ { val := locaTable[idx] data[byteIdx] = byte(val >> 24) byteIdx++ data[byteIdx] = byte(val >> 16) byteIdx++ data[byteIdx] = byte(val >> 8) byteIdx++ data[byteIdx] = byte(val) byteIdx++ } } entry.CheckSum = CheckSum(data) WriteBytes(&buff, data, 0, len(data)) } else { WriteBytes(&buff, ttfp.FontData(), int(entry.Offset), entry.PaddedLength()) } endPosition := buff.Position() tablePosition = endPosition //write table buff.SetPosition(idx*16 + 12) WriteTag(&buff, tags[idx]) WriteUInt32(&buff, uint(entry.CheckSum)) WriteUInt32(&buff, uint(offset)) //offset WriteUInt32(&buff, uint(entry.Length)) tablePosition = endPosition idx++ } //DebugSubType(buff.Bytes()) //me.buffer.Write(buff.Bytes()) return buff.Bytes(), nil } func (p *PdfDictionaryObj) completeGlyphClosure(mapOfglyphs *MapOfCharacterToGlyphIndex) []int { var glyphArray []int //copy isContainZero := false glyphs := mapOfglyphs.AllVals() for _, v := range glyphs { glyphArray = append(glyphArray, int(v)) if v == 0 { isContainZero = true } } if !isContainZero { glyphArray = append(glyphArray, 0) } i := 0 count := len(glyphs) for i < count { p.AddCompositeGlyphs(&glyphArray, glyphArray[i]) i++ } return glyphArray } //AddCompositeGlyphs add composite glyph //composite glyph is a Unicode entity that can be defined as a sequence of one or more other characters. func (p *PdfDictionaryObj) AddCompositeGlyphs(glyphArray *[]int, glyph int) { start := p.GetOffset(int(glyph)) if start == p.GetOffset(int(glyph)+1) { return } offset := start ttfp := p.PtrToSubsetFontObj.GetTTFParser() fontData := ttfp.FontData() numContours, step := ReadShortFromByte(fontData, offset) offset += step if numContours >= 0 { return } offset += 8 for { flags, step1 := ReadUShortFromByte(fontData, offset) offset += step1 cGlyph, step2 := ReadUShortFromByte(fontData, offset) offset += step2 //check cGlyph is contain in glyphArray? glyphContainsKey := false for _, g := range *glyphArray { if g == int(cGlyph) { glyphContainsKey = true break } } if !glyphContainsKey { *glyphArray = append(*glyphArray, int(cGlyph)) } if (flags & moreComponents) == 0 { return } offsetAppend := 4 if (flags & arg1and2areWords) == 0 { offsetAppend = 2 } if (flags & hasScale) != 0 { offsetAppend += 2 } else if (flags & xAndYScale) != 0 { offsetAppend += 4 } if (flags & twoByTwo) != 0 { offsetAppend += 8 } offset += offsetAppend } } const hasScale = 8 const moreComponents = 32 const arg1and2areWords = 1 const xAndYScale = 64 const twoByTwo = 128 //GetOffset get offset from glyf table func (p *PdfDictionaryObj) GetOffset(glyph int) int { ttfp := p.PtrToSubsetFontObj.GetTTFParser() glyf := ttfp.GetTables()["glyf"] offset := int(glyf.Offset + ttfp.LocaTable[glyph]) return offset } //CheckSum check sum func CheckSum(data []byte) uint { var byte3, byte2, byte1, byte0 uint64 byte3 = 0 byte2 = 0 byte1 = 0 byte0 = 0 length := len(data) i := 0 for i < length { byte3 += uint64(data[i]) i++ byte2 += uint64(data[i]) i++ byte1 += uint64(data[i]) i++ byte0 += uint64(data[i]) i++ } //var result uint32 result := uint32(byte3<<24) + uint32(byte2<<16) + uint32(byte1<<8) + uint32(byte0) return uint(result) } ================================================ FILE: vendor/github.com/signintech/gopdf/pdf_info_obj.go ================================================ package gopdf import "time" //PdfInfo Document Information Dictionary type PdfInfo struct { Title string //The document’s title Author string //The name of the person who created the document. Subject string //The subject of the document. Creator string // If the document was converted to PDF from another format, the name of the application original document from which it was converted. Producer string //If the document was converted to PDF from another format, the name of the application (for example, Acrobat Distiller) that converted it to PDF. CreationDate time.Time //The date and time the document was created, in human-readable form } ================================================ FILE: vendor/github.com/signintech/gopdf/pdf_protection.go ================================================ package gopdf import ( "crypto/md5" "crypto/rc4" "encoding/binary" "math/rand" "time" ) const ( //PermissionsPrint setProtection print PermissionsPrint = 4 //PermissionsModify setProtection modify PermissionsModify = 8 //PermissionsCopy setProtection copy PermissionsCopy = 16 //PermissionsAnnotForms setProtection annot-forms PermissionsAnnotForms = 32 ) var protectionPadding = []byte{ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A, } //PDFProtection protection in pdf type PDFProtection struct { encrypted bool //whether document is protected uValue []byte //U entry in pdf document oValue []byte //O entry in pdf document pValue int //P entry in pdf document //var $enc_obj_id; //encryption object id encryptionKey []byte } //SetProtection set protection infomation func (p *PDFProtection) SetProtection(permissions int, userPass []byte, ownerPass []byte) error { return p.setProtection(permissions, userPass, ownerPass) } func (p *PDFProtection) setProtection(permissions int, userPass []byte, ownerPass []byte) error { protection := 192 | permissions if ownerPass == nil || len(ownerPass) == 0 { ownerPass = p.randomPass(24) } return p.generateEncryptionKey(userPass, ownerPass, protection) } func (p *PDFProtection) generateEncryptionKey(userPass []byte, ownerPass []byte, protection int) error { userPass = append(userPass, protectionPadding...) userPassWithPadding := userPass[0:32] ownerPass = append(ownerPass, protectionPadding...) ownerPassWithPadding := ownerPass[0:32] //oValue oValue, err := p.createOValue(userPassWithPadding, ownerPassWithPadding) if err != nil { return err } p.oValue = oValue uValue, err := p.createUValue(userPassWithPadding, oValue, protection) if err != nil { return err } p.uValue = uValue p.pValue = -((protection ^ 255) + 1) return nil } //EncryptionObj get Encryption Object func (p *PDFProtection) EncryptionObj() *EncryptionObj { return p.encryptionObj() } func (p *PDFProtection) encryptionObj() *EncryptionObj { var en EncryptionObj en.oValue = p.oValue en.pValue = p.pValue en.uValue = p.uValue return &en } func (p *PDFProtection) createOValue(userPassWithPadding []byte, ownerPassWithPadding []byte) ([]byte, error) { tmp := md5.Sum(ownerPassWithPadding) ownerRC4key := tmp[0:5] cip, err := rc4.NewCipher(ownerRC4key) if err != nil { return nil, err } dest := make([]byte, len(userPassWithPadding)) cip.XORKeyStream(dest, userPassWithPadding) return dest, nil } func (p *PDFProtection) createUValue(userPassWithPadding []byte, oValue []byte, protection int) ([]byte, error) { m := md5.New() m.Write(userPassWithPadding) m.Write(oValue) m.Write([]byte{byte(protection), byte(0xff), byte(0xff), byte(0xff)}) tmp2 := m.Sum(nil) p.encryptionKey = tmp2[0:5] cip, err := rc4.NewCipher(p.encryptionKey) if err != nil { return nil, err } dest := make([]byte, len(protectionPadding)) cip.XORKeyStream(dest, protectionPadding) return dest, nil } func (p *PDFProtection) randomPass(strlen int) []byte { rand.Seed(time.Now().UTC().UnixNano()) const chars = "abcdef0123456789" result := make([]byte, strlen) for i := 0; i < strlen; i++ { result[i] = chars[rand.Intn(len(chars))] } return result } //Objectkey create object key from ObjID func (p *PDFProtection) Objectkey(objID int) []byte { return p.objectkey(objID) } func (p *PDFProtection) objectkey(n int) []byte { tmp := make([]byte, 8, 8) binary.LittleEndian.PutUint32(tmp, uint32(n)) tmp2 := append(p.encryptionKey, tmp[0], tmp[1], tmp[2], 0, 0) tmp3 := md5.Sum(tmp2) return tmp3[0:10] } func rc4Cip(key []byte, src []byte) ([]byte, error) { cip, err := rc4.NewCipher(key) if err != nil { return nil, err } dest := make([]byte, len(src)) cip.XORKeyStream(dest, src) return dest, nil } ================================================ FILE: vendor/github.com/signintech/gopdf/point.go ================================================ package gopdf //Point a point in a two-dimensional type Point struct { X float64 Y float64 } ================================================ FILE: vendor/github.com/signintech/gopdf/procset_obj.go ================================================ package gopdf import ( "fmt" "io" ) // ProcSetObj is a PDF procSet object. type ProcSetObj struct { //Font Relates RelateFonts RelateXobjs RelateXobjects ExtGStates []ExtGS ImportedTemplateIds map[string]int getRoot func() *GoPdf } func (pr *ProcSetObj) init(funcGetRoot func() *GoPdf) { pr.getRoot = funcGetRoot pr.ImportedTemplateIds = make(map[string]int, 0) pr.ExtGStates = make([]ExtGS, 0) } func (pr *ProcSetObj) write(w io.Writer, objID int) error { content := "<<\n" content += "\t/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n" fonts := "\t/Font <<\n" for _, relate := range pr.Relates { fonts += fmt.Sprintf("\t\t/F%d %d 0 R\n", relate.CountOfFont+1, relate.IndexOfObj+1) } fonts += "\t>>\n" content += fonts xobjects := "\t/XObject <<\n" for _, XObject := range pr.RelateXobjs { xobjects += fmt.Sprintf("\t\t/I%d %d 0 R\n", XObject.IndexOfObj+1, XObject.IndexOfObj+1) } // Write imported template name and their ids for tplName, objID := range pr.ImportedTemplateIds { xobjects += fmt.Sprintf("\t\t%s %d 0 R\n", tplName, objID) } xobjects += "\t>>\n" content += xobjects extGStates := "\t/ExtGState <<\n" for _, extGState := range pr.ExtGStates { extGStates += fmt.Sprintf("\t\t/GS%d %d 0 R\n", extGState.Index+1, extGState.Index+1) } extGStates += "\t>>\n" content += extGStates content += ">>\n" if _, err := io.WriteString(w, content); err != nil { return err } return nil } func (pr *ProcSetObj) getType() string { return "ProcSet" } // RelateFonts is a slice of RelateFont. type RelateFonts []RelateFont // IsContainsFamily checks if font family exists. func (re *RelateFonts) IsContainsFamily(family string) bool { for _, rf := range *re { if rf.Family == family { return true } } return false } // IsContainsFamilyAndStyle checks if font with same name and style already exists . func (re *RelateFonts) IsContainsFamilyAndStyle(family string, style int) bool { for _, rf := range *re { if rf.Family == family && rf.Style == style { return true } } return false } // RelateFont is a metadata index for fonts? type RelateFont struct { Family string //etc /F1 CountOfFont int //etc 5 0 R IndexOfObj int Style int // Regular|Bold|Italic } // RelateXobjects is a slice of RelateXobject. type RelateXobjects []RelateXobject // RelateXobject is an index for ??? type RelateXobject struct { IndexOfObj int } // ExtGS is ??? type ExtGS struct { Index int } ================================================ FILE: vendor/github.com/signintech/gopdf/rect.go ================================================ package gopdf // Rect defines a rectangle. type Rect struct { W float64 H float64 unitOverride int } // PointsToUnits converts the rectangles width and height to Units. When this is called it is assumed the values of the rectangle are in Points func (rect *Rect) PointsToUnits(t int) (r *Rect) { if rect == nil { return } if rect.unitOverride != UnitUnset { t = rect.unitOverride } r = &Rect{W: rect.W, H: rect.H} PointsToUnitsVar(t, &r.W, &r.H) return } // UnitsToPoints converts the rectanlges width and height to Points. When this is called it is assumed the values of the rectangle are in Units func (rect *Rect) UnitsToPoints(t int) (r *Rect) { if rect == nil { return } if rect.unitOverride != UnitUnset { t = rect.unitOverride } r = &Rect{W: rect.W, H: rect.H} UnitsToPointsVar(t, &r.W, &r.H) return } ================================================ FILE: vendor/github.com/signintech/gopdf/smask_obj.go ================================================ package gopdf import ( "fmt" "io" "sync" ) type SMaskSubtypes string const ( SMaskAlphaSubtype = "/Alpha" SMaskLuminositySubtype = "/Luminosity" ) //SMask smask type SMask struct { imgInfo data []byte //getRoot func() *GoPdf pdfProtection *PDFProtection Index int TransparencyXObjectGroupIndex int S string } type SMaskOptions struct { TransparencyXObjectGroupIndex int Subtype SMaskSubtypes } func (smask SMaskOptions) GetId() string { id := fmt.Sprintf("S_%s;G_%d_0_R", smask.Subtype, smask.TransparencyXObjectGroupIndex) return id } func GetCachedMask(opts SMaskOptions, gp *GoPdf) SMask { smask, ok := gp.curr.sMasksMap.Find(opts) if !ok { smask = SMask{ S: string(opts.Subtype), TransparencyXObjectGroupIndex: opts.TransparencyXObjectGroupIndex, } smask.Index = gp.addObj(smask) gp.curr.sMasksMap.Save(opts.GetId(), smask) } return smask } func (s SMask) init(func() *GoPdf) {} func (s *SMask) setProtection(p *PDFProtection) { s.pdfProtection = p } func (s SMask) protection() *PDFProtection { return s.pdfProtection } func (s SMask) getType() string { return "Mask" } func (s SMask) write(w io.Writer, objID int) error { if s.TransparencyXObjectGroupIndex != 0 { content := "<<\n" content += "\t/Type /Mask\n" content += fmt.Sprintf("\t/S %s\n", s.S) content += fmt.Sprintf("\t/G %d 0 R\n", s.TransparencyXObjectGroupIndex+1) content += ">>\n" if _, err := io.WriteString(w, content); err != nil { return err } } else { err := writeImgProps(w, s.imgInfo, false) if err != nil { return err } fmt.Fprintf(w, "/Length %d\n>>\n", len(s.data)) // /Length 62303>>\n io.WriteString(w, "stream\n") if s.protection() != nil { tmp, err := rc4Cip(s.protection().objectkey(objID), s.data) if err != nil { return err } w.Write(tmp) io.WriteString(w, "\n") } else { w.Write(s.data) } io.WriteString(w, "\nendstream\n") } return nil } type SMaskMap struct { syncer sync.Mutex table map[string]SMask } func NewSMaskMap() SMaskMap { return SMaskMap{ syncer: sync.Mutex{}, table: make(map[string]SMask), } } func (smask *SMaskMap) Find(sMask SMaskOptions) (SMask, bool) { key := sMask.GetId() smask.syncer.Lock() defer smask.syncer.Unlock() t, ok := smask.table[key] if !ok { return SMask{}, false } return t, ok } func (smask *SMaskMap) Save(id string, sMask SMask) SMask { smask.syncer.Lock() defer smask.syncer.Unlock() smask.table[id] = sMask return sMask } ================================================ FILE: vendor/github.com/signintech/gopdf/strhelper.go ================================================ package gopdf import ( "math/big" "strings" ) //StrHelperGetStringWidth get string width func StrHelperGetStringWidth(str string, fontSize int, ifont IFont) float64 { w := 0 bs := []byte(str) i := 0 max := len(bs) for i < max { w += ifont.GetCw()[bs[i]] i++ } return float64(w) * (float64(fontSize) / 1000.0) } //CreateEmbeddedFontSubsetName create Embedded font (subset font) name func CreateEmbeddedFontSubsetName(name string) string { name = strings.Replace(name, " ", "+", -1) name = strings.Replace(name, "/", "+", -1) return name } //ReadShortFromByte read short from byte array func ReadShortFromByte(data []byte, offset int) (int64, int) { buff := data[offset : offset+2] num := big.NewInt(0) num.SetBytes(buff) u := num.Uint64() var v int64 if u >= 0x8000 { v = int64(u) - 65536 } else { v = int64(u) } return v, 2 } //ReadUShortFromByte read ushort from byte array func ReadUShortFromByte(data []byte, offset int) (uint64, int) { buff := data[offset : offset+2] num := big.NewInt(0) num.SetBytes(buff) return num.Uint64(), 2 } ================================================ FILE: vendor/github.com/signintech/gopdf/style.go ================================================ package gopdf type PaintStyle string const ( DrawPaintStyle PaintStyle = "S" FillPaintStyle PaintStyle = "f" DrawFillPaintStyle PaintStyle = "B" ) func parseStyle(style string) PaintStyle { op := DrawPaintStyle if style == "F" { op = FillPaintStyle } else if style == "FD" || style == "DF" { op = DrawFillPaintStyle } return op } ================================================ FILE: vendor/github.com/signintech/gopdf/subfont_descriptor_obj.go ================================================ package gopdf import ( "fmt" "io" "github.com/signintech/gopdf/fontmaker/core" ) //SubfontDescriptorObj pdf subfont descriptorObj object type SubfontDescriptorObj struct { PtrToSubsetFontObj *SubsetFontObj indexObjPdfDictionary int } func (s *SubfontDescriptorObj) init(func() *GoPdf) {} func (s *SubfontDescriptorObj) getType() string { return "SubFontDescriptor" } func (s *SubfontDescriptorObj) write(w io.Writer, objID int) error { ttfp := s.PtrToSubsetFontObj.GetTTFParser() //fmt.Printf("-->%d\n", ttfp.UnitsPerEm()) io.WriteString(w, "<<\n") io.WriteString(w, "/Type /FontDescriptor\n") fmt.Fprintf(w, "/Ascent %d\n", DesignUnitsToPdf(ttfp.Ascender(), ttfp.UnitsPerEm())) fmt.Fprintf(w, "/CapHeight %d\n", DesignUnitsToPdf(ttfp.CapHeight(), ttfp.UnitsPerEm())) fmt.Fprintf(w, "/Descent %d\n", DesignUnitsToPdf(ttfp.Descender(), ttfp.UnitsPerEm())) fmt.Fprintf(w, "/Flags %d\n", ttfp.Flag()) fmt.Fprintf(w, "/FontBBox [%d %d %d %d]\n", DesignUnitsToPdf(ttfp.XMin(), ttfp.UnitsPerEm()), DesignUnitsToPdf(ttfp.YMin(), ttfp.UnitsPerEm()), DesignUnitsToPdf(ttfp.XMax(), ttfp.UnitsPerEm()), DesignUnitsToPdf(ttfp.YMax(), ttfp.UnitsPerEm()), ) fmt.Fprintf(w, "/FontFile2 %d 0 R\n", s.indexObjPdfDictionary+1) fmt.Fprintf(w, "/FontName /%s\n", CreateEmbeddedFontSubsetName(s.PtrToSubsetFontObj.GetFamily())) fmt.Fprintf(w, "/ItalicAngle %d\n", ttfp.ItalicAngle()) io.WriteString(w, "/StemV 0\n") fmt.Fprintf(w, "/XHeight %d\n", DesignUnitsToPdf(ttfp.XHeight(), ttfp.UnitsPerEm())) io.WriteString(w, ">>\n") return nil } //SetIndexObjPdfDictionary set PdfDictionary pointer func (s *SubfontDescriptorObj) SetIndexObjPdfDictionary(index int) { s.indexObjPdfDictionary = index } //SetPtrToSubsetFontObj set SubsetFont pointer func (s *SubfontDescriptorObj) SetPtrToSubsetFontObj(ptr *SubsetFontObj) { s.PtrToSubsetFontObj = ptr } //DesignUnitsToPdf convert unit func DesignUnitsToPdf(val int, unitsPerEm uint) int { return core.Round(float64(float64(val) * 1000.00 / float64(unitsPerEm))) } ================================================ FILE: vendor/github.com/signintech/gopdf/subset_font_obj.go ================================================ package gopdf import ( "errors" "fmt" "io" "github.com/signintech/gopdf/fontmaker/core" ) //ErrCharNotFound char not found var ErrCharNotFound = errors.New("char not found") //ErrGlyphNotFound font file not contain glyph var ErrGlyphNotFound = errors.New("glyph not found") //SubsetFontObj pdf subsetFont object type SubsetFontObj struct { ttfp core.TTFParser Family string CharacterToGlyphIndex *MapOfCharacterToGlyphIndex CountOfFont int indexObjCIDFont int indexObjUnicodeMap int ttfFontOption TtfOption funcKernOverride FuncKernOverride } func (s *SubsetFontObj) init(funcGetRoot func() *GoPdf) { s.CharacterToGlyphIndex = NewMapOfCharacterToGlyphIndex() //make(map[rune]uint) s.funcKernOverride = nil } func (s *SubsetFontObj) write(w io.Writer, objID int) error { //me.AddChars("จ") io.WriteString(w, "<<\n") fmt.Fprintf(w, "/BaseFont /%s\n", CreateEmbeddedFontSubsetName(s.Family)) fmt.Fprintf(w, "/DescendantFonts [%d 0 R]\n", s.indexObjCIDFont+1) io.WriteString(w, "/Encoding /Identity-H\n") io.WriteString(w, "/Subtype /Type0\n") fmt.Fprintf(w, "/ToUnicode %d 0 R\n", s.indexObjUnicodeMap+1) io.WriteString(w, "/Type /Font\n") io.WriteString(w, ">>\n") return nil } //SetIndexObjCIDFont set IndexObjCIDFont func (s *SubsetFontObj) SetIndexObjCIDFont(index int) { s.indexObjCIDFont = index } //SetIndexObjUnicodeMap set IndexObjUnicodeMap func (s *SubsetFontObj) SetIndexObjUnicodeMap(index int) { s.indexObjUnicodeMap = index } //SetFamily set font family name func (s *SubsetFontObj) SetFamily(familyname string) { s.Family = familyname } //GetFamily get font family name func (s *SubsetFontObj) GetFamily() string { return s.Family } //SetTtfFontOption set TtfOption must set before SetTTFByPath func (s *SubsetFontObj) SetTtfFontOption(option TtfOption) { s.ttfFontOption = option } //GetTtfFontOption get TtfOption must set before SetTTFByPath func (s *SubsetFontObj) GetTtfFontOption() TtfOption { return s.ttfFontOption } //KernValueByLeft find kern value from kern table by left func (s *SubsetFontObj) KernValueByLeft(left uint) (bool, *core.KernValue) { if !s.ttfFontOption.UseKerning { return false, nil } k := s.ttfp.Kern() if k == nil { return false, nil } if kval, ok := k.Kerning[left]; ok { return true, &kval } return false, nil } //SetTTFByPath set ttf func (s *SubsetFontObj) SetTTFByPath(ttfpath string) error { useKerning := s.ttfFontOption.UseKerning s.ttfp.SetUseKerning(useKerning) err := s.ttfp.Parse(ttfpath) if err != nil { return err } return nil } //SetTTFByReader set ttf func (s *SubsetFontObj) SetTTFByReader(rd io.Reader) error { useKerning := s.ttfFontOption.UseKerning s.ttfp.SetUseKerning(useKerning) err := s.ttfp.ParseByReader(rd) if err != nil { return err } return nil } //SetTTFData set ttf func (s *SubsetFontObj) SetTTFData(data []byte) error { useKerning := s.ttfFontOption.UseKerning s.ttfp.SetUseKerning(useKerning) err := s.ttfp.ParseFontData(data) if err != nil { return err } return nil } //AddChars add char to map CharacterToGlyphIndex func (s *SubsetFontObj) AddChars(txt string) error { for _, runeValue := range txt { if s.CharacterToGlyphIndex.KeyExists(runeValue) { continue } glyphIndex, err := s.CharCodeToGlyphIndex(runeValue) if err == ErrGlyphNotFound { //never return error on this, just call function OnGlyphNotFound if s.ttfFontOption.OnGlyphNotFound != nil { s.ttfFontOption.OnGlyphNotFound(runeValue) } } else if err != nil { return err } s.CharacterToGlyphIndex.Set(runeValue, glyphIndex) // [runeValue] = glyphIndex } return nil } //CharIndex index of char in glyph table func (s *SubsetFontObj) CharIndex(r rune) (uint, error) { glyIndex, ok := s.CharacterToGlyphIndex.Val(r) if ok { return glyIndex, nil } return 0, ErrCharNotFound } //CharWidth with of char func (s *SubsetFontObj) CharWidth(r rune) (uint, error) { glyIndex, ok := s.CharacterToGlyphIndex.Val(r) if ok { return s.GlyphIndexToPdfWidth(glyIndex), nil } return 0, ErrCharNotFound } func (s *SubsetFontObj) getType() string { return "SubsetFont" } func (s *SubsetFontObj) charCodeToGlyphIndexFormat12(r rune) (uint, error) { value := uint(r) gTbs := s.ttfp.GroupingTables() for _, gTb := range gTbs { if value >= gTb.StartCharCode && value <= gTb.EndCharCode { gIndex := (value - gTb.StartCharCode) + gTb.GlyphID return gIndex, nil } } return uint(0), ErrGlyphNotFound } func (s *SubsetFontObj) charCodeToGlyphIndexFormat4(r rune) (uint, error) { value := uint(r) seg := uint(0) segCount := s.ttfp.SegCount for seg < segCount { if value <= s.ttfp.EndCount[seg] { break } seg++ } //fmt.Printf("\ncccc--->%#v\n", me.ttfp.Chars()) if value < s.ttfp.StartCount[seg] { return 0, ErrGlyphNotFound } if s.ttfp.IdRangeOffset[seg] == 0 { return (value + s.ttfp.IdDelta[seg]) & 0xFFFF, nil } //fmt.Printf("IdRangeOffset=%d\n", me.ttfp.IdRangeOffset[seg]) idx := s.ttfp.IdRangeOffset[seg]/2 + (value - s.ttfp.StartCount[seg]) - (segCount - seg) if s.ttfp.GlyphIdArray[int(idx)] == uint(0) { return 0, nil } return (s.ttfp.GlyphIdArray[int(idx)] + s.ttfp.IdDelta[seg]) & 0xFFFF, nil } // CharCodeToGlyphIndex gets glyph index from char code. func (s *SubsetFontObj) CharCodeToGlyphIndex(r rune) (uint, error) { value := uint64(r) if value <= 0xFFFF { gIndex, err := s.charCodeToGlyphIndexFormat4(r) if err != nil { return 0, err } return gIndex, nil } gIndex, err := s.charCodeToGlyphIndexFormat12(r) if err != nil { return 0, err } return gIndex, nil } // GlyphIndexToPdfWidth gets width from glyphIndex. func (s *SubsetFontObj) GlyphIndexToPdfWidth(glyphIndex uint) uint { numberOfHMetrics := s.ttfp.NumberOfHMetrics() unitsPerEm := s.ttfp.UnitsPerEm() if glyphIndex >= numberOfHMetrics { glyphIndex = numberOfHMetrics - 1 } width := s.ttfp.Widths()[glyphIndex] if unitsPerEm == 1000 { return width } return width * 1000 / unitsPerEm } // GetTTFParser gets TTFParser. func (s *SubsetFontObj) GetTTFParser() *core.TTFParser { return &s.ttfp } // GetUt underlineThickness. func (s *SubsetFontObj) GetUt() int { return s.ttfp.UnderlineThickness() } // GetUp underline postion. func (s *SubsetFontObj) GetUp() int { return s.ttfp.UnderlinePosition() } ================================================ FILE: vendor/github.com/signintech/gopdf/transparency.go ================================================ package gopdf import ( "fmt" "sync" "github.com/pkg/errors" ) type BlendModeType string const ( Hue BlendModeType = "/Hue" Color BlendModeType = "/Color" NormalBlendMode BlendModeType = "/Normal" Darken BlendModeType = "/Darken" Screen BlendModeType = "/Screen" Overlay BlendModeType = "/Overlay" Lighten BlendModeType = "/Lighten" Multiply BlendModeType = "/Multiply" Exclusion BlendModeType = "/Exclusion" ColorBurn BlendModeType = "/ColorBurn" HardLight BlendModeType = "/HardLight" SoftLight BlendModeType = "/SoftLight" Difference BlendModeType = "/Difference" Saturation BlendModeType = "/Saturation" Luminosity BlendModeType = "/Luminosity" ColorDodge BlendModeType = "/ColorDodge" ) const DefaultAplhaValue = 1 // Transparency defines an object alpha. type Transparency struct { extGStateIndex int Alpha float64 BlendModeType BlendModeType } func NewTransparency(alpha float64, blendModeType string) (Transparency, error) { if alpha < 0.0 || alpha > 1.0 { return Transparency{}, errors.Errorf("alpha value is out of range (0.0 - 1.0): %.3f", alpha) } bmtType, err := defineBlendModeType(blendModeType) if err != nil { return Transparency{}, err } return Transparency{ Alpha: alpha, BlendModeType: bmtType, }, nil } func (t Transparency) GetId() string { keyStr := fmt.Sprintf("%.3f_%s", t.Alpha, t.BlendModeType) return keyStr } type TransparencyMap struct { syncer sync.Mutex table map[string]Transparency } func NewTransparencyMap() TransparencyMap { return TransparencyMap{ syncer: sync.Mutex{}, table: make(map[string]Transparency), } } func (tm *TransparencyMap) Find(transparency Transparency) (Transparency, bool) { key := transparency.GetId() tm.syncer.Lock() defer tm.syncer.Unlock() t, ok := tm.table[key] if !ok { return Transparency{}, false } return t, ok } func (tm *TransparencyMap) Save(transparency Transparency) Transparency { tm.syncer.Lock() defer tm.syncer.Unlock() key := transparency.GetId() tm.table[key] = transparency return transparency } func defineBlendModeType(bmType string) (BlendModeType, error) { switch bmType { case string(Hue): return Hue, nil case string(Color): return Color, nil case "", string(NormalBlendMode): return NormalBlendMode, nil case string(Darken): return Darken, nil case string(Screen): return Screen, nil case string(Overlay): return Overlay, nil case string(Lighten): return Lighten, nil case string(Multiply): return Multiply, nil case string(Exclusion): return Exclusion, nil case string(ColorBurn): return ColorBurn, nil case string(HardLight): return HardLight, nil case string(SoftLight): return SoftLight, nil case string(Difference): return Difference, nil case string(Saturation): return Saturation, nil case string(Luminosity): return Luminosity, nil case string(ColorDodge): return ColorDodge, nil default: return "", errors.New("blend mode is unknown") } } ================================================ FILE: vendor/github.com/signintech/gopdf/transparency_xobject_group.go ================================================ package gopdf import ( "fmt" "io" ) type TransparencyXObjectGroup struct { Index int BBox [4]float64 Matrix [6]float64 ExtGStateIndexes []int XObjects []cacheContentImage getRoot func() *GoPdf pdfProtection *PDFProtection } type TransparencyXObjectGroupOptions struct { Protection *PDFProtection ExtGStateIndexes []int BBox [4]float64 XObjects []cacheContentImage } func GetCachedTransparencyXObjectGroup(opts TransparencyXObjectGroupOptions, gp *GoPdf) (TransparencyXObjectGroup, error) { group := TransparencyXObjectGroup{ BBox: opts.BBox, XObjects: opts.XObjects, pdfProtection: opts.Protection, ExtGStateIndexes: opts.ExtGStateIndexes, } group.Index = gp.addObj(group) group.init(func() *GoPdf { return gp }) return group, nil } func (s TransparencyXObjectGroup) init(funcGetRoot func() *GoPdf) { s.getRoot = funcGetRoot } func (s *TransparencyXObjectGroup) setProtection(p *PDFProtection) { s.pdfProtection = p } func (s TransparencyXObjectGroup) protection() *PDFProtection { return s.pdfProtection } func (s TransparencyXObjectGroup) getType() string { return "XObject" } func (s TransparencyXObjectGroup) write(w io.Writer, objId int) error { streamBuff := GetBuffer() defer PutBuffer(streamBuff) for _, XObject := range s.XObjects { if err := XObject.write(streamBuff, nil); err != nil { return err } } content := "<<\n" content += "\t/FormType 1\n" content += "\t/Subtype /Form\n" content += fmt.Sprintf("\t/Type /%s\n", s.getType()) content += fmt.Sprintf("\t/Matrix [1 0 0 1 0 0]\n") content += fmt.Sprintf("\t/BBox [%.3F %.3F %.3F %.3F]\n", s.BBox[0], s.BBox[1], s.BBox[2], s.BBox[3]) content += "\t/Group<>\n" content += fmt.Sprintf("\t/Length %d\n", len(streamBuff.Bytes())) content += ">>\n" content += "stream\n" if _, err := io.WriteString(w, content); err != nil { return err } if _, err := w.Write(streamBuff.Bytes()); err != nil { return err } if _, err := io.WriteString(w, "endstream\n"); err != nil { return err } return nil } ================================================ FILE: vendor/github.com/signintech/gopdf/ttf_option.go ================================================ package gopdf //TtfOption font option type TtfOption struct { UseKerning bool Style int // Regular|Bold|Italic OnGlyphNotFound func(r rune) } func defaultTtfFontOption() TtfOption { var defa TtfOption defa.UseKerning = false defa.Style = Regular return defa } ================================================ FILE: vendor/github.com/signintech/gopdf/unicode_map.go ================================================ package gopdf import ( "fmt" "io" ) //UnicodeMap unicode map type UnicodeMap struct { PtrToSubsetFontObj *SubsetFontObj //getRoot func() *GoPdf pdfProtection *PDFProtection } func (u *UnicodeMap) init(funcGetRoot func() *GoPdf) { //u.getRoot = funcGetRoot } func (u *UnicodeMap) setProtection(p *PDFProtection) { u.pdfProtection = p } func (u *UnicodeMap) protection() *PDFProtection { return u.pdfProtection } //SetPtrToSubsetFontObj set pointer to SubsetFontObj func (u *UnicodeMap) SetPtrToSubsetFontObj(ptr *SubsetFontObj) { u.PtrToSubsetFontObj = ptr } func (u *UnicodeMap) getType() string { return "Unicode" } func (u *UnicodeMap) write(w io.Writer, objID int) error { //stream //characterToGlyphIndex := u.PtrToSubsetFontObj.CharacterToGlyphIndex prefix := "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo << /Registry (Adobe)/Ordering (UCS)/Supplement 0>> def\n" + "/CMapName /Adobe-Identity-UCS def /CMapType 2 def\n" suffix := "endcmap CMapName currentdict /CMap defineresource pop end end" glyphIndexToCharacter := newMapGlyphIndexToCharacter() //make(map[int]rune) lowIndex := 65536 hiIndex := -1 keys := u.PtrToSubsetFontObj.CharacterToGlyphIndex.AllKeys() for _, k := range keys { v, _ := u.PtrToSubsetFontObj.CharacterToGlyphIndex.Val(k) index := int(v) if index < lowIndex { lowIndex = index } if index > hiIndex { hiIndex = index } //glyphIndexToCharacter[index] = k glyphIndexToCharacter.set(index, k) } buff := GetBuffer() defer PutBuffer(buff) buff.WriteString(prefix) buff.WriteString("1 begincodespacerange\n") fmt.Fprintf(buff, "<%04X><%04X>\n", lowIndex, hiIndex) buff.WriteString("endcodespacerange\n") fmt.Fprintf(buff, "%d beginbfrange\n", glyphIndexToCharacter.size()) indexs := glyphIndexToCharacter.allIndexs() for _, k := range indexs { v, _ := glyphIndexToCharacter.runeByIndex(k) fmt.Fprintf(buff, "<%04X><%04X><%04X>\n", k, k, v) } buff.WriteString("endbfrange\n") buff.WriteString(suffix) buff.WriteString("\n") io.WriteString(w, "<<\n") fmt.Fprintf(w, "/Length %d\n", buff.Len()) io.WriteString(w, ">>\n") io.WriteString(w, "stream\n") if u.protection() != nil { tmp, err := rc4Cip(u.protection().objectkey(objID), buff.Bytes()) if err != nil { return err } w.Write(tmp) //streambuff.WriteString("\n") } else { buff.WriteTo(w) } io.WriteString(w, "endstream\n") return nil } type mapGlyphIndexToCharacter struct { runes []rune indexs []int } func newMapGlyphIndexToCharacter() *mapGlyphIndexToCharacter { var m mapGlyphIndexToCharacter return &m } func (m *mapGlyphIndexToCharacter) set(index int, r rune) { m.runes = append(m.runes, r) m.indexs = append(m.indexs, index) } func (m *mapGlyphIndexToCharacter) size() int { return len(m.indexs) } func (m *mapGlyphIndexToCharacter) allIndexs() []int { return m.indexs } func (m *mapGlyphIndexToCharacter) runeByIndex(index int) (rune, bool) { var r rune ok := false for i, idx := range m.indexs { if idx == index { r = m.runes[i] ok = true break } } return r, ok } ================================================ FILE: vendor/modules.txt ================================================ # github.com/phpdave11/gofpdi v1.0.11 github.com/phpdave11/gofpdi # github.com/pkg/errors v0.8.1 github.com/pkg/errors # github.com/signintech/gopdf v0.9.20 ## explicit github.com/signintech/gopdf github.com/signintech/gopdf/fontmaker/core