From a324f2395e1474aaaab2aa2802621c1682808736 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Fri, 28 Aug 2026 21:05:30 +0200 Subject: [PATCH] Reach the JBIG2 decoder through the fleet's codec registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JBIG2 decoder was named here, in the module that draws PDF pages. It is also named in go-gfx/gfx/codec, which is where the fleet keeps its image decoders and which reads the JBIG2 FILE form. Two modules were choosing the same third-party decoder without either knowing about the other. codec.DecodeEmbeddedJBIG2 (go-gfx/gfx v0.14.0) takes the headerless form a PDF embeds, so this module asks for a decoded picture and stops holding an opinion about who decodes it. That matters more than usual for this decoder. Its resource limits are process-global rather than per-decode, so a library cannot raise them without changing them for everything else in the binary, and it publishes no tagged version — this module now depends on it only indirectly. The day it is swapped should be a change in one place, and now it is. No pixel moves: same decoder, same inversion, same packed rows. The pages this was measured on come out identical. 100% statement coverage, go vet and -race clean, nine cross-compile targets. --- go.mod | 7 +++++-- go.sum | 10 ++++++++-- jbig2.go | 30 +++++++++++------------------- jbig2_test.go | 8 ++++---- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index cc5c5de..a130ab1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-pdfkit/render go 1.26.4 require ( - github.com/go-gfx/gfx v0.12.0 + github.com/go-gfx/gfx v0.14.0 github.com/go-opentype/fonts v0.9.0 github.com/go-opentype/opentype v0.10.0 github.com/go-pdfkit/reader v0.6.0 @@ -16,6 +16,9 @@ require ( require ( github.com/ajroetker/go-highway v0.0.4 // indirect - github.com/dkrisman/gobig2 v0.0.0-20260513123937-51e39052fde6 + github.com/dkrisman/gobig2 v0.0.0-20260513123937-51e39052fde6 // indirect + github.com/sergeymakinen/go-bmp v1.0.0 // indirect + github.com/sergeymakinen/go-ico v1.0.0 // indirect + golang.org/x/image v0.45.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/go.sum b/go.sum index 8a46a32..d3e626e 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/ajroetker/go-jpeg2000 v0.0.2 h1:ni8brffZrci4Kacx3nM5d92ipmTDfak84KgHY github.com/ajroetker/go-jpeg2000 v0.0.2/go.mod h1:7ld88W47lZy0x8gRQesRGAonDPOpr6ev8rckjCAfbzE= github.com/dkrisman/gobig2 v0.0.0-20260513123937-51e39052fde6 h1:hHPgbpPdcaSXtpLb9NMEUJPxsZ0cxGtGHwmvKGI36NA= github.com/dkrisman/gobig2 v0.0.0-20260513123937-51e39052fde6/go.mod h1:2Ij0rpAVy1tZ+PQ9FM1jCpnVo/OOCMCD6rLtSp4JkEI= -github.com/go-gfx/gfx v0.12.0 h1:zBaYHahRwM6yYr86ifRelX132ZjhN5hZNr/uW9dpIzQ= -github.com/go-gfx/gfx v0.12.0/go.mod h1:DpRUcQrlLZH02CSB23J0iEk1F7mQH8w9WMF60xQBxDU= +github.com/go-gfx/gfx v0.14.0 h1:G6A0ZIirv9XjlxVFOl3HLscLRJOdhe9e1HqiCK5CHz0= +github.com/go-gfx/gfx v0.14.0/go.mod h1:8KcjwJmHW2ImXcjeMEIQ1bY+7Q2DNcMYoQCl270Isr8= github.com/go-opentype/fonts v0.9.0 h1:slB6OB3riLyUPrOxqXe0s6/AzdenF1TDvCN8N87hhQk= github.com/go-opentype/fonts v0.9.0/go.mod h1:C6yQL2apHItfEZ5hztpsHF0S5mlX/hklLlq/Z5fRG/g= github.com/go-opentype/opentype v0.10.0 h1:cZVMZ3RVkcijXmxlmpqyVkjlNc33aSB2ugKVWYvoLUg= @@ -14,5 +14,11 @@ github.com/go-pdfkit/pdffont v0.3.0 h1:G5DKcAmsZJ0e17QhSrcUaL7PKEXKjUx4P/iGMl7bA github.com/go-pdfkit/pdffont v0.3.0/go.mod h1:bfmNLna1l1CljNX/Utg55YzFylovfmI7sJnvgA3bzKI= github.com/go-pdfkit/reader v0.6.0 h1:KAabNOYUcTlZlNBTbG9bEhWP1NiZhjuhzUdavdTdfes= github.com/go-pdfkit/reader v0.6.0/go.mod h1:fQFOVfCMUui1AdvD4qhimdyvvNr9KvvJ1S7IuKZjyV8= +github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M= +github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY= +github.com/sergeymakinen/go-ico v1.0.0 h1:uL3khgvKkY6WfAetA+RqsguClBuu7HpvBB/nq/Jvr80= +github.com/sergeymakinen/go-ico v1.0.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk= +golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= +golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/jbig2.go b/jbig2.go index 73feeaf..5a955b0 100644 --- a/jbig2.go +++ b/jbig2.go @@ -6,10 +6,7 @@ package render import ( - "bytes" - "image" - - "github.com/dkrisman/gobig2" + "github.com/go-gfx/gfx/codec" "github.com/go-pdfkit/reader" ) @@ -31,8 +28,7 @@ func (r *renderer) decodeJBIG2(dict reader.Dict, data []byte, w, h int) []byte { if err != nil || img == nil { return nil } - b := img.Bounds() - if b.Dx() != w || b.Dy() != h { + if img.W != w || img.H != h { // The dictionary's size is the one the page's geometry was computed // from. A stream that decodes to another size is not this image. return nil @@ -44,8 +40,7 @@ func (r *renderer) decodeJBIG2(dict reader.Dict, data []byte, w, h int) []byte { } for y := 0; y < h; y++ { for x := 0; x < w; x++ { - gr, gg, gb, _ := img.At(b.Min.X+x, b.Min.Y+y).RGBA() - if (gr*299+gg*587+gb*114)/1000 < 0x8000 { + if img.Pix[(y*w+x)*4] < 128 { out[y*rowBytes+x/8] &^= 0x80 >> uint(x%8) } } @@ -90,14 +85,11 @@ func (r *renderer) globalsFrom(parms reader.Object) []byte { // jbig2Decode is a variable so a test can watch what happens when a decoder // refuses, which is the case that decides whether the image is drawn wrong or // not drawn. -var jbig2Decode = func(data, globals []byte) (image.Image, error) { - d, err := gobig2.NewDecoderEmbedded(bytes.NewReader(data), globals) - if err != nil { - return nil, err - } - img, err := d.Decode() - if err != nil { - return nil, err - } - return img, nil -} +// +// The decoder is reached through go-gfx/gfx/codec, which is where the fleet +// keeps its image decoders, rather than named here. It matters more than usual +// for this one: its resource limits are process-global rather than per-decode, +// so a library cannot raise them without changing them for everything else in +// the binary, and it publishes no tagged version. The day it is swapped should +// be a change in one place. +var jbig2Decode = codec.DecodeEmbeddedJBIG2 diff --git a/jbig2_test.go b/jbig2_test.go index d1a770c..e425804 100644 --- a/jbig2_test.go +++ b/jbig2_test.go @@ -2,9 +2,9 @@ package render import ( "errors" - "image" "testing" + "github.com/go-gfx/gfx/raster" "github.com/go-pdfkit/reader" ) @@ -160,7 +160,7 @@ func TestAJBIG2DecoderThatRefusesTakesTheImageWithIt(t *testing.T) { // answer; drawing the rectangle anyway is not. restore := jbig2Decode defer func() { jbig2Decode = restore }() - jbig2Decode = func(data, globals []byte) (image.Image, error) { + jbig2Decode = func(data, globals []byte) (*raster.Image, error) { return nil, errors.New("resource budget exceeded") } d := jbig2Page(t, jbig2Ink, 16, 8, nil) @@ -176,7 +176,7 @@ func TestAJBIG2DecoderThatRefusesTakesTheImageWithIt(t *testing.T) { func TestAJBIG2DecoderThatReturnsNothingTakesTheImageWithIt(t *testing.T) { restore := jbig2Decode defer func() { jbig2Decode = restore }() - jbig2Decode = func(data, globals []byte) (image.Image, error) { return nil, nil } + jbig2Decode = func(data, globals []byte) (*raster.Image, error) { return nil, nil } d := jbig2Page(t, jbig2Ink, 16, 8, nil) img, err := Page(d, 1, Options{Scale: 1}) if err != nil { @@ -195,7 +195,7 @@ func seenGlobals(t *testing.T, parms reader.Object) []byte { defer func() { jbig2Decode = restore }() var got []byte var called bool - jbig2Decode = func(data, globals []byte) (image.Image, error) { + jbig2Decode = func(data, globals []byte) (*raster.Image, error) { got, called = globals, true return restore(data, globals) }