diff --git a/go.mod b/go.mod index 51d3553..b94c43a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6 github.com/xiaoqidun/jbig2 v1.1.1 - github.com/xiaoqidun/pdfgo v0.0.0-20261004122813-dd48c1539fca + github.com/xiaoqidun/pdfgo v0.0.0-20261004151751-bff53238d953 golang.org/x/crypto v0.57.0 golang.org/x/image v0.46.0 golang.org/x/text v0.42.0 diff --git a/go.sum b/go.sum index 40947f8..cd292d1 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI= github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ= -github.com/xiaoqidun/pdfgo v0.0.0-20261004122813-dd48c1539fca h1:lpsew7G6nhHcEm3Pzg1VKObMqPYZv4anQDMVRoVnelg= -github.com/xiaoqidun/pdfgo v0.0.0-20261004122813-dd48c1539fca/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= +github.com/xiaoqidun/pdfgo v0.0.0-20261004151751-bff53238d953 h1:4fz7oPfhMZr8N2fWjv7Gn6+L2TInWGarQm3UGaqV+54= +github.com/xiaoqidun/pdfgo v0.0.0-20261004151751-bff53238d953/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= diff --git a/ofdgo_pdf_annot.go b/ofdgo_pdf_annot.go index 996f197..517d72c 100644 --- a/ofdgo_pdf_annot.go +++ b/ofdgo_pdf_annot.go @@ -48,18 +48,11 @@ func (p *pdfImporter) appearanceAnnotation(ctx context.Context, page *pdfgo.Page annotation.Dictionary = maps.Clone(annotation.Dictionary) annotation.Dictionary["Contents"] = pdfgo.String("\xef\xbb\xbf" + remark) } - flags := int64(0) - if value := annotation.Dictionary["F"]; value != nil { - resolved, err := p.reader.Resolve(value) - if err != nil { - return err - } - number, ok := resolved.(pdfgo.Integer) - if !ok { - return fmt.Errorf("invalid PDF annotation flags") - } - flags = int64(number) + flags, err := p.reader.ReadAnnotationFlags(annotation) + if err != nil { + return err } + invisible := flags&1 != 0 && !annotation.IsStandard() if flags&256 != 0 { return &pdfgo.UnsupportedError{Feature: "annotation ToggleNoView flag"} } @@ -101,7 +94,7 @@ func (p *pdfImporter) appearanceAnnotation(ctx context.Context, page *pdfgo.Page if err := stamp.compositeObjects(nodes); err != nil { return err } - if flags&(2|32) == 0 { + if flags&(2|32) == 0 && !invisible { p.compositeNodes = stamp.compositeNodes p.transferBackdrop = stamp.transferBackdrop } @@ -142,7 +135,7 @@ func (p *pdfImporter) appearanceAnnotation(ctx context.Context, page *pdfgo.Page region.Fill, region.Stroke, region.Actions = &no, &no, actions converted.Appearance.Objects = append(converted.Appearance.Objects, GraphicObject{Type: "PathObject", PathObject: region}) } - if flags&(2|32) != 0 { + if flags&(2|32) != 0 || invisible { visible := false converted.Visible = &visible } @@ -194,6 +187,9 @@ func pdfAnnotationType(subtype pdfgo.Name) string { case "Text", "FreeText", "Line", "Square", "Circle", "Polygon", "PolyLine", "Caret", "Ink", "FileAttachment", "Sound", "Movie", "Screen", "Link", "Popup", "Widget", "PrinterMark", "TrapNet", "3D", "RichMedia", "Redact": return "Path" } + if !(pdfgo.Annotation{Subtype: subtype}).IsStandard() { + return "Path" + } return "" } diff --git a/ofdgo_pdf_halftone.go b/ofdgo_pdf_halftone.go index 6831798..ead87de 100644 --- a/ofdgo_pdf_halftone.go +++ b/ofdgo_pdf_halftone.go @@ -21,8 +21,12 @@ import "github.com/xiaoqidun/pdfgo" // 返回: error 严格模式无法保留设备网屏 func (p *pdfImporter) halftone(style pdfgo.Style) error { h := style.Halftone + message := "PDF continuous-tone appearance retained; device halftone not transferred to OFD" if h == nil || h.Type == 0 { - return nil + if style.HalftoneOrigin == nil { + return nil + } + message = "PDF continuous-tone appearance retained; device halftone origin not transferred to OFD" } if p.warning == nil { return &pdfgo.UnsupportedError{Feature: "device halftone conversion"} @@ -32,7 +36,7 @@ func (p *pdfImporter) halftone(style pdfgo.Style) error { } if !p.halftoneWarnings[h] { p.halftoneWarnings[h] = true - p.warning(pdfgo.Diagnostic{Message: "PDF continuous-tone appearance retained; device halftone not transferred to OFD"}) + p.warning(pdfgo.Diagnostic{Message: message}) } return nil } diff --git a/ofdgo_renderer_output_canvas.go b/ofdgo_renderer_output_canvas.go index b82b01d..cb0ae5f 100644 --- a/ofdgo_renderer_output_canvas.go +++ b/ofdgo_renderer_output_canvas.go @@ -128,6 +128,11 @@ func (CanvasBackend) RenderPDF(r *Renderer, pages []RenderDocumentPage, writer i start := buf.Len() p := pdf.New(buf, pages[0].Box.W, pages[0].Box.H, nil) renderer := &pdfRenderer{PDF: p, glyphPaths: make(map[*canvas.Path]*canvas.Path), images: make([][]image.Image, 1)} + defer func() { + if renderer.imageError != nil { + r.canvasState().images = renderCache[*EncodedImage, image.Image]{limit: imageCacheLimit} + } + }() var info DocInfo if docInfo, err := r.Reader.DocInfo(); err == nil { info = *docInfo @@ -143,6 +148,10 @@ func (CanvasBackend) RenderPDF(r *Renderer, pages []RenderDocumentPage, writer i buf.Truncate(start) return fmt.Errorf("failed to render page %d: %w", i+1, err) } + if renderer.imageError != nil { + buf.Truncate(start) + return fmt.Errorf("failed to render page %d: %w", i+1, renderer.imageError) + } pages[i].Content = nil if progress != nil { if err := progress(i+1, len(pages)); err != nil { diff --git a/ofdgo_renderer_pdf_canvas.go b/ofdgo_renderer_pdf_canvas.go index 3080059..9d09ac1 100644 --- a/ofdgo_renderer_pdf_canvas.go +++ b/ofdgo_renderer_pdf_canvas.go @@ -30,6 +30,7 @@ type pdfRenderer struct { glyphPaths map[*canvas.Path]*canvas.Path images [][]image.Image exactImages bool + imageError error } // pdfNavigation PDF导航信息 @@ -59,9 +60,23 @@ type pdfOutline struct { Y float64 } -// RenderImage 在图像局部保存状态前同步不透明画笔,避免恢复后的透明度与后端缓存不一致 +// RenderImage 校验惰性图片并同步不透明画笔,保留解码错误及后端透明度状态 // 入参: img 图像, matrix 图像变换 func (r *pdfRenderer) RenderImage(img image.Image, matrix canvas.Matrix) { + if r.imageError != nil { + return + } + if source, ok := img.(interface{ Image() (image.Image, error) }); ok { + pixels, err := source.Image() + if err != nil { + r.imageError = err + return + } + if pixels == nil || pixels.Bounds() != img.Bounds() { + r.imageError = fmt.Errorf("PDF output image pixel bounds differ") + return + } + } if len(r.images) != 0 { var original image.Image opaque := false diff --git a/ofdgo_renderer_pdf_images.go b/ofdgo_renderer_pdf_images.go index f78aa6a..528decc 100644 --- a/ofdgo_renderer_pdf_images.go +++ b/ofdgo_renderer_pdf_images.go @@ -23,10 +23,13 @@ import ( "github.com/xiaoqidun/pdfgo" ) -// preserveImages 按实际绘制顺序恢复颜色及透明度精度,不依赖后端资源命名或对象编号 +// preserveImages 按绘制顺序恢复颜色及透明度精度,二值透明采用标准Matte预混合 // 入参: ctx 取消上下文, data 后端PDF数据 // 返回: []byte 保留图像精度的PDF数据, error 资源或写入错误 func (r *pdfRenderer) preserveImages(ctx context.Context, data []byte) ([]byte, error) { + if r.imageError != nil { + return nil, r.imageError + } if !r.exactImages { return data, nil } @@ -98,8 +101,21 @@ func (r *pdfRenderer) preserveImages(ctx context.Context, data []byte) ([]byte, if pages != len(r.images) { return nil, fmt.Errorf("PDF output page missing") } + for ref, original := range images { + if err := ctx.Err(); err != nil { + return nil, err + } + pixels, err := imagePixelData(original) + if err != nil { + return nil, err + } + if pixels == nil || pixels.Bounds() != original.Bounds() { + return nil, fmt.Errorf("PDF output image pixel bounds differ") + } + images[ref] = pixels + } var result bytes.Buffer - if _, err := reader.ReplaceImagesTo(ctx, &result, images); err != nil { + if _, err := reader.ReplaceImagesTo(ctx, &result, images, pdfgo.ImageWriteOptions{PreblendBinary: true}); err != nil { return nil, err } return result.Bytes(), nil