Skip to content

Instantly share code, notes, and snippets.

@mattbell87
Created July 28, 2026 05:27
Show Gist options
  • Select an option

  • Save mattbell87/d66c453aa6bcee58dd9f39b60a102f0f to your computer and use it in GitHub Desktop.

Select an option

Save mattbell87/d66c453aa6bcee58dd9f39b60a102f0f to your computer and use it in GitHub Desktop.
HP LaserJet P1005 on modern macOS (Apple Silicon) — sandbox-native CUPS driver + firmware auto-load, no Homebrew

HP LaserJet P1005 on modern macOS (Apple Silicon)

Getting an HP LaserJet P1005 printing on a current macOS (tested on macOS 26 / Apple Silicon) is painful for three reasons:

  1. It's a host-based ZjStream/XQX printer — not PCL, not PostScript. It needs the foo2xqx driver, which isn't in Homebrew and Apple ships nothing for it.
  2. It has no persistent firmware — a .dl blob must be uploaded on every power-on.
  3. macOS sandboxes CUPS filters. A filter cannot exec anything in /usr/local or /opt/homebrew, so the usual ghostscript/foomatic driver simply cannot run under cupsd (and Sandboxing Relaxed in cups-files.conf does not lift this on macOS 26).

This setup sidesteps all three:

  • Rasterising is done by Apple's own cgpdftoraster, which runs inside the sandbox — so no ghostscript / Homebrew is needed at all.
  • A tiny self-contained filter rastertoxqx (links only system libcups/ libcupsimage) converts the CUPS raster to PBM and pipes it to foo2xqx. Both live in /usr/libexec/cups/filter/, the only sandbox-allowed exec location.
  • Firmware is auto-loaded on power-on by the foo2zjs osx-hplj-hotplug IOKit daemon (event-driven, ~0% CPU, ~3 MB RAM).

Print quality is set to the P1005's native 1200×600, density 5 (600×600 looks faded/interlaced).

Install

Only requirement: Xcode Command Line Tools (xcode-select --install). No Homebrew required.

curl -fsSL https://gist.githubusercontent.com/mattbell87/d66c453aa6bcee58dd9f39b60a102f0f/raw/install.sh | sudo bash

or clone this gist and run sudo ./install.sh. Power the printer on and connect it by USB first. The script builds everything from source, fetches the firmware, installs the filters + firmware daemon, and configures the print queue.

Test

lp -d HP_LaserJet_P1005 /path/to/any.pdf

macOS apps always print PDF, so the cgpdftoraster → rastertoxqx → foo2xqx path is exactly what every app uses. (Note: the queue does not accept .ps files — macOS has no PostScript interpreter — but that never matters for real printing.)

If it stops printing

  • Nothing prints after a power-cycle → firmware didn't auto-load. Run once: lp -d HP_LaserJet_P1005 -o raw /usr/local/share/foo2xqx/firmware/sihpP1005.dl (orange light flashes ~5 s), then print normally.
  • Faded / interlaced → not at 1200×600. Check the queue's Resolution default.
  • Filter failed / Operation not permitted in /var/log/cups/error_log → something ended up outside /usr/libexec/cups/filter (sandbox).

Credits / license

foo2xqx, arm2hpdl and osx-hplj-hotplug are from OpenPrinting/foo2zjs (GPL-2.0+). rastertoxqx.c here is GPL-2.0+ to match. Firmware is HP's, mirrored by the Quirinux project.

