Upstream explanation: https://github.com/pdewacht/brlaser/pull/124

--- src/job.cc.orig
+++ src/job.cc
@@ -73,14 +73,15 @@ void job::write_page_header() {
           page_params_.papersize.c_str());
   fprintf(out_, "@PJL SET PAGEPROTECT = AUTO\n");
   fprintf(out_, "@PJL SET ORIENTATION = PORTRAIT\n");
+  fprintf(out_, "@PJL SET DUPLEX = %s\n",
+          page_params_.duplex ? "ON" : "OFF");
+  fprintf(out_, "@PJL SET BINDING = %s\n",
+          page_params_.tumble ? "SHORTEDGE" : "LONGEDGE");
+  fprintf(out_, "@PJL SET COPIES = %d\n",
+          std::max(1, page_params_.num_copies));
   fprintf(out_, "@PJL ENTER LANGUAGE = PCL\n");
 
   fputs("\033E", out_);
-  fprintf(out_, "\033&l%dX", std::max(1, page_params_.num_copies));
-
-  if (page_params_.duplex) {
-    fputs("\033&l2S", out_);
-  }
 }
 
 void job::encode_page(const page_params &page_params,
--- src/job.h.orig
+++ src/job.h
@@ -27,6 +27,7 @@ struct page_params {
   int num_copies;
   int resolution;
   bool duplex;
+  bool tumble;
   bool economode;
   std::string sourcetray;
   std::string mediatype;
@@ -36,6 +37,7 @@ struct page_params {
     return num_copies == o.num_copies
       && resolution == o.resolution
       && duplex == o.duplex
+      && tumble == o.tumble
       && economode == o.economode
       && sourcetray == o.sourcetray
       && mediatype == o.mediatype
--- src/main.cc.orig
+++ src/main.cc
@@ -111,6 +111,7 @@ page_params build_page_params(const cups_page_header2_t &header) {
   p.economode = header.cupsInteger[10];
   p.mediatype = header.MediaType;
   p.duplex = header.Duplex;
+  p.tumble = header.Tumble;
 
   if (header.MediaPosition < sources.size())
     p.sourcetray = sources[header.MediaPosition];
