package png

import (
	
	
	
	
	
	

	
)

// SCALE is the static raster device scale used by PNG, PDF, and PPTX export.
const SCALE = 2.

var pngSignature = [...]byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}

const pngIENDChunk = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"

const (
	pngChunkOverhead = 12
	pngChunkIHDR     = uint32(0x49484452) // IHDR
	pngChunkEXIF     = uint32(0x65584966) // eXIf
	pngChunkIEND     = uint32(0x49454e44) // IEND
	d2ExifIFDSize    = 38
)

// WriteExif writes D2's complete eXIf chunk. A streaming PNG encoder calls it
// after IHDR, before emitting image data, without retaining the encoded image.
func ( io.Writer) error {
	,  := d2ExifDataSize(version.Version)
	if  != nil {
		return 
	}
	 := make([]byte, pngChunkOverhead+)
	writeD2ExifChunk(, 0, version.Version, )
	,  := .Write()
	if  == nil &&  != len() {
		return io.ErrShortWrite
	}
	return 
}

// AddExif returns png with D2's EXIF metadata inserted after IHDR. An existing
// EXIF chunk is replaced at its original position. The input is validated
// without copying chunk payloads so peak memory remains one input and one
// output buffer.
func ( []byte) ([]byte, error) {
	return addExif(, false)
}

// AddExifInPlace returns png with D2's EXIF metadata inserted after IHDR. It
// reuses the input's backing storage when it has sufficient spare capacity and
// otherwise allocates an exact-sized result. On success, callers must consider
// the input and every alias of its backing storage consumed. Invalid input is
// never modified.
func ( []byte) ([]byte, error) {
	return addExif(, true)
}

// AddExifToEncoderOutputInPlace inserts D2's EXIF metadata into a PNG emitted
// directly by an encoder. Unlike AddExifInPlace, it does not rescan and
// checksum every generated image-data chunk. It still verifies the fixed PNG
// envelope before modifying the input. Callers must not use it for untrusted
// or externally supplied PNG data.
func ( []byte) ([]byte, error) {
	const  = len(pngSignature) + pngChunkOverhead + 13
	if len() < +len(pngIENDChunk) || !bytes.Equal([:len(pngSignature)], pngSignature[:]) {
		return nil, fmt.Errorf("not PNG encoder output")
	}
	if binary.BigEndian.Uint32([len(pngSignature):len(pngSignature)+4]) != 13 ||
		binary.BigEndian.Uint32([len(pngSignature)+4:len(pngSignature)+8]) != pngChunkIHDR {
		return nil, fmt.Errorf("PNG encoder output has an invalid IHDR chunk")
	}
	 := binary.BigEndian.Uint32([-4 : ])
	if  := crc32.ChecksumIEEE([len(pngSignature)+4 : -4]);  !=  {
		return nil, fmt.Errorf("PNG encoder output has an invalid IHDR CRC")
	}
	if !bytes.Equal([len()-len(pngIENDChunk):], []byte(pngIENDChunk)) {
		return nil, fmt.Errorf("PNG encoder output has an invalid IEND chunk")
	}
	return addExifAt(, , , true)
}

func addExif( []byte,  bool) ([]byte, error) {
	, ,  := exifChunkLocation()
	if  != nil {
		return nil, 
	}
	return addExifAt(, , , )
}

func addExifAt( []byte, ,  int,  bool) ([]byte, error) {
	 := version.Version
	,  := d2ExifDataSize()
	if  != nil {
		return nil, 
	}
	 := pngChunkOverhead + 
	 :=  - 
	if  > math.MaxInt-len()+ {
		return nil, fmt.Errorf("PNG with EXIF exceeds the platform integer domain")
	}
	 := len() +  - 
	if  &&  <= cap() {
		 := len()
		 := [:]
		copy([+:], [:])
		writeD2ExifChunk(, , , )
		return , nil
	}
	 := make([]byte, )
	 := copy(, [:])
	 = writeD2ExifChunk(, , , )
	 += copy([:], [:])
	if  != len() {
		return nil, fmt.Errorf("PNG EXIF output size %d differs from planned size %d", , len())
	}
	return , nil
}