*PPD-Adobe: "4.3"
*FormatVersion: "4.3"
*FileVersion: "1.0"
*LanguageVersion: English
*LanguageEncoding: ISOLatin1
*PCFileName: "P1005.PPD"
*Manufacturer: "HP"
*ModelName: "HP LaserJet P1005"
*Product: "(HP LaserJet P1005)"
*NickName: "HP LaserJet P1005 rastertoxqx"
*ShortNickName: "HP LaserJet P1005 rastertoxqx"
*PSVersion: "(3010.000) 0"
*LanguageLevel: "3"
*ColorDevice: False
*DefaultColorSpace: Gray
*FileSystem: False
*cupsVersion: 2.3
*cupsModelNumber: 0
*cupsManualCopies: True
*cupsFilter: "application/vnd.cups-raster 50 rastertoxqx"
*OpenUI *PageSize/Media Size: PickOne
*OrderDependency: 10 AnySetup *PageSize
*DefaultPageSize: A4
*PageSize A4/A4: "<</PageSize[595 842]>>setpagedevice"
*PageSize Letter/US Letter: "<</PageSize[612 792]>>setpagedevice"
*CloseUI: *PageSize
*OpenUI *PageRegion/Media Size: PickOne
*DefaultPageRegion: A4
*PageRegion A4/A4: "<</PageSize[595 842]>>setpagedevice"
*PageRegion Letter/US Letter: "<</PageSize[612 792]>>setpagedevice"
*CloseUI: *PageRegion
*DefaultImageableArea: A4
*ImageableArea A4/A4: "0 0 595 842"
*ImageableArea Letter/US Letter: "0 0 612 792"
*DefaultPaperDimension: A4
*PaperDimension A4/A4: "595 842"
*PaperDimension Letter/US Letter: "612 792"
*OpenUI *Resolution/Resolution: PickOne
*OrderDependency: 20 AnySetup *Resolution
*DefaultResolution: 1200x600dpi
*Resolution 600x600dpi/600 dpi: "<</HWResolution[600 600]/cupsBitsPerColor 1/cupsColorSpace 3/cupsColorOrder 0>>setpagedevice"
*Resolution 1200x600dpi/FastRes 1200: "<</HWResolution[1200 600]/cupsBitsPerColor 1/cupsColorSpace 3/cupsColorOrder 0>>setpagedevice"
*CloseUI: *Resolution
#!/bin/bash
#
# HP LaserJet P1005 driver install for modern macOS (Apple Silicon, tested macOS 26).
#
# Sandbox-native: Apple's cgpdftoraster (runs inside the CUPS sandbox) rasterises,
# then rastertoxqx (system libs only) -> foo2xqx -> printer. No ghostscript, no
# Homebrew. Firmware is auto-loaded on power-on by the foo2zjs osx-hplj-hotplug
# daemon. See README.md for the why.
#
# Usage: sudo bash install.sh
#
set -euo pipefail
QUEUE="${QUEUE:-HP_LaserJet_P1005}"
FILTERDIR=/usr/libexec/cups/filter
FWDIR=/usr/local/share/foo2xqx/firmware
FOO2ZJS_REPO="${FOO2ZJS_REPO:-https://github.com/OpenPrinting/foo2zjs.git}"
FW_URL="${FW_URL:-https://www.quirinux.org/printers/sihpP1005.tar.gz}"
[ "$(id -u)" = 0 ] || { echo "Run with sudo: sudo bash $0" >&2; exit 1; }
xcode-select -p >/dev/null 2>&1 || { echo "Install Xcode CLT first: xcode-select --install" >&2; exit 1; }
work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT; cd "$work"
echo ">> Cloning foo2zjs (for foo2xqx, arm2hpdl, hotplug daemon)"
git clone --depth 1 "$FOO2ZJS_REPO" foo2zjs >/dev/null 2>&1
cd foo2zjs
echo ">> Building foo2xqx + arm2hpdl (no external deps; bundled jbig)"
cc -O2 -o foo2xqx foo2xqx.c jbig.c jbig_ar.c -lm
cc -O2 -o arm2hpdl arm2hpdl.c
echo ">> Building firmware hotplug daemon (patched off SIP-protected /usr/share)"
sed -i.bak 's#/usr/share/foo2#/usr/local/share/foo2#g' osx-hotplug/osx-hplj-hotplug.m
cc -O2 osx-hotplug/osx-hplj-hotplug.m -o osx-hplj-hotplug \
-framework IOKit -framework Foundation -framework CoreFoundation
echo ">> Fetching + converting firmware"
curl -fsSL -o sihpP1005.tar.gz "$FW_URL"
tar xzf sihpP1005.tar.gz # -> sihpP1005.img
./arm2hpdl sihpP1005.img > sihpP1005.dl
echo ">> Building rastertoxqx"
cat > rastertoxqx.c <<'RXEOF'
/*
* rastertoxqx - CUPS raster -> foo2xqx (XQX/ZjStream) filter for HP LaserJet P1005.
*
* Self-contained: reads CUPS raster on stdin (produced inside the CUPS sandbox by
* Apple's cgpdftoraster), converts each page to pbmraw, and pipes it to foo2xqx.
* Links only against libcupsimage/libcups (system), so it runs fine under the
* macOS CUPS filter sandbox. foo2xqx lives beside this filter in the CUPS filter
* dir (an allowed exec location); override with $FOO2XQX for offline testing.
*
* CUPS filter argv: 0=name 1=job 2=user 3=title 4=copies 5=options 6=[rasterfile]
*/
#include <cups/raster.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <spawn.h>
extern char **environ;
static const char *foo2xqx_path(void) {
const char *e = getenv("FOO2XQX");
return (e && *e) ? e : "/usr/libexec/cups/filter/foo2xqx";
}
/* Map page size (points) to foo2xqx paper code + clip offset (at 1200x600). */
static void paper_params(double wpt, double hpt, int *pcode, int *xoff, int *yoff) {
struct { double w, h; int p, x, y; } tab[] = {
{595, 842, 9, 176, 84}, /* A4 */
{612, 792, 1, 177, 84}, /* Letter */
{612,1008, 5, 177, 96}, /* Legal */
{420, 595, 11, 192, 96}, /* A5 */
{522, 756, 7, 192, 96}, /* Executive */
};
int best = 0; double bd = 1e18;
for (unsigned i = 0; i < sizeof(tab)/sizeof(tab[0]); i++) {
double d = (wpt-tab[i].w)*(wpt-tab[i].w) + (hpt-tab[i].h)*(hpt-tab[i].h);
if (d < bd) { bd = d; best = i; }
}
*pcode = tab[best].p; *xoff = tab[best].x; *yoff = tab[best].y;
}
int main(int argc, char *argv[]) {
int fd = 0;
if (argc > 6 && argv[6] && argv[6][0]) {
fd = open(argv[6], O_RDONLY);
if (fd < 0) { perror("rastertoxqx: open"); return 1; }
}
int copies = 1;
if (argc > 4 && argv[4]) { copies = atoi(argv[4]); if (copies < 1) copies = 1; }
/* Density from options string: "...foo2xqxDensity=N..."; default 5. */
int density = 5;
if (argc > 5 && argv[5]) {
const char *p = strstr(argv[5], "foo2xqxDensity=");
if (p) { int d = atoi(p + 15); if (d >= 1 && d <= 5) density = d; }
}
cups_raster_t *ras = cupsRasterOpen(fd, CUPS_RASTER_READ);
if (!ras) { fprintf(stderr, "ERROR: rastertoxqx: cannot open raster stream\n"); return 1; }
cups_page_header2_t h;
if (!cupsRasterReadHeader2(ras, &h)) {
fprintf(stderr, "ERROR: rastertoxqx: empty raster (no pages)\n");
cupsRasterClose(ras); return 1;
}
int rx = h.HWResolution[0], ry = h.HWResolution[1];
int pcode, xoff, yoff;
paper_params(h.cupsPageSize[0], h.cupsPageSize[1], &pcode, &xoff, &yoff);
if (rx == 600) xoff /= 2; /* wrapper halves x offset at 600 dpi */
char sres[32], sdim[32], sp[8], sn[8], sT[8], su[24], sl[24];
snprintf(sres, sizeof sres, "%dx%d", rx, ry);
snprintf(sdim, sizeof sdim, "%ux%u", h.cupsWidth, h.cupsHeight);
snprintf(sp, sizeof sp, "%d", pcode);
snprintf(sn, sizeof sn, "%d", copies);
snprintf(sT, sizeof sT, "%d", density);
snprintf(su, sizeof su, "%dx%d", xoff, yoff);
snprintf(sl, sizeof sl, "%dx%d", xoff, yoff);
char *fargv[] = { (char*)foo2xqx_path(),
"-r", sres, "-g", sdim, "-p", sp, "-m", "1", "-n", sn,
"-d", "1", "-s", "7", "-T", sT, "-u", su, "-l", sl, NULL };
/* Pipe our generated pbmraw into foo2xqx; foo2xqx writes XQX to our stdout. */
int pfd[2];
if (pipe(pfd) < 0) { perror("pipe"); return 1; }
posix_spawn_file_actions_t fa;
posix_spawn_file_actions_init(&fa);
posix_spawn_file_actions_adddup2(&fa, pfd[0], 0); /* child stdin = pipe read */
posix_spawn_file_actions_addclose(&fa, pfd[1]);
pid_t pid;
if (posix_spawn(&pid, fargv[0], &fa, NULL, fargv, environ) != 0) {
fprintf(stderr, "ERROR: rastertoxqx: cannot exec %s\n", fargv[0]);
return 1;
}
posix_spawn_file_actions_destroy(&fa);
close(pfd[0]);
FILE *out = fdopen(pfd[1], "wb");
unsigned rowbytes = (h.cupsWidth + 7) / 8;
unsigned char *line = malloc(h.cupsBytesPerLine ? h.cupsBytesPerLine : rowbytes);
unsigned char *pbm = malloc(rowbytes);
int page = 0;
do {
page++;
/* PBM (P4) header for this page. */
fprintf(out, "P4\n%u %u\n", h.cupsWidth, h.cupsHeight);
for (unsigned y = 0; y < h.cupsHeight; y++) {
cupsRasterReadPixels(ras, line, h.cupsBytesPerLine);
if (h.cupsBitsPerColor == 1) {
memcpy(pbm, line, rowbytes);
/* PBM: 1=black. K:1=black (copy). W:1=white (invert). */
if (h.cupsColorSpace == CUPS_CSPACE_W || h.cupsColorSpace == CUPS_CSPACE_SW)
for (unsigned b = 0; b < rowbytes; b++) pbm[b] = ~pbm[b];
} else {
/* 8-bit (or deeper) grayscale -> threshold to 1-bit. */
unsigned bpp = h.cupsBitsPerPixel / 8; if (!bpp) bpp = 1;
memset(pbm, 0, rowbytes);
for (unsigned x = 0; x < h.cupsWidth; x++) {
unsigned v = line[x * bpp];
int black = (h.cupsColorSpace == CUPS_CSPACE_K ||
h.cupsColorSpace == CUPS_CSPACE_SW ||
h.cupsColorSpace == CUPS_CSPACE_W)
? ((h.cupsColorSpace == CUPS_CSPACE_K) ? (v >= 128) : (v < 128))
: (v < 128);
if (black) pbm[x >> 3] |= (0x80 >> (x & 7));
}
}
fwrite(pbm, 1, rowbytes, out);
}
} while (cupsRasterReadHeader2(ras, &h));
free(line); free(pbm);
fclose(out);
cupsRasterClose(ras);
if (fd) close(fd);
int st = 0; waitpid(pid, &st, 0);
fprintf(stderr, "INFO: rastertoxqx: %d page(s)\n", page);
return (WIFEXITED(st) && WEXITSTATUS(st) == 0) ? 0 : 1;
}
RXEOF
cc -O2 -Wall -o rastertoxqx rastertoxqx.c -lcupsimage -lcups
echo ">> Installing filters into $FILTERDIR"
install -m 755 -o root -g wheel rastertoxqx "$FILTERDIR/rastertoxqx"
install -m 755 -o root -g wheel foo2xqx "$FILTERDIR/foo2xqx"
echo ">> Installing firmware + hotplug daemon"
install -d -m 755 "$FWDIR"
install -m 644 sihpP1005.dl "$FWDIR/"
install -m 755 osx-hplj-hotplug /usr/local/bin/
cat > /Library/LaunchDaemons/com.foo2zjs.hpljhotplug.plist <<'PLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.foo2zjs.hpljhotplug</string>
<key>ProgramArguments</key><array><string>/usr/local/bin/osx-hplj-hotplug</string></array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/var/log/osx-hplj-hotplug.log</string>
<key>StandardErrorPath</key><string>/var/log/osx-hplj-hotplug.log</string>
</dict></plist>
PLEOF
launchctl unload /Library/LaunchDaemons/com.foo2zjs.hpljhotplug.plist 2>/dev/null || true
launchctl load /Library/LaunchDaemons/com.foo2zjs.hpljhotplug.plist
echo ">> Writing PPD"
cat > "$work/HP-LaserJet-P1005.ppd" <<'PPDEOF'
*PPD-Adobe: "4.3"
*FormatVersion: "4.3"
*FileVersion: "1.0"
*LanguageVersion: English
*LanguageEncoding: ISOLatin1
*PCFileName: "P1005.PPD"
*Manufacturer: "HP"
*ModelName: "HP LaserJet P1005"
*Product: "(HP LaserJet P1005)"
*NickName: "HP LaserJet P1005 rastertoxqx"
*ShortNickName: "HP LaserJet P1005 rastertoxqx"
*PSVersion: "(3010.000) 0"
*LanguageLevel: "3"
*ColorDevice: False
*DefaultColorSpace: Gray
*FileSystem: False
*cupsVersion: 2.3
*cupsModelNumber: 0
*cupsManualCopies: True
*cupsFilter: "application/vnd.cups-raster 50 rastertoxqx"
*OpenUI *PageSize/Media Size: PickOne
*OrderDependency: 10 AnySetup *PageSize
*DefaultPageSize: A4
*PageSize A4/A4: "<</PageSize[595 842]>>setpagedevice"
*PageSize Letter/US Letter: "<</PageSize[612 792]>>setpagedevice"
*CloseUI: *PageSize
*OpenUI *PageRegion/Media Size: PickOne
*DefaultPageRegion: A4
*PageRegion A4/A4: "<</PageSize[595 842]>>setpagedevice"
*PageRegion Letter/US Letter: "<</PageSize[612 792]>>setpagedevice"
*CloseUI: *PageRegion
*DefaultImageableArea: A4
*ImageableArea A4/A4: "0 0 595 842"
*ImageableArea Letter/US Letter: "0 0 612 792"
*DefaultPaperDimension: A4
*PaperDimension A4/A4: "595 842"
*PaperDimension Letter/US Letter: "612 792"
*OpenUI *Resolution/Resolution: PickOne
*OrderDependency: 20 AnySetup *Resolution
*DefaultResolution: 1200x600dpi
*Resolution 600x600dpi/600 dpi: "<</HWResolution[600 600]/cupsBitsPerColor 1/cupsColorSpace 3/cupsColorOrder 0>>setpagedevice"
*Resolution 1200x600dpi/FastRes 1200: "<</HWResolution[1200 600]/cupsBitsPerColor 1/cupsColorSpace 3/cupsColorOrder 0>>setpagedevice"
*CloseUI: *Resolution
PPDEOF
echo ">> Detecting printer on USB"
URI="$(lpinfo -v 2>/dev/null | awk '/usb:.*[Pp]1005/{print $2; exit}')"
[ -n "$URI" ] || URI="$(lpinfo -v 2>/dev/null | awk '/LaserJet.?P1005/{print $2; exit}')"
if [ -z "$URI" ]; then
echo "!! Printer not found on USB. Power it on / reconnect, then re-run." >&2
exit 1
fi
echo " $URI"
echo ">> Configuring queue '$QUEUE'"
lpadmin -p "$QUEUE" -E -v "$URI" -P "$work/HP-LaserJet-P1005.ppd"
cupsenable "$QUEUE"; cupsaccept "$QUEUE"
echo ">> Loading firmware now (also auto-loads on every power-on)"
lp -d "$QUEUE" -o raw "$FWDIR/sihpP1005.dl" >/dev/null 2>&1 || true
echo
echo "Done. Test with: lp -d $QUEUE /path/to/any.pdf"
/*
* rastertoxqx - CUPS raster -> foo2xqx (XQX/ZjStream) filter for HP LaserJet P1005.
*
* Self-contained: reads CUPS raster on stdin (produced inside the CUPS sandbox by
* Apple's cgpdftoraster), converts each page to pbmraw, and pipes it to foo2xqx.
* Links only against libcupsimage/libcups (system), so it runs fine under the
* macOS CUPS filter sandbox. foo2xqx lives beside this filter in the CUPS filter
* dir (an allowed exec location); override with $FOO2XQX for offline testing.
*
* CUPS filter argv: 0=name 1=job 2=user 3=title 4=copies 5=options 6=[rasterfile]
*/
#include <cups/raster.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <spawn.h>
extern char **environ;
static const char *foo2xqx_path(void) {
const char *e = getenv("FOO2XQX");
return (e && *e) ? e : "/usr/libexec/cups/filter/foo2xqx";
}
/* Map page size (points) to foo2xqx paper code + clip offset (at 1200x600). */
static void paper_params(double wpt, double hpt, int *pcode, int *xoff, int *yoff) {
struct { double w, h; int p, x, y; } tab[] = {
{595, 842, 9, 176, 84}, /* A4 */
{612, 792, 1, 177, 84}, /* Letter */
{612,1008, 5, 177, 96}, /* Legal */
{420, 595, 11, 192, 96}, /* A5 */
{522, 756, 7, 192, 96}, /* Executive */
};
int best = 0; double bd = 1e18;
for (unsigned i = 0; i < sizeof(tab)/sizeof(tab[0]); i++) {
double d = (wpt-tab[i].w)*(wpt-tab[i].w) + (hpt-tab[i].h)*(hpt-tab[i].h);
if (d < bd) { bd = d; best = i; }
}
*pcode = tab[best].p; *xoff = tab[best].x; *yoff = tab[best].y;
}
int main(int argc, char *argv[]) {
int fd = 0;
if (argc > 6 && argv[6] && argv[6][0]) {
fd = open(argv[6], O_RDONLY);
if (fd < 0) { perror("rastertoxqx: open"); return 1; }
}
int copies = 1;
if (argc > 4 && argv[4]) { copies = atoi(argv[4]); if (copies < 1) copies = 1; }
/* Density from options string: "...foo2xqxDensity=N..."; default 5. */
int density = 5;
if (argc > 5 && argv[5]) {
const char *p = strstr(argv[5], "foo2xqxDensity=");
if (p) { int d = atoi(p + 15); if (d >= 1 && d <= 5) density = d; }
}
cups_raster_t *ras = cupsRasterOpen(fd, CUPS_RASTER_READ);
if (!ras) { fprintf(stderr, "ERROR: rastertoxqx: cannot open raster stream\n"); return 1; }
cups_page_header2_t h;
if (!cupsRasterReadHeader2(ras, &h)) {
fprintf(stderr, "ERROR: rastertoxqx: empty raster (no pages)\n");
cupsRasterClose(ras); return 1;
}
int rx = h.HWResolution[0], ry = h.HWResolution[1];
int pcode, xoff, yoff;
paper_params(h.cupsPageSize[0], h.cupsPageSize[1], &pcode, &xoff, &yoff);
if (rx == 600) xoff /= 2; /* wrapper halves x offset at 600 dpi */
char sres[32], sdim[32], sp[8], sn[8], sT[8], su[24], sl[24];
snprintf(sres, sizeof sres, "%dx%d", rx, ry);
snprintf(sdim, sizeof sdim, "%ux%u", h.cupsWidth, h.cupsHeight);
snprintf(sp, sizeof sp, "%d", pcode);
snprintf(sn, sizeof sn, "%d", copies);
snprintf(sT, sizeof sT, "%d", density);
snprintf(su, sizeof su, "%dx%d", xoff, yoff);
snprintf(sl, sizeof sl, "%dx%d", xoff, yoff);
char *fargv[] = { (char*)foo2xqx_path(),
"-r", sres, "-g", sdim, "-p", sp, "-m", "1", "-n", sn,
"-d", "1", "-s", "7", "-T", sT, "-u", su, "-l", sl, NULL };
/* Pipe our generated pbmraw into foo2xqx; foo2xqx writes XQX to our stdout. */
int pfd[2];
if (pipe(pfd) < 0) { perror("pipe"); return 1; }
posix_spawn_file_actions_t fa;
posix_spawn_file_actions_init(&fa);
posix_spawn_file_actions_adddup2(&fa, pfd[0], 0); /* child stdin = pipe read */
posix_spawn_file_actions_addclose(&fa, pfd[1]);
pid_t pid;
if (posix_spawn(&pid, fargv[0], &fa, NULL, fargv, environ) != 0) {
fprintf(stderr, "ERROR: rastertoxqx: cannot exec %s\n", fargv[0]);
return 1;
}
posix_spawn_file_actions_destroy(&fa);
close(pfd[0]);
FILE *out = fdopen(pfd[1], "wb");
unsigned rowbytes = (h.cupsWidth + 7) / 8;
unsigned char *line = malloc(h.cupsBytesPerLine ? h.cupsBytesPerLine : rowbytes);
unsigned char *pbm = malloc(rowbytes);
int page = 0;
do {
page++;
/* PBM (P4) header for this page. */
fprintf(out, "P4\n%u %u\n", h.cupsWidth, h.cupsHeight);
for (unsigned y = 0; y < h.cupsHeight; y++) {
cupsRasterReadPixels(ras, line, h.cupsBytesPerLine);
if (h.cupsBitsPerColor == 1) {
memcpy(pbm, line, rowbytes);
/* PBM: 1=black. K:1=black (copy). W:1=white (invert). */
if (h.cupsColorSpace == CUPS_CSPACE_W || h.cupsColorSpace == CUPS_CSPACE_SW)
for (unsigned b = 0; b < rowbytes; b++) pbm[b] = ~pbm[b];
} else {
/* 8-bit (or deeper) grayscale -> threshold to 1-bit. */
unsigned bpp = h.cupsBitsPerPixel / 8; if (!bpp) bpp = 1;
memset(pbm, 0, rowbytes);
for (unsigned x = 0; x < h.cupsWidth; x++) {
unsigned v = line[x * bpp];
int black = (h.cupsColorSpace == CUPS_CSPACE_K ||
h.cupsColorSpace == CUPS_CSPACE_SW ||
h.cupsColorSpace == CUPS_CSPACE_W)
? ((h.cupsColorSpace == CUPS_CSPACE_K) ? (v >= 128) : (v < 128))
: (v < 128);
if (black) pbm[x >> 3] |= (0x80 >> (x & 7));
}
}
fwrite(pbm, 1, rowbytes, out);
}
} while (cupsRasterReadHeader2(ras, &h));
free(line); free(pbm);
fclose(out);
cupsRasterClose(ras);
if (fd) close(fd);
int st = 0; waitpid(pid, &st, 0);
fprintf(stderr, "INFO: rastertoxqx: %d page(s)\n", page);
return (WIFEXITED(st) && WEXITSTATUS(st) == 0) ? 0 : 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment