头像

张小飞

我有一壶酒,足以慰风尘

《Qt在Linux下打印机原理-QCUPSSupport》

 1周前  •   Qt, Qt源码那点事儿  •   , , , , ,  •   105  •   2

简介

之前看了知乎上一个问题https://www.zhihu.com/question/19612074为什么中国的学校/单位不用 GNU/Linux? 来引出我想写这篇文章来进行科普了。顺便讲一下Qt在Linux下打印机的原理以及源码相关知识。

当前系统环境

目前,学校/单位已经有很多家都在使用Linux系统来办公了。广东省云浮市是个很典型的例子,可以去政府部门官网上去看看
http://www.yunfu.gov.cn/webnew/info/009007009008/201810/601597.html。保存的就是wps格式的文档,看来已经使用上我们WPS for Linux企业版了。
当然这里只是个试点,看到政府部门也一直在更新政务信息,看起来很平稳的从windows平台过度到国产化平台了。
再加上这条新闻
http://www.im2maker.com/news/20181203/7d502a156dbbe71d.html

大家就知道为大家要搞国产化了。

目前国产化芯片以及操作系统

目前龙芯,兆芯,ARM等国产化芯片已经发展的很不错了,cpu完全可以用来做日常的办公处理了,个人也特别期待明年新出的龙芯4000机型,据说能达到2.0GHz,手动@中国原创。目前龙芯3A3000正常办公已经完全没什么问题。ARM,兆芯上的WPS也跑的很流畅,当然离不开国产的deepin深度操作系统团队,以及麒麟系列操作系统等友商的优化,用户目前在国产化平台办公是完全没有问题的。

生态

办公软件作为操作系统的一项必需品,是必须要有的,这也是为什么当时我司就是赔钱也得做下去,所以才有了WPS For Linux这个产品。千万级别的代码,迁移这个工程的工作量不言而喻。目前WPS For Linux在官网上也有了免费的社区版本来服务大家。Linux平台,程序员同行是最多的,我们发布免费的社区版本,一方面是为了服务各位同行,另一方面也是希望更多的人去尝试下这个生态圈。

说的有点跑偏了,之所以想写这个文章是因为看这个下边评论说三天适配打印系统是不是很难,实际上真的不难,@诸葛不亮 是做军工业的,对这个应该也有很多了解。下边我就详细的讲一下Linux下的打印机原理。

Linux下简单的流程就是这样

Created with Raphaël 2.1.4QPrinterQCUPSSupportCUPSLinux Core

CUPS

目前Linux上打印的通用协议是CUPS协议。目前由苹果公司来维护。算了我还是粘一下wiki的词条吧。

wiki词条

CUPS (formerly an acronym for Common UNIX Printing System) is a modular printing system for Unix-like computer operating systems which allows a computer to act as a print server. A computer running CUPS is a host that can accept print jobs from client computers, process them, and send them to the appropriate printer.
CUPS consists of a print spooler and scheduler, a filter system that converts the print data to a format that the printer will understand, and a backend system that sends this data to the print device. CUPS uses the Internet Printing Protocol (IPP) as the basis for managing print jobs and queues. It also provides the traditional command line interfaces for the System V and Berkeley print systems, and provides support for the Berkeley print system’s Line Printer Daemon protocol and limited support for the server message block (SMB) protocol. System administrators can configure the device drivers which CUPS supplies by editing text files in Adobe’s PostScript Printer Description (PPD) format. There are a number of user interfaces for different pl#atforms that can configure CUPS, and it has a built-in web-based interface. CUPS is free software, provided under the Apache License.

CUPS(前为Common Unix Printing System,即UNIX通用打印系统的缩写,但现无官方全名[来源请求])是一个类Unix操作系统的组合式印刷系统,允许一台计算机作为打印服务器。CUPS接受一个客户端的计算机进程,并送到相应的打印机。
CUPS是自由软件,使用GNU通用公共许可证和GNU宽通用公共许可证的第2版。
迈克尔·斯维特,Easy Software Products的拥有者,于1997年开始开发CUPS。首次公开测试版于1999年发布。[2]原本设计的CUPS使用行式打印机后台程序协议,但由于LPD的限制和供应商不兼容,所以由互联网打印协议(IPP)代替。CUPS被迅速默认为一些Linux发行版的打印系统,如Red Hat Linux。2002年3月,苹果公司在Mac OS X v10.2中采用了CUPS。[3]2007年2月,苹果公司聘请了迈克尔·斯维特并购买了CUPS的源代码。[4]

