From 0201fce1a075050720edf0ee1075dfb803476732 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sat, 29 Aug 2026 20:17:20 +0200 Subject: [PATCH] deps: the ink layer of a scanned page, and a check that can see it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workbench was eight releases behind on the renderer, so none of the work on scanned pages reached anybody. render v0.19.0 brings the JBIG2 mask that gives a scanned page's ink its shape, the JPEG 2000 background under it, and optional content; gfx v0.15.0 brings the JBIG2 decoder those go through. Handed a real scanned document, the browser check then said the page never arrived. It had. It counted a pixel as dark when its three channels sum to under 240 — an average under 80, which is nearly black. That is the right question for telling a lit control from an unlit one, which is what it was written for. It is the wrong question for a page: scanned text scaled down to fit a window is GREY. The same page counts 1805 pixels dark by luminance and 9 by that sum, and the empty workbench's own strip and borders have 453, which the page then covers. So a correctly drawn scanned document read as never having arrived. The canvas now also counts ink — dark the way text is dark — and a document is seen to arrive when the canvas changed, more of it is drawn on than before, and there is more ink than the workbench's own furniture. Nearly black is still counted, and still used for what it is good at. Checked both ways: the synthetic document still passes, and a 1556x2439 scanned medical page now opens and saves back out at 3 106 224 bytes. 100% statement coverage, go vet and -race clean, the wasm build, and the browser check. --- browsercheck/check.go | 29 ++++++++++++++++++++++++----- go.mod | 11 ++++++++--- go.sum | 22 ++++++++++++++++------ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/browsercheck/check.go b/browsercheck/check.go index 77860d9..b002bbd 100644 --- a/browsercheck/check.go +++ b/browsercheck/check.go @@ -100,13 +100,21 @@ func check(ctx context.Context, c *conn, page, sample, shot, dl string) error { if err != nil { return false, err } - return v.Hash != empty.Hash && v.Dark > empty.Dark, nil + // The canvas changed, more of it is drawn on than before, and there is + // more ink than the empty workbench's own furniture. + // + // It used to ask for more NEARLY BLACK pixels, and a scanned page has + // almost none: its ink is grey once it has been scaled to fit, and it + // covers the strip and the borders that were the dark pixels being + // counted. A real scanned document therefore read as never having + // arrived, on a page that was drawn correctly. + return v.Hash != empty.Hash && v.N > empty.N && v.Ink > empty.Ink, nil }); err != nil { say(c) return fmt.Errorf("the document never appeared on the canvas: %w", err) } withDoc, _ := look(ctx, c, sid) - fmt.Printf("document shown: %d pixels drawn, %d of them dark\n", withDoc.N, withDoc.Dark) + fmt.Printf("document shown: %d pixels drawn, %d of them ink\n", withDoc.N, withDoc.Ink) if err := screenshot(ctx, c, sid, shot); err != nil { return err @@ -359,8 +367,14 @@ func say(c *conn) { // canvas is what the tab actually has on its canvas. type canvas struct { - N int `json:"n"` + N int `json:"n"` + // Dark is how many pixels are nearly black. It tells a lit control from an + // unlit one, which is what it is for. Dark int `json:"dark"` + // Ink is how many pixels are dark the way text is dark. A scanned page + // scaled to fit a window is grey, not black: the same page counts 1805 + // pixels this way and 9 the other. + Ink int `json:"ink"` Hash int `json:"hash"` } @@ -384,13 +398,18 @@ func lookIn(ctx context.Context, c *conn, sid string, from, to float64) (canvas, const y0 = Math.floor(c.height * 0.07), y1 = Math.floor(c.height * 0.95); const d = c.getContext('2d').getImageData(x0, y0, x1 - x0, y1 - y0).data; const r0 = d[0], g0 = d[1], b0 = d[2]; - let n = 0, dark = 0, hash = 0; + let n = 0, dark = 0, ink = 0, hash = 0; for (let i = 0; i < d.length; i += 4) { if (d[i] !== r0 || d[i+1] !== g0 || d[i+2] !== b0) n++; if (d[i] + d[i+1] + d[i+2] < 240) dark++; + // Ink, as opposed to nearly black. Scanned text that has been scaled + // down to fit a window is grey: the same page counts 1805 pixels this + // way and 9 the other, so which threshold is used decides whether a + // scanned document is seen to arrive at all. + if ((d[i] * 299 + d[i+1] * 587 + d[i+2] * 114) / 1000 < 128) ink++; hash = (hash * 31 + d[i] + d[i+1] * 3 + d[i+2] * 7) | 0; } - return JSON.stringify({n, dark, hash}); + return JSON.stringify({n, dark, ink, hash}); })()`, from, to) var out canvas s, err := eval(ctx, c, sid, js) diff --git a/go.mod b/go.mod index b254f76..a8d5639 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,20 @@ module github.com/go-pdfkit/app go 1.26.4 require ( - github.com/go-gfx/gfx v0.10.0 + github.com/go-gfx/gfx v0.15.0 github.com/go-pdfkit/extract v0.3.0 github.com/go-pdfkit/forms v0.2.2 - github.com/go-pdfkit/ops v0.7.1 + github.com/go-pdfkit/ops v0.7.2 github.com/go-pdfkit/reader v0.6.0 - github.com/go-pdfkit/render v0.11.0 + github.com/go-pdfkit/render v0.19.0 github.com/go-widgets/painter v0.11.0 github.com/go-widgets/toolkit v0.277.0 github.com/go-widgets/webcanvas v0.1.0 ) require ( + github.com/ajroetker/go-highway v0.0.4 // indirect + github.com/ajroetker/go-jpeg2000 v0.0.2 // indirect github.com/andybalholm/brotli v1.2.2 // indirect github.com/coder/websocket v1.8.15 // indirect github.com/go-crdt/collab v0.25.0 // indirect @@ -28,6 +30,9 @@ require ( github.com/go-richdoc/richdoc v0.2.0 // indirect github.com/go-typeset/bidi v0.3.0 // indirect github.com/go-widgets/mvvm v0.5.0 // indirect + github.com/sergeymakinen/go-bmp v1.0.0 // indirect + github.com/sergeymakinen/go-ico v1.0.0 // indirect + github.com/tannevaled/gobig2 v0.1.0 // indirect golang.org/x/image v0.45.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.47.0 // indirect diff --git a/go.sum b/go.sum index 0acf603..00eb4e9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/ajroetker/go-highway v0.0.4 h1:RDQo+9OhTXI6BFctLo+5gYpHNbb92VYJ0ObnR4l6xAQ= +github.com/ajroetker/go-highway v0.0.4/go.mod h1:C/zYPNSSpOaraejY89FUTZTyQNEhi5+rEbU0LjlqJeU= +github.com/ajroetker/go-jpeg2000 v0.0.2 h1:ni8brffZrci4Kacx3nM5d92ipmTDfak84KgHYi6IxFw= +github.com/ajroetker/go-jpeg2000 v0.0.2/go.mod h1:7ld88W47lZy0x8gRQesRGAonDPOpr6ev8rckjCAfbzE= github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM= github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -8,8 +12,8 @@ github.com/go-crdt/collab v0.25.0 h1:xO/cZq41GvsCyPOw+sdldCu2r3lDbMK1N0GgN6D9+Ss github.com/go-crdt/collab v0.25.0/go.mod h1:eYhMwHjmRyN5zMk/IBwBsTe3ZxqHuSCvNx3TdVTpYHg= github.com/go-crdt/crdt v0.31.0 h1:CTi+lo0bjsJv3F09CR/AdILQtWEWmzbDQ+S/6HNBOMU= github.com/go-crdt/crdt v0.31.0/go.mod h1:vbfvns9B9FUaMi2sPKzMYttKmbxfgO7EK3Yu82SrlYw= -github.com/go-gfx/gfx v0.10.0 h1:3AqOO8TZph6/U8+ejJxYkCZ+wzddxAbZ7fxi6TvGop4= -github.com/go-gfx/gfx v0.10.0/go.mod h1:bFt/MWyYWRU3Ic9IaB8XOC9KLMMHRRmahMk4FaIGK7g= +github.com/go-gfx/gfx v0.15.0 h1:ol+4uqUTq8s23cGSjfbEHciTxkrZ7kTrpUuAPPz4XuY= +github.com/go-gfx/gfx v0.15.0/go.mod h1:VAK6hgCgkhT3j3ek2K7G8zDfkCPD3RCF45UbcRlCV8Q= github.com/go-iconoir/iconoir v0.2.0 h1:2ANqG6gkvHMoCtpNgNhSeOsuGMnlWQ+Sl43EndujIC0= github.com/go-iconoir/iconoir v0.2.0/go.mod h1:BrOQ68YO5BMsF7y+/nht4shbgjVfh9CxBoQF35eNosw= github.com/go-images/images v0.0.0-20260811115337-bc5d586f8e38 h1:p+DjIujiwUvBiyD0oS9SUatf3pMKo0GpcA4p/Kyv7d8= @@ -28,14 +32,14 @@ github.com/go-pdfkit/extract v0.3.0 h1:w1ansrvMNqd+Hy1p5fo1orh6hM8meb+xCNKNap/7o github.com/go-pdfkit/extract v0.3.0/go.mod h1:EOWOcx3o0HDC3ryCMoE9pwNIb3mwVmnXBSpTRfO1VdY= github.com/go-pdfkit/forms v0.2.2 h1:tGnENs09LBUBppSrH0dGenV8KCQcbngsi+OatWID90Y= github.com/go-pdfkit/forms v0.2.2/go.mod h1:dQ4FzO6qF7RJh6oEa0dkgL0i8hKBmm/e7hj4XC56h5Q= -github.com/go-pdfkit/ops v0.7.1 h1:JkjJOj4/x60VJpjGZgFTc950AE9hHUYBH9UlwJv3IzM= -github.com/go-pdfkit/ops v0.7.1/go.mod h1:rA+hZHtK3tpZW0j5Sz3sLBHMhbv3scPLI/k8Xij1r+U= +github.com/go-pdfkit/ops v0.7.2 h1:E1vLf+BcmuQuE9tlZ1dYCaiCsRpDEw9otOCVTjp90bY= +github.com/go-pdfkit/ops v0.7.2/go.mod h1:12bC9BoH7ru3OoOC52OjNMC+ti92M8Vrfd8NeRby2XE= github.com/go-pdfkit/pdffont v0.3.1 h1:+K7opHGRERDZ5EMkA12s1GT48t8GpncArhnyzyc5bxU= github.com/go-pdfkit/pdffont v0.3.1/go.mod h1:eFJ/7t9AvcX76KY5qNJMMKetz8hUUUdVM1qSWLi3S1g= github.com/go-pdfkit/reader v0.6.0 h1:KAabNOYUcTlZlNBTbG9bEhWP1NiZhjuhzUdavdTdfes= github.com/go-pdfkit/reader v0.6.0/go.mod h1:fQFOVfCMUui1AdvD4qhimdyvvNr9KvvJ1S7IuKZjyV8= -github.com/go-pdfkit/render v0.11.0 h1:z05asCKb8Bn/1JMU0r5KwqP5DiFkk0jJR3bVKGH4qj8= -github.com/go-pdfkit/render v0.11.0/go.mod h1:9b1LBmvHdZJ8+ACXA8XNUXWSOtNM+lkiOAFSiWRADpQ= +github.com/go-pdfkit/render v0.19.0 h1:BMyxO/mjUYo6bB7UKZHfFuyb3TDFnlJ1VT4i1aBUe/c= +github.com/go-pdfkit/render v0.19.0/go.mod h1:DrVmFaVDRM0qnzF7ZqWGZh4qPMSm7AO0DZaT6LVRd2A= github.com/go-richdoc/richdoc v0.2.0 h1:z9cLox9MoInZL6fIlweMzgDT/VqgnB2ZucSIEaRFglY= github.com/go-richdoc/richdoc v0.2.0/go.mod h1:aCX8ulqg5CoKqSWgWeEWul1Oj0d2VxQeaQNI0+jsm7c= github.com/go-typeset/bidi v0.3.0 h1:4fjGjejvjE2LzLNzY4si8PkVO321NcsKIiANhWT3jF4= @@ -56,6 +60,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-transports/websocket v0.2.0 h1:n4lN066PPcVTvTfLdwa84gfNiAzvS9k4SSQ1yWuG3+g= github.com/grpc-transports/websocket v0.2.0/go.mod h1:2z7Qy11qgPaGDfqzImOt0iyLwH0wjfsvaiidCIIpzgg= +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= +github.com/tannevaled/gobig2 v0.1.0 h1:9PdMvmnmYQURlUF40zt8t35Wnrz3KhZrwfxgCMvQc04= +github.com/tannevaled/gobig2 v0.1.0/go.mod h1:X0S1H+N35kg6zYgVYRqGMsGj3C35zs+iA2r1MXyPcV4= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=