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
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.
74 lines
2.6 KiB
Go
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
|
|
}
|