再次说一遍 目前Unix系列的打印原理都是走的该协议。

CUPS是开源的

我读过这里的源码,为了验证下边有个纠结的问题。全是c写的,也不难。

Linux下的驱动

秉承着Linux下的原则,一切皆文件,实际上Linux下的打印机驱动也是文件,在目录/etc/cups/ppd文件夹下。可以随意装个虚拟驱动来看下

简单的给大家看几行ppd文件,这是我本地下的驱动文件

*PPD-Adobe: "4.3"
*%%%% PPD file for Generic Text-Only Printer with CUPS.
*%%%% Created by the CUPS PPD Compiler CUPS v2.2.7.
*% (c) 2014 OpenPrinting
*FormatVersion: "4.3"
*FileVersion: "1.0"
*LanguageVersion: English
*LanguageEncoding: ISOLatin1
*PCFileName: "textonly.ppd"
*Product: "(Generic Text-Only Printer)"
*Manufacturer: "Generic"
*ModelName: "Generic Text-Only Printer"
*ShortNickName: "Generic Text-Only Printer"
*NickName: "Generic Text-Only Printer"
*PSVersion: "(3010.000) 0"
*LanguageLevel: "3"
*ColorDevice: False
*DefaultColorSpace: Gray
*FileSystem: False
*Throughput: "1"
*LandscapeOrientation: Plus90
*TTRasterizer: Type42
*% Driver-defined attributes...
*cupsFilter2: "text/plain text/plain 0 texttotext"
*RequiresPageRegion All: True
*1284DeviceID: "MFG:Generic;MDL:Text-Only Printer;DES:Generic Text-Only Printer;CLS:PRINTER;CMD:TXT;DRV:Dtextonly,R1,M0;"
*cupsVersion: 2.2
*cupsModelNumber: 0
*cupsManualCopies: True
*cupsFilter: "text/plain 0 texttotext"
*cupsLanguages: "en"

里边主要是一些字符串信息。你可以简单的理解,cups就是来解析ppd文件中的字符串信息的。

Qt对CUPS的支持。

实际上Qt对CUPS的支持是相当好的,源码级别的话都不到千行,将cups提供的信息封装了一层Qt的API来给大家使用。实际上QPrinter也是调用的Qt封装的QCUPSSupport。注意一下QPrinter在Linux下的初始化是非常慢的。不要频繁调用。

上源码QCUPSSupport

//Qt4.8.7/qt-everywhere-opensource-src-4.8.7/src/gui/painting/ 源码在这里
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QCUPS_P_H
#define QCUPS_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "QtCore/qstring.h"
#include "QtCore/qstringlist.h"
#include "QtGui/qprinter.h"
#ifndef QT_NO_CUPS
#include <QtCore/qlibrary.h>
#include <cups/cups.h>
#include <cups/ppd.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_TYPEINFO(cups_option_t, Q_MOVABLE_TYPE | Q_PRIMITIVE_TYPE);
class QCUPSSupport
{
public:
    QCUPSSupport();
    ~QCUPSSupport();
    static bool isAvailable();
    static int cupsVersion() { return isAvailable() ? CUPS_VERSION_MAJOR*10000+CUPS_VERSION_MINOR*100+CUPS_VERSION_PATCH : 0; }
    int availablePrintersCount() const;
    const cups_dest_t* availablePrinters() const;
    int currentPrinterIndex() const;
    const ppd_file_t* setCurrentPrinter(int index);
    const ppd_file_t* currentPPD() const;
    const ppd_option_t* ppdOption(const char *key) const;
    const cups_option_t* printerOption(const QString &key) const;
    const ppd_option_t* pageSizes() const;
    int markOption(const char* name, const char* value);
    void saveOptions(QList<const ppd_option_t*> options, QList<const char*> markedOptions);
    QRect paperRect(const char *choice) const;
    QRect pageRect(const char *choice) const;
    QStringList options() const;
    static bool printerHasPPD(const char *printerName);
    QString unicodeString(const char *s);
    QPair<int, QString> tempFd();
    int printFile(const char * printerName, const char * filename, const char * title,
                  int num_options, cups_option_t * options);
private:
    void collectMarkedOptions(QStringList& list, const ppd_group_t* group = 0) const;
    void collectMarkedOptionsHelper(QStringList& list, const ppd_group_t* group) const;
    int prnCount;
    cups_dest_t *printers;
    const ppd_option_t* page_sizes;
    int currPrinterIndex;
    ppd_file_t *currPPD;
#ifndef QT_NO_TEXTCODEC
    QTextCodec *codec;
#endif
};
QT_END_NAMESPACE
#endif // QT_NO_CUPS
#endif

