// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // // Class: dviRenderer // // Class for rendering TeX DVI files. // Part of KDVI- A previewer for TeX DVI files. // // (C) 2001-2005 Stefan Kebekus // Distributed under the GPL // #include #include "dviRenderer.h" #include "dviFile.h" #include "dvisourcesplitter.h" #include "hyperlink.h" #include "kvs_debug.h" #include "prebookmark.h" #include "psgs.h" //#include "renderedDviPagePixmap.h" #include "dviPageInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_DVIRENDERER QPainter *foreGroundPainter; // QPainter used for text //------ now comes the dviRenderer class implementation ---------- dviRenderer::dviRenderer() : dviFile(0), resolutionInDPI(0), embedPS_progress(0), embedPS_numOfProgressedFiles(0), shrinkfactor(3), source_href(0), HTML_href(0), editorCommand(""), PostScriptOutPutString(0), PS_interface(new ghostscript_interface), _postscript(true), line_boundary_encountered(false), word_boundary_encountered(false), current_page(0), penWidth_in_mInch(0), number_of_elements_in_path(0), currentlyDrawnPage(0), m_eventLoop(0) { #ifdef DEBUG_DVIRENDERER //kDebug(kvs::dvi) << "dviRenderer( parent=" << par << " )"; #endif // connect(&font_pool, SIGNAL( setStatusBarText( const QString& ) ), this, SIGNAL( setStatusBarText( const QString& ) ) ); // connect( &clearStatusBarTimer, SIGNAL(timeout()), this, SLOT(clearStatusBar()) ); // pass status bar messages through // connect(PS_interface, SIGNAL( setStatusBarText( const QString& ) ), this, SIGNAL( setStatusBarText( const QString& ) ) ); } dviRenderer::~dviRenderer() { #ifdef DEBUG_DVIRENDERER kDebug(kvs::dvi) << "~dviRenderer"; #endif QMutexLocker locker(&mutex); delete PS_interface; delete dviFile; } #if 0 void dviRenderer::setPrefs(bool flag_showPS, const QString &str_editorCommand, bool useFontHints ) { //QMutexLocker locker(&mutex); _postscript = flag_showPS; editorCommand = str_editorCommand; font_pool.setParameters( useFontHints ); } void dviRenderer::showInfo() { QMutexLocker locker(&mutex); info->setDVIData(dviFile); info->show(); } #endif //------ this function calls the dvi interpreter ---------- void dviRenderer::drawPage(RenderedDocumentPagePixmap* page) { #ifdef DEBUG_DVIRENDERER //kDebug(kvs::dvi) << "dviRenderer::drawPage(documentPage *) called, page number " << page->pageNumber; #endif // Paranoid safety checks if (page == 0) { kError(kvs::dvi) << "dviRenderer::drawPage(documentPage *) called with argument == 0" << endl; return; } // Paranoid safety checks if (page->pageNumber == 0) { kError(kvs::dvi) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number 0" << endl; return; } QMutexLocker locker(&mutex); if ( dviFile == 0 ) { kError(kvs::dvi) << "dviRenderer::drawPage(documentPage *) called, but no dviFile class allocated." << endl; page->clear(); return; } if (page->pageNumber > dviFile->total_pages) { kError(kvs::dvi) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number " << page->pageNumber << " but the current dviFile has only " << dviFile->total_pages << " pages." << endl; return; } if ( dviFile->dvi_Data() == 0 ) { kError(kvs::dvi) << "dviRenderer::drawPage(documentPage *) called, but no dviFile is loaded yet." << endl; page->clear(); return; } double resolution = page->resolution; if (resolution != resolutionInDPI) setResolution(resolution); currentlyDrawnPage = page; shrinkfactor = 1200/resolutionInDPI; current_page = page->pageNumber-1; // Reset colors colorStack.clear(); globalColor = Qt::black; int pageWidth = page->width; int pageHeight = page->height; QImage img(pageWidth, pageHeight, QImage::Format_RGB32); foreGroundPainter = new QPainter(&img); if (foreGroundPainter != 0) { errorMsg.clear(); draw_page(); delete foreGroundPainter; foreGroundPainter = 0; } else { kDebug(kvs::dvi) << "painter creation failed."; } page->img = img; //page->setImage(img); // Postprocess hyperlinks // Without that, based on the way TeX draws certain characters like german "Umlaute", // some hyperlinks would be broken into two overlapping parts, in the middle of a word. QVector::iterator i = page->hyperLinkList.begin(); QVector::iterator j; while (i != page->hyperLinkList.end()) { // Iterator j always points to the element after i. j = i; j++; if (j == page->hyperLinkList.end()) break; Hyperlink& hi = *i; Hyperlink& hj = *j; bool merged = false; // Merge all hyperlinks that point to the same target, and have the same baseline. while (hi.linkText == hj.linkText && hi.baseline == hj.baseline) { merged = true; hi.box = hi.box.unite(hj.box); j++; if (j == page->hyperLinkList.end()) break; hj = *j; } if (merged) { i = page->hyperLinkList.erase(++i, j); } else { i++; } } #if 0 page->isEmpty = false; if (errorMsg.isEmpty() != true) { KMessageBox::detailedError(parentWidget, i18n("File corruption Okular could not interprete your DVI file. This is " "most commonly caused by a corrupted file."), errorMsg, i18n("DVI File Error")); errorMsg.clear(); currentlyDrawnPage = 0; return; } // Tell the user (once) if the DVI file contains source specials // ... we don't want our great feature to go unnoticed. RenderedDviPagePixmap* currentDVIPage = dynamic_cast(currentlyDrawnPage); if (currentDVIPage) { if ((dviFile->sourceSpecialMarker == true) && (currentDVIPage->sourceHyperLinkList.size() > 0)) { dviFile->sourceSpecialMarker = false; // Show the dialog as soon as event processing is finished, and // the program is idle //FIXME //QTimer::singleShot( 0, this, SLOT(showThatSourceInformationIsPresent()) ); } } #endif currentlyDrawnPage = 0; } void dviRenderer::getText(RenderedDocumentPagePixmap* page) { bool postscriptBackup = _postscript; // Disable postscript-specials temporarely to speed up text extraction. _postscript = false; drawPage(page); _postscript = postscriptBackup; } /* void dviRenderer::showThatSourceInformationIsPresent() { // In principle, we should use a KMessagebox here, but we want to // add a button "Explain in more detail..." which opens the // Helpcenter. Thus, we practically re-implement the KMessagebox // here. Most of the code is stolen from there. // Check if the 'Don't show again' feature was used KConfig *config = KGlobal::config(); KConfigGroup saver(config, "Notification Messages"); bool showMsg = config->readEntry( "KDVI-info_on_source_specials", true); if (showMsg) { KDialogBase dialog(i18n("KDVI: Information"), KDialogBase::Yes, KDialogBase::Yes, KDialogBase::Yes, parentWidget, "information", true, true, KStandardGuiItem::ok()); KVBox *topcontents = new KVBox (&dialog); topcontents->setSpacing(KDialog::spacingHint()*2); topcontents->setMargin(KDialog::marginHint()*2); QWidget *contents = new QWidget(topcontents); QHBoxLayout * lay = new QHBoxLayout(contents); lay->setSpacing(KDialog::spacingHint()*2); lay->addStretch(1); QLabel *label1 = new QLabel( contents); label1->setPixmap(QMessageBox::standardIcon(QMessageBox::Information)); lay->addWidget(label1); QLabel *label2 = new QLabel( i18n("This DVI file contains source file information. You may click into the text with the " "middle mouse button, and an editor will open the TeX-source file immediately."), contents); label2->setMinimumSize(label2->sizeHint()); lay->addWidget(label2); lay->addStretch(1); QSize extraSize = QSize(50,30); QCheckBox *checkbox = new QCheckBox(i18n("Do not show this message again"), topcontents); extraSize = QSize(50,0); dialog.setHelpLinkText(i18n("Explain in more detail...")); dialog.setHelp("inverse-search", "kdvi"); dialog.enableLinkedHelp(true); dialog.setMainWidget(topcontents); dialog.enableButtonSeparator(false); dialog.incInitialSize( extraSize ); dialog.exec(); showMsg = !checkbox->isChecked(); if (!showMsg) { KConfigGroup saver(config, "Notification Messages"); config->writeEntry( "KDVI-info_on_source_specials", showMsg); } config->sync(); } } */ void dviRenderer::embedPostScript() { #ifdef DEBUG_DVIRENDERER kDebug(kvs::dvi) << "dviRenderer::embedPostScript()"; #endif if (!dviFile) return; /* embedPS_progress = new KProgressDialog(parentWidget, i18n("Embedding PostScript Files"), QString(), true); */ if (!embedPS_progress) return; embedPS_progress->setAllowCancel(false); embedPS_progress->showCancelButton(false); embedPS_progress->setMinimumDuration(400); embedPS_progress->progressBar()->setMaximum(dviFile->numberOfExternalPSFiles); embedPS_progress->progressBar()->setValue(0); embedPS_numOfProgressedFiles = 0; quint16 currPageSav = current_page; errorMsg.clear(); for(current_page=0; current_page < dviFile->total_pages; current_page++) { if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = 0; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = NULL; prescan(&dviRenderer::prescan_embedPS); } delete embedPS_progress; embedPS_progress = 0; if (!errorMsg.isEmpty()) { errorMsg = "" + errorMsg + ""; // KMessageBox::detailedError(parentWidget, "" + i18n("Not all PostScript files could be embedded into your document.") + "", errorMsg); errorMsg.clear(); } else /* KMessageBox::information(parentWidget, "" + i18n("All external PostScript files were embedded into your document. You " "will probably want to save the DVI file now.") + "", QString(), "embeddingDone");*/ // Prescan phase starts here #ifdef PERFORMANCE_MEASUREMENT //kDebug(kvs::dvi) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms"; //QTime preScanTimer; //preScanTimer.start(); #endif dviFile->numberOfExternalPSFiles = 0; prebookmarks.clear(); for(current_page=0; current_page < dviFile->total_pages; current_page++) { PostScriptOutPutString = new QString(); if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = 0; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = NULL; prescan(&dviRenderer::prescan_parseSpecials); if (!PostScriptOutPutString->isEmpty()) PS_interface->setPostScript(current_page, *PostScriptOutPutString); delete PostScriptOutPutString; } PostScriptOutPutString = NULL; #ifdef PERFORMANCE_MEASUREMENT //kDebug(kvs::dvi) << "Time required for prescan phase: " << preScanTimer.restart() << "ms"; #endif current_page = currPageSav; _isModified = true; } bool dviRenderer::isValidFile(const QString& filename) const { QFile f(filename); if (!f.open(QIODevice::ReadOnly)) return false; unsigned char test[4]; if ( f.read( (char *)test,2)<2 || test[0] != 247 || test[1] != 2 ) return false; int n = f.size(); if ( n < 134 ) // Too short for a dvi file return false; f.seek( n-4 ); unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf }; if ( f.read( (char *)test, 4 )<4 || strncmp( (char *)test, (char *) trailer, 4 ) ) return false; // We suppose now that the dvi file is complete and OK return true; } bool dviRenderer::setFile(const QString &fname, const KUrl &base) { #ifdef DEBUG_DVIRENDERER kDebug(kvs::dvi) << "dviRenderer::setFile( fname='" << fname << "' )"; //, ref='" << ref << "', sourceMarker=" << sourceMarker << " )"; #endif //QMutexLocker lock(&mutex); QFileInfo fi(fname); QString filename = fi.absoluteFilePath(); // If fname is the empty string, then this means: "close". Delete // the dvifile and the pixmap. if (fname.isEmpty()) { // Delete DVI file /* if(info) info->setDVIData(0);*/ delete dviFile; dviFile = 0; return true; } // Make sure the file actually exists. if (!fi.exists() || fi.isDir()) { /* KMessageBox::error( parentWidget, i18n("File error. The specified file '%1' does not exist. " "KDVI already tried to add the ending '.dvi'.", filename), i18n("File Error")); */ return false; } QApplication::setOverrideCursor( Qt::WaitCursor ); dvifile *dviFile_new = new dvifile(filename, &font_pool); if ((dviFile == 0) || (dviFile->filename != filename)) dviFile_new->sourceSpecialMarker = true; else dviFile_new->sourceSpecialMarker = false; if ((dviFile_new->dvi_Data() == NULL)||(dviFile_new->errorMsg.isEmpty() != true)) { QApplication::restoreOverrideCursor(); if (dviFile_new->errorMsg.isEmpty() != true) /* KMessageBox::detailedError(parentWidget, i18n("File corruption. KDVI could not interprete your DVI file. This is " "most commonly caused by a corrupted file."), dviFile_new->errorMsg, i18n("DVI File Error"));*/ delete dviFile_new; return false; } delete dviFile; dviFile = dviFile_new; numPages = dviFile->total_pages; /* if(info) info->setDVIData(dviFile); */ _isModified = false; baseURL = base; font_pool.setExtraSearchPath( fi.absolutePath() ); font_pool.setCMperDVIunit( dviFile->getCmPerDVIunit() ); // Extract PostScript from the DVI file, and store the PostScript // specials in PostScriptDirectory, and the headers in the // PostScriptHeaderString. PS_interface->clear(); // If the DVI file comes from a remote URL (e.g. downloaded from a // web server), we limit the PostScript files that can be accessed // by this file to the download directory, in order to limit the // possibilities of a denial of service attack. QString includePath; if (!baseURL.isLocalFile()) { includePath = filename; includePath.truncate(includePath.lastIndexOf('/')); } PS_interface->setIncludePath(includePath); // We will also generate a list of hyperlink-anchors and source-file // anchors in the document. So declare the existing lists empty. //anchorList.clear(); sourceHyperLinkAnchors.clear(); //bookmarks.clear(); prebookmarks.clear(); if (dviFile->page_offset.isEmpty() == true) return false; // Locate fonts. font_pool.locateFonts(); // Update the list of fonts in the info window //if (info != 0) //info->setFontInfo(&font_pool); // We should pre-scan the document now (to extract embedded, // PostScript, Hyperlinks, ets). // PRESCAN STARTS HERE #ifdef PERFORMANCE_MEASUREMENT //kDebug(kvs::dvi) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms"; //QTime preScanTimer; //preScanTimer.start(); #endif dviFile->numberOfExternalPSFiles = 0; quint16 currPageSav = current_page; prebookmarks.clear(); for(current_page=0; current_page < dviFile->total_pages; current_page++) { PostScriptOutPutString = new QString(); if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = 0; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = NULL; prescan(&dviRenderer::prescan_parseSpecials); if (!PostScriptOutPutString->isEmpty()) PS_interface->setPostScript(current_page, *PostScriptOutPutString); delete PostScriptOutPutString; } PostScriptOutPutString = NULL; #if 0 // Generate the list of bookmarks bookmarks.clear(); Q3PtrStack stack; stack.setAutoDelete (false); QVector::iterator it; for( it = prebookmarks.begin(); it != prebookmarks.end(); ++it ) { Bookmark *bmk = new Bookmark((*it).title, findAnchor((*it).anchorName)); if (stack.isEmpty()) bookmarks.append(bmk); else { stack.top()->subordinateBookmarks.append(bmk); stack.remove(); } for(int i=0; i<(*it).noOfChildren; i++) stack.push(bmk); } prebookmarks.clear(); #endif #ifdef PERFORMANCE_MEASUREMENT //kDebug(kvs::dvi) << "Time required for prescan phase: " << preScanTimer.restart() << "ms"; #endif current_page = currPageSav; // PRESCAN ENDS HERE pageSizes.resize(0); if (dviFile->suggestedPageSize != 0) { // Fill the vector pageSizes with total_pages identical entries pageSizes.fill(*(dviFile->suggestedPageSize), dviFile->total_pages); } QApplication::restoreOverrideCursor(); return true; } Anchor dviRenderer::parseReference(const QString &reference) { QMutexLocker locker(&mutex); #ifdef DEBUG_DVIRENDERER kError(kvs::dvi) << "dviRenderer::parseReference( " << reference << " ) called" << endl; #endif if (dviFile == 0) return Anchor(); // case 1: The reference is a number, which we'll interpret as a // page number. bool ok; int page = reference.toInt ( &ok ); if (ok == true) { if (page < 0) page = 0; if (page > dviFile->total_pages) page = dviFile->total_pages; return Anchor(page, Length() ); } // case 2: The reference is of form "src:1111Filename", where "1111" // points to line number 1111 in the file "Filename". KDVI then // looks for source specials of the form "src:xxxxFilename", and // tries to find the special with the biggest xxxx if (reference.indexOf("src:", 0, Qt::CaseInsensitive) == 0) { // Extract the file name and the numeral part from the reference string DVI_SourceFileSplitter splitter(reference, dviFile->filename); quint32 refLineNumber = splitter.line(); QString refFileName = splitter.filePath(); if (sourceHyperLinkAnchors.isEmpty()) { /* KMessageBox::sorry(parentWidget, i18n("You have asked KDVI to locate the place in the DVI file which corresponds to " "line %1 in the TeX-file %2. It seems, however, that the DVI file " "does not contain the necessary source file information. " "We refer to the manual of KDVI for a detailed explanation on how to include this " "information. Press the F1 key to open the manual.", refLineNumber, refFileName), i18n("Could Not Find Reference")); */ return Anchor(); } // Go through the list of source file anchors, and find the anchor // whose line number is the biggest among those that are smaller // than the refLineNumber. That way, the position in the DVI file // which is highlighted is always a little further up than the // position in the editor, e.g. if the DVI file contains // positional information at the beginning of every paragraph, // KDVI jumps to the beginning of the paragraph that the cursor is // in, and never to the next paragraph. If source file anchors for // the refFileName can be found, but none of their line numbers is // smaller than the refLineNumber, the reason is most likely, that // the cursor in the editor stands somewhere in the preamble of // the LaTeX file. In that case, we jump to the beginning of the // document. bool anchorForRefFileFound = false; // Flag that is set if source file anchors for the refFileName could be found at all QVector::iterator bestMatch = sourceHyperLinkAnchors.end(); QVector::iterator it; for( it = sourceHyperLinkAnchors.begin(); it != sourceHyperLinkAnchors.end(); ++it ) if (refFileName.trimmed() == it->fileName.trimmed() || refFileName.trimmed() == it->fileName.trimmed() + ".tex" ) { anchorForRefFileFound = true; if ( (it->line <= refLineNumber) && ( (bestMatch == sourceHyperLinkAnchors.end()) || (it->line > bestMatch->line) ) ) bestMatch = it; } if (bestMatch != sourceHyperLinkAnchors.end()) return Anchor(bestMatch->page, bestMatch->distance_from_top); else if (anchorForRefFileFound == false) { /* KMessageBox::sorry(parentWidget, i18n("KDVI was not able to locate the place in the DVI file which corresponds to " "line %1 in the TeX-file %2.", refLineNumber, refFileName), i18n( "Could Not Find Reference" )); */ } else return Anchor(); return Anchor(); } return Anchor(); } void dviRenderer::setResolution(double resolution_in_DPI) { // Ignore minute changes. The difference to the current value would // hardly be visible anyway. That saves a lot of re-painting, // e.g. when the user resizes the window, and a flickery mouse // changes the window size by 1 pixel all the time. if (fabs(resolutionInDPI-resolution_in_DPI) < 1) return; resolutionInDPI = resolution_in_DPI; // Pass the information on to the font pool. font_pool.setDisplayResolution( resolutionInDPI ); shrinkfactor = 1200/resolutionInDPI; return; } void dviRenderer::clearStatusBar() { //emit setStatusBarText( QString() ); } void dviRenderer::handleSRCLink(const QString &linkText, const QPoint& point, DocumentWidget *win) { Q_UNUSED( linkText ); Q_UNUSED( point ); Q_UNUSED( win ); #if 0 KSharedPtr editor(new DVISourceEditor(*this, parentWidget, linkText, point, win)); if (editor->started()) editor_ = editor; #endif } QString dviRenderer::PDFencodingToQString(const QString& _pdfstring) { // This method locates special PDF characters in a string and // replaces them by UTF8. See Section 3.2.3 of the PDF reference // guide for information. QString pdfstring = _pdfstring; pdfstring = pdfstring.replace("\\n", "\n"); pdfstring = pdfstring.replace("\\r", "\n"); pdfstring = pdfstring.replace("\\t", "\t"); pdfstring = pdfstring.replace("\\f", "\f"); pdfstring = pdfstring.replace("\\(", "("); pdfstring = pdfstring.replace("\\)", ")"); pdfstring = pdfstring.replace("\\\\", "\\"); // Now replace octal character codes with the characters they encode int pos; QRegExp rx( "(\\\\)(\\d\\d\\d)" ); // matches "\xyz" where x,y,z are numbers while((pos = rx.indexIn( pdfstring )) != -1) { pdfstring = pdfstring.replace(pos, 4, QChar(rx.cap(2).toInt(0,8))); } rx.setPattern( "(\\\\)(\\d\\d)" ); // matches "\xy" where x,y are numbers while((pos = rx.indexIn( pdfstring )) != -1) { pdfstring = pdfstring.replace(pos, 3, QChar(rx.cap(2).toInt(0,8))); } rx.setPattern( "(\\\\)(\\d)" ); // matches "\x" where x is a number while((pos = rx.indexIn( pdfstring )) != -1) { pdfstring = pdfstring.replace(pos, 4, QChar(rx.cap(2).toInt(0,8))); } return pdfstring; } void dviRenderer::exportPDF() { /* KSharedPtr exporter(new DVIExportToPDF(*this, parentWidget)); if (exporter->started()) all_exports_[exporter.data()] = exporter; */ } void dviRenderer::exportPS(const QString& fname, const QStringList& options, QPrinter* printer) { KSharedPtr exporter(new DVIExportToPS(*this, parentWidget, fname, options, printer)); if (exporter->started()) all_exports_[exporter.data()] = exporter; } /* void dviRenderer::update_info_dialog(const QString& text, bool clear) { if (clear) info->clear(text); else info->outputReceiver(text); } void dviRenderer::editor_finished(const DVISourceEditor*) { editor_.attach(0); } */ void dviRenderer::export_finished(const DVIExport* key) { typedef QMap > ExportMap; ExportMap::iterator it = all_exports_.find(key); if (it != all_exports_.end()) all_exports_.remove(key); } void dviRenderer::setEventLoop(QEventLoop *el) { if (el == NULL) { delete m_eventLoop; m_eventLoop = NULL; } else m_eventLoop = el; } #include "dviRenderer.moc"