package wpdf import ( "bytes" "github.com/jung-kurt/gofpdf" "gogs.mikescher.com/BlackForestBytes/goext/dataext" "gogs.mikescher.com/BlackForestBytes/goext/imageext" "gogs.mikescher.com/BlackForestBytes/goext/langext" "image" "image/color" "image/draw" "net/http" ) type PDFImageRef struct { Info *gofpdf.ImageInfoType Name string Bin []byte Image *image.Image Mime string } type PDFImageRegisterOpt struct { imageType *string readDpi *bool allowNegativePosition *bool name *string } func NewPDFImageRegisterOpt() *PDFImageRegisterOpt { return &PDFImageRegisterOpt{} } func (opt *PDFImageRegisterOpt) ImageType(v string) *PDFImageRegisterOpt { opt.imageType = &v return opt } func (opt *PDFImageRegisterOpt) ReadDpi(v bool) *PDFImageRegisterOpt { opt.readDpi = &v return opt } func (opt *PDFImageRegisterOpt) AllowNegativePosition(v bool) *PDFImageRegisterOpt { opt.allowNegativePosition = &v return opt } func (opt *PDFImageRegisterOpt) Name(v string) *PDFImageRegisterOpt { opt.name = &v return opt } func (b *WPDFBuilder) RegisterImage(bin []byte, opts ...*PDFImageRegisterOpt) *PDFImageRef { imgName := "fpdf_img_" + langext.MustRawHexUUID() imageType := "" readDpi := false allowNegativePosition := false mime := "application/octet-stream" for _, opt := range opts { imageType = langext.Coalesce(opt.imageType, imageType) readDpi = langext.Coalesce(opt.readDpi, readDpi) allowNegativePosition = langext.Coalesce(opt.allowNegativePosition, allowNegativePosition) imgName = langext.Coalesce(opt.name, imgName) } if imageType == "" { ct := "" if len(bin) > 512 { ct = http.DetectContentType(bin[:512]) } else { ct = http.DetectContentType(bin) } switch ct { case "image/jpg": imageType = "JPG" mime = ct case "image/jpeg": imageType = "JPEG" mime = ct case "image/png": imageType = "PNG" mime = ct case "image/gif": imageType = "GIF" mime = ct } } else { switch imageType { case "JPG": case "JPEG": mime = "image/jpeg" case "PNG": mime = "image/png" case "GIF": mime = "image/gif" } } options := gofpdf.ImageOptions{ ImageType: imageType, ReadDpi: readDpi, AllowNegativePosition: allowNegativePosition, } info := b.b.RegisterImageOptionsReader(imgName, options, bytes.NewReader(bin)) return &PDFImageRef{ Name: imgName, Info: info, Bin: bin, Image: nil, Mime: mime, } } type PDFImageOpt struct { x *float64 y *float64 width *float64 height *float64 flow *bool link *int linkStr *string imageType *string readDpi *bool allowNegativePosition *bool imageFit *imageext.ImageFit fillColor *color.Color compression *imageext.ImageCompresson reEncodePixelPerMM *float64 crop *imageext.ImageCrop alphaOverride *dataext.Tuple[float64, PDFBlendMode] debug *bool } func NewPDFImageOpt() *PDFImageOpt { return &PDFImageOpt{} } func (opt *PDFImageOpt) X(v float64) *PDFImageOpt { opt.x = &v return opt } func (opt *PDFImageOpt) Y(v float64) *PDFImageOpt { opt.y = &v return opt } func (opt *PDFImageOpt) Width(v float64) *PDFImageOpt { opt.width = &v return opt } func (opt *PDFImageOpt) Height(v float64) *PDFImageOpt { opt.height = &v return opt } func (opt *PDFImageOpt) Debug(v bool) *PDFImageOpt { opt.debug = &v return opt } func (opt *PDFImageOpt) Flow(v bool) *PDFImageOpt { opt.flow = &v return opt } func (opt *PDFImageOpt) Link(v int) *PDFImageOpt { opt.link = &v return opt } func (opt *PDFImageOpt) LinkStr(v string) *PDFImageOpt { opt.linkStr = &v return opt } func (opt *PDFImageOpt) ImageType(v string) *PDFImageOpt { opt.imageType = &v return opt } func (opt *PDFImageOpt) ReadDpi(v bool) *PDFImageOpt { opt.readDpi = &v return opt } func (opt *PDFImageOpt) AllowNegativePosition(v bool) *PDFImageOpt { opt.allowNegativePosition = &v return opt } func (opt *PDFImageOpt) ImageFit(v imageext.ImageFit) *PDFImageOpt { opt.imageFit = &v return opt } func (opt *PDFImageOpt) FillColor(v color.Color) *PDFImageOpt { opt.fillColor = &v return opt } func (opt *PDFImageOpt) Compression(v imageext.ImageCompresson) *PDFImageOpt { opt.compression = &v return opt } func (opt *PDFImageOpt) ReEncodePixelPerMM(v float64) *PDFImageOpt { opt.reEncodePixelPerMM = &v return opt } func (opt *PDFImageOpt) Crop(cropX float64, cropY float64, cropWidth float64, cropHeight float64) *PDFImageOpt { opt.crop = &imageext.ImageCrop{ CropX: cropX, CropY: cropY, CropWidth: cropWidth, CropHeight: cropHeight, } return opt } func (opt *PDFImageOpt) Alpha(alpha float64, blendMode PDFBlendMode) *PDFImageOpt { opt.alphaOverride = &dataext.Tuple[float64, PDFBlendMode]{V1: alpha, V2: blendMode} return opt } func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) { var err error x := b.GetX() y := b.GetY() w := img.Info.Width() h := img.Info.Height() flow := true link := 0 linkStr := "" imageType := "" readDpi := false allowNegativePosition := false reEncodePixelPerMM := 15.0 var imageFit *imageext.ImageFit = nil var fillColor color.Color = color.Transparent compression := imageext.CompressionPNGSpeed debug := b.debug var crop *imageext.ImageCrop = nil var alphaOverride *dataext.Tuple[float64, PDFBlendMode] for _, opt := range opts { x = langext.Coalesce(opt.x, x) y = langext.Coalesce(opt.y, y) w = langext.Coalesce(opt.width, w) h = langext.Coalesce(opt.height, h) flow = langext.Coalesce(opt.flow, flow) link = langext.Coalesce(opt.link, link) linkStr = langext.Coalesce(opt.linkStr, linkStr) imageType = langext.Coalesce(opt.imageType, imageType) readDpi = langext.Coalesce(opt.readDpi, readDpi) allowNegativePosition = langext.Coalesce(opt.allowNegativePosition, allowNegativePosition) imageFit = langext.CoalesceOpt(opt.imageFit, imageFit) fillColor = langext.Coalesce(opt.fillColor, fillColor) compression = langext.Coalesce(opt.compression, compression) reEncodePixelPerMM = langext.Coalesce(opt.reEncodePixelPerMM, reEncodePixelPerMM) crop = langext.CoalesceOpt(opt.crop, crop) debug = langext.Coalesce(opt.debug, debug) alphaOverride = langext.CoalesceOpt(opt.alphaOverride, alphaOverride) } if flow { y = b.GetY() } regName := img.Name var subImageBounds *imageext.PercentageRectangle = nil if imageFit != nil || fillColor != nil || crop != nil { var dataimg image.Image if img.Image != nil { dataimg = *img.Image } else { dataimg, err = imageext.VerifyAndDecodeImage(bytes.NewReader(img.Bin), img.Mime) if err != nil { b.b.SetError(err) return } } if crop != nil { dataimg, err = imageext.CropImage(dataimg, crop.CropX, crop.CropY, crop.CropWidth, crop.CropHeight) if err != nil { b.b.SetError(err) return } } if imageFit != nil { pdfPixelPerMillimeter := 15.0 pxw := w * pdfPixelPerMillimeter pxh := h * pdfPixelPerMillimeter var dataImgRect imageext.PercentageRectangle dataimg, dataImgRect, err = imageext.ObjectFitImage(dataimg, pxw, pxh, *imageFit, fillColor) if err != nil { b.b.SetError(err) return } subImageBounds = &dataImgRect } if dataimg.ColorModel() != color.RGBAModel && dataimg.ColorModel() != color.NRGBAModel { // the image cannto be 16bpp or similar - otherwise fpdf errors out dataImgRGBA := image.NewNRGBA(image.Rect(0, 0, dataimg.Bounds().Dx(), dataimg.Bounds().Dy())) draw.Draw(dataImgRGBA, dataImgRGBA.Bounds(), dataimg, dataimg.Bounds().Min, draw.Src) dataimg = dataImgRGBA } bfr, imgMime, err := imageext.EncodeImage(dataimg, compression) if err != nil { b.b.SetError(err) return } regName = regName + "_" + langext.MustRawHexUUID() switch imgMime { case "image/jpeg": imageType = "JPEG" case "image/png": imageType = "PNG" case "image/gif": imageType = "GIF" } b.b.RegisterImageOptionsReader(regName, gofpdf.ImageOptions{ImageType: imageType}, &bfr) } if alphaOverride != nil { oldA, oldBMS := b.b.GetAlpha() b.b.SetAlpha(alphaOverride.V1, string(alphaOverride.V2)) defer func() { b.b.SetAlpha(oldA, oldBMS) }() } fpdfOpt := gofpdf.ImageOptions{ ImageType: imageType, ReadDpi: readDpi, AllowNegativePosition: allowNegativePosition, } b.b.ImageOptions(regName, x, y, w, h, flow, fpdfOpt, link, linkStr) if debug { b.Rect(w, h, RectOutline, NewPDFRectOpt().X(x).Y(y).LineWidth(0.25).DrawColor(255, 0, 0)) if subImageBounds != nil { r := subImageBounds.Of(imageext.Rectangle{X: x, Y: y, W: w, H: h}) b.Rect(r.W, r.H, RectOutline, NewPDFRectOpt().X(r.X).Y(r.Y).LineWidth(0.25).DrawColor(255, 0, 0)) b.Rect(r.W, r.H, RectFill, NewPDFRectOpt().X(r.X).Y(r.Y).FillColor(255, 0, 0).Alpha(0.2, BlendNormal)) b.Line(r.X, r.Y, r.X+r.W, r.Y+r.H, NewPDFLineOpt().LineWidth(0.25).DrawColor(255, 0, 0)) b.Line(r.X+r.W, r.Y, r.X, r.Y+r.H, NewPDFLineOpt().LineWidth(0.25).DrawColor(255, 0, 0)) } } }