cpp

//Qt4.8.7/qt-everywhere-opensource-src-4.8.7/src/gui/painting/qcups.cpp

/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qdebug.h>
#include "qcups_p.h"
#ifndef QT_NO_CUPS
#ifndef QT_LINUXBASE // LSB merges everything into cups.h
# include <cups/language.h>
#endif
#include <qtextcodec.h>
QT_BEGIN_NAMESPACE
typedef int (*CupsGetDests)(cups_dest_t **dests);
typedef void (*CupsFreeDests)(int num_dests, cups_dest_t *dests);
typedef const char* (*CupsGetPPD)(const char *printer);
typedef int (*CupsMarkOptions)(ppd_file_t *ppd, int num_options, cups_option_t *options);
typedef ppd_file_t* (*PPDOpenFile)(const char *filename);
typedef void (*PPDMarkDefaults)(ppd_file_t *ppd);
typedef int (*PPDMarkOption)(ppd_file_t *ppd, const char *keyword, const char *option);
typedef void (*PPDClose)(ppd_file_t *ppd);
typedef int (*PPDMarkOption)(ppd_file_t *ppd, const char *keyword, const char *option);
typedef void (*CupsFreeOptions)(int num_options, cups_option_t *options);
typedef void (*CupsSetDests)(int num_dests, cups_dest_t *dests);
typedef cups_lang_t* (*CupsLangGet)(const char *language);
typedef const char* (*CupsLangEncoding)(cups_lang_t *language);
typedef int (*CupsAddOption)(const char *name, const char *value, int num_options, cups_option_t **options);
typedef int (*CupsTempFd)(char *name, int len);
typedef int (*CupsPrintFile)(const char * name, const char * filename, const char * title, int num_options, cups_option_t * options);
static bool cupsLoaded = false;
static int qt_cups_num_printers = 0;
static CupsGetDests _cupsGetDests = 0;
static CupsFreeDests _cupsFreeDests = 0;
static CupsGetPPD _cupsGetPPD = 0;
static PPDOpenFile _ppdOpenFile = 0;
static PPDMarkDefaults _ppdMarkDefaults = 0;
static PPDClose _ppdClose = 0;
static CupsMarkOptions _cupsMarkOptions = 0;
static PPDMarkOption _ppdMarkOption = 0;
static CupsFreeOptions _cupsFreeOptions = 0;
static CupsSetDests _cupsSetDests = 0;
static CupsLangGet _cupsLangGet = 0;
static CupsLangEncoding _cupsLangEncoding = 0;
static CupsAddOption _cupsAddOption = 0;
static CupsTempFd _cupsTempFd = 0;
static CupsPrintFile _cupsPrintFile = 0;
static void resolveCups()
{
    QLibrary cupsLib(QLatin1String("cups"), 2);
    if(cupsLib.load()) {
        _cupsGetDests = (CupsGetDests) cupsLib.resolve("cupsGetDests");
        _cupsFreeDests = (CupsFreeDests) cupsLib.resolve("cupsFreeDests");
        _cupsGetPPD = (CupsGetPPD) cupsLib.resolve("cupsGetPPD");
        _cupsLangGet = (CupsLangGet) cupsLib.resolve("cupsLangGet");
        _cupsLangEncoding = (CupsLangEncoding) cupsLib.resolve("cupsLangEncoding");
        _ppdOpenFile = (PPDOpenFile) cupsLib.resolve("ppdOpenFile");
        _ppdMarkDefaults = (PPDMarkDefaults) cupsLib.resolve("ppdMarkDefaults");
        _ppdClose = (PPDClose) cupsLib.resolve("ppdClose");
        _cupsMarkOptions = (CupsMarkOptions) cupsLib.resolve("cupsMarkOptions");
        _ppdMarkOption = (PPDMarkOption) cupsLib.resolve("ppdMarkOption");
        _cupsFreeOptions = (CupsFreeOptions) cupsLib.resolve("cupsFreeOptions");
        _cupsSetDests = (CupsSetDests) cupsLib.resolve("cupsSetDests");
        _cupsAddOption = (CupsAddOption) cupsLib.resolve("cupsAddOption");
        _cupsTempFd = (CupsTempFd) cupsLib.resolve("cupsTempFd");
        _cupsPrintFile = (CupsPrintFile) cupsLib.resolve("cupsPrintFile");
        if (_cupsGetDests && _cupsFreeDests) {
            cups_dest_t *printers;
            int num_printers = _cupsGetDests(&printers);
            if (num_printers)
                _cupsFreeDests(num_printers, printers);
            qt_cups_num_printers = num_printers;
        }
    }
    cupsLoaded = true;
}
// ================ CUPS Support class ========================
QCUPSSupport::QCUPSSupport()
    :
    prnCount(0),
    printers(0),
    page_sizes(0),
    currPrinterIndex(0),
    currPPD(0)
{
    if (!cupsLoaded)
        resolveCups();
    // getting all available printers
    if (!isAvailable())
        return;
    // Update the available printer count
    qt_cups_num_printers = prnCount = _cupsGetDests(&printers);
    for (int i = 0; i < prnCount; ++i) {
        if (printers[i].is_default) {
            currPrinterIndex = i;
            setCurrentPrinter(i);
            break;
        }
    }
#ifndef QT_NO_TEXTCODEC
    cups_lang_t *cupsLang = _cupsLangGet(0);
    codec = QTextCodec::codecForName(_cupsLangEncoding(cupsLang));
    if (!codec)
        codec = QTextCodec::codecForLocale();
#endif
}
QCUPSSupport::~QCUPSSupport()
{
     if (currPPD)
        _ppdClose(currPPD);
     if (prnCount)
         _cupsFreeDests(prnCount, printers);
}
int QCUPSSupport::availablePrintersCount() const
{
    return prnCount;
}
const cups_dest_t* QCUPSSupport::availablePrinters() const
{
    return printers;
}
const ppd_file_t* QCUPSSupport::currentPPD() const
{
    return currPPD;
}
const ppd_file_t* QCUPSSupport::setCurrentPrinter(int index)
{
    Q_ASSERT(index >= 0 && index <= prnCount);
    if (index == prnCount)
        return 0;
    currPrinterIndex = index;
    if (currPPD)
        _ppdClose(currPPD);
    currPPD = 0;
    page_sizes = 0;
    const char *ppdFile = _cupsGetPPD(printers[index].name);
    if (!ppdFile)
      return 0;
    currPPD = _ppdOpenFile(ppdFile);
    unlink(ppdFile);
    // marking default options
    _ppdMarkDefaults(currPPD);
    // marking options explicitly set
    _cupsMarkOptions(currPPD, printers[currPrinterIndex].num_options, printers[currPrinterIndex].options);
    // getting pointer to page sizes
    page_sizes = ppdOption("PageSize");
    return currPPD;
}
int QCUPSSupport::currentPrinterIndex() const
{
    return currPrinterIndex;
}
bool QCUPSSupport::isAvailable()
{
    if(!cupsLoaded)
        resolveCups();
    return _cupsGetDests &&
        _cupsFreeDests &&
        _cupsGetPPD &&
        _ppdOpenFile &&
        _ppdMarkDefaults &&
        _ppdClose &&
        _cupsMarkOptions &&
        _ppdMarkOption &&
        _cupsFreeOptions &&
        _cupsSetDests &&
        _cupsLangGet &&
        _cupsLangEncoding &&
        _cupsAddOption &&
        (qt_cups_num_printers > 0);
}
const ppd_option_t* QCUPSSupport::ppdOption(const char *key) const
{
    if (currPPD) {
        for (int gr = 0; gr < currPPD->num_groups; ++gr) {
            for (int opt = 0; opt < currPPD->groups[gr].num_options; ++opt) {
                if (qstrcmp(currPPD->groups[gr].options[opt].keyword, key) == 0)
                    return &currPPD->groups[gr].options[opt];
            }
        }
    }
    return 0;
}
const cups_option_t* QCUPSSupport::printerOption(const QString &key) const
{
    for (int i = 0; i < printers[currPrinterIndex].num_options; ++i) {
        if (QLatin1String(printers[currPrinterIndex].options[i].name) == key)
            return &printers[currPrinterIndex].options[i];
    }
    return 0;
}
const ppd_option_t* QCUPSSupport::pageSizes() const
{
    return page_sizes;
}
int QCUPSSupport::markOption(const char* name, const char* value)
{
    return _ppdMarkOption(currPPD, name, value);
}
void QCUPSSupport::saveOptions(QList<const ppd_option_t*> options, QList<const char*> markedOptions)
{
    int oldOptionCount = printers[currPrinterIndex].num_options;
    cups_option_t* oldOptions = printers[currPrinterIndex].options;
    int newOptionCount = 0;
    cups_option_t* newOptions = 0;
    // copying old options that are not on the new list
    for (int i = 0; i < oldOptionCount; ++i) {
        bool contains = false;
        for (int j = 0; j < options.count(); ++j) {
            if (qstrcmp(options.at(j)->keyword, oldOptions[i].name) == 0) {
                contains = true;
                break;
            }
        }
        if (!contains) {
            newOptionCount = _cupsAddOption(oldOptions[i].name, oldOptions[i].value, newOptionCount, &newOptions);
        }
    }
    // we can release old option list
     _cupsFreeOptions(oldOptionCount, oldOptions);
    // adding marked options
    for (int i = 0; i < markedOptions.count(); ++i) {
        const char* name = markedOptions.at(i);
        ++i;
        newOptionCount = _cupsAddOption(name, markedOptions.at(i), newOptionCount, &newOptions);
    }
    // placing the new option list
    printers[currPrinterIndex].num_options = newOptionCount;
    printers[currPrinterIndex].options = newOptions;
    // saving new default values
    _cupsSetDests(prnCount, printers);
}
QRect QCUPSSupport::paperRect(const char *choice) const
{
    if (!currPPD)
        return QRect();
    for (int i = 0; i < currPPD->num_sizes; ++i) {
        if (qstrcmp(currPPD->sizes[i].name, choice) == 0)
            return QRect(0, 0, qRound(currPPD->sizes[i].width), qRound(currPPD->sizes[i].length));
    }
    return QRect();
}
QRect QCUPSSupport::pageRect(const char *choice) const
{
    if (!currPPD)
        return QRect();
    for (int i = 0; i < currPPD->num_sizes; ++i) {
        if (qstrcmp(currPPD->sizes[i].name, choice) == 0)
            return QRect(qRound(currPPD->sizes[i].left),
                         qRound(currPPD->sizes[i].length - currPPD->sizes[i].top),
                         qRound(currPPD->sizes[i].right - currPPD->sizes[i].left),
                         qRound(currPPD->sizes[i].top - currPPD->sizes[i].bottom));
    }
    return QRect();
}
QStringList QCUPSSupport::options() const
{
    QStringList list;
    collectMarkedOptions(list);
    return list;
}
bool QCUPSSupport::printerHasPPD(const char *printerName)
{
    if (!isAvailable())
        return false;
    const char *ppdFile = _cupsGetPPD(printerName);
    if (ppdFile)
        unlink(ppdFile);
    return (ppdFile != 0);
}
QString QCUPSSupport::unicodeString(const char *s)
{
#ifndef QT_NO_TEXTCODEC
    return codec->toUnicode(s);
#else
    return QLatin1String(s);
#endif
}
void QCUPSSupport::collectMarkedOptions(QStringList& list, const ppd_group_t* group) const
{
    if (group == 0) {
        if (!currPPD)
            return;
        for (int i = 0; i < currPPD->num_groups; ++i) {
            collectMarkedOptions(list, &currPPD->groups[i]);
            collectMarkedOptionsHelper(list, &currPPD->groups[i]);
        }
    } else {
        for (int i = 0; i < group->num_subgroups; ++i)
            collectMarkedOptionsHelper(list, &group->subgroups[i]);
    }
}
void QCUPSSupport::collectMarkedOptionsHelper(QStringList& list, const ppd_group_t* group) const
{
    for (int i = 0; i < group->num_options; ++i) {
        for (int j = 0; j < group->options[i].num_choices; ++j) {
            if (group->options[i].choices[j].marked == 1 && qstrcmp(group->options[i].choices[j].choice, group->options[i].defchoice) != 0)
                list << QString::fromLocal8Bit(group->options[i].keyword) << QString::fromLocal8Bit(group->options[i].choices[j].choice);
        }
    }
}
QPair<int, QString> QCUPSSupport::tempFd()
{
    char filename[512];
    int fd = _cupsTempFd(filename, 512);
    return QPair<int, QString>(fd, QString::fromLocal8Bit(filename));
}
// Prints the given file and returns a job id.
int QCUPSSupport::printFile(const char * printerName, const char * filename, const char * title,
                            int num_options, cups_option_t * options)
{
    return _cupsPrintFile(printerName, filename, title, num_options, options);
}
QT_END_NAMESPACE
#endif // QT_NO_CUPS

