// Copyright ©2023 The go-pdf Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. /* * Copyright (c) 2013-2016 Kurt Jung (Gmail: kurt.w.jung) * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package fpdf import ( "fmt" "strings" ) func (f *Fpdf) pngColorSpace(ct byte) (colspace string, colorVal int) { colorVal = 1 switch ct { case 0, 4: colspace = "DeviceGray" case 2, 6: colspace = "DeviceRGB" colorVal = 3 case 3: colspace = "Indexed" default: f.err = fmt.Errorf("unknown color type in PNG buffer: %d", ct) } return } func (f *Fpdf) parsepngstream(r *rbuffer, readdpi bool) (info *ImageInfoType) { info = f.newImageInfo() // Check signature if string(r.Next(8)) != "\x89PNG\x0d\x0a\x1a\x0a" { f.err = fmt.Errorf("not a PNG buffer") return } // Read header chunk _ = r.Next(4) if string(r.Next(4)) != "IHDR" { f.err = fmt.Errorf("incorrect PNG buffer") return } w := r.i32() h := r.i32() bpc := r.u8() if bpc > 8 { if f.pdfVersion < pdfVers1_5 { f.pdfVersion = pdfVers1_5 } } ct := r.u8() var colspace string var colorVal int colspace, colorVal = f.pngColorSpace(ct) if f.err != nil { return } if r.u8() != 0 { f.err = fmt.Errorf("'unknown compression method in PNG buffer") return } if r.u8() != 0 { f.err = fmt.Errorf("'unknown filter method in PNG buffer") return } if r.u8() != 0 { f.err = fmt.Errorf("interlacing not supported in PNG buffer") return } _ = r.Next(4) dp := sprintf("/Predictor 15 /Colors %d /BitsPerComponent %d /Columns %d", colorVal, bpc, w) // Scan chunks looking for palette, transparency and image data var ( pal []byte trns []int npix = w * h data = make([]byte, 0, npix/8) loop = true ) for loop { n := int(r.i32()) // dbg("Loop [%d]", n) switch string(r.Next(4)) { case "PLTE": // dbg("PLTE") // Read palette pal = r.Next(n) _ = r.Next(4) case "tRNS": // dbg("tRNS") // Read transparency info t := r.Next(n) switch ct { case 0: trns = []int{int(t[1])} // ord(substr($t,1,1))); case 2: trns = []int{int(t[1]), int(t[3]), int(t[5])} // array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1))); default: pos := strings.Index(string(t), "\x00") if pos >= 0 { trns = []int{pos} // array($pos); } } _ = r.Next(4) case "IDAT": // dbg("IDAT") // Read image data block data = append(data, r.Next(n)...) _ = r.Next(4) case "IEND": // dbg("IEND") loop = false case "pHYs": // dbg("pHYs") // png files theoretically support different x/y dpi // but we ignore files like this // but if they're the same then we can stamp our info // object with it x := int(r.i32()) y := int(r.i32()) units := r.u8() // fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n", // x, y, int(units), readdpi) // only modify the info block if the user wants us to if x == y && readdpi { switch units { // if units is 1 then measurement is px/meter case 1: info.dpi = float64(x) / 39.3701 // inches per meter default: info.dpi = float64(x) } } _ = r.Next(4) default: // dbg("default") _ = r.Next(n + 4) } if loop { loop = n > 0 } } if colspace == "Indexed" && len(pal) == 0 { f.err = fmt.Errorf("missing palette in PNG buffer") } info.w = float64(w) info.h = float64(h) info.cs = colspace info.bpc = int(bpc) info.f = "FlateDecode" info.dp = dp info.pal = pal info.trns = trns // dbg("ct [%d]", ct) if ct >= 4 { // Separate alpha and color channels mem, err := xmem.uncompress(data) if err != nil { f.err = err return } data = mem.bytes() var ( color wbuffer alpha wbuffer ) if ct == 4 { // Gray image width := int(w) height := int(h) length := 2 * width sz := height * (width + 1) color.p = data[:sz] // reuse decompressed data buffer. alpha.p = make([]byte, sz) var pos, elPos int for i := 0; i < height; i++ { pos = (1 + length) * i color.u8(data[pos]) alpha.u8(data[pos]) elPos = pos + 1 for k := 0; k < width; k++ { color.u8(data[elPos]) alpha.u8(data[elPos+1]) elPos += 2 } } } else { // RGB image width := int(w) height := int(h) length := 4 * width sz := width * height color.p = data[:sz*3+height] // reuse decompressed data buffer. alpha.p = make([]byte, sz+height) var pos, elPos int for i := 0; i < height; i++ { pos = (1 + length) * i color.u8(data[pos]) alpha.u8(data[pos]) elPos = pos + 1 for k := 0; k < width; k++ { tmp := data[elPos : elPos+4] color.u8(tmp[0]) color.u8(tmp[1]) color.u8(tmp[2]) alpha.u8(tmp[3]) elPos += 4 } } } xc := xmem.compress(color.bytes()) data = xc.copy() xc.release() // release uncompressed data buffer, after the color buffer // has been compressed. mem.release() xa := xmem.compress(alpha.bytes()) info.smask = xa.copy() xa.release() if f.pdfVersion < pdfVers1_4 { f.pdfVersion = pdfVers1_4 } } info.data = data return }