/*************************************************************************** * Copyright (C) 2005 by Stefan Kebekus * * kebekus@kde.org * * * * Copyright (C) 2005 by Wilfried Huss * * Wilfried.Huss@gmx.at * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "GBitmap.h" #include "BSByteStream.h" #include "IFFByteStream.h" #include "prefs.h" #include "documentWidget.h" #include "djvurenderer.h" #include "djvumultipage.h" #include "hyperlink.h" #include "renderedDocumentPagePixmap.h" #include "textBox.h" //#define KF_DEBUG inline GUTF8String GStringFromTQString(const TQString& x) { GUTF8String retval=(const char*)x.utf8(); return retval; } inline TQString TQStringFromGString(const GUTF8String& x) { TQString retval=TQString::fromUtf8((const char*)x); return retval; } DjVuRenderer::DjVuRenderer(TQWidget* par) : DocumentRenderer(par) { #ifdef KF_DEBUG kdError() << "DjVuRenderer( parent=" << par << " )" << endl; #endif PPMstream = ByteStream::create(); } DjVuRenderer::~DjVuRenderer() { #ifdef KF_DEBUG kdDebug() << "~DjVuRenderer" << endl; #endif // Wait for all access to this documentRenderer to finish TQMutexLocker locker( &mutex ); } void DjVuRenderer::drawPage(double resolution, RenderedDocumentPage* page) { #ifdef KF_DEBUG kdDebug() << "DjVuRenderer::drawPage(documentPage*) called, page number " << page->getPageNumber() << endl; #endif // Paranoid safety checks if (page == 0) { kdError() << "DjVuRenderer::drawPage(documentPage*) called with argument == 0" << endl; return; } if (page->getPageNumber() == 0) { kdError() << "DjVuRenderer::drawPage(documentPage*) called for a documentPage with page number 0" << endl; return; } // Wait for all access to this documentRenderer to finish TQMutexLocker locker( &mutex ); // more paranoid safety checks if (page->getPageNumber() > numPages) { kdError() << "DjVuRenderer::drawPage(documentPage*) called for a documentPage with page number " << page->getPageNumber() << " but the current fax file has only " << numPages << " pages." << endl; return; } int pageNumber = page->getPageNumber() - 1; GP djvuPage = document->get_page(pageNumber, true); if (!djvuPage->wait_for_complete_decode()) { kdDebug() << "decoding failed." << endl; return; } if (!pageSizes[pageNumber].isValid()) { int djvuResolution = djvuPage->get_dpi(); int djvuPageWidth = djvuPage->get_width(); int djvuPageHeight = djvuPage->get_height(); Length w, h; w.setLength_in_inch(djvuPageWidth / (double)djvuResolution); h.setLength_in_inch(djvuPageHeight / (double)djvuResolution); pageSizes[pageNumber].setPageSize(w, h); SimplePageSize ps = sizeOfPage(page->getPageNumber()); // If we are not printing we need to resize the pixmap. RenderedDocumentPagePixmap* pagePixmap = dynamic_cast(page); if (pagePixmap) pagePixmap->resize(ps.sizeInPixel(resolution)); } //kdDebug() << "render page " << pageNumber + 1 << " at size (" << pageWidth << ", " << pageHeight << ")" << endl; int pageHeight = page->height(); int pageWidth = page->width(); GRect pageRect(0, 0, pageWidth, pageHeight); GP djvuPixmap; if (Prefs::renderMode() == Prefs::EnumRenderMode::Color) djvuPixmap = djvuPage->get_pixmap(pageRect, pageRect); else if (Prefs::renderMode() == Prefs::EnumRenderMode::Foreground) djvuPixmap = djvuPage->get_fg_pixmap(pageRect, pageRect); else if (Prefs::renderMode() == Prefs::EnumRenderMode::Background) djvuPixmap = djvuPage->get_bg_pixmap(pageRect, pageRect); TQPainter* foreGroundPaint = page->getPainter(); if (foreGroundPaint != 0) { if(djvuPixmap && Prefs::renderMode() != Prefs::EnumRenderMode::BlackAndWhite) { PPMstream->seek(0); djvuPixmap->save_ppm(*PPMstream); long pixmapsize = PPMstream->tell(); PPMstream->seek(0); uchar* buf = new uchar[pixmapsize]; long bytesRead = PPMstream->readall(buf, pixmapsize); bool ok = pixmap.loadFromData(buf, bytesRead, "PPM"); if (!ok) { kdError() << "loading failed" << endl; //draw an empty page foreGroundPaint->fillRect(0, 0, pageWidth, pageHeight, TQt::white); } foreGroundPaint->drawPixmap(0, 0, pixmap); delete[] buf; /* for (int i = 0; i < pageHeight; i++) { GPixel* pixmapRow = (*djvuPixmap)[i]; for (int j = 0; j < pageWidth; j++) { GPixel pixel = pixmapRow[j]; foreGroundPaint->setPen(TQColor(pixel.r, pixel.g, pixel.b)); foreGroundPaint->drawPoint(j, pageHeight - i - 1); } }*/ } else { GP djvuBitmap = djvuPage->get_bitmap(pageRect, pageRect); if(djvuBitmap) { PPMstream->seek(0); if(djvuBitmap->get_grays() == 2) djvuBitmap->save_pbm(*PPMstream); else djvuBitmap->save_pgm(*PPMstream); long pixmapsize = PPMstream->tell(); PPMstream->seek(0); uchar* buf = new uchar[pixmapsize]; long bytesRead = PPMstream->readall(buf, pixmapsize); bool ok = pixmap.loadFromData(buf, bytesRead, "PPM"); if (!ok) { kdError() << "loading failed" << endl; //draw an empty page foreGroundPaint->fillRect(0, 0, pageWidth, pageHeight, TQt::white); } foreGroundPaint->drawPixmap(0, 0, pixmap); delete[] buf; /* for (int i = 0; i < pageHeight; i++) { unsigned char* bitmapRow = (*djvuBitmap)[i]; for (int j = 0; j < pageWidth; j++) { unsigned char pixel = 255-bitmapRow[j]; foreGroundPaint->setPen(TQColor(pixel, pixel, pixel)); foreGroundPaint->drawPoint(j, pageHeight - i - 1); } }*/ } else { //draw an empty page foreGroundPaint->fillRect(0, 0, pageWidth, pageHeight, TQt::white); } } //kdDebug() << "rendering page " << pageNumber + 1 << " at size (" << pageWidth << ", " << pageHeight << ") finished." << endl; page->returnPainter(foreGroundPaint); } GP pageText = getText(pageNumber); if (pageText) { TQSize djvuPageSize(djvuPage->get_width(), djvuPage->get_real_height()); fillInText(page, pageText, pageText->page_zone, djvuPageSize); //kdDebug() << "Text of page " << pageNumber << endl; //kdDebug() << (const char*)pageText->textUTF8 << endl; } getAnnotations(page, djvuPage); page->isEmpty = false; } bool DjVuRenderer::setFile(const TQString &fname, const KURL &) { #ifdef KF_DEBUG kdDebug() << "DjVuRenderer::setFile(" << fname << ") called" << endl; #endif // Wait for all access to this documentRenderer to finish TQMutexLocker locker( &mutex ); // If fname is the empty string, then this means: "close". if (fname.isEmpty()) { kdDebug() << "DjVuRenderer::setFile( ... ) called with empty filename. Closing the file." << endl; return true; } // Paranoid saftey checks: make sure the file actually exists, and // that it is a file, not a directory. Otherwise, show an error // message and exit.. TQFileInfo fi(fname); TQString filename = fi.absFilePath(); if (!fi.exists() || fi.isDir()) { KMessageBox::error( parentWidget, i18n("File error. The specified file '%1' does not exist.").arg(filename), i18n("File Error")); // the return value 'false' indicates that this operation was not successful. return false; } // Clear previously loaded document clear(); // Now we assume that the file is fine and load the file. G_TRY { document = DjVuDocEditor::create_wait(GURL::Filename::UTF8(GStringFromTQString(filename))); } G_CATCH(ex) { ; } G_ENDCATCH; // If the above assumption was false. if (!document) { KMessageBox::error( parentWidget, i18n("File error. The specified file '%1' could not be loaded.").arg(filename), i18n("File Error")); clear(); kdDebug(1223) << "Loading of document failed." << endl; return false; } bool r = initializeDocument(); // the return value 'true' indicates that this operation was successful. return r; } void DjVuRenderer::getAnnotations(RenderedDocumentPage* page, GP djvuPage) { GP annotations = djvuPage->get_anno(); if (!(annotations && annotations->size())) return; GP ant = DjVuANT::create(); GP iff = IFFByteStream::create(annotations); GUTF8String chkid; while (iff->get_chunk(chkid)) { if (chkid == "ANTa") { ant->merge(*iff->get_bytestream()); } else if (chkid == "ANTz") { GP bsiff = BSByteStream::create(iff->get_bytestream()); ant->merge(*bsiff); } iff->close_chunk(); } if (!ant->is_empty()) { // Scaling factors for the coordinate conversion. // TODO: Refractor this into a function shared with fillInText. int pageWidth = page->width(); int pageHeight = page->height(); double scaleX = pageWidth / (double)djvuPage->get_width(); double scaleY = pageHeight / (double)djvuPage->get_height(); GPList map = ant->map_areas; for (GPosition pos = map; pos; ++pos) { // Currently we only support rectangular links if (!map[pos]->get_shape_type() == GMapArea::RECT) continue; GRect rect = map[pos]->get_bound_rect(); TQRect hyperlinkRect((int)(rect.xmin*scaleX+0.5), (int)((djvuPage->get_height()-rect.ymax)*scaleY+0.5), (int)(rect.width()*scaleX +0.5), (int)(rect.height()*scaleY+0.5)); TQString url((const char*)map[pos]->url); TQString target((const char*)map[pos]->target); TQString comment((const char*)map[pos]->comment); // Create an anchor for this link. if (!anchorList.contains(url)) { // For now we only accept links to pages in the same document. if(url[0] == '#' && target == "_self") { bool conversionOk; PageNumber targetPage = url.remove('#').toInt(&conversionOk); if (conversionOk) anchorList[url] = Anchor(targetPage, Length()); } } Hyperlink hyperlink(hyperlinkRect.bottom(), hyperlinkRect, url); page->hyperLinkList.push_back(hyperlink); } } } bool DjVuRenderer::initializeDocument() { if (document == 0) return false; if (!document->wait_for_complete_init()) { kdDebug() << "Document Initialization failed." << endl; return false; } // Set the number of pages page sizes numPages = document->get_pages_num(); pageSizes.resize(numPages); Length w,h; // Set the page sizes in the pageSizes array. Give feedback for // very long documents if (numPages > 100) emit setStatusBarText(i18n("Loading file. Computing page sizes...")); for(TQ_UINT16 i=0; iprocessEvents(); GP djvuFile = document->get_djvu_file(i); int resolution; int pageWidth; int pageHeight; bool ok = getPageInfo(djvuFile, pageWidth, pageHeight, resolution); if (!ok) kdError() << "Decoding info of page " << i << " failed." << endl; else { w.setLength_in_inch(pageWidth / (double)resolution); h.setLength_in_inch(pageHeight / (double)resolution); pageSizes[i].setPageSize(w, h); } } emit setStatusBarText(TQString()); // We will also generate a list of hyperlink-anchors in the document. // So declare the existing lists empty. anchorList.clear(); return true; } GP DjVuRenderer::getText(PageNumber pageNumber) { GUTF8String chkid; const GP file = document->get_djvu_file(pageNumber); const GP bs(file->get_text()); if (bs) { long int i=0; const GP iff(IFFByteStream::create(bs)); while (iff->get_chunk(chkid)) { i++; if (chkid == GUTF8String("TXTa")) { GP txt = DjVuTXT::create(); txt->decode(iff->get_bytestream()); return txt; } else if (chkid == GUTF8String("TXTz")) { GP txt = DjVuTXT::create(); GP bsiff=BSByteStream::create(iff->get_bytestream()); txt->decode(bsiff); return txt; } iff->close_chunk(); } } return 0; } void DjVuRenderer::fillInText(RenderedDocumentPage* page, const GP& text, DjVuTXT::Zone& zone, TQSize& djvuPageSize) { if (zone.children.isempty()) { int pageWidth = page->width(); int pageHeight = page->height(); double scaleX = pageWidth / (double)djvuPageSize.width(); double scaleY = pageHeight / (double)djvuPageSize.height(); TQString zoneString = TQStringFromGString(text->textUTF8.substr(zone.text_start, zone.text_length)); //kdDebug() << "zone text: " << zoneString << endl; TQRect textRect((int)(zone.rect.xmin*scaleX+0.5), (int)((djvuPageSize.height()-zone.rect.ymax)*scaleY+0.5), (int)(zone.rect.width()*scaleX+0.5), (int)(zone.rect.height()*scaleY+0.5)); //kdDebug() << "zone rect: " << textRect.x() << ", " << textRect.y() << ", " << textRect.width() << ", " << textRect.height() << endl; TextBox textBox(textRect, zoneString); page->textBoxList.push_back(textBox); } else { for (GPosition pos=zone.children; pos; ++pos) { fillInText(page, text, zone.children[pos], djvuPageSize); } } } bool DjVuRenderer::getPageInfo(GP file, int& width, int& height, int& dpi) { if (!file || !file->is_all_data_present()) return false; const GP pbs(file->get_djvu_bytestream(false, false)); const GP iff(IFFByteStream::create(pbs)); GUTF8String chkid; if (iff->get_chunk(chkid)) { if (chkid == "FORM:DJVU") { while (iff->get_chunk(chkid) && chkid!="INFO") iff->close_chunk(); if (chkid == "INFO") { GP gbs = iff->get_bytestream(); GP info=DjVuInfo::create(); info->decode(*gbs); int rot = ((360-GRect::findangle(info->orientation))/90)%4; width = (rot&1) ? info->height : info->width; height = (rot&1) ? info->width : info->height; dpi = info->dpi; return true; } } else if (chkid == "FORM:BM44" || chkid == "FORM:PM44") { while (iff->get_chunk(chkid) && chkid!="BM44" && chkid!="PM44") iff->close_chunk(); if (chkid=="BM44" || chkid=="PM44") { GP gbs = iff->get_bytestream(); if (gbs->read8() == 0) { gbs->read8(); gbs->read8(); unsigned char xhi = gbs->read8(); unsigned char xlo = gbs->read8(); unsigned char yhi = gbs->read8(); unsigned char ylo = gbs->read8(); width = (xhi<<8)+xlo; height = (yhi<<8)+ylo; dpi = 100; return true; } } } } return false; } void DjVuRenderer::getText(RenderedDocumentPage* page) { TQMutexLocker locker( &mutex ); int pageNumber = page->getPageNumber() - 1; GP pageText = getText(pageNumber); if (pageText) { GP djvuFile = document->get_djvu_file(pageNumber); int resolution; int pageWidth; int pageHeight; bool ok = getPageInfo(djvuFile, pageWidth, pageHeight, resolution); if (ok) { TQSize djvuPageSize(pageWidth, pageHeight); fillInText(page, pageText, pageText->page_zone, djvuPageSize); } } } bool DjVuRenderer::convertToPSFile( DjVuToPS &converter, TQString filename, TQValueList &pageList ) { if (document == 0) { kdError(1223) << "DjVuRenderer::convertToPSFile(..) called when document was 0" << endl; return false; } TQMutexLocker locker( &mutex ); // Set up progress dialog KProgressDialog *pdialog = new KProgressDialog(parentWidget, "Printing-ProgressDialog", i18n("Printing..."), i18n("Preparing pages for printing..."), true); pdialog->setButtonText(i18n("Abort")); pdialog->showCancelButton(true); pdialog->progressBar()->setTotalSteps(pageList.size()); pdialog->progressBar()->setFormat(TQString()); // Open output file GURL outname = GURL::Filename::UTF8(GStringFromTQString(filename)); GP obs = ByteStream::create(outname, "w"); TQString pagename; TQValueList::ConstIterator it = pageList.begin(); while (true) { pagename += TQString::number(*it); ++it; if (it == pageList.end()) break; pagename += ","; } GUTF8String pages = GStringFromTQString(pagename); converter.set_info_cb(printerInfoCallBack, (void*)pdialog); bool iscancelled = false; G_TRY { converter.print(*obs, (DjVuDocument *)document, pages ); } G_CATCH(ex) { iscancelled = true; } G_ENDCATCH; delete pdialog; // This is to keep the GUI updated. kapp->processEvents(); obs->flush(); return !iscancelled; } void DjVuRenderer::deletePages(TQ_UINT16 from, TQ_UINT16 to) { // Paranoia security checks if (document == 0) { kdError(1223) << "DjVuRenderer::deletePages(...) called when no document was loaded" << endl; return; } if ((from > to) || (from == 0) || (from > totalPages()) || (to > totalPages())) { kdError(1223) << "DjVuRenderer::deletePages(...) called with invalid arguments" << endl; return; } TQMutexLocker locker( &mutex ); KProgressDialog *pdialog = 0; if (to-from > 9) { pdialog = new KProgressDialog(parentWidget, "Printing-ProgressDialog", i18n("Deleting pages..."), i18n("Please wait while pages are removed..."), true); pdialog->showCancelButton(false); pdialog->progressBar()->setTotalSteps(to-from+1); pdialog->progressBar()->setFormat(TQString()); pdialog->show(); kapp->processEvents(); } // set the document pointer temporarily to 0, so that no-one tries // to render a page while we are deleting pages GP document_new = document; document = 0; // Delete pages if (pdialog == 0) { GList pageList; for(TQ_UINT16 i=from; i<= to; i++) pageList.append(i-1); document_new->remove_pages(pageList); } else { for(TQ_UINT16 i=from; i<=to; i++) { document_new->remove_page(from-1); pdialog->progressBar()->setProgress(i-from); pdialog->progressBar()->setFormat(i18n("deleting page %1").arg(i)); kapp->processEvents(); } delete pdialog; } _isModified = true; document = document_new; initializeDocument(); } bool DjVuRenderer::save(const TQString &filename) { if (document == 0) { kdError() << "DjVuRenderer::save(..) called when document==0" << endl; return false; } TQMutexLocker locker( &mutex ); G_TRY { document->save_as(GURL::Filename::UTF8(GStringFromTQString(filename)), true); } G_CATCH(ex) { return false; } G_ENDCATCH; document->save_as(GURL::Filename::UTF8(filename.ascii()), true); if (TQFile::exists(filename) == false) return false; _isModified = false; return true; } void DjVuRenderer::printerInfoCallBack(int page_num, int page_count, int, DjVuToPS::Stage, void *pd) { if (pd == 0) return; // Update the progress dialog. KProgressDialog *pdialog = (KProgressDialog *)pd; pdialog->progressBar()->setProgress(page_count); pdialog->progressBar()->setFormat(i18n("processing page %1").arg(page_num+1)); pdialog->show(); if (pdialog->wasCancelled()) G_THROW("STOP"); // This is to keep the GUI updated. kapp->processEvents(); } #include "djvurenderer.moc"