总代码量大概也就600行,简单的来讲,Qt通过函数resolveCups()把cups的库函数封装了起来。别说一个博士生,估计一个刚上班的本科生看这点代码去适配Linux下的驱动小半天也就搞完了,更何况Qt都已经封装好了给大家使用了。

下边的代码是cups所有的库函数。

typedef int (*CupsGetDests)(cups_dest_t **dests);
typedef void (*CupsFreeDests)(int num_dests, cups_dest_t *dests);
typedef const char* (*CupsGetPPD)(const char *printer);
typedef int (*CupsMarkOptions)(ppd_file_t *ppd, int num_options, cups_option_t *options);
typedef ppd_file_t* (*PPDOpenFile)(const char *filename);
typedef void (*PPDMarkDefaults)(ppd_file_t *ppd);
typedef int (*PPDMarkOption)(ppd_file_t *ppd, const char *keyword, const char *option);
typedef void (*PPDClose)(ppd_file_t *ppd);
typedef int (*PPDMarkOption)(ppd_file_t *ppd, const char *keyword, const char *option);
typedef void (*CupsFreeOptions)(int num_options, cups_option_t *options);
typedef void (*CupsSetDests)(int num_dests, cups_dest_t *dests);
typedef cups_lang_t* (*CupsLangGet)(const char *language);
typedef const char* (*CupsLangEncoding)(cups_lang_t *language);
typedef int (*CupsAddOption)(const char *name, const char *value, int num_options, cups_option_t **options);
typedef int (*CupsTempFd)(char *name, int len);
typedef int (*CupsPrintFile)(const char * name, const char * filename, const char * title, int num_options, cups_option_t * options);
static bool cupsLoaded = false;
static int qt_cups_num_printers = 0;
static CupsGetDests _cupsGetDests = 0;
static CupsFreeDests _cupsFreeDests = 0;
static CupsGetPPD _cupsGetPPD = 0;
static PPDOpenFile _ppdOpenFile = 0;
static PPDMarkDefaults _ppdMarkDefaults = 0;
static PPDClose _ppdClose = 0;
static CupsMarkOptions _cupsMarkOptions = 0;
static PPDMarkOption _ppdMarkOption = 0;
static CupsFreeOptions _cupsFreeOptions = 0;
static CupsSetDests _cupsSetDests = 0;
static CupsLangGet _cupsLangGet = 0;
static CupsLangEncoding _cupsLangEncoding = 0;
static CupsAddOption _cupsAddOption = 0;
static CupsTempFd _cupsTempFd = 0;
static CupsPrintFile _cupsPrintFile = 0;

