採用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閱讀器並非遙不可及。目前該閱讀器僅完成了基本的功能,後續會逐步完善,敬請期待。