func d2ExifDataSize( string) (int, error) {
	if len() < 4 {
		return d2ExifIFDSize, nil
	}
	if len() > math.MaxInt32-d2ExifIFDSize-1 {
		return 0, fmt.Errorf("PNG EXIF model is too large: %d bytes", len())
	}
	return d2ExifIFDSize + len() + 1, nil
}

// exifChunkLocation returns the range to replace. Without an existing eXIf
// chunk it returns the empty range immediately after IHDR.
func exifChunkLocation( []byte) (,  int,  error) {
	if len() < len(pngSignature) || !bytes.Equal([:len(pngSignature)], pngSignature[:]) {
		return 0, 0, fmt.Errorf("not PNG data")
	}
	 := len(pngSignature)
	 := 0
	,  := 0, 0
	 := false
	for  := 0;  < len(); ++ {
		if len()- < pngChunkOverhead {
			return 0, 0, fmt.Errorf("truncated PNG chunk at byte %d", )
		}
		 := uint64(binary.BigEndian.Uint32([ : +4]))
		if  > math.MaxInt32 {
			return 0, 0, fmt.Errorf("PNG chunk at byte %d exceeds the maximum length: %d", , )
		}
		if  > uint64(len()--pngChunkOverhead) {
			return 0, 0, fmt.Errorf("truncated PNG chunk data at byte %d", )
		}
		 :=  + pngChunkOverhead + int()
		 := binary.BigEndian.Uint32([+4 : +8])
		if  == 0 {
			if  != pngChunkIHDR {
				return 0, 0, fmt.Errorf("first PNG chunk is %q, want IHDR", [+4:+8])
			}
			if  != 13 {
				return 0, 0, fmt.Errorf("PNG IHDR length is %d, want 13", )
			}
			 = 
		}
		 := binary.BigEndian.Uint32([-4 : ])
		 := crc32.ChecksumIEEE([+4 : -4])
		if  !=  {
			return 0, 0, fmt.Errorf("PNG chunk %q at byte %d has invalid CRC", [+4:+8], )
		}
		if  == pngChunkEXIF {
			if  != 0 {
				return 0, 0, fmt.Errorf("PNG has multiple eXIf chunks")
			}
			,  = , 
		}
		if  == pngChunkIEND {
			if  != 0 {
				return 0, 0, fmt.Errorf("PNG IEND length is %d, want 0", )
			}
			if  != len() {
				return 0, 0, fmt.Errorf("PNG has %d trailing bytes after IEND", len()-)
			}
			 = true
		}
		 = 
	}
	if  == 0 {
		return 0, 0, fmt.Errorf("PNG has no IHDR chunk")
	}
	if ! {
		return 0, 0, fmt.Errorf("PNG has no IEND chunk")
	}
	if  != 0 {
		return , , nil
	}
	return , , nil
}

func writeD2ExifChunk( []byte,  int,  string,  int) int {
	binary.BigEndian.PutUint32([:+4], uint32())
	binary.BigEndian.PutUint32([+4:+8], pngChunkEXIF)
	 := [+8 : +8+]

	// A PNG eXIf payload starts directly with the TIFF header, without the
	// "Exif\x00\x00" identifier used by JPEG APP1 segments.
	copy([0:4], "MM\x00\x2a")
	binary.BigEndian.PutUint32([4:8], 8)
	binary.BigEndian.PutUint16([8:10], 2)
	binary.BigEndian.PutUint16([10:12], 0x010f) // Make
	binary.BigEndian.PutUint16([12:14], 2)      // ASCII
	binary.BigEndian.PutUint32([14:18], 3)
	copy([18:22], "D2\x00\x00")
	binary.BigEndian.PutUint16([22:24], 0x0110) // Model
	binary.BigEndian.PutUint16([24:26], 2)      // ASCII
	binary.BigEndian.PutUint32([26:30], uint32(len()+1))
	if len() < 4 {
		clear([30:34])
		copy([30:34], )
	} else {
		binary.BigEndian.PutUint32([30:34], d2ExifIFDSize)
		copy([d2ExifIFDSize:len()-1], )
		[len()-1] = 0
	}
	binary.BigEndian.PutUint32([34:38], 0)

	 :=  + pngChunkOverhead + 
	 := crc32.ChecksumIEEE([+4 : -4])
	binary.BigEndian.PutUint32([-4:], )
	return 
}