#include "printerworker.h" #include "papersizes.h" #include "convertchecker.h" #include "mimer.h" #include #include #include #include #include #include #include #include "ippprinter.h" #include "pdf2printable.h" #include "ppm2pwg.h" #include "baselinify.h" #include #include #define OK(call) if(!(call)) throw ConvertFailedException() PrinterWorker::PrinterWorker(IppPrinter* parent) { _printer = parent; _url = parent->httpUrl(); _thread.reset(new QThread); moveToThread(_thread.get()); _thread->start(); } PrinterWorker::~PrinterWorker() { QMetaObject::invokeMethod(this, "cleanup"); _thread->wait(); } void PrinterWorker::cleanup() { _thread->quit(); } void PrinterWorker::urlChanged(QUrl url) { _url = url; } void PrinterWorker::getStrings(QUrl url) { CurlRequester cr(url, CurlRequester::HttpGetRequest); awaitResult(cr, "getStringsFinished"); } void PrinterWorker::getImage(QUrl url) { CurlRequester cr(url, CurlRequester::HttpGetRequest); awaitResult(cr, "getImageFinished"); } void PrinterWorker::getPrinterAttributes(Bytestream msg) { CurlRequester cr(_url, CurlRequester::IppRequest, &msg); awaitResult(cr, "getPrinterAttributesFinished"); } void PrinterWorker::getJobs(Bytestream msg) { CurlRequester cr(_url, CurlRequester::IppRequest, &msg); awaitResult(cr, "getJobsRequestFinished"); } void PrinterWorker::cancelJob(Bytestream msg) { CurlRequester cr(_url, CurlRequester::IppRequest, &msg); awaitResult(cr, "cancelJobFinished"); } void PrinterWorker::identify(Bytestream msg) { CurlRequester cr(_url, CurlRequester::IppRequest, &msg); awaitResult(cr, "identifyFinished"); } void PrinterWorker::print2(QString filename, QString mimeType, QString targetFormat, IppMsg createJob, IppMsg sendDocument, PrintParameters Params, QMargins margins) { emit busyMessage(tr("Preparing")); Bytestream header = createJob.encode(); CurlRequester cr(_url, CurlRequester::IppRequest, &header); Bytestream resData; CURLcode res = cr.await(&resData); if(res == CURLE_OK) { IppMsg resMsg(resData); qDebug() << resMsg.getOpAttrs() << resMsg.getJobAttrs(); QJsonObject resJobAttrs = resMsg.getJobAttrs()[0].toObject(); if(resMsg.getStatus() <= 0xff && resJobAttrs.contains("job-id")) { int jobId = resJobAttrs["job-id"].toObject()["value"].toInt(); sendDocument.setOpAttr("job-id", IppMsg::Integer, jobId); sendDocument.setOpAttr("last-document", IppMsg::Boolean, true); print(filename, mimeType, targetFormat, sendDocument, Params, margins); } else { QMetaObject::invokeMethod(_printer, "printRequestFinished", Qt::QueuedConnection, Q_ARG(CURLcode, res), Q_ARG(Bytestream, resData)); } } else { QMetaObject::invokeMethod(_printer, "printRequestFinished", Qt::QueuedConnection, Q_ARG(CURLcode, res), Q_ARG(Bytestream, resData)); } } void PrinterWorker::print(QString filename, QString mimeType, QString targetFormat, IppMsg job, PrintParameters Params, QMargins margins) { try { Mimer* mimer = Mimer::instance(); Bytestream contents = job.encode(); emit busyMessage(tr("Preparing")); if((mimeType == targetFormat) && (targetFormat == Mimer::Postscript)) { // Can't process Postscript justUpload(filename, contents); } else if((mimeType == targetFormat) && (targetFormat == Mimer::Plaintext)) { fixupPlaintext(filename, contents); } else if((mimeType != Mimer::SVG) && mimer->isImage(mimeType) && mimer->isImage(targetFormat)) { // Just make sure the image is in the desired format (and jpeg baseline-encoded), don't resize locally printImageAsImage(filename, contents, targetFormat); } else if(Params.format != PrintParameters::Invalid) // Params.format can be trusted { if(mimeType == Mimer::PDF) { convertPdf(filename, contents, Params); } else if(mimeType == Mimer::Plaintext) { convertPlaintext(filename, contents, Params); } else if(Mimer::isImage(mimeType)) { convertImage(filename, contents, Params, margins); } else if(Mimer::isOffice(mimeType)) { convertOfficeDocument(filename, contents, Params); } else { emit failed(tr("Cannot convert this file format")); } } else { emit failed(tr("Cannot convert this file format")); } return; } catch(const ConvertFailedException& e) { emit failed(e.what() == QString("") ? tr("Print error") : e.what()); } } void PrinterWorker::justUpload(QString filename, Bytestream header) { emit busyMessage(tr("Printing")); CurlRequester cr(_url); QFile file(filename); file.open(QFile::ReadOnly); OK(cr.write((char*)header.raw(), header.size())); QByteArray tmp = file.readAll(); OK(cr.write(tmp.data(), tmp.length())); file.close(); awaitResult(cr, "printRequestFinished"); } void PrinterWorker::printImageAsImage(QString filename, Bytestream header, QString targetFormat) { QString imageFormat = ""; QStringList supportedImageFormats = {Mimer::JPEG, Mimer::PNG}; if(targetFormat == Mimer::RBMP) { // ok } else if(supportedImageFormats.contains(targetFormat)) { imageFormat = targetFormat.split("/")[1]; } else { throw ConvertFailedException(tr("Unknown target format")); } QString mimeType = Mimer::instance()->get_type(filename); Bytestream OutBts; CurlRequester cr(_url); if(mimeType == Mimer::JPEG && targetFormat == Mimer::JPEG) { std::ifstream ifs = std::ifstream(filename.toStdString(), std::ios::in | std::ios::binary); Bytestream InBts(ifs); baselinify(InBts, OutBts); } else if(targetFormat == Mimer::RBMP) { QImageReader reader(filename); reader.setAutoTransform(true); QImage inImage = reader.read(); QBuffer buf; if(inImage.isNull()) { qDebug() << "failed to load"; throw ConvertFailedException(tr("Failed to load image")); } // TODO: calculate paper width minus margins // (depends on understanding/parsing custom paper sizes) int width = 576; int height = inImage.height() * ((width*1.0)/inImage.width()); inImage = inImage.scaled(width, height); inImage = inImage.convertToFormat(QImage::Format_Mono); inImage = inImage.transformed(QMatrix().scale(1,-1)); buf.open(QIODevice::ReadWrite); inImage.save(&buf, "bmp"); buf.seek(0); OutBts = Bytestream(buf.size()); buf.read((char*)(OutBts.raw()), buf.size()); } else if(targetFormat == mimeType) { std::ifstream ifs = std::ifstream(filename.toStdString(), std::ios::in | std::ios::binary); OutBts = Bytestream(ifs); } else { QImageReader reader(filename); reader.setAutoTransform(true); QImage inImage = reader.read(); QBuffer buf; if(inImage.isNull()) { qDebug() << "failed to load"; throw ConvertFailedException(tr("Failed to load image")); } buf.open(QIODevice::ReadWrite); inImage.save(&buf, imageFormat.toStdString().c_str()); buf.seek(0); OutBts = Bytestream(buf.size()); buf.read((char*)(OutBts.raw()), buf.size()); } emit busyMessage(tr("Printing")); OK(cr.write((char*)header.raw(), header.size())); OK(cr.write((char*)OutBts.raw(), OutBts.size())); awaitResult(cr, "printRequestFinished"); } void PrinterWorker::fixupPlaintext(QString filename, Bytestream header) { CurlRequester cr(_url); QFile inFile(filename); if(!inFile.open(QIODevice::ReadOnly)) { throw ConvertFailedException(tr("Failed to open file")); } QString allText = inFile.readAll(); if(allText.startsWith("\f")) { allText.remove(0, 1); } if(allText.endsWith("\f")) { allText.chop(1); } else if(allText.endsWith("\f\n")) { allText.chop(2); } QStringList lines; for(QString rnline : allText.split("\r\n")) { lines.append(rnline.split("\n")); } QByteArray outData = lines.join("\r\n").toUtf8(); OK(cr.write((char*)header.raw(), header.size())); OK(cr.write(outData.data(), outData.length())); awaitResult(cr, "printRequestFinished"); } void PrinterWorker::convertPdf(QString filename, Bytestream header, PrintParameters Params) { emit busyMessage(tr("Printing")); CurlRequester cr(_url); OK(cr.write((char*)header.raw(), header.size())); write_fun WriteFun([&cr](unsigned char const* buf, unsigned int len) -> bool { if(len == 0) return true; return cr.write((const char*)buf, len); }); progress_fun ProgressFun([this](size_t page, size_t total) -> void { emit progress(page, total); }); bool verbose = QLoggingCategory::defaultCategory()->isDebugEnabled(); int res = pdf_to_printable(filename.toStdString(), WriteFun, Params, ProgressFun, verbose); if(res != 0) { throw ConvertFailedException(tr("Conversion failed")); } awaitResult(cr, "printRequestFinished"); qDebug() << "Finished"; } void PrinterWorker::convertImage(QString filename, Bytestream header, PrintParameters Params, QMargins margins) { QString mimeType = Mimer::instance()->get_type(filename); if(Params.format == PrintParameters::URF && (Params.hwResW != Params.hwResH)) { // URF only supports symmetric resolutions qDebug() << "Unsupported URF resolution"; throw ConvertFailedException(tr("Unsupported resolution (dpi)")); } qDebug() << "Size is" << Params.getPaperSizeWInPixels() << "x" << Params.getPaperSizeHInPixels(); int leftMarginPx = (margins.left()/2540.0)*Params.hwResW; int rightMarginPx = (margins.right()/2540.0)*Params.hwResW; int topMarginPx = (margins.top()/2540.0)*Params.hwResH; int bottomMarginPx = (margins.bottom()/2540.0)*Params.hwResH; int totalXMarginPx = leftMarginPx+rightMarginPx; int totalYMarginPx = topMarginPx+bottomMarginPx; size_t targetWidth = Params.getPaperSizeWInPixels()-totalXMarginPx; size_t targetHeight = Params.getPaperSizeHInPixels()-totalYMarginPx; QImage inImage; if(mimeType == Mimer::SVG) { QSvgRenderer renderer(filename); if(!renderer.isValid()) { qDebug() << "failed to load svg"; throw ConvertFailedException(tr("Failed to load image")); } QSize defaultSize = renderer.defaultSize(); QSize targetSize(targetWidth, targetHeight); if(defaultSize.width() > defaultSize.height()) { targetSize.transpose(); } QSize initialSize = defaultSize.scaled(targetSize, Qt::KeepAspectRatio); inImage = QImage(initialSize, QImage::Format_RGB32); inImage.fill(QColor("white")); QPainter painter(&inImage); renderer.render(&painter); painter.end(); // Or else the painter segfaults on destruction if we have messed with the image if(inImage.width() > inImage.height()) { inImage = inImage.transformed(QMatrix().rotate(270.0)); } } else { QImageReader reader(filename); reader.setAutoTransform(true); inImage = reader.read(); if(inImage.isNull()) { qDebug() << "failed to load"; throw ConvertFailedException(tr("Failed to load image")); } if(inImage.width() > inImage.height()) { inImage = inImage.transformed(QMatrix().rotate(270.0)); } inImage = inImage.scaled(targetWidth, targetHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } if(Params.format == PrintParameters::PDF || Params.format == PrintParameters::Postscript) { QTemporaryFile tmpPdfFile; tmpPdfFile.open(); QPdfWriter pdfWriter(tmpPdfFile.fileName()); pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION); QPageSize pageSize({Params.getPaperSizeWInPoints(), Params.getPaperSizeHInPoints()}, QPageSize::Point); pdfWriter.setPageSize(pageSize); pdfWriter.setResolution(Params.hwResH); QPainter painter(&pdfWriter); int xOffset = ((pdfWriter.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx; int yOffset = ((pdfWriter.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx; painter.drawImage(xOffset, yOffset, inImage); painter.end(); convertPdf(tmpPdfFile.fileName(), header, Params); } else { size_t total_pages = Params.documentCopies*Params.pageCopies; if(total_pages > 1 && Params.duplex) { // Images are one page by definition - if we need to do client-side copies, they must be one-sided or we'd have to insert backsides qDebug() << "Inconsistent duplex setting"; throw ConvertFailedException(tr("Inconsistent duplex setting")); } QImage outImage = QImage(Params.getPaperSizeWInPixels(), Params.getPaperSizeHInPixels(), inImage.format()); outImage.fill(Qt::white); QPainter painter(&outImage); int xOffset = ((outImage.width()-totalXMarginPx)-inImage.width())/2 + leftMarginPx; int yOffset = ((outImage.height()-totalYMarginPx)-inImage.height())/2 + topMarginPx; painter.drawImage(xOffset, yOffset, inImage); painter.end(); QBuffer buf; buf.open(QIODevice::ReadWrite); Bytestream fileHdr, outBts; if(inImage.allGray()) { Params.colors = 1; // No need to waste space/bandwidth... } outImage.save(&buf, Params.colors==1 ? "pgm" : "ppm"); buf.seek(0); // Skip header - TODO consider reimplementing buf.readLine(255); buf.readLine(255); buf.readLine(255); Bytestream inBts(Params.getPaperSizeWInPixels() * Params.getPaperSizeHInPixels() * Params.colors); if((((size_t)buf.size())-buf.pos()) != inBts.size()) { qDebug() << buf.size() << buf.pos() << inBts.size(); throw ConvertFailedException(); } buf.read((char*)(inBts.raw()), inBts.size()); fileHdr << (Params.format == PrintParameters::URF ? make_urf_file_hdr(1) : make_pwg_file_hdr()); bool verbose = QLoggingCategory::defaultCategory()->isDebugEnabled(); bmp_to_pwg(inBts, outBts, 1, Params, verbose); CurlRequester cr(_url); emit busyMessage(tr("Printing")); OK(cr.write((char*)header.raw(), header.size())); OK(cr.write((char*)fileHdr.raw(), fileHdr.size())); for(size_t c = 0; c < total_pages; c++) { OK(cr.write((char*)(outBts.raw()), outBts.size())); emit progress(c+1, total_pages); } awaitResult(cr, "printRequestFinished"); } qDebug() << "posted"; } void PrinterWorker::convertOfficeDocument(QString filename, Bytestream header, PrintParameters Params) { if(Params.format == PrintParameters::URF && (Params.hwResW != Params.hwResH)) { // URF only supports symmetric resolutions qDebug() << "Unsupported URF resolution"; throw ConvertFailedException(tr("Unsupported resolution (dpi)")); } QString ShortPaperSize; if(CalligraPaperSizes.contains(Params.paperSizeName.c_str())) { ShortPaperSize = CalligraPaperSizes[Params.paperSizeName.c_str()]; } else { qDebug() << "Unsupported PDF paper size" << Params.paperSizeName.c_str(); throw ConvertFailedException(tr("Unsupported PDF paper size")); } QProcess CalligraConverter(this); CalligraConverter.setProgram("calligraconverter"); QStringList CalligraConverterArgs = {"--batch", "--mimetype", Mimer::PDF, "--print-orientation", "Portrait", "--print-papersize", ShortPaperSize}; CalligraConverterArgs << filename; QTemporaryFile tmpPdfFile; tmpPdfFile.open(); CalligraConverterArgs << tmpPdfFile.fileName(); qDebug() << "CalligraConverteArgs is" << CalligraConverterArgs; CalligraConverter.setArguments(CalligraConverterArgs); CalligraConverter.start(); qDebug() << "CalligraConverter Starting"; if(!CalligraConverter.waitForStarted()) { qDebug() << "CalligraConverter died"; throw ConvertFailedException(); } qDebug() << "CalligraConverter Started"; if(!CalligraConverter.waitForFinished(-1)) { qDebug() << "CalligraConverter failed"; throw ConvertFailedException(); } // qDebug() << CalligraConverter->readAllStandardError(); convertPdf(tmpPdfFile.fileName(), header, Params); qDebug() << "posted"; } void PrinterWorker::convertPlaintext(QString filename, Bytestream header, PrintParameters Params) { if(!PaperSizes.contains(Params.paperSizeName.c_str())) { qDebug() << "Unsupported paper size" << Params.paperSizeName.c_str(); throw ConvertFailedException(tr("Unsupported paper size")); } QSizeF size = PaperSizes[Params.paperSizeName.c_str()]; QFile inFile(filename); if(!inFile.open(QIODevice::ReadOnly)) { throw ConvertFailedException(tr("Failed to open file")); } quint32 resolution = std::min(Params.hwResW, Params.hwResH); QTemporaryFile tmpPdfFile; tmpPdfFile.open(); QPdfWriter pdfWriter(tmpPdfFile.fileName()); pdfWriter.setCreator("SeaPrint " SEAPRINT_VERSION); QPageSize pageSize(size, QPageSize::Millimeter); pdfWriter.setPageSize(pageSize); pdfWriter.setResolution(resolution); qreal docHeight = pageSize.sizePixels(resolution).height(); QTextDocument doc; QFont font = QFont("Courier"); font.setPointSizeF(1); qreal charHeight = 0; // Find the optimal font size while(true) { QFont tmpFont = font; tmpFont.setPointSizeF(font.pointSizeF()+0.5); QFontMetricsF qfm(tmpFont, &pdfWriter); charHeight = qfm.lineSpacing(); if(charHeight*66 > docHeight) { break; } font=tmpFont; } QFontMetricsF qfm(font, &pdfWriter); charHeight = qfm.height(); int textHeight = 60*charHeight; qreal margin = ((docHeight-textHeight)/2); qreal mmMargin = margin/(resolution/25.4); doc.setDefaultFont(font); (void)doc.documentLayout(); // wat // Needs to be before painter pdfWriter.setMargins({mmMargin, mmMargin, mmMargin, mmMargin}); QPainter painter(&pdfWriter); doc.documentLayout()->setPaintDevice(painter.device()); doc.setDocumentMargin(margin); // Hack to make the document and pdfWriter margins overlap // Apparently calls to painter.translate() stack... who knew! painter.translate(-margin, -margin); QRectF body = pageSize.rectPixels(resolution); doc.setPageSize(body.size()); QString allText = inFile.readAll(); if(allText.startsWith("\f")) { allText.remove(0, 1); } if(allText.endsWith("\f")) { allText.chop(1); } else if(allText.endsWith("\f\n")) { allText.chop(2); } QStringList pages = allText.split('\f'); bool first = true; int pageCount = 0; for(QString page : pages) { if(!first) { pdfWriter.newPage(); } first = false; if(page.endsWith("\n")) { page.chop(1); } doc.setPlainText(page); int p = 0; // Page number in this document, starting from 0 while(true) { painter.save(); painter.translate(body.left(), body.top() - p*body.height()); QRectF view(0, p*body.height(), body.width(), body.height()); painter.setClipRect(view); QAbstractTextDocumentLayout::PaintContext context; context.clip = view; context.palette.setColor(QPalette::Text, Qt::black); doc.documentLayout()->draw(&painter, context); painter.restore(); p++; pageCount++; if(p >= doc.pageCount()) break; pdfWriter.newPage(); } } painter.end(); convertPdf(tmpPdfFile.fileName(), header, Params); qDebug() << "Finished"; qDebug() << "posted"; } void PrinterWorker::awaitResult(CurlRequester& cr, QString callback) { Bytestream resMsg; CURLcode res = cr.await(&resMsg); QMetaObject::invokeMethod(_printer, callback.toStdString().c_str(), Qt::QueuedConnection, Q_ARG(CURLcode, res), Q_ARG(Bytestream, resMsg)); }