|
|
|
//
|
|
|
|
// 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 <config.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <tqcheckbox.h>
|
|
|
|
#include <tqclipboard.h>
|
|
|
|
#include <tqcursor.h>
|
|
|
|
#include <tqlabel.h>
|
|
|
|
#include <tqfileinfo.h>
|
|
|
|
#include <tqlayout.h>
|
|
|
|
#include <tqmessagebox.h>
|
|
|
|
#include <tqpaintdevice.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqptrstack.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqurl.h>
|
|
|
|
#include <tqvbox.h>
|
|
|
|
|
|
|
|
#include <tdeapplication.h>
|
|
|
|
#include <tdemessagebox.h>
|
|
|
|
#include <kmimemagic.h>
|
|
|
|
#include <tdeglobal.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <keditcl.h>
|
|
|
|
#include <tdefiledialog.h>
|
|
|
|
#include <tdeio/job.h>
|
|
|
|
#include <tdeio/netaccess.h>
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <kprinter.h>
|
|
|
|
#include <kprocess.h>
|
|
|
|
#include <kprogress.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <kstdguiitem.h>
|
|
|
|
|
|
|
|
#include "documentWidget.h"
|
|
|
|
#include "dviFile.h"
|
|
|
|
#include "dviRenderer.h"
|
|
|
|
#include "fontpool.h"
|
|
|
|
#include "fontprogress.h"
|
|
|
|
#include "hyperlink.h"
|
|
|
|
#include "infodialog.h"
|
|
|
|
#include "kdvi_multipage.h"
|
|
|
|
#include "performanceMeasurement.h"
|
|
|
|
#include "prebookmark.h"
|
|
|
|
#include "psgs.h"
|
|
|
|
#include "xdvi.h"
|
|
|
|
#include "zoomlimits.h"
|
|
|
|
#include "dvisourcesplitter.h"
|
|
|
|
#include "renderedDviPagePixmap.h"
|
|
|
|
|
|
|
|
//#define DEBUG_DVIRENDERER
|
|
|
|
|
|
|
|
TQPainter *foreGroundPainter; // TQPainter used for text
|
|
|
|
|
|
|
|
|
|
|
|
//------ now comes the dviRenderer class implementation ----------
|
|
|
|
|
|
|
|
dviRenderer::dviRenderer(TQWidget *par)
|
|
|
|
: DocumentRenderer(par), info(new infoDialog(par))
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_DVIRENDERER
|
|
|
|
kdDebug(4300) << "dviRenderer( parent=" << par << " )" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// initialize the dvi machinery
|
|
|
|
dviFile = 0;
|
|
|
|
|
|
|
|
connect(&font_pool, TQ_SIGNAL( setStatusBarText( const TQString& ) ), this, TQ_SIGNAL( setStatusBarText( const TQString& ) ) );
|
|
|
|
|
|
|
|
parentWidget = par;
|
|
|
|
shrinkfactor = 3;
|
|
|
|
current_page = 0;
|
|
|
|
resolutionInDPI = 0.0;
|
|
|
|
|
|
|
|
connect( &clearStatusBarTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(clearStatusBar()) );
|
|
|
|
|
|
|
|
currentlyDrawnPage = 0;
|
|
|
|
editorCommand = "";
|
|
|
|
|
|
|
|
PostScriptOutPutString = NULL;
|
|
|
|
HTML_href = NULL;
|
|
|
|
_postscript = 0;
|
|
|
|
|
|
|
|
// Storage used for dvips and friends, i.e. for the "export" functions.
|
|
|
|
proc = 0;
|
|
|
|
progress = 0;
|
|
|
|
export_printer = 0;
|
|
|
|
export_fileName = "";
|
|
|
|
export_tmpFileName = "";
|
|
|
|
export_errorString = "";
|
|
|
|
|
|
|
|
PS_interface = new ghostscript_interface();
|
|
|
|
// pass status bar messages through
|
|
|
|
connect(PS_interface, TQ_SIGNAL( setStatusBarText( const TQString& ) ), this, TQ_SIGNAL( setStatusBarText( const TQString& ) ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dviRenderer::~dviRenderer()
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_DVIRENDERER
|
|
|
|
kdDebug(4300) << "~dviRenderer" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mutex.lock();
|
|
|
|
mutex.unlock();
|
|
|
|
|
|
|
|
delete PS_interface;
|
|
|
|
delete proc;
|
|
|
|
delete dviFile;
|
|
|
|
// Don't delete the export printer. This is owned by the
|
|
|
|
// kdvi_multipage.
|
|
|
|
export_printer = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void dviRenderer::setPrefs(bool flag_showPS, const TQString &str_editorCommand, bool useFontHints )
|
|
|
|
{
|
|
|
|
TQMutexLocker locker(&mutex);
|
|
|
|
_postscript = flag_showPS;
|
|
|
|
editorCommand = str_editorCommand;
|
|
|
|
font_pool.setParameters( useFontHints );
|
|
|
|
emit(documentIsChanged());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void dviRenderer::showInfo()
|
|
|
|
{
|
|
|
|
mutex.lock();
|
|
|
|
info->setDVIData(dviFile);
|
|
|
|
info->show();
|
|
|
|
mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//------ this function calls the dvi interpreter ----------
|
|
|
|
|
|
|
|
|
|
|
|
void dviRenderer::drawPage(double resolution, RenderedDocumentPage *page)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_DVIRENDERER
|
|
|
|
kdDebug(4300) << "dviRenderer::drawPage(documentPage *) called, page number " << page->getPageNumber() << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Paranoid safety checks
|
|
|
|
if (page == 0) {
|
|
|
|
kdError(4300) << "dviRenderer::drawPage(documentPage *) called with argument == 0" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (page->getPageNumber() == 0) {
|
|
|
|
kdError(4300) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number 0" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex.lock();
|
|
|
|
if ( dviFile == 0 ) {
|
|
|
|
kdError(4300) << "dviRenderer::drawPage(documentPage *) called, but no dviFile class allocated." << endl;
|
|
|
|
page->clear();
|
|
|
|
mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (page->getPageNumber() > dviFile->total_pages) {
|
|
|
|
kdError(4300) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number " << page->getPageNumber()
|
|
|
|
<< " but the current dviFile has only " << dviFile->total_pages << " pages." << endl;
|
|
|
|
mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( dviFile->dvi_Data() == 0 ) {
|
|
|
|
kdError(4300) << "dviRenderer::drawPage(documentPage *) called, but no dviFile is loaded yet." << endl;
|
|
|
|
page->clear();
|
|
|
|
mutex.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolution != resolutionInDPI)
|
|
|
|
setResolution(resolution);
|
|
|
|
|
|
|
|
currentlyDrawnPage = page;
|
|
|
|
shrinkfactor = 1200/resolutionInDPI;
|
|
|
|
current_page = page->getPageNumber()-1;
|
|
|
|
|
|
|
|
|
|
|
|
// Reset colors
|
|
|
|
colorStack.clear();
|
|
|
|
globalColor = TQt::black;
|
|
|
|
|
|
|
|
TQApplication::setOverrideCursor( waitCursor );
|
|
|
|
foreGroundPainter = page->getPainter();
|
|
|
|
if (foreGroundPainter != 0) {
|
|
|
|
errorMsg = TQString();
|
|
|
|
draw_page();
|
|
|
|
page->returnPainter(foreGroundPainter);
|
|
|
|
}
|
|
|
|
TQApplication::restoreOverrideCursor();
|
|
|
|
|
|
|
|
page->isEmpty = false;
|
|
|
|
if (errorMsg.isEmpty() != true) {
|
|
|
|
KMessageBox::detailedError(parentWidget,
|
|
|
|
i18n("<qt><strong>File corruption!</strong> KDVI had trouble interpreting your DVI file. Most "
|
|
|
|
"likely this means that the DVI file is broken.</qt>"),
|
|
|
|
errorMsg, i18n("DVI File Error"));
|
|
|
|
errorMsg = TQString();
|
|
|
|
currentlyDrawnPage = 0;
|
|
|
|
mutex.unlock();
|
|
|
|
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<RenderedDviPagePixmap*>(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
|
|
|
|
TQTimer::singleShot( 0, this, TQ_SLOT(showThatSourceInformationIsPresent()) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
currentlyDrawnPage = 0;
|
|
|
|
mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void dviRenderer::getText(RenderedDocumentPage* page)
|
|
|
|
{
|
|
|
|
bool postscriptBackup = _postscript;
|
|
|
|
// Disable postscript-specials temporarely to speed up text extraction.
|
|
|
|
_postscript = false;
|
|
|
|
|
|
|
|
drawPage(100.0, 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
|
|
|
|
TDEConfig *config = kapp->config();
|
|
|
|
TDEConfigGroupSaver saver( config, "Notification Messages" );
|
|
|
|
bool showMsg = config->readBoolEntry( "KDVI-info_on_source_specials", true);
|
|
|
|
|
|
|
|
if (showMsg) {
|
|
|
|
KDialogBase *dialog= new KDialogBase(i18n("KDVI: Information"), KDialogBase::Yes, KDialogBase::Yes, KDialogBase::Yes,
|
|
|
|
parentWidget, "information", true, true,KStdGuiItem::ok() );
|
|
|
|
|
|
|
|
TQVBox *topcontents = new TQVBox (dialog);
|
|
|
|
topcontents->setSpacing(KDialog::spacingHint()*2);
|
|
|
|
topcontents->setMargin(KDialog::marginHint()*2);
|
|
|
|
|
|
|
|
TQWidget *contents = new TQWidget(topcontents);
|
|
|
|
TQHBoxLayout * lay = new TQHBoxLayout(contents);
|
|
|
|
lay->setSpacing(KDialog::spacingHint()*2);
|
|
|
|
|
|
|
|
lay->addStretch(1);
|
|
|
|
TQLabel *label1 = new TQLabel( contents);
|
|
|
|
label1->setPixmap(TQMessageBox::standardIcon(TQMessageBox::Information));
|
|
|
|
lay->add( label1 );
|
|
|
|
TQLabel *label2 = new TQLabel( i18n("<qt>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.</qt>"),
|
|
|
|
contents);
|
|
|
|
label2->setMinimumSize(label2->sizeHint());
|
|
|
|
lay->add( label2 );
|
|
|
|
lay->addStretch(1);
|
|
|
|
TQSize extraSize = TQSize(50,30);
|
|
|
|
TQCheckBox *checkbox = new TQCheckBox(i18n("Do not show this message again"), topcontents);
|
|
|
|
extraSize = TQSize(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();
|
|
|
|
delete dialog;
|
|
|
|
|
|
|
|
showMsg = !checkbox->isChecked();
|
|
|
|
if (!showMsg) {
|
|
|
|
TDEConfigGroupSaver saver( config, "Notification Messages" );
|
|
|
|
config->writeEntry( "KDVI-info_on_source_specials", showMsg);
|
|
|
|
}
|
|
|
|
config->sync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void dviRenderer::embedPostScript()
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_DVIRENDERER
|
|
|
|
kdDebug(4300) << "dviRenderer::embedPostScript()" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!dviFile)
|
|
|
|
return;
|
|
|
|
|
|
|
|
embedPS_progress = new KProgressDialog(parentWidget, "embedPSProgressDialog",
|
|
|
|
i18n("Embedding PostScript Files"), TQString(), true);
|
|
|
|
if (!embedPS_progress)
|
|
|
|
return;
|
|
|
|
embedPS_progress->setAllowCancel(false);
|
|
|
|
embedPS_progress->showCancelButton(false);
|
|
|
|
embedPS_progress->setMinimumDuration(400);
|
|
|
|
embedPS_progress->progressBar()->setTotalSteps(dviFile->numberOfExternalPSFiles);
|
|
|
|
embedPS_progress->progressBar()->setProgress(0);
|
|
|
|
embedPS_numOfProgressedFiles = 0;
|
|
|
|
|
|
|
|
|
|
|
|
TQ_UINT16 currPageSav = current_page;
|
|
|
|
errorMsg = TQString();
|
|
|
|
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[current_page];
|
|
|
|
end_pointer = dviFile->dvi_Data() + dviFile->page_offset[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;
|
|
|
|
|
|
|
|
if (!errorMsg.isEmpty()) {
|
|
|
|
errorMsg = "<qt>" + errorMsg + "</qt>";
|
|
|
|
KMessageBox::detailedError(parentWidget, "<qt>" + i18n("Not all PostScript files could be embedded into your document.") + "</qt>", errorMsg);
|
|
|
|
errorMsg = TQString();
|
|
|
|
} else
|
|
|
|
KMessageBox::information(parentWidget, "<qt>" + i18n("All external PostScript files were embedded into your document. You "
|
|
|
|
"will probably want to save the DVI file now.") + "</qt>",
|
|
|
|
TQString(), "embeddingDone");
|
|
|
|
|
|
|
|
// Prescan phase starts here
|
|
|
|
#ifdef PERFORMANCE_MEASUREMENT
|
|
|
|
kdDebug(4300) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms" << endl;
|
|
|
|
TQTime preScanTimer;
|
|
|
|
preScanTimer.start();
|
|
|
|
#endif
|
|
|
|
dviFile->numberOfExternalPSFiles = 0;
|
|
|
|
prebookmarks.clear();
|
|
|
|
for(current_page=0; current_page < dviFile->total_pages; current_page++) {
|
|
|
|
PostScriptOutPutString = new TQString();
|
|
|
|
|
|
|
|
if (current_page < dviFile->total_pages) {
|
|
|
|
command_pointer = dviFile->dvi_Data() + dviFile->page_offset[current_page];
|
|
|
|
end_pointer = dviFile->dvi_Data() + dviFile->page_offset[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
|
|
|
|
kdDebug(4300) << "Time required for prescan phase: " << preScanTimer.restart() << "ms" << endl;
|
|
|
|
#endif
|
|
|
|
current_page = currPageSav;
|
|
|
|
_isModified = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool dviRenderer::isValidFile(const TQString& filename) const
|
|
|
|
{
|
|
|
|
TQFile f(filename);
|
|
|
|
if (!f.open(IO_ReadOnly))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
unsigned char test[4];
|
|
|
|
if ( f.readBlock( (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.at( n-4 );
|
|
|
|
|
|
|
|
unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf };
|
|
|
|
|
|
|
|
if ( f.readBlock( (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 TQString &fname, const KURL &base)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_DVIRENDERER
|
|
|
|
kdDebug(4300) << "dviRenderer::setFile( fname='" << fname << "', ref='" << ref << "', sourceMarker=" << sourceMarker << " )" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
|
|
|
|
TQFileInfo fi(fname);
|
|
|
|
TQString filename = fi.absFilePath();
|
|
|
|
|
|
|
|
// If fname is the empty string, then this means: "close". Delete
|
|
|
|
// the dvifile and the pixmap.
|
|
|
|
if (fname.isEmpty()) {
|
|
|
|
// Delete DVI file
|
|
|
|
info->setDVIData(0);
|
|
|
|
delete dviFile;
|
|
|
|
dviFile = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure the file actually exists.
|
|
|
|
if (!fi.exists() || fi.isDir()) {
|
|
|
|
KMessageBox::error( parentWidget,
|
|
|
|
i18n("<qt><strong>File error.</strong> The specified file '%1' does not exist. "
|
|
|
|
"KDVI already tried to add the ending '.dvi'.</qt>").arg(filename),
|
|
|
|
i18n("File Error!"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we are really loading a DVI file, and complain about the
|
|
|
|
// mime type, if the file is not DVI. Perhaps we should move the
|
|
|
|
// procedure later to the kviewpart, instead of the implementaton in
|
|
|
|
// the multipage.
|
|
|
|
TQString mimetype( KMimeMagic::self()->findFileType( fname )->mimeType() );
|
|
|
|
if (mimetype != "application/x-dvi") {
|
|
|
|
KMessageBox::sorry( parentWidget,
|
|
|
|
i18n( "<qt>Could not open file <nobr><strong>%1</strong></nobr> which has "
|
|
|
|
"type <strong>%2</strong>. KDVI can only load DVI (.dvi) files.</qt>" )
|
|
|
|
.arg( fname )
|
|
|
|
.arg( mimetype ) );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the file is a valid DVI file.
|
|
|
|
if (!isValidFile(filename))
|
|
|
|
{
|
|
|
|
KMessageBox::sorry( parentWidget,
|
|
|
|
i18n("<qt>File corruption! KDVI had trouble interpreting your DVI file. Most "
|
|
|
|
"likely this means that the DVI file is broken.</qt>")
|
|
|
|
.arg( fname ) );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQApplication::setOverrideCursor( 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)) {
|
|
|
|
TQApplication::restoreOverrideCursor();
|
|
|
|
if (dviFile_new->errorMsg.isEmpty() != true)
|
|
|
|
KMessageBox::detailedError(parentWidget,
|
|
|
|
i18n("<qt>File corruption! KDVI had trouble interpreting your DVI file. Most "
|
|
|
|
"likely this means that the DVI file is broken.</qt>"),
|
|
|
|
dviFile_new->errorMsg, i18n("DVI File Error"));
|
|
|
|
delete dviFile_new;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete dviFile;
|
|
|
|
dviFile = dviFile_new;
|
|
|
|
numPages = dviFile->total_pages;
|
|
|
|
info->setDVIData(dviFile);
|
|
|
|
_isModified = false;
|
|
|
|
baseURL = base;
|
|
|
|
|
|
|
|
font_pool.setExtraSearchPath( fi.dirPath(true) );
|
|
|
|
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.
|
|
|
|
TQString includePath;
|
|
|
|
if (!baseURL.isLocalFile()) {
|
|
|
|
includePath = filename;
|
|
|
|
includePath.truncate(includePath.findRev('/'));
|
|
|
|
}
|
|
|
|
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
|
|
|
|
kdDebug(4300) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms" << endl;
|
|
|
|
TQTime preScanTimer;
|
|
|
|
preScanTimer.start();
|
|
|
|
#endif
|
|
|
|
dviFile->numberOfExternalPSFiles = 0;
|
|
|
|
TQ_UINT16 currPageSav = current_page;
|
|
|
|
prebookmarks.clear();
|
|
|
|
|
|
|
|
for(current_page=0; current_page < dviFile->total_pages; current_page++) {
|
|
|
|
PostScriptOutPutString = new TQString();
|
|
|
|
|
|
|
|
if (current_page < dviFile->total_pages) {
|
|
|
|
command_pointer = dviFile->dvi_Data() + dviFile->page_offset[current_page];
|
|
|
|
end_pointer = dviFile->dvi_Data() + dviFile->page_offset[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;
|
|
|
|
|
|
|
|
// Generate the list of bookmarks
|
|
|
|
bookmarks.clear();
|
|
|
|
TQPtrStack<Bookmark> stack;
|
|
|
|
stack.setAutoDelete (false);
|
|
|
|
TQValueVector<PreBookmark>::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();
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef PERFORMANCE_MEASUREMENT
|
|
|
|
kdDebug(4300) << "Time required for prescan phase: " << preScanTimer.restart() << "ms" << endl;
|
|
|
|
#endif
|
|
|
|
current_page = currPageSav;
|
|
|
|
// PRESCAN ENDS HERE
|
|
|
|
|
|
|
|
|
|
|
|
pageSizes.resize(0);
|
|
|
|
if (dviFile->suggestedPageSize != 0) {
|
|
|
|
// Fill the vector pageSizes with total_pages identical entries
|
|
|
|
pageSizes.resize(dviFile->total_pages, *(dviFile->suggestedPageSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQApplication::restoreOverrideCursor();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Anchor dviRenderer::parseReference(const TQString &reference)
|
|
|
|
{
|
|
|
|
mutex.lock();
|
|
|
|
|
|
|
|
#ifdef DEBUG_DVIRENDERER
|
|
|
|
kdError(4300) << "dviRenderer::parseReference( " << reference << " ) called" << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (dviFile == 0) {
|
|
|
|
mutex.unlock();
|
|
|
|
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;
|
|
|
|
|
|
|
|
mutex.unlock();
|
|
|
|
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.find("src:",0,false) == 0) {
|
|
|
|
|
|
|
|
// Extract the file name and the numeral part from the reference string
|
|
|
|
DVI_SourceFileSplitter splitter(reference, dviFile->filename);
|
|
|
|
TQ_UINT32 refLineNumber = splitter.line();
|
|
|
|
TQString refFileName = splitter.filePath();
|
|
|
|
|
|
|
|
if (sourceHyperLinkAnchors.isEmpty()) {
|
|
|
|
KMessageBox::sorry(parentWidget, i18n("<qt>You have asked KDVI to locate the place in the DVI file which corresponds to "
|
|
|
|
"line %1 in the TeX-file <strong>%2</strong>. 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.</qt>").arg(refLineNumber).arg(refFileName),
|
|
|
|
i18n("Could Not Find Reference"));
|
|
|
|
mutex.unlock();
|
|
|
|
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
|
|
|
|
|
|
|
|
TQValueVector<DVI_SourceFileAnchor>::iterator bestMatch = sourceHyperLinkAnchors.end();
|
|
|
|
TQValueVector<DVI_SourceFileAnchor>::iterator it;
|
|
|
|
for( it = sourceHyperLinkAnchors.begin(); it != sourceHyperLinkAnchors.end(); ++it )
|
|
|
|
if (refFileName.stripWhiteSpace() == it->fileName.stripWhiteSpace()
|
|
|
|
|| refFileName.stripWhiteSpace() == it->fileName.stripWhiteSpace() + ".tex"
|
|
|
|
) {
|
|
|
|
anchorForRefFileFound = true;
|
|
|
|
|
|
|
|
if ( (it->line <= refLineNumber) &&
|
|
|
|
( (bestMatch == sourceHyperLinkAnchors.end()) || (it->line > bestMatch->line) ) )
|
|
|
|
bestMatch = it;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bestMatch != sourceHyperLinkAnchors.end()) {
|
|
|
|
mutex.unlock();
|
|
|
|
return Anchor(bestMatch->page, bestMatch->distance_from_top);
|
|
|
|
} else
|
|
|
|
if (anchorForRefFileFound == false)
|
|
|
|
KMessageBox::sorry(parentWidget, i18n("<qt>KDVI was not able to locate the place in the DVI file which corresponds to "
|
|
|
|
"line %1 in the TeX-file <strong>%2</strong>.</qt>").arg(refLineNumber).arg(refFileName),
|
|
|
|
i18n( "Could Not Find Reference" ));
|
|
|
|
else {
|
|
|
|
mutex.unlock();
|
|
|
|
return Anchor();
|
|
|
|
}
|
|
|
|
mutex.unlock();
|
|
|
|
return Anchor();
|
|
|
|
}
|
|
|
|
mutex.unlock();
|
|
|
|
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( TQString() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void dviRenderer::handleSRCLink(const TQString &linkText, TQMouseEvent *e, DocumentWidget *win)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_SPECIAL
|
|
|
|
RenderedDviPagePixmap* currentDVIPage = dynamic_cast<RenderedDviPagePixmap*> currentlyDrawnPage;
|
|
|
|
if (currentDVIPage)
|
|
|
|
{
|
|
|
|
kdDebug(4300) << "Source hyperlink to " << currentDVIPage->sourceHyperLinkList[i].linkText << endl;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
DVI_SourceFileSplitter splitter(linkText, dviFile->filename);
|
|
|
|
TQString TeXfile = splitter.filePath();
|
|
|
|
if ( ! splitter.fileExists() )
|
|
|
|
{
|
|
|
|
KMessageBox::sorry(parentWidget, TQString("<qt>") +
|
|
|
|
i18n("The DVI-file refers to the TeX-file "
|
|
|
|
"<strong>%1</strong> which could not be found.").arg(KShellProcess::quote(TeXfile)) +
|
|
|
|
TQString("</qt>"),
|
|
|
|
i18n( "Could Not Find File" ));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString command = editorCommand;
|
|
|
|
if (command.isEmpty() == true) {
|
|
|
|
int r = KMessageBox::warningContinueCancel(parentWidget, TQString("<qt>") +
|
|
|
|
i18n("You have not yet specified an editor for inverse search. "
|
|
|
|
"Please choose your favorite editor in the "
|
|
|
|
"<strong>DVI options dialog</strong> "
|
|
|
|
"which you will find in the <strong>Settings</strong>-menu.") +
|
|
|
|
TQString("</qt>"),
|
|
|
|
i18n("Need to Specify Editor"),
|
|
|
|
i18n("Use TDE's Editor Kate for Now"));
|
|
|
|
if (r == KMessageBox::Continue)
|
|
|
|
command = "kate %f";
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
command = command.replace( "%l", TQString::number(splitter.line()) ).replace( "%f", KShellProcess::quote(TeXfile) );
|
|
|
|
|
|
|
|
#ifdef DEBUG_SPECIAL
|
|
|
|
kdDebug(4300) << "Calling program: " << command << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// There may still be another program running. Since we don't
|
|
|
|
// want to mix the output of several programs, we will
|
|
|
|
// henceforth dimiss the output of the older programm. "If it
|
|
|
|
// hasn't failed until now, we don't care."
|
|
|
|
if (proc != 0) {
|
|
|
|
tqApp->disconnect(proc, TQ_SIGNAL(receivedStderr(TDEProcess *, char *, int)), 0, 0);
|
|
|
|
tqApp->disconnect(proc, TQ_SIGNAL(receivedStdout(TDEProcess *, char *, int)), 0, 0);
|
|
|
|
proc = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a shell process with the editor command.
|
|
|
|
proc = new KShellProcess();
|
|
|
|
if (proc == 0) {
|
|
|
|
kdError(4300) << "Could not allocate ShellProcess for the editor command." << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
tqApp->connect(proc, TQ_SIGNAL(receivedStderr(TDEProcess *, char *, int)), this, TQ_SLOT(dvips_output_receiver(TDEProcess *, char *, int)));
|
|
|
|
tqApp->connect(proc, TQ_SIGNAL(receivedStdout(TDEProcess *, char *, int)), this, TQ_SLOT(dvips_output_receiver(TDEProcess *, char *, int)));
|
|
|
|
tqApp->connect(proc, TQ_SIGNAL(processExited(TDEProcess *)), this, TQ_SLOT(editorCommand_terminated(TDEProcess *)));
|
|
|
|
// Merge the editor-specific editor message here.
|
|
|
|
export_errorString = i18n("<qt>The external program<br><br><tt><strong>%1</strong></tt><br/><br/>which was used to call the editor "
|
|
|
|
"for inverse search, reported an error. You might wish to look at the <strong>document info "
|
|
|
|
"dialog</strong> which you will find in the File-Menu for a precise error report. The "
|
|
|
|
"manual for KDVI contains a detailed explanation how to set up your editor for use with KDVI, "
|
|
|
|
"and a list of common problems.</qt>").arg(command);
|
|
|
|
|
|
|
|
info->clear(i18n("Starting the editor..."));
|
|
|
|
|
|
|
|
int flashOffset = e->y(); // Heuristic correction. Looks better.
|
|
|
|
win->flash(flashOffset);
|
|
|
|
|
|
|
|
|
|
|
|
proc->clearArguments();
|
|
|
|
*proc << command;
|
|
|
|
proc->closeStdin();
|
|
|
|
if (proc->start(TDEProcess::NotifyOnExit, TDEProcess::AllOutput) == false) {
|
|
|
|
kdError(4300) << "Editor failed to start" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQString dviRenderer::PDFencodingToTQString(const TQString& _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.
|
|
|
|
TQString 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;
|
|
|
|
TQRegExp rx( "(\\\\)(\\d\\d\\d)" ); // matches "\xyz" where x,y,z are numbers
|
|
|
|
while((pos = rx.search( pdfstring )) != -1) {
|
|
|
|
pdfstring = pdfstring.replace(pos, 4, TQChar(rx.cap(2).toInt(0,8)));
|
|
|
|
}
|
|
|
|
rx.setPattern( "(\\\\)(\\d\\d)" ); // matches "\xy" where x,y are numbers
|
|
|
|
while((pos = rx.search( pdfstring )) != -1) {
|
|
|
|
pdfstring = pdfstring.replace(pos, 3, TQChar(rx.cap(2).toInt(0,8)));
|
|
|
|
}
|
|
|
|
rx.setPattern( "(\\\\)(\\d)" ); // matches "\x" where x is a number
|
|
|
|
while((pos = rx.search( pdfstring )) != -1) {
|
|
|
|
pdfstring = pdfstring.replace(pos, 4, TQChar(rx.cap(2).toInt(0,8)));
|
|
|
|
}
|
|
|
|
return pdfstring;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "dviRenderer.moc"
|