Qt的QPrinter打印对话框中的属性设置实际上就是调用的QCUPSSupport来设置的属性,对于程序员来讲,只需要关心QCUPSSupport外边暴露的接口就可以了。

TIPS

实际上QCUPSSupport在使用中还是有两个比较严重的问题,但是都不是Qt的锅,基本上都是CUPS的问题

QCUPSSupport初始化过慢

QCUPSSupport初始化过慢,慢到了解析ppd驱动文件上。相应的QPrinter的构造函数也比较慢,所以尽量要申请一个QPrinter的变量来控制打印机属性,或者少用。当然这是代码设计的问题了。

_ppdMarkOption

_ppdMarkOption 这个函数是设置打印机属性的。成功返回0,失败返回非0。

ppd驱动文件中有很多打印机的属性设置。

有的打印机某几个选项是冲突的,比如有的打印机可以设置A5纸张,但是不能同时设置双面打印。如果同时设置,实际上是设置失效的。比如,这时候,你先设置好了纸张大小A5,这时候再去设置打印机属性双面长边打印。这时候_ppdMarkOption是返回非0。(也就是这个设置失败了)。但是,敲重点,但是,即使设置错误,双面打印的属性依旧设置到了该打印机中,这时候再去打印,实际上是不会双面打印出来的。

上Github上看了看 CUPS中 _ppdMarkOption的实现源码,发现这个函数设计就是这样的,即使设置失败也不会设置回去原来的值。这样只能由我们调用者去兼容这个恶心的函数接口了。

至于为啥没有上Qt5的源码

Qt5的工程结构已经变了cups的代码一部分呢已经放到qpainter里了。原理没变。

上一篇:
下一篇:

 评论


 已有2条评论

  1. rekols 还差那么一点 Linux | 谷歌浏览器 71.0.3578.80 1周前

    路过。

    • 张小飞 真爱 Windows 10 | 火狐浏览器 63.0 2天前

      rekols你的文档更新的怎么样了