diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8d68911..e9e1d2c 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -14,7 +14,7 @@ jobs: - name: install dependencies run: | sudo apt-get update - sudo apt-get install -y libpoppler-dev libpoppler-glib-dev libcurl4-openssl-dev + sudo apt-get install -y libpoppler-dev libpoppler-glib-dev libcairo2-dev libglib2.0-dev libjpeg-dev libcurl4-openssl-dev zlib1g-dev - name: make run: make -j - name: make tests diff --git a/Makefile b/Makefile index 09fa19c..a5d0a54 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ ippdecode: bytestream.o ippmsg.o ippattr.o ippdecode.o $(CXX) $^ -o $@ ippclient: ippmsg.o ippattr.o ippprintjob.o printparameters.o ippclient.o curlrequester.o minimime.o pdf2printable.o ppm2pwg.o baselinify.o bytestream.o - $(CXX) $^ $(shell pkg-config --libs poppler-glib) -ljpeg -lcurl -o $@ + $(CXX) $^ $(shell pkg-config --libs poppler-glib) -ljpeg -lcurl -lz -o $@ minimime: minimime_main.o minimime.o bytestream.o $(CXX) $^ -o $@ diff --git a/README.md b/README.md index 5012d05..d24dad9 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ An IPP client that harnesses the above tools for converting files to be printed. Install dependencies: -`sudo apt install libpoppler-dev libpoppler-glib-dev libcairo2-dev libglib2.0-dev libjpeg-dev libcurl4-openssl-dev` +`sudo apt install libpoppler-dev libpoppler-glib-dev libcairo2-dev libglib2.0-dev libjpeg-dev libcurl4-openssl-dev zlib1g-dev` Build: diff --git a/lib/curlrequester.cpp b/lib/curlrequester.cpp index 2c4e99c..ea37da8 100644 --- a/lib/curlrequester.cpp +++ b/lib/curlrequester.cpp @@ -42,9 +42,7 @@ void CurlRequester::doRun() curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &buf); curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, write_callback); - CURLcode res = curl_easy_perform(_curl); - - _result = res; + _result = curl_easy_perform(_curl); _resultMsg = buf; }); } @@ -75,6 +73,7 @@ bool CurlIppPosterBase::write(const void* data, size_t size) memcpy(_data, data, size); _size = size; _offset = 0; + _compression = _nextCompression; _canRead.unlock(); return true; } @@ -103,24 +102,70 @@ size_t CurlIppPosterBase::requestWrite(char* dest, size_t size) if(!_reading) { _canRead.lock(); - if(_done) - { - return 0; - } _reading = true; } - size_t actualSize = std::min(size, (_size - _offset)); + size_t remaining = (_size - _offset); + size_t bytesWritten = 0; - memcpy(dest, (_data+_offset), actualSize); - _offset += actualSize; + if(_compression != Compression::None) + { + _zstrm.next_out = (Bytef*)dest; + _zstrm.avail_out = size; + _zstrm.next_in = (_data + _offset); + _zstrm.avail_in = remaining; + deflate(&_zstrm, _done ? Z_FINISH : Z_NO_FLUSH); + _offset += (remaining - _zstrm.avail_in); + bytesWritten = size - _zstrm.avail_out; + } + else // No compression = memcpy + { + bytesWritten = std::min(size, remaining); + memcpy(dest, (_data+_offset), bytesWritten); + _offset += bytesWritten; + } - if(_offset == _size) + if(!_done) { - _reading = false; - _canWrite.unlock(); + if(_offset == _size) + { // End of input + _reading = false; + _canWrite.unlock(); + } + if(bytesWritten == 0) + { // Compression produced no output this time, + // we'll most likely have gone to write mode, + // keep waiting. + return requestWrite(dest, size); + } } - return actualSize; + + return bytesWritten; +} + +void CurlIppPosterBase::setCompression(Compression compression) +{ + if(_nextCompression != Compression::None) + { + throw std::logic_error("unsetting/changing compression"); + } + _nextCompression = compression; + + _zstrm.zalloc = Z_NULL; + _zstrm.zfree = Z_NULL; + _zstrm.opaque = Z_NULL; + + int level = 11; + if(compression == Compression::Deflate) + { + level *= -1; + } + else if(compression == Compression::Gzip) + { + level += 16; + } + + deflateInit2(&_zstrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, level, 7, Z_DEFAULT_STRATEGY); } CurlIppPosterBase::CurlIppPosterBase(std::string addr, bool ignoreSslErrors, bool verbose) @@ -130,6 +175,7 @@ CurlIppPosterBase::CurlIppPosterBase(std::string addr, bool ignoreSslErrors, boo _canRead.lock(); curl_easy_setopt(_curl, CURLOPT_POST, 1L); + curl_easy_setopt(_curl, CURLOPT_UPLOAD_BUFFERSIZE, 2*1024*1024); curl_easy_setopt(_curl, CURLOPT_READFUNCTION, trampoline); curl_easy_setopt(_curl, CURLOPT_READDATA, this); @@ -146,6 +192,9 @@ CURLcode CurlIppPosterBase::await(Bytestream* data) } _done = true; + _data = Array(0); + _size = 0; + _offset = 0; _canRead.unlock(); return CurlRequester::await(data); } diff --git a/lib/curlrequester.h b/lib/curlrequester.h index 19845b5..8e7116a 100644 --- a/lib/curlrequester.h +++ b/lib/curlrequester.h @@ -6,11 +6,19 @@ #endif #include +#include #include "lthread.h" #include #include #include +enum class Compression +{ + None = 0, + Deflate, + Gzip +}; + class CurlRequester { public: @@ -64,6 +72,9 @@ class CurlRequester CURL* _curl; struct curl_slist* _opts = NULL; + z_stream _zstrm; + Compression _nextCompression = Compression::None; + Compression _compression = Compression::None; LThread _worker; }; @@ -76,6 +87,8 @@ class CurlIppPosterBase : public CurlRequester bool give(Bytestream& bts); size_t requestWrite(char* dest, size_t size); + void setCompression(Compression compression); + static size_t trampoline(char* dest, size_t size, size_t nmemb, void* userp) { CurlIppPosterBase* cid = (CurlIppPosterBase*)userp; diff --git a/lib/ippprintjob.cpp b/lib/ippprintjob.cpp index cebb54f..2fe68b3 100644 --- a/lib/ippprintjob.cpp +++ b/lib/ippprintjob.cpp @@ -65,6 +65,19 @@ Error IppPrintJob::finalize(std::string inputFormat, int pages) return Error("Failed to determine traget format"); } + if(MiniMime::isPrinterRaster(targetFormat)) + { + List compressionSupported = compression.getSupported(); + if(compressionSupported.contains("gzip")) + { + compression.set("gzip"); + } + else if(compressionSupported.contains("deflate")) + { + compression.set("deflate"); + } + } + if(!printParams.setPaperSize(media.get(printParams.paperSizeName))) { return Error("Invalid paper size name"); @@ -437,12 +450,21 @@ Error IppPrintJob::doPrint(std::string addr, std::string inFile, ConvertFun conv CurlIppStreamer cr(addr, true, verbose); cr.write((char*)(hdr.raw()), hdr.size()); + if(compression.get() == "gzip") + { + cr.setCompression(Compression::Gzip); + } + else if(compression.get() == "deflate") + { + cr.setCompression(Compression::Deflate); + } + WriteFun writeFun([&cr](unsigned char const* buf, unsigned int len) -> bool - { - if(len == 0) - return true; - return cr.write((const char*)buf, len); - }); + { + if(len == 0) + return true; + return cr.write((const char*)buf, len); + }); ProgressFun progressFun([verbose](size_t page, size_t total) -> void { diff --git a/lib/ippprintjob.h b/lib/ippprintjob.h index 34397bb..7f6e843 100644 --- a/lib/ippprintjob.h +++ b/lib/ippprintjob.h @@ -32,6 +32,7 @@ class IppPrintJob ChoiceSetting scaling = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "print-scaling"); ChoiceSetting documentFormat = ChoiceSetting(&_printerAttrs, &opAttrs, IppMsg::MimeMediaType, "document-format"); + ChoiceSetting compression = ChoiceSetting(&_printerAttrs, &opAttrs, IppMsg::Keyword, "compression"); ChoiceSetting mediaType = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "media-type", "media-col"); ChoiceSetting mediaSource = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "media-source", "media-col"); diff --git a/lib/pdf2printable.cpp b/lib/pdf2printable.cpp index e6959cc..b8a9463 100644 --- a/lib/pdf2printable.cpp +++ b/lib/pdf2printable.cpp @@ -104,16 +104,14 @@ Error pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParamet surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, params.getPaperSizeWInPixels(), params.getPaperSizeHInPixels()); - Bytestream fileHdr; if(params.format == PrintParameters::URF) { - fileHdr = make_urf_file_hdr(seq.size()); + outBts = make_urf_file_hdr(seq.size()); } else { - fileHdr = make_pwg_file_hdr(); + outBts = make_pwg_file_hdr(); } - CHECK(writeFun(fileHdr.raw(), fileHdr.size())); } else if(params.format == PrintParameters::PDF) { diff --git a/tests/Makefile b/tests/Makefile index 0d0a15a..f022eb8 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,4 @@ -LDFLAGS = -ldl -Wl,--export-dynamic -lcurl -ljpeg $(shell pkg-config --libs poppler-glib) +LDFLAGS = -ldl -Wl,--export-dynamic -lcurl -ljpeg $(shell pkg-config --libs poppler-glib) -lz CXXFLAGS = -std=c++17 -g -pedantic -Wall -Werror -Wextra -I ../lib -I ../bytestream \ $(shell pkg-config --cflags poppler-glib) TEST_INCLUDES = -I ../lib -I ../bytestream/minitest -I ../bytestream/minitest/tests diff --git a/tests/test.cpp b/tests/test.cpp index 2186eda..64180c7 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1747,6 +1747,7 @@ TEST(finalize) ASSERT(ip.printParams.duplexMode == PrintParameters::TwoSidedLongEdge); ASSERT(ip.printParams.quality == PrintParameters::HighQuality); ASSERT(ip.printParams.colorMode == PrintParameters::Gray8); + ASSERT(ip.compression.get() == ""); // Forget choices ip = IppPrintJob(printerAttrs); @@ -1810,6 +1811,7 @@ TEST(finalize) ASSERT(ip.printParams.hwResH == 600); ASSERT(ip.printParams.backXformMode == PrintParameters::Rotated); ASSERT(ip.printParams.copies == 2); + ASSERT(ip.compression.get() == ""); // Add Postscript, forget settings printerAttrs.set("document-format-supported", @@ -1916,6 +1918,13 @@ TEST(finalize) ASSERT(ip.printParams.format == PrintParameters::PWG); ASSERT(ip.printParams.colorMode == PrintParameters::Gray8); // Since we don't support color + // Support compression, forget choices + printerAttrs.set("compression-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"none", "deflate", "gzip"})); + ip = IppPrintJob(printerAttrs); + ip.finalize("application/pdf", 0); + // gzip has higher priority + ASSERT(ip.compression.get() == "gzip"); + } TEST(additional_formats)