採用QT技術,開發OFD電子文檔閱讀器

前言 ofd作為板式文檔規範,相當於國產化的pdf。由於pdf標準制定的較早,相關生態也比較完備,市面上的pdf閱讀器種類繁多。中國ofd閱讀器寥寥無幾,作者此前採用wpf開發了一款閱讀器,但該閱讀器只能在windows上運行。若實現跨平台運行,採用QT開發應該是首選。筆者並無QT開發經驗,但有多年MFC開發經驗,又對ofd研究多年;編程到達一定境界考驗的就是思維,在學習QT的過程中,感覺都是熟悉的味道的。邊學習邊開發,終於完成了一款簡易的ofd閱讀器。簡述開發思路,希望對讀者有所啟發。

程式下載地址:百度網盤: //pan.baidu.com/s/1_uyTWCP_jFOd0YR1-1Yapw 提取碼: cxpv。

功能簡述:

 閱讀器實現了縮放、旋轉、選中、複製、單雙頁顯示等功能。

開發思路解析

ofd閱讀器顯示的內容包括:文字、圖形、圖等,稱之為圖元;閱讀器可能需要顯示成千上萬個圖元。採用qt完成此功能,有多重方案可供選擇,選擇方案時必須考慮下列因素:1)顯示的性能。2)圖元與滑鼠鍵盤的交互。我選擇了「Graphics View Framework 圖形視圖框架」;程式處理的邏輯見下圖:

ofd解壓:

  ofd本身就是壓縮文件,和zip後綴的文件處理完全一樣。解壓縮採用QuaZip庫。作者在此庫基礎上作了進一步封裝,使之更便於使用。

OfdFileReader::OfdFileReader()
{
    _pZipInfo = nullptr;
    _file = nullptr;
}

OfdFileReader::~OfdFileReader()
{
    MemManage::instance()->Delete(_pZipInfo);
    MemManage::instance()->Delete(_file);
}

bool OfdFileReader::Open(QString fileName)
{
     MemManage::instance()->Delete( _file);

    _file =MemManage::instance()->New<QFile,QString>(fileName);

    if (!_file->open(QIODevice::ReadOnly))
        return false;

    _ofdFileName = fileName;
    return Open(_file);
}

bool OfdFileReader::Open(QIODevice *ioDevice)
{
     MemManage::instance()->Delete(_pZipInfo);

    _pZipInfo =MemManage::instance()->New<QuaZip,QIODevice*>(ioDevice);
    bool isOpen = _pZipInfo->open(QuaZip::mdUnzip);
    if(!isOpen)
        return false;

    _listFilePath.clear();
    GetAllZipInfo();

    return true;
}

QString OfdFileReader::GetFileFullName()
{
    return _ofdFileName;
}

QString OfdFileReader::GetFileShortName()
{
    QFileInfo fileInfo(_ofdFileName);
    return fileInfo.baseName();
}

void OfdFileReader::GetAllZipInfo()
{
    for (bool f = _pZipInfo->goToFirstFile(); f;f=_pZipInfo->goToNextFile())
    {
        QString relativePath = _pZipInfo->getCurrentFileName();
        _listFilePath.append(relativePath);

        //qDebug() << relativePath;
    }
}

int OfdFileReader::GetFileCount()
{
    return _listFilePath.count();
}

QString OfdFileReader::GetFilePath(int index)
{
    return _listFilePath[index];
}

QStringList OfdFileReader::GetFilePathList()
{
    return _listFilePath;
}

QByteArray OfdFileReader::GetFileContent(const QString& relativePath)
{
    if(relativePath.size()==0)
    {
        QByteArray empty;
        return empty;
    }

    _pZipInfo->setCurrentFile(relativePath);

    QuaZipFile  zFile(_pZipInfo,0);
    if(!zFile.open(QIODevice::ReadOnly))
    {
        QByteArray empty;
        return empty;
    }

    QByteArray ba = zFile.readAll();
    zFile.close();
    return ba;
}

xml解析

  ofd主要是由xml文本和資源文件組成。qt解析xml有兩個庫:DOM解析(QDomDocument)和流式解析(QXmlStreamReader)。DOM解析使用起來簡單,但是性能慢;流式解析反之。從性能角度考慮,作者採用了流式解析的方法。

Qt Graphics View Framework 圖形視圖框架

  繪製大量圖元最佳方案就是採用qt提供的「Graphics View Framework」架構。此架構確保高效的繪製大量圖元,又能快速的根據區域定位到圖元。該架構採用面向對象的方法處理圖元,減輕了開發難度。圖元的描述稱之為scene,圖元顯示為view。一個scene可以由多個view展示。首先需要將ofd頁面中文字、線、圖等元素轉換成對應的scene。以顯示文字為例,定義類型 class OfdVisualItemText : public QGraphicsObject。需要實現兩個虛函數:

   QRectF boundingRect() const override;
   void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
paint函數根據scene數據,繪製對應的文字。第一次繪製時,須記錄每個文字的區域;滑鼠滑動時,根據選擇區域與每個文字的關係,確定文字是否被選中。
void OfdVisualItemText::paint(QPainter *painter,
                              const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    painter->setRenderHint(QPainter::TextAntialiasing);
    painter->setBrush(Qt::black);
    painter->setPen(Qt::black);

    SetPen(painter);
    SetFont(painter);

    //SetCTMTransfer(painter);
    if(_isFirstPaint)
    {
        SetCTMTransfer();
    }

    if(_isSelect)
    {
        QList<QRectData*> selectData = _boundingRectManage.GetSelectData(_selectPolygon);
        foreach(QRectData *item,selectData)
        {
            painter->fillRect(item->rect,*OfdViewParam::TextSelectBrush);
        }
    }

    OfdPageItemText *itemText = (OfdPageItemText*)_ofdPageItem;

    int charCount = itemText->TextCode.GetCharCount();
    QChar charItem;
    float x;
    float y;

    QRectF textboundingRect;
    QRectF textClipRect;

    float baseline = GetBaseline();
    for(int i=0;i<charCount;i++)
    {
        itemText->TextCode.GetChar(i,charItem,x,y);
        double xPixel = OfdConvert::OfdMMToPixel(x);
        double yPixel = OfdConvert::OfdMMToPixel(y);
        QString textChar(charItem);

        textClipRect.setRect(xPixel,yPixel-baseline,10000,10000);
        painter->drawText(textClipRect,0,textChar,&textboundingRect);

        AdjustTextRect(textboundingRect);

        if(_isFirstPaint )
        {
            TextInfo *textInfo = MemManage::instance()->New<TextInfo>();
            textInfo->Text = textChar;
            _textCharInfoGroup.append(textInfo);
            _boundingRectManage.AddRect(textboundingRect,textInfo);
        }
    }

    _isFirstPaint = false;
}

 閱讀器操作截圖

後記:理清思路,選對框架是成功的第一步。qt作為一款優秀的跨平台框架,為方便我們開發提供了大量的類庫。在充分理解ofd的基礎上,配合qt的「Graphics View Framework」框架,開發ofd閱讀器並非遙不可及。目前該閱讀器僅完成了基本的功能,後續會逐步完善,敬請期待。

 

Tags: