Files
mesh-catalog/modules/brother/cmd/brother/pdf.go
T
jschoubben d5f9cd465c
mesh/merge-gate head d5f9cd465c96 merged into trunk d62c2edc35f3 — pass: builds brother → shanks; no bus step; every machine composes with the change as it…
mesh/repo-check head d5f9cd465c96 merged into trunk d62c2edc35f3 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
Send brother's scan settings on one line and scan from the feeder, both sides, into one PDF (issue 1452)
The Brother ignores the whole body when it holds a line break, so every setting was ignored and the glass was scanned at its default.
2026-10-21 01:05:55 +02:00

74 lines
2.6 KiB
Go

package main
import (
"bytes"
"fmt"
"image/color"
"image/jpeg"
"strings"
)
// PDF writes a one-page PDF holding the JPEG as it is (DCTDecode), the page as large as the image really is:
// pixels over dpi, in points (72 to the inch).
func PDF(jpegBytes []byte, dpi int) ([]byte, error) {
return PDFPages([]PDFPage{{JPEG: jpegBytes, DPI: dpi}})
}
// PDFPage is one image and its own resolution.
type PDFPage struct {
JPEG []byte
DPI int
}
// PDFPages writes a PDF with one page for each image, in order, each as it is (DCTDecode) and as large as it really
// is.
func PDFPages(pages []PDFPage) ([]byte, error) {
if len(pages) == 0 {
return nil, fmt.Errorf("a PDF needs a page")
}
var b bytes.Buffer
var offsets []int
b.WriteString("%PDF-1.4\n%\xE2\xE3\xCF\xD3\n")
// Objects 1 and 2 are the catalog and the page tree; page i is objects 3+3i (page), 4+3i (image), 5+3i (content).
add := func(body string) {
offsets = append(offsets, b.Len())
fmt.Fprintf(&b, "%d 0 obj\n%s\nendobj\n", len(offsets), body)
}
kids := make([]string, len(pages))
for i := range pages {
kids[i] = fmt.Sprintf("%d 0 R", 3+3*i)
}
add("<< /Type /Catalog /Pages 2 0 R >>")
add(fmt.Sprintf("<< /Type /Pages /Kids [%s] /Count %d >>", strings.Join(kids, " "), len(pages)))
for i, pg := range pages {
if pg.DPI <= 0 {
return nil, fmt.Errorf("resolution %d dpi is no resolution", pg.DPI)
}
cfg, err := jpeg.DecodeConfig(bytes.NewReader(pg.JPEG))
if err != nil {
return nil, fmt.Errorf("the image of page %d cannot be read: %w", i+1, err)
}
space := "DeviceRGB"
if cfg.ColorModel == color.GrayModel {
space = "DeviceGray"
}
wpt := float64(cfg.Width) / float64(pg.DPI) * 72
hpt := float64(cfg.Height) / float64(pg.DPI) * 72
content := fmt.Sprintf("q %.2f 0 0 %.2f 0 0 cm /Im0 Do Q\n", wpt, hpt)
add(fmt.Sprintf("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 %.2f %.2f] /Resources << /XObject << /Im0 %d 0 R >> >> /Contents %d 0 R >>", wpt, hpt, 4+3*i, 5+3*i))
offsets = append(offsets, b.Len())
fmt.Fprintf(&b, "%d 0 obj\n<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /%s /BitsPerComponent 8 /Filter /DCTDecode /Length %d >>\nstream\n",
len(offsets), cfg.Width, cfg.Height, space, len(pg.JPEG))
b.Write(pg.JPEG)
b.WriteString("\nendstream\nendobj\n")
add(fmt.Sprintf("<< /Length %d >>\nstream\n%sendstream", len(content), content))
}
xref := b.Len()
fmt.Fprintf(&b, "xref\n0 %d\n0000000000 65535 f \n", len(offsets)+1)
for _, o := range offsets {
fmt.Fprintf(&b, "%010d 00000 n \n", o)
}
fmt.Fprintf(&b, "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n", len(offsets)+1, xref)
return b.Bytes(), nil
}