Repository navigation
pdftopdf rotates duplex back sides 180° when the job carries sides=two-sided-* (duplex prints upside down on IPP Everywhere/URF printers) #762
Description
Summary
On a driverless (IPP Everywhere / AirPrint, image/urf) queue, every job whose options contain the IPP attribute sides=two-sided-long-edge (or -short-edge) gets its back sides rotated 180° by pdftopdf, on top of the duplex imposition the printer itself performs. The back side of each duplexed sheet therefore prints upside down.
Jobs that request duplex only through the PPD option (Duplex=DuplexNoTumble, which is what GTK-based applications send) print correctly — the difference is only that one attribute. Since Apple/AirPrint clients communicate duplex exclusively through sides and never pre-rotate pages, printers must be doing the back-side orientation themselves, so the extra rotation in pdftopdf duplicates it.
Environment
- OS: Arch-based rolling distribution (CachyOS), x86_64
- CUPS
2:2.4.20-1 - cups-filters
2.0.1-3.1, libcupsfilters2.2.1.r32.gd49e9284-1.1, libppd2.1.1-2.1, pdfio1.6.5, Ghostscript10.08.0 - Printer: Pantum M7100DN (IPP Everywhere / AirPrint over
ipps://<printer>.local:443/ipp/print), advertisesimage/urf(andimage/pwg-raster),sides-supported = one-sided,two-sided-short-edge,two-sided-long-edge,landscape-orientation-requested-preferred = 5 - Queue: created by CUPS as a driverless queue (auto-generated PPD,
*PCFileName: "ippeve.ppd"), filter chainpdftopdf→gstoraster→image/urf
Steps to reproduce (no printer needed)
Using the queue's PPD and any two-page portrait PDF:
PPD=/etc/cups/ppd/<queue>.ppd
cupsfilter -P $PPD -i application/pdf -m application/vnd.cups-pdf -o sides=two-sided-long-edge two-page.pdf > a.pdf
cupsfilter -P $PPD -i application/pdf -m application/vnd.cups-pdf -o Duplex=DuplexNoTumble two-page.pdf > b.pdfa.pdf: page 2 is rotated 180° (rendering both pages and comparing a mark that is at the top of page 2 in the input shows it at the bottom in the output; the transformed content stream contains a 180° transform).b.pdf: page 2 is unchanged.
Same result when the filter is invoked directly:
PPD=$PPD /usr/lib/cups/filter/pdftopdf 1 user title 1 "sides=two-sided-long-edge" two-page.pdf > out.pdfObserved on real hardware
Printing a two-page document duplexed on that queue:
- Applications that set
sides(reproduced with WPS Office and ONLYOFFICE Desktop Editors, and withlp -o sides=...): back side upside down - Applications that only set the PPD option (GNOME Papers / Document Viewer, which sends
Duplex=DuplexNoTumbleand nosides): correct output
The duplex request itself reaches the printer in both cases. The URF stream's duplex byte (offset 14) is 0x03 for Duplex=DuplexNoTumble and also 0x03 for sides=two-sided-long-edge (0x02 for -short-edge, 0x01 simplex). In other words the extra rotation is purely on the CUPS side and duplicates the printer's own imposition.
For comparison, the same printer driven through its vendor PostScript driver (raw port 9100) duplexes correctly, which is why the driverless path is the suspect here.
Things that do not help (all verified on 2.2.1.x)
*cupsBackSide: Normal(as generated) /Rotated/ deleting the attribute*cupsFlipDuplex: true*LandscapeOrientation: Plus90|Minus90- passing
orientation-requested=3|4on the job (does not suppress it either, unlike pdftopdf injects an unwanted 90° rotation on an already-correctly-oriented PDF, unless orientation-requested is explicitly forced #723) - removing
*OpenUI/*CloseUIof theDuplexoption: then clients cannot request duplex at all (URF duplex byte becomes0x01), so it is not a workaround - renaming the
DuplexPPD option: clients lose the duplex control (the GTK CUPS backend builds it from the option literally namedDuplex, seegtkprintbackendcups.c– the{ "Duplex", "DuplexNoTumble", ... }table and the("Duplex","sides")mapping), but clients that map their own duplex setting tosidesstill send it — verified with a real application that kept sendingsides=two-sided-long-edgeeven when the choice values were renamed. So no PPD-level knob prevents this.
Expected behaviour
For printers that implement duplex themselves (any IPP Everywhere / URF / AirPrint device), pdftopdf should not rotate the back sides — or the documented PPD attributes (cupsBackSide, cupsFlipDuplex) should actually control it, or there should be an option to turn it off (the old nopdfAutoRotate escape hatch no longer exists in 2.x, see #723).
Related issues
- pdftopdf injects an unwanted 90° rotation on an already-correctly-oriented PDF, unless orientation-requested is explicitly forced #723 –
pdftopdfinjects an unwanted 90° rotation on an already-correctly-oriented PDF, unlessorientation-requestedis explicitly forced - rastertolabel uncontrollably dithering.... #221 – auto-rotation regression after libcupsfilters 2.2.1
- PDF printout rotated 180 degrees and mirrored #582 – PDF printout rotated 180 degrees and mirrored (moved to cups-filters by the CUPS maintainer)
Notes on the code path
libppd/ppd/ppd-filter.c reads cupsBackSide / cupsFlipDuplex / APDuplexRequiresFlippedMargin and forwards them as flip-duplex / duplex-requires-flipped-margin to the page-management code (cfFilterPDFToPDF in libcupsfilters), and detects the duplex state by checking the marked PPD options (Duplex, JCLDuplex, EFDuplex, EFDuplexing, ARDuplex, KD03Duplex). The back-side rotation applied for sides=two-sided-long-edge appears to come from that page-management step and is not affected by any of the PPD attributes above.
Activity
YangtseSu commentedon Oct 19, 2026
Follow-up with the exact defect location, since the reproduction here points at a different repository:
The rotation is in libcupsfilters,
cupsfilters/pdftopdf.c,cfFilterPDFToPDF():This was introduced by the QPDF -> PDFio rewrite (commit
74fc5c360f2148f7a57e302238585798eeb8423d, "Replace QPDF by PDFio as PDF manipulation library in libcupsfilters", 2025-10-01), which added the PDF-side back side transform. Because the value is a compile-time constant,prepare_documents()always ends up in therotatedbranch, sosides=two-sided-long-edgegets a-1 0 0 -1 <x2> <y2> cmapplied to the back pages, whiletwo-sided-short-edgegets no transform at all (its branch requiresflipped/manual-tumble). A compiler folds thestrcmp()calls against that constant away, which is exactly whycupsBackSide,cupsFlipDuplexand theback-side-orientation/duplex-requires-flipped-marginoptions cannot influence this code path (all verified here).Affected releases: 2.2.0 and 2.2.1. The 2.1.x line is not affected - before the rewrite
pdftopdfhad no back-side transform at all (theduplexparameter only drove page order and blank-page insertion), and back side orientation was handled solely by the raster filters throughcfGetBackSideOrientation().The answer to "which back side does the printer want" already exists in the same library:
cfGetBackSideOrientation()(cupsfilters/ipp.c) mapsurf-supportedDM1 -> CF_BACKSIDE_NORMAL,DM2 -> FLIPPED,DM3 -> ROTATED,DM4 -> MANUAL_TUMBLEforimage/urfoutput, and defaults toCF_BACKSIDE_NORMALwhen nothing is known. The raster paths (pdftoraster.c,ghostscript.c,rastertopwg.c) use it; the PDF path does not. Every printer that reportsDM1therefore asks for back sides as sent - which is also what AirPrint clients do - and must not get an extra rotation.Suggested fix (
cupsfilters/pdftopdf.c, incfFilterPDFToPDF()):Verification without a printer: with a build carrying this patch, the reproduction from the report (
pdftopdf ... sides=two-sided-long-edge) no longer moves a mark that is printed at the top of page 2 - it stays at the top, whereas the unpatched library moves it to the bottom and emits the 180 degreecm. The duplex flag in the generatedimage/urfstream (byte 14) is unaffected:0x03for long edge,0x02for short edge,0x01for simplex. Upstream's own test suite (make check) passes with the patch (14 pass, 1 skip, 0 fail). A printer with the patch installed then duplexes correctly, no PPD workaround needed.Patch applies to master and to
d49e9284withpatch -p1; happy to post it as a pull request (or to open the issue in libcupsfilters if that is the preferred tracker for this one).YangtseSu commentedon Oct 19, 2026
Closing as a duplicate: same defect, tracked where the code lives - OpenPrinting/libcupsfilters#263, with a pending fix in OpenPrinting/libcupsfilters#265. Thanks for the pointer to #263.