diff --git a/.gitignore b/.gitignore index 01fc7e4..f2b1ac4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ pdf2printable_mad hexdump baselinify baselinify_mad +ippposter diff --git a/Makefile b/Makefile index 3c72152..3dea62a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ $(shell pkg-config --cflags poppler-glib) VPATH = bytestream lib utils -all: ppm2pwg pwg2ppm pdf2printable hexdump baselinify +all: ppm2pwg pwg2ppm pdf2printable hexdump baselinify ippposter pdf2printable_mad.o: pdf2printable.cpp @@ -36,8 +36,8 @@ baselinify: bytestream.o baselinify.o baselinify_main.o baselinify_mad: bytestream.o baselinify_mad.o baselinify_main.o $(CXX) $^ -ldl -o $@ -ippposter: ippposter.o curlrequester.o bytestream.o - $(CXX) $^ -lcurl -o $@ +ippposter: ippmsg.o ippattr.o ippprintjob.o printparameters.o ippposter.o curlrequester.o pdf2printable.o ppm2pwg.o baselinify.o bytestream.o + $(CXX) $^ $(shell pkg-config --libs poppler-glib) -ljpeg -lcurl -o $@ clean: rm -f *.o ppm2pwg pwg2ppm pdf2printable pdf2printable_mad hexdump baselinify baselinify_mad diff --git a/README.md b/README.md index a4c8518..3c3bcb1 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,14 @@ IPP-printers are only required to support baseline-encoded jpeg according to PWG Despite working with in-memory data, it only requires the libjpeg 62.2.0 and not 62.3.0/7.3+ API, so it works on conservative distros. +## ippposter (WIP) +An IPP client that harnesses the above tools for converting files to be printed. + ## Building Install dependencies: -`sudo apt install libpoppler-dev libpoppler-glib-dev libcairo2-dev libglib2.0-dev libjpeg-dev` +`sudo apt install libpoppler-dev libpoppler-glib-dev libcairo2-dev libglib2.0-dev libjpeg-dev libcurl4-openssl-dev` Build: diff --git a/lib/curlrequester.cpp b/lib/curlrequester.cpp index ee0f115..8b5b081 100644 --- a/lib/curlrequester.cpp +++ b/lib/curlrequester.cpp @@ -1,6 +1,5 @@ #include "curlrequester.h" #include -#include #include #include @@ -43,8 +42,6 @@ void CurlRequester::doRun() curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, write_callback); CURLcode res = curl_easy_perform(_curl); - if(res != CURLE_OK) - std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res); _result = res; _resultMsg = buf; @@ -126,7 +123,7 @@ size_t CurlIppPosterBase::requestWrite(char* dest, size_t size) } CurlIppPosterBase::CurlIppPosterBase(std::string addr, bool ignoreSslErrors, bool verbose) - : CurlRequester(addr, ignoreSslErrors, verbose) + : CurlRequester("http"+addr.erase(0,3), ignoreSslErrors, verbose) { _canWrite.unlock(); _canRead.lock(); diff --git a/lib/error.h b/lib/error.h new file mode 100644 index 0000000..62792c9 --- /dev/null +++ b/lib/error.h @@ -0,0 +1,8 @@ +#ifndef ERROR_H +#define ERROR_H + +#include + +typedef std::optional Error; + +#endif // ERROR_H diff --git a/lib/functions.h b/lib/functions.h new file mode 100644 index 0000000..58aeb33 --- /dev/null +++ b/lib/functions.h @@ -0,0 +1,9 @@ +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +#include + +typedef std::function WriteFun; +typedef std::function ProgressFun; + +#endif // FUNCTIONS_H diff --git a/lib/ippattr.cpp b/lib/ippattr.cpp new file mode 100644 index 0000000..1bf5700 --- /dev/null +++ b/lib/ippattr.cpp @@ -0,0 +1,132 @@ +#include "ippattr.h" +#include + +bool IppAttr::isList() const +{ + return is(); +} +IppOneSetOf IppAttr::asList() const +{ + if(isList()) + { + return get(); + } + else + { + return IppOneSetOf {{value()}}; + } +} + +std::ostream& operator<<(std::ostream& os, const IppIntRange& ir) +{ + os << "{\"min\": " << ir.low << ", \"max\": " << ir.high << "}"; + return os; +} +std::ostream& operator<<(std::ostream& os, const IppResolution& res) +{ + os << "{\"x\": " << res.x + << ", \"y\": " << res.y + << ", \"units\": " << +res.units << "}"; + return os; +} +std::ostream& operator<<(std::ostream& os, const IppDateTime& dt) +{ + // 2000-01-02T00:00:57 GMT+0200 + os << "\"" << std::setfill('0') << std::setw(4) << dt.year << "-" + << std::setw(2) << +dt.month << "-" << std::setw(2) << +dt.day + << "T" << std::setw(2) << +dt.hour << ":" + << std::setw(2) << +dt.minutes << ":" << std::setw(2) << +dt.seconds + << "." << std::setw(3) << dt.deciSeconds*100 << " GMT" << dt.plusMinus + << std::setw(2) << +dt.utcHOffset << std::setw(2) << +dt.utcMOffset << "\""; + return os; +} + +std::ostream& operator<<(std::ostream& os, const IppValue& iv) +{ + if(iv.is()) + { + os << "\"" << iv.get() << "\""; + } + else if(iv.is()) + { + os << iv.get(); + } + else if(iv.is()) + { + os << iv.get(); + } + else if(iv.is()) + { + os << iv.get(); + } + else if(iv.is()) + { + os << iv.get(); + } + else if(iv.is()) + { + os << iv.get(); + } + else if(iv.is()) + { + IppOneSetOf oneSet = iv.get(); + os << "[" << oneSet.takeFront(); + for(IppValue iv2 : oneSet) + { + os << ", " << iv2; + } + os << "]"; + } + else if(iv.is()) + { + os << "{"; + IppCollection ippCollection = iv.get(); + IppCollection::const_iterator it = ippCollection.cbegin(); + os << "\"" << it->first << "\": " << it->second; + it++; + for(; it != ippCollection.cend(); it++) + { + os << ", \"" << it->first << "\": " << it->second; + } + os << "}"; + } + else + { + os << "unhandled value"; + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, const IppAttrs& as) +{ + if(as.empty()) + { + os << "{}" << std::endl; + return os; + } + + IppAttrs::const_iterator it = as.cbegin(); + os << "{" + << "\"" << it->first << "\": {\"tag\": " << +it->second.tag() + << ", \"value\": " << it->second.value() << "}"; + it++; + for(; it != as.cend(); it++) + { + os << "," << std::endl + << "\"" << it->first << "\": {\"tag\": " << +it->second.tag() + << ", \"value\": " << it->second.value() << "}"; + } + os << "}" << std::endl; + return os; +} + +bool IppCollection::has(std::string key) const +{ + return find(key) != end(); +} + +void IppCollection::set(std::string key, IppAttr value) +{ + insert_or_assign(key, value); +} diff --git a/lib/ippattr.h b/lib/ippattr.h new file mode 100644 index 0000000..9291ad6 --- /dev/null +++ b/lib/ippattr.h @@ -0,0 +1,153 @@ +#ifndef IPPATTR_H +#define IPPATTR_H + +#include "list.h" +#include +#include +#include +#include +#include +#include "polymorph.h" + +struct IppOneSetOf; +struct IppCollection; +class IppAttr; + +struct IppIntRange +{ + int32_t low = 0; + int32_t high = 0; + + bool operator==(const IppIntRange& other) const + { + return other.low == low && other.high == high; + } +}; + +struct IppResolution +{ + enum Units : uint8_t + { + Invalid=0, + DPI=3, + DPCM=4 + }; + + uint32_t x = 0; + uint32_t y = 0; + uint8_t units = Invalid; + + bool operator==(const IppResolution& other) const + { + return other.units == units && other.x == x && other.y == y; + } +}; + +struct IppDateTime +{ + uint16_t year = 0; + uint8_t month = 0; + uint8_t day = 0; + uint8_t hour = 0; + uint8_t minutes = 0; + uint8_t seconds = 0; + uint8_t deciSeconds = 0; + uint8_t plusMinus = '+'; + uint8_t utcHOffset = 0; + uint8_t utcMOffset = 0; + + bool operator==(const IppDateTime& other) const + { + return other.year == year && + other.month == month && + other.day == day && + other.hour == hour && + other.minutes == minutes && + other.seconds == seconds && + other.deciSeconds == deciSeconds && + other.plusMinus == plusMinus && + other.utcHOffset == utcHOffset && + other.utcMOffset == utcMOffset; + } +}; + +using IppValue = Polymorph; + +struct IppOneSetOf: public List +{ + using List::List; +}; + +struct IppCollection: public std::map +{ + using std::map::map; + bool has(std::string key) const; + void set(std::string key, IppAttr value); +}; + +class IppAttr : public IppValue +{ +public: + bool isList() const ; + IppOneSetOf asList() const; + + template + IppAttr(uint8_t tag, T value) : IppValue(value), _tag(tag) {} + + uint8_t tag() const {return _tag;} + IppValue value() const {return *this;} + +private: + uint8_t _tag; + +}; + +struct IppAttrs: public std::map +{ + using std::map::map; + + bool has(std::string key) const + { + return find(key) != end(); + } + + template + T get(std::string name, T res=T()) const + { + if(find(name) != end()) + { + res = at(name).get(); + } + return res; + } + + void set(std::string key, IppAttr value) + { + insert_or_assign(key, value); + } + + template + List getList(std::string name) const + { + List res; + if(has(name)) + { + for(const IppValue& v : at(name).asList()) + { + res.push_back(v.get()); + } + } + return res; + } +}; + +std::ostream& operator<<(std::ostream& os, const IppIntRange& iv); +std::ostream& operator<<(std::ostream& os, const IppResolution& iv); +std::ostream& operator<<(std::ostream& os, const IppDateTime& iv); +std::ostream& operator<<(std::ostream& os, const IppValue& iv); + +std::ostream& operator<<(std::ostream& os, const IppAttrs& as); + +#endif // IPPATTR_H diff --git a/lib/ippmsg.cpp b/lib/ippmsg.cpp new file mode 100644 index 0000000..0b256f5 --- /dev/null +++ b/lib/ippmsg.cpp @@ -0,0 +1,438 @@ +#include "ippmsg.h" +#include + +uint32_t IppMsg::_reqId=1; + +IppMsg::IppMsg(Bytestream& bts) +{ + uint32_t reqId; + + bts >> _majVsn >> _minVsn >> _opOrStatus >> reqId; + + IppAttrs attrs; + Tag currentAttrType = EndAttrs; + + while(!bts.atEnd()) + { + if(bts.peekU8() <= UnsupportedAttrs) + { + if(currentAttrType == OpAttrs) + { + _opAttrs = attrs; + } + else if (currentAttrType == JobAttrs) + { + _jobAttrs.push_back(attrs); + } + else if (currentAttrType == PrinterAttrs) + { + _printerAttrs = attrs; + } + else if (currentAttrType == UnsupportedAttrs) + { + std::cerr << "WARNING: unsupported attrs reported: " << std::endl + << attrs; + } + + if(bts >>= EndAttrs) + { + break; + } + + currentAttrType = (Tag)bts.getU8(); + attrs = IppAttrs(); + } + else + { + consumeAttributes(attrs, bts); + } + } +} + +IppMsg::IppMsg(uint16_t opOrStatus, IppAttrs opAttrs, IppAttrs jobAttrs, + uint8_t majVsn, uint8_t minVsn, IppAttrs printerAttrs) +{ + _opOrStatus = opOrStatus; + _majVsn = majVsn; + _minVsn = minVsn; + _opAttrs = opAttrs; + _jobAttrs = {jobAttrs}; + _printerAttrs = printerAttrs; +} + +Bytestream IppMsg::encode() const +{ + Bytestream ipp; + ipp << _majVsn << _minVsn; + ipp << _opOrStatus; + ipp << _reqId++; + ipp << OpAttrs; + + IppAttrs opAttrs = _opAttrs; + + // attributes-charset and attributes-natural-language are required to be first + // some printers fail if the other mandatory parameters are not in this specific order + std::list InitialAttrs = {"attributes-charset", + "attributes-natural-language", + "printer-uri", + "job-id", + "requesting-user-name"}; + for(const std::string& key : InitialAttrs) + { + if(opAttrs.has(key)) + { + IppAttr val = opAttrs.at(key); + encodeAttribute(ipp, key, val); + opAttrs.erase(key); + } + } + encodeAttributes(ipp, opAttrs); + for(IppAttrs attrs : _jobAttrs) + { + if (!attrs.empty()) { + ipp << JobAttrs; + encodeAttributes(ipp, attrs); + } + } + if (!_printerAttrs.empty()) { + ipp << PrinterAttrs; + encodeAttributes(ipp, _printerAttrs); + } + ipp << EndAttrs; + return ipp; +} + +void IppMsg::setOpAttr(std::string name, IppAttr attr) +{ + _opAttrs.insert({name, attr}); +} + +IppAttrs IppMsg::baseOpAttrs(std::string url) +{ + std::string name = getenv("USER"); + IppAttrs o + { + {"attributes-charset", IppAttr(IppMsg::Charset, "utf-8")}, + {"attributes-natural-language", IppAttr(IppMsg::NaturalLanguage, "en-us")}, + {"printer-uri", IppAttr(IppMsg::Uri, url)}, + {"requesting-user-name", IppAttr(IppMsg::NameWithoutLanguage, name)}, + }; + return o; +} + +IppValue IppMsg::decodeValue(uint8_t tag, Bytestream& data) const +{ + uint16_t tmpLen; + + switch (tag) + { + case OpAttrs: + case JobAttrs: + case EndAttrs: + case PrinterAttrs: + case UnsupportedAttrs: + throw std::logic_error("Unexpected tag"); + case Integer: + case Enum: + { + uint32_t tmp_u32=0; + if(!(data >>= (uint16_t)0)) + { + data >> (uint16_t)4 >> tmp_u32; + } + return IppValue((int)tmp_u32); + } + case Boolean: + { + uint8_t tmp_bool=0; + if(!(data >>= (uint16_t)0)) + { + data >> (uint16_t)1 >> tmp_bool; + } + return IppValue((bool)tmp_bool); + } + case DateTime: + { + IppDateTime tmpDateTime; + if(!(data >>= (uint16_t)0)) + { + data >> (uint16_t)11 >> tmpDateTime.year >> tmpDateTime.month >> tmpDateTime.day + >> tmpDateTime.hour >> tmpDateTime.minutes >> tmpDateTime.seconds >> tmpDateTime.deciSeconds + >> tmpDateTime.plusMinus >> tmpDateTime.utcHOffset >> tmpDateTime.utcMOffset; + } + return IppValue(tmpDateTime); + } + case Resolution: + { + IppResolution tmpResolution; + if(!(data >>= (uint16_t)0)) + { + data >> (uint16_t)9 >> tmpResolution.x >> tmpResolution.y >> tmpResolution.units; + } + return IppValue(tmpResolution); + } + case IntegerRange: + { + IppIntRange tmpIntegerRange; + if(!(data >>= (uint16_t)0)) + { + data >> (uint16_t)8 >> tmpIntegerRange.low >> tmpIntegerRange.high; + } + return IppValue(tmpIntegerRange); + } + case OctetStringUnknown: + case TextWithLanguage: + case NameWithLanguage: + case TextWithoutLanguage: + case NameWithoutLanguage: + case Keyword: + case Uri: + case UriScheme: + case Charset: + case NaturalLanguage: + case MimeMediaType: + default: + { + std::string tmp_str; + data >> tmpLen; + data/tmpLen >> tmp_str; + return IppValue(tmp_str); + } + }; +} + +List IppMsg::getUnnamedAttributes(Bytestream& data) const +{ + uint8_t tag; + List attrs; + while(data.remaining()) + { + data >> tag; + if(data >>= (uint16_t)0) + { + attrs.push_back(IppAttr(tag, decodeValue(tag, data))); + } + else + { + data -= 1; + break; + } + } + return attrs; +} + +IppValue IppMsg::collectAttributes(List& attrs) const +{ + IppOneSetOf resArr; + IppCollection resVal; + while(!attrs.empty()) + { + IppAttr tmpval = attrs.takeFront(); + uint8_t tag = tmpval.tag(); + if(tag == MemberName) + { + std::string key = tmpval.get(); + tmpval = attrs.takeFront(); + if(tmpval.tag() == BeginCollection) + { + resVal.insert({key, IppAttr(BeginCollection, collectAttributes(attrs))}); + } + else + { // This should be general data attributes + IppOneSetOf restOfSet; + while(attrs.front().tag() == tmpval.tag()) + { + restOfSet.push_back(attrs.takeFront().value()); + } + + if(restOfSet.empty()) + { + resVal.insert({key, tmpval}); + } + else + { + restOfSet.push_front(tmpval.value()); + tmpval = IppAttr(tmpval.tag(), restOfSet); + resVal.insert({key, tmpval}); + } + } + } + else if(tag == EndCollection) + { + resArr.push_back(resVal); + resVal = IppCollection(); + if(attrs.empty()) + { // end of collection + break; + } + else if(attrs.front().tag() == BeginCollection) + { // this is a 1setOf + attrs.pop_front(); + continue; + } + else + { // the following attribute(s) belong to an outer collection + break; + } + } + else + { + std::cerr << "out of sync with collection" << tmpval.tag(); + } + } + + if(resArr.size()==1) + { // The code above unconditionally produces arrays, collapse if they are just a single object + return resArr.front(); + } + else + { + return resArr; + } +} + +std::string IppMsg::consumeAttributes(IppAttrs& attrs, Bytestream& data) const +{ + uint8_t tag; + uint16_t tmpLen; + std::string name; + + data >> tag >> tmpLen; + data/tmpLen >> name; + + IppValue value = decodeValue(tag, data); + List unnamed = getUnnamedAttributes(data); + + if(tag == BeginCollection) + { + value = collectAttributes(unnamed); + } + + if(!unnamed.empty()) + { + IppOneSetOf tmpOneSet; + tmpOneSet.push_back(value); + for(IppAttr a : unnamed) + { + tmpOneSet.push_back(a.value()); + } + attrs.insert({name, IppAttr(tag, tmpOneSet)}); + } + else + { + attrs.insert({name, IppAttr(tag, value)}); + } + return name; +} + +void IppMsg::encodeAttributes(Bytestream& msg, const IppAttrs& attrs) const +{ + for(std::pair attr : attrs) + { + encodeAttribute(msg, attr.first, attr.second); + } +} + +void IppMsg::encodeAttribute(Bytestream& msg, std::string name, IppAttr attr, bool subCollection) const +{ + uint8_t tag = attr.tag(); + if(subCollection) + { + msg << MemberName << (uint16_t)0 << (uint16_t)name.length() << name; + name = ""; + } + + msg << tag << uint16_t(name.length()) << name; + + if(attr.value().is()) + { + IppOneSetOf oneSet = attr.get(); + encodeValue(msg, tag, oneSet.takeFront()); + while(!oneSet.empty()) + { + msg << tag << uint16_t(0); + encodeValue(msg, tag, oneSet.takeFront()); + } + } + else + { + encodeValue(msg, attr.tag(), attr.value()); + } +} + +void IppMsg::encodeValue(Bytestream& msg, uint8_t tag, IppValue val) const +{ + switch(tag) + { + case OpAttrs: + case JobAttrs: + case EndAttrs: + case PrinterAttrs: + throw std::logic_error("Unexpected tag"); + case Integer: + case Enum: + { + uint32_t tmp_u32 = val.get(); + msg << (uint16_t)4 << tmp_u32; + break; + } + case Boolean: + { + uint8_t tmp_u8 = val.get(); + msg << (uint16_t)1 << tmp_u8; + break; + } + case DateTime: + { + IppDateTime tmpDateTime = val.get(); + msg << (uint16_t)11 << tmpDateTime.year << tmpDateTime.month << tmpDateTime.day + << tmpDateTime.hour << tmpDateTime.minutes << tmpDateTime.seconds << tmpDateTime.deciSeconds + << tmpDateTime.plusMinus << tmpDateTime.utcHOffset << tmpDateTime.utcMOffset; + break; + } + case Resolution: + { + IppResolution tmpRes = val.get(); + msg << (uint16_t)9 << tmpRes.x << tmpRes.y << tmpRes.units; + break; + } + case IntegerRange: + { + IppIntRange tmpRange = val.get(); + msg << (uint16_t)8 << tmpRange.low << tmpRange.high; + break; + } + case BeginCollection: + { + msg << (uint16_t)0; // length of value + IppCollection collection = val.get(); + for(auto const& x : collection) + { + encodeAttribute(msg, x.first, x.second, true); + } + msg << EndCollection << (uint16_t)0 << (uint16_t)0; + break; + } + case OctetStringUnknown: + case TextWithLanguage: + case NameWithLanguage: + case TextWithoutLanguage: + case NameWithoutLanguage: + case Keyword: + case Uri: + case UriScheme: + case Charset: + case NaturalLanguage: + case MimeMediaType: + { + std::string tmpstr = val.get(); + msg << uint16_t(tmpstr.length()); + msg.putBytes(tmpstr.data(), tmpstr.length()); + break; + } + default: + std::cerr << "uncaught tag " << tag; + throw std::logic_error("Uncaught tag"); + break; + } +} diff --git a/lib/ippmsg.h b/lib/ippmsg.h new file mode 100644 index 0000000..37b6102 --- /dev/null +++ b/lib/ippmsg.h @@ -0,0 +1,109 @@ +#ifndef IPPMSG_H +#define IPPMSG_H + +#include +#include +#include +#include +#include "ippattr.h" + +class IppMsg +{ +public: + + enum Operation : uint16_t + { + PrintJob = 0x0002, + PrintUri = 0x0003, + ValidateJob = 0x0004, + CreateJob = 0x0005, + SendDocument = 0x0006, + SendUri = 0x0007, + CancelJob = 0x0008, + GetJobAttrs = 0x0009, + GetJobs = 0x000A, + GetPrinterAttrs = 0x000B, + HoldJob = 0x000C, + ReleaseJob = 0x000D, + RestartJob = 0x000E, + PausePrinter = 0x0010, + ResumePrinter = 0x0011, + PurgeJobs = 0x0012, + IdentifyPrinter = 0x003C + }; + + enum Tag : uint8_t + { + OpAttrs = 0x01, + JobAttrs = 0x02, + EndAttrs = 0x03, + PrinterAttrs = 0x04, + UnsupportedAttrs = 0x05, + Unsupported = 0x10, + Integer = 0x21, + Boolean = 0x22, + Enum = 0x23, + OctetStringUnknown = 0x30, + DateTime = 0x31, + Resolution = 0x32, + IntegerRange = 0x33, + BeginCollection = 0x34, + TextWithLanguage = 0x35, + NameWithLanguage = 0x36, + EndCollection = 0x37, + TextWithoutLanguage = 0x41, + NameWithoutLanguage = 0x42, + Keyword = 0x44, + Uri = 0x45, + UriScheme = 0x46, + Charset = 0x47, + NaturalLanguage = 0x48, + MimeMediaType = 0x49, + MemberName = 0x4A + }; + + IppMsg() = delete; + IppMsg(Bytestream& msg); + IppMsg(uint16_t opOrStatus, IppAttrs opAttrs, IppAttrs jobAttrs=IppAttrs(), + uint8_t majVsn=1, uint8_t minVsn=1, IppAttrs printerAttrs=IppAttrs()); + IppMsg(const IppMsg& other) = default; + ~IppMsg() = default; + + IppAttrs getPrinterAttrs() const {return _printerAttrs;} + std::list getJobAttrs() const {return _jobAttrs;} + IppAttrs getOpAttrs() const {return _opAttrs;} + uint16_t getStatus() const {return _opOrStatus;} + + Bytestream encode() const; + void setOpAttr(std::string name, IppAttr attr); + + static IppAttrs baseOpAttrs(std::string url); + + static void setReqId(uint32_t reqId) + { + _reqId = reqId; + } + +private: + IppValue decodeValue(uint8_t tag, Bytestream& data) const; + List getUnnamedAttributes(Bytestream& data) const; + IppValue collectAttributes(List& attrs) const; + std::string consumeAttributes(IppAttrs& attrs, Bytestream& data) const; + void encodeAttributes(Bytestream& msg, const IppAttrs& attrs) const; + void encodeAttribute(Bytestream& msg, std::string name, IppAttr attr, bool subCollection=false) const; + void encodeValue(Bytestream& msg, uint8_t tag, IppValue val) const; + + uint16_t _opOrStatus; + + uint8_t _majVsn; + uint8_t _minVsn; + + IppAttrs _opAttrs; + std::list _jobAttrs; + IppAttrs _printerAttrs; + + static uint32_t _reqId; + +}; + +#endif // IPPMSG_H diff --git a/lib/ippprintjob.cpp b/lib/ippprintjob.cpp new file mode 100644 index 0000000..6e3120c --- /dev/null +++ b/lib/ippprintjob.cpp @@ -0,0 +1,490 @@ +#include "ippprintjob.h" +#include "mediaposition.h" +#include "curlrequester.h" + +#include + +inline bool startsWith(std::string s, std::string start) +{ + if(start.length() <= s.length()) + { + return s.substr(0, start.length()) == start; + } + return false; +} + +inline bool contains(std::string s, std::string what) +{ + return s.find(what) != std::string::npos; +} + +List IppPrintJob::additionalDocumentFormats() +{ + List additionalFormats; + std::string printerDeviceId = _printerAttrs.get("printer-device-id"); + if(!documentFormat.getSupported().contains(PDF) && + contains(printerDeviceId, "PDF")) + { + additionalFormats.push_back(PDF); + } + if(!documentFormat.getSupported().contains(Postscript) && + (contains(printerDeviceId, "POSTSCRIPT") || contains(printerDeviceId, "PostScript"))) + { + additionalFormats.push_back(Postscript); + } + if(!documentFormat.getSupported().contains(PWG) && + contains(printerDeviceId, "PWG")) + { + additionalFormats.push_back(PWG); + } + if(!documentFormat.getSupported().contains(URF) && + (contains(printerDeviceId, "URF") || contains(printerDeviceId, "AppleRaster"))) + { + additionalFormats.push_back(URF); + } + return additionalFormats; +} + +Error IppPrintJob::finalize(std::string inputFormat, int pages) +{ + // handle additional formats + + targetFormat = determineTargetFormat(inputFormat); + // Only set if regular supported format - else set OctetSteam + if(documentFormat.getSupported().contains(targetFormat)) + { + documentFormat.set(targetFormat); + } + else + { + documentFormat.set(OctetStream); + } + + if(targetFormat == "" || targetFormat == OctetStream) + { + return Error("Failed to determine traget format"); + } + + if(!printParams.setPaperSize(media.get(printParams.paperSizeName))) + { + return Error("Invalid paper size name"); + } + + // Only keep margin setting for image formats + if(!isImage(targetFormat)) + { + margins.top = topMargin.get(); + margins.bottom = bottomMargin.get(); + margins.left = leftMargin.get(); + margins.right = rightMargin.get(); + + topMargin.unset(); + bottomMargin.unset(); + leftMargin.unset(); + rightMargin.unset(); + } + + if(jobAttrs.has("media-col") && media.isSet()) + { + int x = printParams.getPaperSizeWInMillimeters()*100; + int y = printParams.getPaperSizeHInMillimeters()*100; + + IppCollection dimensions {{"x-dimension", IppAttr(IppMsg::Integer, x)}, + {"y-dimension", IppAttr(IppMsg::Integer, y)}}; + + IppCollection mediaCol = jobAttrs.get("media-col"); + mediaCol.set("media-size", IppAttr(IppMsg::BeginCollection, dimensions)); + jobAttrs.set("media-col", IppAttr(IppMsg::BeginCollection, mediaCol)); + media.unset(); + } + + if(inputFormat == PDF) + { + if(targetFormat == PDF) + { + printParams.format = PrintParameters::PDF; + } + else if(targetFormat == Postscript) + { + printParams.format = PrintParameters::Postscript; + } + else if(targetFormat == PWG) + { + printParams.format = PrintParameters::PWG; + } + else if(targetFormat == URF) + { + printParams.format = PrintParameters::URF; + } + else + { + printParams.format = PrintParameters::Invalid; + } + } + else + { + printParams.format = PrintParameters::Invalid; + } + + IppResolution ippResolution = resolution.get({printParams.hwResW, printParams.hwResH, IppResolution::DPI}); + printParams.hwResW = ippResolution.x; + printParams.hwResH = ippResolution.y; + + // Effected locally for PDF (and upstream formats) + if(inputFormat == PDF) + { + for(IppIntRange range : jobAttrs.getList("page-ranges")) + { + printParams.pageRangeList.push_back({range.low, range.high}); + } + pageRanges.unset(); + } + + adjustRasterSettings(pages); + + if(sides.get() == "two-sided-long-edge") + { + printParams.duplexMode = PrintParameters::TwoSidedLongEdge; + } + else if(sides.get() == "two-sided-short-edge") + { + printParams.duplexMode = PrintParameters::TwoSidedShortEdge; + } + + switch (printQuality.get()) + { + case 3: + printParams.quality = PrintParameters::DraftQuality; + break; + case 4: + printParams.quality = PrintParameters::NormalQuality; + break; + case 5: + printParams.quality = PrintParameters::HighQuality; + break; + default: + printParams.quality = PrintParameters::DefaultQuality; + break; + } + + bool supportsColor = colorMode.getSupported().contains("color"); + + printParams.colorMode = colorMode.get().find("color") != std::string::npos ? PrintParameters::sRGB24 + : colorMode.get().find("monochrome") != std::string::npos + || !supportsColor ? PrintParameters::Gray8 + : printParams.colorMode; + + std::map MediaPositionMap MEDIA_POSITION_MAP; + if(mediaSource.isSet()) + { + printParams.mediaPosition = MediaPositionMap.at(mediaSource.get()); + } + printParams.mediaType = mediaType.get(); + + return Error(); +} + +std::string IppPrintJob::determineTargetFormat(std::string inputFormat) +{ + std::string targetFormat = documentFormat.get(OctetStream); + bool canConvert = Pipelines.find({inputFormat, targetFormat}) != Pipelines.end(); + + if(!documentFormat.isSet() && !canConvert) + { // User made no choice, and we don't know the target format - treat as if auto + targetFormat = OctetStream; + } + + if(targetFormat == OctetStream) + { + List supportedFormats = documentFormat.getSupported(); + supportedFormats += additionalDocumentFormats(); + + if(supportedFormats.contains(inputFormat)) + { // If we have a supported format in, assume we can send it as-is + targetFormat = inputFormat; + } + for(const std::pair& p : Pipelines) + { // Try to find a convert-pipeline (preferred over sending as-is) + if(p.first.first == inputFormat && supportedFormats.contains(p.first.second)) + { + targetFormat = p.first.second; + break; + } + } + } + return targetFormat; +} + +bool IppPrintJob::isImage(std::string format) +{ + return startsWith(format, "image/") && format != URF && format != PWG; +} + +void IppPrintJob::adjustRasterSettings(int pages) +{ + if(printParams.format != PrintParameters::PWG && printParams.format != PrintParameters::URF) + { + return; + } + + resolution.unset(); + + if(printParams.format == PrintParameters::PWG) + { + uint32_t diff = std::numeric_limits::max(); + uint32_t AdjustedHwResX = printParams.hwResW; + uint32_t AdjustedHwResY = printParams.hwResH; + + for(IppResolution res : _printerAttrs.getList("pwg-raster-document-resolution-supported")) + { + if(res.units != IppResolution::DPI) + { + continue; + } + uint32_t tmpDiff = std::abs(int(printParams.hwResW-res.x)) + std::abs(int(printParams.hwResH-res.y)); + if(tmpDiff < diff) + { + diff = tmpDiff; + AdjustedHwResX = res.x; + AdjustedHwResY = res.y; + } + } + printParams.hwResW = AdjustedHwResX; + printParams.hwResH = AdjustedHwResY; + } + else if(printParams.format == PrintParameters::URF) + { // Ensure symmetric resolution for URF + printParams.hwResW = printParams.hwResH = std::min(printParams.hwResW, printParams.hwResH); + + uint32_t diff = std::numeric_limits::max(); + uint32_t AdjustedHwRes = printParams.hwResW; + List urfSupported = _printerAttrs.getList("urf-supported"); + + for(const std::string& us : urfSupported) + { + if(startsWith(us, "RS")) + { //RS300[-600] + std::string rs = us.substr(2); + size_t pos = 0; + while(pos <= rs.length()) + { + size_t found = std::min(rs.length(), rs.find("-", pos)); + std::string substr(rs, pos, (found-pos)); + pos = found+1; + + int intRes = std::stoi(substr); + uint32_t tmpDiff = std::abs(int(printParams.hwResW - intRes)); + if(tmpDiff < diff) + { + diff = tmpDiff; + AdjustedHwRes = intRes; + } + } + printParams.hwResW = printParams.hwResH = AdjustedHwRes; + break; + } + } + } + + if(printParams.format == PrintParameters::PWG) + { + std::string DocumentSheetBack = _printerAttrs.get("pwg-raster-document-sheet-back"); + if(DocumentSheetBack=="flipped") + { + printParams.backXformMode=PrintParameters::Flipped; + } + else if(DocumentSheetBack=="rotated") + { + printParams.backXformMode=PrintParameters::Rotated; + } + else if(DocumentSheetBack=="manual-tumble") + { + printParams.backXformMode=PrintParameters::ManualTumble; + } + } + else if(printParams.format == PrintParameters::URF) + { + List urfSupported = _printerAttrs.getList("urf-supported"); + if(urfSupported.contains("DM2")) + { + printParams.backXformMode=PrintParameters::Flipped; + } + else if(urfSupported.contains("DM3")) + { + printParams.backXformMode=PrintParameters::Rotated; + } + else if(urfSupported.contains("DM4")) + { + printParams.backXformMode=PrintParameters::ManualTumble; + } + } + + int copiesRequested = copies.get(1); + // Actual (non-support) value can be 1, non-presense will be 0 + bool supportsCopies = _printerAttrs.get("copies-supported").high > 1; + + List varyingAttributes = _printerAttrs.getList("document-format-varying-attributes"); + if(varyingAttributes.contains("copies") || varyingAttributes.contains("copies-supported")) + { + supportsCopies = false; + } + + if(copiesRequested > 1 && !supportsCopies) + { + std::string copyMode = multipleDocumentHandling.get(); + printParams.copies = copiesRequested; + if(copyMode == "separate-documents-uncollated-copies") + { // Only do silly copies if explicitly requested + printParams.collatedCopies = false; + } + copies.unset(); + + if(sides.get() != "one-sided") + { + bool singlePageRange = false; + if(printParams.pageRangeList.size() == 1) + { + size_t fromPage = printParams.pageRangeList.begin()->first; + size_t toPage = printParams.pageRangeList.begin()->second; + singlePageRange = fromPage != 0 && fromPage == toPage; + } + if(pages == 1 || singlePageRange) + { + sides.set("one-sided"); + } + } + } +} + +Error IppPrintJob::run(std::string addr, std::string inFile, std::string inFormat, bool verbose) +{ + Error error; + try + { + List supportedOperations = _printerAttrs.getList("operations-supported"); + std::string fileName = std::filesystem::path(inFile).filename(); + + error = finalize(inFormat); + if(error) + { + return error; + } + + ConvertFun convertFun; + + std::map::iterator pit = + Pipelines.find(ConvertKey {inFormat, targetFormat}); + + if(pit != Pipelines.end()) + { + convertFun = pit->second; + } + else + { + return Error("No conversion method found for " + inFormat + " to " + targetFormat); + } + + if(supportedOperations.contains(IppMsg::CreateJob) && supportedOperations.contains(IppMsg::SendDocument)) + { + IppAttrs createJobOpAttrs = IppMsg::baseOpAttrs(addr); + createJobOpAttrs.set("job-name", IppAttr {IppMsg::NameWithoutLanguage, fileName}); + + IppMsg createJobMsg(IppMsg::CreateJob, createJobOpAttrs, jobAttrs); + CurlIppPoster createJobReq(addr, createJobMsg.encode(), true, verbose); + + Bytestream createJobResult; + CURLcode res = createJobReq.await(&createJobResult); + + if(res == CURLE_OK) + { + IppMsg createJobResp(createJobResult); + IppAttrs createJobRespJobAttrs; + if(!createJobResp.getJobAttrs().empty()) + { + createJobRespJobAttrs = createJobResp.getJobAttrs().front(); + } + if(createJobResp.getStatus() <= 0xff && createJobRespJobAttrs.has("job-id")) + { + int jobId = createJobRespJobAttrs.get("job-id"); + IppAttrs sendDocumentOpAttrs = IppMsg::baseOpAttrs(addr); + sendDocumentOpAttrs.insert(opAttrs.begin(), opAttrs.end()); + sendDocumentOpAttrs.set("job-id", IppAttr {IppMsg::Integer, jobId}); + sendDocumentOpAttrs.set("last-document", IppAttr {IppMsg::Boolean, true}); + IppMsg sendDocumentMsg(IppMsg::SendDocument, sendDocumentOpAttrs); + error = doPrint(addr, inFile, convertFun, sendDocumentMsg.encode(), verbose); + } + else + { + error = "Create job failed: " + createJobResp.getOpAttrs().get("status-message", "unknown"); + } + } + else + { + error = std::string("Create job failed: ") + curl_easy_strerror(res); + } + } + else + { + IppAttrs printJobOpAttrs = IppMsg::baseOpAttrs(addr); + printJobOpAttrs.insert(opAttrs.begin(), opAttrs.end()); + printJobOpAttrs.set("job-name", IppAttr {IppMsg::NameWithoutLanguage, fileName}); + IppMsg printJobMsg(IppMsg::PrintJob, printJobOpAttrs, jobAttrs); + error = doPrint(addr, inFile, convertFun, printJobMsg.encode(), verbose); + } + } + catch(const std::exception& e) + { + error = std::string("Exception caught while printing: ") + e.what(); + } + return error; +} + +Error IppPrintJob::doPrint(std::string addr, std::string inFile, ConvertFun convertFun, Bytestream hdr, bool verbose) +{ + Error error; + CurlIppStreamer cr(addr, true, verbose); + cr.write((char*)(hdr.raw()), hdr.size()); + + WriteFun writeFun([&cr](unsigned char const* buf, unsigned int len) -> bool + { + if(len == 0) + return true; + return cr.write((const char*)buf, len); + }); + + ProgressFun progressFun([verbose](size_t page, size_t total) -> void + { + if(verbose) + { + std::cerr << page << "/" << total << std::endl; + } + }); + + convertFun(inFile, writeFun, *this, progressFun, verbose); + + Bytestream result; + CURLcode cres = cr.await(&result); + if(cres == CURLE_OK) + { + IppMsg response(result); + if(response.getStatus() > 0xff) + { + error = "Print job failed: " + response.getOpAttrs().get("status-message", "unknown"); + } + } + else + { + error = curl_easy_strerror(cres); + } + + return error; +} + +const std::string IppPrintJob::OctetStream = "application/octet-stream"; + +const std::string IppPrintJob::PDF = "application/pdf"; +const std::string IppPrintJob::Postscript = "application/postscript"; +const std::string IppPrintJob::PWG = "image/pwg-raster"; +const std::string IppPrintJob::URF = "image/urf"; + +const std::string IppPrintJob::JPEG = "image/jpeg"; diff --git a/lib/ippprintjob.h b/lib/ippprintjob.h new file mode 100644 index 0000000..ddcea05 --- /dev/null +++ b/lib/ippprintjob.h @@ -0,0 +1,119 @@ +#ifndef IPPPARAMETERS_H +#define IPPPARAMETERS_H + +#include "printparameters.h" +#include "pdf2printable.h" +#include "baselinify.h" +#include "ippmsg.h" +#include "binfile.h" +#include "setting.h" +#include "error.h" + +class IppPrintJob +{ +public: + IppPrintJob(IppAttrs printerAttrs) : _printerAttrs(printerAttrs) + {} + + ChoiceSetting sides = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "sides"); + PreferredChoiceSetting media = PreferredChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "media", "ready"); + + IntegerSetting copies = IntegerSetting(&_printerAttrs, &jobAttrs, IppMsg::Integer, "copies"); + ChoiceSetting multipleDocumentHandling = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "multiple-document-handling"); + + IntegerRangeListSetting pageRanges = IntegerRangeListSetting(&_printerAttrs, &jobAttrs, IppMsg::IntegerRange, "page-ranges"); + + IntegerChoiceSetting numberUp = IntegerChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Integer, "number-up"); + + ChoiceSetting colorMode = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "print-color-mode"); + ChoiceSetting printQuality = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Enum, "print-quality"); + ChoiceSetting resolution = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Resolution, "printer-resolution"); + ChoiceSetting scaling = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "print-scaling"); + + ChoiceSetting documentFormat = ChoiceSetting(&_printerAttrs, &opAttrs, IppMsg::MimeMediaType, "document-format"); + + ChoiceSetting mediaType = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "media-type", "media-col"); + ChoiceSetting mediaSource = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "media-source", "media-col"); + ChoiceSetting outputBin = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Keyword, "output-bin"); + + ChoiceSetting topMargin = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Integer, "media-top-margin", "media-col"); + ChoiceSetting bottomMargin = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Integer, "media-bottom-margin", "media-col"); + ChoiceSetting leftMargin = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Integer, "media-left-margin", "media-col"); + ChoiceSetting rightMargin = ChoiceSetting(&_printerAttrs, &jobAttrs, IppMsg::Integer, "media-right-margin", "media-col"); + + List additionalDocumentFormats(); + + IppPrintJob& operator=(const IppPrintJob& other) + { + opAttrs = other.opAttrs; + jobAttrs = other.jobAttrs; + printParams = other.printParams; + _printerAttrs = other._printerAttrs; + targetFormat = other.targetFormat; + margins = other.margins; + return *this; + }; + + typedef std::pair ConvertKey; + typedef std::function ConvertFun; + + Error finalize(std::string inputFormat, int pages=0); + Error run(std::string addr, std::string inFile, std::string inFormat, bool verbose); + Error doPrint(std::string addr, std::string inFile, ConvertFun convertFun, Bytestream hdr, bool verbose); + + ConvertFun Pdf2Printable = [](std::string inFileName, WriteFun writeFun, const IppPrintJob& job, ProgressFun progressFun, bool verbose) + { + return pdf_to_printable(inFileName, writeFun, job.printParams, progressFun, verbose); + }; + + ConvertFun Baselinify = [](std::string inFileName, WriteFun writeFun, const IppPrintJob&, ProgressFun progressFun, bool) + { + InBinFile in(inFileName); + Bytestream inBts(in); + Bytestream baselinified; + baselinify(inBts, baselinified); + writeFun(baselinified.raw(), baselinified.size()); + progressFun(1, 1); + // We'll check on the cURL status in just a bit, so no point in returning errors here. + return Error(); + }; + + std::map Pipelines {{{PDF, PDF}, Pdf2Printable}, + {{PDF, Postscript}, Pdf2Printable}, + {{PDF, PWG}, Pdf2Printable}, + {{PDF, URF}, Pdf2Printable}, + {{JPEG, JPEG}, Baselinify}}; + + IppAttrs opAttrs; + IppAttrs jobAttrs; + PrintParameters printParams; + + std::string targetFormat; + + struct Margins + { + int top = 0; + int bottom = 0; + int left = 0; + int right = 0; + }; + + Margins margins; + +private: + + std::string determineTargetFormat(std::string inputFormat); + bool isImage(std::string format); + void adjustRasterSettings(int pages); + + static const std::string OctetStream; + static const std::string PDF; + static const std::string Postscript; + static const std::string PWG; + static const std::string URF; + static const std::string JPEG; + + IppAttrs _printerAttrs; +}; + +#endif // IPPPARAMETERS_H diff --git a/lib/list.h b/lib/list.h new file mode 100644 index 0000000..1ba4d9a --- /dev/null +++ b/lib/list.h @@ -0,0 +1,38 @@ +#ifndef LIST_H +#define LIST_H + +#include + +template +class List: public std::list +{ +public: + using std::list::list; + + T takeFront() + { + T tmp = std::list::front(); + std::list::pop_front(); + return tmp; + } + + bool contains(const T elem) const + { + for(const T& e : *this) + { + if(e == elem) + { + return true; + } + } + return false; + } + + List& operator+=(const List& other) + { + this->insert(this->cbegin(), other.cbegin(), other.cend()); + return *this; + } +}; + +#endif // LIST_H diff --git a/lib/pdf2printable.cpp b/lib/pdf2printable.cpp index fd4c261..e6959cc 100644 --- a/lib/pdf2printable.cpp +++ b/lib/pdf2printable.cpp @@ -5,8 +5,6 @@ #include #include -#include -#include #include #include #include @@ -14,7 +12,7 @@ #include #include -#include +#include "pointer.h" #include "ppm2pwg.h" #include "pdf2printable.h" @@ -35,7 +33,7 @@ #define PDF_CREATOR "pdf2printable" #endif -#define CHECK(call) if(!(call)) {return 1;} +#define CHECK(call) if(!(call)) {return Error("Write error");} void copy_raster_buffer(Bytestream& bmpBts, uint32_t* data, const PrintParameters& params); @@ -53,13 +51,12 @@ inline double round2(double d) return round(d*100)/100; } -int pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParameters& params, - ProgressFun progressFun, bool verbose) +Error pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParameters& params, + ProgressFun progressFun, bool verbose) { if(params.format == PrintParameters::URF && (params.hwResW != params.hwResH)) - { // URF must have a symmetric resolution - std::cerr << "URF must have a symmetric resolution." << std::endl; - return 1; + { + return Error("URF must have a symmetric resolution."); } #if MADNESS @@ -92,9 +89,9 @@ int pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParameter if(doc == nullptr) { - std::cerr << "Failed to open PDF: " << error->message << " (" << inFile << ")" << std::endl; + std::string errStr(error->message); g_error_free(error); - return 1; + return Error("Failed to open PDF: " + errStr + " (" + inFile + ")"); } size_t pages = poppler_document_get_n_pages(doc); @@ -134,7 +131,7 @@ int pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParameter } else { - return 1; + return Error("Unknown format"); } for(size_t pageNo : seq) @@ -186,8 +183,7 @@ int pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParameter status = cairo_status(cairo); if(status) { - std::cerr << "cairo error: " << cairo_status_to_string(status) << std::endl; - return 1; + return Error(std::string("Cairo error: ") + cairo_status_to_string(status)); } cairo_surface_show_page(surface); @@ -216,7 +212,7 @@ int pdf_to_printable(std::string inFile, WriteFun writeFun, const PrintParameter CHECK(writeFun(outBts.raw(), outBts.size())); } - return 0; + return Error(); } void copy_raster_buffer(Bytestream& bmpBts, uint32_t* data, const PrintParameters& params) diff --git a/lib/pdf2printable.h b/lib/pdf2printable.h index d72afe0..0caa031 100644 --- a/lib/pdf2printable.h +++ b/lib/pdf2printable.h @@ -1,13 +1,11 @@ #ifndef PDF2PRINTABLE_H #define PDF2PRINTABLE_H -#include +#include "error.h" +#include "functions.h" #include "printparameters.h" -typedef std::function WriteFun; -typedef std::function ProgressFun; - -int pdf_to_printable(std::string infile, WriteFun writeFun, const PrintParameters& params, - ProgressFun progressFun = nullptr, bool verbose = false); +Error pdf_to_printable(std::string infile, WriteFun writeFun, const PrintParameters& params, + ProgressFun progressFun = nullptr, bool verbose = false); #endif //PDF2PRINTABLE_H diff --git a/lib/polymorph.h b/lib/polymorph.h new file mode 100644 index 0000000..9ff2a65 --- /dev/null +++ b/lib/polymorph.h @@ -0,0 +1,28 @@ +#ifndef POLYMORPH_H +#define POLYMORPH_H + +#include + +template +class Polymorph : public std::variant +{ +public: + using std::variant::variant; + + template + bool is() const + { + return std::holds_alternative(*this); + } + + template + T get() const + { + return std::get(*this); + } + +private: + +}; + +#endif // POLYMORPH_H diff --git a/lib/printparameters.cpp b/lib/printparameters.cpp index 6cc2901..83dfe73 100644 --- a/lib/printparameters.cpp +++ b/lib/printparameters.cpp @@ -186,7 +186,7 @@ PageSequence PrintParameters::getPageSequence(size_t pages) const return seq; } -bool PrintParameters::setPageRange(const std::string& rangeStr) +PageRangeList PrintParameters::parsePageRange(const std::string& rangeStr) { PageRangeList rangeList; const std::regex single("^([0-9]+)$"); @@ -210,16 +210,22 @@ bool PrintParameters::setPageRange(const std::string& rangeStr) size_t to = stol(match[2]); if(to < from) { - return false; + return {}; } rangeList.push_back({from, to}); } else { - return false; + return {}; } pos = found+1; } + return rangeList; +} + +bool PrintParameters::setPageRange(const std::string& rangeStr) +{ + PageRangeList rangeList = parsePageRange(rangeStr); if(!rangeList.empty()) { diff --git a/lib/printparameters.h b/lib/printparameters.h index 482d58b..e62cf40 100644 --- a/lib/printparameters.h +++ b/lib/printparameters.h @@ -110,8 +110,8 @@ class PrintParameters bool antiAlias = false; std::string paperSizeName = "iso_a4_210x297mm"; - size_t hwResW = 300; - size_t hwResH = 300; + uint32_t hwResW = 300; + uint32_t hwResH = 300; enum PaperSizeUnits { @@ -164,6 +164,7 @@ class PrintParameters PageSequence getPageSequence(size_t pages) const; + static PageRangeList parsePageRange(const std::string& rangeStr); bool setPageRange(const std::string& rangeStr); bool setPaperSize(const std::string& sizeStr); diff --git a/lib/setting.h b/lib/setting.h new file mode 100644 index 0000000..8c0f009 --- /dev/null +++ b/lib/setting.h @@ -0,0 +1,187 @@ +#ifndef SETTING_H +#define SETTING_H + +template +class Setting +{ +public: + Setting() = delete; + Setting& operator=(const Setting& other) = delete; + Setting(IppAttrs* printerAttrs, IppAttrs* attrs, IppMsg::Tag tag, std::string name, std::string subKey = "") + : _printerAttrs(printerAttrs), _attrs(attrs), _tag(tag), _name(name), _subKey(subKey) + {} + virtual bool isSupported() + { + return _printerAttrs->has(_name+"-supported"); + } + T getDefault(T fallback=T()) const + { + return _printerAttrs->get(_name+"-default", fallback); + } + void set(T value) + { + if(_subKey != "") + { + IppCollection col; + if(_attrs->has(_subKey)) + { + col = _attrs->at(_subKey).get(); + } + col.set(_name, IppAttr(_tag, value)); + _attrs->set(_subKey, IppAttr(IppMsg::BeginCollection, col)); + } + else + { + _attrs->set(_name, IppAttr(_tag, value)); + } + } + bool isSet() const + { + if(_subKey != "") + { + return _attrs->has(_subKey) && _attrs->at(_subKey).get().has(_name); + } + else + { + return _attrs->has(_name); + } + } + void unset() + { + if(_subKey != "") + { + IppCollection col; + if(_attrs->has(_subKey)) + { + col = _attrs->at(_subKey).get(); + } + col.erase(_name); + if(col.empty()) + { + _attrs->erase(_subKey); + } + else + { + _attrs->set(_subKey, IppAttr(IppMsg::BeginCollection, col)); + } + } + else + { + _attrs->erase(_name); + } + } + T get(T fallback=T()) const + { + if(isSet()) + { + if(_subKey != "") + { + return _attrs->at(_subKey).get().at(_name).get(); + } + else + { + return _attrs->at(_name).get(); + } + } + else + { + return getDefault(fallback); + } + } +protected: + IppAttrs* _printerAttrs; + IppAttrs* _attrs; + IppMsg::Tag _tag; + std::string _name; + std::string _subKey; +}; + +template +class ChoiceSetting : public Setting +{ +public: + ChoiceSetting(IppAttrs* printerAttrs, IppAttrs* attrs, IppMsg::Tag tag, std::string name, std::string subKey = "") + : Setting(printerAttrs, attrs, tag, name, subKey) + {} + + List getSupported() const + { + IppAttrs* pa = this->_printerAttrs; + return pa->getList(this->_name+"-supported"); + } +}; + +template +class PreferredChoiceSetting : public ChoiceSetting +{ +public: + PreferredChoiceSetting(IppAttrs* printerAttrs, IppAttrs* attrs, IppMsg::Tag tag, std::string name, std::string pref) + : ChoiceSetting(printerAttrs, attrs, tag, name), _pref(pref) + {} + + List getPreferred() const + { + IppAttrs* pa = this->_printerAttrs; + return pa->getList(this->_name+"-"+_pref); + } + +private: + std::string _pref; +}; + +class IntegerSetting : public Setting +{ +public: + IntegerSetting(IppAttrs* printerAttrs, IppAttrs* attrs, IppMsg::Tag tag, std::string name) + : Setting(printerAttrs, attrs, tag, name) + {} + int getSupportedMin() + { + return _printerAttrs->get(this->_name+"-supported").low; + } + int getSupportedMax() + { + return _printerAttrs->get(this->_name+"-supported").high; + } +}; + +class IntegerRangeListSetting : public Setting +{ +public: + IntegerRangeListSetting(IppAttrs* printerAttrs, IppAttrs* attrs, IppMsg::Tag tag, std::string name) + : Setting(printerAttrs, attrs, tag, name) + {} + + bool getSupported() const + { + return _printerAttrs->get(this->_name+"-supported"); + } +}; + +class IntegerChoiceSetting : public Setting +{ +public: + IntegerChoiceSetting(IppAttrs* printerAttrs, IppAttrs* attrs, IppMsg::Tag tag, std::string name) + : Setting(printerAttrs, attrs, tag, name) + {} + List getSupported() const + { + List res; + if(_printerAttrs->has(_name+"-supported") && + _printerAttrs->at(_name+"-supported").is()) + { + IppIntRange range = _printerAttrs->at(_name+"-supported").get(); + for(int i = range.low; i <= range.high; i++) + { + res.push_back(i); + } + } + else + { + res = _printerAttrs->getList(_name+"-supported"); + } + return res; + } +}; + +#endif // SETTING_H diff --git a/tests/Makefile b/tests/Makefile index f785a72..15999bc 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,6 @@ -LDFLAGS = -ldl -Wl,--export-dynamic -CXXFLAGS = -std=c++17 -g -pedantic -Wall -Werror -Wextra -I ../bytestream +LDFLAGS = -ldl -Wl,--export-dynamic -lcurl -ljpeg $(shell pkg-config --libs poppler-glib) +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 VPATH = ../bytestream ../lib @@ -12,7 +13,7 @@ test.o: test.cpp %.o: %.cpp $(CXX) -c $(CXXFLAGS) $< -test: bytestream.o printparameters.o pwg2ppm.o test.o +test: bytestream.o ippprintjob.o curlrequester.o printparameters.o ppm2pwg.o pwg2ppm.o pdf2printable.o baselinify.o ippmsg.o ippattr.o test.o $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@ clean: diff --git a/tests/test.cpp b/tests/test.cpp index edfec63..82faa8c 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -6,6 +6,8 @@ #include "printparameters.h" #include "argget.h" #include "lthread.h" +#include "ippmsg.h" +#include "ippprintjob.h" #include using namespace std; @@ -1230,3 +1232,672 @@ TEST(lthread) proceed = true; ltr.await(); } + +TEST(ippattr) +{ + + IppAttr attr1(IppMsg::BeginCollection, IppCollection{{"key", IppAttr(7, 42)}}); + IppAttr attr2(IppMsg::Keyword, IppOneSetOf {IppValue {42}}); + ASSERT(attr2.isList()); + ASSERT(attr2.get().front().is()); + ASSERT(attr2.asList().front().is()); + ASSERT(attr2.asList().front().get() == 42); + + IppAttr attr3(IppMsg::Enum, 42); + ASSERT_FALSE(attr3.isList()); + ASSERT(attr3.get() == 42); + ASSERT(attr3.asList().front().is()); + ASSERT(attr3.asList().front().get() == 42); + +} + +TEST(ippprintjob_support) +{ + IppAttrs printerAttrs = + {{"sides-default", IppAttr(IppMsg::Keyword, "one-sided")}, + {"sides-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"one-sided", + "two-sided-long-edge", + "two-sided-short-edge"})}, + {"media-default", IppAttr(IppMsg::Keyword, "iso_a4_210x297mm")}, + {"media-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"iso_a4_210x297mm", + "na_letter_8.5x11in"})}, + {"media-ready", IppAttr(IppMsg::Keyword, "iso_a4_210x297mm")}, + {"copies-default", IppAttr(IppMsg::Integer, 1)}, + {"copies-supported", IppAttr(IppMsg::IntegerRange, IppIntRange {1, 999})}, + {"multiple-document-handling-default", IppAttr(IppMsg::Keyword, "separate-documents-uncollated-copies")}, + {"multiple-document-handling-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"single-document", + "separate-documents-uncollated-copies", + "separate-documents-collated-copies"})}, + {"page-ranges-supported", IppAttr(IppMsg::Boolean, false)}, + {"number-up-default", IppAttr(IppMsg::Integer, 1)}, + {"number-up-supported", IppAttr(IppMsg::Integer, IppOneSetOf{1, 2, 4})}, + {"print-color-mode-default", IppAttr(IppMsg::Keyword, "auto")}, + {"print-color-mode-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "color", "monochrome"})}, + {"print-quality-default", IppAttr(IppMsg::Enum, 4)}, + {"print-quality-supported", IppAttr(IppMsg::Enum, IppOneSetOf {3, 4, 5})}, + {"printer-resolution-default", IppAttr(IppMsg::Resolution, IppResolution {600, 600, 3})}, + {"printer-resolution-supported", IppAttr(IppMsg::Resolution, IppOneSetOf {IppResolution {600, 600, 3}, + IppResolution {600, 1200, 3}})}, + {"document-format-default", IppAttr(IppMsg::Keyword, "application/octet-stream")}, + {"document-format-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"application/octet-stream", + "image/urf", + "image/pwg-raster", + "application/pdf"})}, + {"print-scaling-default", IppAttr(IppMsg::Keyword, "auto")}, + {"print-scaling-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "fill", "fit"})}, + {"media-type-default", IppAttr(IppMsg::Keyword, "stationery")}, + {"media-type-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"stationery", "cardstock", "labels"})}, + {"media-source-default", IppAttr(IppMsg::Keyword, "auto")}, + {"media-source-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "envelope", "manual", "tray-1"})}, + {"output-bin-default", IppAttr(IppMsg::Keyword, "face-down")}, + {"output-bin-supported", IppAttr(IppMsg::Keyword, "face-down")}, + + {"media-top-margin-default", IppAttr(IppMsg::Integer, 1)}, + {"media-top-margin-supported", IppAttr(IppMsg::Integer, IppOneSetOf {1, 2, 3, 4})}, + {"media-bottom-margin-default", IppAttr(IppMsg::Integer, 2)}, + {"media-bottom-margin-supported", IppAttr(IppMsg::Integer, IppOneSetOf {2, 3, 4})}, + {"media-left-margin-default", IppAttr(IppMsg::Integer, 3)}, + {"media-left-margin-supported", IppAttr(IppMsg::Integer, IppOneSetOf {3, 4})}, + {"media-right-margin-default", IppAttr(IppMsg::Integer, 4)}, + {"media-right-margin-supported", IppAttr(IppMsg::Integer, 4)}, + }; + + IppMsg getAttrsMsg(0, IppAttrs(), IppAttrs(), 1, 1, printerAttrs); + Bytestream encoded = getAttrsMsg.encode(); + IppMsg msg2(encoded); + + ASSERT(printerAttrs == msg2.getPrinterAttrs()); + + encoded -= encoded.size(); + IppMsg::setReqId(1); // Revert automatic global incrementation of ReqId. + Bytestream encoded2 = msg2.encode(); + ASSERT(encoded2 == encoded); + + IppPrintJob ip(printerAttrs); + + ASSERT(ip.sides.isSupported()); + ASSERT(ip.sides.getDefault() == "one-sided"); + ASSERT(ip.sides.getSupported() == List({"one-sided", + "two-sided-long-edge", + "two-sided-short-edge"})); + + ASSERT(ip.media.isSupported()); + ASSERT(ip.media.getDefault() == "iso_a4_210x297mm"); + ASSERT(ip.media.getSupported() == List({"iso_a4_210x297mm", "na_letter_8.5x11in"})); + // A single choice was not wrapped in OneSetOf, but still returned as a list. + ASSERT(ip.media.getPreferred() == List({"iso_a4_210x297mm"})); + + ASSERT(ip.copies.isSupported()); + ASSERT(ip.copies.getDefault() == 1); + ASSERT(ip.copies.getSupportedMin() == 1); + ASSERT(ip.copies.getSupportedMax() == 999); + + ASSERT(ip.multipleDocumentHandling.isSupported()); + ASSERT(ip.multipleDocumentHandling.getDefault() == "separate-documents-uncollated-copies"); + ASSERT(ip.multipleDocumentHandling.getSupported() == List({"single-document", + "separate-documents-uncollated-copies", + "separate-documents-collated-copies"})); + + ASSERT(ip.pageRanges.isSupported()); + ASSERT(ip.pageRanges.getSupported() == false); + + ASSERT(ip.numberUp.isSupported()); + ASSERT(ip.numberUp.getDefault() == 1); + ASSERT(ip.numberUp.getSupported() == List({1, 2, 4})); + + // number-up-supported may be a range too... + printerAttrs.set("number-up-supported", IppAttr(IppMsg::IntegerRange, IppIntRange {1, 4})); + + ip = IppPrintJob(printerAttrs); + + ASSERT(ip.numberUp.isSupported()); + ASSERT(ip.numberUp.getDefault() == 1); + ASSERT(ip.numberUp.getSupported() == List({1, 2, 3, 4})); + + ASSERT(ip.colorMode.isSupported()); + ASSERT(ip.colorMode.getDefault() == "auto"); + ASSERT(ip.colorMode.getSupported() == List({"auto", "color", "monochrome"})); + + ASSERT(ip.printQuality.isSupported()); + ASSERT(ip.printQuality.getDefault() == 4); + ASSERT(ip.printQuality.getSupported() == List({3, 4, 5})); + + ASSERT(ip.resolution.isSupported()); + ASSERT(ip.resolution.getDefault() == IppResolution({600, 600, 3})); + ASSERT(ip.resolution.getSupported() == List({{600, 600, 3}, {600, 1200, 3}})); + + ASSERT(ip.scaling.isSupported()); + ASSERT(ip.scaling.getDefault() == "auto"); + ASSERT(ip.scaling.getSupported() == List({"auto", "fill", "fit"})); + + ASSERT(ip.documentFormat.isSupported()); + ASSERT(ip.documentFormat.getDefault() == "application/octet-stream"); + ASSERT(ip.documentFormat.getSupported() == List({"application/octet-stream", + "image/urf", + "image/pwg-raster", + "application/pdf"})); + + ASSERT(ip.mediaType.isSupported()); + ASSERT(ip.mediaType.getDefault() == "stationery"); + ASSERT(ip.mediaType.getSupported() == List({"stationery", "cardstock", "labels"})); + + ASSERT(ip.mediaSource.isSupported()); + ASSERT(ip.mediaSource.getDefault() == "auto"); + ASSERT(ip.mediaSource.getSupported() == List({"auto", "envelope", "manual", "tray-1"})); + + ASSERT(ip.outputBin.isSupported()); + ASSERT(ip.outputBin.getDefault() == "face-down"); + ASSERT(ip.outputBin.getSupported() == List({"face-down"})); + + ASSERT(ip.topMargin.isSupported()); + ASSERT(ip.topMargin.getDefault() == 1); + ASSERT(ip.topMargin.getSupported() == List({1, 2, 3, 4})); + + ASSERT(ip.bottomMargin.isSupported()); + ASSERT(ip.bottomMargin.getDefault() == 2); + ASSERT(ip.bottomMargin.getSupported() == List({2, 3, 4})); + + ASSERT(ip.leftMargin.isSupported()); + ASSERT(ip.leftMargin.getDefault() == 3); + ASSERT(ip.leftMargin.getSupported() == List({3, 4})); + + ASSERT(ip.rightMargin.isSupported()); + ASSERT(ip.rightMargin.getDefault() == 4); + ASSERT(ip.rightMargin.getSupported() == List({4})); + +} + +TEST(ippprintjob_empty) +{ + IppAttrs printerAttrs; + IppPrintJob ip(printerAttrs); + + ASSERT_FALSE(ip.sides.isSupported()); + ASSERT(ip.sides.getDefault() == ""); + ASSERT(ip.sides.getSupported() == List()); + + ASSERT_FALSE(ip.media.isSupported()); + ASSERT(ip.media.getDefault() == ""); + ASSERT(ip.media.getSupported() == List()); + ASSERT(ip.media.getPreferred() == List()); + + ASSERT_FALSE(ip.copies.isSupported()); + ASSERT(ip.copies.getDefault() == 0); + ASSERT(ip.copies.getSupportedMin() == 0); + ASSERT(ip.copies.getSupportedMax() == 0); + + ASSERT_FALSE(ip.multipleDocumentHandling.isSupported()); + ASSERT(ip.multipleDocumentHandling.getDefault() == ""); + ASSERT(ip.multipleDocumentHandling.getSupported() == List()); + + ASSERT_FALSE(ip.pageRanges.isSupported()); + ASSERT(ip.pageRanges.getSupported() == false); + + ASSERT_FALSE(ip.numberUp.isSupported()); + ASSERT(ip.numberUp.getDefault() == 0); + ASSERT(ip.numberUp.getSupported() == List()); + + ASSERT_FALSE(ip.colorMode.isSupported()); + ASSERT(ip.colorMode.getDefault() == ""); + ASSERT(ip.colorMode.getSupported() == List()); + + ASSERT_FALSE(ip.printQuality.isSupported()); + ASSERT(ip.printQuality.getDefault() == 0); + ASSERT(ip.printQuality.getSupported() == List()); + + ASSERT_FALSE(ip.resolution.isSupported()); + ASSERT(ip.resolution.getDefault() == IppResolution()); + ASSERT(ip.resolution.getSupported() == List()); + + ASSERT_FALSE(ip.scaling.isSupported()); + ASSERT(ip.scaling.getDefault() == ""); + ASSERT(ip.scaling.getSupported() == List()); + + ASSERT_FALSE(ip.documentFormat.isSupported()); + ASSERT(ip.documentFormat.getDefault() == ""); + ASSERT(ip.documentFormat.getSupported() == List()); + + ASSERT_FALSE(ip.mediaType.isSupported()); + ASSERT(ip.mediaType.getDefault() == ""); + ASSERT(ip.mediaType.getSupported() == List()); + + ASSERT_FALSE(ip.mediaSource.isSupported()); + ASSERT(ip.mediaSource.getDefault() == ""); + ASSERT(ip.mediaSource.getSupported() == List()); + + ASSERT_FALSE(ip.outputBin.isSupported()); + ASSERT(ip.outputBin.getDefault() == ""); + ASSERT(ip.outputBin.getSupported() == List()); + + ASSERT_FALSE(ip.topMargin.isSupported()); + ASSERT(ip.topMargin.getDefault() == 0); + ASSERT(ip.topMargin.getSupported() == List()); + + ASSERT_FALSE(ip.bottomMargin.isSupported()); + ASSERT(ip.bottomMargin.getDefault() == 0); + ASSERT(ip.bottomMargin.getSupported() == List()); + + ASSERT_FALSE(ip.leftMargin.isSupported()); + ASSERT(ip.leftMargin.getDefault() == 0); + ASSERT(ip.leftMargin.getSupported() == List()); + + ASSERT_FALSE(ip.rightMargin.isSupported()); + ASSERT(ip.rightMargin.getDefault() == 0); + ASSERT(ip.rightMargin.getSupported() == List()); + +} + +TEST(ippprintjob_set) +{ + IppAttrs printerAttrs; + IppPrintJob ip(printerAttrs); + + IppAttrs jobAttrs; + + ip.sides.set("two-sided"); + ASSERT(ip.sides.isSet()); + ASSERT(ip.sides.get() == "two-sided"); + jobAttrs.set("sides", IppAttr(IppMsg::Keyword, "two-sided")); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.media.set("iso_a4_210x297mm"); + ASSERT(ip.media.isSet()); + ASSERT(ip.media.get() == "iso_a4_210x297mm"); + jobAttrs.set("media", IppAttr(IppMsg::Keyword, "iso_a4_210x297mm")); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.copies.set(42); + ASSERT(ip.copies.isSet()); + ASSERT(ip.copies.get() == 42); + jobAttrs.set("copies", IppAttr(IppMsg::Integer, 42)); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.multipleDocumentHandling.set("separate-documents-collated-copies"); + ASSERT(ip.multipleDocumentHandling.isSet()); + ASSERT(ip.multipleDocumentHandling.get() == "separate-documents-collated-copies"); + jobAttrs.set("multiple-document-handling", IppAttr(IppMsg::Keyword, "separate-documents-collated-copies")); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.pageRanges.set({IppIntRange {17, 42}}); + ASSERT(ip.pageRanges.isSet()); + ASSERT((ip.pageRanges.get() == IppOneSetOf {IppIntRange {17, 42}})); + jobAttrs.set("page-ranges", IppAttr(IppMsg::IntegerRange, IppOneSetOf {IppIntRange {17, 42}})); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.pageRanges.set({{IppIntRange {1, 2}}, {IppIntRange {17, 42}}}); + ASSERT(ip.pageRanges.isSet()); + ASSERT((ip.pageRanges.get() == IppOneSetOf {IppIntRange {1, 2}, IppIntRange {17, 42}})); + jobAttrs.set("page-ranges", IppAttr(IppMsg::IntegerRange, IppOneSetOf {IppIntRange {1, 2}, IppIntRange {17, 42}})); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.numberUp.set(8); + ASSERT(ip.numberUp.isSet()); + ASSERT(ip.numberUp.get() == 8); + jobAttrs.set("number-up", IppAttr(IppMsg::Integer, 8)); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.colorMode.set("monochrome"); + ASSERT(ip.colorMode.isSet()); + ASSERT(ip.colorMode.get() == "monochrome"); + jobAttrs.set("print-color-mode", IppAttr(IppMsg::Keyword, "monochrome")); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.printQuality.set(5); + ASSERT(ip.printQuality.isSet()); + ASSERT(ip.printQuality.get() == 5); + jobAttrs.set("print-quality", IppAttr(IppMsg::Integer, 5)); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.resolution.set(IppResolution {600, 600, 3}); + ASSERT(ip.resolution.isSet()); + ASSERT((ip.resolution.get() == IppResolution {600, 600, 3})); + jobAttrs.set("printer-resolution", IppAttr(IppMsg::Resolution, IppResolution {600, 600, 3})); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.scaling.set("auto"); + ASSERT(ip.scaling.isSet()); + ASSERT(ip.scaling.get() == "auto"); + jobAttrs.set("print-scaling", IppAttr(IppMsg::Keyword, "auto")); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.documentFormat.set("application/pdf"); + ASSERT(ip.documentFormat.isSet()); + ASSERT(ip.documentFormat.get() == "application/pdf"); + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "application/pdf")}})); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.outputBin.set("face-down"); + ASSERT(ip.outputBin.isSet()); + ASSERT(ip.outputBin.get() == "face-down"); + jobAttrs.set("output-bin", IppAttr(IppMsg::Keyword, "face-down")); + ASSERT(ip.jobAttrs == jobAttrs); + + ip.mediaType.set("stationery"); + ASSERT(ip.mediaType.isSet()); + ASSERT(ip.mediaType.get() == "stationery"); + ip.mediaSource.set("main"); + ASSERT(ip.mediaSource.isSet()); + ASSERT(ip.mediaSource.get() == "main"); + ip.topMargin.set(1); + ASSERT(ip.topMargin.isSet()); + ASSERT(ip.topMargin.get() == 1); + ip.bottomMargin.set(2); + ASSERT(ip.bottomMargin.isSet()); + ASSERT(ip.bottomMargin.get() == 2); + ip.leftMargin.set(3); + ASSERT(ip.leftMargin.isSet()); + ASSERT(ip.leftMargin.get() == 3); + ip.rightMargin.set(4); + ASSERT(ip.rightMargin.isSet()); + ASSERT(ip.rightMargin.get() == 4); + + IppCollection mediaCol {{"media-type", IppAttr(IppMsg::Keyword, "stationery")}, + {"media-source", IppAttr(IppMsg::Keyword, "main")}, + {"media-top-margin", IppAttr(IppMsg::Integer, 1)}, + {"media-bottom-margin", IppAttr(IppMsg::Integer, 2)}, + {"media-left-margin", IppAttr(IppMsg::Integer, 3)}, + {"media-right-margin", IppAttr(IppMsg::Integer, 4)}}; + + jobAttrs.set("media-col", IppAttr(IppMsg::BeginCollection, mediaCol)); + + ASSERT(ip.jobAttrs == jobAttrs); + +} + +TEST(finalize) +{ + IppAttrs printerAttrs = + {{"sides-default", IppAttr(IppMsg::Keyword, "one-sided")}, + {"sides-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"one-sided", + "two-sided-long-edge", + "two-sided-short-edge"})}, + {"media-default", IppAttr(IppMsg::Keyword, "iso_a4_210x297mm")}, + {"media-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"iso_a4_210x297mm", + "na_letter_8.5x11in"})}, + {"media-ready", IppAttr(IppMsg::Keyword, "iso_a4_210x297mm")}, + {"copies-default", IppAttr(IppMsg::Integer, 1)}, + {"copies-supported", IppAttr(IppMsg::IntegerRange, IppIntRange {1, 999})}, + {"multiple-document-handling-default", IppAttr(IppMsg::Keyword, "separate-documents-uncollated-copies")}, + {"multiple-document-handling-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"single-document", + "separate-documents-uncollated-copies", + "separate-documents-collated-copies"})}, + {"page-ranges-supported", IppAttr(IppMsg::Boolean, false)}, + {"number-up-default", IppAttr(IppMsg::Integer, 1)}, + {"number-up-supported", IppAttr(IppMsg::Integer, IppOneSetOf{1, 2, 4})}, + {"print-color-mode-default", IppAttr(IppMsg::Keyword, "auto")}, + {"print-color-mode-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "color", "monochrome"})}, + {"print-quality-default", IppAttr(IppMsg::Enum, 4)}, + {"print-quality-supported", IppAttr(IppMsg::Enum, IppOneSetOf {3, 4, 5})}, + {"printer-resolution-default", IppAttr(IppMsg::Resolution, IppResolution {600, 600, 3})}, + {"printer-resolution-supported", IppAttr(IppMsg::Resolution, IppOneSetOf {IppResolution {600, 600, 3}, + IppResolution {600, 1200, 3}})}, + {"document-format-default", IppAttr(IppMsg::Keyword, "application/octet-stream")}, + {"document-format-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"application/octet-stream", + "image/urf", + "image/pwg-raster", + "application/pdf"})}, + {"print-scaling-default", IppAttr(IppMsg::Keyword, "auto")}, + {"print-scaling-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "fill", "fit"})}, + {"media-type-default", IppAttr(IppMsg::Keyword, "stationery")}, + {"media-type-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"stationery", "cardstock", "labels"})}, + {"media-source-default", IppAttr(IppMsg::Keyword, "auto")}, + {"media-source-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "envelope", "manual", "tray-1"})}, + {"output-bin-default", IppAttr(IppMsg::Keyword, "face-down")}, + {"output-bin-supported", IppAttr(IppMsg::Keyword, "face-down")}, + + {"media-top-margin-default", IppAttr(IppMsg::Integer, 1)}, + {"media-top-margin-supported", IppAttr(IppMsg::Integer, IppOneSetOf {1, 2, 3, 4})}, + {"media-bottom-margin-default", IppAttr(IppMsg::Integer, 2)}, + {"media-bottom-margin-supported", IppAttr(IppMsg::Integer, IppOneSetOf {2, 3, 4})}, + {"media-left-margin-default", IppAttr(IppMsg::Integer, 3)}, + {"media-left-margin-supported", IppAttr(IppMsg::Integer, IppOneSetOf {3, 4})}, + {"media-right-margin-default", IppAttr(IppMsg::Integer, 4)}, + {"media-right-margin-supported", IppAttr(IppMsg::Integer, 4)}, + + {"pwg-raster-document-resolution-supported", IppAttr(IppMsg::Resolution, IppOneSetOf {IppResolution {300, 300, 3}, + IppResolution {600, 600, 3}})}, + {"pwg-raster-document-sheet-back", IppAttr(IppMsg::Keyword, "flipped")}, + {"document-format-varying-attributes", IppAttr(IppMsg::Keyword, "copies-supported")}, + {"urf-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"RS300-600", "DM3"})}, + + }; + + IppPrintJob ip(printerAttrs); + + // Margins - to be unset/ignored for PDF derivatives + ip.topMargin.set(1); + ip.bottomMargin.set(2); + ip.leftMargin.set(3); + ip.rightMargin.set(4); + + // To propagate to printParams + ip.resolution.set(IppResolution {600, 600, 3}); + + // To be kept in media since media-col is emptied when margins are removed + ip.media.set("na_letter_8.5x11in"); + + // To be removed in jobAttrs, but set in jobAttrs + ip.pageRanges.set({IppIntRange {17, 42}}); + + // To be kept and set in printParams + ip.sides.set("two-sided-long-edge"); + ip.printQuality.set(5); + ip.colorMode.set("monochrome"); + + ip.finalize("application/pdf"); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "application/pdf")}})); + ASSERT((ip.jobAttrs == IppAttrs {{"printer-resolution", IppAttr(IppMsg::Resolution, IppResolution {600, 600, 3})}, + {"media", IppAttr(IppMsg::Keyword, "na_letter_8.5x11in")}, + {"sides", IppAttr(IppMsg::Keyword, "two-sided-long-edge")}, + {"print-quality", IppAttr(IppMsg::Enum, 5)}, + {"print-color-mode", IppAttr(IppMsg::Keyword, "monochrome")}})); + ASSERT(ip.printParams.format == PrintParameters::PDF); + ASSERT(ip.printParams.paperSizeName == "na_letter_8.5x11in"); + ASSERT(ip.printParams.hwResW == 600); + ASSERT(ip.printParams.hwResH == 600); + ASSERT((ip.printParams.pageRangeList == PageRangeList {{17, 42}})); + ASSERT(ip.printParams.duplexMode == PrintParameters::TwoSidedLongEdge); + ASSERT(ip.printParams.quality == PrintParameters::HighQuality); + ASSERT(ip.printParams.colorMode == PrintParameters::Gray8); + + // Forget choices + ip = IppPrintJob(printerAttrs); + + // With some random media-col setting, media-size moves to media-col too + ip.media.set("iso_a4_210x297mm"); + ip.mediaSource.set("tray-1"); + ip.mediaType.set("cardstock"); + + ip.finalize("application/pdf"); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "application/pdf")}})); + IppCollection dimensions {{"x-dimension", IppAttr(IppMsg::Integer, 21000)}, + {"y-dimension", IppAttr(IppMsg::Integer, 29700)}}; + IppCollection mediaCol {{"media-type", IppAttr(IppMsg::Keyword, "cardstock")}, + {"media-size", IppAttr(IppMsg::BeginCollection, dimensions)}, + {"media-source", IppAttr(IppMsg::Keyword, "tray-1")}}; + ASSERT((ip.jobAttrs == IppAttrs {{"media-col", IppAttr(IppMsg::BeginCollection, mediaCol)}})); + + ASSERT(ip.printParams.paperSizeName == "iso_a4_210x297mm"); + ASSERT(ip.printParams.mediaPosition == PrintParameters::Tray1); + ASSERT(ip.printParams.mediaType == "cardstock"); + + // Remove PDF support, forget choices + printerAttrs.set("document-format-supported", + IppAttr(IppMsg::Keyword, IppOneSetOf {"application/octet-stream", + "image/urf", + "image/pwg-raster"})); + ip = IppPrintJob(printerAttrs); + + // To be removed and clamped to 600x600 in printParams + ip.resolution.set(IppResolution {600, 1200, 3}); + + ip.copies.set(2); + + ip.finalize("application/pdf"); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "image/pwg-raster")}})); + ASSERT((ip.jobAttrs == IppAttrs {})); + ASSERT(ip.printParams.format == PrintParameters::PWG); + ASSERT(ip.printParams.hwResW == 600); + ASSERT(ip.printParams.hwResH == 600); + ASSERT(ip.printParams.backXformMode == PrintParameters::Flipped); + ASSERT(ip.printParams.copies == 2); + + // Forget and do almost the same again, but with URF selected + ip = IppPrintJob(printerAttrs); + ip.documentFormat.set("image/urf"); + + // To be removed and clamped to 600x600 in printParams + ip.resolution.set(IppResolution {600, 1200, 3}); + + ip.copies.set(2); + + ip.finalize("application/pdf"); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "image/urf")}})); + ASSERT((ip.jobAttrs == IppAttrs {})); + ASSERT(ip.printParams.format == PrintParameters::URF); + ASSERT(ip.printParams.hwResW == 600); + ASSERT(ip.printParams.hwResH == 600); + ASSERT(ip.printParams.backXformMode == PrintParameters::Rotated); + ASSERT(ip.printParams.copies == 2); + + // Add Postscript, forget settings + printerAttrs.set("document-format-supported", + IppAttr(IppMsg::Keyword, IppOneSetOf {"application/octet-stream", + "application/postscript", + "image/urf", + "image/pwg-raster"})); + ip = IppPrintJob(printerAttrs); + + // Margins - to be unset/ignored for Postscript + ip.topMargin.set(1); + ip.bottomMargin.set(2); + ip.leftMargin.set(3); + ip.rightMargin.set(4); + + ip.resolution.set(IppResolution {600, 600, 3}); + // To be kept for Postscript + ip.pageRanges.set({IppIntRange {17, 42}}); + + ip.finalize("application/postscript"); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "application/postscript")}})); + ASSERT((ip.jobAttrs == IppAttrs {{"printer-resolution", IppAttr(IppMsg::Resolution, IppResolution {600, 600, 3})}, + {"page-ranges", IppAttr(IppMsg::IntegerRange, IppOneSetOf {IppIntRange {17, 42}})}})); + // PrintParameters only used for PDF input + ASSERT(ip.printParams.format == PrintParameters::Invalid); + + // Add JPEG, forget settings + printerAttrs.set("document-format-supported", + IppAttr(IppMsg::Keyword, IppOneSetOf {"application/octet-stream", + "image/jpeg", + "image/urf", + "image/pwg-raster"})); + ip = IppPrintJob(printerAttrs); + + // Margins - to be kept for images + ip.topMargin.set(1); + ip.bottomMargin.set(2); + ip.leftMargin.set(3); + ip.rightMargin.set(4); + + ip.resolution.set(IppResolution {600, 600, 3}); + + ip.finalize("image/jpeg"); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "image/jpeg")}})); + IppCollection mediaCol2 {{"media-top-margin", IppAttr(IppMsg::Integer, 1)}, + {"media-bottom-margin", IppAttr(IppMsg::Integer, 2)}, + {"media-left-margin", IppAttr(IppMsg::Integer, 3)}, + {"media-right-margin", IppAttr(IppMsg::Integer, 4)}}; + ASSERT((ip.jobAttrs == IppAttrs {{"printer-resolution", IppAttr(IppMsg::Resolution, IppResolution {600, 600, 3})}, + {"media-col", IppAttr(IppMsg::BeginCollection, mediaCol2)}})); + // PrintParameters only used for PDF input + ASSERT(ip.printParams.format == PrintParameters::Invalid); + + // Forget choices + ip = IppPrintJob(printerAttrs); + + // Margins - to be unset/ignored for rasters + ip.topMargin.set(1); + ip.bottomMargin.set(2); + ip.leftMargin.set(3); + ip.rightMargin.set(4); + + // To be changed for single-page document doing copies + ip.sides.set("two-sided-long-edge"); + ip.copies.set(2); + + ip.finalize("application/pdf", 1); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "image/pwg-raster")}})); + ASSERT((ip.jobAttrs == IppAttrs {{"sides", IppAttr(IppMsg::Keyword, "one-sided")}})); + // Not an image taregt format - margins removed + IppCollection mediaCol3 = ip.jobAttrs.get("media-col"); + ASSERT_FALSE(mediaCol3.has("media-top-margin")); + ASSERT_FALSE(mediaCol3.has("media-bottom-margin")); + ASSERT_FALSE(mediaCol3.has("media-left-margin")); + ASSERT_FALSE(mediaCol3.has("media-right-margin")); + ASSERT(ip.margins.top == 1); + ASSERT(ip.margins.bottom == 2); + ASSERT(ip.margins.left == 3); + ASSERT(ip.margins.right == 4); + ASSERT(ip.printParams.format == PrintParameters::PWG); + ASSERT(ip.printParams.colorMode == PrintParameters::sRGB24); + + // Un-support color, forget choices + printerAttrs.set("print-color-mode-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"auto", "monochrome"})); + ip = IppPrintJob(printerAttrs); + ip.finalize("application/pdf", 0); + + ASSERT((ip.opAttrs == IppAttrs {{"document-format", IppAttr(IppMsg::MimeMediaType, "image/pwg-raster")}})); + ASSERT(ip.printParams.format == PrintParameters::PWG); + ASSERT(ip.printParams.colorMode == PrintParameters::Gray8); // Since we don't support color + +} + +TEST(additional_formats) +{ + IppAttrs printerAttrs; + IppPrintJob ip(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List()); + + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MANUFACTURER:Xerox;COMMAND SET:PDF,URF,JPEG;MODEL:WorkCentre 6515;URF:IS4-20,IFU20,OB10,CP255,DM1,MT1,PQ4-5,RS600,W8,SRGB24,V1.4,FN3")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"application/pdf", "image/urf"})); + + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MFG:Canon;CMD:URF;MDL:SELPHY CP1300;CLS:PRINTER;URF:W8,SRGB24,V1.4,RS300,IS7,MT11,PQ4,OB9,IFU0,OFU0,CP99;")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"image/urf"})); + + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MANUFACTURER:Xerox;COMMAND SET:PCL 6 Emulation, PostScript Level 3 Emulation, URF, PWG, NPAP, PJL;MODEL:C230 Color Printer;CLS:PRINTER;DES:Xerox(R) C230 Color Printer;CID:XR_PCL6_XCPT_Color_A4;COMMENT:ECP1.0, LV_0924, LP_9D15, LF_00C7;")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"application/postscript", "image/pwg-raster", "image/urf"})); + + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX,ESCPR7,END4,GENEP,PWGRaster,URF;MDL:SC-P900 Series;CLS:PRINTER;DES:EPSON SC-P900 Series;CID:EpsonRGB;RID:02;DDS:401900;ELG:13F0;SN:583757503030333448;URF:CP1,PQ3-4-5,OB9,OFU0,RS360-720,SRGB24,ADOBERGB24-48,W8,IS18-20-21-22,V1.4,MT1-10-11;")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"image/pwg-raster", "image/urf"})); + + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MFG:HP;CMD:PJL,PML,PWG_RASTER,URP;MDL:HP LaserJet MFP M28-M31;CLS:PRINTER;DES:HP LaserJet MFP M28w;MEM:MEM=279MB;PRN:W2G55A;S:0300000000000000000000000000000000;COMMENT:RES=600x2;LEDMDIS:USB#ff#04#01;CID:HPLJPCLMSMV1;MCT:MF;MCL:FL;MCV:2.0;")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"image/pwg-raster"})); + + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MFG:HP;MDL:DeskJet 3700 series;CMD:PCL3GUI,PJL,Automatic,JPEG,PCLM,AppleRaster,PWGRaster,DW-PCL,802.11,DESKJET,DYN;CLS:PRINTER;DES:T8X04B;CID:HPDeskjet_P976D;LEDMDIS:USB#FF#CC#00,USB#07#01#02,USB#FF#04#01;SN:CN69R3D24J06H3;S:038000C480a00001002c0000000c1400046;Z:05000001000008,12000,17000000000000,181;")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"image/pwg-raster", "image/urf"})); + + printerAttrs = IppAttrs(); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List()); + + printerAttrs.set("document-format-supported", IppAttr(IppMsg::Keyword, IppOneSetOf {"application/octet-stream", + "application/postscript", + "image/urf"})); + printerAttrs.set("printer-device-id", IppAttr(IppMsg::TextWithoutLanguage, "MFG:OKI DATA c332.js-CORP;CMD:PCL,XPS,IBMPPR,EPSONFX,POSTSCRIPT,PDF,PCLXL,HIPERMIP,URF;MDL:C332;CLS:PRINTER;DES:OKI c332.js-C332;CID:OK_N_B800;")); + ip = IppPrintJob(printerAttrs); + ASSERT(ip.additionalDocumentFormats() == List({"application/pdf"})); + +} diff --git a/utils/ippposter.cpp b/utils/ippposter.cpp index 43b9438..2a8d686 100644 --- a/utils/ippposter.cpp +++ b/utils/ippposter.cpp @@ -1,34 +1,318 @@ #include #include -#include +#include "argget.h" #include "curlrequester.h" +#include "ippmsg.h" +#include "ippprintjob.h" -// WIP: use ArgGet and stream from stdin before it's official +#define HELPTEXT "" + +inline void print_error(std::string hint, std::string argHelp) +{ + std::cerr << hint << std::endl << std::endl << argHelp << std::endl << HELPTEXT << std::endl; +} + +template +std::ostream& operator<<(std::ostream& os, List bl) +{ + if(bl.size() > 0) + { + os << "[" << bl.takeFront(); + for(T b : bl) + { + os << ", " << b; + } + os << "]"; + } + else + { + os << "[]"; + } + return os; +} int main(int argc, char** argv) { - if(argc != 3) + bool help = false; + bool verbose = false; + std::string pages; + int copies; + std::string paperSize; + uint32_t hwRes; + uint32_t hwResX; + uint32_t hwResY; + bool duplex = false; + bool tumble = false; + std::string colorMode; + int quality; + + std::string scaling; + std::string format; + std::string mimeType = "application/pdf"; + std::string mediaType; + std::string mediaSource; + std::string outputBin; + + int margin; + int topMargin; + int bottomMargin; + int leftMargin; + int rightMargin; + + bool antiAlias; + + std::string addr; + std::string infile; + + SwitchArg helpOpt(help, {"-h", "--help"}, "Print this help text"); + SwitchArg verboseOpt(verbose, {"-v", "--verbose"}, "Be verbose, print headers and progress"); + SwitchArg pagesOpt(pages, {"-p", "--pages"}, "What pages to process, e.g.: 1,17-42"); + SwitchArg copiesOpt(copies, {"--copies"}, "Number of copies to output"); + + SwitchArg paperSizeOpt(paperSize, {"--paper-size"}, "Paper size to output, e.g.: iso_a4_210x297mm"); + SwitchArg resolutionOpt(hwRes, {"-r", "--resolution"}, "Resolution (in DPI) for rasterization"); + SwitchArg resolutionXOpt(hwResX, {"-rx", "--resolution-x"}, "Resolution (in DPI) for rasterization, x-axis"); + SwitchArg resolutionYOpt(hwResY, {"-ry", "--resolution-y"}, "Resolution (in DPI) for rasterization, y-axis"); + SwitchArg duplexOpt(duplex, {"-d", "--duplex"}, "Do duplex printing"); + SwitchArg tumbleOpt(tumble, {"-t", "--tumble"}, "Do tumbled duplex printing"); + + SwitchArg colorModeOpt(colorMode, {"-c", "--color-mode"}, "Color mode (as per IPP)"); + EnumSwitchArg qualityOpt(quality, {{"draft", PrintParameters::DraftQuality}, + {"normal", PrintParameters::NormalQuality}, + {"high", PrintParameters::HighQuality}}, + {"-q", "--quality"}, + "Quality (draft/normal/high)"); + + SwitchArg scalingOpt(scaling, {"--scaling"}, "Scaling (as per IPP)"); + SwitchArg formatOpt(format, {"--format"}, "Transfer format"); + SwitchArg mimeTypeOpt(mimeType, {"--mime-type"}, "Input file mime-type"); + SwitchArg mediaTypeOpt(mediaType, {"--media-type"}, "Media type (as per IPP)"); + SwitchArg mediaSourceOpt(mediaSource, {"--media-source"}, "Media source (as per IPP)"); + SwitchArg outputBinOpt(outputBin, {"--output-bin"}, "Output bin (as per IPP)"); + + SwitchArg marginOpt(margin, {"-m", "--margin"}, "Margin (as per IPP)"); + SwitchArg topMarginOpt(topMargin, {"-tm", "--top-margin"}, "Top margin (as per IPP)"); + SwitchArg bottomMarginOpt(bottomMargin, {"-bm", "--bottom-margin"}, "Bottom margin (as per IPP)"); + SwitchArg leftMarginOpt(leftMargin, {"-lm", "--left-margin"}, "Left margin (as per IPP)"); + SwitchArg rightMarginOpt(rightMargin, {"-rm", "--right-margin"}, "Right margin (as per IPP)"); + + SwitchArg antiAliasOpt(antiAlias, {"-aa", "--antaialias"}, "Enable antialiasing in rasterization"); + + PosArg addrArg(addr, "printer address"); + PosArg pdfArg(infile, "input file"); + + ArgGet args({&helpOpt, &verboseOpt, &pagesOpt, + &copiesOpt, &paperSizeOpt, &resolutionOpt, + &resolutionXOpt, &resolutionYOpt, &duplexOpt, &tumbleOpt, + &colorModeOpt, &qualityOpt, &scalingOpt, &formatOpt, &mimeTypeOpt, + &mediaTypeOpt, &mediaSourceOpt, &outputBinOpt, + &marginOpt, &topMarginOpt, &bottomMarginOpt, &leftMarginOpt, &rightMarginOpt, + &antiAliasOpt}, + {&addrArg, &pdfArg}); + + bool correctArgs = args.get_args(argc, argv); + if(help) + { + std::cout << args.argHelp() << std::endl << HELPTEXT << std::endl; + return 0; + } + else if(!correctArgs) + { + print_error(args.errmsg(), args.argHelp()); + return 1; + } + + IppAttrs getPrinterAttrsJobAttrs = IppMsg::baseOpAttrs(addr); + IppMsg getPrinterAttrsMsg(IppMsg::GetPrinterAttrs, getPrinterAttrsJobAttrs); + Bytestream getPrinterAttrsEnc = getPrinterAttrsMsg.encode(); + + CurlIppPoster getPrinterAttrsReq(addr, getPrinterAttrsEnc, true, verbose); + getPrinterAttrsReq.write((char*)(getPrinterAttrsEnc.raw()), getPrinterAttrsEnc.size()); + Bytestream getPrinterAttrsResult; + CURLcode res0 = getPrinterAttrsReq.await(&getPrinterAttrsResult); + IppAttrs printerAttrs; + if(res0 == CURLE_OK) { + IppMsg getPrinterAttrsResp(getPrinterAttrsResult); + printerAttrs = getPrinterAttrsResp.getPrinterAttrs(); + } + else + { + std::cerr << "Could not get printer attributes: " << curl_easy_strerror(res0) << std::endl; return 1; } - std::string addr = argv[1]; - std::string inFile = argv[2]; + IppPrintJob ip(printerAttrs); + + if(antiAliasOpt.isSet()) + { + ip.printParams.antiAlias = antiAlias; + } + + if(verbose) + { + std::cerr << "Supported settings:" << std::endl << std::endl; + + std::cerr << "sides: " << ip.sides.getDefault() << std::endl; + std::cerr << ip.sides.getSupported() << std::endl << std::endl; + + std::cerr << "media: " << ip.media.getDefault() << std::endl; + std::cerr << ip.media.getSupported() << std::endl; + std::cerr << ip.media.getPreferred() << std::endl << std::endl; + + std::cerr << "copies: " << ip.copies.getDefault() << std::endl; + std::cerr << ip.copies.getSupportedMin() << " - " + << ip.copies.getSupportedMax() << std::endl << std::endl; + + std::cerr << "collated copies: " << ip.multipleDocumentHandling.getDefault() << std::endl; + std::cerr << ip.multipleDocumentHandling.getSupported() << std::endl << std::endl; + + std::cerr << "page ranges: " << ip.pageRanges.getSupported() << std::endl << std::endl; + + std::cerr << "number up: " << ip.numberUp.getDefault() << std::endl; + std::cerr << ip.numberUp.getSupported() << std::endl << std::endl; + + std::cerr << "color: " << ip.colorMode.getDefault() << std::endl; + std::cerr << ip.colorMode.getSupported() << std::endl << std::endl; + + std::cerr << "quality: " << ip.printQuality.getDefault() << std::endl; + std::cerr << ip.printQuality.getSupported() << std::endl << std::endl; + + std::cerr << "resolution: " << ip.resolution.getDefault() << std::endl; + std::cerr << ip.resolution.getSupported() << std::endl << std::endl; + + std::cerr << "scaling: " << ip.scaling.getDefault() << std::endl; + std::cerr << ip.scaling.getSupported() << std::endl << std::endl; + + std::cerr << "format: " << ip.documentFormat.getDefault() << std::endl; + std::cerr << ip.documentFormat.getSupported() << std::endl << std::endl; - std::ifstream ifs(inFile, std::ios::in | std::ios::binary); + std::cerr << "media type: " << ip.mediaType.getDefault() << std::endl; + std::cerr << ip.mediaType.getSupported() << std::endl << std::endl; - Bytestream hdr(ifs); - Bytestream data(std::cin); + std::cerr << "media source: " << ip.mediaSource.getDefault() << std::endl; + std::cerr << ip.mediaSource.getSupported() << std::endl << std::endl; - CurlIppStreamer req(addr); + std::cerr << "output bin: " << ip.outputBin.getDefault() << std::endl; + std::cerr << ip.outputBin.getSupported() << std::endl << std::endl; - req.write(hdr.raw(), hdr.size()); - req.write(data.raw(), data.size()); + std::cerr << "media top margin: " << ip.topMargin.getDefault() << std::endl; + std::cerr << ip.topMargin.getSupported() << std::endl << std::endl; - Bytestream result; + std::cerr << "media bottom margin: " << ip.bottomMargin.getDefault() << std::endl; + std::cerr << ip.bottomMargin.getSupported() << std::endl << std::endl; - req.await(&result); + std::cerr << "media left margin: " << ip.leftMargin.getDefault() << std::endl; + std::cerr << ip.leftMargin.getSupported() << std::endl << std::endl; - std::cerr << "resp: \n" << result.hexdump(result.size()) << std::endl; + std::cerr << "media right margin: " << ip.rightMargin.getDefault() << std::endl; + std::cerr << ip.rightMargin.getSupported() << std::endl << std::endl; + } + + // Error on unsupported - add force-option + // check invalid/redundant combinations + + if(pagesOpt.isSet()) + { + PageRangeList pageRanges = PrintParameters::parsePageRange(pages); + if(pageRanges.empty()) + { + print_error("Invalid page range.", args.argHelp()); + return 1; + } + IppOneSetOf ippPageRanges; + for(std::pair pageRange : pageRanges) + { + ippPageRanges.push_back(IppIntRange {pageRange.first, pageRange.second}); + } + ip.pageRanges.set(ippPageRanges); + } + if(copiesOpt.isSet()) + { + ip.copies.set(copies); + } + if(copiesOpt.isSet()) + { + ip.copies.set(copies); + } + if(paperSizeOpt.isSet()) + { + ip.media.set(paperSize); + } + if(resolutionOpt.isSet()) + { + ip.resolution.set(IppResolution {hwRes, hwRes, IppResolution::DPI}); + } + if(resolutionXOpt.isSet() && resolutionYOpt.isSet()) + { + ip.resolution.set(IppResolution {hwResX, hwResY, IppResolution::DPI}); + } + if(tumble) + { + ip.sides.set("two-sided-short-edge"); + } + if(duplex) + { + ip.sides.set("two-sided-long-edge"); + } + if(colorModeOpt.isSet()) + { + ip.colorMode.set(colorMode); + } + if(qualityOpt.isSet()) + { + ip.printQuality.set(quality); + } + if(scalingOpt.isSet()) + { + ip.scaling.set(scaling); + } + if(formatOpt.isSet()) + { + ip.documentFormat.set(format); + } + if(mediaTypeOpt.isSet()) + { + ip.mediaType.set(mediaType); + } + if(mediaSourceOpt.isSet()) + { + ip.mediaSource.set(mediaSource); + } + if(outputBinOpt.isSet()) + { + ip.outputBin.set(outputBin); + } + if(marginOpt.isSet()) + { + ip.topMargin.set(margin); + ip.bottomMargin.set(margin); + ip.leftMargin.set(margin); + ip.rightMargin.set(margin); + } + if(topMarginOpt.isSet()) + { + ip.topMargin.set(topMargin); + } + if(bottomMarginOpt.isSet()) + { + ip.bottomMargin.set(bottomMargin); + } + if(leftMarginOpt.isSet()) + { + ip.leftMargin.set(leftMargin); + } + if(rightMarginOpt.isSet()) + { + ip.rightMargin.set(rightMargin); + } + + Error error = ip.run(addr, infile, mimeType, verbose); + if(error) + { + std::cerr << "Print failed: " << error.value() << std::endl; + return 1; + } + return 0; } diff --git a/utils/pdf2printable_main.cpp b/utils/pdf2printable_main.cpp index 670ab23..c4ac7bc 100644 --- a/utils/pdf2printable_main.cpp +++ b/utils/pdf2printable_main.cpp @@ -16,7 +16,7 @@ inline void print_error(std::string hint, std::string argHelp) std::cerr << hint << std::endl << std::endl << argHelp << std::endl << HELPTEXT << std::endl; } -inline bool ends_with(std::string s, std::string ending) +inline bool endsWith(std::string s, std::string ending) { if(ending.length() <= s.length()) { @@ -115,19 +115,19 @@ int main(int argc, char** argv) if(!formatOpt.isSet()) { - if(ends_with(outFileName, ".ps")) + if(endsWith(outFileName, ".ps")) { params.format = PrintParameters::Postscript; } - else if(ends_with(outFileName, ".pwg")) + else if(endsWith(outFileName, ".pwg")) { params.format = PrintParameters::PWG; } - else if(ends_with(outFileName, ".urf")) + else if(endsWith(outFileName, ".urf")) { params.format = PrintParameters::URF; } - else if(ends_with(outFileName, ".pdf")) + else if(endsWith(outFileName, ".pdf")) { params.format = PrintParameters::PDF; } @@ -200,17 +200,25 @@ int main(int argc, char** argv) return outFile->exceptions() == std::ostream::goodbit; }); + Error error; + if(verbose) { ProgressFun progressFun([](size_t page, size_t total) -> void { std::cerr << "Progress: " << page << "/" << total << "\n\n"; }); - return pdf_to_printable(inFileName, writeFun, params, progressFun, true); + error = pdf_to_printable(inFileName, writeFun, params, progressFun, true); } else { - return pdf_to_printable(inFileName, writeFun, params); + error = pdf_to_printable(inFileName, writeFun, params); } - + if(error) + { + std::cerr << "Conversion failed: " << error.value() << std::endl; + return 1; + } + return 0; } +