Skip to content
Snippets Groups Projects
Select Git revision
  • f80031c52c36d14d518c8e249f8f29a480df0980
  • master default protected
2 results

cups-pdf-postprocess.py

Blame
  • cups-pdf-postprocess.py 7.57 KiB
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    """
    XXX
    
    Author: Radek Hušek
    """
    
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    import subprocess as sp
    import ast
    import os
    
    CONFIG_FILE = "/etc/cups/cups-pdf-postprocess.conf"
    
    def exec_pipeline(pipeline, inp, out = None, err_callback = None):
      def make_proc(cmd):
        p = QProcess()
        p.setProgram(cmd[0])
        p.setArguments(cmd[1:])
        return p
    
      def readChunk(cb, proc, channel):
        def x():
          proc.setReadChannel(channel)
          cb(str(proc.readAll(), encoding='utf-8'))
        return x
    
      procs = [ make_proc(cmd) for cmd in pipeline ]
      for a, b in zip(procs, procs[1:]):
        a.setStandardOutputProcess(b)
      
      procs[0].setStandardInputFile(inp)
      if out is None:
        if err_callback is not None:
          procs[-1].readyReadStandardOutput.connect(
            readChunk(err_callback, procs[-1], QProcess.StandardOutput))
      else:
        procs[-1].setStandardOutputFile(out)
    
      if err_callback is not None:
        for p in procs:
          p.readyReadStandardError.connect(
            readChunk(err_callback, p, QProcess.StandardError))
        err_callback("< '%s' \\\n" % inp)
        err_callback(" |\n".join(("'%s'" % "' '".join(cmd)) for cmd in pipeline))
        err_callback("\n")
    
      for p in procs:
        p.start(QProcess.Unbuffered | QProcess.ReadWrite)
    
      for p in procs:
        while not p.waitForFinished(30):
          QApplication.instance().processEvents()
    
    
    
    
    class CPP(QWidget):
      def __init__(self, config, pdf, user):
        super().__init__()
        self._pdf = pdf
        self._config = config
        self._defaults = self._config.get("defaults", {})
    
        self.init_printers()
        self.init_gui()
    
        self.show()
    
    
      def init_printers(self):
        self._save_as_file = object()
    
        self._printers = self._config.get("printers", [])
        self._printers.append(("Save as ...", self._save_as_file))
    
        for l in sp.check_output(["lpstat", "-a"]).decode("UTF-8").split("\n"):
          p = l.split(" ")[0]
          if p == "":
            continue
          self._printers.append((p, ["lp", "-d", p]))
    
    
      def init_gui(self):
        vbox = QVBoxLayout()
        vbox.addStretch(1)
    
        o = QLabel("Printing %s" % (self._pdf,), self)
        o.setMinimumWidth(400)
        o.setMaximumWidth(800)
        o.setWordWrap(True)
        vbox.addWidget(o)
    
        def margin_spinbox(name, layout, margin_id):
          hbox = QHBoxLayout()
          hbox.addWidget(QLabel(name))
          s = QSpinBox(self)
          s.setMinimum(0)
          s.setMaximum(100)
          s.setSingleStep(1)
          s.setValue(self._defaults.get("crop_margins", [20, 20, 20, 20])[margin_id])
          s.setEnabled(False)
          hbox.addWidget(s)
          layout.addLayout(hbox)
          return s
    
        hbox = QHBoxLayout()
        self._crop = QCheckBox('C&rop', self)
        hbox.addWidget(self._crop)
        _vbox = QVBoxLayout()
        _vbox.addWidget(QLabel("Margins [mm]"))
        self._crop_margin = (
          margin_spinbox("Left", _vbox, 0),
          margin_spinbox("Top", _vbox, 1),
          margin_spinbox("Right", _vbox, 2),
          margin_spinbox("Bottom", _vbox, 3),
        )
        hbox.addLayout(_vbox)
        vbox.addLayout(hbox)
    
        def _crop_changed(checked):
          for m in self._crop_margin:
            m.setEnabled(checked)
        self._crop.clicked[bool].connect(_crop_changed)
        if self._defaults.get("crop", False):
          self._crop.setChecked(True)
          _crop_changed(True)
    
        hbox = QHBoxLayout()
        self._compact = QCheckBox('Co&mpact', self)
        self._compact_copies = QSpinBox(self)
        self._compact_copies.setMinimum(1)
        self._compact_copies.setMaximum(1000)
        self._compact_copies.setSingleStep(1)
        self._compact_copies.setValue(self._defaults.get("copies", 1))
        self._compact_copies.setEnabled(False)
    
        def _compact_changed(enabled):
          self._pdfbook.setEnabled(not enabled)
          self._compact_copies.setEnabled(enabled)
          if enabled:
            self._duplex.setCurrentIndex(1)
            self._pdfbook.setChecked(False)
            _pdfbook_changed(False)
          self._duplex.setEnabled(not enabled)
    
        self._compact.clicked[bool].connect(_compact_changed)
        if self._defaults.get("compact", False):
          self._compact.setChecked(True)
          _compact_changed(True)
    
        hbox.addWidget(self._compact)
        hbox.addWidget(self._compact_copies)
        vbox.addLayout(hbox)
    
        hbox = QHBoxLayout()
        hbox.addWidget(QLabel("Duplex:"))
        self._duplex = QComboBox(self)
        self._duplex.addItem("None", 'duplex=one-sided')
        self._duplex.addItem("Long Edge", "duplex=two-sided-long-edge")
        self._duplex.addItem("Short Side", "duplex=two-sided-long-edge")
        self._duplex.setCurrentIndex(1)
        self._pdfbook = QCheckBox('Use pdf&book', self)
        self._staple = QCheckBox('&Staple', self)
        self._staple.setEnabled(False)
    
        def _pdfbook_changed(enabled):
          self._staple.setEnabled(enabled)
          self._duplex.setEnabled(not enabled)
          if enabled:
            self._duplex.setCurrentIndex(1)
    
        self._pdfbook.clicked[bool].connect(_pdfbook_changed)
        if self._defaults.get("pdfbook", False):
          self._pdfbook.setChecked(True)
          _pdfbook_changed(True)
        if self._defaults.get("staple", False):
          self._staple.setChecked(True)
    
        hbox.addWidget(self._duplex)
        vbox.addLayout(hbox)
        vbox.addWidget(self._pdfbook)
        vbox.addWidget(self._staple)
    
        o = QComboBox(self)
        for (p, cmd) in self._printers:
          o.addItem(p, cmd)
        vbox.addWidget(o)
        self._printer = o
    
        hbox = QHBoxLayout()
        b_p = QPushButton("&Print", self)
        b_p.clicked.connect(self.print)
        hbox.addWidget(b_p)
        self._button_print = b_p
        b_c = QPushButton("&Cancel", self)
        b_c.clicked.connect(self.close)
        hbox.addWidget(b_c)
        self._button_cancel = b_c
        vbox.addLayout(hbox)
    
        self._logger = QTextEdit()
        self._logger.setReadOnly(True)
        vbox.addWidget(self._logger)
    
        self.setLayout(vbox)
        self.setWindowTitle('CUPS pdf printer postprocessor')
    
    
      def print(self):
        lp = QProcess()
        self._button_print.setEnabled(False)
        log = []
        pipeline = []
    
        def logMsg(msg):
          log.append(msg)
          self._logger.setPlainText("".join(log))
    
        MM_TO_BP = 2.83465
    
        if self._crop.isChecked():
          margin = " ".join([ "%d" % (MM_TO_BP * m.value()) for m in self._crop_margin ])
          pipeline.append([ "%s/pdfcrop_wrapper.sh" % DIR,
                            "--margin", margin, "-", "/dev/stdout" ])
    
        if self._pdfbook.isChecked():
          pipeline.append([ "pdfbook", "/dev/stdin", "-o", "/dev/stdout" ])
    
        if self._compact.isChecked():
          pipeline.append([ "%s/print_compact.sh" % DIR,
                            "%i" % self._compact_copies.value() ])
    
        if self._printer.currentData() is self._save_as_file:
          f = QFileDialog.getSaveFileName(self, 'Save as ...')
          if not f[0]:
            self.close()
          pipeline.append([ "dd", "of=%s" % (f[0],) ])
        else:
          cmd = self._printer.currentData().copy()
          cmd += [ "-o", self._duplex.currentData() ]
          if self._staple.isChecked():
            cmd += [ "-o", "XRFold=BiFoldStaple" ]
          cmd.append("-")
          pipeline.append(cmd)
    
        exec_pipeline(pipeline, self._pdf, err_callback=logMsg)
        self._button_cancel.setText("&Close")
    
    
    DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
    
    if __name__ == '__main__':
      args = sys.argv
      if args[1] == '-c':
        CONFIG_FILE = args[2]
        args = args[2:]
      else:
        CONFIG_FILE = os.getenv('CUPS_PDF_POSTPROCESS_CONFIG', CONFIG_FILE)
    
      with open(CONFIG_FILE) as config:
        CONFIG = ast.literal_eval("{\n%s\n}" % config.read())
    
      # env must be fixed before we create Qt stuff
      for var, val in CONFIG.get("enviroment", {}).items():
        if val is None:
          del os.environ[var]
        else:
          os.environ[var] = val
    
      app = QApplication(args[2:])
      ex = CPP(CONFIG, args[1], args[2])
      sys.exit(app.exec_())