diff --git a/autotests/parttest.cpp b/autotests/parttest.cpp index 89e98f171..6a745afd6 100644 --- a/autotests/parttest.cpp +++ b/autotests/parttest.cpp @@ -1,293 +1,293 @@ /*************************************************************************** * Copyright (C) 2013 by Albert Astals Cid * * * * 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. * ***************************************************************************/ #include #include "../core/page.h" #include "../part.h" #include "../ui/toc.h" #include "../ui/pageview.h" #include #include #include #include #include namespace Okular { class PartTest : public QObject { Q_OBJECT static bool openDocument(Okular::Part *part, const QString &filePath); private slots: void testReload(); void testCanceledReload(); void testTOCReload(); - void testFowardPDF(); - void testFowardPDF_data(); + void testForwardPDF(); + void testForwardPDF_data(); void testGeneratorPreferences(); void testSelectText(); void testClickInternalLink(); }; class PartThatHijacksQueryClose : public Okular::Part { public: PartThatHijacksQueryClose(QWidget* parentWidget, QObject* parent, const QVariantList& args) : Okular::Part(parentWidget, parent, args), behavior(PassThru) {} enum Behavior { PassThru, ReturnTrue, ReturnFalse }; void setQueryCloseBehavior(Behavior new_behavior) { behavior = new_behavior; } bool queryClose() override { if (behavior == PassThru) return Okular::Part::queryClose(); else // ReturnTrue or ReturnFalse return (behavior == ReturnTrue); } private: Behavior behavior; }; bool PartTest::openDocument(Okular::Part *part, const QString &filePath) { part->openDocument( filePath ); return part->m_document->isOpened(); } // Test that Okular doesn't crash after a successful reload void PartTest::testReload() { QVariantList dummyArgs; Okular::Part part(nullptr, nullptr, dummyArgs); QVERIFY( openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")) ); part.reload(); qApp->processEvents(); } // Test that Okular doesn't crash after a canceled reload void PartTest::testCanceledReload() { QVariantList dummyArgs; PartThatHijacksQueryClose part(nullptr, nullptr, dummyArgs); QVERIFY( openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")) ); // When queryClose() returns false, the reload operation is canceled (as if // the user had chosen Cancel in the "Save changes?" message box) part.setQueryCloseBehavior(PartThatHijacksQueryClose::ReturnFalse); part.reload(); qApp->processEvents(); } void PartTest::testTOCReload() { QVariantList dummyArgs; Okular::Part part(nullptr, nullptr, dummyArgs); QVERIFY( openDocument(&part, QStringLiteral(KDESRCDIR "data/tocreload.pdf")) ); QCOMPARE(part.m_toc->expandedNodes().count(), 0); part.m_toc->m_treeView->expandAll(); QCOMPARE(part.m_toc->expandedNodes().count(), 3); part.reload(); qApp->processEvents(); QCOMPARE(part.m_toc->expandedNodes().count(), 3); } -void PartTest::testFowardPDF() +void PartTest::testForwardPDF() { QFETCH(QString, dir); QVariantList dummyArgs; Okular::Part part(nullptr, nullptr, dummyArgs); // Create temp dir named like this: ${system temp dir}/${random string}/${dir} const QTemporaryDir tempDir; const QDir workDir(QDir(tempDir.path()).filePath(dir)); workDir.mkpath(QStringLiteral(".")); QFile f(QStringLiteral(KDESRCDIR "data/synctextest.tex")); const QString texDestination = workDir.path() + QStringLiteral("/synctextest.tex"); QVERIFY(f.copy(texDestination)); QProcess process; process.setWorkingDirectory(workDir.path()); process.start(QStringLiteral("pdflatex"), QStringList() << QStringLiteral("-synctex=1") << QStringLiteral("-interaction=nonstopmode") << texDestination); bool started = process.waitForStarted(); if (!started) { qDebug() << "start error:" << process.error(); qDebug() << "start stdout:" << process.readAllStandardOutput(); qDebug() << "start stderr:" << process.readAllStandardError(); } QVERIFY(started); process.waitForFinished(); if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { qDebug() << "exit error:" << process.error() << "status" << process.exitStatus() << "code" << process.exitCode(); qDebug() << "exit stdout:" << process.readAllStandardOutput(); qDebug() << "exit stderr:" << process.readAllStandardError(); } const QString pdfResult = workDir.path() + QStringLiteral("/synctextest.pdf"); QVERIFY(QFile::exists(pdfResult)); QVERIFY( openDocument(&part, pdfResult) ); part.m_document->setViewportPage(0); QCOMPARE(part.m_document->currentPage(), 0u); part.closeUrl(); QUrl u(QUrl::fromLocalFile(pdfResult)); u.setFragment(QStringLiteral("src:100") + texDestination); part.openUrl(u); QCOMPARE(part.m_document->currentPage(), 1u); } -void PartTest::testFowardPDF_data() +void PartTest::testForwardPDF_data() { QTest::addColumn("dir"); QTest::newRow("non-utf8") << QString::fromUtf8("synctextest"); QTest::newRow("utf8") << QString::fromUtf8("ßðđđŋßðđŋ"); } void PartTest::testGeneratorPreferences() { KConfigDialog * dialog; QVariantList dummyArgs; Okular::Part part(nullptr, nullptr, dummyArgs); // Test that we don't crash while opening the dialog dialog = part.slotGeneratorPreferences(); qApp->processEvents(); delete dialog; // closes the dialog and recursively destroys all widgets // Test that we don't crash while opening a new instance of the dialog // This catches attempts to reuse widgets that have been destroyed dialog = part.slotGeneratorPreferences(); qApp->processEvents(); delete dialog; } void PartTest::testSelectText() { QVariantList dummyArgs; Okular::Part part(nullptr, nullptr, dummyArgs); QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); part.widget()->show(); QTest::qWaitForWindowExposed(part.widget()); const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); part.m_document->setViewportPage(0); // wait for pixmap QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); const int mouseY = height * 0.052; const int mouseStartX = width * 0.12; const int mouseEndX = width * 0.7; QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseStartX, mouseY)); QTest::mousePress(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(mouseStartX, mouseY)); QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseEndX, mouseY)); // without this wait the test fails. 100ms were enough on my local system, but when running under valgrind // or on the CI server we need to wait longer. QTest::qWait(1000); QTest::mouseRelease(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(mouseEndX, mouseY)); QApplication::clipboard()->clear(); QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection")); QCOMPARE(QApplication::clipboard()->text(), QStringLiteral("Hola que tal\n")); } void PartTest::testClickInternalLink() { QVariantList dummyArgs; Okular::Part part(nullptr, nullptr, dummyArgs); QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); part.widget()->show(); QTest::qWaitForWindowExposed(part.widget()); const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); part.m_document->setViewportPage(0); // wait for pixmap while (!part.m_document->page(0)->hasPixmap(part.m_pageView)) QTest::qWait(100); QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal"); QCOMPARE(part.m_document->currentPage(), 0u); QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.17, height * 0.05)); QCOMPARE(part.m_document->currentPage(), 1u); } } int main(int argc, char *argv[]) { // Force consistent locale QLocale locale(QStringLiteral("en_US.UTF-8")); if (locale == QLocale::c()) { // This is the way to check if the above worked locale = QLocale(QLocale::English, QLocale::UnitedStates); } QLocale::setDefault(locale); qputenv("LC_ALL", "en_US.UTF-8"); // For UNIX, third-party libraries // Ensure consistent configs/caches QTemporaryDir homeDir; // QTemporaryDir automatically cleans up when it goes out of scope Q_ASSERT(homeDir.isValid()); QByteArray homePath = QFile::encodeName(homeDir.path()); qDebug() << homePath; qputenv("USERPROFILE", homePath); qputenv("HOME", homePath); qputenv("XDG_DATA_HOME", homePath + "/.local"); qputenv("XDG_CONFIG_HOME", homePath + "/.kde-unit-test/xdg/config"); // Disable fancy debug output qunsetenv("QT_MESSAGE_PATTERN"); QApplication app( argc, argv ); app.setApplicationName(QLatin1String("okularparttest")); app.setOrganizationDomain(QLatin1String("kde.org")); app.setQuitOnLastWindowClosed(false); qRegisterMetaType(); /*as done by kapplication*/ qRegisterMetaType>(); Okular::PartTest test; return QTest::qExec( &test, argc, argv ); } #include "parttest.moc" diff --git a/core/document.cpp b/core/document.cpp index 044f09d8b..91c623b58 100644 --- a/core/document.cpp +++ b/core/document.cpp @@ -1,5195 +1,5195 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2008 by Albert Astals Cid * * * * 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. * ***************************************************************************/ #include "document.h" #include "document_p.h" #include "documentcommands_p.h" #include #ifdef Q_OS_WIN #define _WIN32_WINNT 0x0500 #include #elif defined(Q_OS_FREEBSD) #include #include #include #endif // qt/kde/system includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // local includes #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "audioplayer.h" #include "audioplayer_p.h" #include "bookmarkmanager.h" #include "chooseenginedialog_p.h" #include "debug_p.h" #include "generator_p.h" #include "interfaces/configinterface.h" #include "interfaces/guiinterface.h" #include "interfaces/printinterface.h" #include "interfaces/saveinterface.h" #include "observer.h" #include "misc.h" #include "page.h" #include "page_p.h" #include "pagecontroller_p.h" #include "scripter.h" #include "settings_core.h" #include "sourcereference.h" #include "sourcereference_p.h" #include "texteditors_p.h" #include "tile.h" #include "tilesmanager_p.h" #include "utils_p.h" #include "view.h" #include "view_p.h" #include "form.h" #include "utils.h" #include #include using namespace Okular; struct AllocatedPixmap { // owner of the page DocumentObserver *observer; int page; qulonglong memory; // public constructor: initialize data AllocatedPixmap( DocumentObserver *o, int p, qulonglong m ) : observer( o ), page( p ), memory( m ) {} }; struct ArchiveData { ArchiveData() { } QTemporaryFile document; QTemporaryFile metadataFile; }; struct RunningSearch { // store search properties int continueOnPage; RegularAreaRect continueOnMatch; QSet< int > highlightedPages; // fields related to previous searches (used for 'continueSearch') QString cachedString; Document::SearchType cachedType; Qt::CaseSensitivity cachedCaseSensitivity; bool cachedViewportMove : 1; bool isCurrentlySearching : 1; QColor cachedColor; int pagesDone; }; #define foreachObserver( cmd ) {\ QSet< DocumentObserver * >::const_iterator it=d->m_observers.constBegin(), end=d->m_observers.constEnd();\ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } #define foreachObserverD( cmd ) {\ QSet< DocumentObserver * >::const_iterator it = m_observers.constBegin(), end = m_observers.constEnd();\ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } #define OKULAR_HISTORY_MAXSTEPS 100 #define OKULAR_HISTORY_SAVEDSTEPS 10 /***** Document ******/ QString DocumentPrivate::pagesSizeString() const { if (m_generator) { if (m_generator->pagesSizeMetric() != Generator::None) { QSizeF size = m_parent->allPagesSize(); if (size.isValid()) return localizedSize(size); else return QString(); } else return QString(); } else return QString(); } QString DocumentPrivate::namePaperSize(double inchesWidth, double inchesHeight) const { const QPrinter::Orientation orientation = inchesWidth > inchesHeight ? QPrinter::Landscape : QPrinter::Portrait; const QSize pointsSize(inchesWidth *72.0, inchesHeight*72.0); const QPageSize::PageSizeId paperSize = QPageSize::id(pointsSize, QPageSize::FuzzyOrientationMatch); const QString paperName = QPageSize::name(paperSize); if (orientation == QPrinter::Portrait) { return i18nc("paper type and orientation (eg: Portrait A4)", "Portrait %0").arg(paperName); } else { return i18nc("paper type and orientation (eg: Portrait A4)", "Landscape %0").arg(paperName); } } QString DocumentPrivate::localizedSize(const QSizeF &size) const { double inchesWidth = 0, inchesHeight = 0; switch (m_generator->pagesSizeMetric()) { case Generator::Points: inchesWidth = size.width() / 72.0; inchesHeight = size.height() / 72.0; break; case Generator::Pixels: { const QSizeF dpi = m_generator->dpi(); inchesWidth = size.width() / dpi.width(); inchesHeight = size.height() / dpi.height(); } break; case Generator::None: break; } if (QLocale::system().measurementSystem() == QLocale::ImperialSystem) { return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 in (%3)", inchesWidth, inchesHeight, namePaperSize(inchesWidth, inchesHeight)); } else { return i18nc("%1 is width, %2 is height, %3 is paper size name", "%1 x %2 mm (%3)", QString::number(inchesWidth * 25.4, 'd', 0), QString::number(inchesHeight * 25.4, 'd', 0), namePaperSize(inchesWidth, inchesHeight)); } } qulonglong DocumentPrivate::calculateMemoryToFree() { // [MEM] choose memory parameters based on configuration profile qulonglong clipValue = 0; qulonglong memoryToFree = 0; switch ( SettingsCore::memoryLevel() ) { case SettingsCore::EnumMemoryLevel::Low: memoryToFree = m_allocatedPixmapsTotalMemory; break; case SettingsCore::EnumMemoryLevel::Normal: { qulonglong thirdTotalMemory = getTotalMemory() / 3; qulonglong freeMemory = getFreeMemory(); if (m_allocatedPixmapsTotalMemory > thirdTotalMemory) memoryToFree = m_allocatedPixmapsTotalMemory - thirdTotalMemory; if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; } break; case SettingsCore::EnumMemoryLevel::Aggressive: { qulonglong freeMemory = getFreeMemory(); if (m_allocatedPixmapsTotalMemory > freeMemory) clipValue = (m_allocatedPixmapsTotalMemory - freeMemory) / 2; } break; case SettingsCore::EnumMemoryLevel::Greedy: { qulonglong freeSwap; qulonglong freeMemory = getFreeMemory( &freeSwap ); const qulonglong memoryLimit = qMin( qMax( freeMemory, getTotalMemory()/2 ), freeMemory+freeSwap ); if (m_allocatedPixmapsTotalMemory > memoryLimit) clipValue = (m_allocatedPixmapsTotalMemory - memoryLimit) / 2; } break; } if ( clipValue > memoryToFree ) memoryToFree = clipValue; return memoryToFree; } void DocumentPrivate::cleanupPixmapMemory() { cleanupPixmapMemory( calculateMemoryToFree() ); } void DocumentPrivate::cleanupPixmapMemory( qulonglong memoryToFree ) { if ( memoryToFree < 1 ) return; const int currentViewportPage = (*m_viewportIterator).pageNumber; // Create a QMap of visible rects, indexed by page number QMap< int, VisiblePageRect * > visibleRects; QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) visibleRects.insert( (*vIt)->pageNumber, (*vIt) ); // Free memory starting from pages that are farthest from the current one int pagesFreed = 0; while ( memoryToFree > 0 ) { AllocatedPixmap * p = searchLowestPriorityPixmap( true, true ); if ( !p ) // No pixmap to remove break; qCDebug(OkularCoreDebug).nospace() << "Evicting cache pixmap observer=" << p->observer << " page=" << p->page; // m_allocatedPixmapsTotalMemory can't underflow because we always add or remove // the memory used by the AllocatedPixmap so at most it can reach zero m_allocatedPixmapsTotalMemory -= p->memory; // Make sure memoryToFree does not underflow if ( p->memory > memoryToFree ) memoryToFree = 0; else memoryToFree -= p->memory; pagesFreed++; // delete pixmap m_pagesVector.at( p->page )->deletePixmap( p->observer ); // delete allocation descriptor delete p; } // If we're still on low memory, try to free individual tiles // Store pages that weren't completely removed QLinkedList< AllocatedPixmap * > pixmapsToKeep; while (memoryToFree > 0) { int clean_hits = 0; foreach (DocumentObserver *observer, m_observers) { AllocatedPixmap * p = searchLowestPriorityPixmap( false, true, observer ); if ( !p ) // No pixmap to remove continue; clean_hits++; TilesManager *tilesManager = m_pagesVector.at( p->page )->d->tilesManager( observer ); if ( tilesManager && tilesManager->totalMemory() > 0 ) { qulonglong memoryDiff = p->memory; NormalizedRect visibleRect; if ( visibleRects.contains( p->page ) ) visibleRect = visibleRects[ p->page ]->rect; // Free non visible tiles tilesManager->cleanupPixmapMemory( memoryToFree, visibleRect, currentViewportPage ); p->memory = tilesManager->totalMemory(); memoryDiff -= p->memory; memoryToFree = (memoryDiff < memoryToFree) ? (memoryToFree - memoryDiff) : 0; m_allocatedPixmapsTotalMemory -= memoryDiff; if ( p->memory > 0 ) pixmapsToKeep.append( p ); else delete p; } else pixmapsToKeep.append( p ); } if (clean_hits == 0) break; } m_allocatedPixmaps += pixmapsToKeep; //p--rintf("freeMemory A:[%d -%d = %d] \n", m_allocatedPixmaps.count() + pagesFreed, pagesFreed, m_allocatedPixmaps.count() ); } /* Returns the next pixmap to evict from cache, or NULL if no suitable pixmap * if found. If unloadableOnly is set, only unloadable pixmaps are returned. If * thenRemoveIt is set, the pixmap is removed from m_allocatedPixmaps before * returning it */ AllocatedPixmap * DocumentPrivate::searchLowestPriorityPixmap( bool unloadableOnly, bool thenRemoveIt, DocumentObserver *observer ) { QLinkedList< AllocatedPixmap * >::iterator pIt = m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator pEnd = m_allocatedPixmaps.end(); QLinkedList< AllocatedPixmap * >::iterator farthestPixmap = pEnd; const int currentViewportPage = (*m_viewportIterator).pageNumber; /* Find the pixmap that is farthest from the current viewport */ int maxDistance = -1; while ( pIt != pEnd ) { const AllocatedPixmap * p = *pIt; // Filter by observer if ( observer == nullptr || p->observer == observer ) { const int distance = qAbs( p->page - currentViewportPage ); if ( maxDistance < distance && ( !unloadableOnly || p->observer->canUnloadPixmap( p->page ) ) ) { maxDistance = distance; farthestPixmap = pIt; } } ++pIt; } /* No pixmap to remove */ if ( farthestPixmap == pEnd ) return nullptr; AllocatedPixmap * selectedPixmap = *farthestPixmap; if ( thenRemoveIt ) m_allocatedPixmaps.erase( farthestPixmap ); return selectedPixmap; } qulonglong DocumentPrivate::getTotalMemory() { static qulonglong cachedValue = 0; if ( cachedValue ) return cachedValue; #if defined(Q_OS_LINUX) // if /proc/meminfo doesn't exist, return 128MB QFile memFile( QStringLiteral("/proc/meminfo") ); if ( !memFile.open( QIODevice::ReadOnly ) ) return (cachedValue = 134217728); QTextStream readStream( &memFile ); while ( true ) { QString entry = readStream.readLine(); if ( entry.isNull() ) break; if ( entry.startsWith( QLatin1String("MemTotal:") ) ) return (cachedValue = (Q_UINT64_C(1024) * entry.section( QLatin1Char ( ' ' ), -2, -2 ).toULongLong())); } #elif defined(Q_OS_FREEBSD) qulonglong physmem; int mib[] = {CTL_HW, HW_PHYSMEM}; size_t len = sizeof( physmem ); if ( sysctl( mib, 2, &physmem, &len, NULL, 0 ) == 0 ) return (cachedValue = physmem); #elif defined(Q_OS_WIN) MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); GlobalMemoryStatusEx (&stat); return ( cachedValue = stat.ullTotalPhys ); #endif return (cachedValue = 134217728); } qulonglong DocumentPrivate::getFreeMemory( qulonglong *freeSwap ) { static QTime lastUpdate = QTime::currentTime().addSecs(-3); static qulonglong cachedValue = 0; static qulonglong cachedFreeSwap = 0; if ( qAbs( lastUpdate.secsTo( QTime::currentTime() ) ) <= 2 ) { if (freeSwap) *freeSwap = cachedFreeSwap; return cachedValue; } /* Initialize the returned free swap value to 0. It is overwritten if the * actual value is available */ if (freeSwap) *freeSwap = 0; #if defined(Q_OS_LINUX) // if /proc/meminfo doesn't exist, return MEMORY FULL QFile memFile( QStringLiteral("/proc/meminfo") ); if ( !memFile.open( QIODevice::ReadOnly ) ) return 0; // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers' // and 'Cached' fields. consider swapped memory as used memory. qulonglong memoryFree = 0; QString entry; QTextStream readStream( &memFile ); static const int nElems = 5; QString names[nElems] = { QStringLiteral("MemFree:"), QStringLiteral("Buffers:"), QStringLiteral("Cached:"), QStringLiteral("SwapFree:"), QStringLiteral("SwapTotal:") }; qulonglong values[nElems] = { 0, 0, 0, 0, 0 }; bool foundValues[nElems] = { false, false, false, false, false }; while ( true ) { entry = readStream.readLine(); if ( entry.isNull() ) break; for ( int i = 0; i < nElems; ++i ) { if ( entry.startsWith( names[i] ) ) { values[i] = entry.section( QLatin1Char ( ' ' ), -2, -2 ).toULongLong( &foundValues[i] ); } } } memFile.close(); bool found = true; for ( int i = 0; found && i < nElems; ++i ) found = found && foundValues[i]; if ( found ) { /* MemFree + Buffers + Cached - SwapUsed = * = MemFree + Buffers + Cached - (SwapTotal - SwapFree) = * = MemFree + Buffers + Cached + SwapFree - SwapTotal */ memoryFree = values[0] + values[1] + values[2] + values[3]; if ( values[4] > memoryFree ) memoryFree = 0; else memoryFree -= values[4]; } else { return 0; } lastUpdate = QTime::currentTime(); if (freeSwap) *freeSwap = ( cachedFreeSwap = (Q_UINT64_C(1024) * values[3]) ); return ( cachedValue = (Q_UINT64_C(1024) * memoryFree) ); #elif defined(Q_OS_FREEBSD) qulonglong cache, inact, free, psize; size_t cachelen, inactlen, freelen, psizelen; cachelen = sizeof( cache ); inactlen = sizeof( inact ); freelen = sizeof( free ); psizelen = sizeof( psize ); // sum up inactive, cached and free memory if ( sysctlbyname( "vm.stats.vm.v_cache_count", &cache, &cachelen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_inactive_count", &inact, &inactlen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_free_count", &free, &freelen, NULL, 0 ) == 0 && sysctlbyname( "vm.stats.vm.v_page_size", &psize, &psizelen, NULL, 0 ) == 0 ) { lastUpdate = QTime::currentTime(); return (cachedValue = (cache + inact + free) * psize); } else { return 0; } #elif defined(Q_OS_WIN) MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); GlobalMemoryStatusEx (&stat); lastUpdate = QTime::currentTime(); if (freeSwap) *freeSwap = ( cachedFreeSwap = stat.ullAvailPageFile ); return ( cachedValue = stat.ullAvailPhys ); #else // tell the memory is full.. will act as in LOW profile return 0; #endif } void DocumentPrivate::loadDocumentInfo() // note: load data and stores it internally (document or pages). observers // are still uninitialized at this point so don't access them { //qCDebug(OkularCoreDebug).nospace() << "Using '" << d->m_xmlFileName << "' as document info file."; if ( m_xmlFileName.isEmpty() ) return; QFile infoFile( m_xmlFileName ); loadDocumentInfo( infoFile ); } void DocumentPrivate::loadDocumentInfo( QFile &infoFile ) { if ( !infoFile.exists() || !infoFile.open( QIODevice::ReadOnly ) ) return; // Load DOM from XML file QDomDocument doc( QStringLiteral("documentInfo") ); if ( !doc.setContent( &infoFile ) ) { qCDebug(OkularCoreDebug) << "Can't load XML pair! Check for broken xml."; infoFile.close(); return; } infoFile.close(); QDomElement root = doc.documentElement(); if ( root.tagName() != QLatin1String("documentInfo") ) return; // Parse the DOM tree QDomNode topLevelNode = root.firstChild(); while ( topLevelNode.isElement() ) { QString catName = topLevelNode.toElement().tagName(); // Restore page attributes (bookmark, annotations, ...) from the DOM if ( catName == QLatin1String("pageList") ) { QDomNode pageNode = topLevelNode.firstChild(); while ( pageNode.isElement() ) { QDomElement pageElement = pageNode.toElement(); if ( pageElement.hasAttribute( QStringLiteral("number") ) ) { // get page number (node's attribute) bool ok; int pageNumber = pageElement.attribute( QStringLiteral("number") ).toInt( &ok ); // pass the domElement to the right page, to read config data from if ( ok && pageNumber >= 0 && pageNumber < (int)m_pagesVector.count() ) m_pagesVector[ pageNumber ]->d->restoreLocalContents( pageElement ); } pageNode = pageNode.nextSibling(); } } // Restore 'general info' from the DOM else if ( catName == QLatin1String("generalInfo") ) { QDomNode infoNode = topLevelNode.firstChild(); while ( infoNode.isElement() ) { QDomElement infoElement = infoNode.toElement(); // restore viewports history if ( infoElement.tagName() == QLatin1String("history") ) { // clear history m_viewportHistory.clear(); // append old viewports QDomNode historyNode = infoNode.firstChild(); while ( historyNode.isElement() ) { QDomElement historyElement = historyNode.toElement(); if ( historyElement.hasAttribute( QStringLiteral("viewport") ) ) { QString vpString = historyElement.attribute( QStringLiteral("viewport") ); m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport( vpString ) ); } historyNode = historyNode.nextSibling(); } // consistancy check if ( m_viewportHistory.isEmpty() ) m_viewportIterator = m_viewportHistory.insert( m_viewportHistory.end(), DocumentViewport() ); } else if ( infoElement.tagName() == QLatin1String("rotation") ) { QString str = infoElement.text(); bool ok = true; int newrotation = !str.isEmpty() ? ( str.toInt( &ok ) % 4 ) : 0; if ( ok && newrotation != 0 ) { setRotationInternal( newrotation, false ); } } else if ( infoElement.tagName() == QLatin1String("views") ) { QDomNode viewNode = infoNode.firstChild(); while ( viewNode.isElement() ) { QDomElement viewElement = viewNode.toElement(); if ( viewElement.tagName() == QLatin1String("view") ) { const QString viewName = viewElement.attribute( QStringLiteral("name") ); Q_FOREACH ( View * view, m_views ) { if ( view->name() == viewName ) { loadViewsInfo( view, viewElement ); break; } } } viewNode = viewNode.nextSibling(); } } infoNode = infoNode.nextSibling(); } } topLevelNode = topLevelNode.nextSibling(); } // } void DocumentPrivate::loadViewsInfo( View *view, const QDomElement &e ) { QDomNode viewNode = e.firstChild(); while ( viewNode.isElement() ) { QDomElement viewElement = viewNode.toElement(); if ( viewElement.tagName() == QLatin1String("zoom") ) { const QString valueString = viewElement.attribute( QStringLiteral("value") ); bool newzoom_ok = true; const double newzoom = !valueString.isEmpty() ? valueString.toDouble( &newzoom_ok ) : 1.0; if ( newzoom_ok && newzoom != 0 && view->supportsCapability( View::Zoom ) && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { view->setCapability( View::Zoom, newzoom ); } const QString modeString = viewElement.attribute( QStringLiteral("mode") ); bool newmode_ok = true; const int newmode = !modeString.isEmpty() ? modeString.toInt( &newmode_ok ) : 2; if ( newmode_ok && view->supportsCapability( View::ZoomModality ) && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { view->setCapability( View::ZoomModality, newmode ); } } viewNode = viewNode.nextSibling(); } } void DocumentPrivate::saveViewsInfo( View *view, QDomElement &e ) const { if ( view->supportsCapability( View::Zoom ) && ( view->capabilityFlags( View::Zoom ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) && view->supportsCapability( View::ZoomModality ) && ( view->capabilityFlags( View::ZoomModality ) & ( View::CapabilityRead | View::CapabilitySerializable ) ) ) { QDomElement zoomEl = e.ownerDocument().createElement( QStringLiteral("zoom") ); e.appendChild( zoomEl ); bool ok = true; const double zoom = view->capability( View::Zoom ).toDouble( &ok ); if ( ok && zoom != 0 ) { zoomEl.setAttribute( QStringLiteral("value"), QString::number(zoom) ); } const int mode = view->capability( View::ZoomModality ).toInt( &ok ); if ( ok ) { zoomEl.setAttribute( QStringLiteral("mode"), mode ); } } } QUrl DocumentPrivate::giveAbsoluteUrl( const QString & fileName ) const { if ( !QDir::isRelativePath( fileName ) ) return QUrl::fromLocalFile(fileName); if ( !m_url.isValid() ) return QUrl(); return QUrl(KIO::upUrl(m_url).toString() + fileName); } bool DocumentPrivate::openRelativeFile( const QString & fileName ) { QUrl url = giveAbsoluteUrl( fileName ); if ( url.isEmpty() ) return false; qCDebug(OkularCoreDebug).nospace() << "openRelativeFile: '" << url << "'"; emit m_parent->openUrl( url ); return true; } Generator * DocumentPrivate::loadGeneratorLibrary( const KPluginMetaData &service ) { KPluginLoader loader( service.fileName() ); qCDebug(OkularCoreDebug) << service.fileName(); KPluginFactory *factory = loader.factory(); if ( !factory ) { qCWarning(OkularCoreDebug).nospace() << "Invalid plugin factory for " << service.fileName() << ":" << loader.errorString(); return nullptr; } Generator * plugin = factory->create(); GeneratorInfo info( plugin, service ); m_loadedGenerators.insert( service.pluginId(), info ); return plugin; } void DocumentPrivate::loadAllGeneratorLibraries() { if ( m_generatorsLoaded ) return; loadServiceList( availableGenerators() ); m_generatorsLoaded = true; } void DocumentPrivate::loadServiceList( const QVector& offers ) { int count = offers.count(); if ( count <= 0 ) return; for ( int i = 0; i < count; ++i ) { QString id = offers.at(i).pluginId(); // don't load already loaded generators QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( id ); if ( !m_loadedGenerators.isEmpty() && genIt != m_loadedGenerators.constEnd() ) continue; Generator * g = loadGeneratorLibrary( offers.at(i) ); (void)g; } } void DocumentPrivate::unloadGenerator( const GeneratorInfo& info ) { delete info.generator; } void DocumentPrivate::cacheExportFormats() { if ( m_exportCached ) return; const ExportFormat::List formats = m_generator->exportFormats(); for ( int i = 0; i < formats.count(); ++i ) { if ( formats.at( i ).mimeType().name() == QLatin1String( "text/plain" ) ) m_exportToText = formats.at( i ); else m_exportFormats.append( formats.at( i ) ); } m_exportCached = true; } ConfigInterface* DocumentPrivate::generatorConfig( GeneratorInfo& info ) { if ( info.configChecked ) return info.config; info.config = qobject_cast< Okular::ConfigInterface * >( info.generator ); info.configChecked = true; return info.config; } SaveInterface* DocumentPrivate::generatorSave( GeneratorInfo& info ) { if ( info.saveChecked ) return info.save; info.save = qobject_cast< Okular::SaveInterface * >( info.generator ); info.saveChecked = true; return info.save; } Document::OpenResult DocumentPrivate::openDocumentInternal( const KPluginMetaData& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ) { QString propName = offer.pluginId(); QHash< QString, GeneratorInfo >::const_iterator genIt = m_loadedGenerators.constFind( propName ); m_walletGenerator = nullptr; if ( genIt != m_loadedGenerators.constEnd() ) { m_generator = genIt.value().generator; } else { m_generator = loadGeneratorLibrary( offer ); if ( !m_generator ) return Document::OpenError; genIt = m_loadedGenerators.constFind( propName ); Q_ASSERT( genIt != m_loadedGenerators.constEnd() ); } Q_ASSERT_X( m_generator, "Document::load()", "null generator?!" ); m_generator->d_func()->m_document = this; // connect error reporting signals QObject::connect( m_generator, &Generator::error, m_parent, &Document::error ); QObject::connect( m_generator, &Generator::warning, m_parent, &Document::warning ); QObject::connect( m_generator, &Generator::notice, m_parent, &Document::notice ); QApplication::setOverrideCursor( Qt::WaitCursor ); const QSizeF dpi = Utils::realDpi(m_widget); qCDebug(OkularCoreDebug) << "Output DPI:" << dpi; m_generator->setDPI(dpi); Document::OpenResult openResult = Document::OpenError; if ( !isstdin ) { openResult = m_generator->loadDocumentWithPassword( docFile, m_pagesVector, password ); } else if ( !filedata.isEmpty() ) { if ( m_generator->hasFeature( Generator::ReadRawData ) ) { openResult = m_generator->loadDocumentFromDataWithPassword( filedata, m_pagesVector, password ); } else { m_tempFile = new QTemporaryFile(); if ( !m_tempFile->open() ) { delete m_tempFile; m_tempFile = nullptr; } else { m_tempFile->write( filedata ); QString tmpFileName = m_tempFile->fileName(); m_tempFile->close(); openResult = m_generator->loadDocumentWithPassword( tmpFileName, m_pagesVector, password ); } } } QApplication::restoreOverrideCursor(); if ( openResult != Document::OpenSuccess || m_pagesVector.size() <= 0 ) { m_generator->d_func()->m_document = nullptr; QObject::disconnect( m_generator, nullptr, m_parent, nullptr ); // TODO this is a bit of a hack, since basically means that // you can only call walletDataForFile after calling openDocument // but since in reality it's what happens I've decided not to refactor/break API // One solution is just kill walletDataForFile and make OpenResult be an object // where the wallet data is also returned when OpenNeedsPassword m_walletGenerator = m_generator; m_generator = nullptr; qDeleteAll( m_pagesVector ); m_pagesVector.clear(); delete m_tempFile; m_tempFile = nullptr; // TODO: emit a message telling the document is empty if ( openResult == Document::OpenSuccess ) openResult = Document::OpenError; } return openResult; } bool DocumentPrivate::savePageDocumentInfo( QTemporaryFile *infoFile, int what ) const { if ( infoFile->open() ) { // 1. Create DOM QDomDocument doc( QStringLiteral("documentInfo") ); QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) ); doc.appendChild( xmlPi ); QDomElement root = doc.createElement( QStringLiteral("documentInfo") ); doc.appendChild( root ); // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM QDomElement pageList = doc.createElement( QStringLiteral("pageList") ); root.appendChild( pageList ); // .... save pages that hold data QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->saveLocalContents( pageList, doc, PageItems( what ) ); // 3. Save DOM to XML file QString xml = doc.toString(); QTextStream os( infoFile ); os.setCodec( "UTF-8" ); os << xml; return true; } return false; } DocumentViewport DocumentPrivate::nextDocumentViewport() const { DocumentViewport ret = m_nextDocumentViewport; if ( !m_nextDocumentDestination.isEmpty() && m_generator ) { DocumentViewport vp( m_parent->metaData( QStringLiteral("NamedViewport"), m_nextDocumentDestination ).toString() ); if ( vp.isValid() ) { ret = vp; } } return ret; } void DocumentPrivate::warnLimitedAnnotSupport() { if ( !m_showWarningLimitedAnnotSupport ) return; m_showWarningLimitedAnnotSupport = false; // Show the warning once if ( m_annotationsNeedSaveAs ) { // Shown if the user is editing annotations in a file whose metadata is // not stored locally (.okular archives belong to this category) KMessageBox::information( m_widget, i18n("Your annotation changes will not be saved automatically. Use File -> Save As...\nor your changes will be lost once the document is closed"), QString(), QStringLiteral("annotNeedSaveAs") ); } else if ( !canAddAnnotationsNatively() ) { // If the generator doesn't support native annotations KMessageBox::information( m_widget, i18n("Your annotations are saved internally by Okular.\nYou can export the annotated document using File -> Export As -> Document Archive"), QString(), QStringLiteral("annotExportAsArchive") ); } } void DocumentPrivate::performAddPageAnnotation( int page, Annotation * annotation ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr; // find out the page to attach annotation Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; // the annotation belongs already to a page if ( annotation->d_ptr->m_page ) return; // add annotation to the page kp->addAnnotation( annotation ); // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Addition) ) proxy->notifyAddition( annotation, page ); // notify observers about the change notifyAnnotationChanges( page ); if ( annotation->flags() & Annotation::ExternallyDrawn ) { // Redraw everything, including ExternallyDrawn annotations refreshPixmaps( page ); } warnLimitedAnnotSupport(); } void DocumentPrivate::performRemovePageAnnotation( int page, Annotation * annotation ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr; bool isExternallyDrawn; // find out the page Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; if ( annotation->flags() & Annotation::ExternallyDrawn ) isExternallyDrawn = true; else isExternallyDrawn = false; // try to remove the annotation if ( m_parent->canRemovePageAnnotation( annotation ) ) { // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Removal) ) proxy->notifyRemoval( annotation, page ); kp->removeAnnotation( annotation ); // Also destroys the object // in case of success, notify observers about the change notifyAnnotationChanges( page ); if ( isExternallyDrawn ) { // Redraw everything, including ExternallyDrawn annotations refreshPixmaps( page ); } } warnLimitedAnnotSupport(); } void DocumentPrivate::performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ) { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); AnnotationProxy *proxy = iface ? iface->annotationProxy() : nullptr; // find out the page Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; // tell the annotation proxy if ( proxy && proxy->supports(AnnotationProxy::Modification) ) { proxy->notifyModification( annotation, page, appearanceChanged ); } // notify observers about the change notifyAnnotationChanges( page ); if ( appearanceChanged && (annotation->flags() & Annotation::ExternallyDrawn) ) { /* When an annotation is being moved, the generator will not render it. * Therefore there's no need to refresh pixmaps after the first time */ if ( annotation->flags() & (Annotation::BeingMoved | Annotation::BeingResized) ) { if ( m_annotationBeingModified ) return; else // First time: take note m_annotationBeingModified = true; } else { m_annotationBeingModified = false; } // Redraw everything, including ExternallyDrawn annotations qCDebug(OkularCoreDebug) << "Refreshing Pixmaps"; refreshPixmaps( page ); } // If the user is moving or resizing the annotation, don't steal the focus if ( (annotation->flags() & (Annotation::BeingMoved | Annotation::BeingResized) ) == 0 ) warnLimitedAnnotSupport(); } void DocumentPrivate::performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ) { bool appearanceChanged = false; // Check if appearanceChanged should be true switch ( annot->subType() ) { // If it's an in-place TextAnnotation, set the inplace text case Okular::Annotation::AText: { Okular::TextAnnotation * txtann = static_cast< Okular::TextAnnotation * >( annot ); if ( txtann->textType() == Okular::TextAnnotation::InPlace ) { appearanceChanged = true; } break; } // If it's a LineAnnotation, check if caption text is visible case Okular::Annotation::ALine: { Okular::LineAnnotation * lineann = static_cast< Okular::LineAnnotation * >( annot ); if ( lineann->showCaption() ) appearanceChanged = true; break; } default: break; } // Set contents annot->setContents( newContents ); // Tell the document the annotation has been modified performModifyPageAnnotation( pageNumber, annot, appearanceChanged ); } void DocumentPrivate::recalculateForms() { const QVariant fco = m_parent->metaData(QLatin1String("FormCalculateOrder")); const QVector formCalculateOrder = fco.value>(); foreach(int formId, formCalculateOrder) { for ( uint pageIdx = 0; pageIdx < m_parent->pages(); pageIdx++ ) { const Page *p = m_parent->page( pageIdx ); if (p) { foreach( FormField *form, p->formFields() ) { if ( form->id() == formId ) { Action *action = form->additionalAction( FormField::CalculateField ); if (action) { m_parent->processAction( action ); } else { qWarning() << "Form that is part of calculate order doesn't have a calculate action"; } } } } } } } void DocumentPrivate::saveDocumentInfo() const { if ( m_xmlFileName.isEmpty() ) return; QFile infoFile( m_xmlFileName ); qCDebug(OkularCoreDebug) << "About to save document info to" << m_xmlFileName; if (!infoFile.open( QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(OkularCoreDebug) << "Failed to open docdata file" << m_xmlFileName; return; } // 1. Create DOM QDomDocument doc( QStringLiteral("documentInfo") ); QDomProcessingInstruction xmlPi = doc.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) ); doc.appendChild( xmlPi ); QDomElement root = doc.createElement( QStringLiteral("documentInfo") ); root.setAttribute( QStringLiteral("url"), m_url.toDisplayString(QUrl::PreferLocalFile) ); doc.appendChild( root ); // 2.1. Save page attributes (bookmark state, annotations, ... ) to DOM QDomElement pageList = doc.createElement( QStringLiteral("pageList") ); root.appendChild( pageList ); PageItems saveWhat = AllPageItems; if ( m_annotationsNeedSaveAs ) { /* In this case, if the user makes a modification, he's requested to * save to a new document. Therefore, if there are existing local * annotations, we save them back unmodified in the original * document's metadata, so that it appears that it was not changed */ saveWhat |= OriginalAnnotationPageItems; } // .... save pages that hold data QVector< Page * >::const_iterator pIt = m_pagesVector.constBegin(), pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->saveLocalContents( pageList, doc, saveWhat ); // 2.2. Save document info (current viewport, history, ... ) to DOM QDomElement generalInfo = doc.createElement( QStringLiteral("generalInfo") ); root.appendChild( generalInfo ); // create rotation node if ( m_rotation != Rotation0 ) { QDomElement rotationNode = doc.createElement( QStringLiteral("rotation") ); generalInfo.appendChild( rotationNode ); rotationNode.appendChild( doc.createTextNode( QString::number( (int)m_rotation ) ) ); } // ... save history up to OKULAR_HISTORY_SAVEDSTEPS viewports QLinkedList< DocumentViewport >::const_iterator backIterator = m_viewportIterator; if ( backIterator != m_viewportHistory.constEnd() ) { // go back up to OKULAR_HISTORY_SAVEDSTEPS steps from the current viewportIterator int backSteps = OKULAR_HISTORY_SAVEDSTEPS; while ( backSteps-- && backIterator != m_viewportHistory.constBegin() ) --backIterator; // create history root node QDomElement historyNode = doc.createElement( QStringLiteral("history") ); generalInfo.appendChild( historyNode ); // add old[backIterator] and present[viewportIterator] items QLinkedList< DocumentViewport >::const_iterator endIt = m_viewportIterator; ++endIt; while ( backIterator != endIt ) { QString name = (backIterator == m_viewportIterator) ? QStringLiteral ("current") : QStringLiteral ("oldPage"); QDomElement historyEntry = doc.createElement( name ); historyEntry.setAttribute( QStringLiteral("viewport"), (*backIterator).toString() ); historyNode.appendChild( historyEntry ); ++backIterator; } } // create views root node QDomElement viewsNode = doc.createElement( QStringLiteral("views") ); generalInfo.appendChild( viewsNode ); Q_FOREACH ( View * view, m_views ) { QDomElement viewEntry = doc.createElement( QStringLiteral("view") ); viewEntry.setAttribute( QStringLiteral("name"), view->name() ); viewsNode.appendChild( viewEntry ); saveViewsInfo( view, viewEntry ); } // 3. Save DOM to XML file QString xml = doc.toString(); QTextStream os( &infoFile ); os.setCodec( "UTF-8" ); os << xml; infoFile.close(); } void DocumentPrivate::slotTimedMemoryCheck() { // [MEM] clean memory (for 'free mem dependant' profiles only) if ( SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Low && m_allocatedPixmapsTotalMemory > 1024*1024 ) cleanupPixmapMemory(); } void DocumentPrivate::sendGeneratorPixmapRequest() { /* If the pixmap cache will have to be cleaned in order to make room for the * next request, get the distance from the current viewport of the page * whose pixmap will be removed. We will ignore preload requests for pages * that are at the same distance or farther */ const qulonglong memoryToFree = calculateMemoryToFree(); const int currentViewportPage = (*m_viewportIterator).pageNumber; int maxDistance = INT_MAX; // Default: No maximum if ( memoryToFree ) { AllocatedPixmap *pixmapToReplace = searchLowestPriorityPixmap( true ); if ( pixmapToReplace ) maxDistance = qAbs( pixmapToReplace->page - currentViewportPage ); } // find a request PixmapRequest * request = nullptr; m_pixmapRequestsMutex.lock(); while ( !m_pixmapRequestsStack.isEmpty() && !request ) { PixmapRequest * r = m_pixmapRequestsStack.last(); if (!r) { m_pixmapRequestsStack.pop_back(); continue; } QRect requestRect = r->isTile() ? r->normalizedRect().geometry( r->width(), r->height() ) : QRect( 0, 0, r->width(), r->height() ); TilesManager *tilesManager = r->d->tilesManager(); // If it's a preload but the generator is not threaded no point in trying to preload if ( r->preload() && !m_generator->hasFeature( Generator::Threaded ) ) { m_pixmapRequestsStack.pop_back(); delete r; } // request only if page isn't already present and request has valid id // request only if page isn't already present and request has valid id else if ( ( !r->d->mForce && r->page()->hasPixmap( r->observer(), r->width(), r->height(), r->normalizedRect() ) ) || !m_observers.contains(r->observer()) ) { m_pixmapRequestsStack.pop_back(); delete r; } else if ( !r->d->mForce && r->preload() && qAbs( r->pageNumber() - currentViewportPage ) >= maxDistance ) { m_pixmapRequestsStack.pop_back(); //qCDebug(OkularCoreDebug) << "Ignoring request that doesn't fit in cache"; delete r; } // Ignore requests for pixmaps that are already being generated else if ( tilesManager && tilesManager->isRequesting( r->normalizedRect(), r->width(), r->height() ) ) { m_pixmapRequestsStack.pop_back(); delete r; } // If the requested area is above 8000000 pixels, switch on the tile manager else if ( !tilesManager && m_generator->hasFeature( Generator::TiledRendering ) && (long)r->width() * (long)r->height() > 8000000L ) { // if the image is too big. start using tiles qCDebug(OkularCoreDebug).nospace() << "Start using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; // fill the tiles manager with the last rendered pixmap const QPixmap *pixmap = r->page()->_o_nearestPixmap( r->observer(), r->width(), r->height() ); if ( pixmap ) { tilesManager = new TilesManager( r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation() ); tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ) ); tilesManager->setSize( r->width(), r->height() ); } else { // create new tiles manager tilesManager = new TilesManager( r->pageNumber(), r->width(), r->height(), r->page()->rotation() ); } tilesManager->setRequest( r->normalizedRect(), r->width(), r->height() ); r->page()->deletePixmap( r->observer() ); r->page()->d->setTilesManager( r->observer(), tilesManager ); r->setTile( true ); // Change normalizedRect to the smallest rect that contains all // visible tiles. if ( !r->normalizedRect().isNull() ) { NormalizedRect tilesRect; const QList tiles = tilesManager->tilesAt( r->normalizedRect(), TilesManager::TerminalTile ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { Tile tile = *tIt; if ( tilesRect.isNull() ) tilesRect = tile.rect(); else tilesRect |= tile.rect(); ++tIt; } r->setNormalizedRect( tilesRect ); request = r; } else { // Discard request if normalizedRect is null. This happens in // preload requests issued by PageView if the requested page is // not visible and the user has just switched from a non-tiled // zoom level to a tiled one m_pixmapRequestsStack.pop_back(); delete r; } } // If the requested area is below 6000000 pixels, switch off the tile manager else if ( tilesManager && (long)r->width() * (long)r->height() < 6000000L ) { qCDebug(OkularCoreDebug).nospace() << "Stop using tiles on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; // page is too small. stop using tiles. r->page()->deletePixmap( r->observer() ); r->setTile( false ); request = r; } else if ( (long)requestRect.width() * (long)requestRect.height() > 200000000L && (SettingsCore::memoryLevel() != SettingsCore::EnumMemoryLevel::Greedy ) ) { m_pixmapRequestsStack.pop_back(); if ( !m_warnedOutOfMemory ) { qCWarning(OkularCoreDebug).nospace() << "Running out of memory on page " << r->pageNumber() << " (" << r->width() << "x" << r->height() << " px);"; qCWarning(OkularCoreDebug) << "this message will be reported only once."; m_warnedOutOfMemory = true; } delete r; } else { request = r; } } // if no request found (or already generated), return if ( !request ) { m_pixmapRequestsMutex.unlock(); return; } // [MEM] preventive memory freeing qulonglong pixmapBytes = 0; TilesManager * tm = request->d->tilesManager(); if ( tm ) pixmapBytes = tm->totalMemory(); else pixmapBytes = 4 * request->width() * request->height(); if ( pixmapBytes > (1024 * 1024) ) cleanupPixmapMemory( memoryToFree /* previously calculated value */ ); // submit the request to the generator if ( m_generator->canGeneratePixmap() ) { QRect requestRect = !request->isTile() ? QRect(0, 0, request->width(), request->height() ) : request->normalizedRect().geometry( request->width(), request->height() ); qCDebug(OkularCoreDebug).nospace() << "sending request observer=" << request->observer() << " " <pageNumber() << " async == " << request->asynchronous() << " isTile == " << request->isTile(); m_pixmapRequestsStack.removeAll ( request ); if ( tm ) tm->setRequest( request->normalizedRect(), request->width(), request->height() ); if ( (int)m_rotation % 2 ) request->d->swap(); if ( m_rotation != Rotation0 && !request->normalizedRect().isNull() ) request->setNormalizedRect( TilesManager::fromRotatedRect( request->normalizedRect(), m_rotation ) ); // we always have to unlock _before_ the generatePixmap() because // a sync generation would end with requestDone() -> deadlock, and // we can not really know if the generator can do async requests m_executingPixmapRequests.push_back( request ); m_pixmapRequestsMutex.unlock(); m_generator->generatePixmap( request ); } else { m_pixmapRequestsMutex.unlock(); // pino (7/4/2006): set the polling interval from 10 to 30 QTimer::singleShot( 30, m_parent, SLOT(sendGeneratorPixmapRequest()) ); } } void DocumentPrivate::rotationFinished( int page, Okular::Page *okularPage ) { Okular::Page *wantedPage = m_pagesVector.value( page, 0 ); if ( !wantedPage || wantedPage != okularPage ) return; foreach(DocumentObserver *o, m_observers) o->notifyPageChanged( page, DocumentObserver::Pixmap | DocumentObserver::Annotations ); } void DocumentPrivate::slotFontReadingProgress( int page ) { emit m_parent->fontReadingProgress( page ); if ( page >= (int)m_parent->pages() - 1 ) { emit m_parent->fontReadingEnded(); m_fontThread = nullptr; m_fontsCached = true; } } void DocumentPrivate::fontReadingGotFont( const Okular::FontInfo& font ) { // Try to avoid duplicate fonts if (m_fontsCache.indexOf(font) == -1) { m_fontsCache.append( font ); emit m_parent->gotFont( font ); } } void DocumentPrivate::slotGeneratorConfigChanged( const QString& ) { if ( !m_generator ) return; // reparse generator config and if something changed clear Pages bool configchanged = false; QHash< QString, GeneratorInfo >::iterator it = m_loadedGenerators.begin(), itEnd = m_loadedGenerators.end(); for ( ; it != itEnd; ++it ) { Okular::ConfigInterface * iface = generatorConfig( it.value() ); if ( iface ) { bool it_changed = iface->reparseConfig(); if ( it_changed && ( m_generator == it.value().generator ) ) configchanged = true; } } if ( configchanged ) { // invalidate pixmaps QVector::const_iterator it = m_pagesVector.constBegin(), end = m_pagesVector.constEnd(); for ( ; it != end; ++it ) { (*it)->deletePixmaps(); } // [MEM] remove allocation descriptors qDeleteAll( m_allocatedPixmaps ); m_allocatedPixmaps.clear(); m_allocatedPixmapsTotalMemory = 0; // send reload signals to observers foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap ) ); } // free memory if in 'low' profile if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !m_allocatedPixmaps.isEmpty() && !m_pagesVector.isEmpty() ) cleanupPixmapMemory(); } void DocumentPrivate::refreshPixmaps( int pageNumber ) { Page* page = m_pagesVector.value( pageNumber, 0 ); if ( !page ) return; QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QMap< DocumentObserver*, PagePrivate::PixmapObject >::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd(); for ( ; it != itEnd; ++it ) { QSize size = (*it).m_pixmap->size(); PixmapRequest * p = new PixmapRequest( it.key(), pageNumber, size.width(), size.height(), 1, PixmapRequest::Asynchronous ); p->d->mForce = true; requestedPixmaps.push_back( p ); } foreach (DocumentObserver *observer, m_observers) { TilesManager *tilesManager = page->d->tilesManager( observer ); if ( tilesManager ) { tilesManager->markDirty(); PixmapRequest * p = new PixmapRequest( observer, pageNumber, tilesManager->width(), tilesManager->height(), 1, PixmapRequest::Asynchronous ); NormalizedRect tilesRect; // Get the visible page rect NormalizedRect visibleRect; QVector< Okular::VisiblePageRect * >::const_iterator vIt = m_pageRects.constBegin(), vEnd = m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) { if ( (*vIt)->pageNumber == pageNumber ) { visibleRect = (*vIt)->rect; break; } } if ( !visibleRect.isNull() ) { p->setNormalizedRect( visibleRect ); p->setTile( true ); p->d->mForce = true; requestedPixmaps.push_back( p ); } else { delete p; } } } if ( !requestedPixmaps.isEmpty() ) m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); } void DocumentPrivate::_o_configChanged() { // free text pages if needed calculateMaxTextPages(); while (m_allocatedTextPagesFifo.count() > m_maxAllocatedTextPages) { int pageToKick = m_allocatedTextPagesFifo.takeFirst(); m_pagesVector.at(pageToKick)->setTextPage( nullptr ); // deletes the textpage } } void DocumentPrivate::doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct) { DoContinueDirectionMatchSearchStruct *searchStruct = static_cast(doContinueDirectionMatchSearchStruct); RunningSearch *search = m_searches.value(searchStruct->searchID); if ((m_searchCancelled && !searchStruct->match) || !search) { // if the user cancelled but he just got a match, give him the match! QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchStruct->searchID, Document::SearchCancelled ); delete searchStruct->pagesToNotify; delete searchStruct; return; } const bool forward = search->cachedType == Document::NextMatch; bool doContinue = false; // if no match found, loop through the whole doc, starting from currentPage if ( !searchStruct->match ) { const int pageCount = m_pagesVector.count(); if (search->pagesDone < pageCount) { doContinue = true; if ( searchStruct->currentPage >= pageCount || searchStruct->currentPage < 0 ) { doContinue = false; search->isCurrentlySearching = false; search->continueOnPage = forward ? 0 : pageCount - 1; search->continueOnMatch = RegularAreaRect(); emit m_parent->searchFinished ( searchStruct->searchID, Document::EndOfDocumentReached ); } } } if (doContinue) { // get page Page * page = m_pagesVector[ searchStruct->currentPage ]; // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( page->number() ); // if found a match on the current page, end the loop searchStruct->match = page->findText( searchStruct->searchID, search->cachedString, forward ? FromTop : FromBottom, search->cachedCaseSensitivity ); if ( !searchStruct->match ) { if (forward) searchStruct->currentPage++; else searchStruct->currentPage--; search->pagesDone++; } else { search->pagesDone = 1; } // Both of the previous if branches need to call doContinueDirectionMatchSearch QMetaObject::invokeMethod(m_parent, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); } else { doProcessSearchMatch( searchStruct->match, search, searchStruct->pagesToNotify, searchStruct->currentPage, searchStruct->searchID, search->cachedViewportMove, search->cachedColor ); delete searchStruct; } } void DocumentPrivate::doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ) { // reset cursor to previous shape QApplication::restoreOverrideCursor(); bool foundAMatch = false; search->isCurrentlySearching = false; // if a match has been found.. if ( match ) { // update the RunningSearch structure adding this match.. foundAMatch = true; search->continueOnPage = currentPage; search->continueOnMatch = *match; search->highlightedPages.insert( currentPage ); // ..add highlight to the page.. m_pagesVector[ currentPage ]->d->setHighlight( searchID, match, color ); // ..queue page for notifying changes.. pagesToNotify->insert( currentPage ); // Create a normalized rectangle around the search match that includes a 5% buffer on all sides. const Okular::NormalizedRect matchRectWithBuffer = Okular::NormalizedRect( match->first().left - 0.05, match->first().top - 0.05, match->first().right + 0.05, match->first().bottom + 0.05 ); const bool matchRectFullyVisible = isNormalizedRectangleFullyVisible( matchRectWithBuffer, currentPage ); // ..move the viewport to show the first of the searched word sequence centered if ( moveViewport && !matchRectFullyVisible ) { DocumentViewport searchViewport( currentPage ); searchViewport.rePos.enabled = true; searchViewport.rePos.normalizedX = (match->first().left + match->first().right) / 2.0; searchViewport.rePos.normalizedY = (match->first().top + match->first().bottom) / 2.0; m_parent->setViewport( searchViewport, nullptr, true ); } delete match; } // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pagesToNotify; } void DocumentPrivate::doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID) { QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); RunningSearch *search = m_searches.value(searchID); if (m_searchCancelled || !search) { typedef QVector MatchesVector; QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchID, Document::SearchCancelled ); foreach(const MatchesVector &mv, *pageMatches) qDeleteAll(mv); delete pageMatches; delete pagesToNotify; return; } if (currentPage < m_pagesVector.count()) { // get page (from the first to the last) Page *page = m_pagesVector.at(currentPage); int pageNumber = page->number(); // redundant? is it == currentPage ? // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( pageNumber ); // loop on a page adding highlights for all found items RegularAreaRect * lastMatch = nullptr; while ( 1 ) { if ( lastMatch ) lastMatch = page->findText( searchID, search->cachedString, NextResult, search->cachedCaseSensitivity, lastMatch ); else lastMatch = page->findText( searchID, search->cachedString, FromTop, search->cachedCaseSensitivity ); if ( !lastMatch ) break; // add highligh rect to the matches map (*pageMatches)[page].append(lastMatch); } delete lastMatch; QMetaObject::invokeMethod(m_parent, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID)); } else { // reset cursor to previous shape QApplication::restoreOverrideCursor(); search->isCurrentlySearching = false; bool foundAMatch = pageMatches->count() != 0; QMap< Page *, QVector >::const_iterator it, itEnd; it = pageMatches->constBegin(); itEnd = pageMatches->constEnd(); for ( ; it != itEnd; ++it) { foreach(RegularAreaRect *match, it.value()) { it.key()->d->setHighlight( searchID, match, search->cachedColor ); delete match; } search->highlightedPages.insert( it.key()->number() ); pagesToNotify->insert( it.key()->number() ); } foreach(DocumentObserver *observer, m_observers) observer->notifySetup( m_pagesVector, 0 ); // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished(searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pageMatches; delete pagesToNotify; } } void DocumentPrivate::doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words) { typedef QPair MatchColor; QMap< Page *, QVector > *pageMatches = static_cast< QMap< Page *, QVector > * >(pageMatchesMap); QSet< int > *pagesToNotify = static_cast< QSet< int > * >( pagesToNotifySet ); RunningSearch *search = m_searches.value(searchID); if (m_searchCancelled || !search) { typedef QVector MatchesVector; QApplication::restoreOverrideCursor(); if (search) search->isCurrentlySearching = false; emit m_parent->searchFinished( searchID, Document::SearchCancelled ); foreach(const MatchesVector &mv, *pageMatches) { foreach(const MatchColor &mc, mv) delete mc.first; } delete pageMatches; delete pagesToNotify; return; } const int wordCount = words.count(); const int hueStep = (wordCount > 1) ? (60 / (wordCount - 1)) : 60; int baseHue, baseSat, baseVal; search->cachedColor.getHsv( &baseHue, &baseSat, &baseVal ); if (currentPage < m_pagesVector.count()) { // get page (from the first to the last) Page *page = m_pagesVector.at(currentPage); int pageNumber = page->number(); // redundant? is it == currentPage ? // request search page if needed if ( !page->hasTextPage() ) m_parent->requestTextPage( pageNumber ); // loop on a page adding highlights for all found items bool allMatched = wordCount > 0, anyMatched = false; for ( int w = 0; w < wordCount; w++ ) { const QString &word = words[ w ]; int newHue = baseHue - w * hueStep; if ( newHue < 0 ) newHue += 360; QColor wordColor = QColor::fromHsv( newHue, baseSat, baseVal ); RegularAreaRect * lastMatch = nullptr; // add all highlights for current word bool wordMatched = false; while ( 1 ) { if ( lastMatch ) lastMatch = page->findText( searchID, word, NextResult, search->cachedCaseSensitivity, lastMatch ); else lastMatch = page->findText( searchID, word, FromTop, search->cachedCaseSensitivity); if ( !lastMatch ) break; // add highligh rect to the matches map (*pageMatches)[page].append(MatchColor(lastMatch, wordColor)); wordMatched = true; } allMatched = allMatched && wordMatched; anyMatched = anyMatched || wordMatched; } // if not all words are present in page, remove partial highlights const bool matchAll = search->cachedType == Document::GoogleAll; if ( !allMatched && matchAll ) { QVector &matches = (*pageMatches)[page]; foreach(const MatchColor &mc, matches) delete mc.first; pageMatches->remove(page); } QMetaObject::invokeMethod(m_parent, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotifySet), Q_ARG(void *, pageMatches), Q_ARG(int, currentPage + 1), Q_ARG(int, searchID), Q_ARG(QStringList, words)); } else { // reset cursor to previous shape QApplication::restoreOverrideCursor(); search->isCurrentlySearching = false; bool foundAMatch = pageMatches->count() != 0; QMap< Page *, QVector >::const_iterator it, itEnd; it = pageMatches->constBegin(); itEnd = pageMatches->constEnd(); for ( ; it != itEnd; ++it) { foreach(const MatchColor &mc, it.value()) { it.key()->d->setHighlight( searchID, mc.first, mc.second ); delete mc.first; } search->highlightedPages.insert( it.key()->number() ); pagesToNotify->insert( it.key()->number() ); } // send page lists to update observers (since some filter on bookmarks) foreach(DocumentObserver *observer, m_observers) observer->notifySetup( m_pagesVector, 0 ); // notify observers about highlights changes foreach(int pageNumber, *pagesToNotify) foreach(DocumentObserver *observer, m_observers) observer->notifyPageChanged( pageNumber, DocumentObserver::Highlights ); if (foundAMatch) emit m_parent->searchFinished( searchID, Document::MatchFound ); else emit m_parent->searchFinished( searchID, Document::NoMatchFound ); delete pageMatches; delete pagesToNotify; } } QVariant DocumentPrivate::documentMetaData( const Generator::DocumentMetaDataKey key, const QVariant &option ) const { switch ( key ) { case Generator::PaperColorMetaData: { bool giveDefault = option.toBool(); QColor color; if ( ( SettingsCore::renderMode() == SettingsCore::EnumRenderMode::Paper ) && SettingsCore::changeColors() ) { color = SettingsCore::paperColor(); } else if ( giveDefault ) { color = Qt::white; } return color; } break; case Generator::TextAntialiasMetaData: switch ( SettingsCore::textAntialias() ) { case SettingsCore::EnumTextAntialias::Enabled: return true; break; #if 0 case Settings::EnumTextAntialias::UseKDESettings: // TODO: read the KDE configuration return true; break; #endif case SettingsCore::EnumTextAntialias::Disabled: return false; break; } break; case Generator::GraphicsAntialiasMetaData: switch ( SettingsCore::graphicsAntialias() ) { case SettingsCore::EnumGraphicsAntialias::Enabled: return true; break; case SettingsCore::EnumGraphicsAntialias::Disabled: return false; break; } break; case Generator::TextHintingMetaData: switch ( SettingsCore::textHinting() ) { case SettingsCore::EnumTextHinting::Enabled: return true; break; case SettingsCore::EnumTextHinting::Disabled: return false; break; } break; } return QVariant(); } bool DocumentPrivate::isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ) { bool rectFullyVisible = false; const QVector & visibleRects = m_parent->visiblePageRects(); QVector::const_iterator vEnd = visibleRects.end(); QVector::const_iterator vIt = visibleRects.begin(); for ( ; ( vIt != vEnd ) && !rectFullyVisible; ++vIt ) { if ( (*vIt)->pageNumber == rectPage && (*vIt)->rect.contains( rectOfInterest.left, rectOfInterest.top ) && (*vIt)->rect.contains( rectOfInterest.right, rectOfInterest.bottom ) ) { rectFullyVisible = true; } } return rectFullyVisible; } struct pdfsyncpoint { QString file; qlonglong x; qlonglong y; int row; int column; int page; }; void DocumentPrivate::loadSyncFile( const QString & filePath ) { QFile f( filePath + QLatin1String( "sync" ) ); if ( !f.open( QIODevice::ReadOnly ) ) return; QTextStream ts( &f ); // first row: core name of the pdf output const QString coreName = ts.readLine(); // second row: version string, in the form 'Version %u' QString versionstr = ts.readLine(); QRegExp versionre( QStringLiteral("Version (\\d+)") ); versionre.setCaseSensitivity( Qt::CaseInsensitive ); if ( !versionre.exactMatch( versionstr ) ) return; QHash points; QStack fileStack; int currentpage = -1; const QLatin1String texStr( ".tex" ); const QChar spaceChar = QChar::fromLatin1( ' ' ); fileStack.push( coreName + texStr ); const QSizeF dpi = m_generator->dpi(); QString line; while ( !ts.atEnd() ) { line = ts.readLine(); const QStringList tokens = line.split( spaceChar, QString::SkipEmptyParts ); const int tokenSize = tokens.count(); if ( tokenSize < 1 ) continue; if ( tokens.first() == QLatin1String( "l" ) && tokenSize >= 3 ) { int id = tokens.at( 1 ).toInt(); QHash::const_iterator it = points.constFind( id ); if ( it == points.constEnd() ) { pdfsyncpoint pt; pt.x = 0; pt.y = 0; pt.row = tokens.at( 2 ).toInt(); pt.column = 0; // TODO pt.page = -1; pt.file = fileStack.top(); points[ id ] = pt; } } else if ( tokens.first() == QLatin1String( "s" ) && tokenSize >= 2 ) { currentpage = tokens.at( 1 ).toInt() - 1; } else if ( tokens.first() == QLatin1String( "p*" ) && tokenSize >= 4 ) { // TODO qCDebug(OkularCoreDebug) << "PdfSync: 'p*' line ignored"; } else if ( tokens.first() == QLatin1String( "p" ) && tokenSize >= 4 ) { int id = tokens.at( 1 ).toInt(); QHash::iterator it = points.find( id ); if ( it != points.end() ) { it->x = tokens.at( 2 ).toInt(); it->y = tokens.at( 3 ).toInt(); it->page = currentpage; } } else if ( line.startsWith( QLatin1Char( '(' ) ) && tokenSize == 1 ) { QString newfile = line; // chop the leading '(' newfile.remove( 0, 1 ); if ( !newfile.endsWith( texStr ) ) { newfile += texStr; } fileStack.push( newfile ); } else if ( line == QLatin1String( ")" ) ) { if ( !fileStack.isEmpty() ) { fileStack.pop(); } else qCDebug(OkularCoreDebug) << "PdfSync: going one level down too much"; } else qCDebug(OkularCoreDebug).nospace() << "PdfSync: unknown line format: '" << line << "'"; } QVector< QLinkedList< Okular::SourceRefObjectRect * > > refRects( m_pagesVector.size() ); foreach ( const pdfsyncpoint& pt, points ) { // drop pdfsync points not completely valid if ( pt.page < 0 || pt.page >= m_pagesVector.size() ) continue; // magic numbers for TeX's RSU's (Ridiculously Small Units) conversion to pixels Okular::NormalizedPoint p( ( pt.x * dpi.width() ) / ( 72.27 * 65536.0 * m_pagesVector[pt.page]->width() ), ( pt.y * dpi.height() ) / ( 72.27 * 65536.0 * m_pagesVector[pt.page]->height() ) ); QString file = pt.file; Okular::SourceReference * sourceRef = new Okular::SourceReference( file, pt.row, pt.column ); refRects[ pt.page ].append( new Okular::SourceRefObjectRect( p, sourceRef ) ); } for ( int i = 0; i < refRects.size(); ++i ) if ( !refRects.at(i).isEmpty() ) m_pagesVector[i]->setSourceReferences( refRects.at(i) ); } Document::Document( QWidget *widget ) : QObject( nullptr ), d( new DocumentPrivate( this ) ) { d->m_widget = widget; d->m_bookmarkManager = new BookmarkManager( d ); d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), DocumentViewport() ); d->m_undoStack = new QUndoStack(this); connect( SettingsCore::self(), SIGNAL(configChanged()), this, SLOT(_o_configChanged()) ); connect(d->m_undoStack, &QUndoStack::canUndoChanged, this, &Document::canUndoChanged); connect(d->m_undoStack, &QUndoStack::canRedoChanged, this, &Document::canRedoChanged); qRegisterMetaType(); } Document::~Document() { // delete generator, pages, and related stuff closeDocument(); QSet< View * >::const_iterator viewIt = d->m_views.constBegin(), viewEnd = d->m_views.constEnd(); for ( ; viewIt != viewEnd; ++viewIt ) { View *v = *viewIt; v->d_func()->document = nullptr; } // delete the bookmark manager delete d->m_bookmarkManager; // delete the loaded generators QHash< QString, GeneratorInfo >::const_iterator it = d->m_loadedGenerators.constBegin(), itEnd = d->m_loadedGenerators.constEnd(); for ( ; it != itEnd; ++it ) d->unloadGenerator( it.value() ); d->m_loadedGenerators.clear(); // delete the private structure delete d; } QString DocumentPrivate::docDataFileName(const QUrl &url, qint64 document_size) { QString fn = url.fileName(); fn = QString::number( document_size ) + QLatin1Char('.') + fn + QStringLiteral(".xml"); QString docdataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/okular/docdata"); // make sure that the okular/docdata/ directory exists (probably this used to be handled by KStandardDirs) if (!QFileInfo::exists(docdataDir)) { qCDebug(OkularCoreDebug) << "creating docdata folder" << docdataDir; QDir().mkpath(docdataDir); } QString newokularfile = docdataDir + QLatin1Char('/') + fn; // we don't want to accidentally migrate old files when running unit tests if (!QFile::exists( newokularfile ) && !QStandardPaths::isTestModeEnabled()) { // see if an KDE4 file still exists static Kdelibs4Migration k4migration; QString oldfile = k4migration.locateLocal("data", QStringLiteral("okular/docdata/") + fn); if (oldfile.isEmpty()) { oldfile = k4migration.locateLocal("data", QStringLiteral("kpdf/") + fn); } if ( !oldfile.isEmpty() && QFile::exists( oldfile ) ) { // ### copy or move? if ( !QFile::copy( oldfile, newokularfile ) ) return QString(); } } return newokularfile; } QVector DocumentPrivate::availableGenerators() { static QVector result; if (result.isEmpty()) { result = KPluginLoader::findPlugins( QLatin1String ( "okular/generators" ) ); } return result; } KPluginMetaData DocumentPrivate::generatorForMimeType(const QMimeType& type, QWidget* widget, const QVector &triedOffers) { // First try to find an exact match, and then look for more general ones (e. g. the plain text one) // Ideally we would rank these by "closeness", but that might be overdoing it const QVector available = availableGenerators(); QVector offers; QVector exactMatches; QMimeDatabase mimeDatabase; for (const KPluginMetaData& md : available) { if (triedOffers.contains(md)) continue; foreach (const QString& supported, md.mimeTypes()) { QMimeType mimeType = mimeDatabase.mimeTypeForName(supported); if (mimeType == type && !exactMatches.contains(md)) { exactMatches << md; } if (type.inherits(supported) && !offers.contains(md)) { offers << md; } } } if (!exactMatches.isEmpty()) { offers = exactMatches; } if (offers.isEmpty()) { return KPluginMetaData(); } int hRank=0; // best ranked offer search int offercount = offers.size(); if (offercount > 1) { // sort the offers: the offers with an higher priority come before auto cmp = [](const KPluginMetaData& s1, const KPluginMetaData& s2) { const QString property = QStringLiteral("X-KDE-Priority"); return s1.rawData()[property].toInt() > s2.rawData()[property].toInt(); }; std::stable_sort(offers.begin(), offers.end(), cmp); if (SettingsCore::chooseGenerators()) { QStringList list; for (int i = 0; i < offercount; ++i) { list << offers.at(i).pluginId(); } ChooseEngineDialog choose(list, type, widget); if (choose.exec() == QDialog::Rejected) return KPluginMetaData(); hRank = choose.selectedGenerator(); } } Q_ASSERT(hRank < offers.size()); return offers.at(hRank); } Document::OpenResult Document::openDocument(const QString & docFile, const QUrl &url, const QMimeType &_mime, const QString & password ) { QMimeDatabase db; QMimeType mime = _mime; QByteArray filedata; qint64 document_size = -1; bool isstdin = url.fileName() == QLatin1String( "-" ); bool triedMimeFromFileContent = false; if ( !isstdin ) { if ( !mime.isValid() ) return OpenError; // docFile is always local so we can use QFileInfo on it QFileInfo fileReadTest( docFile ); if ( fileReadTest.isFile() && !fileReadTest.isReadable() ) { d->m_docFileName.clear(); return OpenError; } // determine the related "xml document-info" filename d->m_url = url; d->m_docFileName = docFile; if ( url.isLocalFile() && !d->m_archiveData ) { document_size = fileReadTest.size(); d->m_xmlFileName = DocumentPrivate::docDataFileName(url, document_size); } } else { QFile qstdin; qstdin.open( stdin, QIODevice::ReadOnly ); filedata = qstdin.readAll(); mime = db.mimeTypeForData( filedata ); if ( !mime.isValid() || mime.isDefault() ) return OpenError; document_size = filedata.size(); triedMimeFromFileContent = true; } // 0. load Generator // request only valid non-disabled plugins suitable for the mimetype KPluginMetaData offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget); if ( !offer.isValid() && !triedMimeFromFileContent ) { QMimeType newmime = db.mimeTypeForFile(docFile, QMimeDatabase::MatchExtension); triedMimeFromFileContent = true; if ( newmime != mime ) { mime = newmime; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget); } if ( !offer.isValid() ) { // There's still no offers, do a final mime search based on the filename // We need this because sometimes (e.g. when downloading from a webserver) the mimetype we // use is the one fed by the server, that may be wrong newmime = db.mimeTypeForUrl( url ); if ( !newmime.isDefault() && newmime != mime ) { mime = newmime; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget); } } } if (!offer.isValid()) { emit error( i18n( "Can not find a plugin which is able to handle the document being passed." ), -1 ); qCWarning(OkularCoreDebug).nospace() << "No plugin for mimetype '" << mime.name() << "'."; return OpenError; } // 1. load Document OpenResult openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); if ( openResult == OpenError ) { QVector triedOffers; triedOffers << offer; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); while ( offer.isValid() ) { openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); if ( openResult == OpenError ) { triedOffers << offer; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); } else break; } if (openResult == OpenError && !triedMimeFromFileContent ) { QMimeType newmime = db.mimeTypeForFile(docFile, QMimeDatabase::MatchExtension); triedMimeFromFileContent = true; if ( newmime != mime ) { mime = newmime; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); while ( offer.isValid() ) { openResult = d->openDocumentInternal( offer, isstdin, docFile, filedata, password ); if ( openResult == OpenError ) { triedOffers << offer; offer = DocumentPrivate::generatorForMimeType(mime, d->m_widget, triedOffers); } else break; } } } if ( openResult == OpenSuccess ) { // Clear errors, since we're trying various generators, maybe one of them errored out // but we finally succeeded // TODO one can still see the error message animating out but since this is a very rare // condition we can leave this for future work emit error( QString(), -1 ); } } if ( openResult != OpenSuccess ) { return openResult; } // no need to check for the existence of a synctex file, no parser will be // created if none exists d->m_synctex_scanner = synctex_scanner_new_with_output_file( QFile::encodeName( docFile ).constData(), nullptr, 1); if ( !d->m_synctex_scanner && QFile::exists(docFile + QLatin1String( "sync" ) ) ) { d->loadSyncFile(docFile); } d->m_generatorName = offer.pluginId(); d->m_pageController = new PageController(); connect( d->m_pageController, SIGNAL(rotationFinished(int,Okular::Page*)), this, SLOT(rotationFinished(int,Okular::Page*)) ); bool containsExternalAnnotations = false; foreach ( Page * p, d->m_pagesVector ) { p->d->m_doc = d; if ( !p->annotations().empty() ) containsExternalAnnotations = true; } // Be quiet while restoring local annotations d->m_showWarningLimitedAnnotSupport = false; d->m_annotationsNeedSaveAs = false; // 2. load Additional Data (bookmarks, local annotations and metadata) about the document if ( d->m_archiveData ) { d->loadDocumentInfo( d->m_archiveData->metadataFile ); d->m_annotationsNeedSaveAs = true; } else { d->loadDocumentInfo(); d->m_annotationsNeedSaveAs = ( d->canAddAnnotationsNatively() && containsExternalAnnotations ); } d->m_showWarningLimitedAnnotSupport = true; d->m_bookmarkManager->setUrl( d->m_url ); // 3. setup observers inernal lists and data foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ) ); // 4. set initial page (restoring the page saved in xml if loaded) DocumentViewport loadedViewport = (*d->m_viewportIterator); if ( loadedViewport.isValid() ) { (*d->m_viewportIterator) = DocumentViewport(); if ( loadedViewport.pageNumber >= (int)d->m_pagesVector.size() ) loadedViewport.pageNumber = d->m_pagesVector.size() - 1; } else loadedViewport.pageNumber = 0; setViewport( loadedViewport ); // start bookmark saver timer if ( !d->m_saveBookmarksTimer ) { d->m_saveBookmarksTimer = new QTimer( this ); connect( d->m_saveBookmarksTimer, SIGNAL(timeout()), this, SLOT(saveDocumentInfo()) ); } d->m_saveBookmarksTimer->start( 5 * 60 * 1000 ); // start memory check timer if ( !d->m_memCheckTimer ) { d->m_memCheckTimer = new QTimer( this ); connect( d->m_memCheckTimer, SIGNAL(timeout()), this, SLOT(slotTimedMemoryCheck()) ); } d->m_memCheckTimer->start( 2000 ); const DocumentViewport nextViewport = d->nextDocumentViewport(); if ( nextViewport.isValid() ) { setViewport( nextViewport ); d->m_nextDocumentViewport = DocumentViewport(); d->m_nextDocumentDestination = QString(); } AudioPlayer::instance()->d->m_currentDocument = isstdin ? QUrl() : d->m_url; d->m_docSize = document_size; const QStringList docScripts = d->m_generator->metaData( QStringLiteral("DocumentScripts"), QStringLiteral ( "JavaScript" ) ).toStringList(); if ( !docScripts.isEmpty() ) { d->m_scripter = new Scripter( d ); Q_FOREACH ( const QString &docscript, docScripts ) { d->m_scripter->execute( JavaScript, docscript ); } } return OpenSuccess; } KXMLGUIClient* Document::guiClient() { if ( d->m_generator ) { Okular::GuiInterface * iface = qobject_cast< Okular::GuiInterface * >( d->m_generator ); if ( iface ) return iface->guiClient(); } return nullptr; } void Document::closeDocument() { // check if there's anything to close... if ( !d->m_generator ) return; delete d->m_pageController; d->m_pageController = nullptr; delete d->m_scripter; d->m_scripter = nullptr; // remove requests left in queue d->m_pixmapRequestsMutex.lock(); QLinkedList< PixmapRequest * >::const_iterator sIt = d->m_pixmapRequestsStack.constBegin(); QLinkedList< PixmapRequest * >::const_iterator sEnd = d->m_pixmapRequestsStack.constEnd(); for ( ; sIt != sEnd; ++sIt ) delete *sIt; d->m_pixmapRequestsStack.clear(); d->m_pixmapRequestsMutex.unlock(); QEventLoop loop; bool startEventLoop = false; do { d->m_pixmapRequestsMutex.lock(); startEventLoop = !d->m_executingPixmapRequests.isEmpty(); d->m_pixmapRequestsMutex.unlock(); if ( startEventLoop ) { d->m_closingLoop = &loop; loop.exec(); d->m_closingLoop = nullptr; } } while ( startEventLoop ); if ( d->m_fontThread ) { disconnect( d->m_fontThread, nullptr, this, nullptr ); d->m_fontThread->stopExtraction(); d->m_fontThread->wait(); d->m_fontThread = nullptr; } // stop any audio playback AudioPlayer::instance()->stopPlaybacks(); // close the current document and save document info if a document is still opened if ( d->m_generator && d->m_pagesVector.size() > 0 ) { d->saveDocumentInfo(); d->m_generator->closeDocument(); } if ( d->m_synctex_scanner ) { synctex_scanner_free( d->m_synctex_scanner ); d->m_synctex_scanner = nullptr; } // stop timers if ( d->m_memCheckTimer ) d->m_memCheckTimer->stop(); if ( d->m_saveBookmarksTimer ) d->m_saveBookmarksTimer->stop(); if ( d->m_generator ) { // disconnect the generator from this document ... d->m_generator->d_func()->m_document = nullptr; // .. and this document from the generator signals disconnect( d->m_generator, nullptr, this, nullptr ); QHash< QString, GeneratorInfo >::const_iterator genIt = d->m_loadedGenerators.constFind( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.constEnd() ); } d->m_generator = nullptr; d->m_generatorName = QString(); d->m_url = QUrl(); d->m_walletGenerator = nullptr; d->m_docFileName = QString(); d->m_xmlFileName = QString(); delete d->m_tempFile; d->m_tempFile = nullptr; delete d->m_archiveData; d->m_archiveData = nullptr; d->m_docSize = -1; d->m_exportCached = false; d->m_exportFormats.clear(); d->m_exportToText = ExportFormat(); d->m_fontsCached = false; d->m_fontsCache.clear(); d->m_rotation = Rotation0; // send an empty list to observers (to free their data) foreachObserver( notifySetup( QVector< Page * >(), DocumentObserver::DocumentChanged ) ); // delete pages and clear 'd->m_pagesVector' container QVector< Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) delete *pIt; d->m_pagesVector.clear(); // clear 'memory allocation' descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); // clear 'running searches' descriptors QMap< int, RunningSearch * >::const_iterator rIt = d->m_searches.constBegin(); QMap< int, RunningSearch * >::const_iterator rEnd = d->m_searches.constEnd(); for ( ; rIt != rEnd; ++rIt ) delete *rIt; d->m_searches.clear(); // clear the visible areas and notify the observers QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) delete *vIt; d->m_pageRects.clear(); foreachObserver( notifyVisibleRectsChanged() ); // reset internal variables d->m_viewportHistory.clear(); d->m_viewportHistory.append( DocumentViewport() ); d->m_viewportIterator = d->m_viewportHistory.begin(); d->m_allocatedPixmapsTotalMemory = 0; d->m_allocatedTextPagesFifo.clear(); d->m_pageSize = PageSize(); d->m_pageSizes.clear(); d->m_documentInfo = DocumentInfo(); d->m_documentInfoAskedKeys.clear(); AudioPlayer::instance()->d->m_currentDocument = QUrl(); d->m_undoStack->clear(); } void Document::addObserver( DocumentObserver * pObserver ) { Q_ASSERT( !d->m_observers.contains( pObserver ) ); d->m_observers << pObserver; // if the observer is added while a document is already opened, tell it if ( !d->m_pagesVector.isEmpty() ) { pObserver->notifySetup( d->m_pagesVector, DocumentObserver::DocumentChanged ); pObserver->notifyViewportChanged( false /*disables smoothMove*/ ); } } void Document::removeObserver( DocumentObserver * pObserver ) { // remove observer from the map. it won't receive notifications anymore if ( d->m_observers.contains( pObserver ) ) { // free observer's pixmap data QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); for ( ; it != end; ++it ) (*it)->deletePixmap( pObserver ); // [MEM] free observer's allocation descriptors QLinkedList< AllocatedPixmap * >::iterator aIt = d->m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator aEnd = d->m_allocatedPixmaps.end(); while ( aIt != aEnd ) { AllocatedPixmap * p = *aIt; if ( p->observer == pObserver ) { aIt = d->m_allocatedPixmaps.erase( aIt ); delete p; } else ++aIt; } // delete observer entry from the map d->m_observers.remove( pObserver ); } } void Document::reparseConfig() { // reparse generator config and if something changed clear Pages bool configchanged = false; if ( d->m_generator ) { Okular::ConfigInterface * iface = qobject_cast< Okular::ConfigInterface * >( d->m_generator ); if ( iface ) configchanged = iface->reparseConfig(); } if ( configchanged ) { // invalidate pixmaps QVector::const_iterator it = d->m_pagesVector.constBegin(), end = d->m_pagesVector.constEnd(); for ( ; it != end; ++it ) { (*it)->deletePixmaps(); } // [MEM] remove allocation descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); d->m_allocatedPixmapsTotalMemory = 0; // send reload signals to observers foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) ); } // free memory if in 'low' profile if ( SettingsCore::memoryLevel() == SettingsCore::EnumMemoryLevel::Low && !d->m_allocatedPixmaps.isEmpty() && !d->m_pagesVector.isEmpty() ) d->cleanupPixmapMemory(); } bool Document::isOpened() const { return d->m_generator; } bool Document::canConfigurePrinter( ) const { if ( d->m_generator ) { Okular::PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); return iface ? true : false; } else return 0; } DocumentInfo Document::documentInfo() const { QSet keys; for (Okular::DocumentInfo::Key ks = Okular::DocumentInfo::Title; ks < Okular::DocumentInfo::Invalid; ks = Okular::DocumentInfo::Key( ks+1 ) ) { keys << ks; } return documentInfo( keys ); } DocumentInfo Document::documentInfo( const QSet &keys ) const { DocumentInfo result = d->m_documentInfo; const QSet missingKeys = keys - d->m_documentInfoAskedKeys; if ( d->m_generator && !missingKeys.isEmpty() ) { DocumentInfo info = d->m_generator->generateDocumentInfo( missingKeys ); if ( missingKeys.contains( DocumentInfo::FilePath ) ) { info.set( DocumentInfo::FilePath, currentDocument().toDisplayString() ); } if ( d->m_docSize != -1 && missingKeys.contains( DocumentInfo::DocumentSize ) ) { const QString sizeString = KFormat().formatByteSize( d->m_docSize ); info.set( DocumentInfo::DocumentSize, sizeString ); } if ( missingKeys.contains( DocumentInfo::PagesSize ) ) { const QString pagesSize = d->pagesSizeString(); if ( !pagesSize.isEmpty() ) { info.set( DocumentInfo::PagesSize, pagesSize ); } } if ( missingKeys.contains( DocumentInfo::Pages ) && info.get( DocumentInfo::Pages ).isEmpty() ) { info.set( DocumentInfo::Pages, QString::number( this->pages() ) ); } d->m_documentInfo.d->values.unite(info.d->values); d->m_documentInfo.d->titles.unite(info.d->titles); result.d->values.unite(info.d->values); result.d->titles.unite(info.d->titles); } d->m_documentInfoAskedKeys += keys; return result; } const DocumentSynopsis * Document::documentSynopsis() const { return d->m_generator ? d->m_generator->generateDocumentSynopsis() : nullptr; } void Document::startFontReading() { if ( !d->m_generator || !d->m_generator->hasFeature( Generator::FontInfo ) || d->m_fontThread ) return; if ( d->m_fontsCached ) { // in case we have cached fonts, simulate a reading // this way the API is the same, and users no need to care about the // internal caching for ( int i = 0; i < d->m_fontsCache.count(); ++i ) { emit gotFont( d->m_fontsCache.at( i ) ); emit fontReadingProgress( i / pages() ); } emit fontReadingEnded(); return; } d->m_fontThread = new FontExtractionThread( d->m_generator, pages() ); connect( d->m_fontThread, SIGNAL(gotFont(Okular::FontInfo)), this, SLOT(fontReadingGotFont(Okular::FontInfo)) ); connect( d->m_fontThread.data(), SIGNAL(progress(int)), this, SLOT(slotFontReadingProgress(int)) ); d->m_fontThread->startExtraction( /*d->m_generator->hasFeature( Generator::Threaded )*/true ); } void Document::stopFontReading() { if ( !d->m_fontThread ) return; disconnect( d->m_fontThread, nullptr, this, nullptr ); d->m_fontThread->stopExtraction(); d->m_fontThread = nullptr; d->m_fontsCache.clear(); } bool Document::canProvideFontInformation() const { return d->m_generator ? d->m_generator->hasFeature( Generator::FontInfo ) : false; } const QList *Document::embeddedFiles() const { return d->m_generator ? d->m_generator->embeddedFiles() : nullptr; } const Page * Document::page( int n ) const { return ( n < d->m_pagesVector.count() ) ? d->m_pagesVector.at(n) : 0; } const DocumentViewport & Document::viewport() const { return (*d->m_viewportIterator); } const QVector< VisiblePageRect * > & Document::visiblePageRects() const { return d->m_pageRects; } void Document::setVisiblePageRects( const QVector< VisiblePageRect * > & visiblePageRects, DocumentObserver *excludeObserver ) { QVector< VisiblePageRect * >::const_iterator vIt = d->m_pageRects.constBegin(); QVector< VisiblePageRect * >::const_iterator vEnd = d->m_pageRects.constEnd(); for ( ; vIt != vEnd; ++vIt ) delete *vIt; d->m_pageRects = visiblePageRects; // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) if ( o != excludeObserver ) o->notifyVisibleRectsChanged(); } uint Document::currentPage() const { return (*d->m_viewportIterator).pageNumber; } uint Document::pages() const { return d->m_pagesVector.size(); } QUrl Document::currentDocument() const { return d->m_url; } bool Document::isAllowed( Permission action ) const { if ( action == Okular::AllowNotes && !d->m_annotationEditingEnabled ) return false; #if !OKULAR_FORCE_DRM if ( KAuthorized::authorize( QStringLiteral("skip_drm") ) && !SettingsCore::obeyDRM() ) return true; #endif return d->m_generator ? d->m_generator->isAllowed( action ) : false; } bool Document::supportsSearching() const { return d->m_generator ? d->m_generator->hasFeature( Generator::TextExtraction ) : false; } bool Document::supportsPageSizes() const { return d->m_generator ? d->m_generator->hasFeature( Generator::PageSizes ) : false; } bool Document::supportsTiles() const { return d->m_generator ? d->m_generator->hasFeature( Generator::TiledRendering ) : false; } PageSize::List Document::pageSizes() const { if ( d->m_generator ) { if ( d->m_pageSizes.isEmpty() ) d->m_pageSizes = d->m_generator->pageSizes(); return d->m_pageSizes; } return PageSize::List(); } bool Document::canExportToText() const { if ( !d->m_generator ) return false; d->cacheExportFormats(); return !d->m_exportToText.isNull(); } bool Document::exportToText( const QString& fileName ) const { if ( !d->m_generator ) return false; d->cacheExportFormats(); if ( d->m_exportToText.isNull() ) return false; return d->m_generator->exportTo( fileName, d->m_exportToText ); } ExportFormat::List Document::exportFormats() const { if ( !d->m_generator ) return ExportFormat::List(); d->cacheExportFormats(); return d->m_exportFormats; } bool Document::exportTo( const QString& fileName, const ExportFormat& format ) const { return d->m_generator ? d->m_generator->exportTo( fileName, format ) : false; } bool Document::historyAtBegin() const { return d->m_viewportIterator == d->m_viewportHistory.begin(); } bool Document::historyAtEnd() const { return d->m_viewportIterator == --(d->m_viewportHistory.end()); } QVariant Document::metaData( const QString & key, const QVariant & option ) const { // if option starts with "src:" assume that we are handling a // source reference if ( key == QLatin1String("NamedViewport") && option.toString().startsWith( QLatin1String("src:"), Qt::CaseInsensitive ) && d->m_synctex_scanner) { const QString reference = option.toString(); // The reference is of form "src:1111Filename", where "1111" // points to line number 1111 in the file "Filename". // Extract the file name and the numeral part from the reference string. // This will fail if Filename starts with a digit. QString name, lineString; // Remove "src:". Presence of substring has been checked before this // function is called. name = reference.mid( 4 ); // split int nameLength = name.length(); int i = 0; for( i = 0; i < nameLength; ++i ) { if ( !name[i].isDigit() ) break; } lineString = name.left( i ); name = name.mid( i ); // Remove spaces. name = name.trimmed(); lineString = lineString.trimmed(); // Convert line to integer. bool ok; int line = lineString.toInt( &ok ); if (!ok) line = -1; // Use column == -1 for now. - if( synctex_display_query( d->m_synctex_scanner, QFile::encodeName(name).constData(), line, -1 ) > 0 ) + if( synctex_display_query( d->m_synctex_scanner, QFile::encodeName(name).constData(), line, -1, 0 ) > 0 ) { - synctex_node_t node; + synctex_node_p node; // For now use the first hit. Could possibly be made smarter // in case there are multiple hits. - while( ( node = synctex_next_result( d->m_synctex_scanner ) ) ) + while( ( node = synctex_scanner_next_result( d->m_synctex_scanner ) ) ) { Okular::DocumentViewport viewport; // TeX pages start at 1. viewport.pageNumber = synctex_node_page( node ) - 1; if ( viewport.pageNumber >= 0 ) { const QSizeF dpi = d->m_generator->dpi(); // TeX small points ... double px = (synctex_node_visible_h( node ) * dpi.width()) / 72.27; double py = (synctex_node_visible_v( node ) * dpi.height()) / 72.27; viewport.rePos.normalizedX = px / page(viewport.pageNumber)->width(); viewport.rePos.normalizedY = ( py + 0.5 ) / page(viewport.pageNumber)->height(); viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::Center; return viewport.toString(); } } } } return d->m_generator ? d->m_generator->metaData( key, option ) : QVariant(); } Rotation Document::rotation() const { return d->m_rotation; } QSizeF Document::allPagesSize() const { bool allPagesSameSize = true; QSizeF size; for (int i = 0; allPagesSameSize && i < d->m_pagesVector.count(); ++i) { const Page *p = d->m_pagesVector.at(i); if (i == 0) size = QSizeF(p->width(), p->height()); else { allPagesSameSize = (size == QSizeF(p->width(), p->height())); } } if (allPagesSameSize) return size; else return QSizeF(); } QString Document::pageSizeString(int page) const { if (d->m_generator) { if (d->m_generator->pagesSizeMetric() != Generator::None) { const Page *p = d->m_pagesVector.at( page ); return d->localizedSize(QSizeF(p->width(), p->height())); } } return QString(); } void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests ) { requestPixmaps( requests, RemoveAllPrevious ); } void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests, PixmapRequestFlags reqOptions ) { if ( requests.isEmpty() ) return; if ( !d->m_pageController ) { // delete requests.. QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) delete *rIt; // ..and return return; } // 1. [CLEAN STACK] remove previous requests of requesterID // FIXME This assumes all requests come from the same observer, that is true atm but not enforced anywhere DocumentObserver *requesterObserver = requests.first()->observer(); QSet< int > requestedPages; { QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) requestedPages.insert( (*rIt)->pageNumber() ); } const bool removeAllPrevious = reqOptions & RemoveAllPrevious; d->m_pixmapRequestsMutex.lock(); QLinkedList< PixmapRequest * >::iterator sIt = d->m_pixmapRequestsStack.begin(), sEnd = d->m_pixmapRequestsStack.end(); while ( sIt != sEnd ) { if ( (*sIt)->observer() == requesterObserver && ( removeAllPrevious || requestedPages.contains( (*sIt)->pageNumber() ) ) ) { // delete request and remove it from stack delete *sIt; sIt = d->m_pixmapRequestsStack.erase( sIt ); } else ++sIt; } // 2. [ADD TO STACK] add requests to stack QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) { // set the 'page field' (see PixmapRequest) and check if it is valid PixmapRequest * request = *rIt; qCDebug(OkularCoreDebug).nospace() << "request observer=" << request->observer() << " " <width() << "x" << request->height() << "@" << request->pageNumber(); if ( d->m_pagesVector.value( request->pageNumber() ) == 0 ) { // skip requests referencing an invalid page (must not happen) delete request; continue; } request->d->mPage = d->m_pagesVector.value( request->pageNumber() ); if ( request->isTile() ) { // Change the current request rect so that only invalid tiles are // requested. Also make sure the rect is tile-aligned. NormalizedRect tilesRect; const QList tiles = request->d->tilesManager()->tilesAt( request->normalizedRect(), TilesManager::TerminalTile ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Tile &tile = *tIt; if ( !tile.isValid() ) { if ( tilesRect.isNull() ) tilesRect = tile.rect(); else tilesRect |= tile.rect(); } tIt++; } request->setNormalizedRect( tilesRect ); } if ( !request->asynchronous() ) request->d->mPriority = 0; // add request to the 'stack' at the right place if ( !request->priority() ) // add priority zero requests to the top of the stack d->m_pixmapRequestsStack.append( request ); else { // insert in stack sorted by priority sIt = d->m_pixmapRequestsStack.begin(); sEnd = d->m_pixmapRequestsStack.end(); while ( sIt != sEnd && (*sIt)->priority() > request->priority() ) ++sIt; d->m_pixmapRequestsStack.insert( sIt, request ); } } d->m_pixmapRequestsMutex.unlock(); // 3. [START FIRST GENERATION] if generator is ready, start a new generation, // or else (if gen is running) it will be started when the new contents will //come from generator (in requestDone()) // all handling of requests put into sendGeneratorPixmapRequest // if ( generator->canRequestPixmap() ) d->sendGeneratorPixmapRequest(); } void Document::requestTextPage( uint page ) { Page * kp = d->m_pagesVector[ page ]; if ( !d->m_generator || !kp ) return; // Memory management for TextPages d->m_generator->generateTextPage( kp ); } void DocumentPrivate::notifyAnnotationChanges( int page ) { int flags = DocumentObserver::Annotations; if ( m_annotationsNeedSaveAs ) flags |= DocumentObserver::NeedSaveAs; foreachObserverD( notifyPageChanged( page, flags ) ); } void Document::addPageAnnotation( int page, Annotation * annotation ) { // Transform annotation's base boundary rectangle into unrotated coordinates Page *p = d->m_pagesVector[page]; QTransform t = p->d->rotationMatrix(); annotation->d_ptr->baseTransform(t.inverted()); QUndoCommand *uc = new AddAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } bool Document::canModifyPageAnnotation( const Annotation * annotation ) const { if ( !annotation || ( annotation->flags() & Annotation::DenyWrite ) ) return false; if ( !isAllowed(Okular::AllowNotes) ) return false; if ( ( annotation->flags() & Annotation::External ) && !d->canModifyExternalAnnotations() ) return false; switch ( annotation->subType() ) { case Annotation::AText: case Annotation::ALine: case Annotation::AGeom: case Annotation::AHighlight: case Annotation::AStamp: case Annotation::AInk: return true; default: return false; } } void Document::prepareToModifyAnnotationProperties( Annotation * annotation ) { Q_ASSERT(d->m_prevPropsOfAnnotBeingModified.isNull()); if (!d->m_prevPropsOfAnnotBeingModified.isNull()) { qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties has already been called since last call to Document::modifyPageAnnotationProperties"; return; } d->m_prevPropsOfAnnotBeingModified = annotation->getAnnotationPropertiesDomNode(); } void Document::modifyPageAnnotationProperties( int page, Annotation * annotation ) { Q_ASSERT(!d->m_prevPropsOfAnnotBeingModified.isNull()); if (d->m_prevPropsOfAnnotBeingModified.isNull()) { qCCritical(OkularCoreDebug) << "Error: Document::prepareToModifyAnnotationProperties must be called before Annotation is modified"; return; } QDomNode prevProps = d->m_prevPropsOfAnnotBeingModified; QUndoCommand *uc = new Okular::ModifyAnnotationPropertiesCommand( d, annotation, page, prevProps, annotation->getAnnotationPropertiesDomNode() ); d->m_undoStack->push( uc ); d->m_prevPropsOfAnnotBeingModified.clear(); } void Document::translatePageAnnotation(int page, Annotation* annotation, const NormalizedPoint & delta ) { int complete = (annotation->flags() & Okular::Annotation::BeingMoved) == 0; QUndoCommand *uc = new Okular::TranslateAnnotationCommand( d, annotation, page, delta, complete ); d->m_undoStack->push(uc); } void Document::adjustPageAnnotation( int page, Annotation *annotation, const Okular::NormalizedPoint & delta1, const Okular::NormalizedPoint & delta2 ) { const bool complete = (annotation->flags() & Okular::Annotation::BeingResized) == 0; QUndoCommand *uc = new Okular::AdjustAnnotationCommand( d, annotation, page, delta1, delta2, complete ); d->m_undoStack->push(uc); } void Document::editPageAnnotationContents( int page, Annotation* annotation, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QString prevContents = annotation->contents(); QUndoCommand *uc = new EditAnnotationContentsCommand( d, annotation, page, newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); } bool Document::canRemovePageAnnotation( const Annotation * annotation ) const { if ( !annotation || ( annotation->flags() & Annotation::DenyDelete ) ) return false; if ( ( annotation->flags() & Annotation::External ) && !d->canRemoveExternalAnnotations() ) return false; switch ( annotation->subType() ) { case Annotation::AText: case Annotation::ALine: case Annotation::AGeom: case Annotation::AHighlight: case Annotation::AStamp: case Annotation::AInk: case Annotation::ACaret: return true; default: return false; } } void Document::removePageAnnotation( int page, Annotation * annotation ) { QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } void Document::removePageAnnotations( int page, const QList &annotations ) { d->m_undoStack->beginMacro(i18nc("remove a collection of annotations from the page", "remove annotations")); foreach(Annotation* annotation, annotations) { QUndoCommand *uc = new RemoveAnnotationCommand(this->d, annotation, page); d->m_undoStack->push(uc); } d->m_undoStack->endMacro(); } bool DocumentPrivate::canAddAnnotationsNatively() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Addition) ) return true; return false; } bool DocumentPrivate::canModifyExternalAnnotations() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Modification) ) return true; return false; } bool DocumentPrivate::canRemoveExternalAnnotations() const { Okular::SaveInterface * iface = qobject_cast< Okular::SaveInterface * >( m_generator ); if ( iface && iface->supportsOption(Okular::SaveInterface::SaveChanges) && iface->annotationProxy() && iface->annotationProxy()->supports(AnnotationProxy::Removal) ) return true; return false; } void Document::setPageTextSelection( int page, RegularAreaRect * rect, const QColor & color ) { Page * kp = d->m_pagesVector[ page ]; if ( !d->m_generator || !kp ) return; // add or remove the selection basing whether rect is null or not if ( rect ) kp->d->setTextSelections( rect, color ); else kp->d->deleteTextSelections(); // notify observers about the change foreachObserver( notifyPageChanged( page, DocumentObserver::TextSelection ) ); } bool Document::canUndo() const { return d->m_undoStack->canUndo(); } bool Document::canRedo() const { return d->m_undoStack->canRedo(); } /* REFERENCE IMPLEMENTATION: better calling setViewport from other code void Document::setNextPage() { // advance page and set viewport on observers if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber + 1 ) ); } void Document::setPrevPage() { // go to previous page and set viewport on observers if ( (*d->m_viewportIterator).pageNumber > 0 ) setViewport( DocumentViewport( (*d->m_viewportIterator).pageNumber - 1 ) ); } */ void Document::setViewportPage( int page, DocumentObserver *excludeObserver, bool smoothMove ) { // clamp page in range [0 ... numPages-1] if ( page < 0 ) page = 0; else if ( page > (int)d->m_pagesVector.count() ) page = d->m_pagesVector.count() - 1; // make a viewport from the page and broadcast it setViewport( DocumentViewport( page ), excludeObserver, smoothMove ); } void Document::setViewport( const DocumentViewport & viewport, DocumentObserver *excludeObserver, bool smoothMove ) { if ( !viewport.isValid() ) { qCDebug(OkularCoreDebug) << "invalid viewport:" << viewport.toString(); return; } if ( viewport.pageNumber >= int(d->m_pagesVector.count()) ) { //qCDebug(OkularCoreDebug) << "viewport out of document:" << viewport.toString(); return; } // if already broadcasted, don't redo it DocumentViewport & oldViewport = *d->m_viewportIterator; // disabled by enrico on 2005-03-18 (less debug output) //if ( viewport == oldViewport ) // qCDebug(OkularCoreDebug) << "setViewport with the same viewport."; const int oldPageNumber = oldViewport.pageNumber; // set internal viewport taking care of history if ( oldViewport.pageNumber == viewport.pageNumber || !oldViewport.isValid() ) { // if page is unchanged save the viewport at current position in queue oldViewport = viewport; } else { // remove elements after viewportIterator in queue d->m_viewportHistory.erase( ++d->m_viewportIterator, d->m_viewportHistory.end() ); // keep the list to a reasonable size by removing head when needed if ( d->m_viewportHistory.count() >= OKULAR_HISTORY_MAXSTEPS ) d->m_viewportHistory.pop_front(); // add the item at the end of the queue d->m_viewportIterator = d->m_viewportHistory.insert( d->m_viewportHistory.end(), viewport ); } const int currentViewportPage = (*d->m_viewportIterator).pageNumber; const bool currentPageChanged = (oldPageNumber != currentViewportPage); // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) { if ( o != excludeObserver ) o->notifyViewportChanged( smoothMove ); if ( currentPageChanged ) o->notifyCurrentPageChanged( oldPageNumber, currentViewportPage ); } } void Document::setZoom(int factor, DocumentObserver *excludeObserver) { // notify change to all other (different from id) observers foreach(DocumentObserver *o, d->m_observers) if (o != excludeObserver) o->notifyZoom( factor ); } void Document::setPrevViewport() // restore viewport from the history { if ( d->m_viewportIterator != d->m_viewportHistory.begin() ) { const int oldViewportPage = (*d->m_viewportIterator).pageNumber; // restore previous viewport and notify it to observers --d->m_viewportIterator; foreachObserver( notifyViewportChanged( true ) ); const int currentViewportPage = (*d->m_viewportIterator).pageNumber; if (oldViewportPage != currentViewportPage) foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); } } void Document::setNextViewport() // restore next viewport from the history { QLinkedList< DocumentViewport >::const_iterator nextIterator = d->m_viewportIterator; ++nextIterator; if ( nextIterator != d->m_viewportHistory.end() ) { const int oldViewportPage = (*d->m_viewportIterator).pageNumber; // restore next viewport and notify it to observers ++d->m_viewportIterator; foreachObserver( notifyViewportChanged( true ) ); const int currentViewportPage = (*d->m_viewportIterator).pageNumber; if (oldViewportPage != currentViewportPage) foreachObserver( notifyCurrentPageChanged( oldViewportPage, currentViewportPage ) ); } } void Document::setNextDocumentViewport( const DocumentViewport & viewport ) { d->m_nextDocumentViewport = viewport; } void Document::setNextDocumentDestination( const QString &namedDestination ) { d->m_nextDocumentDestination = namedDestination; } void Document::searchText( int searchID, const QString & text, bool fromStart, Qt::CaseSensitivity caseSensitivity, SearchType type, bool moveViewport, const QColor & color ) { d->m_searchCancelled = false; // safety checks: don't perform searches on empty or unsearchable docs if ( !d->m_generator || !d->m_generator->hasFeature( Generator::TextExtraction ) || d->m_pagesVector.isEmpty() ) { emit searchFinished( searchID, NoMatchFound ); return; } // if searchID search not recorded, create new descriptor and init params QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); if ( searchIt == d->m_searches.end() ) { RunningSearch * search = new RunningSearch(); search->continueOnPage = -1; searchIt = d->m_searches.insert( searchID, search ); } RunningSearch * s = *searchIt; // update search structure bool newText = text != s->cachedString; s->cachedString = text; s->cachedType = type; s->cachedCaseSensitivity = caseSensitivity; s->cachedViewportMove = moveViewport; s->cachedColor = color; s->isCurrentlySearching = true; // global data for search QSet< int > *pagesToNotify = new QSet< int >; // remove highlights from pages and queue them for notifying changes *pagesToNotify += s->highlightedPages; foreach(int pageNumber, s->highlightedPages) d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); s->highlightedPages.clear(); // set hourglass cursor QApplication::setOverrideCursor( Qt::WaitCursor ); // 1. ALLDOC - proces all document marking pages if ( type == AllDocument ) { QMap< Page *, QVector > *pageMatches = new QMap< Page *, QVector >; // search and highlight 'text' (as a solid phrase) on all pages QMetaObject::invokeMethod(this, "doContinueAllDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID)); } // 2. NEXTMATCH - find next matching item (or start from top) // 3. PREVMATCH - find previous matching item (or start from bottom) else if ( type == NextMatch || type == PreviousMatch ) { // find out from where to start/resume search from const bool forward = type == NextMatch; const int viewportPage = (*d->m_viewportIterator).pageNumber; const int fromStartSearchPage = forward ? 0 : d->m_pagesVector.count() - 1; int currentPage = fromStart ? fromStartSearchPage : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage); Page * lastPage = fromStart ? 0 : d->m_pagesVector[ currentPage ]; int pagesDone = 0; // continue checking last TextPage first (if it is the current page) RegularAreaRect * match = nullptr; if ( lastPage && lastPage->number() == s->continueOnPage ) { if ( newText ) match = lastPage->findText( searchID, text, forward ? FromTop : FromBottom, caseSensitivity ); else match = lastPage->findText( searchID, text, forward ? NextResult : PreviousResult, caseSensitivity, &s->continueOnMatch ); if ( !match ) { if (forward) currentPage++; else currentPage--; pagesDone++; } } s->pagesDone = pagesDone; DoContinueDirectionMatchSearchStruct *searchStruct = new DoContinueDirectionMatchSearchStruct(); searchStruct->pagesToNotify = pagesToNotify; searchStruct->match = match; searchStruct->currentPage = currentPage; searchStruct->searchID = searchID; QMetaObject::invokeMethod(this, "doContinueDirectionMatchSearch", Qt::QueuedConnection, Q_ARG(void *, searchStruct)); } // 4. GOOGLE* - process all document marking pages else if ( type == GoogleAll || type == GoogleAny ) { QMap< Page *, QVector< QPair > > *pageMatches = new QMap< Page *, QVector > >; const QStringList words = text.split( QLatin1Char ( ' ' ), QString::SkipEmptyParts ); // search and highlight every word in 'text' on all pages QMetaObject::invokeMethod(this, "doContinueGooglesDocumentSearch", Qt::QueuedConnection, Q_ARG(void *, pagesToNotify), Q_ARG(void *, pageMatches), Q_ARG(int, 0), Q_ARG(int, searchID), Q_ARG(QStringList, words)); } } void Document::continueSearch( int searchID ) { // check if searchID is present in runningSearches QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); if ( it == d->m_searches.constEnd() ) { emit searchFinished( searchID, NoMatchFound ); return; } // start search with cached parameters from last search by searchID RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, p->cachedType, p->cachedViewportMove, p->cachedColor ); } void Document::continueSearch( int searchID, SearchType type ) { // check if searchID is present in runningSearches QMap< int, RunningSearch * >::const_iterator it = d->m_searches.constFind( searchID ); if ( it == d->m_searches.constEnd() ) { emit searchFinished( searchID, NoMatchFound ); return; } // start search with cached parameters from last search by searchID RunningSearch * p = *it; if ( !p->isCurrentlySearching ) searchText( searchID, p->cachedString, false, p->cachedCaseSensitivity, type, p->cachedViewportMove, p->cachedColor ); } void Document::resetSearch( int searchID ) { // if we are closing down, don't bother doing anything if ( !d->m_generator ) return; // check if searchID is present in runningSearches QMap< int, RunningSearch * >::iterator searchIt = d->m_searches.find( searchID ); if ( searchIt == d->m_searches.end() ) return; // get previous parameters for search RunningSearch * s = *searchIt; // unhighlight pages and inform observers about that foreach(int pageNumber, s->highlightedPages) { d->m_pagesVector.at(pageNumber)->d->deleteHighlights( searchID ); foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) ); } // send the setup signal too (to update views that filter on matches) foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); // remove serch from the runningSearches list and delete it d->m_searches.erase( searchIt ); delete s; } void Document::cancelSearch() { d->m_searchCancelled = true; } void Document::undo() { d->m_undoStack->undo(); } void Document::redo() { d->m_undoStack->redo(); } void Document::editFormText( int pageNumber, Okular::FormFieldText* form, const QString & newContents, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QUndoCommand *uc = new EditFormTextCommand( this->d, form, pageNumber, newContents, newCursorPos, form->text(), prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); d->recalculateForms(); } void Document::editFormList( int pageNumber, FormFieldChoice* form, const QList< int > & newChoices ) { const QList< int > prevChoices = form->currentChoices(); QUndoCommand *uc = new EditFormListCommand( this->d, form, pageNumber, newChoices, prevChoices ); d->m_undoStack->push( uc ); d->recalculateForms(); } void Document::editFormCombo( int pageNumber, FormFieldChoice* form, const QString & newText, int newCursorPos, int prevCursorPos, int prevAnchorPos ) { QString prevText; if ( form->currentChoices().isEmpty() ) { prevText = form->editChoice(); } else { prevText = form->choices()[form->currentChoices().constFirst()]; } QUndoCommand *uc = new EditFormComboCommand( this->d, form, pageNumber, newText, newCursorPos, prevText, prevCursorPos, prevAnchorPos ); d->m_undoStack->push( uc ); d->recalculateForms(); } void Document::editFormButtons( int pageNumber, const QList< FormFieldButton* >& formButtons, const QList< bool >& newButtonStates ) { QUndoCommand *uc = new EditFormButtonsCommand( this->d, pageNumber, formButtons, newButtonStates ); d->m_undoStack->push( uc ); } void Document::reloadDocument() const { const int numOfPages = pages(); for( int i = currentPage(); i >= 0; i -- ) d->refreshPixmaps( i ); for( int i = currentPage() + 1; i < numOfPages; i ++ ) d->refreshPixmaps( i ); } BookmarkManager * Document::bookmarkManager() const { return d->m_bookmarkManager; } QList Document::bookmarkedPageList() const { QList list; uint docPages = pages(); //pages are 0-indexed internally, but 1-indexed externally for ( uint i = 0; i < docPages; i++ ) { if ( bookmarkManager()->isBookmarked( i ) ) { list << i + 1; } } return list; } QString Document::bookmarkedPageRange() const { // Code formerly in Part::slotPrint() // range detecting QString range; uint docPages = pages(); int startId = -1; int endId = -1; for ( uint i = 0; i < docPages; ++i ) { if ( bookmarkManager()->isBookmarked( i ) ) { if ( startId < 0 ) startId = i; if ( endId < 0 ) endId = startId; else ++endId; } else if ( startId >= 0 && endId >= 0 ) { if ( !range.isEmpty() ) range += QLatin1Char ( ',' ); if ( endId - startId > 0 ) range += QStringLiteral( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); else range += QString::number( startId + 1 ); startId = -1; endId = -1; } } if ( startId >= 0 && endId >= 0 ) { if ( !range.isEmpty() ) range += QLatin1Char ( ',' ); if ( endId - startId > 0 ) range += QStringLiteral( "%1-%2" ).arg( startId + 1 ).arg( endId + 1 ); else range += QString::number( startId + 1 ); } return range; } void Document::processAction( const Action * action ) { if ( !action ) return; switch( action->actionType() ) { case Action::Goto: { const GotoAction * go = static_cast< const GotoAction * >( action ); d->m_nextDocumentViewport = go->destViewport(); d->m_nextDocumentDestination = go->destinationName(); // Explanation of why d->m_nextDocumentViewport is needed: // all openRelativeFile does is launch a signal telling we // want to open another URL, the problem is that when the file is // non local, the loading is done assynchronously so you can't // do a setViewport after the if as it was because you are doing the setViewport // on the old file and when the new arrives there is no setViewport for it and // it does not show anything // first open filename if link is pointing outside this document if ( go->isExternal() && !d->openRelativeFile( go->fileName() ) ) { qCWarning(OkularCoreDebug).nospace() << "Action: Error opening '" << go->fileName() << "'."; return; } else { const DocumentViewport nextViewport = d->nextDocumentViewport(); // skip local links that point to nowhere (broken ones) if ( !nextViewport.isValid() ) return; setViewport( nextViewport, nullptr, true ); d->m_nextDocumentViewport = DocumentViewport(); d->m_nextDocumentDestination = QString(); } } break; case Action::Execute: { const ExecuteAction * exe = static_cast< const ExecuteAction * >( action ); const QString fileName = exe->fileName(); if ( fileName.endsWith( QLatin1String(".pdf"), Qt::CaseInsensitive ) ) { d->openRelativeFile( fileName ); return; } // Albert: the only pdf i have that has that kind of link don't define // an application and use the fileName as the file to open QUrl url = d->giveAbsoluteUrl( fileName ); QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl( url ); // Check executables if ( KRun::isExecutableFile( url, mime.name() ) ) { // Don't have any pdf that uses this code path, just a guess on how it should work if ( !exe->parameters().isEmpty() ) { url = d->giveAbsoluteUrl( exe->parameters() ); mime = db.mimeTypeForUrl( url ); if ( KRun::isExecutableFile( url, mime.name() ) ) { // this case is a link pointing to an executable with a parameter // that also is an executable, possibly a hand-crafted pdf KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); return; } } else { // this case is a link pointing to an executable with no parameters // core developers find unacceptable executing it even after asking the user KMessageBox::information( d->m_widget, i18n("The document is trying to execute an external application and, for your safety, Okular does not allow that.") ); return; } } KService::Ptr ptr = KMimeTypeTrader::self()->preferredService( mime.name(), QStringLiteral("Application") ); if ( ptr ) { QList lst; lst.append( url ); KRun::runService( *ptr, lst, nullptr ); } else KMessageBox::information( d->m_widget, i18n( "No application found for opening file of mimetype %1.", mime.name() ) ); } break; case Action::DocAction: { const DocumentAction * docaction = static_cast< const DocumentAction * >( action ); switch( docaction->documentActionType() ) { case DocumentAction::PageFirst: setViewportPage( 0 ); break; case DocumentAction::PagePrev: if ( (*d->m_viewportIterator).pageNumber > 0 ) setViewportPage( (*d->m_viewportIterator).pageNumber - 1 ); break; case DocumentAction::PageNext: if ( (*d->m_viewportIterator).pageNumber < (int)d->m_pagesVector.count() - 1 ) setViewportPage( (*d->m_viewportIterator).pageNumber + 1 ); break; case DocumentAction::PageLast: setViewportPage( d->m_pagesVector.count() - 1 ); break; case DocumentAction::HistoryBack: setPrevViewport(); break; case DocumentAction::HistoryForward: setNextViewport(); break; case DocumentAction::Quit: emit quit(); break; case DocumentAction::Presentation: emit linkPresentation(); break; case DocumentAction::EndPresentation: emit linkEndPresentation(); break; case DocumentAction::Find: emit linkFind(); break; case DocumentAction::GoToPage: emit linkGoToPage(); break; case DocumentAction::Close: emit close(); break; } } break; case Action::Browse: { const BrowseAction * browse = static_cast< const BrowseAction * >( action ); QString lilySource; int lilyRow = 0, lilyCol = 0; // if the url is a mailto one, invoke mailer if ( browse->url().scheme() == QLatin1String("mailto") ) { QDesktopServices::openUrl( browse->url() ); } else if ( extractLilyPondSourceReference( browse->url(), &lilySource, &lilyRow, &lilyCol ) ) { const SourceReference ref( lilySource, lilyRow, lilyCol ); processSourceReference( &ref ); } else { const QUrl url = browse->url(); // fix for #100366, documents with relative links that are the form of http:foo.pdf if ((url.scheme() == "http") && url.host().isEmpty() && url.fileName().endsWith("pdf")) { d->openRelativeFile(url.fileName()); return; } // handle documents with relative path if ( d->m_url.isValid() ) { const QUrl realUrl = KIO::upUrl(d->m_url).resolved(url); // KRun autodeletes new KRun( realUrl, d->m_widget ); } } } break; case Action::Sound: { const SoundAction * linksound = static_cast< const SoundAction * >( action ); AudioPlayer::instance()->playSound( linksound->sound(), linksound ); } break; case Action::Script: { const ScriptAction * linkscript = static_cast< const ScriptAction * >( action ); if ( !d->m_scripter ) d->m_scripter = new Scripter( d ); d->m_scripter->execute( linkscript->scriptType(), linkscript->script() ); } break; case Action::Movie: emit processMovieAction( static_cast< const MovieAction * >( action ) ); break; case Action::Rendition: { const RenditionAction * linkrendition = static_cast< const RenditionAction * >( action ); if ( !linkrendition->script().isEmpty() ) { if ( !d->m_scripter ) d->m_scripter = new Scripter( d ); d->m_scripter->execute( linkrendition->scriptType(), linkrendition->script() ); } emit processRenditionAction( static_cast< const RenditionAction * >( action ) ); } break; case Action::BackendOpaque: { d->m_generator->opaqueAction( static_cast< const BackendOpaqueAction * >( action ) ); } break; } } void Document::processSourceReference( const SourceReference * ref ) { if ( !ref ) return; const QUrl url = d->giveAbsoluteUrl( ref->fileName() ); if ( !url.isLocalFile() ) { qCDebug(OkularCoreDebug) << url.url() << "is not a local file."; return; } const QString absFileName = url.toLocalFile(); if ( !QFile::exists( absFileName ) ) { qCDebug(OkularCoreDebug) << "No such file:" << absFileName; return; } bool handled = false; emit sourceReferenceActivated(absFileName, ref->row(), ref->column(), &handled); if(handled) { return; } static QHash< int, QString > editors; // init the editors table if empty (on first run, usually) if ( editors.isEmpty() ) { editors = buildEditorsMap(); } QHash< int, QString >::const_iterator it = editors.constFind( SettingsCore::externalEditor() ); QString p; if ( it != editors.constEnd() ) p = *it; else p = SettingsCore::externalEditorCommand(); // custom editor not yet configured if ( p.isEmpty() ) return; // manually append the %f placeholder if not specified if ( p.indexOf( QLatin1String( "%f" ) ) == -1 ) p.append( QLatin1String( " %f" ) ); // replacing the placeholders QHash< QChar, QString > map; map.insert( QLatin1Char ( 'f' ), absFileName ); map.insert( QLatin1Char ( 'c' ), QString::number( ref->column() ) ); map.insert( QLatin1Char ( 'l' ), QString::number( ref->row() ) ); const QString cmd = KMacroExpander::expandMacrosShellQuote( p, map ); if ( cmd.isEmpty() ) return; const QStringList args = KShell::splitArgs( cmd ); if ( args.isEmpty() ) return; KProcess::startDetached( args ); } const SourceReference * Document::dynamicSourceReference( int pageNr, double absX, double absY ) { if ( !d->m_synctex_scanner ) return nullptr; const QSizeF dpi = d->m_generator->dpi(); if (synctex_edit_query(d->m_synctex_scanner, pageNr + 1, absX * 72. / dpi.width(), absY * 72. / dpi.height()) > 0) { - synctex_node_t node; + synctex_node_p node; // TODO what should we do if there is really more than one node? - while (( node = synctex_next_result( d->m_synctex_scanner ) )) + while (( node = synctex_scanner_next_result( d->m_synctex_scanner ) )) { int line = synctex_node_line(node); int col = synctex_node_column(node); // column extraction does not seem to be implemented in synctex so far. set the SourceReference default value. if ( col == -1 ) { col = 0; } const char *name = synctex_scanner_get_name( d->m_synctex_scanner, synctex_node_tag( node ) ); return new Okular::SourceReference( QFile::decodeName( name ), line, col ); } } return nullptr; } Document::PrintingType Document::printingSupport() const { if ( d->m_generator ) { if ( d->m_generator->hasFeature( Generator::PrintNative ) ) { return NativePrinting; } #ifndef Q_OS_WIN if ( d->m_generator->hasFeature( Generator::PrintPostscript ) ) { return PostscriptPrinting; } #endif } return NoPrinting; } bool Document::supportsPrintToFile() const { return d->m_generator ? d->m_generator->hasFeature( Generator::PrintToFile ) : false; } bool Document::print( QPrinter &printer ) { return d->m_generator ? d->m_generator->print( printer ) : false; } QString Document::printError() const { Okular::Generator::PrintError err = Generator::UnknownPrintError; if ( d->m_generator ) { QMetaObject::invokeMethod( d->m_generator, "printError", Qt::DirectConnection, Q_RETURN_ARG(Okular::Generator::PrintError, err) ); } Q_ASSERT( err != Generator::NoPrintError ); switch ( err ) { case Generator::TemporaryFileOpenPrintError: return i18n( "Could not open a temporary file" ); case Generator::FileConversionPrintError: return i18n( "Print conversion failed" ); case Generator::PrintingProcessCrashPrintError: return i18n( "Printing process crashed" ); case Generator::PrintingProcessStartPrintError: return i18n( "Printing process could not start" ); case Generator::PrintToFilePrintError: return i18n( "Printing to file failed" ); case Generator::InvalidPrinterStatePrintError: return i18n( "Printer was in invalid state" ); case Generator::UnableToFindFilePrintError: return i18n( "Unable to find file to print" ); case Generator::NoFileToPrintError: return i18n( "There was no file to print" ); case Generator::NoBinaryToPrintError: return i18n( "Could not find a suitable binary for printing. Make sure CUPS lpr binary is available" ); case Generator::InvalidPageSizePrintError: return i18n( "The page print size is invalid" ); case Generator::NoPrintError: return QString(); case Generator::UnknownPrintError: return QString(); } return QString(); } QWidget* Document::printConfigurationWidget() const { if ( d->m_generator ) { PrintInterface * iface = qobject_cast< Okular::PrintInterface * >( d->m_generator ); return iface ? iface->printConfigurationWidget() : nullptr; } else return nullptr; } void Document::fillConfigDialog( KConfigDialog * dialog ) { if ( !dialog ) return; // ensure that we have all the generators with settings loaded QVector offers = DocumentPrivate::configurableGenerators(); d->loadServiceList( offers ); bool pagesAdded = false; QHash< QString, GeneratorInfo >::iterator it = d->m_loadedGenerators.begin(); QHash< QString, GeneratorInfo >::iterator itEnd = d->m_loadedGenerators.end(); for ( ; it != itEnd; ++it ) { Okular::ConfigInterface * iface = d->generatorConfig( it.value() ); if ( iface ) { iface->addPages( dialog ); pagesAdded = true; } } if ( pagesAdded ) { connect( dialog, SIGNAL(settingsChanged(QString)), this, SLOT(slotGeneratorConfigChanged(QString)) ); } } QVector DocumentPrivate::configurableGenerators() { const QVector available = availableGenerators(); QVector result; for (const KPluginMetaData& md : available) { if (md.rawData()[QStringLiteral("X-KDE-okularHasInternalSettings")].toBool()) { result << md; } } return result; } KPluginMetaData Document::generatorInfo() const { if (!d->m_generator) return KPluginMetaData(); auto genIt = d->m_loadedGenerators.constFind(d->m_generatorName); Q_ASSERT(genIt != d->m_loadedGenerators.constEnd()); return genIt.value().metadata; } int Document::configurableGenerators() const { return DocumentPrivate::configurableGenerators().size(); } QStringList Document::supportedMimeTypes() const { // TODO: make it a static member of DocumentPrivate? QStringList result = d->m_supportedMimeTypes; if (result.isEmpty()) { const QVector available = DocumentPrivate::availableGenerators(); for (const KPluginMetaData& md : available) { result << md.mimeTypes(); } // Remove duplicate mimetypes represented by different names QMimeDatabase mimeDatabase; QSet uniqueMimetypes; for (const QString &mimeName : result) { uniqueMimetypes.insert(mimeDatabase.mimeTypeForName(mimeName)); } result.clear(); for (const QMimeType &mimeType : uniqueMimetypes) { result.append(mimeType.name()); } // Sorting by mimetype name doesn't make a ton of sense, // but ensures that the list is ordered the same way every time qSort(result); d->m_supportedMimeTypes = result; } return result; } bool Document::canSaveChanges() const { if ( !d->m_generator ) return false; Q_ASSERT( !d->m_generatorName.isEmpty() ); QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.end() ); SaveInterface* saveIface = d->generatorSave( genIt.value() ); if ( !saveIface ) return false; return saveIface->supportsOption( SaveInterface::SaveChanges ); } bool Document::canSaveChanges( SaveCapability cap ) const { switch ( cap ) { case SaveFormsCapability: /* Assume that if the generator supports saving, forms can be saved. * We have no means to actually query the generator at the moment * TODO: Add some method to query the generator in SaveInterface */ return canSaveChanges(); case SaveAnnotationsCapability: return d->canAddAnnotationsNatively(); } return false; } bool Document::saveChanges( const QString &fileName ) { QString errorText; return saveChanges( fileName, &errorText ); } bool Document::saveChanges( const QString &fileName, QString *errorText ) { if ( !d->m_generator || fileName.isEmpty() ) return false; Q_ASSERT( !d->m_generatorName.isEmpty() ); QHash< QString, GeneratorInfo >::iterator genIt = d->m_loadedGenerators.find( d->m_generatorName ); Q_ASSERT( genIt != d->m_loadedGenerators.end() ); SaveInterface* saveIface = d->generatorSave( genIt.value() ); if ( !saveIface || !saveIface->supportsOption( SaveInterface::SaveChanges ) ) return false; return saveIface->save( fileName, SaveInterface::SaveChanges, errorText ); } void Document::registerView( View *view ) { if ( !view ) return; Document *viewDoc = view->viewDocument(); if ( viewDoc ) { // check if already registered for this document if ( viewDoc == this ) return; viewDoc->unregisterView( view ); } d->m_views.insert( view ); view->d_func()->document = d; } void Document::unregisterView( View *view ) { if ( !view ) return; Document *viewDoc = view->viewDocument(); if ( !viewDoc || viewDoc != this ) return; view->d_func()->document = nullptr; d->m_views.remove( view ); } QByteArray Document::fontData(const FontInfo &font) const { QByteArray result; if (d->m_generator) { QMetaObject::invokeMethod(d->m_generator, "requestFontData", Qt::DirectConnection, Q_ARG(Okular::FontInfo, font), Q_ARG(QByteArray *, &result)); } return result; } Document::OpenResult Document::openDocumentArchive( const QString & docFile, const QUrl & url, const QString & password ) { QMimeDatabase db; const QMimeType mime = db.mimeTypeForFile( docFile, QMimeDatabase::MatchExtension ); if ( !mime.inherits( QStringLiteral("application/vnd.kde.okular-archive") ) ) return OpenError; KZip okularArchive( docFile ); if ( !okularArchive.open( QIODevice::ReadOnly ) ) return OpenError; const KArchiveDirectory * mainDir = okularArchive.directory(); const KArchiveEntry * mainEntry = mainDir->entry( QStringLiteral("content.xml") ); if ( !mainEntry || !mainEntry->isFile() ) return OpenError; std::unique_ptr< QIODevice > mainEntryDevice( static_cast< const KZipFileEntry * >( mainEntry )->createDevice() ); QDomDocument doc; if ( !doc.setContent( mainEntryDevice.get() ) ) return OpenError; mainEntryDevice.reset(); QDomElement root = doc.documentElement(); if ( root.tagName() != QLatin1String("OkularArchive") ) return OpenError; QString documentFileName; QString metadataFileName; QDomElement el = root.firstChild().toElement(); for ( ; !el.isNull(); el = el.nextSibling().toElement() ) { if ( el.tagName() == QLatin1String("Files") ) { QDomElement fileEl = el.firstChild().toElement(); for ( ; !fileEl.isNull(); fileEl = fileEl.nextSibling().toElement() ) { if ( fileEl.tagName() == QLatin1String("DocumentFileName") ) documentFileName = fileEl.text(); else if ( fileEl.tagName() == QLatin1String("MetadataFileName") ) metadataFileName = fileEl.text(); } } } if ( documentFileName.isEmpty() ) return OpenError; const KArchiveEntry * docEntry = mainDir->entry( documentFileName ); if ( !docEntry || !docEntry->isFile() ) return OpenError; std::unique_ptr< ArchiveData > archiveData( new ArchiveData() ); const int dotPos = documentFileName.indexOf( QLatin1Char('.') ); if ( dotPos != -1 ) archiveData->document.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX") + documentFileName.mid(dotPos)); if ( !archiveData->document.open() ) return OpenError; QString tempFileName = archiveData->document.fileName(); { std::unique_ptr< QIODevice > docEntryDevice( static_cast< const KZipFileEntry * >( docEntry )->createDevice() ); copyQIODevice( docEntryDevice.get(), &archiveData->document ); archiveData->document.close(); } const KArchiveEntry * metadataEntry = mainDir->entry( metadataFileName ); if ( metadataEntry && metadataEntry->isFile() ) { std::unique_ptr< QIODevice > metadataEntryDevice( static_cast< const KZipFileEntry * >( metadataEntry )->createDevice() ); archiveData->metadataFile.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.xml")); if ( archiveData->metadataFile.open() ) { copyQIODevice( metadataEntryDevice.get(), &archiveData->metadataFile ); archiveData->metadataFile.close(); } } const QMimeType docMime = db.mimeTypeForFile( tempFileName, QMimeDatabase::MatchContent ); d->m_archiveData = archiveData.get(); d->m_archivedFileName = documentFileName; const OpenResult ret = openDocument( tempFileName, url, docMime, password ); if ( ret == OpenSuccess ) { archiveData.release(); } else { d->m_archiveData = nullptr; } return ret; } bool Document::saveDocumentArchive( const QString &fileName ) { if ( !d->m_generator ) return false; /* If we opened an archive, use the name of original file (eg foo.pdf) * instead of the archive's one (eg foo.okular) */ QString docFileName = d->m_archiveData ? d->m_archivedFileName : d->m_url.fileName(); if ( docFileName == QLatin1String( "-" ) ) return false; QString docPath = d->m_docFileName; const QFileInfo fi( docPath ); if ( fi.isSymLink() ) docPath = fi.symLinkTarget(); KZip okularArchive( fileName ); if ( !okularArchive.open( QIODevice::WriteOnly ) ) return false; const KUser user; #ifndef Q_OS_WIN const KUserGroup userGroup( user.groupId() ); #else const KUserGroup userGroup( QString( "" ) ); #endif QDomDocument contentDoc( QStringLiteral("OkularArchive") ); QDomProcessingInstruction xmlPi = contentDoc.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"utf-8\"" ) ); contentDoc.appendChild( xmlPi ); QDomElement root = contentDoc.createElement( QStringLiteral("OkularArchive") ); contentDoc.appendChild( root ); QDomElement filesNode = contentDoc.createElement( QStringLiteral("Files") ); root.appendChild( filesNode ); QDomElement fileNameNode = contentDoc.createElement( QStringLiteral("DocumentFileName") ); filesNode.appendChild( fileNameNode ); fileNameNode.appendChild( contentDoc.createTextNode( docFileName ) ); QDomElement metadataFileNameNode = contentDoc.createElement( QStringLiteral("MetadataFileName") ); filesNode.appendChild( metadataFileNameNode ); metadataFileNameNode.appendChild( contentDoc.createTextNode( QStringLiteral("metadata.xml") ) ); // If the generator can save annotations natively, do it QTemporaryFile modifiedFile; bool annotationsSavedNatively = false; if ( d->canAddAnnotationsNatively() ) { if ( !modifiedFile.open() ) return false; modifiedFile.close(); // We're only interested in the file name QString errorText; if ( saveChanges( modifiedFile.fileName(), &errorText ) ) { docPath = modifiedFile.fileName(); // Save this instead of the original file annotationsSavedNatively = true; } else { qCWarning(OkularCoreDebug) << "saveChanges failed: " << errorText; qCDebug(OkularCoreDebug) << "Falling back to saving a copy of the original file"; } } QTemporaryFile metadataFile; PageItems saveWhat = annotationsSavedNatively ? None : AnnotationPageItems; if ( !d->savePageDocumentInfo( &metadataFile, saveWhat ) ) return false; const QByteArray contentDocXml = contentDoc.toByteArray(); const mode_t perm = 0100644; okularArchive.writeFile( QStringLiteral("content.xml"), contentDocXml, perm, user.loginName(), userGroup.name() ); okularArchive.addLocalFile( docPath, docFileName ); okularArchive.addLocalFile( metadataFile.fileName(), QStringLiteral("metadata.xml") ); if ( !okularArchive.close() ) return false; return true; } QPrinter::Orientation Document::orientation() const { double width, height; int landscape, portrait; const Okular::Page *currentPage; // if some pages are landscape and others are not, the most common wins, as // QPrinter does not accept a per-page setting landscape = 0; portrait = 0; for (uint i = 0; i < pages(); i++) { currentPage = page(i); width = currentPage->width(); height = currentPage->height(); if (currentPage->orientation() == Okular::Rotation90 || currentPage->orientation() == Okular::Rotation270) qSwap(width, height); if (width > height) landscape++; else portrait++; } return (landscape > portrait) ? QPrinter::Landscape : QPrinter::Portrait; } void Document::setAnnotationEditingEnabled( bool enable ) { d->m_annotationEditingEnabled = enable; foreachObserver( notifySetup( d->m_pagesVector, 0 ) ); } void Document::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const { if (d->m_generator) { d->m_generator->walletDataForFile( fileName, walletName, walletFolder, walletKey ); } else if (d->m_walletGenerator) { d->m_walletGenerator->walletDataForFile( fileName, walletName, walletFolder, walletKey ); } } QAbstractItemModel * Document::layersModel() const { return d->m_generator ? d->m_generator->layersModel() : nullptr; } void DocumentPrivate::requestDone( PixmapRequest * req ) { if ( !req ) return; if ( !m_generator || m_closingLoop ) { m_pixmapRequestsMutex.lock(); m_executingPixmapRequests.removeAll( req ); m_pixmapRequestsMutex.unlock(); delete req; if ( m_closingLoop ) m_closingLoop->exit(); return; } #ifndef NDEBUG if ( !m_generator->canGeneratePixmap() ) qCDebug(OkularCoreDebug) << "requestDone with generator not in READY state."; #endif // [MEM] 1.1 find and remove a previous entry for the same page and id QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmaps.begin(); QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmaps.end(); for ( ; aIt != aEnd; ++aIt ) if ( (*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer() ) { AllocatedPixmap * p = *aIt; m_allocatedPixmaps.erase( aIt ); m_allocatedPixmapsTotalMemory -= p->memory; delete p; break; } DocumentObserver *observer = req->observer(); if ( m_observers.contains(observer) ) { // [MEM] 1.2 append memory allocation descriptor to the FIFO qulonglong memoryBytes = 0; const TilesManager *tm = req->d->tilesManager(); if ( tm ) memoryBytes = tm->totalMemory(); else memoryBytes = 4 * req->width() * req->height(); AllocatedPixmap * memoryPage = new AllocatedPixmap( req->observer(), req->pageNumber(), memoryBytes ); m_allocatedPixmaps.append( memoryPage ); m_allocatedPixmapsTotalMemory += memoryBytes; // 2. notify an observer that its pixmap changed observer->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap ); } #ifndef NDEBUG else qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer; #endif // 3. delete request m_pixmapRequestsMutex.lock(); m_executingPixmapRequests.removeAll( req ); m_pixmapRequestsMutex.unlock(); delete req; // 4. start a new generation if some is pending m_pixmapRequestsMutex.lock(); bool hasPixmaps = !m_pixmapRequestsStack.isEmpty(); m_pixmapRequestsMutex.unlock(); if ( hasPixmaps ) sendGeneratorPixmapRequest(); } void DocumentPrivate::setPageBoundingBox( int page, const NormalizedRect& boundingBox ) { Page * kp = m_pagesVector[ page ]; if ( !m_generator || !kp ) return; if ( kp->boundingBox() == boundingBox ) return; kp->setBoundingBox( boundingBox ); // notify observers about the change foreachObserverD( notifyPageChanged( page, DocumentObserver::BoundingBox ) ); // TODO: For generators that generate the bbox by pixmap scanning, if the first generated pixmap is very small, the bounding box will forever be inaccurate. // TODO: Crop computation should also consider annotations, actions, etc. to make sure they're not cropped away. // TODO: Help compute bounding box for generators that create a QPixmap without a QImage, like text and plucker. // TODO: Don't compute the bounding box if no one needs it (e.g., Trim Borders is off). } void DocumentPrivate::calculateMaxTextPages() { int multipliers = qMax(1, qRound(getTotalMemory() / 536870912.0)); // 512 MB switch (SettingsCore::memoryLevel()) { case SettingsCore::EnumMemoryLevel::Low: m_maxAllocatedTextPages = multipliers * 2; break; case SettingsCore::EnumMemoryLevel::Normal: m_maxAllocatedTextPages = multipliers * 50; break; case SettingsCore::EnumMemoryLevel::Aggressive: m_maxAllocatedTextPages = multipliers * 250; break; case SettingsCore::EnumMemoryLevel::Greedy: m_maxAllocatedTextPages = multipliers * 1250; break; } } void DocumentPrivate::textGenerationDone( Page *page ) { if ( !m_pageController ) return; // 1. If we reached the cache limit, delete the first text page from the fifo if (m_allocatedTextPagesFifo.size() == m_maxAllocatedTextPages) { int pageToKick = m_allocatedTextPagesFifo.takeFirst(); if (pageToKick != page->number()) // this should never happen but better be safe than sorry { m_pagesVector.at(pageToKick)->setTextPage( nullptr ); // deletes the textpage } } // 2. Add the page to the fifo of generated text pages m_allocatedTextPagesFifo.append( page->number() ); } void Document::setRotation( int r ) { d->setRotationInternal( r, true ); } void DocumentPrivate::setRotationInternal( int r, bool notify ) { Rotation rotation = (Rotation)r; if ( !m_generator || ( m_rotation == rotation ) ) return; // tell the pages to rotate QVector< Okular::Page * >::const_iterator pIt = m_pagesVector.constBegin(); QVector< Okular::Page * >::const_iterator pEnd = m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->rotateAt( rotation ); if ( notify ) { // notify the generator that the current rotation has changed m_generator->rotationChanged( rotation, m_rotation ); } // set the new rotation m_rotation = rotation; if ( notify ) { foreachObserverD( notifySetup( m_pagesVector, DocumentObserver::NewLayoutForPages ) ); foreachObserverD( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights | DocumentObserver::Annotations ) ); } qCDebug(OkularCoreDebug) << "Rotated:" << r; } void Document::setPageSize( const PageSize &size ) { if ( !d->m_generator || !d->m_generator->hasFeature( Generator::PageSizes ) ) return; if ( d->m_pageSizes.isEmpty() ) d->m_pageSizes = d->m_generator->pageSizes(); int sizeid = d->m_pageSizes.indexOf( size ); if ( sizeid == -1 ) return; // tell the pages to change size QVector< Okular::Page * >::const_iterator pIt = d->m_pagesVector.constBegin(); QVector< Okular::Page * >::const_iterator pEnd = d->m_pagesVector.constEnd(); for ( ; pIt != pEnd; ++pIt ) (*pIt)->d->changeSize( size ); // clear 'memory allocation' descriptors qDeleteAll( d->m_allocatedPixmaps ); d->m_allocatedPixmaps.clear(); d->m_allocatedPixmapsTotalMemory = 0; // notify the generator that the current page size has changed d->m_generator->pageSizeChanged( size, d->m_pageSize ); // set the new page size d->m_pageSize = size; foreachObserver( notifySetup( d->m_pagesVector, DocumentObserver::NewLayoutForPages ) ); foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap | DocumentObserver::Highlights ) ); qCDebug(OkularCoreDebug) << "New PageSize id:" << sizeid; } /** DocumentViewport **/ DocumentViewport::DocumentViewport( int n ) : pageNumber( n ) { // default settings rePos.enabled = false; rePos.normalizedX = 0.5; rePos.normalizedY = 0.0; rePos.pos = Center; autoFit.enabled = false; autoFit.width = false; autoFit.height = false; } DocumentViewport::DocumentViewport( const QString & xmlDesc ) : pageNumber( -1 ) { // default settings (maybe overridden below) rePos.enabled = false; rePos.normalizedX = 0.5; rePos.normalizedY = 0.0; rePos.pos = Center; autoFit.enabled = false; autoFit.width = false; autoFit.height = false; // check for string presence if ( xmlDesc.isEmpty() ) return; // decode the string bool ok; int field = 0; QString token = xmlDesc.section( QLatin1Char(';'), field, field ); while ( !token.isEmpty() ) { // decode the current token if ( field == 0 ) { pageNumber = token.toInt( &ok ); if ( !ok ) return; } else if ( token.startsWith( QLatin1String("C1") ) ) { rePos.enabled = true; rePos.normalizedX = token.section( QLatin1Char(':'), 1, 1 ).toDouble(); rePos.normalizedY = token.section( QLatin1Char(':'), 2, 2 ).toDouble(); rePos.pos = Center; } else if ( token.startsWith( QLatin1String("C2") ) ) { rePos.enabled = true; rePos.normalizedX = token.section( QLatin1Char(':'), 1, 1 ).toDouble(); rePos.normalizedY = token.section( QLatin1Char(':'), 2, 2 ).toDouble(); if (token.section( QLatin1Char(':'), 3, 3 ).toInt() == 1) rePos.pos = Center; else rePos.pos = TopLeft; } else if ( token.startsWith( QLatin1String("AF1") ) ) { autoFit.enabled = true; autoFit.width = token.section( QLatin1Char(':'), 1, 1 ) == QLatin1String("T"); autoFit.height = token.section( QLatin1Char(':'), 2, 2 ) == QLatin1String("T"); } // proceed tokenizing string field++; token = xmlDesc.section( QLatin1Char(';'), field, field ); } } QString DocumentViewport::toString() const { // start string with page number QString s = QString::number( pageNumber ); // if has center coordinates, save them on string if ( rePos.enabled ) s += QStringLiteral( ";C2:" ) + QString::number( rePos.normalizedX ) + QLatin1Char(':') + QString::number( rePos.normalizedY ) + QLatin1Char(':') + QString::number( rePos.pos ); // if has autofit enabled, save its state on string if ( autoFit.enabled ) s += QStringLiteral( ";AF1:" ) + (autoFit.width ? QLatin1Char('T') : QLatin1Char('F')) + QLatin1Char(':') + (autoFit.height ? QLatin1Char('T') : QLatin1Char('F')); return s; } bool DocumentViewport::isValid() const { return pageNumber >= 0; } bool DocumentViewport::operator==( const DocumentViewport & vp ) const { bool equal = ( pageNumber == vp.pageNumber ) && ( rePos.enabled == vp.rePos.enabled ) && ( autoFit.enabled == vp.autoFit.enabled ); if ( !equal ) return false; if ( rePos.enabled && (( rePos.normalizedX != vp.rePos.normalizedX) || ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) ) return false; if ( autoFit.enabled && (( autoFit.width != vp.autoFit.width ) || ( autoFit.height != vp.autoFit.height )) ) return false; return true; } bool DocumentViewport::operator<( const DocumentViewport & vp ) const { // TODO: Check autoFit and Position if ( pageNumber != vp.pageNumber ) return pageNumber < vp.pageNumber; if ( !rePos.enabled && vp.rePos.enabled ) return true; if ( !vp.rePos.enabled ) return false; if ( rePos.normalizedY != vp.rePos.normalizedY ) return rePos.normalizedY < vp.rePos.normalizedY; return rePos.normalizedX < vp.rePos.normalizedX; } /** DocumentInfo **/ DocumentInfo::DocumentInfo() : d(new DocumentInfoPrivate()) { } DocumentInfo::DocumentInfo(const DocumentInfo &info) : d(new DocumentInfoPrivate()) { *this = info; } DocumentInfo& DocumentInfo::operator=(const DocumentInfo &info) { d->values = info.d->values; d->titles = info.d->titles; return *this; } DocumentInfo::~DocumentInfo() { delete d; } void DocumentInfo::set( const QString &key, const QString &value, const QString &title ) { d->values[ key ] = value; d->titles[ key ] = title; } void DocumentInfo::set( Key key, const QString &value ) { d->values[ getKeyString( key ) ] = value; } QStringList DocumentInfo::keys() const { return d->values.keys(); } QString DocumentInfo::get( Key key ) const { return get( getKeyString( key ) ); } QString DocumentInfo::get( const QString &key ) const { return d->values[ key ]; } QString DocumentInfo::getKeyString( Key key ) //const { switch ( key ) { case Title: return QStringLiteral("title"); break; case Subject: return QStringLiteral("subject"); break; case Description: return QStringLiteral("description"); break; case Author: return QStringLiteral("author"); break; case Creator: return QStringLiteral("creator"); break; case Producer: return QStringLiteral("producer"); break; case Copyright: return QStringLiteral("copyright"); break; case Pages: return QStringLiteral("pages"); break; case CreationDate: return QStringLiteral("creationDate"); break; case ModificationDate: return QStringLiteral("modificationDate"); break; case MimeType: return QStringLiteral("mimeType"); break; case Category: return QStringLiteral("category"); break; case Keywords: return QStringLiteral("keywords"); break; case FilePath: return QStringLiteral("filePath"); break; case DocumentSize: return QStringLiteral("documentSize"); break; case PagesSize: return QStringLiteral("pageSize"); break; default: qCWarning(OkularCoreDebug) << "Unknown" << key; return QString(); break; } } DocumentInfo::Key DocumentInfo::getKeyFromString( const QString &key ) //const { if (key == QLatin1String("title")) return Title; else if (key == QLatin1String("subject")) return Subject; else if (key == QLatin1String("description")) return Description; else if (key == QLatin1String("author")) return Author; else if (key == QLatin1String("creator")) return Creator; else if (key == QLatin1String("producer")) return Producer; else if (key == QLatin1String("copyright")) return Copyright; else if (key == QLatin1String("pages")) return Pages; else if (key == QLatin1String("creationDate")) return CreationDate; else if (key == QLatin1String("modificationDate")) return ModificationDate; else if (key == QLatin1String("mimeType")) return MimeType; else if (key == QLatin1String("category")) return Category; else if (key == QLatin1String("keywords")) return Keywords; else if (key == QLatin1String("filePath")) return FilePath; else if (key == QLatin1String("documentSize")) return DocumentSize; else if (key == QLatin1String("pageSize")) return PagesSize; else return Invalid; } QString DocumentInfo::getKeyTitle( Key key ) //const { switch ( key ) { case Title: return i18n( "Title" ); break; case Subject: return i18n( "Subject" ); break; case Description: return i18n( "Description" ); break; case Author: return i18n( "Author" ); break; case Creator: return i18n( "Creator" ); break; case Producer: return i18n( "Producer" ); break; case Copyright: return i18n( "Copyright" ); break; case Pages: return i18n( "Pages" ); break; case CreationDate: return i18n( "Created" ); break; case ModificationDate: return i18n( "Modified" ); break; case MimeType: return i18n( "Mime Type" ); break; case Category: return i18n( "Category" ); break; case Keywords: return i18n( "Keywords" ); break; case FilePath: return i18n( "File Path" ); break; case DocumentSize: return i18n( "File Size" ); break; case PagesSize: return i18n("Page Size"); break; default: return QString(); break; } } QString DocumentInfo::getKeyTitle( const QString &key ) const { QString title = getKeyTitle ( getKeyFromString( key ) ); if ( title.isEmpty() ) title = d->titles[ key ]; return title; } /** DocumentSynopsis **/ DocumentSynopsis::DocumentSynopsis() : QDomDocument( QStringLiteral("DocumentSynopsis") ) { // void implementation, only subclassed for naming } DocumentSynopsis::DocumentSynopsis( const QDomDocument &document ) : QDomDocument( document ) { } /** EmbeddedFile **/ EmbeddedFile::EmbeddedFile() { } EmbeddedFile::~EmbeddedFile() { } VisiblePageRect::VisiblePageRect( int page, const NormalizedRect &rectangle ) : pageNumber( page ), rect( rectangle ) { } #undef foreachObserver #undef foreachObserverD #include "moc_document.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/document_p.h b/core/document_p.h index a7e3fc4ec..b6cf425bc 100644 --- a/core/document_p.h +++ b/core/document_p.h @@ -1,302 +1,302 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2007 by Albert Astals Cid * * * * 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. * ***************************************************************************/ #ifndef _OKULAR_DOCUMENT_P_H_ #define _OKULAR_DOCUMENT_P_H_ #include "document.h" #include "synctex/synctex_parser.h" // qt/kde/system includes #include #include #include #include #include #include #include // local includes #include "fontinfo.h" #include "generator.h" class QUndoStack; class QEventLoop; class QFile; class QTimer; class QTemporaryFile; class KPluginMetaData; struct AllocatedPixmap; struct ArchiveData; struct RunningSearch; namespace Okular { class ConfigInterface; class PageController; class SaveInterface; class Scripter; class View; } struct GeneratorInfo { explicit GeneratorInfo( Okular::Generator *g, const KPluginMetaData &data) : generator( g ), metadata( data ), config( nullptr ), save( nullptr ), configChecked( false ), saveChecked( false ) {} Okular::Generator * generator; KPluginMetaData metadata; Okular::ConfigInterface * config; Okular::SaveInterface * save; bool configChecked : 1; bool saveChecked : 1; }; namespace Okular { class FontExtractionThread; struct DoContinueDirectionMatchSearchStruct { QSet< int > *pagesToNotify; RegularAreaRect *match; int currentPage; int searchID; }; class DocumentPrivate { public: DocumentPrivate( Document *parent ) : m_parent( parent ), m_tempFile( nullptr ), m_docSize( -1 ), m_allocatedPixmapsTotalMemory( 0 ), m_maxAllocatedTextPages( 0 ), m_warnedOutOfMemory( false ), m_rotation( Rotation0 ), m_exportCached( false ), m_bookmarkManager( nullptr ), m_memCheckTimer( nullptr ), m_saveBookmarksTimer( nullptr ), m_generator( nullptr ), m_walletGenerator( nullptr ), m_generatorsLoaded( false ), m_pageController( nullptr ), m_closingLoop( nullptr ), m_scripter( nullptr ), m_archiveData( nullptr ), m_fontsCached( false ), m_annotationEditingEnabled ( true ), m_annotationBeingModified( false ), m_synctex_scanner( nullptr ) { calculateMaxTextPages(); } // private methods QString pagesSizeString() const; QString namePaperSize(double inchesWidth, double inchesHeight) const; QString localizedSize(const QSizeF &size) const; qulonglong calculateMemoryToFree(); void cleanupPixmapMemory(); void cleanupPixmapMemory( qulonglong memoryToFree ); AllocatedPixmap * searchLowestPriorityPixmap( bool unloadableOnly = false, bool thenRemoveIt = false, DocumentObserver *observer = nullptr /* any */ ); void calculateMaxTextPages(); qulonglong getTotalMemory(); qulonglong getFreeMemory( qulonglong *freeSwap = nullptr ); void loadDocumentInfo(); void loadDocumentInfo( QFile &infoFile ); void loadViewsInfo( View *view, const QDomElement &e ); void saveViewsInfo( View *view, QDomElement &e ) const; QUrl giveAbsoluteUrl( const QString & fileName ) const; bool openRelativeFile( const QString & fileName ); Generator * loadGeneratorLibrary( const KPluginMetaData& service ); void loadAllGeneratorLibraries(); void loadServiceList( const QVector& offers ); void unloadGenerator( const GeneratorInfo& info ); void cacheExportFormats(); void setRotationInternal( int r, bool notify ); ConfigInterface* generatorConfig( GeneratorInfo& info ); SaveInterface* generatorSave( GeneratorInfo& info ); Document::OpenResult openDocumentInternal( const KPluginMetaData& offer, bool isstdin, const QString& docFile, const QByteArray& filedata, const QString& password ); bool savePageDocumentInfo( QTemporaryFile *infoFile, int what ) const; DocumentViewport nextDocumentViewport() const; void notifyAnnotationChanges( int page ); bool canAddAnnotationsNatively() const; bool canModifyExternalAnnotations() const; bool canRemoveExternalAnnotations() const; void warnLimitedAnnotSupport(); OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size); // Methods that implement functionality needed by undo commands void performAddPageAnnotation( int page, Annotation *annotation ); void performRemovePageAnnotation( int page, Annotation * annotation ); void performModifyPageAnnotation( int page, Annotation * annotation, bool appearanceChanged ); void performSetAnnotationContents( const QString & newContents, Annotation *annot, int pageNumber ); void recalculateForms(); // private slots void saveDocumentInfo() const; void slotTimedMemoryCheck(); void sendGeneratorPixmapRequest(); void rotationFinished( int page, Okular::Page *okularPage ); void slotFontReadingProgress( int page ); void fontReadingGotFont( const Okular::FontInfo& font ); void slotGeneratorConfigChanged( const QString& ); void refreshPixmaps( int ); void _o_configChanged(); void doContinueDirectionMatchSearch(void *doContinueDirectionMatchSearchStruct); void doContinueAllDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID); void doContinueGooglesDocumentSearch(void *pagesToNotifySet, void *pageMatchesMap, int currentPage, int searchID, const QStringList & words); void doProcessSearchMatch( RegularAreaRect *match, RunningSearch *search, QSet< int > *pagesToNotify, int currentPage, int searchID, bool moveViewport, const QColor & color ); // generators stuff /** * This method is used by the generators to signal the finish of * the pixmap generation @p request. */ void requestDone( PixmapRequest * request ); void textGenerationDone( Page *page ); /** * Sets the bounding box of the given @p page (in terms of upright orientation, i.e., Rotation0). */ void setPageBoundingBox( int page, const NormalizedRect& boundingBox ); /** * Request a particular metadata of the Document itself (ie, not something * depending on the document type/backend). */ QVariant documentMetaData( const Generator::DocumentMetaDataKey key, const QVariant &option ) const; /** * Return whether the normalized rectangle @p rectOfInterest on page number @p rectPage * is fully visible. */ bool isNormalizedRectangleFullyVisible( const Okular::NormalizedRect & rectOfInterest, int rectPage ); // For sync files void loadSyncFile( const QString & filePath ); // member variables Document *m_parent; QPointer m_widget; // find descriptors, mapped by ID (we handle multiple searches) QMap< int, RunningSearch * > m_searches; bool m_searchCancelled; // needed because for remote documents docFileName is a local file and // we want the remote url when the document refers to relativeNames QUrl m_url; // cached stuff QString m_docFileName; QString m_xmlFileName; QTemporaryFile *m_tempFile; qint64 m_docSize; // viewport stuff QLinkedList< DocumentViewport > m_viewportHistory; QLinkedList< DocumentViewport >::iterator m_viewportIterator; DocumentViewport m_nextDocumentViewport; // see Link::Goto for an explanation QString m_nextDocumentDestination; // observers / requests / allocator stuff QSet< DocumentObserver * > m_observers; QLinkedList< PixmapRequest * > m_pixmapRequestsStack; QLinkedList< PixmapRequest * > m_executingPixmapRequests; QMutex m_pixmapRequestsMutex; QLinkedList< AllocatedPixmap * > m_allocatedPixmaps; qulonglong m_allocatedPixmapsTotalMemory; QList< int > m_allocatedTextPagesFifo; int m_maxAllocatedTextPages; bool m_warnedOutOfMemory; // the rotation applied to the document Rotation m_rotation; // the current size of the pages (if available), and the cache of the // available page sizes PageSize m_pageSize; PageSize::List m_pageSizes; // cache of the export formats bool m_exportCached; ExportFormat::List m_exportFormats; ExportFormat m_exportToText; // our bookmark manager BookmarkManager *m_bookmarkManager; // timers (memory checking / info saver) QTimer *m_memCheckTimer; QTimer *m_saveBookmarksTimer; QHash m_loadedGenerators; Generator * m_generator; QString m_generatorName; Generator * m_walletGenerator; bool m_generatorsLoaded; QVector< Page * > m_pagesVector; QVector< VisiblePageRect * > m_pageRects; // cache of the mimetype we support QStringList m_supportedMimeTypes; PageController *m_pageController; QEventLoop *m_closingLoop; Scripter *m_scripter; ArchiveData *m_archiveData; QString m_archivedFileName; QPointer< FontExtractionThread > m_fontThread; bool m_fontsCached; QSet m_documentInfoAskedKeys; DocumentInfo m_documentInfo; FontInfo::List m_fontsCache; QSet< View * > m_views; bool m_annotationEditingEnabled; bool m_annotationsNeedSaveAs; bool m_annotationBeingModified; // is an annotation currently being moved or resized? bool m_showWarningLimitedAnnotSupport; QUndoStack *m_undoStack; QDomNode m_prevPropsOfAnnotBeingModified; - synctex_scanner_t m_synctex_scanner; + synctex_scanner_p m_synctex_scanner; // generator selection static QVector availableGenerators(); static QVector configurableGenerators(); static KPluginMetaData generatorForMimeType(const QMimeType& type, QWidget* widget, const QVector &triedOffers = QVector()); }; class DocumentInfoPrivate { public: QMap values; // key -> value QMap titles; // key -> title For the custom keys }; } #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/synctex/patches/00-disable-SYNCTEX_INLINE.diff b/core/synctex/patches/00-disable-SYNCTEX_INLINE.diff index a28faf18a..d822fadc0 100644 --- a/core/synctex/patches/00-disable-SYNCTEX_INLINE.diff +++ b/core/synctex/patches/00-disable-SYNCTEX_INLINE.diff @@ -1,16 +1,17 @@ -Description: Disable SYNCTEX_INLINE on non-MSVC compilers. - It is misplaced in the code (before "static"), so it causes compiling errors. +Disable SYNCTEX_INLINE on non-MSVC compilers. It is misplaced +in the code (before "static"), so it causes compiling errors. Author: Pino Toscano -diff --git a/generators/poppler/synctex/synctex_parser.c b/generators/poppler/synctex/synctex_parser.c -index 0d3de08..53f462d 100644 ---- a/generators/poppler/synctex/synctex_parser.c -+++ b/generators/poppler/synctex/synctex_parser.c -@@ -67,7 +67,7 @@ Thu Jun 19 09:39:21 UTC 2008 - # if defined(_MSC_VER) + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -91,7 +91,7 @@ + # if defined(_MSC_VER) # define SYNCTEX_INLINE __inline # else -# define SYNCTEX_INLINE inline +# define SYNCTEX_INLINE # endif # endif diff --git a/core/synctex/patches/01-fix-win32-define.diff b/core/synctex/patches/01-fix-win32-define.diff deleted file mode 100644 index 75b3198d0..000000000 --- a/core/synctex/patches/01-fix-win32-define.diff +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/generators/poppler/synctex/synctex_parser_utils.c b/generators/poppler/synctex/synctex_parser_utils.c -index 569f7e9..e85ca73 100644 ---- a/generators/poppler/synctex/synctex_parser_utils.c -+++ b/generators/poppler/synctex/synctex_parser_utils.c -@@ -157,7 +157,7 @@ synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs - /* Remove the leading regex '(\./+)*' in both rhs and lhs */ - lhs = synctex_ignore_leading_dot_slash(lhs); - rhs = synctex_ignore_leading_dot_slash(rhs); --# if SYNCTEX_WINDOWS -+# ifdef SYNCTEX_WINDOWS - /* On Windows, filename should be compared case insensitive. - * The characters '/' and '\' are both valid path separators. - * There will be a very serious problem concerning UTF8 because -@@ -189,7 +189,7 @@ synctex_bool_t _synctex_path_is_absolute(const char * name) { - if(!strlen(name)) { - return synctex_NO; - } --# if SYNCTEX_WINDOWS -+# ifdef SYNCTEX_WINDOWS - if(strlen(name)>2) { - return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; - } -diff --git a/generators/poppler/synctex/synctex_parser_utils.h b/generators/poppler/synctex/synctex_parser_utils.h -index e67f8f5..f0709e9 100644 ---- a/generators/poppler/synctex/synctex_parser_utils.h -+++ b/generators/poppler/synctex/synctex_parser_utils.h -@@ -61,13 +61,13 @@ authorization from the copyright holder. - extern "C" { - #endif - --# if _WIN32 -+# ifdef _WIN32 - # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) - # else - # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) - # endif - --# if _WIN32 -+# ifdef _WIN32 - # define SYNCTEX_IS_DOT(c) ('.' == c) - # else - # define SYNCTEX_IS_DOT(c) ('.' == c) diff --git a/core/synctex/patches/04-gcc-specify-printf-format.diff b/core/synctex/patches/04-gcc-specify-printf-format.diff index 7b2d7960a..bdd53649c 100644 --- a/core/synctex/patches/04-gcc-specify-printf-format.diff +++ b/core/synctex/patches/04-gcc-specify-printf-format.diff @@ -1,28 +1,67 @@ -diff --git a/generators/poppler/synctex/synctex_parser_utils.h b/generators/poppler/synctex/synctex_parser_utils.h -index f0709e9..92184b8 100644 ---- a/generators/poppler/synctex/synctex_parser_utils.h -+++ b/generators/poppler/synctex/synctex_parser_utils.h -@@ -72,7 +72,13 @@ extern "C" { +_synctex_error has a printf-like format, so use the proper gcc extensions +to check its arguments (doing nothing for other compilers) +Author: Pino Toscano + +Index: synctex/synctex_parser_utils.h +=================================================================== +--- synctex.orig/synctex_parser_utils.h ++++ synctex/synctex_parser_utils.h +@@ -83,6 +83,12 @@ extern "C" { # else - # define SYNCTEX_IS_DOT(c) ('.' == c) + # define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right)) # endif -- + +# ifdef __GNUC__ +# define SYNCTEX_PRINTF_FORMAT(si, ftc) __attribute__ ((format (printf, si, ftc))) +# else +# define SYNCTEX_PRINTF_FORMAT(si, ftc) +# endif -+ + /* This custom malloc functions initializes to 0 the newly allocated memory. * There is no bzero function on windows. */ - void *_synctex_malloc(size_t size); -@@ -80,7 +86,7 @@ void *_synctex_malloc(size_t size); +@@ -95,8 +101,8 @@ void _synctex_free(void * ptr); /* This is used to log some informational message to the standard error stream. * On Windows, the stderr stream is not exposed and another method is used. * The return value is the number of characters printed. */ --int _synctex_error(const char * reason,...); -+int _synctex_error(const char * reason,...) SYNCTEX_PRINTF_FORMAT(1, 2); +- int _synctex_error(const char * reason,...); +- int _synctex_debug(const char * reason,...); ++ int _synctex_error(const char * reason,...) SYNCTEX_PRINTF_FORMAT(1, 2); ++ int _synctex_debug(const char * reason,...) SYNCTEX_PRINTF_FORMAT(1, 2); /* strip the last extension of the given string, this string is modified! * This function depends on the OS because the path separator may differ. +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -8148,6 +8148,7 @@ struct synctex_updater_t { + int length; /* the number of chars appended */ + }; + ++static int _synctex_updater_print(synctex_updater_p updater, const char * format, ...) SYNCTEX_PRINTF_FORMAT(2, 3); + static int _synctex_updater_print(synctex_updater_p updater, const char * format, ...) { + int result = 0; + if (updater) { +@@ -8184,6 +8185,7 @@ static int vasprintf(char **ret, + /** + * gzvprintf is not available until OSX 10.10 + */ ++static int _synctex_updater_print_gz(synctex_updater_p updater, const char * format, ...) SYNCTEX_PRINTF_FORMAT(2, 3); + static int _synctex_updater_print_gz(synctex_updater_p updater, const char * format, ...) { + int result = 0; + if (updater) { +Index: synctex/synctex_parser_utils.c +=================================================================== +--- synctex.orig/synctex_parser_utils.c ++++ synctex/synctex_parser_utils.c +@@ -87,7 +87,9 @@ void _synctex_free(void * ptr) { + # include + #endif + +-int _synctex_log(int level, const char * prompt, const char * reason, va_list arg;) { ++int _synctex_log(int level, const char * prompt, const char * reason, ...) SYNCTEX_PRINTF_FORMAT(3, 4); ++int _synctex_log(int level, const char * prompt, const char * reason, ...) { ++ va_list arg; + int result; + # ifdef SYNCTEX_RECENT_WINDOWS + {/* This code is contributed by William Blum. diff --git a/core/synctex/patches/05-fix-error-formats.diff b/core/synctex/patches/05-fix-error-formats.diff deleted file mode 100644 index fad5a9e40..000000000 --- a/core/synctex/patches/05-fix-error-formats.diff +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/generators/poppler/synctex/synctex_parser.c b/generators/poppler/synctex/synctex_parser.c -index 53f462d..588f350 100644 ---- a/generators/poppler/synctex/synctex_parser.c -+++ b/generators/poppler/synctex/synctex_parser.c -@@ -1440,7 +1440,7 @@ next_character: - len = SYNCTEX_END - SYNCTEX_CUR; - if (current_size>UINT_MAX-len-1) { - /* We have reached the limit. */ -- _synctex_error("limit reached (missing %i).",current_size-(UINT_MAX-len-1)); -+ _synctex_error("limit reached (missing %lu).",current_size-(UINT_MAX-len-1)); - return SYNCTEX_STATUS_ERROR; - } - new_size = current_size+len; diff --git a/core/synctex/patches/06-mingw-_synctex_error.diff b/core/synctex/patches/06-mingw-_synctex_error.diff index 5c838d775..1a5020b4a 100644 --- a/core/synctex/patches/06-mingw-_synctex_error.diff +++ b/core/synctex/patches/06-mingw-_synctex_error.diff @@ -1,35 +1,36 @@ _vscprintf() is specific for MSVC; thus in _synctex_error(), for any other compiler on Windows, use _vsnprintf() and grow the buffer until necessary. Patch provided by Patrick Spendrin . -diff --git a/generators/poppler/synctex/synctex_parser_utils.c b/generators/poppler/synctex/synctex_parser_utils.c -index e85ca73..ef1645e 100644 ---- a/generators/poppler/synctex/synctex_parser_utils.c -+++ b/generators/poppler/synctex/synctex_parser_utils.c -@@ -90,8 +90,26 @@ int _synctex_error(const char * reason,...) { + +Index: synctex/synctex_parser_utils.c +=================================================================== +--- synctex.orig/synctex_parser_utils.c ++++ synctex/synctex_parser_utils.c +@@ -101,8 +101,26 @@ int _synctex_log(int level, const char * char *buff; size_t len; - OutputDebugStringA("SyncTeX ERROR: "); + OutputDebugStringA(prompt); +# ifdef _MSC_VER len = _vscprintf(reason, arg) + 1; buff = (char*)malloc( len * sizeof(char) ); -+#else /* MinGW */ ++# else /* MinGW */ + size_t buffersize = 1024; + size_t max_buffersize = 1024 * buffersize; + int result; + buff = (char*)malloc(buffersize * sizeof(char)); + result = _vsnprintf(buff, buffersize - 1, reason, arg); + while(-1 == result && buffersize <= max_buffersize) { + buffersize = buffersize * 2; + buff = (char*)realloc(buff, buffersize * sizeof(char)); + result = _vsnprintf(buff, buffersize - 1, reason, arg); + } + if(-1 == result) { + // could not make the buffer big enough or simply could not write to it + free(buff); + return -1; + } -+#endif - result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: "); ++# endif + result = vsprintf(buff, reason, arg) +strlen(prompt); OutputDebugStringA(buff); OutputDebugStringA("\n"); diff --git a/core/synctex/patches/07-synctex_scanner_new_with_output_file-reset-mode.diff b/core/synctex/patches/07-synctex_scanner_new_with_output_file-reset-mode.diff deleted file mode 100644 index 4e096378d..000000000 --- a/core/synctex/patches/07-synctex_scanner_new_with_output_file-reset-mode.diff +++ /dev/null @@ -1,21 +0,0 @@ -Description: synctex_scanner_new_with_output_file: reset the io_mode to read before trying to read again - synctex_scanner_new_with_output_file() tries first to open the file in an - "unquoted mode"; if it fails, it tries again quoting the file name. - During the first attempt, the synctex_io_mode_t is passed as pointer and it - can be incremented, making the second attempt start from "rb" instead of "r". - The easiest solution is to reset the io_mode before doing the second open - attempt. -Author: Albert Astals Cid -diff --git a/generators/poppler/synctex/synctex_parser.c b/generators/poppler/synctex/synctex_parser.c -index 588f350..c8f61bb 100644 ---- a/generators/poppler/synctex/synctex_parser.c -+++ b/generators/poppler/synctex/synctex_parser.c -@@ -2584,6 +2584,8 @@ synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, cons - } - /* now open the synctex file */ - if (_synctex_open(output,build_directory,&synctex,&file,synctex_ADD_QUOTES,&io_mode) || !file) { -+ /* reset the io_mode to the default, ie read */ -+ io_mode = 0; - if (_synctex_open(output,build_directory,&synctex,&file,synctex_DONT_ADD_QUOTES,&io_mode) || !file) { - return NULL; - } diff --git a/core/synctex/patches/08-fix_cpp_comments.diff b/core/synctex/patches/08-fix_cpp_comments.diff index 90413b485..7cf131665 100644 --- a/core/synctex/patches/08-fix_cpp_comments.diff +++ b/core/synctex/patches/08-fix_cpp_comments.diff @@ -1,31 +1,37 @@ -diff --git a/generators/poppler/synctex/synctex_parser.c b/generators/poppler/synctex/synctex_parser.c -index c8f61bb..8244f27 100644 ---- a/generators/poppler/synctex/synctex_parser.c -+++ b/generators/poppler/synctex/synctex_parser.c -@@ -3395,11 +3395,11 @@ int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,i - } - start_ref += 1; - SYNCTEX_END = (char *)start_ref; -- SYNCTEX_CUR = NULL;// added on behalf of Jose Alliste -- return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);// added on behalf Jan Sundermeyer -+ SYNCTEX_CUR = NULL;/* added on behalf of Jose Alliste */ -+ return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);/* added on behalf Jan Sundermeyer */ +Fix cpp comments +Author: Albert Astals Cid + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -6048,7 +6048,7 @@ static int _synctex_scanner_get_tag(sync } - SYNCTEX_CUR = NULL; -- // return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); removed on behalf Jan Sundermeyer -+ /* return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); removed on behalf Jan Sundermeyer */ - } - # if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) - break; -diff --git a/generators/poppler/synctex/synctex_parser_utils.c b/generators/poppler/synctex/synctex_parser_utils.c -index ef1645e..3be45b7 100644 ---- a/generators/poppler/synctex/synctex_parser_utils.c -+++ b/generators/poppler/synctex/synctex_parser_utils.c -@@ -492,6 +492,6 @@ int _synctex_get_name(const char * output, const char * build_directory, char ** + } while((input = __synctex_tree_sibling(input))); + } +- // 2011 version ++ /* 2011 version */ + name = _synctex_base_name(name); + if ((input = scanner->input)) { + do { +@@ -6058,7 +6058,7 @@ static int _synctex_scanner_get_tag(sync + if (_synctex_is_equivalent_file_name(name,_synctex_base_name(_synctex_data_name(other_input))) + && (strlen(_synctex_data_name(input))!=strlen(_synctex_data_name(other_input)) + || strncmp(_synctex_data_name(other_input),_synctex_data_name(input),strlen(_synctex_data_name(input))))) { +- // There is a second possible candidate ++ /* There is a second possible candidate */ + return 0; + } + } +Index: synctex/synctex_parser_utils.c +=================================================================== +--- synctex.orig/synctex_parser_utils.c ++++ synctex/synctex_parser_utils.c +@@ -583,6 +583,6 @@ int _synctex_get_name(const char * outpu const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) { static const char * synctex_io_modes[4] = {"r","rb","a","ab"}; - unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste + unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);/* bug pointed out by Jose Alliste */ return synctex_io_modes[index]; } diff --git a/core/synctex/patches/09-fix_path_comparison.diff b/core/synctex/patches/09-fix_path_comparison.diff deleted file mode 100644 index 984a0a628..000000000 --- a/core/synctex/patches/09-fix_path_comparison.diff +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/generators/poppler/synctex/synctex_parser_utils.c b/generators/poppler/synctex/synctex_parser_utils.c -index 3be45b7..3dd764f 100644 ---- a/generators/poppler/synctex/synctex_parser_utils.c -+++ b/generators/poppler/synctex/synctex_parser_utils.c -@@ -199,7 +199,16 @@ next_character: - ++rhs; - goto next_character; - # else -- return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; -+ if (lhs[0] == '/' && rhs[0] == '/') { /* for absolute paths compare the real paths */ -+ char *lhsreal = realpath(lhs, 0); -+ char *rhsreal = realpath(rhs, 0); -+ synctex_bool_t result = lhsreal && rhsreal && 0 == strcmp(lhsreal,rhsreal)?synctex_YES:synctex_NO; -+ free (lhsreal); -+ free (rhsreal); -+ return result; -+ } else { -+ return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; -+ } - # endif - } - diff --git a/core/synctex/patches/10-fix-typo.diff b/core/synctex/patches/10-fix-typo.diff new file mode 100644 index 000000000..b22982be1 --- /dev/null +++ b/core/synctex/patches/10-fix-typo.diff @@ -0,0 +1,17 @@ +Fix some typos found by Krazy2 +Author: Yuri Chornoivan +Original commit: 3b73357 + +Index: synctex/synctex_parser_utils.c +=================================================================== +--- synctex.orig/synctex_parser_utils.c ++++ synctex/synctex_parser_utils.c +@@ -406,7 +406,7 @@ char * _synctex_merge_strings(const char + + /* The purpose of _synctex_get_name is to find the name of the synctex file. + * There is a list of possible filenames from which we return the most recent one and try to remove all the others. +- * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. ++ * With two runs of pdftex or xetex we are sure the synctex file is really the most appropriate. + */ + int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref) + { diff --git a/core/synctex/patches/11-fix-unused-parameters-warnings.diff b/core/synctex/patches/11-fix-unused-parameters-warnings.diff new file mode 100644 index 000000000..db6564919 --- /dev/null +++ b/core/synctex/patches/11-fix-unused-parameters-warnings.diff @@ -0,0 +1,49 @@ +Fix low hanging unused parameters warnings +Author: Martin T. H. Sandsmark +Original commit: d5bdca6 + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -198,9 +198,7 @@ typedef struct synctex_tlcpector_t { + } synctex_tlcpector_s; + typedef const synctex_tlcpector_s * synctex_tlcpector_p; + static int _synctex_int_none(synctex_node_p node) { +-# ifdef __DARWIN_UNIX03 +-# pragma unused(node) +-# endif ++ (void)node; /* unused */ + return 0; + } + static const synctex_tlcpector_s synctex_tlcpector_none = { +@@ -234,9 +232,7 @@ typedef struct synctex_vispector_t { + synctex_float_getter_f depth; + } synctex_vispector_s; + static float _synctex_float_none(synctex_node_p node) { +-# ifdef __DARWIN_UNIX03 +-# pragma unused(node) +-# endif ++ (void)node; /* unused */ + return 0; + } + static const synctex_vispector_s synctex_vispector_none = { +@@ -7206,9 +7202,7 @@ static synctex_node_p _synctex_display_q + return first_handle; + } + synctex_iterator_p synctex_iterator_new_display(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint) { +-# ifdef __DARWIN_UNIX03 +-# pragma unused(column) +-# endif ++ (void)column; /* unused */ + if (scanner) { + int tag = synctex_scanner_get_tag(scanner,name);/* parse if necessary */ + int max_line = 0; +@@ -7977,6 +7971,7 @@ SYNCTEX_INLINE static synctex_nd_lr_s __ + } + #endif + SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_vbox_v2(synctex_point_p hitP, synctex_node_p nodeP) { ++ (void)nodeP; /* unused */ + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + synctex_nd_s nd = SYNCTEX_ND_0; + if ((nd.node = synctex_node_child(nd.node))) { diff --git a/core/synctex/patches/12-omit-no-file-warning.diff b/core/synctex/patches/12-omit-no-file-warning.diff new file mode 100644 index 000000000..c4cfa5d8f --- /dev/null +++ b/core/synctex/patches/12-omit-no-file-warning.diff @@ -0,0 +1,17 @@ +Omit warning message when opening non-synctex pdf +Author: Henrik Fehlauer + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -5838,7 +5838,9 @@ synctex_scanner_p synctex_scanner_new_wi + if ((scanner->reader = synctex_reader_init_with_output_file(scanner->reader, output, build_directory))) { + return parse? synctex_scanner_parse(scanner):scanner; + } ++#if defined(SYNCTEX_DEBUG) + _synctex_error("No file?"); ++#endif + return NULL; + } + diff --git a/core/synctex/patches/13-fix-Wundef-warnings.diff b/core/synctex/patches/13-fix-Wundef-warnings.diff new file mode 100644 index 000000000..00012916d --- /dev/null +++ b/core/synctex/patches/13-fix-Wundef-warnings.diff @@ -0,0 +1,326 @@ +Fix compiler warnings for -Wundef +Author: Henrik Fehlauer + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -362,7 +362,7 @@ __synctex_scanner_register_handle_to(NOD + # pragma mark Tree SETGET + # endif + +-#if SYNCTEX_DEBUG > 1000 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG > 1000 + #define SYNCTEX_PARAMETER_ASSERT(WHAT) \ + do { \ + if (!(WHAT)) { \ +@@ -459,11 +459,11 @@ DEFINE_SYNCTEX_TREE_GETSET(next_hbox) + DEFINE_SYNCTEX_TREE_GETSET(arg_sibling) + DEFINE_SYNCTEX_TREE_GETSET(target) + +-#if SYNCTEX_DEBUG>1000 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>1000 + # undef SYNCTEX_USE_NODE_COUNT + # define SYNCTEX_USE_NODE_COUNT 1 + #endif +-#if SYNCTEX_USE_NODE_COUNT>0 ++#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>0 + # define SYNCTEX_DECLARE_NODE_COUNT int node_count; + # define SYNCTEX_INIT_NODE_COUNT \ + do { node_count = 0; } while(synctex_NO) +@@ -472,7 +472,7 @@ DEFINE_SYNCTEX_TREE_GETSET(target) + # define SYNCTEX_INIT_NODE_COUNT + #endif + +-#if SYNCTEX_USE_NODE_COUNT>10 ++#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>10 + # define SYNCTEX_DID_NEW(N) _synctex_did_new(N) + # define SYNCTEX_WILL_FREE(N) _synctex_will_free(N) + #else +@@ -865,7 +865,7 @@ SYNCTEX_INLINE static void __synctex_sca + node->class->scanner->handle = NNN; + } + #endif +-#if SYNCTEX_USE_NODE_COUNT>10 ++#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>10 + SYNCTEX_INLINE static void _synctex_did_new(synctex_node_p node) { + printf("NODE CREATED # %i, %s, %p\n", + (node->class->scanner->node_count)++, +@@ -2516,7 +2516,7 @@ SYNCTEX_INLINE static synctex_nns_s _syn + } + static char * _synctex_node_abstract(synctex_node_p node); + SYNCTEX_INLINE static synctex_node_p synctex_tree_set_friend(synctex_node_p node,synctex_node_p new_friend) { +-#if SYNCTEX_DEBUG ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG + synctex_node_p F = new_friend; + while (F) { + if (node == F) { +@@ -2545,7 +2545,7 @@ SYNCTEX_INLINE static synctex_node_p __s + i = i%(node->class->scanner->number_of_lists); + old = synctex_tree_set_friend(node,(node->class->scanner->lists_of_friends)[i]); + (node->class->scanner->lists_of_friends)[i] = node; +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("tl(%i)=>",i); + synctex_node_log(node); + if (synctex_node_parent_form(node)) { +@@ -4186,7 +4186,7 @@ static synctex_ns_s __synctex_parse_new_ + /* Prepend this input node to the input linked list of the scanner */ + __synctex_tree_set_sibling(input,scanner->input);/* input has no parent */ + scanner->input = input; +-# if SYNCTEX_VERBOSE ++# ifdef SYNCTEX_VERBOSE + synctex_node_log(input); + # endif + return (synctex_ns_s){input,_synctex_next_line(scanner)};/* read the line termination character, if any */ +@@ -5083,10 +5083,10 @@ content_loop: + */ + /* forms are everywhere */ + ns = SYNCTEX_NS_NULL; +-#if SYNCTEX_VERBOSE ++#ifdef SYNCTEX_VERBOSE + synctex_scanner_set_display_switcher(scanner,-1); + printf("NEW CONTENT LOOP\n"); +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + synctex_node_display(sheet); + #endif + #endif +@@ -5106,7 +5106,7 @@ content_loop: + } + parent = ns.node; + child = _synctex_tree_last(parent); +-# if SYNCTEX_VERBOSE ++# ifdef SYNCTEX_VERBOSE + synctex_node_log(parent); + # endif + input.node = _synctex_input_register_line(input.node,parent); +@@ -5124,7 +5124,7 @@ content_loop: + } + child = parent; + parent = _synctex_tree_parent(child); +-# if SYNCTEX_VERBOSE ++# ifdef SYNCTEX_VERBOSE + synctex_node_log(child); + # endif + if (_synctex_next_line(scanner)500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + synctex_node_display(parent); + synctex_node_display(child); + #endif +@@ -5406,7 +5406,7 @@ _synctex_make_hbox_contain_box(parent,_s + } + scanner->ref_in_sheet = child; + } +-# if SYNCTEX_VERBOSE ++# ifdef SYNCTEX_VERBOSE + synctex_node_log(child); + # endif + goto content_loop; +@@ -5427,7 +5427,7 @@ _synctex_make_hbox_contain_box(parent,_s + __synctex_node_make_friend_tlc(child); + } + _synctex_make_hbox_contain_point(parent,_synctex_data_point(child)); +-# if SYNCTEX_VERBOSE ++# ifdef SYNCTEX_VERBOSE + synctex_node_log(child); + # endif + input.node = _synctex_input_register_line(input.node,child); +@@ -5536,7 +5536,7 @@ SYNCTEX_INLINE static synctex_ns_s __syn + sibling->line_index = arg_sibling->line_index; + } + #endif +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! Ref replacement:\n"); + synctex_node_log(ref); + synctex_node_display(synctex_node_sibling(ref)); +@@ -5600,7 +5600,7 @@ SYNCTEX_INLINE static synctex_status_t _ + } + } + do { +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("POST PROCESSING %s\n",_synctex_node_abstract(proxy)); + { + int i,j = 0; +@@ -5619,7 +5619,7 @@ SYNCTEX_INLINE static synctex_status_t _ + } + #endif + f(proxy); +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + { + int i,j = 0; + for (i=0;iclass->scanner->number_of_lists;++i) { +@@ -5638,7 +5638,7 @@ SYNCTEX_INLINE static synctex_status_t _ + #endif + /* Side effect: create the hierarchy on the fly */ + proxy = synctex_node_next(proxy); /* Change is here */ +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + if (proxy) { + int i,j = 0; + for (i=0;iclass->scanner->number_of_lists;++i) { +@@ -5670,7 +5670,7 @@ SYNCTEX_INLINE static synctex_status_t _ + SYNCTEX_INLINE static synctex_status_t _synctex_post_process(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + synctex_ns_s ns = {NULL,SYNCTEX_STATUS_NOT_OK}; +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! entering _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +@@ -5681,7 +5681,7 @@ SYNCTEX_INLINE static synctex_status_t _ + if (ns.status500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! ref replaced in form _synctex_post_process.\n"); + synctex_node_display(scanner->form); + #endif +@@ -5703,7 +5703,7 @@ SYNCTEX_INLINE static synctex_status_t _ + status = ns.status; + } + scanner->ref_in_sheet = NULL; +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! ref replaced in sheet _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + #endif +@@ -5725,7 +5725,7 @@ SYNCTEX_INLINE static synctex_status_t _ + } + } + #endif +-#if SYNCTEX_DEBUG>10000 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>10000 + { + int i; + for (i=0;inumber_of_lists;++i) { +@@ -5746,7 +5746,7 @@ SYNCTEX_INLINE static synctex_status_t _ + if (ns.status500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! exiting _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +@@ -5861,7 +5861,7 @@ int synctex_scanner_free(synctex_scanner + synctex_iterator_free(scanner->iterator); + free(scanner->output_fmt); + free(scanner->lists_of_friends); +-#if SYNCTEX_USE_NODE_COUNT>0 ++#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>0 + node_count = scanner->node_count; + #endif + free(scanner); +@@ -5919,7 +5919,7 @@ synctex_scanner_p synctex_scanner_parse( + if (status500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + synctex_scanner_set_display_switcher(scanner, 100); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +@@ -8048,7 +8048,7 @@ static synctex_nd_s __synctex_closest_de + SYNCTEX_LINEINDEX(node)); + #endif + do { +-#if SYNCTEX_DEBUG>500 ++#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + synctex_node_display(child); + #endif + synctex_nd_s nd = SYNCTEX_ND_0; +Index: synctex/synctex_parser_utils.c +=================================================================== +--- synctex.orig/synctex_parser_utils.c ++++ synctex/synctex_parser_utils.c +@@ -126,7 +126,7 @@ int _synctex_log(int level, const char * + OutputDebugStringA("\n"); + free(buff); + } +-# elif SYNCTEX_USE_SYSLOG ++# elif defined SYNCTEX_USE_SYSLOG + char * buffer1 = NULL; + char * buffer2 = NULL; + openlog ("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0); diff --git a/core/synctex/patches/14-fix-misc-compiler-warnings.diff b/core/synctex/patches/14-fix-misc-compiler-warnings.diff new file mode 100644 index 000000000..9b4959d7a --- /dev/null +++ b/core/synctex/patches/14-fix-misc-compiler-warnings.diff @@ -0,0 +1,62 @@ +Fix even more compiler warnings +Author: Henrik Fehlauer + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -529,8 +529,8 @@ typedef struct synctex_reader_t { + char * current; /* current location in the buffer */ + char * start; /* start of the buffer */ + char * end; /* end of the buffer */ +- int min_size; +- int size; ++ size_t min_size; ++ size_t size; + int lastv; + int line_number; + SYNCTEX_DECLARE_CHAR_OFFSET +@@ -759,7 +759,6 @@ synctex_reader_p synctex_reader_init_wit + (char *)_synctex_malloc(reader->size+1); /* one more character for null termination */ + if (NULL == reader->start) { + _synctex_error("! malloc error in synctex_reader_init_with_output_file."); +- bailey: + #ifdef SYNCTEX_DEBUG + return reader; + #else +@@ -5293,7 +5292,7 @@ content_loop: + # pragma mark + SCAN KERN + # endif + ns = _synctex_parse_new_kern(scanner); +- continue_scan: ++ /* continue_scan: */ + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); +@@ -6531,7 +6530,7 @@ SYNCTEX_INLINE static synctex_point_s _s + return old; + } + SYNCTEX_INLINE static synctex_box_s _synctex_data_box(synctex_node_p node) { +- synctex_box_s box = {0,0,0,0}; ++ synctex_box_s box = {{0,0},{0,0}}; + int n; + n = synctex_node_width(node); + if (n<0) { +@@ -6547,7 +6546,7 @@ SYNCTEX_INLINE static synctex_box_s _syn + return box; + } + SYNCTEX_INLINE static synctex_box_s _synctex_data_xob(synctex_node_p node) { +- synctex_box_s box = {0,0,0,0}; ++ synctex_box_s box = {{0,0},{0,0}}; + int n; + n = synctex_node_width(node); + if (n>0) { +@@ -6563,7 +6562,7 @@ SYNCTEX_INLINE static synctex_box_s _syn + return box; + } + SYNCTEX_INLINE static synctex_box_s _synctex_data_box_V(synctex_node_p node) { +- synctex_box_s box = {0,0,0,0}; ++ synctex_box_s box = {{0,0},{0,0}}; + int n; + n = _synctex_node_width_V(node); + if (n<0) { diff --git a/core/synctex/patches/15-prevent-leaks-and-segfault.diff b/core/synctex/patches/15-prevent-leaks-and-segfault.diff new file mode 100644 index 000000000..17bc677a2 --- /dev/null +++ b/core/synctex/patches/15-prevent-leaks-and-segfault.diff @@ -0,0 +1,54 @@ +Plug multiple leaks and prevent a segfault: +- When the returned value to be assigned to reader is NULL, reader should be freed beforehand. +- Do not leak memory when scanner creation succeeded, but no synctex file was found. +- A valid reader is the prerequisite for accessing SYNCTEX_FILE. +- Two calls to malloc() for reader->start also require two calls to free(). + +Author: Henrik Fehlauer + + +Index: synctex/synctex_parser.c +=================================================================== +--- synctex.orig/synctex_parser.c ++++ synctex/synctex_parser.c +@@ -739,6 +739,7 @@ synctex_reader_p synctex_reader_init_wit + if (open.statusreader && SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } +@@ -5883,6 +5885,15 @@ synctex_scanner_p synctex_scanner_parse( + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->reader->line_number = 1; + ++ /* TODO: cleanup ++ * In some (all?) cases SYNCTEX_START is already initialized ++ * in synctex_reader_init_with_output_file(). Much of the ++ * following code seems like a duplicate and is perhaps a ++ * candidate for deletion. To be on the safe side though, we ++ * keep it for now and just free() any prior malloc() if ++ * existing. */ ++ _synctex_free(SYNCTEX_START); ++ + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if (NULL == SYNCTEX_START) { + _synctex_error("! malloc error in synctex_scanner_parse."); diff --git a/core/synctex/patches/series b/core/synctex/patches/series index 7497c5244..d9482f9ce 100644 --- a/core/synctex/patches/series +++ b/core/synctex/patches/series @@ -1,8 +1,10 @@ 00-disable-SYNCTEX_INLINE.diff -01-fix-win32-define.diff 04-gcc-specify-printf-format.diff -05-fix-error-formats.diff 06-mingw-_synctex_error.diff -07-synctex_scanner_new_with_output_file-reset-mode.diff 08-fix_cpp_comments.diff -09-fix_path_comparison.diff +10-fix-typo.diff +11-fix-unused-parameters-warnings.diff +12-omit-no-file-warning.diff +13-fix-Wundef-warnings.diff +14-fix-misc-compiler-warnings.diff +15-prevent-leaks-and-segfault.diff diff --git a/core/synctex/synctex_parser.c b/core/synctex/synctex_parser.c index 52d0f0ca3..bd2878ee5 100644 --- a/core/synctex/synctex_parser.c +++ b/core/synctex/synctex_parser.c @@ -1,4245 +1,8678 @@ -/* -Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr - -This file is part of the SyncTeX package. - -Latest Revision: Tue Jun 14 08:23:30 UTC 2011 - -Version: 1.16 - -See synctex_parser_readme.txt for more details - -License: --------- -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE - -Except as contained in this notice, the name of the copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in this Software without prior written -authorization from the copyright holder. - -Acknowledgments: ----------------- -The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, -and significant help from XeTeX developer Jonathan Kew - -Nota Bene: ----------- -If you include or use a significant part of the synctex package into a software, -I would appreciate to be listed as contributor and see "SyncTeX" highlighted. - -Version 1 -Thu Jun 19 09:39:21 UTC 2008 - -*/ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.19) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + + Acknowledgments: + ---------------- + The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, + and significant help from XeTeX developer Jonathan Kew + + Nota Bene: + ---------- + If you include or use a significant part of the synctex package into a software, + I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + + */ /* We assume that high level application like pdf viewers will want * to embed this code as is. We assume that they also have locale.h and setlocale. * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER, * when building. You also have to create and customize synctex_parser_local.h to fit your system. * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined. * With this design, you should not need to edit this file. */ +/** + * \file synctex_parser.c + * \brief SyncTeX file parser and controller. + * - author: Jérôme LAURENS + * \version 1.19 + * \date Mon Apr 24 07:08:56 UTC 2017 + * + * Reads and parse *.synctex[.gz] files, + * performs edit and display queries. + * + * See + * - synctex_scanner_new_with_output_file + * - synctex_scanner_parse + * - synctex_scanner_free + * - synctex_display_query + * - synctex_edit_query + * - synctex_scanner_next_result + * - synctex_scanner_reset_result + * + * The data is organized in a graph with multiple entries. + * The root object is a scanner, it is created with the contents on a synctex file. + * Each node of the tree is a synctex_node_t object. + * There are 3 subtrees, two of them sharing the same leaves. + * The first tree is the list of input records, where input file names are associated with tags. + * The second tree is the box tree as given by TeX when shipping pages out. + * First level objects are sheets and forms, containing boxes, glues, kerns... + * The third tree allows to browse leaves according to tag and line. + */ # if defined(SYNCTEX_USE_LOCAL_HEADER) # include "synctex_parser_local.h" # else # define HAVE_LOCALE_H 1 # define HAVE_SETLOCALE 1 -# if defined(_MSC_VER) +# if defined(_MSC_VER) # define SYNCTEX_INLINE __inline # else # define SYNCTEX_INLINE # endif # endif #include #include #include #include #include #if defined(HAVE_LOCALE_H) #include #endif -/* The data is organized in a graph with multiple entries. - * The root object is a scanner, it is created with the contents on a synctex file. - * Each leaf of the tree is a synctex_node_t object. - * There are 3 subtrees, two of them sharing the same leaves. - * The first tree is the list of input records, where input file names are associated with tags. - * The second tree is the box tree as given by TeX when shipping pages out. - * First level objects are sheets, containing boxes, glues, kerns... - * The third tree allows to browse leaves according to tag and line. - */ - -#include "synctex_parser.h" -#include "synctex_parser_utils.h" +#include "synctex_parser_advanced.h" +SYNCTEX_INLINE static int _synctex_abs(int x) { + return x>0? x: -x; +} /* These are the possible extensions of the synctex file */ const char * synctex_suffix = ".synctex"; const char * synctex_suffix_gz = ".gz"; -/* each synctex node has a class */ -typedef struct __synctex_class_t _synctex_class_t; -typedef _synctex_class_t * synctex_class_t; +typedef synctex_node_p(*synctex_node_new_f)(synctex_scanner_p); +typedef void(*synctex_node_fld_f)(synctex_node_p); +typedef char *(*synctex_node_str_f)(synctex_node_p); + +/** + * Pseudo class. + * - author: J. Laurens + * + * Each nodes has a class, it is therefore called an object. + * Each class has a unique scanner. + * Each class has a type which is a unique identifier. + * Each class has a node mask which identifies node's attributes. + * Each class has an info mask which info's attributes. + * The class points to various methods, + * each of them vary amongst objects. + * The navigator records the offsets of the tree members getters. + * The modelator records the offsets of the data members getters, relative to the last navigator getter. + */ + +/* 8 fields + size: spcflnat */ +typedef struct synctex_tree_model_t { + int sibling; + int parent; + int child; + int friend; + int last; + int next_hbox; + int arg_sibling; + int target; + int size; +} synctex_tree_model_s; +typedef const synctex_tree_model_s * synctex_tree_model_p; + +typedef struct synctex_data_model_t { + int tag; + int line; + int column; + int h; + int v; + int width; + int height; + int depth; + int mean_line; + int weight; + int h_V; + int v_V; + int width_V; + int height_V; + int depth_V; + int name; + int page; + int size; +} synctex_data_model_s; + +static const synctex_data_model_s synctex_data_model_none = { + -1, /* tag */ + -1, /* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + 0 +}; +typedef const synctex_data_model_s * synctex_data_model_p; + +typedef int (*synctex_int_getter_f)(synctex_node_p); +typedef struct synctex_tlcpector_t { + synctex_int_getter_f tag; + synctex_int_getter_f line; + synctex_int_getter_f column; +} synctex_tlcpector_s; +typedef const synctex_tlcpector_s * synctex_tlcpector_p; +static int _synctex_int_none(synctex_node_p node) { + (void)node; /* unused */ + return 0; +} +static const synctex_tlcpector_s synctex_tlcpector_none = { + &_synctex_int_none, /* tag */ + &_synctex_int_none, /* line */ + &_synctex_int_none, /* column */ +}; +typedef struct synctex_inspector_t { + synctex_int_getter_f h; + synctex_int_getter_f v; + synctex_int_getter_f width; + synctex_int_getter_f height; + synctex_int_getter_f depth; +} synctex_inspector_s; +typedef const synctex_inspector_s * synctex_inspector_p; +static const synctex_inspector_s synctex_inspector_none = { + &_synctex_int_none, /* h */ + &_synctex_int_none, /* v */ + &_synctex_int_none, /* width */ + &_synctex_int_none, /* height */ + &_synctex_int_none, /* depth */ +}; -/* synctex_node_t is a pointer to a node - * _synctex_node is the target of the synctex_node_t pointer - * It is a pseudo object oriented program. - * class is a pointer to the class object the node belongs to. - * implementation is meant to contain the private data of the node - * basically, there are 2 kinds of information: navigation information and - * synctex information. Both will depend on the type of the node, - * thus different nodes will have different private data. - * There is no inheritancy overhead. - */ -typedef union _synctex_info_t { - int INT; - char * PTR; -} synctex_info_t; - -struct _synctex_node { - synctex_class_t class; - synctex_info_t * implementation; +typedef float (*synctex_float_getter_f)(synctex_node_p); +typedef struct synctex_vispector_t { + synctex_float_getter_f h; + synctex_float_getter_f v; + synctex_float_getter_f width; + synctex_float_getter_f height; + synctex_float_getter_f depth; +} synctex_vispector_s; +static float _synctex_float_none(synctex_node_p node) { + (void)node; /* unused */ + return 0; +} +static const synctex_vispector_s synctex_vispector_none = { + &_synctex_float_none, /* h */ + &_synctex_float_none, /* v */ + &_synctex_float_none, /* width */ + &_synctex_float_none, /* height */ + &_synctex_float_none, /* depth */ +}; +typedef const synctex_vispector_s * synctex_vispector_p; + +struct synctex_class_t { + synctex_scanner_p scanner; + synctex_node_type_t type; + synctex_node_new_f new; + synctex_node_fld_f free; + synctex_node_fld_f log; + synctex_node_fld_f display; + synctex_node_str_f abstract; + synctex_tree_model_p navigator; + synctex_data_model_p modelator; + synctex_tlcpector_p tlcpector; + synctex_inspector_p inspector; + synctex_vispector_p vispector; }; -/* Each node of the tree, except the scanner itself belongs to a class. - * The class object is just a struct declaring the owning scanner - * This is a pointer to the scanner as root of the tree. - * The type is used to identify the kind of node. - * The class declares pointers to a creator and a destructor method. - * The log and display fields are used to log and display the node. - * display will also display the child, sibling and parent sibling. - * parent, child and sibling are used to navigate the tree, - * from TeX box hierarchy point of view. - * The friend field points to a method which allows to navigate from friend to friend. - * A friend is a node with very close tag and line numbers. - * Finally, the info field point to a method giving the private node info offset. +/** + * Nota bene: naming convention. + * For static API, when the name contains proxy, it applies to proxies. + * When the name contains noxy, it applies to non proxies only. + * When the name contains node, weel it depends... */ -typedef synctex_node_t *(*_synctex_node_getter_t)(synctex_node_t); -typedef synctex_info_t *(*_synctex_info_getter_t)(synctex_node_t); - -struct __synctex_class_t { - synctex_scanner_t scanner; - int type; - synctex_node_t (*new)(synctex_scanner_t scanner); - void (*free)(synctex_node_t); - void (*log)(synctex_node_t); - void (*display)(synctex_node_t); - _synctex_node_getter_t parent; - _synctex_node_getter_t child; - _synctex_node_getter_t sibling; - _synctex_node_getter_t friend; - _synctex_node_getter_t next_box; - _synctex_info_getter_t info; -}; +typedef synctex_node_p synctex_proxy_p; +typedef synctex_node_p synctex_noxy_p; # ifdef SYNCTEX_NOTHING # pragma mark - # pragma mark Abstract OBJECTS and METHODS # endif -/* These macros are shortcuts - * This macro checks if a message can be sent. - */ -# define SYNCTEX_CAN_PERFORM(NODE,SELECTOR)\ - (NULL!=((((NODE)->class))->SELECTOR)) - -/* This macro is some kind of objc_msg_send. - * It takes care of sending the proper message if possible. - */ -# define SYNCTEX_MSG_SEND(NODE,SELECTOR) if (NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR)) {\ - (*((((NODE)->class))->SELECTOR))(NODE);\ - } - -/* read only safe getter +/** + * \def SYNCTEX_MSG_SEND + * \brief Takes care of sending the given message if possible. + * - parameter NODE: of type synctex_node_p + * - parameter SELECTOR: one of the class pointer properties */ -# define SYNCTEX_GET(NODE,SELECTOR)((NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR))?SYNCTEX_GETTER(NODE,SELECTOR)[0]:(NULL)) - -/* read/write getter +# define SYNCTEX_MSG_SEND(NODE,SELECTOR) do {\ + synctex_node_p N__ = NODE;\ + if (N__ && N__->class->SELECTOR) {\ + (*(N__->class->SELECTOR))(N__);\ + }\ +} while (synctex_NO) + +/** + * Free the given node by sending the free message. + * - parameter NODE: of type synctex_node_p */ -# define SYNCTEX_GETTER(NODE,SELECTOR)\ - ((synctex_node_t *)((*((((NODE)->class))->SELECTOR))(NODE))) - -# define SYNCTEX_FREE(NODE) SYNCTEX_MSG_SEND(NODE,free); +void synctex_node_free(synctex_node_p node) { + SYNCTEX_MSG_SEND(node,free); +} +# if defined(SYNCTEX_TESTING) +# if !defined(SYNCTEX_USE_HANDLE) +# define SYNCTEX_USE_HANDLE 1 +# endif +# if !defined(SYNCTEX_USE_CHARINDEX) +# define SYNCTEX_USE_CHARINDEX 1 +# endif +# endif +SYNCTEX_INLINE static synctex_node_p _synctex_new_handle_with_target(synctex_node_p target); +# if defined(SYNCTEX_USE_HANDLE) +# define SYNCTEX_SCANNER_FREE_HANDLE(SCANR) \ +__synctex_scanner_free_handle(SCANR) +# define SYNCTEX_SCANNER_REMOVE_HANDLE_TO(WHAT) \ +__synctex_scanner_remove_handle_to(WHAT) +# define SYNCTEX_REGISTER_HANDLE_TO(NODE) \ +__synctex_scanner_register_handle_to(NODE) +# else +# define SYNCTEX_SCANNER_FREE_HANDLE(SCANR) +# define SYNCTEX_SCANNER_REMOVE_HANDLE_TO(WHAT) +# define SYNCTEX_REGISTER_HANDLE_TO(NODE) +# endif -/* Parent getter and setter - */ -# define SYNCTEX_PARENT(NODE) SYNCTEX_GET(NODE,parent) -# define SYNCTEX_SET_PARENT(NODE,NEW_PARENT) if (NODE && NEW_PARENT && SYNCTEX_CAN_PERFORM(NODE,parent)){\ - SYNCTEX_GETTER(NODE,parent)[0]=NEW_PARENT;\ - } +# if defined(SYNCTEX_USE_CHARINDEX) +# define SYNCTEX_CHARINDEX(NODE) (NODE->char_index) +# define SYNCTEX_LINEINDEX(NODE) (NODE->line_index) +# define SYNCTEX_PRINT_CHARINDEX_FMT "#%i" +# define SYNCTEX_PRINT_CHARINDEX_WHAT ,SYNCTEX_CHARINDEX(node) +# define SYNCTEX_PRINT_CHARINDEX \ + printf(SYNCTEX_PRINT_CHARINDEX_FMT SYNCTEX_PRINT_CHARINDEX_WHAT) +# define SYNCTEX_PRINT_LINEINDEX_FMT "L#%i" +# define SYNCTEX_PRINT_LINEINDEX_WHAT ,SYNCTEX_LINEINDEX(node) +# define SYNCTEX_PRINT_LINEINDEX \ + printf(SYNCTEX_PRINT_LINEINDEX_FMT SYNCTEX_PRINT_LINEINDEX_WHAT) +# define SYNCTEX_PRINT_CHARINDEX_NL \ + printf(SYNCTEX_PRINT_CHARINDEX_FMT "\n" SYNCTEX_PRINT_CHARINDEX_WHAT) +# define SYNCTEX_PRINT_LINEINDEX_NL \ + printf(SYNCTEX_PRINT_CHARINDEX_FMT "\n"SYNCTEX_PRINT_LINEINDEX_WHAT) +# define SYNCTEX_IMPLEMENT_CHARINDEX(NODE,CORRECTION)\ + NODE->char_index = (synctex_charindex_t)(scanner->reader->charindex_offset+SYNCTEX_CUR-SYNCTEX_START+(CORRECTION)); \ + NODE->line_index = scanner->reader->line_number; +# else +# define SYNCTEX_CHARINDEX(NODE) 0 +# define SYNCTEX_LINEINDEX(NODE) 0 +# define SYNCTEX_PRINT_CHARINDEX_FMT +# define SYNCTEX_PRINT_CHARINDEX_WHAT +# define SYNCTEX_PRINT_CHARINDEX +# define SYNCTEX_PRINT_CHARINDEX +# define SYNCTEX_PRINT_LINEINDEX_FMT +# define SYNCTEX_PRINT_LINEINDEX_WHAT +# define SYNCTEX_PRINT_LINEINDEX +# define SYNCTEX_PRINT_CHARINDEX_NL printf("\n") +# define SYNCTEX_PRINT_LINEINDEX_NL printf("\n") +# define SYNCTEX_IMPLEMENT_CHARINDEX(NODE,CORRECTION) +# endif -/* Child getter and setter +/** + * The next macros are used to access the node tree info + * SYNCTEX_DATA(node) points to the first synctex integer or pointer data of node + * SYNCTEX_DATA(node)[index] is the information at index + * for example, the page of a sheet is stored in SYNCTEX_DATA(sheet)[_synctex_data_page_idx] + * - parameter NODE: of type synctex_node_p + * If the name starts with "__", the argument is nonullable */ -# define SYNCTEX_CHILD(NODE) SYNCTEX_GET(NODE,child) -# define SYNCTEX_SET_CHILD(NODE,NEW_CHILD) if (NODE && NEW_CHILD){\ - SYNCTEX_GETTER(NODE,child)[0]=NEW_CHILD;\ - SYNCTEX_GETTER(NEW_CHILD,parent)[0]=NODE;\ - } +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Tree SETGET +# endif -/* Sibling getter and setter - */ -# define SYNCTEX_SIBLING(NODE) SYNCTEX_GET(NODE,sibling) -# define SYNCTEX_SET_SIBLING(NODE,NEW_SIBLING) if (NODE && NEW_SIBLING) {\ - SYNCTEX_GETTER(NODE,sibling)[0]=NEW_SIBLING;\ - if (SYNCTEX_CAN_PERFORM(NEW_SIBLING,parent) && SYNCTEX_CAN_PERFORM(NODE,parent)) {\ - SYNCTEX_GETTER(NEW_SIBLING,parent)[0]=SYNCTEX_GETTER(NODE,parent)[0];\ - }\ - } -/* Friend getter and setter. A friend is a kern, math, glue or void box node which tag and line numbers are similar. - * This is a first filter on the nodes that avoids testing all of them. - * Friends are used mainly in forward synchronization aka from source to output. - */ -# define SYNCTEX_FRIEND(NODE) SYNCTEX_GET(NODE,friend) -# define SYNCTEX_SET_FRIEND(NODE,NEW_FRIEND) if (NODE && NEW_FRIEND){\ - SYNCTEX_GETTER(NODE,friend)[0]=NEW_FRIEND;\ - } +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG > 1000 +#define SYNCTEX_PARAMETER_ASSERT(WHAT) \ + do { \ + if (!(WHAT)) { \ + printf("! Parameter failure: %s\n",#WHAT); \ + } \ + } while (synctex_NO) +#define DEFINE_SYNCTEX_TREE_HAS(WHAT)\ +static synctex_bool_t _synctex_tree_has_##WHAT(synctex_node_p node) {\ + if (node) {\ + if (node->class->navigator->WHAT>=0) {\ + return synctex_YES; \ + } else {\ + printf("WARNING: NO tree %s for %s\n", #WHAT, synctex_node_isa(node));\ + }\ + }\ + return synctex_NO;\ +} +#else +#define SYNCTEX_PARAMETER_ASSERT(WHAT) +#define DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +static synctex_bool_t _synctex_tree_has_##WHAT(synctex_node_p node) {\ + return (node && (node->class->navigator->WHAT>=0));\ +} +#endif -/* Next box getter and setter. The box tree can be traversed from one horizontal box to the other. - * Navigation starts with the deeper boxes. +# define DEFINE_SYNCTEX_TREE__GET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p __synctex_tree_##WHAT(synctex_non_null_node_p node) {\ + return node->data[node->class->navigator->WHAT].as_node;\ +} +# define DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE__GET(WHAT) \ +static synctex_node_p _synctex_tree_##WHAT(synctex_node_p node) {\ + if (_synctex_tree_has_##WHAT(node)) {\ + return __synctex_tree_##WHAT(node);\ + }\ + return 0;\ +} +# define DEFINE_SYNCTEX_TREE__RESET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p __synctex_tree_reset_##WHAT(synctex_non_null_node_p node) {\ + synctex_node_p old = node->data[node->class->navigator->WHAT].as_node;\ + node->data[node->class->navigator->WHAT].as_node=NULL;\ + return old;\ +} +# define DEFINE_SYNCTEX_TREE_RESET(WHAT) \ +DEFINE_SYNCTEX_TREE__RESET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p _synctex_tree_reset_##WHAT(synctex_node_p node) {\ + return _synctex_tree_has_##WHAT(node)? \ + __synctex_tree_reset_##WHAT(node): NULL; \ +} +# define DEFINE_SYNCTEX_TREE__SET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p __synctex_tree_set_##WHAT(synctex_non_null_node_p node, synctex_node_p new_value) {\ + synctex_node_p old = __synctex_tree_##WHAT(node);\ + node->data[node->class->navigator->WHAT].as_node=new_value;\ + return old;\ +} +# define DEFINE_SYNCTEX_TREE_SET(WHAT) \ +DEFINE_SYNCTEX_TREE__SET(WHAT) \ +SYNCTEX_INLINE static synctex_node_p _synctex_tree_set_##WHAT(synctex_node_p node, synctex_node_p new_value) {\ + return _synctex_tree_has_##WHAT(node)?\ + __synctex_tree_set_##WHAT(node,new_value):NULL;\ +} +# define DEFINE_SYNCTEX_TREE__GETSETRESET(WHAT) \ +DEFINE_SYNCTEX_TREE__GET(WHAT) \ +DEFINE_SYNCTEX_TREE__SET(WHAT) \ +DEFINE_SYNCTEX_TREE__RESET(WHAT) + +# define DEFINE_SYNCTEX_TREE_GETSET(WHAT) \ +DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE_SET(WHAT) + +# define DEFINE_SYNCTEX_TREE_GETRESET(WHAT) \ +DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE_RESET(WHAT) + +# define DEFINE_SYNCTEX_TREE_GETSETRESET(WHAT) \ +DEFINE_SYNCTEX_TREE_HAS(WHAT) \ +DEFINE_SYNCTEX_TREE_GET(WHAT) \ +DEFINE_SYNCTEX_TREE_SET(WHAT) \ +DEFINE_SYNCTEX_TREE_RESET(WHAT) + +/* + * _synctex_tree_set_... methods return the old value. + * The return value of _synctex_tree_set_child and + * _synctex_tree_set_sibling must be released somehown. */ -# define SYNCTEX_NEXT_HORIZ_BOX(NODE) SYNCTEX_GET(NODE,next_box) -# define SYNCTEX_SET_NEXT_HORIZ_BOX(NODE,NEXT_BOX) if (NODE && NEXT_BOX){\ - SYNCTEX_GETTER(NODE,next_box)[0]=NEXT_BOX;\ - } - -void _synctex_free_node(synctex_node_t node); -void _synctex_free_leaf(synctex_node_t node); +DEFINE_SYNCTEX_TREE__GETSETRESET(sibling) +DEFINE_SYNCTEX_TREE_GETSETRESET(parent) +DEFINE_SYNCTEX_TREE_GETSETRESET(child) +DEFINE_SYNCTEX_TREE_GETSETRESET(friend) +DEFINE_SYNCTEX_TREE_GETSET(last) +DEFINE_SYNCTEX_TREE_GETSET(next_hbox) +DEFINE_SYNCTEX_TREE_GETSET(arg_sibling) +DEFINE_SYNCTEX_TREE_GETSET(target) + +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>1000 +# undef SYNCTEX_USE_NODE_COUNT +# define SYNCTEX_USE_NODE_COUNT 1 +#endif +#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>0 +# define SYNCTEX_DECLARE_NODE_COUNT int node_count; +# define SYNCTEX_INIT_NODE_COUNT \ + do { node_count = 0; } while(synctex_NO) +#else +# define SYNCTEX_DECLARE_NODE_COUNT +# define SYNCTEX_INIT_NODE_COUNT +#endif -/* A node is meant to own its child and sibling. - * It is not owned by its parent, unless it is its first child. - * This destructor is for all nodes with children. - */ -void _synctex_free_node(synctex_node_t node) { - if (node) { - (*((node->class)->sibling))(node); - SYNCTEX_FREE(SYNCTEX_SIBLING(node)); - SYNCTEX_FREE(SYNCTEX_CHILD(node)); - free(node); - } - return; -} +#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>10 +# define SYNCTEX_DID_NEW(N) _synctex_did_new(N) +# define SYNCTEX_WILL_FREE(N) _synctex_will_free(N) +#else +# define SYNCTEX_DID_NEW(N) +# define SYNCTEX_WILL_FREE(N) +#endif -/* A node is meant to own its child and sibling. - * It is not owned by its parent, unless it is its first child. - * This destructor is for nodes with no child. - */ -void _synctex_free_leaf(synctex_node_t node) { - if (node) { - SYNCTEX_FREE(SYNCTEX_SIBLING(node)); - free(node); - } - return; -} +#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && _synctex_tree_child(NODE)) # ifdef __SYNCTEX_WORK__ # include "/usr/include/zlib.h" # else # include # endif -/* The synctex scanner is the root object. - * Is is initialized with the contents of a text file or a gzipped file. - * The buffer_? are first used to parse the text. - */ -struct __synctex_scanner_t { - gzFile file; /* The (possibly compressed) file */ - char * buffer_cur; /* current location in the buffer */ - char * buffer_start; /* start of the buffer */ - char * buffer_end; /* end of the buffer */ - char * output_fmt; /* dvi or pdf, not yet used */ - char * output; /* the output name used to create the scanner */ - char * synctex; /* the .synctex or .synctex.gz name used to create the scanner */ - int version; /* 1, not yet used */ - struct { - unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ - unsigned reserved:sizeof(unsigned)-1; /* alignment */ - } flags; - int pre_magnification; /* magnification from the synctex preamble */ - int pre_unit; /* unit from the synctex preamble */ - int pre_x_offset; /* X offste from the synctex preamble */ - int pre_y_offset; /* Y offset from the synctex preamble */ - int count; /* Number of records, from the synctex postamble */ - float unit; /* real unit, from synctex preamble or post scriptum */ - float x_offset; /* X offset, from synctex preamble or post scriptum */ - float y_offset; /* Y Offset, from synctex preamble or post scriptum */ - synctex_node_t sheet; /* The first sheet node, its siblings are the other sheet nodes */ - synctex_node_t input; /* The first input node, its siblings are the other input nodes */ - int number_of_lists; /* The number of friend lists */ - synctex_node_t * lists_of_friends;/* The friend lists */ - _synctex_class_t class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ -}; - -/* SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts - */ -# define SYNCTEX_CUR (scanner->buffer_cur) -# define SYNCTEX_START (scanner->buffer_start) -# define SYNCTEX_END (scanner->buffer_end) +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark STATUS +# endif +/* When the end of the synctex file has been reached: */ +# define SYNCTEX_STATUS_EOF 0 +/* When the function could not return the value it was asked for: */ +# define SYNCTEX_STATUS_NOT_OK (SYNCTEX_STATUS_EOF+1) +/* When the function returns the value it was asked for: + It must be the biggest one */ +# define SYNCTEX_STATUS_OK (SYNCTEX_STATUS_NOT_OK+1) +/* Generic error: */ +# define SYNCTEX_STATUS_ERROR (SYNCTEX_STATUS_EOF-1) +/* Parameter error: */ +# define SYNCTEX_STATUS_BAD_ARGUMENT (SYNCTEX_STATUS_ERROR-1) # ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark OBJECTS, their creators and destructors. +# pragma mark File reader # endif -/* Here, we define the indices for the different informations. - * They are used to declare the size of the implementation. - * For example, if one object uses SYNCTEX_HORIZ_IDX is its size, - * then its info will contain a tag, line, column, horiz but no width nor height nor depth +/* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ +/* Actually, the minimum buffer size is driven by integer and float parsing, including the unit. + * ±0.123456789e123?? */ +# define SYNCTEX_BUFFER_MIN_SIZE 32 +# define SYNCTEX_BUFFER_SIZE 32768 -/* The sheet is a first level node. - * It has no parent (the parent is the scanner itself) - * Its sibling points to another sheet. - * Its child points to its first child, in general a box. - * A sheet node contains only one synctex information: the page. - * This is the 1 based page index as given by TeX. - */ -/* The next macros are used to access the node info - * SYNCTEX_INFO(node) points to the first synctex integer or pointer data of node - * SYNCTEX_INFO(node)[index] is the information at index - * for example, the page of a sheet is stored in SYNCTEX_INFO(sheet)[SYNCTEX_PAGE_IDX] - */ -# define SYNCTEX_INFO(NODE) ((*((((NODE)->class))->info))(NODE)) -# define SYNCTEX_PAGE_IDX 0 -# define SYNCTEX_PAGE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_PAGE_IDX].INT +#if SYNCTEX_BUFFER_SIZE >= UINT_MAX +# error BAD BUFFER SIZE(1) +#endif +#if SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE +# error BAD BUFFER SIZE(2) +#endif -/* This macro defines implementation offsets - * It is only used for pointer values - */ -# define SYNCTEX_MAKE_GET(SYNCTEX_GETTER,OFFSET)\ -synctex_node_t * SYNCTEX_GETTER (synctex_node_t node);\ -synctex_node_t * SYNCTEX_GETTER (synctex_node_t node) {\ - return node?(synctex_node_t *)((&((node)->implementation))+OFFSET):NULL;\ -} -SYNCTEX_MAKE_GET(_synctex_implementation_0,0) -SYNCTEX_MAKE_GET(_synctex_implementation_1,1) -SYNCTEX_MAKE_GET(_synctex_implementation_2,2) -SYNCTEX_MAKE_GET(_synctex_implementation_3,3) -SYNCTEX_MAKE_GET(_synctex_implementation_4,4) -SYNCTEX_MAKE_GET(_synctex_implementation_5,5) +typedef struct synctex_reader_t { + gzFile file; /* The (possibly compressed) file */ + char * output; + char * synctex; + char * current; /* current location in the buffer */ + char * start; /* start of the buffer */ + char * end; /* end of the buffer */ + size_t min_size; + size_t size; + int lastv; + int line_number; + SYNCTEX_DECLARE_CHAR_OFFSET +} synctex_reader_s; + +typedef synctex_reader_s * synctex_reader_p; typedef struct { - synctex_class_t class; - synctex_info_t implementation[3+SYNCTEX_PAGE_IDX+1];/* child, sibling, next box, - * SYNCTEX_PAGE_IDX */ -} synctex_sheet_t; - -synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner); -void _synctex_display_sheet(synctex_node_t sheet); -void _synctex_log_sheet(synctex_node_t sheet); - -static _synctex_class_t synctex_class_sheet = { - NULL, /* No scanner yet */ - synctex_node_type_sheet, /* Node type */ - &_synctex_new_sheet, /* creator */ - &_synctex_free_node, /* destructor */ - &_synctex_log_sheet, /* log */ - &_synctex_display_sheet, /* display */ - NULL, /* No parent */ - &_synctex_implementation_0, /* child */ - &_synctex_implementation_1, /* sibling */ - NULL, /* No friend */ - &_synctex_implementation_2, /* Next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 /* info */ -}; - -/* sheet node creator */ -synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_sheet_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_sheet:(synctex_class_t)&synctex_class_sheet; - } - return node; -} + synctex_status_t status; + char * synctex; + gzFile file; + synctex_io_mode_t io_mode; +} synctex_open_s; -/* A box node contains navigation and synctex information - * There are different kind of boxes. - * Only horizontal boxes are treated differently because of their visible size. +/* This functions opens the file at the "output" given location. + * It manages the problem of quoted filenames that appear with pdftex and filenames containing the space character. + * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. + * This function will remove them if possible. + * All the reference arguments will take a value on return. They must be non NULL. + * - returns: an open structure which status is + * SYNCTEX_STATUS_OK on success, + * SYNCTEX_STATUS_ERROR on failure. + * - note: on success, the caller is the owner + * of the fields of the returned open structure. */ -# define SYNCTEX_TAG_IDX 0 -# define SYNCTEX_LINE_IDX (SYNCTEX_TAG_IDX+1) -# define SYNCTEX_COLUMN_IDX (SYNCTEX_LINE_IDX+1) -# define SYNCTEX_HORIZ_IDX (SYNCTEX_COLUMN_IDX+1) -# define SYNCTEX_VERT_IDX (SYNCTEX_HORIZ_IDX+1) -# define SYNCTEX_WIDTH_IDX (SYNCTEX_VERT_IDX+1) -# define SYNCTEX_HEIGHT_IDX (SYNCTEX_WIDTH_IDX+1) -# define SYNCTEX_DEPTH_IDX (SYNCTEX_HEIGHT_IDX+1) -/* the corresponding info accessors */ -# define SYNCTEX_TAG(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_TAG_IDX].INT -# define SYNCTEX_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_LINE_IDX].INT -# define SYNCTEX_COLUMN(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_COLUMN_IDX].INT -# define SYNCTEX_HORIZ(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_IDX].INT -# define SYNCTEX_VERT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_IDX].INT -# define SYNCTEX_WIDTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_IDX].INT -# define SYNCTEX_HEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_IDX].INT -# define SYNCTEX_DEPTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_IDX].INT -# define SYNCTEX_ABS_WIDTH(NODE) ((SYNCTEX_WIDTH(NODE)>0?SYNCTEX_WIDTH(NODE):-SYNCTEX_WIDTH(NODE))) -# define SYNCTEX_ABS_HEIGHT(NODE) ((SYNCTEX_HEIGHT(NODE)>0?SYNCTEX_HEIGHT(NODE):-SYNCTEX_HEIGHT(NODE))) -# define SYNCTEX_ABS_DEPTH(NODE) ((SYNCTEX_DEPTH(NODE)>0?SYNCTEX_DEPTH(NODE):-SYNCTEX_DEPTH(NODE))) - -typedef struct { - synctex_class_t class; - synctex_info_t implementation[5+SYNCTEX_DEPTH_IDX+1]; /* parent,child,sibling,friend,next box, - * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, - * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH */ -} synctex_vert_box_node_t; - -synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner); -void _synctex_log_box(synctex_node_t sheet); -void _synctex_display_vbox(synctex_node_t node); +static synctex_open_s __synctex_open_v2(const char * output, synctex_io_mode_t io_mode, synctex_bool_t add_quotes) { + synctex_open_s open = {SYNCTEX_STATUS_ERROR, NULL, NULL, io_mode}; + char * quoteless_synctex_name = NULL; + const char * mode = _synctex_get_io_mode_name(open.io_mode); + size_t size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; + if (NULL == (open.synctex = (char *)malloc(size))) { + _synctex_error("! __synctex_open_v2: Memory problem (1)\n"); + return open; + } + /* we have reserved for synctex enough memory to copy output (including its 2 eventual quotes), both suffices, + * including the terminating character. size is free now. */ + if (open.synctex != strcpy(open.synctex,output)) { + _synctex_error("! __synctex_open_v2: Copy problem\n"); + return_on_error: + free(open.synctex); + open.synctex = NULL; + free(quoteless_synctex_name);/* We MUST have quoteless_synctex_name<>synctex_name */ + return open; + } + /* remove the last path extension if any */ + _synctex_strip_last_path_extension(open.synctex); + if (!strlen(open.synctex)) { + goto return_on_error; + } + /* now insert quotes. */ + if (add_quotes) { + char * quoted = NULL; + if (_synctex_copy_with_quoting_last_path_component(open.synctex,"ed,size) || quoted == NULL) { + /* There was an error or quoting does not make sense: */ + goto return_on_error; + } + quoteless_synctex_name = open.synctex; + open.synctex = quoted; + } + /* Now add to open.synctex the first path extension. */ + if (open.synctex != strcat(open.synctex,synctex_suffix)){ + _synctex_error("! __synctex_open_v2: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); + goto return_on_error; + } + /* Add to quoteless_synctex_name as well, if relevant. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (open.file = gzopen(open.synctex,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("could not open %s, error %i\n",open.synctex,errno); + goto return_on_error; + } + /* Apparently, there is no uncompressed synctex file. Try the compressed version */ + if (open.synctex != strcat(open.synctex,synctex_suffix_gz)){ + _synctex_error("! __synctex_open_v2: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); + goto return_on_error; + } + open.io_mode |= synctex_io_gz_mask; + mode = _synctex_get_io_mode_name(open.io_mode); /* the file is a compressed and is a binary file, this caused errors on Windows */ + /* Add the suffix to the quoteless_synctex_name as well. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix_gz))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (open.file = gzopen(open.synctex,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("Could not open %s, error %i\n",open.synctex,errno); + } + goto return_on_error; + } + } + /* At this point, the file is properly open. + * If we are in the add_quotes mode, we change the file name by removing the quotes. */ + if (quoteless_synctex_name) { + gzclose(open.file); + if (rename(open.synctex,quoteless_synctex_name)) { + _synctex_error("Could not rename %s to %s, error %i\n",open.synctex,quoteless_synctex_name,errno); + /* We could not rename, reopen the file with the quoted name. */ + if (NULL == (open.file = gzopen(open.synctex,mode))) { + /* No luck, could not re open this file, something has happened meanwhile */ + if (errno != ENOENT) { + /* The file does not exist any more, it has certainly be removed somehow + * this is a lower level error, I can't do anything. */ + _synctex_error("Could not open again %s, error %i\n",open.synctex,errno); + } + goto return_on_error; + } + } else { + /* The file has been successfully renamed */ + if (NULL == (open.file = gzopen(quoteless_synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("Could not open renamed %s, error %i\n",quoteless_synctex_name,errno); + } + goto return_on_error; + } + /* The quote free file name should replace the old one:*/ + free(open.synctex); + open.synctex = quoteless_synctex_name; + quoteless_synctex_name = NULL; + } + } + /* The operation is successfull, return the arguments by value. */ + open.status = SYNCTEX_STATUS_OK; + return open; +} -/* These are static class objects, each scanner will make a copy of them and setup the scanner field. +/* Opens the ouput file, taking into account the eventual build_directory. + * - returns: an open structure which status is + * SYNCTEX_STATUS_OK on success, + * SYNCTEX_STATUS_ERROR on failure. + * - note: on success, the caller is the owner + * of the fields of the returned open structure. */ -static _synctex_class_t synctex_class_vbox = { - NULL, /* No scanner yet */ - synctex_node_type_vbox, /* Node type */ - &_synctex_new_vbox, /* creator */ - &_synctex_free_node, /* destructor */ - &_synctex_log_box, /* log */ - &_synctex_display_vbox, /* display */ - &_synctex_implementation_0, /* parent */ - &_synctex_implementation_1, /* child */ - &_synctex_implementation_2, /* sibling */ - &_synctex_implementation_3, /* friend */ - &_synctex_implementation_4, /* next box */ - (_synctex_info_getter_t)&_synctex_implementation_5 -}; - -/* vertical box node creator */ -synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_vert_box_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_vbox:(synctex_class_t)&synctex_class_vbox; - } - return node; -} - -# define SYNCTEX_HORIZ_V_IDX (SYNCTEX_DEPTH_IDX+1) -# define SYNCTEX_VERT_V_IDX (SYNCTEX_HORIZ_V_IDX+1) -# define SYNCTEX_WIDTH_V_IDX (SYNCTEX_VERT_V_IDX+1) -# define SYNCTEX_HEIGHT_V_IDX (SYNCTEX_WIDTH_V_IDX+1) -# define SYNCTEX_DEPTH_V_IDX (SYNCTEX_HEIGHT_V_IDX+1) -/* the corresponding info accessors */ -# define SYNCTEX_HORIZ_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_V_IDX].INT -# define SYNCTEX_VERT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_V_IDX].INT -# define SYNCTEX_WIDTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_V_IDX].INT -# define SYNCTEX_HEIGHT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_V_IDX].INT -# define SYNCTEX_DEPTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_V_IDX].INT -# define SYNCTEX_ABS_WIDTH_V(NODE) ((SYNCTEX_WIDTH_V(NODE)>0?SYNCTEX_WIDTH_V(NODE):-SYNCTEX_WIDTH_V(NODE))) -# define SYNCTEX_ABS_HEIGHT_V(NODE) ((SYNCTEX_HEIGHT_V(NODE)>0?SYNCTEX_HEIGHT_V(NODE):-SYNCTEX_HEIGHT_V(NODE))) -# define SYNCTEX_ABS_DEPTH_V(NODE) ((SYNCTEX_DEPTH_V(NODE)>0?SYNCTEX_DEPTH_V(NODE):-SYNCTEX_DEPTH_V(NODE))) - -/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness */ -typedef struct { - synctex_class_t class; - synctex_info_t implementation[5+SYNCTEX_DEPTH_V_IDX+1]; /*parent,child,sibling,friend,next box, - * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, - * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH, - * SYNCTEX_HORIZ_V,SYNCTEX_VERT_V,SYNCTEX_WIDTH_V,SYNCTEX_HEIGHT_V,SYNCTEX_DEPTH_V*/ -} synctex_horiz_box_node_t; - -synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner); -void _synctex_display_hbox(synctex_node_t node); -void _synctex_log_horiz_box(synctex_node_t sheet); - - -static _synctex_class_t synctex_class_hbox = { - NULL, /* No scanner yet */ - synctex_node_type_hbox, /* Node type */ - &_synctex_new_hbox, /* creator */ - &_synctex_free_node, /* destructor */ - &_synctex_log_horiz_box, /* log */ - &_synctex_display_hbox, /* display */ - &_synctex_implementation_0, /* parent */ - &_synctex_implementation_1, /* child */ - &_synctex_implementation_2, /* sibling */ - &_synctex_implementation_3, /* friend */ - &_synctex_implementation_4, /* next box */ - (_synctex_info_getter_t)&_synctex_implementation_5 -}; - -/* horizontal box node creator */ -synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_horiz_box_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_hbox:(synctex_class_t)&synctex_class_hbox; - } - return node; +static synctex_open_s _synctex_open_v2(const char * output, const char * build_directory, synctex_io_mode_t io_mode, synctex_bool_t add_quotes) { + synctex_open_s open = __synctex_open_v2(output,io_mode,add_quotes); + if (open.status == SYNCTEX_STATUS_OK) { + return open; + } + if (build_directory && strlen(build_directory)) { + char * build_output; + const char *lpc; + size_t size; + synctex_bool_t is_absolute; + build_output = NULL; + lpc = _synctex_last_path_component(output); + size = strlen(build_directory)+strlen(lpc)+2; /* One for the '/' and one for the '\0'. */ + is_absolute = _synctex_path_is_absolute(build_directory); + if (!is_absolute) { + size += strlen(output); + } + if ((build_output = (char *)_synctex_malloc(size))) { + if (is_absolute) { + build_output[0] = '\0'; + } else { + if (build_output != strcpy(build_output,output)) { + _synctex_free(build_output); + return open; + } + build_output[lpc-output]='\0'; + } + if (build_output == strcat(build_output,build_directory)) { + /* Append a path separator if necessary. */ + if (!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { + if (build_output != strcat(build_output,"/")) { + _synctex_free(build_output); + return open; + } + } + /* Append the last path component of the output. */ + if (build_output != strcat(build_output,lpc)) { + _synctex_free(build_output); + return open; + } + open = __synctex_open_v2(build_output,io_mode,add_quotes); + } + _synctex_free(build_output); + } /* if ((build_output... */ + } /* if (build_directory...) */ + return open; } - -/* This void box node implementation is either horizontal or vertical - * It does not contain a child field. +void synctex_reader_free(synctex_reader_p reader) { + if (reader) { + _synctex_free(reader->output); + _synctex_free(reader->synctex); + _synctex_free(reader->start); + gzclose(reader->file); + _synctex_free(reader); + } +} +/* + * Return reader on success. + * Deallocate reader and return NULL on failure. */ -typedef struct { - synctex_class_t class; - synctex_info_t implementation[3+SYNCTEX_DEPTH_IDX+1]; /* parent,sibling,friend, - * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, - * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH*/ -} synctex_void_box_node_t; - -synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner); -void _synctex_log_void_box(synctex_node_t sheet); -void _synctex_display_void_vbox(synctex_node_t node); - -static _synctex_class_t synctex_class_void_vbox = { - NULL, /* No scanner yet */ - synctex_node_type_void_vbox,/* Node type */ - &_synctex_new_void_vbox, /* creator */ - &_synctex_free_node, /* destructor */ - &_synctex_log_void_box, /* log */ - &_synctex_display_void_vbox,/* display */ - &_synctex_implementation_0, /* parent */ - NULL, /* No child */ - &_synctex_implementation_1, /* sibling */ - &_synctex_implementation_2, /* friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 -}; - -/* vertical void box node creator */ -synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_void_vbox:(synctex_class_t)&synctex_class_void_vbox; - } - return node; -} - -synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner); -void _synctex_display_void_hbox(synctex_node_t node); - -static _synctex_class_t synctex_class_void_hbox = { - NULL, /* No scanner yet */ - synctex_node_type_void_hbox,/* Node type */ - &_synctex_new_void_hbox, /* creator */ - &_synctex_free_node, /* destructor */ - &_synctex_log_void_box, /* log */ - &_synctex_display_void_hbox,/* display */ - &_synctex_implementation_0, /* parent */ - NULL, /* No child */ - &_synctex_implementation_1, /* sibling */ - &_synctex_implementation_2, /* friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 -}; - -/* horizontal void box node creator */ -synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_void_hbox:(synctex_class_t)&synctex_class_void_hbox; - } - return node; +synctex_reader_p synctex_reader_init_with_output_file(synctex_reader_p reader, const char * output, const char * build_directory) { + if (reader) { + /* now open the synctex file */ + synctex_open_s open = _synctex_open_v2(output,build_directory,0,synctex_ADD_QUOTES); + if (open.statussynctex = open.synctex; + reader->file = open.file; + /* make a private copy of output */ + if (NULL == (reader->output = (char *)_synctex_malloc(strlen(output)+1))){ + _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), reader's output is not reliable."); + } else if (reader->output != strcpy(reader->output,output)) { + _synctex_free(reader->output); + reader->output = NULL; + _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, reader's output is not reliable."); + } + reader->start = reader->end = reader->current = NULL; + reader->min_size = SYNCTEX_BUFFER_MIN_SIZE; + reader->size = SYNCTEX_BUFFER_SIZE; + reader->start = reader->current = + (char *)_synctex_malloc(reader->size+1); /* one more character for null termination */ + if (NULL == reader->start) { + _synctex_error("! malloc error in synctex_reader_init_with_output_file."); +#ifdef SYNCTEX_DEBUG + return reader; +#else + synctex_reader_free(reader); + return NULL; +#endif + } + reader->end = reader->start+reader->size; + /* reader->end always points to a null terminating character. + * Maybe there is another null terminating character between reader->current and reader->end-1. + * At least, we are sure that reader->current points to a string covering a valid part of the memory. */ +# if defined(SYNCTEX_USE_CHARINDEX) + reader->charindex_offset = -reader->size; +# endif + } + return reader; } -/* The medium nodes correspond to kern, glue, penalty and math nodes. */ -typedef struct { - synctex_class_t class; - synctex_info_t implementation[3+SYNCTEX_WIDTH_IDX+1]; /* parent,sibling,friend, - * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, - * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH */ -} synctex_medium_node_t; - -#define SYNCTEX_IS_BOX(NODE)\ - ((NODE->class->type == synctex_node_type_vbox)\ - || (NODE->class->type == synctex_node_type_void_vbox)\ - || (NODE->class->type == synctex_node_type_hbox)\ - || (NODE->class->type == synctex_node_type_void_hbox)) - -#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && SYNCTEX_CHILD(NODE)) - -void _synctex_log_medium_node(synctex_node_t node); +# if defined(SYNCTEX_USE_HANDLE) +# define SYNCTEX_DECLARE_HANDLE synctex_node_p handle; +# else +# define SYNCTEX_DECLARE_HANDLE +# endif -/* math node creator */ -synctex_node_t _synctex_new_math(synctex_scanner_t scanner); -void _synctex_display_math(synctex_node_t node); - -static _synctex_class_t synctex_class_math = { - NULL, /* No scanner yet */ - synctex_node_type_math, /* Node type */ - &_synctex_new_math, /* creator */ - &_synctex_free_leaf, /* destructor */ - &_synctex_log_medium_node, /* log */ - &_synctex_display_math, /* display */ - &_synctex_implementation_0, /* parent */ - NULL, /* No child */ - &_synctex_implementation_1, /* sibling */ - &_synctex_implementation_2, /* friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER +# endif +/** + * The synctex scanner is the root object. + * Is is initialized with the contents of a text file or a gzipped file. + * The buffer_.* are first used to parse the text. + */ +struct synctex_scanner_t { + synctex_reader_p reader; + SYNCTEX_DECLARE_NODE_COUNT + SYNCTEX_DECLARE_HANDLE + char * output_fmt; /* dvi or pdf, not yet used */ + synctex_iterator_p iterator;/* result iterator */ + int version; /* 1, not yet used */ + struct { + unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned postamble:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned reserved:sizeof(unsigned)-2; /* alignment */ + } flags; + int pre_magnification; /* magnification from the synctex preamble */ + int pre_unit; /* unit from the synctex preamble */ + int pre_x_offset; /* X offset from the synctex preamble */ + int pre_y_offset; /* Y offset from the synctex preamble */ + int count; /* Number of records, from the synctex postamble */ + float unit; /* real unit, from synctex preamble or post scriptum */ + float x_offset; /* X offset, from synctex preamble or post scriptum */ + float y_offset; /* Y Offset, from synctex preamble or post scriptum */ + synctex_node_p input; /* The first input node, its siblings are the other input nodes */ + synctex_node_p sheet; /* The first sheet node, its siblings are the other sheet nodes */ + synctex_node_p form; /* The first form, its siblings are the other forms */ + synctex_node_p ref_in_sheet; /* The first form ref node in sheet, its friends are the other form ref nodes */ + synctex_node_p ref_in_form; /* The first form ref node, its friends are the other form ref nodes in sheet */ + int number_of_lists; /* The number of friend lists */ + synctex_node_r lists_of_friends;/* The friend lists */ + synctex_class_s class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ + int display_switcher; + char * display_prompt; }; -synctex_node_t _synctex_new_math(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_math:(synctex_class_t)&synctex_class_math; - } - return node; +/** + * Create a new node of the given type. + * - parameter scanner: of type synctex_node_p + * - parameter type: a type, the client is responsible + * to ask for an acceptable type. + */ +synctex_node_p synctex_node_new(synctex_scanner_p scanner, synctex_node_type_t type) { + return scanner? scanner->class[type].new(scanner):NULL; +} +# if defined(SYNCTEX_USE_HANDLE) +SYNCTEX_INLINE static void __synctex_scanner_free_handle(synctex_scanner_p scanner) { + synctex_node_free(scanner->handle); +} +SYNCTEX_INLINE static void __synctex_scanner_remove_handle_to(synctex_node_p node) { + synctex_node_p arg_sibling = NULL; + synctex_node_p handle = node->class->scanner->handle; + while (handle) { + synctex_node_p sibling; + if (node == _synctex_tree_target(handle)) { + sibling = __synctex_tree_reset_sibling(handle); + if (arg_sibling) { + __synctex_tree_set_sibling(arg_sibling, sibling); + } else { + node->class->scanner->handle = sibling; + } + synctex_node_free(handle); + break; + } else { + sibling = __synctex_tree_sibling(handle); + } + arg_sibling = handle; + handle = sibling; + } +} +SYNCTEX_INLINE static void __synctex_scanner_register_handle_to(synctex_node_p node) { + synctex_node_p NNN = _synctex_new_handle_with_target(node); + __synctex_tree_set_sibling(NNN,node->class->scanner->handle); + node->class->scanner->handle = NNN; } +#endif +#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>10 +SYNCTEX_INLINE static void _synctex_did_new(synctex_node_p node) { + printf("NODE CREATED # %i, %s, %p\n", + (node->class->scanner->node_count)++, + synctex_node_isa(node), + node); +} +SYNCTEX_INLINE static void _synctex_will_free(synctex_node_p node) { + printf("NODE DELETED # %i, %s, %p\n", + --(node->class->scanner->node_count), + synctex_node_isa(node), + node); +} +#endif -/* kern node creator */ -synctex_node_t _synctex_new_kern(synctex_scanner_t scanner); -void _synctex_display_kern(synctex_node_t node); - -static _synctex_class_t synctex_class_kern = { - NULL, /* No scanner yet */ - synctex_node_type_kern, /* Node type */ - &_synctex_new_kern, /* creator */ - &_synctex_free_leaf, /* destructor */ - &_synctex_log_medium_node, /* log */ - &_synctex_display_kern, /* display */ - &_synctex_implementation_0, /* parent */ - NULL, /* No child */ - &_synctex_implementation_1, /* sibling */ - &_synctex_implementation_2, /* friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 -}; +/** + * Free the given node. + * - parameter node: of type synctex_node_p + * - note: a node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with children. + */ +static void _synctex_free_node(synctex_node_p node) { + if (node) { + SYNCTEX_SCANNER_REMOVE_HANDLE_TO(node); + SYNCTEX_WILL_FREE(node); + synctex_node_free(__synctex_tree_sibling(node)); + synctex_node_free(_synctex_tree_child(node)); + _synctex_free(node); + } + return; +} -synctex_node_t _synctex_new_kern(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_kern:(synctex_class_t)&synctex_class_kern; - } - return node; +/** + * Free the given leaf node. + * - parameter node: of type synctex_node_p, with no child nor sibling. + * - note: a node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with no children. + */ +static void _synctex_free_leaf(synctex_node_p node) { + if (node) { + SYNCTEX_SCANNER_REMOVE_HANDLE_TO(node); + SYNCTEX_WILL_FREE(node); + synctex_node_free(__synctex_tree_sibling(node)); + _synctex_free(node); + } + return; } -/* The small nodes correspond to glue and boundary nodes. */ -typedef struct { - synctex_class_t class; - synctex_info_t implementation[3+SYNCTEX_VERT_IDX+1]; /* parent,sibling,friend, - * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, - * SYNCTEX_HORIZ,SYNCTEX_VERT */ -} synctex_small_node_t; +/** + SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts + */ +# define SYNCTEX_CUR (scanner->reader->current) +# define SYNCTEX_START (scanner->reader->start) +# define SYNCTEX_END (scanner->reader->end) -void _synctex_log_small_node(synctex_node_t node); -/* glue node creator */ -synctex_node_t _synctex_new_glue(synctex_scanner_t scanner); -void _synctex_display_glue(synctex_node_t node); - -static _synctex_class_t synctex_class_glue = { - NULL, /* No scanner yet */ - synctex_node_type_glue, /* Node type */ - &_synctex_new_glue, /* creator */ - &_synctex_free_leaf, /* destructor */ - &_synctex_log_medium_node, /* log */ - &_synctex_display_glue, /* display */ - &_synctex_implementation_0, /* parent */ - NULL, /* No child */ - &_synctex_implementation_1, /* sibling */ - &_synctex_implementation_2, /* friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 -}; -synctex_node_t _synctex_new_glue(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_glue:(synctex_class_t)&synctex_class_glue; - } - return node; +/* Here are gathered all the possible status that the next scanning functions will return. + * All these functions return a status, and pass their result through pointers. + * Negative values correspond to errors. + * The management of the buffer is causing some significant overhead. + * Every function that may access the buffer returns a status related to the buffer and file state. + * status >= SYNCTEX_STATUS_OK means the function worked as expected + * status < SYNCTEX_STATUS_OK means the function did not work as expected + * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. + * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. + * statushandle:NULL; } +#endif -/* boundary node creator */ -synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner); -void _synctex_display_boundary(synctex_node_t node); - -static _synctex_class_t synctex_class_boundary = { - NULL, /* No scanner yet */ - synctex_node_type_boundary, /* Node type */ - &_synctex_new_boundary, /* creator */ - &_synctex_free_leaf, /* destructor */ - &_synctex_log_small_node, /* log */ - &_synctex_display_boundary, /* display */ - &_synctex_implementation_0, /* parent */ - NULL, /* No child */ - &_synctex_implementation_1, /* sibling */ - &_synctex_implementation_2, /* friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_3 -}; +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Decoding prototypes +# endif -synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_small_node_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_boundary:(synctex_class_t)&synctex_class_boundary; - } - return node; -} +typedef struct { + int integer; + synctex_status_t status; +} synctex_is_s; -# define SYNCTEX_NAME_IDX (SYNCTEX_TAG_IDX+1) -# define SYNCTEX_NAME(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NAME_IDX].PTR +static synctex_is_s _synctex_decode_int(synctex_scanner_p scanner); +static synctex_is_s _synctex_decode_int_opt(synctex_scanner_p scanner, int default_value); +static synctex_is_s _synctex_decode_int_v(synctex_scanner_p scanner); -/* Input nodes only know about their sibling, which is another input node. - * The synctex information is the SYNCTEX_TAG and SYNCTEX_NAME*/ typedef struct { - synctex_class_t class; - synctex_info_t implementation[1+SYNCTEX_NAME_IDX+1]; /* sibling, - * SYNCTEX_TAG,SYNCTEX_NAME */ -} synctex_input_t; - -synctex_node_t _synctex_new_input(synctex_scanner_t scanner); -void _synctex_free_input(synctex_node_t node); -void _synctex_display_input(synctex_node_t node); -void _synctex_log_input(synctex_node_t sheet); - -static _synctex_class_t synctex_class_input = { - NULL, /* No scanner yet */ - synctex_node_type_input, /* Node type */ - &_synctex_new_input, /* creator */ - &_synctex_free_input, /* destructor */ - &_synctex_log_input, /* log */ - &_synctex_display_input, /* display */ - NULL, /* No parent */ - NULL, /* No child */ - &_synctex_implementation_0, /* sibling */ - NULL, /* No friend */ - NULL, /* No next box */ - (_synctex_info_getter_t)&_synctex_implementation_1 -}; + char * string; + synctex_status_t status; +} synctex_ss_s; + +static synctex_ss_s _synctex_decode_string(synctex_scanner_p scanner); -synctex_node_t _synctex_new_input(synctex_scanner_t scanner) { - synctex_node_t node = _synctex_malloc(sizeof(synctex_input_t)); - if (node) { - node->class = scanner?scanner->class+synctex_node_type_input:(synctex_class_t)&synctex_class_input; - } - return node; -} -void _synctex_free_input(synctex_node_t node){ - if (node) { - SYNCTEX_FREE(SYNCTEX_SIBLING(node)); - free(SYNCTEX_NAME(node)); - free(node); - } -} # ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark Navigation +# pragma mark Data SETGET # endif -synctex_node_t synctex_node_parent(synctex_node_t node) -{ - return SYNCTEX_PARENT(node); + +/** + * The next macros are used to access the node data info + * through the class modelator integer fields. + * - parameter NODE: of type synctex_node_p + */ +# define SYNCTEX_DATA(NODE) ((*((((NODE)->class))->info))(NODE)) +#if defined SYNCTEX_DEBUG > 1000 +# define DEFINE_SYNCTEX_DATA_HAS(WHAT) \ +SYNCTEX_INLINE static synctex_bool_t __synctex_data_has_##WHAT(synctex_node_p node) {\ + return (node && (node->class->modelator->WHAT>=0));\ +}\ +SYNCTEX_INLINE static synctex_bool_t _synctex_data_has_##WHAT(synctex_node_p node) {\ + if (node && (node->class->modelator->WHAT<0)) {\ + printf("WARNING: NO %s for %s\n", #WHAT, synctex_node_isa(node));\ + }\ + return __synctex_data_has_##WHAT(node);\ } -synctex_node_t synctex_node_sheet(synctex_node_t node) -{ - while(node && node->class->type != synctex_node_type_sheet) { - node = SYNCTEX_PARENT(node); - } - /* exit the while loop either when node is NULL or node is a sheet */ - return node; +#else +# define DEFINE_SYNCTEX_DATA_HAS(WHAT) \ +SYNCTEX_INLINE static synctex_bool_t __synctex_data_has_##WHAT(synctex_node_p node) {\ + return (node && (node->class->modelator->WHAT>=0));\ +}\ +SYNCTEX_INLINE static synctex_bool_t _synctex_data_has_##WHAT(synctex_node_p node) {\ + return __synctex_data_has_##WHAT(node);\ } -synctex_node_t synctex_node_child(synctex_node_t node) -{ - return SYNCTEX_CHILD(node); +#endif + +SYNCTEX_INLINE static synctex_data_p __synctex_data(synctex_node_p node) { + return node->data+node->class->navigator->size; } -synctex_node_t synctex_node_sibling(synctex_node_t node) -{ - return SYNCTEX_SIBLING(node); -} -synctex_node_t synctex_node_next(synctex_node_t node) { - if (SYNCTEX_CHILD(node)) { - return SYNCTEX_CHILD(node); - } -sibling: - if (SYNCTEX_SIBLING(node)) { - return SYNCTEX_SIBLING(node); - } - if ((node = SYNCTEX_PARENT(node))) { - if (node->class->type == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ - return NULL; - } - goto sibling; - } - return NULL; +# define DEFINE_SYNCTEX_DATA_INT_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_HAS(WHAT)\ +static int _synctex_data_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + return __synctex_data(node)[node->class->modelator->WHAT].as_integer;\ + }\ + return 0;\ +}\ +static int _synctex_data_set_##WHAT(synctex_node_p node, int new_value) {\ + int old = 0;\ + if (_synctex_data_has_##WHAT(node)) {\ + old = __synctex_data(node)[node->class->modelator->WHAT].as_integer;\ + __synctex_data(node)[node->class->modelator->WHAT].as_integer=new_value;\ + }\ + return old;\ } -# ifdef SYNCTEX_NOTHING -# pragma mark - -# pragma mark CLASS -# endif - -/* Public node accessor: the type */ -synctex_node_type_t synctex_node_type(synctex_node_t node) { - if (node) { - return (((node)->class))->type; - } - return synctex_node_type_error; +#define DEFINE_SYNCTEX_DATA_INT_DECODE(WHAT) \ +static synctex_status_t _synctex_data_decode_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + synctex_is_s is = _synctex_decode_int(node->class->scanner);\ + if (is.status == SYNCTEX_STATUS_OK) {\ + _synctex_data_set_##WHAT(node,is.integer);\ + } \ + return is.status;\ + }\ + return SYNCTEX_STATUS_BAD_ARGUMENT;\ } - -/* Public node accessor: the human readable type */ -const char * synctex_node_isa(synctex_node_t node) { -static const char * isa[synctex_node_number_of_types] = - {"Not a node","input","sheet","vbox","void vbox","hbox","void hbox","kern","glue","math","boundary"}; - return isa[synctex_node_type(node)]; +# define DEFINE_SYNCTEX_DATA_INT_DECODE_v(WHAT) \ +static synctex_status_t _synctex_data_decode_##WHAT##_v(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + synctex_is_s is = _synctex_decode_int_v(node->class->scanner);\ + if (is.status == SYNCTEX_STATUS_OK) {\ + _synctex_data_set_##WHAT(node,is.integer);\ + } \ + return is.status;\ + }\ + return SYNCTEX_STATUS_BAD_ARGUMENT;\ } +#define DEFINE_SYNCTEX_DATA_STR_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_HAS(WHAT)\ +static char * _synctex_data_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + return node->data[node->class->navigator->size+node->class->modelator->WHAT].as_string;\ + }\ + return NULL;\ +}\ +static char * _synctex_data_set_##WHAT(synctex_node_p node, char * new_value) {\ + char * old = "";\ + if (_synctex_data_has_##WHAT(node)) {\ + old = node->data[node->class->navigator->size+node->class->modelator->WHAT].as_string;\ + node->data[node->class->navigator->size+node->class->modelator->WHAT].as_string =new_value;\ + }\ + return old;\ +} +#define DEFINE_SYNCTEX_DATA_STR_DECODE(WHAT) \ +static synctex_status_t _synctex_data_decode_##WHAT(synctex_node_p node) {\ + if (_synctex_data_has_##WHAT(node)) {\ + synctex_ss_s ss = _synctex_decode_string(node->class->scanner);\ + if (ss.status == SYNCTEX_STATUS_OK) {\ + _synctex_data_set_##WHAT(node,ss.string);\ + } \ + return ss.status;\ + }\ + return SYNCTEX_STATUS_BAD_ARGUMENT;\ +} +#define DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_DECODE(WHAT) +#define DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE_v(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_INT_DECODE_v(WHAT) +#define DEFINE_SYNCTEX_DATA_STR_GETSET_DECODE(WHAT) \ +DEFINE_SYNCTEX_DATA_STR_GETSET(WHAT) \ +DEFINE_SYNCTEX_DATA_STR_DECODE(WHAT) # ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark SYNCTEX_LOG +# pragma mark OBJECTS, their creators and destructors. # endif -# define SYNCTEX_LOG(NODE) SYNCTEX_MSG_SEND(NODE,log) +# ifdef SYNCTEX_NOTHING +# pragma mark input. +# endif -/* Public node logger */ -void synctex_node_log(synctex_node_t node) { - SYNCTEX_LOG(node); -} - -# define SYNCTEX_DISPLAY(NODE) SYNCTEX_MSG_SEND(NODE,display) - -void synctex_node_display(synctex_node_t node) { - SYNCTEX_DISPLAY(node); -} - -void _synctex_display_input(synctex_node_t node) { - printf("....Input:%i:%s\n", - SYNCTEX_TAG(node), - SYNCTEX_NAME(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_log_sheet(synctex_node_t sheet) { - if (sheet) { - printf("%s:%i\n",synctex_node_isa(sheet),SYNCTEX_PAGE(sheet)); - printf("SELF:%p",(void *)sheet); - printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(sheet)); - printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(sheet)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(sheet)); - printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(sheet)); - } -} - -void _synctex_log_small_node(synctex_node_t node) { - printf("%s:%i,%i:%i,%i\n", - synctex_node_isa(node), - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node)); - printf("SELF:%p",(void *)node); - printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); - printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); - printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); -} - -void _synctex_log_medium_node(synctex_node_t node) { - printf("%s:%i,%i:%i,%i:%i\n", - synctex_node_isa(node), - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node), - SYNCTEX_WIDTH(node)); - printf("SELF:%p",(void *)node); - printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); - printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); - printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); -} - -void _synctex_log_void_box(synctex_node_t node) { - printf("%s",synctex_node_isa(node)); - printf(":%i",SYNCTEX_TAG(node)); - printf(",%i",SYNCTEX_LINE(node)); - printf(",%i",0); - printf(":%i",SYNCTEX_HORIZ(node)); - printf(",%i",SYNCTEX_VERT(node)); - printf(":%i",SYNCTEX_WIDTH(node)); - printf(",%i",SYNCTEX_HEIGHT(node)); - printf(",%i",SYNCTEX_DEPTH(node)); - printf("\nSELF:%p",(void *)node); - printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); - printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); - printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); -} - -void _synctex_log_box(synctex_node_t node) { - printf("%s",synctex_node_isa(node)); - printf(":%i",SYNCTEX_TAG(node)); - printf(",%i",SYNCTEX_LINE(node)); - printf(",%i",0); - printf(":%i",SYNCTEX_HORIZ(node)); - printf(",%i",SYNCTEX_VERT(node)); - printf(":%i",SYNCTEX_WIDTH(node)); - printf(",%i",SYNCTEX_HEIGHT(node)); - printf(",%i",SYNCTEX_DEPTH(node)); - printf("\nSELF:%p",(void *)node); - printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); - printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); - printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); -} - -void _synctex_log_horiz_box(synctex_node_t node) { - printf("%s",synctex_node_isa(node)); - printf(":%i",SYNCTEX_TAG(node)); - printf(",%i",SYNCTEX_LINE(node)); - printf(",%i",0); - printf(":%i",SYNCTEX_HORIZ(node)); - printf(",%i",SYNCTEX_VERT(node)); - printf(":%i",SYNCTEX_WIDTH(node)); - printf(",%i",SYNCTEX_HEIGHT(node)); - printf(",%i",SYNCTEX_DEPTH(node)); - printf("/%i",SYNCTEX_HORIZ_V(node)); - printf(",%i",SYNCTEX_VERT_V(node)); - printf(":%i",SYNCTEX_WIDTH_V(node)); - printf(",%i",SYNCTEX_HEIGHT_V(node)); - printf(",%i",SYNCTEX_DEPTH_V(node)); - printf("\nSELF:%p",(void *)node); - printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); - printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); - printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); -} - -void _synctex_log_input(synctex_node_t node) { - printf("%s",synctex_node_isa(node)); - printf(":%i",SYNCTEX_TAG(node)); - printf(",%s",SYNCTEX_NAME(node)); - printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); -} - -void _synctex_display_sheet(synctex_node_t sheet) { - if (sheet) { - printf("....{%i\n",SYNCTEX_PAGE(sheet)); - SYNCTEX_DISPLAY(SYNCTEX_CHILD(sheet)); - printf("....}\n"); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(sheet)); - } -} - -void _synctex_display_vbox(synctex_node_t node) { - printf("....[%i,%i:%i,%i:%i,%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node), - SYNCTEX_WIDTH(node), - SYNCTEX_HEIGHT(node), - SYNCTEX_DEPTH(node)); - SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); - printf("....]\n"); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_hbox(synctex_node_t node) { - printf("....(%i,%i:%i,%i:%i,%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node), - SYNCTEX_WIDTH(node), - SYNCTEX_HEIGHT(node), - SYNCTEX_DEPTH(node)); - SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); - printf("....)\n"); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_void_vbox(synctex_node_t node) { - printf("....v%i,%i;%i,%i:%i,%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node), - SYNCTEX_WIDTH(node), - SYNCTEX_HEIGHT(node), - SYNCTEX_DEPTH(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_void_hbox(synctex_node_t node) { - printf("....h%i,%i:%i,%i:%i,%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node), - SYNCTEX_WIDTH(node), - SYNCTEX_HEIGHT(node), - SYNCTEX_DEPTH(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_glue(synctex_node_t node) { - printf("....glue:%i,%i:%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_math(synctex_node_t node) { - printf("....math:%i,%i:%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_kern(synctex_node_t node) { - printf("....kern:%i,%i:%i,%i:%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node), - SYNCTEX_WIDTH(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} - -void _synctex_display_boundary(synctex_node_t node) { - printf("....boundary:%i,%i:%i,%i\n", - SYNCTEX_TAG(node), - SYNCTEX_LINE(node), - SYNCTEX_HORIZ(node), - SYNCTEX_VERT(node)); - SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); -} +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(tag) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(line) +DEFINE_SYNCTEX_DATA_STR_GETSET_DECODE(name) -# ifdef SYNCTEX_NOTHING +/* Input nodes only know about their sibling, which is another input node. + * The synctex information is the _synctex_data_tag and _synctex_data_name + * note: the input owns its name. */ + +# define SYNCTEX_INPUT_MARK "Input:" + +static const synctex_tree_model_s synctex_tree_model_input = { + synctex_tree_sibling_idx, /* sibling */ + -1, /* parent */ + -1, /* child */ + -1, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_s_input_max +}; +static const synctex_data_model_s synctex_data_model_input = { + synctex_data_input_tag_idx, /* tag */ + synctex_data_input_line_idx,/* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + synctex_data_input_name_idx, /* name */ + -1, /* page */ + synctex_data_input_tln_max +}; + +#define SYNCTEX_INSPECTOR_GETTER_F(WHAT)\ +&_synctex_data_##WHAT, &_synctex_data_set_##WHAT + +static synctex_node_p _synctex_new_input(synctex_scanner_p scanner); +static void _synctex_free_input(synctex_node_p node); +static void _synctex_log_input(synctex_node_p node); +static char * _synctex_abstract_input(synctex_node_p node); +static void _synctex_display_input(synctex_node_p node); + +static const synctex_tlcpector_s synctex_tlcpector_input = { + &_synctex_data_tag, /* tag */ + &_synctex_int_none, /* line */ + &_synctex_int_none, /* column */ +}; + +static synctex_class_s synctex_class_input = { + NULL, /* No scanner yet */ + synctex_node_type_input, /* Node type */ + &_synctex_new_input, /* creator */ + &_synctex_free_input, /* destructor */ + &_synctex_log_input, /* log */ + &_synctex_display_input, /* display */ + &_synctex_abstract_input, /* abstract */ + &synctex_tree_model_input, /* tree model */ + &synctex_data_model_input, /* data model */ + &synctex_tlcpector_input, /* inspector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_s_input_max+synctex_data_input_tln_max]; +} synctex_input_s; + +static synctex_node_p _synctex_new_input(synctex_scanner_p scanner) { + if (scanner) { + synctex_node_p node = _synctex_malloc(sizeof(synctex_input_s)); + if (node) { + node->class = scanner->class+synctex_node_type_input; + SYNCTEX_DID_NEW(node); + SYNCTEX_IMPLEMENT_CHARINDEX(node,0); + SYNCTEX_REGISTER_HANDLE_TO(node); + } + return node; + } + return NULL; +} + +static void _synctex_free_input(synctex_node_p node){ + if (node) { + SYNCTEX_SCANNER_REMOVE_HANDLE_TO(node); + SYNCTEX_WILL_FREE(node); + synctex_node_free(__synctex_tree_sibling(node)); + _synctex_free(_synctex_data_name(node)); + _synctex_free(node); + } +} + +/* The sheet is a first level node. + * It has no parent (the owner is the scanner itself) + * Its sibling points to another sheet. + * Its child points to its first child, in general a box. + * A sheet node contains only one synctex information: the page. + * This is the 1 based page index as given by TeX. + */ + +# ifdef SYNCTEX_NOTHING +# pragma mark sheet. +# endif +/** + * Every node has the same structure, but not the same size. + */ + +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(page) + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_scn_sheet_max+synctex_data_p_sheet_max]; +} synctex_node_sheet_s; + +/* sheet node creator */ + +#define DEFINE_synctex_new_scanned_NODE(NAME)\ +static synctex_node_p _synctex_new_##NAME(synctex_scanner_p scanner) {\ + if (scanner) {\ + ++SYNCTEX_CUR;\ + synctex_node_p node = _synctex_malloc(sizeof(synctex_node_##NAME##_s));\ + if (node) {\ + node->class = scanner->class+synctex_node_type_##NAME;\ + SYNCTEX_DID_NEW(node); \ + SYNCTEX_IMPLEMENT_CHARINDEX(node,-1);\ + SYNCTEX_REGISTER_HANDLE_TO(node); \ + }\ + return node;\ + }\ + return NULL;\ +} +/* NB: -1 in SYNCTEX_IMPLEMENT_CHARINDEX above because + * the first char of the line has been scanned + */ +DEFINE_synctex_new_scanned_NODE(sheet) +static void _synctex_log_sheet(synctex_node_p node); +static char * _synctex_abstract_sheet(synctex_node_p node); +static void _synctex_display_sheet(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_sheet = { + synctex_tree_sibling_idx, /* sibling */ + -1, /* parent */ + synctex_tree_s_child_idx, /* child */ + -1, /* friend */ + -1, /* last */ + synctex_tree_sc_next_hbox_idx, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_scn_sheet_max +}; +static const synctex_data_model_s synctex_data_model_sheet = { + -1, /* tag */ + -1, /* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + synctex_data_sheet_page_idx, /* page */ + synctex_data_p_sheet_max +}; +static synctex_class_s synctex_class_sheet = { + NULL, /* No scanner yet */ + synctex_node_type_sheet, /* Node type */ + &_synctex_new_sheet, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_sheet, /* log */ + &_synctex_display_sheet, /* display */ + &_synctex_abstract_sheet, /* abstract */ + &synctex_tree_model_sheet, /* tree model */ + &synctex_data_model_sheet, /* data model */ + &synctex_tlcpector_none, /* tlcpector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark form. +# endif +/** + * Every node has the same structure, but not the same size. + */ +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_sct_form_max+synctex_data_t_form_max]; +} synctex_node_form_s; + +DEFINE_synctex_new_scanned_NODE(form) + +static char * _synctex_abstract_form(synctex_node_p node); +static void _synctex_display_form(synctex_node_p node); +static void _synctex_log_form(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_form = { + synctex_tree_sibling_idx, /* sibling */ + -1, /* parent */ + synctex_tree_s_child_idx, /* child */ + -1, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_sc_target_idx, /* target */ + synctex_tree_sct_form_max +}; +static const synctex_data_model_s synctex_data_model_form = { + synctex_data_form_tag_idx, /* tag */ + -1, /* line */ + -1, /* column */ + -1, /* h */ + -1, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_t_form_max +}; +static synctex_class_s synctex_class_form = { + NULL, /* No scanner yet */ + synctex_node_type_form, /* Node type */ + &_synctex_new_form, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_form, /* log */ + &_synctex_display_form, /* display */ + &_synctex_abstract_form, /* abstract */ + &synctex_tree_model_form, /* tree model */ + &synctex_data_model_form, /* data model */ + &synctex_tlcpector_none, /* tlcpector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark vbox. +# endif + +/* A box node contains navigation and synctex information + * There are different kinds of boxes. + * Only horizontal boxes are treated differently because of their visible size. + */ +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spcfl_vbox_max+synctex_data_box_max]; +} synctex_node_vbox_s; + +/* vertical box node creator */ +DEFINE_synctex_new_scanned_NODE(vbox) + +static char * _synctex_abstract_vbox(synctex_node_p node); +static void _synctex_display_vbox(synctex_node_p node); +static void _synctex_log_vbox(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_vbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_spcfl_vbox_max +}; + +DEFINE_SYNCTEX_DATA_INT_GETSET(column) +static synctex_status_t _synctex_data_decode_column(synctex_node_p node) { + if (_synctex_data_has_column(node)) { + synctex_is_s is = _synctex_decode_int_opt(node->class->scanner,-1); + if (is.status == SYNCTEX_STATUS_OK) { + _synctex_data_set_column(node,is.integer); + } + return is.status; + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(h) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE_v(v) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(width) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(height) +DEFINE_SYNCTEX_DATA_INT_GETSET_DECODE(depth) + +static const synctex_data_model_s synctex_data_model_box = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx,/* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + synctex_data_width_idx, /* width */ + synctex_data_height_idx,/* height */ + synctex_data_depth_idx, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_box_max +}; +static const synctex_tlcpector_s synctex_tlcpector_default = { + &_synctex_data_tag, /* tag */ + &_synctex_data_line, /* line */ + &_synctex_data_column, /* column */ +}; +static const synctex_inspector_s synctex_inspector_box = { + &_synctex_data_h, + &_synctex_data_v, + &_synctex_data_width, + &_synctex_data_height, + &_synctex_data_depth, +}; +static float __synctex_node_visible_h(synctex_node_p node); +static float __synctex_node_visible_v(synctex_node_p node); +static float __synctex_node_visible_width(synctex_node_p node); +static float __synctex_node_visible_height(synctex_node_p node); +static float __synctex_node_visible_depth(synctex_node_p node); +static synctex_vispector_s synctex_vispector_box = { + &__synctex_node_visible_h, + &__synctex_node_visible_v, + &__synctex_node_visible_width, + &__synctex_node_visible_height, + &__synctex_node_visible_depth, +}; +/* These are static class objects, each scanner will make a copy of them and setup the scanner field. + */ +static synctex_class_s synctex_class_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_vbox, /* Node type */ + &_synctex_new_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_vbox, /* log */ + &_synctex_display_vbox, /* display */ + &_synctex_abstract_vbox, /* abstract */ + &synctex_tree_model_vbox, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark hbox. +# endif + +/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness. + * They also contain an average of the line numbers of the containing nodes. */ + +static const synctex_tree_model_s synctex_tree_model_hbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + synctex_tree_spcfl_next_hbox_idx, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_spcfln_hbox_max +}; + +DEFINE_SYNCTEX_DATA_INT_GETSET(mean_line) +DEFINE_SYNCTEX_DATA_INT_GETSET(weight) +DEFINE_SYNCTEX_DATA_INT_GETSET(h_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(v_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(width_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(height_V) +DEFINE_SYNCTEX_DATA_INT_GETSET(depth_V) + +/** + * The hbox model. + * It contains V variants of geometrical information. + * It happens that hboxes contain material that is not used to compute + * the bounding box. Some letters may appear out of the box given by TeX. + * In such a situation, the visible bouding box is bigger ence the V variant. + * Only hboxes have such variant. It does not make sense for void boxes + * and it is not used here for vboxes. + * - author: JL + */ + +static const synctex_data_model_s synctex_data_model_hbox = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx,/* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + synctex_data_width_idx, /* width */ + synctex_data_height_idx,/* height */ + synctex_data_depth_idx, /* depth */ + synctex_data_mean_line_idx, /* mean_line */ + synctex_data_weight_idx, /* weight */ + synctex_data_h_V_idx, /* h_V */ + synctex_data_v_V_idx, /* v_V */ + synctex_data_width_V_idx, /* width_V */ + synctex_data_height_V_idx, /* height_V */ + synctex_data_depth_V_idx, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_hbox_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spcfln_hbox_max+synctex_data_hbox_max]; +} synctex_node_hbox_s; + +/* horizontal box node creator */ +DEFINE_synctex_new_scanned_NODE(hbox) + +static void _synctex_log_hbox(synctex_node_p node); +static char * _synctex_abstract_hbox(synctex_node_p node); +static void _synctex_display_hbox(synctex_node_p node); + +static synctex_class_s synctex_class_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_hbox, /* Node type */ + &_synctex_new_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_hbox, /* log */ + &_synctex_display_hbox, /* display */ + &_synctex_abstract_hbox, /* abstract */ + &synctex_tree_model_hbox, /* tree model */ + &synctex_data_model_hbox, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark void vbox. +# endif + +/* This void box node implementation is either horizontal or vertical + * It does not contain a child field. + */ +static const synctex_tree_model_s synctex_tree_model_spf = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + -1, /* target */ + synctex_tree_spf_max +}; +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spf_max+synctex_data_box_max]; +} synctex_node_void_vbox_s; + +/* vertical void box node creator */ +DEFINE_synctex_new_scanned_NODE(void_vbox) + +static void _synctex_log_void_box(synctex_node_p node); +static char * _synctex_abstract_void_vbox(synctex_node_p node); +static void _synctex_display_void_vbox(synctex_node_p node); + +static synctex_class_s synctex_class_void_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_vbox,/* Node type */ + &_synctex_new_void_vbox, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_vbox,/* display */ + &_synctex_abstract_void_vbox,/* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark void hbox. +# endif + +typedef synctex_node_void_vbox_s synctex_node_void_hbox_s; + +/* horizontal void box node creator */ +DEFINE_synctex_new_scanned_NODE(void_hbox) + +static char * _synctex_abstract_void_hbox(synctex_node_p node); +static void _synctex_display_void_hbox(synctex_node_p node); + +static synctex_class_s synctex_class_void_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_hbox,/* Node type */ + &_synctex_new_void_hbox, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_hbox,/* display */ + &_synctex_abstract_void_hbox,/* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark form ref. +# endif + +/* The form ref node. */ +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spfa_max+synctex_data_ref_thv_max]; +} synctex_node_ref_s; + +/* form ref node creator */ +DEFINE_synctex_new_scanned_NODE(ref) + +static void _synctex_log_ref(synctex_node_p node); +static char * _synctex_abstract_ref(synctex_node_p node); +static void _synctex_display_ref(synctex_node_p node); + +static const synctex_tree_model_s synctex_tree_model_spfa = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + synctex_tree_spf_arg_sibling_idx, /* arg_sibling */ + -1, /* target */ + synctex_tree_spfa_max +}; +static const synctex_data_model_s synctex_data_model_ref = { + synctex_data_tag_idx, /* tag */ + -1, /* line */ + -1, /* column */ + synctex_data_ref_h_idx, /* h */ + synctex_data_ref_v_idx, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + synctex_data_ref_thv_max +}; +static synctex_class_s synctex_class_ref = { + NULL, /* No scanner yet */ + synctex_node_type_ref, /* Node type */ + &_synctex_new_ref, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_ref, /* log */ + &_synctex_display_ref, /* display */ + &_synctex_abstract_ref, /* abstract */ + &synctex_tree_model_spfa, /* navigator */ + &synctex_data_model_ref, /* data model */ + &synctex_tlcpector_none, /* tlcpector */ + &synctex_inspector_none, /* inspector */ + &synctex_vispector_none, /* vispector */ +}; +# ifdef SYNCTEX_NOTHING +# pragma mark small node. +# endif + +/* The small nodes correspond to glue, penalty, math and boundary nodes. */ +static const synctex_data_model_s synctex_data_model_tlchv = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx, /* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_tlchv_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spf_max+synctex_data_tlchv_max]; +} synctex_node_tlchv_s; + +static void _synctex_log_tlchv_node(synctex_node_p node); + +# ifdef SYNCTEX_NOTHING +# pragma mark math. +# endif + +typedef synctex_node_tlchv_s synctex_node_math_s; + +/* math node creator */ +DEFINE_synctex_new_scanned_NODE(math) + +static char * _synctex_abstract_math(synctex_node_p node); +static void _synctex_display_math(synctex_node_p node); +static synctex_inspector_s synctex_inspector_hv = { + &_synctex_data_h, + &_synctex_data_v, + &_synctex_int_none, + &_synctex_int_none, + &_synctex_int_none, +}; +static synctex_vispector_s synctex_vispector_hv = { + &__synctex_node_visible_h, + &__synctex_node_visible_v, + &_synctex_float_none, + &_synctex_float_none, + &_synctex_float_none, +}; + +static synctex_class_s synctex_class_math = { + NULL, /* No scanner yet */ + synctex_node_type_math, /* Node type */ + &_synctex_new_math, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_math, /* display */ + &_synctex_abstract_math, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark kern node. +# endif + +static const synctex_data_model_s synctex_data_model_tlchvw = { + synctex_data_tag_idx, /* tag */ + synctex_data_line_idx, /* line */ + synctex_data_column_idx,/* column */ + synctex_data_h_idx, /* h */ + synctex_data_v_idx, /* v */ + synctex_data_width_idx, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_tlchvw_max +}; +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spf_max+synctex_data_tlchvw_max]; +} synctex_node_kern_s; + +/* kern node creator */ +DEFINE_synctex_new_scanned_NODE(kern) + +static void _synctex_log_kern_node(synctex_node_p node); +static char * _synctex_abstract_kern(synctex_node_p node); +static void _synctex_display_kern(synctex_node_p node); + +static synctex_inspector_s synctex_inspector_kern = { + &_synctex_data_h, + &_synctex_data_v, + &_synctex_data_width, + &_synctex_int_none, + &_synctex_int_none, +}; +static float __synctex_kern_visible_h(synctex_node_p node); +static float __synctex_kern_visible_width(synctex_node_p node); +static synctex_vispector_s synctex_vispector_kern = { + &__synctex_kern_visible_h, + &__synctex_node_visible_v, + &__synctex_kern_visible_width, + &_synctex_float_none, + &_synctex_float_none, +}; + +static synctex_class_s synctex_class_kern = { + NULL, /* No scanner yet */ + synctex_node_type_kern, /* Node type */ + &_synctex_new_kern, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_kern_node, /* log */ + &_synctex_display_kern, /* display */ + &_synctex_abstract_kern, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchvw, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_kern, /* inspector */ + &synctex_vispector_kern, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark glue. +# endif + +/* glue node creator */ +typedef synctex_node_tlchv_s synctex_node_glue_s; +DEFINE_synctex_new_scanned_NODE(glue) + +static char * _synctex_abstract_glue(synctex_node_p node); +static void _synctex_display_glue(synctex_node_p node); + +static synctex_class_s synctex_class_glue = { + NULL, /* No scanner yet */ + synctex_node_type_glue, /* Node type */ + &_synctex_new_glue, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_glue, /* display */ + &_synctex_abstract_glue, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +/* The small nodes correspond to glue and boundary nodes. */ + +# ifdef SYNCTEX_NOTHING +# pragma mark rule. +# endif + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spf_max+synctex_data_box_max]; +} synctex_node_rule_s; + +DEFINE_synctex_new_scanned_NODE(rule) + +static void _synctex_log_rule(synctex_node_p node); +static char * _synctex_abstract_rule(synctex_node_p node); +static void _synctex_display_rule(synctex_node_p node); + +static float __synctex_rule_visible_h(synctex_node_p node); +static float __synctex_rule_visible_v(synctex_node_p node); +static float __synctex_rule_visible_width(synctex_node_p node); +static float __synctex_rule_visible_height(synctex_node_p node); +static float __synctex_rule_visible_depth(synctex_node_p node); +static synctex_vispector_s synctex_vispector_rule = { + &__synctex_rule_visible_h, + &__synctex_rule_visible_v, + &__synctex_rule_visible_width, + &__synctex_rule_visible_height, + &__synctex_rule_visible_depth, +}; + +static synctex_class_s synctex_class_rule = { + NULL, /* No scanner yet */ + synctex_node_type_rule, /* Node type */ + &_synctex_new_rule, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_rule, /* log */ + &_synctex_display_rule, /* display */ + &_synctex_abstract_rule, /* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_box, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_box, /* inspector */ + &synctex_vispector_rule, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark boundary. +# endif + +/* boundary node creator */ +typedef synctex_node_tlchv_s synctex_node_boundary_s; +DEFINE_synctex_new_scanned_NODE(boundary) + +static char * _synctex_abstract_boundary(synctex_node_p node); +static void _synctex_display_boundary(synctex_node_p node); + +static synctex_class_s synctex_class_boundary = { + NULL, /* No scanner yet */ + synctex_node_type_boundary, /* Node type */ + &_synctex_new_boundary, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_boundary, /* display */ + &_synctex_abstract_boundary,/* abstract */ + &synctex_tree_model_spf, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark box boundary. +# endif + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spfa_max+synctex_data_tlchv_max]; +} synctex_node_box_bdry_s; + +#define DEFINE_synctex_new_unscanned_NODE(NAME)\ +SYNCTEX_INLINE static synctex_node_p _synctex_new_##NAME(synctex_scanner_p scanner) {\ + if (scanner) {\ + synctex_node_p node = _synctex_malloc(sizeof(synctex_node_##NAME##_s));\ + if (node) {\ + node->class = scanner->class+synctex_node_type_##NAME;\ + SYNCTEX_DID_NEW(node); \ + }\ + return node;\ + }\ + return NULL;\ +} +DEFINE_synctex_new_unscanned_NODE(box_bdry) + +static char * _synctex_abstract_box_bdry(synctex_node_p node); +static void _synctex_display_box_bdry(synctex_node_p node); + +static synctex_class_s synctex_class_box_bdry = { + NULL, /* No scanner yet */ + synctex_node_type_box_bdry, /* Node type */ + &_synctex_new_box_bdry, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_tlchv_node, /* log */ + &_synctex_display_box_bdry, /* display */ + &_synctex_abstract_box_bdry,/* display */ + &synctex_tree_model_spfa, /* tree model */ + &synctex_data_model_tlchv, /* data model */ + &synctex_tlcpector_default, /* tlcpector */ + &synctex_inspector_hv, /* inspector */ + &synctex_vispector_hv, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark hbox proxy. +# endif + +/** + * Standard nodes refer to TeX nodes: math, kern, boxes... + * Proxy nodes are used to support forms. + * A form is parsed as a tree of standard nodes starting + * at the top left position. + * When a reference is used, the form is duplicated + * to the location specified by the reference. + * As the same form can be duplicated at different locations, + * the geometrical information is relative to its own top left point. + * As we need absolute locations, we use proxy nodes. + * A proxy node records an offset and the target node. + * The target partly acts as a delegate. + * The h and v position of the proxy node is the h and v + * position of the target shifted by the proxy's offset. + * The width, height and depth are not sensitive to offsets. + * When are proxies created ? + * 1) when the synctex file has been parsed, all the form refs + * are replaced by proxies to the content of a form. + * This content is a node with siblings (actually none). + * Those root proxies have the parent of the ref they replace, + * so their parents exist and are no proxy. + * Moreover, if they have no sibling, it means that their target have no + * sibling as well. + * Such nodes are called root proxies. + * 2) On the fly, when a proxy is asked for its child + * (or sibling) and has none, a proxy to its target's child + * (or sibling) is created if any. There are only 2 possible situations: + * either the newly created proxy is the child of a proxy, + * or it is the sibling of a proxy created on the fly. + * In both cases, the parent is a proxy with children. + * Such nodes are called child proxies. + * How to compute the offset of a proxy ? + * The offset of root proxy objects is exactly + * the offset of the ref they replace. + * The offset of other proxies is their owner's, + * except when pointing to a root proxy. + * What happens for cascading forms ? + * Here is an example diagram + * + * At parse time, the arrow means "owns": + * sheet0 -> ref_to1 + * + * target1 -> ref_to2 + * + * target2 -> child22 + * + * After replacing the refs: + * sheet0 -> proxy00 -> proxy01 -> proxy02 + * | | | + * target1 -> proxy11 -> proxy12 + * | | + * target2 -> proxy22 + * + * proxy00, proxy11 and proxy22 are root proxies. + * Their offset is the one of the ref they replace + * proxy01, proxy02 and proxy12 are child proxies. + * Their proxy is the one of their parent. + * Optimization. + * After all the refs are replaced, there are only root nodes + * targeting standard node. We make sure that each child proxy + * also targets a standard node. + * It is possible for a proxy to have a standard sibling + * whereas its target has no sibling at all. Root proxies + * are such nodes, and are the only ones. + * The consequence is that proxies created on the fly + * must take into account this situation. + */ + +/* A proxy to a hbox. + * A proxy do have a target, which can be a proxy + */ + +static const synctex_tree_model_s synctex_tree_model_proxy_hbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + synctex_tree_spcfl_next_hbox_idx, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spcfln_target_idx, /* target */ + synctex_tree_spcflnt_proxy_hbox_max +}; +static const synctex_data_model_s synctex_data_model_proxy = { + -1, /* tag */ + -1, /* line */ + -1, /* column */ + synctex_data_proxy_h_idx, /* h */ + synctex_data_proxy_v_idx, /* v */ + -1, /* width */ + -1, /* height */ + -1, /* depth */ + -1, /* mean_line */ + -1, /* weight */ + -1, /* h_V */ + -1, /* v_V */ + -1, /* width_V */ + -1, /* height_V */ + -1, /* depth_V */ + -1, /* name */ + -1, /* page */ + synctex_data_proxy_hv_max +}; +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spcflnt_proxy_hbox_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_hbox_s; + +/* box proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy_hbox) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy_hbox(synctex_node_p node); +static void _synctex_display_proxy_hbox(synctex_node_p node); + +static int _synctex_proxy_tag(synctex_node_p); +static int _synctex_proxy_line(synctex_node_p); +static int _synctex_proxy_column(synctex_node_p); + +static synctex_tlcpector_s synctex_tlcpector_proxy = { + &_synctex_proxy_tag, + &_synctex_proxy_line, + &_synctex_proxy_column, +}; +static int _synctex_proxy_h(synctex_node_p); +static int _synctex_proxy_v(synctex_node_p); +static int _synctex_proxy_width(synctex_node_p); +static int _synctex_proxy_height(synctex_node_p); +static int _synctex_proxy_depth(synctex_node_p); +static synctex_inspector_s synctex_inspector_proxy_box = { + &_synctex_proxy_h, + &_synctex_proxy_v, + &_synctex_proxy_width, + &_synctex_proxy_height, + &_synctex_proxy_depth, +}; + +static float __synctex_proxy_visible_h(synctex_node_p); +static float __synctex_proxy_visible_v(synctex_node_p); +static float __synctex_proxy_visible_width(synctex_node_p); +static float __synctex_proxy_visible_height(synctex_node_p); +static float __synctex_proxy_visible_depth(synctex_node_p); + +static synctex_vispector_s synctex_vispector_proxy_box = { + &__synctex_proxy_visible_h, + &__synctex_proxy_visible_v, + &__synctex_proxy_visible_width, + &__synctex_proxy_visible_height, + &__synctex_proxy_visible_depth, +}; + +static synctex_class_s synctex_class_proxy_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_proxy_hbox, /* Node type */ + &_synctex_new_proxy_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy_hbox, /* display */ + &_synctex_abstract_proxy_hbox, /* abstract */ + &synctex_tree_model_proxy_hbox, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark vbox proxy. +# endif + +/* A proxy to a vbox. */ + +static const synctex_tree_model_s synctex_tree_model_proxy_vbox = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + synctex_tree_spc_friend_idx, /* friend */ + synctex_tree_spcf_last_idx, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spcfl_target_idx, /* target */ + synctex_tree_spcflt_proxy_vbox_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spcflt_proxy_vbox_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_vbox_s; + +/* box proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy_vbox) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy_vbox(synctex_node_p node); +static void _synctex_display_proxy_vbox(synctex_node_p node); + +static synctex_class_s synctex_class_proxy_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_proxy_vbox, /* Node type */ + &_synctex_new_proxy_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy_vbox, /* display */ + &_synctex_abstract_proxy_vbox, /* abstract */ + &synctex_tree_model_proxy_vbox, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy_box, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark proxy. +# endif + +/** + * A proxy to a node but a box. + */ + +static const synctex_tree_model_s synctex_tree_model_proxy = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spf_target_idx,/* target */ + synctex_tree_spft_proxy_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spft_proxy_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_s; + +/* proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy(synctex_node_p node); +static void _synctex_display_proxy(synctex_node_p node); + +static synctex_vispector_s synctex_vispector_proxy = { + &__synctex_proxy_visible_h, + &__synctex_proxy_visible_v, + &__synctex_proxy_visible_width, + &_synctex_float_none, + &_synctex_float_none, +}; + +static synctex_class_s synctex_class_proxy = { + NULL, /* No scanner yet */ + synctex_node_type_proxy, /* Node type */ + &_synctex_new_proxy, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy, /* display */ + &_synctex_abstract_proxy, /* abstract */ + &synctex_tree_model_proxy, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark last proxy. +# endif + +/** + * A proxy to the last proxy/box boundary. + */ + +static const synctex_tree_model_s synctex_tree_model_proxy_last = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + -1, /* child */ + synctex_tree_sp_friend_idx, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + synctex_tree_spf_arg_sibling_idx, /* arg_sibling */ + synctex_tree_spfa_target_idx, /* target */ + synctex_tree_spfat_proxy_last_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spfat_proxy_last_max+synctex_data_proxy_hv_max]; +} synctex_node_proxy_last_s; + +/* proxy node creator */ +DEFINE_synctex_new_unscanned_NODE(proxy_last) + +static void _synctex_log_proxy(synctex_node_p node); +static char * _synctex_abstract_proxy(synctex_node_p node); +static void _synctex_display_proxy(synctex_node_p node); + +static synctex_class_s synctex_class_proxy_last = { + NULL, /* No scanner yet */ + synctex_node_type_proxy_last, /* Node type */ + &_synctex_new_proxy, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_proxy, /* log */ + &_synctex_display_proxy, /* display */ + &_synctex_abstract_proxy, /* abstract */ + &synctex_tree_model_proxy_last, /* tree model */ + &synctex_data_model_proxy, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy, /* vispector */ +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark handle. +# endif + +/** + * A result node. + */ + +static const synctex_tree_model_s synctex_tree_model_handle = { + synctex_tree_sibling_idx, /* sibling */ + synctex_tree_s_parent_idx, /* parent */ + synctex_tree_sp_child_idx, /* child */ + -1, /* friend */ + -1, /* last */ + -1, /* next_hbox */ + -1, /* arg_sibling */ + synctex_tree_spc_target_idx,/* target */ + synctex_tree_spct_handle_max +}; + +typedef struct { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; + synctex_data_u data[synctex_tree_spct_handle_max+0]; +} synctex_node_handle_s; + +/* result node creator */ +DEFINE_synctex_new_unscanned_NODE(handle) + +static void _synctex_log_handle(synctex_node_p node); +static char * _synctex_abstract_handle(synctex_node_p node); +static void _synctex_display_handle(synctex_node_p node); + +static synctex_class_s synctex_class_handle = { + NULL, /* No scanner yet */ + synctex_node_type_handle, /* Node type */ + &_synctex_new_handle, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_handle, /* log */ + &_synctex_display_handle, /* display */ + &_synctex_abstract_handle, /* abstract */ + &synctex_tree_model_handle, /* tree model */ + &synctex_data_model_none, /* data model */ + &synctex_tlcpector_proxy, /* tlcpector */ + &synctex_inspector_proxy_box, /* inspector */ + &synctex_vispector_proxy_box, /* vispector */ +}; + +SYNCTEX_INLINE static synctex_node_p _synctex_new_handle_with_target(synctex_node_p target) { + if (target) { + synctex_node_p result = _synctex_new_handle(target->class->scanner); + if (result) { + _synctex_tree_set_target(result,target); + return result; + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Navigation +# endif +synctex_node_p synctex_node_parent(synctex_node_p node) +{ + return _synctex_tree_parent(node); +} +synctex_node_p synctex_node_parent_sheet(synctex_node_p node) +{ + while(node && synctex_node_type(node) != synctex_node_type_sheet) { + node = _synctex_tree_parent(node); + } + /* exit the while loop either when node is NULL or node is a sheet */ + return node; +} +synctex_node_p synctex_node_parent_form(synctex_node_p node) +{ + while(node && synctex_node_type(node) != synctex_node_type_form) { + node = _synctex_tree_parent(node); + } + /* exit the while loop either when node is NULL or node is a form */ + return node; +} + +/** + * The returned proxy will be the child or a sibling of source. + * The returned proxy has no parent, child nor sibling. + * Used only by __synctex_replace_ref. + * argument to_node: a box, not a proxy nor anything else. + */ +SYNCTEX_INLINE static synctex_node_p __synctex_new_proxy_from_ref_to(synctex_node_p ref, synctex_node_p to_node) { + synctex_node_p proxy = NULL; + if (!ref || !to_node) { + return NULL; + } + switch(synctex_node_type(to_node)) { + case synctex_node_type_vbox: + proxy = _synctex_new_proxy_vbox(ref->class->scanner); + break; + case synctex_node_type_hbox: + proxy = _synctex_new_proxy_hbox(ref->class->scanner); + break; + default: + _synctex_error("! __synctex_new_proxy_from_ref_to. Unexpected form child (%s). Please report.", synctex_node_isa(to_node)); + return NULL; + } + if (!proxy) { + _synctex_error("! __synctex_new_proxy_from_ref_to. Internal error. Please report."); + return NULL; + } + _synctex_data_set_h(proxy, _synctex_data_h(ref)); + _synctex_data_set_v(proxy, _synctex_data_v(ref)); + _synctex_tree_set_target(proxy,to_node); +# if defined(SYNCTEX_USE_CHARINDEX) + proxy->line_index=to_node?to_node->line_index:0; + proxy->char_index=to_node?to_node->char_index:0; +# endif + return proxy; +} +/** + * The returned proxy will be the child or a sibling of owning_proxy. + * The returned proxy has no parent, nor child. + * Used only by synctex_node_child and synctex_node_sibling + * to create proxies on the fly. + * If the to_node has an already computed sibling, + * then the returned proxy has itself a sibling + * pointing to that already computed sibling. + */ +SYNCTEX_INLINE static synctex_node_p __synctex_new_child_proxy_to(synctex_node_p owner, synctex_node_p to_node) { + synctex_node_p proxy = NULL; + synctex_node_p target = to_node; + if (!owner) { + return NULL; + } + switch(synctex_node_type(target)) { + case synctex_node_type_vbox: + if ((proxy = _synctex_new_proxy_vbox(owner->class->scanner))) { + exit_standard: + _synctex_data_set_h(proxy, _synctex_data_h(owner)); + _synctex_data_set_v(proxy, _synctex_data_v(owner)); + exit0: + _synctex_tree_set_target(proxy,target); +# if defined(SYNCTEX_USE_CHARINDEX) + proxy->line_index=to_node?to_node->line_index:0; + proxy->char_index=to_node?to_node->char_index:0; +# endif + return proxy; + }; + break; + case synctex_node_type_proxy_vbox: + if ((proxy = _synctex_new_proxy_vbox(owner->class->scanner))) { + exit_proxy: + target = _synctex_tree_target(to_node); + _synctex_data_set_h(proxy, _synctex_data_h(owner)+_synctex_data_h(to_node)); + _synctex_data_set_v(proxy, _synctex_data_v(owner)+_synctex_data_v(to_node)); + goto exit0; + }; + break; + case synctex_node_type_hbox: + if ((proxy = _synctex_new_proxy_hbox(owner->class->scanner))) { + goto exit_standard; + }; + break; + case synctex_node_type_proxy_hbox: + if ((proxy = _synctex_new_proxy_hbox(owner->class->scanner))) { + goto exit_proxy; + }; + break; + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + if ((proxy = _synctex_new_proxy(owner->class->scanner))) { + goto exit_proxy; + }; + break; + default: + if ((proxy = _synctex_new_proxy(owner->class->scanner))) { + goto exit_standard; + }; + break; + } + _synctex_error("! __synctex_new_child_proxy_to. " + "Internal error. " + "Please report."); + return NULL; +} +SYNCTEX_INLINE static synctex_node_p _synctex_tree_set_sibling(synctex_node_p node, synctex_node_p new_sibling); +typedef struct synctex_nns_t { + synctex_node_p first; + synctex_node_p last; + synctex_status_t status; +} synctex_nns_s; +/** + * Given a target node, create a list of proxies. + * The first proxy points to the target node, + * its sibling points to the target's sibling and so on. + * Returns the first created proxy, the last one and + * an error status. + */ +SYNCTEX_INLINE static synctex_nns_s _synctex_new_child_proxies_to(synctex_node_p owner, synctex_node_p to_node) { + synctex_nns_s nns = {NULL,NULL,SYNCTEX_STATUS_OK}; + if ((nns.first = nns.last = __synctex_new_child_proxy_to(owner,to_node))) { + synctex_node_p to_next_sibling = __synctex_tree_sibling(to_node); + synctex_node_p to_sibling; + while ((to_sibling = to_next_sibling)) { + synctex_node_p sibling; + if ((to_next_sibling = __synctex_tree_sibling(to_sibling))) { + /* This is not the last sibling */ + if((sibling = __synctex_new_child_proxy_to(owner,to_sibling))) { + _synctex_tree_set_sibling(nns.last,sibling); + nns.last = sibling; + continue; + } else { + _synctex_error("! _synctex_new_child_proxy_to. " + "Internal error (1). " + "Please report."); + nns.status = SYNCTEX_STATUS_ERROR; + } + } else if((sibling = _synctex_new_proxy_last(owner->class->scanner))) { + _synctex_tree_set_sibling(nns.last,sibling); + nns.last = sibling; + _synctex_data_set_h(nns.last, _synctex_data_h(nns.first)); + _synctex_data_set_v(nns.last, _synctex_data_v(nns.first)); + _synctex_tree_set_target(nns.last,to_sibling); +# if defined(SYNCTEX_USE_CHARINDEX) + nns.last->line_index=to_sibling->line_index; + nns.last->char_index=to_sibling->char_index; +# endif + } else { + _synctex_error("! _synctex_new_child_proxy_to. " + "Internal error (2). " + "Please report."); + nns.status = SYNCTEX_STATUS_ERROR; + } + break; + } + } + return nns; +} +static char * _synctex_node_abstract(synctex_node_p node); +SYNCTEX_INLINE static synctex_node_p synctex_tree_set_friend(synctex_node_p node,synctex_node_p new_friend) { +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG + synctex_node_p F = new_friend; + while (F) { + if (node == F) { + printf("THIS IS AN ERROR\n"); + F = new_friend; + while (F) { + printf("%s\n",_synctex_node_abstract(F)); + if (node == F) { + return NULL; + } + F = _synctex_tree_friend(F); + } + return NULL; + } + F = _synctex_tree_friend(F); + } +#endif + return new_friend?_synctex_tree_set_friend(node,new_friend):_synctex_tree_reset_friend(node); +} +/** + * + */ +SYNCTEX_INLINE static synctex_node_p __synctex_node_make_friend(synctex_node_p node, int i) { + synctex_node_p old = NULL; + if (i>=0) { + i = i%(node->class->scanner->number_of_lists); + old = synctex_tree_set_friend(node,(node->class->scanner->lists_of_friends)[i]); + (node->class->scanner->lists_of_friends)[i] = node; +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("tl(%i)=>",i); + synctex_node_log(node); + if (synctex_node_parent_form(node)) { + printf("! ERROR. No registration expected!\n"); + } +#endif + } + return old; +} +/** + * All proxies have tlc attributes, on behalf of their target. + * The purpose is to register all af them. + * - argument node: is the proxy, must not be NULL + */ +SYNCTEX_INLINE static synctex_node_p __synctex_proxy_make_friend_and_next_hbox(synctex_node_p node) { + synctex_node_p old = NULL; + synctex_node_p target = _synctex_tree_target(node); + if (target) { + int i = _synctex_data_tag(target)+_synctex_data_line(target); + old = __synctex_node_make_friend(node,i); + } else { + old = __synctex_tree_reset_friend(node); + } + if (synctex_node_type(node) == synctex_node_type_proxy_hbox) { + synctex_node_p sheet = synctex_node_parent_sheet(node); + if (sheet) { + _synctex_tree_set_next_hbox(node,_synctex_tree_next_hbox(sheet)); + _synctex_tree_set_next_hbox(sheet,node); + } + } + return old; +} +/** + * Register a node which have tag, line and column. + * - argument node: the node + */ +SYNCTEX_INLINE static synctex_node_p __synctex_node_make_friend_tlc(synctex_node_p node) { + int i = synctex_node_tag(node)+synctex_node_line(node); + return __synctex_node_make_friend(node,i); +} +/** + * Register a node which have tag, line and column. + * Does nothing if the argument is NULL. + * Calls __synctex_node_make_friend_tlc. + * - argument node: the node + */ +SYNCTEX_INLINE static void _synctex_node_make_friend_tlc(synctex_node_p node) { + if (node) { + __synctex_node_make_friend_tlc(node); + } +} +static synctex_node_p _synctex_node_set_child(synctex_node_p node, synctex_node_p new_child); +/** + * The (first) child of the node, if any, NULL otherwise. + * At parse time, non void box nodes have children. + * All other nodes have no children. + * In order to support pdf forms, proxies are created + * to place form nodes at real locations. + * Ref nodes are replaced by root proxies targeting + * form contents. If root proxies have no children, + * they are created on the fly as proxies to the + * children of the targeted box. + * As such, proxies created here are targeting a + * node that belongs to a form. + * This is the only place where child proxies are created. + */ +synctex_node_p synctex_node_child(synctex_node_p node) { + synctex_node_p child = NULL; + synctex_node_p target = NULL; + if ((child = _synctex_tree_child(node))) { + return child; + } else if ((target = _synctex_tree_target(node))) { + if ((child = synctex_node_child(target))) { + /* This is a proxy with no child + * which target does have a child. */ + synctex_nns_s nns = _synctex_new_child_proxies_to(node, child); + if (nns.first) { + _synctex_node_set_child(node,nns.first); + return nns.first; + } else { + _synctex_error("! synctex_node_child. Internal inconsistency. Please report."); + } + } + } + return NULL; +} +/* + * Set the parent/child bound. + * Things get complicated when new_child has siblings. + * The caller is responsible for releasing the returned value. + */ +static synctex_node_p _synctex_node_set_child(synctex_node_p parent, synctex_node_p new_child) { + if (parent) { + synctex_node_p old = _synctex_tree_set_child(parent,new_child); + synctex_node_p last_child = NULL; + synctex_node_p child; + if ((child = old)) { + do { + _synctex_tree_reset_parent(child); + } while ((child = __synctex_tree_sibling(child))); + } + if ((child = new_child)) { + do { + _synctex_tree_set_parent(child,parent); + last_child = child; + } while ((child = __synctex_tree_sibling(child))); + } + _synctex_tree_set_last(parent,last_child); + return old; + } + return NULL; +} + +/* The last child of the given node, or NULL. + */ +synctex_node_p synctex_node_last_child(synctex_node_p node) { + return _synctex_tree_last(node); +} +/** + * All nodes siblings are properly set up at parse time + * except for non root proxies. + */ +synctex_node_p synctex_node_sibling(synctex_node_p node) { + return node? __synctex_tree_sibling(node): NULL; +} +/** + * All the _synctex_tree_... methods refer to the tree model. + * __synctex_tree_... methods are low level. + */ +/** + * Replace the sibling. + * Connect to the arg_sibling of the new_sibling if relevant. + * - returns the old sibling. + * The caller is responsible for releasing the old sibling. + * The bound to the parent is managed below. + */ +SYNCTEX_INLINE static synctex_node_p _synctex_tree_set_sibling(synctex_node_p node, synctex_node_p new_sibling) { + if (node == new_sibling) { + printf("BOF\n"); + } + synctex_node_p old = node? __synctex_tree_set_sibling(node,new_sibling): NULL; + _synctex_tree_set_arg_sibling(new_sibling,node); + return old; +} +/** + * Replace the sibling. + * Set the parent of the new sibling (and further siblings) + * to the parent of the receiver. + * Also set the last sibling of parent. + * - argument new_sibling: must not be NULL. + * - returns the old sibling. + * The caller is responsible for releasing the old sibling. + */ +static synctex_node_p _synctex_node_set_sibling(synctex_node_p node, synctex_node_p new_sibling) { + if (node && new_sibling) { + synctex_node_p old = _synctex_tree_set_sibling(node,new_sibling); + if (_synctex_tree_has_parent(node)) { + synctex_node_p parent = __synctex_tree_parent(node); + if (parent) { + synctex_node_p N = new_sibling; + while (synctex_YES) { + if (_synctex_tree_has_parent(N)) { + __synctex_tree_set_parent(N,parent); + _synctex_tree_set_last(parent,N); + N = __synctex_tree_sibling(N); + continue; + } else if (N) { + _synctex_error("! synctex_node_sibling. " + "Internal inconsistency. " + "Please report."); + } + break; + } + } + } + return old; + } + return NULL; +} +/** + * The last sibling of the given node, or NULL with node. + */ +synctex_node_p synctex_node_last_sibling(synctex_node_p node) { + synctex_node_p sibling; + do { + sibling = node; + } while((node = synctex_node_sibling(node))); + return sibling; +} +/** + * The next nodes corresponds to a deep first tree traversal. + * Does not create child proxies as side effect contrary to + * the synctex_node_next method above. + * May loop infinitely many times if the tree + * is not properly built (contains loops). + */ +SYNCTEX_INLINE static synctex_node_p _synctex_node_sibling_or_parents(synctex_node_p node) { + while (node) { + synctex_node_p N; + if ((N = __synctex_tree_sibling(node))) { + return N; + } else if ((node = _synctex_tree_parent(node))) { + if (synctex_node_type(node) == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ + return NULL; + } else if (synctex_node_type(node) == synctex_node_type_form) { + return NULL; + } + } else { + return NULL; + } + } + return NULL; +} +/** + * The next nodes corresponds to a deep first tree traversal. + * Creates child proxies as side effect. + * May loop infinitely many times if the tree + * is not properly built (contains loops). + */ +synctex_node_p synctex_node_next(synctex_node_p node) { + synctex_node_p N = synctex_node_child(node); + if (N) { + return N; + } + return _synctex_node_sibling_or_parents(node); +} +/** + * The next nodes corresponds to a deep first tree traversal. + * Does not create child proxies as side effect contrary to + * the synctex_node_next method above. + * May loop infinitely many times if the tree + * is not properly built (contains loops). + */ +synctex_node_p _synctex_node_next(synctex_node_p node) { + synctex_node_p N = _synctex_tree_child(node); + if (N) { + return N; + } + return _synctex_node_sibling_or_parents(node); +} +/** + * The node which argument is the sibling. + * - return: NULL if the argument has no parent or + * is the first child of its parent. + * - Input nodes have no arg siblings + */ +synctex_node_p synctex_node_arg_sibling(synctex_node_p node) { +#if 1 + return _synctex_tree_arg_sibling(node); +#else + synctex_node_p N = _synctex_tree_parent(node); + if ((N = _synctex_tree_child(N))) { + do { + synctex_node_p NN = __synctex_tree_sibling(N); + if (NN == node) { + return N; + } + N = NN; + } while (N); + } + return N; +#endif +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark CLASS +# endif + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_type(synctex_node_p node) { + return node? node->class->type: synctex_node_type_none; +} + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_target_type(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + return (((target)->class))->type; + } else if (node) { + return (((node)->class))->type; + } + return synctex_node_type_none; +} + +/* Public node accessor: the human readable type */ +const char * synctex_node_isa(synctex_node_p node) { + static const char * isa[synctex_node_number_of_types] = + {"Not a node", + "input", + "sheet", + "form", + "ref", + "vbox", + "void vbox", + "hbox", + "void hbox", + "kern", + "glue", + "rule", + "math", + "boundary", + "box_bdry", + "proxy", + "last proxy", + "vbox proxy", + "hbox proxy", + "handle"}; + return isa[synctex_node_type(node)]; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark LOG +# endif + +/* Public node logger */ +void synctex_node_log(synctex_node_p node) { + SYNCTEX_MSG_SEND(node,log); +} + +static void _synctex_log_input(synctex_node_p node) { + if (node) { + printf("%s:%i,%s(%i)\n",synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_name(node), + _synctex_data_line(node)); + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n", + (void *)__synctex_tree_sibling(node)); + } +} + +static void _synctex_log_sheet(synctex_node_p node) { + if (node) { + printf("%s:%i",synctex_node_isa(node),_synctex_data_page(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" NEXT_hbox:%p\n",(void *)_synctex_tree_next_hbox(node)); + } +} + +static void _synctex_log_form(synctex_node_p node) { + if (node) { + printf("%s:%i",synctex_node_isa(node),_synctex_data_tag(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_ref(synctex_node_p node) { + if (node) { + printf("%s:%i:%i,%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + } +} + +static void _synctex_log_tlchv_node(synctex_node_p node) { + if (node) { + printf("%s:%i,%i:%i,%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_kern_node(synctex_node_p node) { + if (node) { + printf("%s:%i,%i:%i,%i:%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_rule(synctex_node_p node) { + if (node) { + printf("%s:%i,%i:%i,%i", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_void_box(synctex_node_p node) { + if (node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_tag(node)); + printf(",%i",_synctex_data_line(node)); + printf(",%i",0); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + } +} + +static void _synctex_log_vbox(synctex_node_p node) { + if (node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_tag(node)); + printf(",%i",_synctex_data_line(node)); + printf(",%i",0); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" NEXT_hbox:%p\n",(void *)_synctex_tree_next_hbox(node)); + } +} + +static void _synctex_log_hbox(synctex_node_p node) { + if (node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_tag(node)); + printf(",%i~%i*%i",_synctex_data_line(node),_synctex_data_mean_line(node),_synctex_data_weight(node)); + printf(",%i",0); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + printf(":%i",_synctex_data_width(node)); + printf(",%i",_synctex_data_height(node)); + printf(",%i",_synctex_data_depth(node)); + printf("/%i",_synctex_data_h_V(node)); + printf(",%i",_synctex_data_v_V(node)); + printf(":%i",_synctex_data_width_V(node)); + printf(",%i",_synctex_data_height_V(node)); + printf(",%i",_synctex_data_depth_V(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" PARENT:%p\n",(void *)_synctex_tree_parent(node)); + printf(" CHILD:%p\n",(void *)_synctex_tree_child(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" NEXT_hbox:%p\n",(void *)_synctex_tree_next_hbox(node)); + } +} +static void _synctex_log_proxy(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s",synctex_node_isa(node)); + printf(":%i",_synctex_data_h(node)); + printf(",%i",_synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" LEFT:%p\n",(void *)_synctex_tree_friend(node)); + printf(" ->%s\n",_synctex_node_abstract(N)); + } +} +static void _synctex_log_handle(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s",synctex_node_isa(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + printf("SELF:%p\n",(void *)node); + printf(" SIBLING:%p\n",(void *)__synctex_tree_sibling(node)); + printf(" ->%s\n",_synctex_node_abstract(N)); + } +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SYNCTEX_DISPLAY +# endif + +int synctex_scanner_display_switcher(synctex_scanner_p scanR) { + return scanR->display_switcher; +} +void synctex_scanner_set_display_switcher(synctex_scanner_p scanR, int switcher) { + scanR->display_switcher = switcher; +} +static const char * const _synctex_display_prompt = "................................"; + +static char * _synctex_scanner_display_prompt_down(synctex_scanner_p scanR) { + if (scanR->display_prompt>_synctex_display_prompt) { + --scanR->display_prompt; + } + return scanR->display_prompt; +} +static char * _synctex_scanner_display_prompt_up(synctex_scanner_p scanR) { + if (scanR->display_prompt+1<_synctex_display_prompt+strlen(_synctex_display_prompt)) { + ++scanR->display_prompt; + } + return scanR->display_prompt; +} + +void synctex_node_display(synctex_node_p node) { + if (node) { + synctex_scanner_p scanR = node->class->scanner; + if (scanR) { + if (scanR->display_switcher<0) { + SYNCTEX_MSG_SEND(node, display); + } else if (scanR->display_switcher>0 && --scanR->display_switcher>0) { + SYNCTEX_MSG_SEND(node, display); + } else if (scanR->display_switcher-->=0) { + printf("%s Next display skipped. Reset display switcher.\n",node->class->scanner->display_prompt); + } + } else { + SYNCTEX_MSG_SEND(node, display); + } + } +} +static char * _synctex_node_abstract(synctex_node_p node) { + SYNCTEX_PARAMETER_ASSERT(node || node->class); + return (node && node->class->abstract)? node->class->abstract(node):"none"; +} + +SYNCTEX_INLINE static void _synctex_display_child(synctex_node_p node) { + synctex_node_p N = _synctex_tree_child(node); + if (N) { + _synctex_scanner_display_prompt_down(N->class->scanner); + synctex_node_display(N); + _synctex_scanner_display_prompt_up(N->class->scanner); + } +} + +SYNCTEX_INLINE static void _synctex_display_sibling(synctex_node_p node) { + synctex_node_display(__synctex_tree_sibling(node)); +} +#define SYNCTEX_ABSTRACT_MAX 128 +static char * _synctex_abstract_input(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"Input:%i:%s(%i)" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_name(node), + _synctex_data_line(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_input(synctex_node_p node) { + if (node) { + printf("Input:%i:%s(%i)" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + _synctex_data_tag(node), + _synctex_data_name(node), + _synctex_data_line(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + synctex_node_display(__synctex_tree_sibling(node)); + } +} + +static char * _synctex_abstract_sheet(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"{%i...}" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_page(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_sheet(synctex_node_p node) { + if (node) { + printf("%s{%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_page(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s}\n",node->class->scanner->display_prompt); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_form(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"<%i...>" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + SYNCTEX_PRINT_CHARINDEX; + } + return abstract; +} + +static void _synctex_display_form(synctex_node_p node) { + if (node) { + printf("%s<%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s>\n",node->class->scanner->display_prompt); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_vbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"[%i,%i:%i,%i:%i,%i,%i...]" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_vbox(synctex_node_p node) { + if (node) { + printf("%s[%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s]\n%slast:%s\n", + node->class->scanner->display_prompt, + node->class->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_hbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"(%i,%i~%i*%i:%i,%i:%i,%i,%i...)" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_mean_line(node), + _synctex_data_weight(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_hbox(synctex_node_p node) { + if (node) { + printf("%s(%i,%i~%i*%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_mean_line(node), + _synctex_data_weight(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s)\n%slast:%s\n", + node->class->scanner->display_prompt, + node->class->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_void_vbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"v%i,%i;%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_void_vbox(synctex_node_p node) { + if (node) { + printf("%sv%i,%i;%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_void_hbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"h%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_void_hbox(synctex_node_p node) { + if (node) { + printf("%sh%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_glue(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"glue:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_glue(synctex_node_p node) { + if (node) { + printf("%sglue:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_rule(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"rule:%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_rule(synctex_node_p node) { + if (node) { + printf("%srule:%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node), + _synctex_data_height(node), + _synctex_data_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_math(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"math:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_math(synctex_node_p node) { + if (node) { + printf("%smath:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_kern(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"kern:%i,%i:%i,%i:%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_kern(synctex_node_p node) { + if (node) { + printf("%skern:%i,%i:%i,%i:%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + _synctex_data_width(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_boundary(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"boundary:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_boundary(synctex_node_p node) { + if (node) { + printf("%sboundary:%i,%i:%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_box_bdry(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"box bdry:%i,%i:%i,%i" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_box_bdry(synctex_node_p node) { + if (node) { + printf("%sbox bdry:%i,%i:%i,%i", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_line(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_ref(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"form ref:%i:%i,%i" SYNCTEX_PRINT_CHARINDEX_FMT, + _synctex_data_tag(node), + _synctex_data_h(node), + _synctex_data_v(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_ref(synctex_node_p node) { + if (node) { + printf("%sform ref:%i:%i,%i", + node->class->scanner->display_prompt, + _synctex_data_tag(node), + _synctex_data_h(node), + _synctex_data_v(node)); + SYNCTEX_PRINT_CHARINDEX_NL; + _synctex_display_sibling(node); + } +} +static char * _synctex_abstract_proxy(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + synctex_node_p N = _synctex_tree_target(node); + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"%s:%i,%i:%i,%i/%p%s", + synctex_node_isa(node), + synctex_node_tag(node), + synctex_node_line(node), + _synctex_data_h(node), + _synctex_data_v(node), + node, + _synctex_node_abstract(N)); + } + return abstract; +} +static void _synctex_display_proxy(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s%s:%i,%i:%i,%i", + node->class->scanner->display_prompt, + synctex_node_isa(node), + synctex_node_tag(node), + synctex_node_line(node), + _synctex_data_h(node), + _synctex_data_v(node)); + if (N) { + printf("=%i,%i:%i,%i,%i->%s", + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node), + _synctex_node_abstract(N)); + } + printf("\n"); + _synctex_display_child(node); + _synctex_display_sibling(node); + } +} +static char * _synctex_abstract_proxy_vbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX, + "[*%i,%i:%i,%i:%i,%i,%i...*]" + SYNCTEX_PRINT_CHARINDEX_FMT, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_proxy_vbox(synctex_node_p node) { + if (node) { + printf("%s[*%i,%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s*]\n%slast:%s\n", + node->class->scanner->display_prompt, + node->class->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_proxy_hbox(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"(*%i,%i~%i*%i:%i,%i:%i,%i,%i...*)/%p" + SYNCTEX_PRINT_CHARINDEX_FMT, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_mean_line(node), + synctex_node_weight(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node), + node + SYNCTEX_PRINT_CHARINDEX_WHAT); + } + return abstract; +} + +static void _synctex_display_proxy_hbox(synctex_node_p node) { + if (node) { + printf("%s(*%i,%i~%i*%i:%i,%i:%i,%i,%i" + SYNCTEX_PRINT_CHARINDEX_FMT + "\n", + node->class->scanner->display_prompt, + synctex_node_tag(node), + synctex_node_line(node), + synctex_node_mean_line(node), + synctex_node_weight(node), + synctex_node_h(node), + synctex_node_v(node), + synctex_node_width(node), + synctex_node_height(node), + synctex_node_depth(node) + SYNCTEX_PRINT_CHARINDEX_WHAT); + _synctex_display_child(node); + printf("%s*)\n%slast:%s\n", + node->class->scanner->display_prompt, + node->class->scanner->display_prompt, + _synctex_node_abstract(_synctex_tree_last(node))); + _synctex_display_sibling(node); + } +} + +static char * _synctex_abstract_handle(synctex_node_p node) { + static char abstract[SYNCTEX_ABSTRACT_MAX] = "none"; + if (node) { + synctex_node_p N = _synctex_tree_target(node); + if (N && !N->class) { + exit(1); + } + snprintf(abstract,SYNCTEX_ABSTRACT_MAX,"%s:%s", + synctex_node_isa(node), + (N?_synctex_node_abstract(N):"")); + } + return abstract; +} +static void _synctex_display_handle(synctex_node_p node) { + if (node) { + synctex_node_p N = _synctex_tree_target(node); + printf("%s%s:->%s\n", + node->class->scanner->display_prompt, + synctex_node_isa(node), + _synctex_node_abstract(N)); + _synctex_display_child(node); + _synctex_display_sibling(node); + } +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark STATUS +# endif + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Prototypes +# endif +typedef struct { + size_t size; + synctex_status_t status; +} synctex_zs_s; +static synctex_zs_s _synctex_buffer_get_available_size(synctex_scanner_p scanner, size_t size); +static synctex_status_t _synctex_next_line(synctex_scanner_p scanner); +static synctex_status_t _synctex_match_string(synctex_scanner_p scanner, const char * the_string); + +typedef struct synctex_ns_t { + synctex_node_p node; + synctex_status_t status; +} synctex_ns_s; +static synctex_ns_s __synctex_parse_new_input(synctex_scanner_p scanner); +static synctex_status_t _synctex_scan_preamble(synctex_scanner_p scanner); +typedef struct { + float value; + synctex_status_t status; +} synctex_fs_s; +static synctex_fs_s _synctex_scan_float_and_dimension(synctex_scanner_p scanner); +static synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_p scanner); +static synctex_status_t _synctex_scan_postamble(synctex_scanner_p scanner); +static synctex_status_t _synctex_setup_visible_hbox(synctex_node_p box); +static synctex_status_t _synctex_scan_content(synctex_scanner_p scanner); +int synctex_scanner_pre_x_offset(synctex_scanner_p scanner); +int synctex_scanner_pre_y_offset(synctex_scanner_p scanner); +const char * synctex_scanner_get_output_fmt(synctex_scanner_p scanner); + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER UTILITIES +# endif + +# define SYNCTEX_FILE (scanner->reader->file) + +/** + * Try to ensure that the buffer contains at least size bytes. + * Passing a huge size argument means the whole buffer length. + * Passing a 0 size argument means return the available buffer length, without reading the file. + * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL. + * The size_t value returned is the number of bytes now available in the buffer. This is a nonnegative integer, it may take the value 0. + * It is the responsibility of the caller to test whether this size is conforming to its needs. + * Negative values may return in case of error, actually + * when there was an error reading the synctex file. + * - parameter scanner: The owning scanner. When NULL, returns SYNCTEX_STATUS_BAD_ARGUMENT. + * - parameter expected: expected number of bytes. + * - returns: a size and a status. + */ +static synctex_zs_s _synctex_buffer_get_available_size(synctex_scanner_p scanner, size_t expected) { + size_t size = 0; + if (NULL == scanner) { + return (synctex_zs_s){0,SYNCTEX_STATUS_BAD_ARGUMENT}; + } + if (expected>scanner->reader->size){ + expected = scanner->reader->size; + } + size = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ + if (expected<=size) { + /* There are already sufficiently many characters in the buffer */ + return (synctex_zs_s){size,SYNCTEX_STATUS_OK}; + } + if (SYNCTEX_FILE) { + /* Copy the remaining part of the buffer to the beginning, + * then read the next part of the file */ + int already_read = 0; +# if defined(SYNCTEX_USE_CHARINDEX) + scanner->reader->charindex_offset += SYNCTEX_CUR - SYNCTEX_START; +# endif + if (size) { + memmove(SYNCTEX_START, SYNCTEX_CUR, size); + } + SYNCTEX_CUR = SYNCTEX_START + size; /* the next character after the move, will change. */ + /* Fill the buffer up to its end */ + already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,(int)(SYNCTEX_BUFFER_SIZE - size)); + if (already_read>0) { + /* We assume that 0already_read) { + /* There is a possible error in reading the file */ + int errnum = 0; + const char * error_string = gzerror(SYNCTEX_FILE, &errnum); + if (Z_ERRNO == errnum) { + /* There is an error in zlib caused by the file system */ + _synctex_error("gzread error from the file system (%i)",errno); + return (synctex_zs_s){0,SYNCTEX_STATUS_ERROR}; + } else if (errnum) { + _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); + return (synctex_zs_s){0,SYNCTEX_STATUS_ERROR}; + } + } + /* Nothing was read, we are at the end of the file. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_END = SYNCTEX_CUR; + SYNCTEX_CUR = SYNCTEX_START; + * SYNCTEX_END = '\0';/* Terminate the string properly.*/ + /* there might be a bit of text left */ + return (synctex_zs_s){SYNCTEX_END - SYNCTEX_CUR,SYNCTEX_STATUS_EOF}; + } + /* We cannot enlarge the buffer because the end of the file was reached. */ + return (synctex_zs_s){size,SYNCTEX_STATUS_EOF}; +} + +/* Used when parsing the synctex file. + * Advance to the next character starting a line. + * Actually, only '\n' is recognized as end of line marker. + * On normal completion, the returned value is the number of unparsed characters available in the buffer. + * In general, it is a positive value, 0 meaning that the end of file was reached. + * -1 is returned in case of error, actually because there was an error while feeding the buffer. + * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. + * J. Laurens: Sat May 10 07:52:31 UTC 2008 + */ +static synctex_status_t _synctex_next_line(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +infinite_loop: + while(SYNCTEX_CURreader->line_number; + return _synctex_buffer_get_available_size(scanner, 1).status; + } + ++SYNCTEX_CUR; + } + /* Here, we have SYNCTEX_CUR == SYNCTEX_END, such that the next call to _synctex_buffer_get_available_size + * will read another bunch of synctex file. Little by little, we advance to the end of the file. */ + status = _synctex_buffer_get_available_size(scanner, 1).status; + if (status<=SYNCTEX_STATUS_EOF) { + return status; + } + goto infinite_loop; +} + +/* Scan the given string. + * Both scanner and the_string must not be NULL, and the_string must not be 0 length. + * SYNCTEX_STATUS_OK is returned if the string is found, + * SYNCTEX_STATUS_EOF is returned when the EOF is reached, + * SYNCTEX_STATUS_NOT_OK is returned is the string is not found, + * an error status is returned otherwise. + * This is a critical method because buffering renders things more difficult. + * The given string might be as long as the maximum size_t value. + * As side effect, the buffer state may have changed if the given argument string can't fit into the buffer. + */ +static synctex_status_t _synctex_match_string(synctex_scanner_p scanner, const char * the_string) { + size_t tested_len = 0; /* the number of characters at the beginning of the_string that match */ + size_t remaining_len = 0; /* the number of remaining characters of the_string that should match */ + size_t available = 0; + synctex_zs_s zs = {0,0}; + if (NULL == scanner || NULL == the_string) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + remaining_len = strlen(the_string); /* All the_string should match */ + if (0 == remaining_len) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* How many characters available in the buffer? */ + zs = _synctex_buffer_get_available_size(scanner,remaining_len); + if (zs.status=remaining_len) { + /* The buffer is sufficiently big to hold the expected number of characters. */ + if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + return SYNCTEX_STATUS_NOT_OK; + } + return_OK: + /* Advance SYNCTEX_CUR to the next character after the_string. */ + SYNCTEX_CUR += remaining_len; + return SYNCTEX_STATUS_OK; + } else if (strncmp((char *)SYNCTEX_CUR,the_string,zs.size)) { + /* No need to go further, this is not the expected string in the buffer. */ + return SYNCTEX_STATUS_NOT_OK; + } else if (SYNCTEX_FILE) { + /* The buffer was too small to contain remaining_len characters. + * We have to cut the string into pieces. */ + z_off_t offset = 0L; + /* the first part of the string is found, advance the_string to the next untested character. */ + the_string += zs.size; + /* update the remaining length and the parsed length. */ + remaining_len -= zs.size; + tested_len += zs.size; + SYNCTEX_CUR += zs.size; /* We validate the tested characters. */ + if (0 == remaining_len) { + /* Nothing left to test, we have found the given string. */ + return SYNCTEX_STATUS_OK; + } + /* We also have to record the current state of the file cursor because + * if the_string does not match, all this should be a totally blank operation, + * for which the file and buffer states should not be modified at all. + * In fact, the states of the buffer before and after this function are in general different + * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR + * can be safely discarded. */ + offset = gztell(SYNCTEX_FILE); + /* offset now corresponds to the first character of the file that was not buffered. */ + /* SYNCTEX_CUR - SYNCTEX_START is the number of chars that where already buffered and + * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered + * because the whole buffer contents is replaced in _synctex_buffer_get_available_size. + * They were buffered from offset-len location in the file. */ + offset -= SYNCTEX_CUR - SYNCTEX_START; + more_characters: + /* There is still some work to be done, so read another bunch of file. + * This is the second call to _synctex_buffer_get_available_size, + * which means that the actual contents of the buffer will be discarded. + * We will definitely have to recover the previous state in case we do not find the expected string. */ + zs = _synctex_buffer_get_available_size(scanner,remaining_len); + if (zs.statusptr) { + SYNCTEX_CUR = end; + return (synctex_is_s){result,SYNCTEX_STATUS_OK}; + } + return (synctex_is_s){result,SYNCTEX_STATUS_NOT_OK}; +} +static synctex_is_s _synctex_decode_int_opt(synctex_scanner_p scanner, int default_value) { + char * ptr = NULL; + char * end = NULL; + synctex_zs_s zs = {0, 0}; + if (NULL == scanner) { + return (synctex_is_s){default_value, SYNCTEX_STATUS_BAD_ARGUMENT}; + } + zs = _synctex_buffer_get_available_size(scanner, SYNCTEX_BUFFER_MIN_SIZE); + if (zs.statusptr) { + SYNCTEX_CUR = end; + return (synctex_is_s){result,SYNCTEX_STATUS_OK}; + } + return (synctex_is_s){default_value,SYNCTEX_STATUS_NOT_OK}; + } + return (synctex_is_s){default_value,SYNCTEX_STATUS_OK}; +} +/* Used when parsing the synctex file. + * Decode an integer for a v field. + * Try the _synctex_decode_int version and set the last v field scanned. + * If it does not succeed, tries to match an '=' sign, + * which is a shortcut for the last v field scanned. + */ +# define SYNCTEX_INPUT_COMEQUALS ",=" +static synctex_is_s _synctex_decode_int_v(synctex_scanner_p scanner) { + synctex_is_s is = _synctex_decode_int(scanner); + if (SYNCTEX_STATUS_OK == is.status) { + scanner->reader->lastv = is.integer; + return is; + } + is.status = _synctex_match_string(scanner,SYNCTEX_INPUT_COMEQUALS); + if (is.statusreader->lastv; + return is; +} + +/* The purpose of this function is to read a string. + * A string is an array of characters from the current parser location + * and before the next '\n' character. + * If a string was properly decoded, it is returned in value_ref and + * the cursor points to the new line marker. + * The returned string was alloced on the heap, the caller is the owner and + * is responsible to free it in due time, + * unless it transfers the ownership to another object. + * If no string is parsed, * value_ref is undefined. + * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. + * If you just want to blindly parse the file up to the end of the current line, + * use _synctex_next_line instead. + * On return, the scanner cursor is unchanged if a string could not be scanned or + * points to the terminating '\n' character otherwise. As a consequence, + * _synctex_next_line is necessary after. + * If either scanner or value_ref is NULL, it is considered as an error and + * SYNCTEX_STATUS_BAD_ARGUMENT is returned. + */ +static synctex_ss_s _synctex_decode_string(synctex_scanner_p scanner) { + char * end = NULL; + size_t len = 0;/* The number of bytes to copy */ + size_t already_len = 0; + synctex_zs_s zs = {0,0}; + char * string = NULL; + if (NULL == scanner) { + return (synctex_ss_s){NULL,SYNCTEX_STATUS_BAD_ARGUMENT}; + } + /* The buffer must at least contain one character: the '\n' end of line marker */ + if (SYNCTEX_CUR>=SYNCTEX_END) { +more_characters: + zs = _synctex_buffer_get_available_size(scanner,1); + if (zs.status < SYNCTEX_STATUS_EOF) { + return (synctex_ss_s){NULL,zs.status}; + } else if (0 == zs.size) { + return (synctex_ss_s){NULL,SYNCTEX_STATUS_EOF}; + } + } + /* Now we are sure that there is at least one available character, either because + * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ + /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ + end = SYNCTEX_CUR; + /* We scan all the characters up to the next '\n' */ + while (end0) { + already_len = len--; + if (string[len]!=' ') { + break; + } + } + string[already_len] = '\0'; + return (synctex_ss_s){string,SYNCTEX_STATUS_OK}; + } + free(string); + _synctex_error("could not copy memory (1)."); + return (synctex_ss_s){NULL,SYNCTEX_STATUS_ERROR}; + } + } + _synctex_error("could not (re)allocate memory (1)."); + return (synctex_ss_s){NULL,SYNCTEX_STATUS_ERROR}; +} + +/* Used when parsing the synctex file. + * Read an Input record. + * - parameter scanner: non NULL scanner + * - returns SYNCTEX_STATUS_OK on successful completions, others values otherwise. + */ +static synctex_ns_s __synctex_parse_new_input(synctex_scanner_p scanner) { + synctex_node_p input = NULL; + synctex_status_t status = SYNCTEX_STATUS_BAD_ARGUMENT; + synctex_zs_s zs = {0,0}; + if (NULL == scanner) { + return (synctex_ns_s){NULL,status}; + } + if ((status=_synctex_match_string(scanner,SYNCTEX_INPUT_MARK))input);/* input has no parent */ + scanner->input = input; +# ifdef SYNCTEX_VERBOSE + synctex_node_log(input); +# endif + return (synctex_ns_s){input,_synctex_next_line(scanner)};/* read the line termination character, if any */ +} + +typedef synctex_is_s (*synctex_decoder_t)(synctex_scanner_p); + +/* Used when parsing the synctex file. + * Read one of the settings. + * On normal completion, returns SYNCTEX_STATUS_OK. + * On error, returns SYNCTEX_STATUS_ERROR. + * Both arguments must not be NULL. + * On return, the scanner points to the next character after the decoded object whatever it is. + * It is the responsibility of the caller to prepare the scanner for the next line. + */ +static synctex_status_t _synctex_scan_named(synctex_scanner_p scanner,const char * name) { + synctex_status_t status = 0; + if (NULL == scanner || NULL == name) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +not_found: + status = _synctex_match_string(scanner,name); + if (statusversion = is.integer; + /* Read all the input records */ + do { + status = __synctex_parse_new_input(scanner).status; + if (statusoutput_fmt = ss.string; + if ((status=_synctex_scan_named(scanner,"Magnification:"))pre_magnification = is.integer; + if ((status=_synctex_scan_named(scanner,"Unit:"))pre_unit = is.integer; + if ((status=_synctex_scan_named(scanner,"X Offset:"))pre_x_offset = is.integer; + if ((status=_synctex_scan_named(scanner,"Y Offset:"))pre_y_offset = is.integer; + return SYNCTEX_STATUS_OK; +} + +/* parse a float with a dimension */ +static synctex_fs_s _synctex_scan_float_and_dimension(synctex_scanner_p scanner) { + synctex_fs_s fs = {0,0}; + synctex_zs_s zs = {0,0}; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if (NULL == scanner) { + return (synctex_fs_s){0,SYNCTEX_STATUS_BAD_ARGUMENT}; + } + zs = _synctex_buffer_get_available_size(scanner, SYNCTEX_BUFFER_MIN_SIZE); + if (zs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536/2.54f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f*65536/25.4f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 72.27f/72*65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 12.0*65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 1.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 1238.0f/1157*65536.0f; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 14856.0f/1157*65536; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 685.0f/642*65536; + } else if (fs.status= SYNCTEX_STATUS_OK) { + fs.value *= 1370.0f/107*65536; + } else if (fs.statusunit = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if (endptr == SYNCTEX_CUR) { + _synctex_error("bad magnification in the post scriptum, a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + if (scanner->unit<=0) { + _synctex_error("bad magnification in the post scriptum, a positive float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + goto next_line; + } + if (statusx_offset = fs.value; + goto next_line; + } else if (statusx_offset = fs.value; + goto next_line; + } else if (statusflags.postamble && (status=_synctex_match_string(scanner,"Postamble:"))count = is.integer; + /* Now we scan the last part of the SyncTeX file: the Post Scriptum section. */ + return _synctex_scan_post_scriptum(scanner); +} + +/* Horizontal boxes also have visible size. + * Visible size are bigger than real size. + * For example 0 width boxes may contain text. + * At creation time, the visible size is set to the values of the real size. + */ +static synctex_status_t _synctex_setup_visible_hbox(synctex_node_p box) { + if (box) { + switch(synctex_node_type(box)) { + case synctex_node_type_hbox: + _synctex_data_set_h_V(box,_synctex_data_h(box)); + _synctex_data_set_v_V(box,_synctex_data_v(box)); + _synctex_data_set_width_V(box,_synctex_data_width(box)); + _synctex_data_set_height_V(box,_synctex_data_height(box)); + _synctex_data_set_depth_V(box,_synctex_data_depth(box)); + return SYNCTEX_STATUS_OK; + default: + break; + } + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} + +/* This method is sent to an horizontal box to setup the visible size + * Some box have 0 width but do contain text material. + * With this method, one can enlarge the box to contain the given point (h,v). + */ +static synctex_status_t _synctex_make_hbox_contain_point(synctex_node_p node,synctex_point_s point) { + int min, max, n; + if (NULL == node || synctex_node_type(node) != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if ((n = _synctex_data_width_V(node))<0) { + max = _synctex_data_h_V(node); + min = max+n; + if (point.hmax) { + _synctex_data_set_h_V(node,point.h); + _synctex_data_set_width_V(node,min-point.h); + } + } else { + min = _synctex_data_h_V(node); + max = min+n; + if (point.hmax) { + _synctex_data_set_width_V(node,point.h - min); + } + } + n = _synctex_data_v_V(node); + min = n - _synctex_data_height_V(node); + max = n + _synctex_data_depth_V(node); + if (point.vmax) { + _synctex_data_set_depth_V(node,point.v-n); + } + return SYNCTEX_STATUS_OK; +} +static synctex_status_t _synctex_make_hbox_contain_box(synctex_node_p node,synctex_box_s box) { + int min, max, n; + if (NULL == node || synctex_node_type(node) != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if ((n = _synctex_data_width_V(node))<0) { + max = _synctex_data_h_V(node); + min = max+n; + if (box.min.h max) { + _synctex_data_set_h_V(node,box.max.h); + _synctex_data_set_width_V(node,min-box.max.h); + } + } else { + min = _synctex_data_h_V(node); + max = min+n; + if (box.min.hmax) { + _synctex_data_set_width_V(node,box.max.h - min); + } + } + n = _synctex_data_v_V(node); + min = n - _synctex_data_height_V(node); + max = n + _synctex_data_depth_V(node); + if (box.min.vmax) { + _synctex_data_set_depth_V(node,box.max.v-n); + } + return SYNCTEX_STATUS_OK; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SPECIAL CHARACTERS +# endif + + +/* Here are the control characters that strat each line of the synctex output file. + * Their values define the meaning of the line. + */ +# define SYNCTEX_CHAR_BEGIN_SHEET '{' +# define SYNCTEX_CHAR_END_SHEET '}' +# define SYNCTEX_CHAR_BEGIN_FORM '<' +# define SYNCTEX_CHAR_END_FORM '>' +# define SYNCTEX_CHAR_BEGIN_VBOX '[' +# define SYNCTEX_CHAR_END_VBOX ']' +# define SYNCTEX_CHAR_BEGIN_HBOX '(' +# define SYNCTEX_CHAR_END_HBOX ')' +# define SYNCTEX_CHAR_ANCHOR '!' +# define SYNCTEX_CHAR_VOID_VBOX 'v' +# define SYNCTEX_CHAR_VOID_HBOX 'h' +# define SYNCTEX_CHAR_KERN 'k' +# define SYNCTEX_CHAR_GLUE 'g' +# define SYNCTEX_CHAR_RULE 'r' +# define SYNCTEX_CHAR_MATH '$' +# define SYNCTEX_CHAR_FORM_REF 'f' +# define SYNCTEX_CHAR_BOUNDARY 'x' +# define SYNCTEX_CHAR_CHARACTER 'c' +# define SYNCTEX_CHAR_COMMENT '%' + +# define SYNCTEX_RETURN(STATUS) return STATUS; + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNERS & PARSERS +# endif + +# define SYNCTEX_DECODE_FAILED(NODE,WHAT) \ +(_synctex_data_decode_##WHAT(NODE)sheet) { + synctex_node_p last_sheet = scanner->sheet; + synctex_node_p next_sheet = NULL; + while ((next_sheet = __synctex_tree_sibling(last_sheet))) { + last_sheet = next_sheet; + } + __synctex_tree_set_sibling(last_sheet,node); /* sheets have no parent */ + } else { + scanner->sheet = node; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_free_node(node); + } + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +/** + * - requirement: scanner != NULL + */ +static synctex_ns_s _synctex_parse_new_form(synctex_scanner_p scanner) { + synctex_node_p node; + if ((node = _synctex_new_form(scanner))) { + if ( + SYNCTEX_DECODE_FAILED(node,tag)) { + _synctex_error("Bad sheet record."); + } else if (_synctex_next_line(scanner)form) { + synctex_node_p last_form = scanner->form; + synctex_node_p next_form = NULL; + while ((next_form = __synctex_tree_sibling(last_form))) { + last_form = next_form; + } + __synctex_tree_set_sibling(last_form,node); + } else { + scanner->form = node; + } + return (synctex_ns_s){node,SYNCTEX_STATUS_OK}; + } + _synctex_free_node(node); + } + return (synctex_ns_s){NULL,SYNCTEX_STATUS_ERROR}; +} +# define SYNCTEX_SHOULD_DECODE_FAILED(NODE,WHAT) \ +(_synctex_data_has_##WHAT(NODE) &&(_synctex_data_decode_##WHAT(NODE)class->scanner,_synctex_data_tag(node)); + } + if (_synctex_data_line(node)>_synctex_data_line(input)) { + _synctex_data_set_line(input,_synctex_data_line(node)); + } + return input; +} +/** + * Scan sheets, forms and input records. + * - parameter scanner: owning scanner + * - returns: status + */ +static synctex_status_t __synctex_parse_sfi(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + synctex_zs_s zs = {0,0}; + synctex_ns_s input = SYNCTEX_NS_NULL; + synctex_node_p sheet = NULL; + synctex_node_p form = NULL; + synctex_node_p parent = NULL; + synctex_node_p child = NULL; + synctex_ns_s ns = SYNCTEX_NS_NULL; + int form_depth = 0; + int ignored_form_depth = 0; + synctex_bool_t try_input = synctex_YES; +# ifdef SYNCTEX_NOTHING +# pragma mark MAIN LOOP +# endif +main_loop: + status = SYNCTEX_STATUS_OK; + sheet = form = parent = child = NULL; +# define SYNCTEX_START_SCAN(WHAT)\ +(*SYNCTEX_CUR == SYNCTEX_CHAR_##WHAT) + if (SYNCTEX_CURflags.postamble = 1; + return status; + } + status = _synctex_next_line(scanner); + if (status" at top level. + * - in a box, the unique possibility is '<', '[', '(' or ">". + * We still keep the '(' for a sheet, because that dos not cost too much. + * We must also consider void boxes as children. + */ + /* forms are everywhere */ + ns = SYNCTEX_NS_NULL; +#ifdef SYNCTEX_VERBOSE + synctex_scanner_set_display_switcher(scanner,-1); + printf("NEW CONTENT LOOP\n"); +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + synctex_node_display(sheet); +#endif +#endif + if (SYNCTEX_CURreader->charindex_offset+SYNCTEX_CUR-SYNCTEX_START); + synctex_lineindex_t line_index = scanner->reader->line_number; +# endif + ns = _synctex_parse_new_hbox(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + parent = ns.node; + /* add a box boundary node at the start */ + if ((child = _synctex_new_box_bdry(scanner))) { +# if defined(SYNCTEX_USE_CHARINDEX) + child->line_index=line_index; + child->char_index=char_index; +# endif + _synctex_node_set_child(parent,child); + _synctex_data_set_tag(child,_synctex_data_tag(parent)); + _synctex_data_set_line(child,_synctex_data_line(parent)); + _synctex_data_set_h(child,_synctex_data_h(parent)); + _synctex_data_set_v(child,_synctex_data_v(parent)); + if (!form) { + __synctex_node_make_friend_tlc(child); + } + } else { + _synctex_error("Can't create box bdry record."); + } +# ifdef SYNCTEX_VERBOSE + synctex_node_log(parent); +# endif + input.node = _synctex_input_register_line(input.node,parent); + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(END_HBOX)) { + if (synctex_node_type(parent) == synctex_node_type_hbox) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN XOBH +# endif + ++SYNCTEX_CUR; + { + /* setting the next horizontal box at the end ensures + * that a child is recorded before any of its ancestors. + */ + if (form == NULL /* && sheet != NULL*/ ) { + _synctex_tree_set_next_hbox(parent,_synctex_tree_next_hbox(sheet)); + _synctex_tree_set_next_hbox(sheet,parent); + } + /* Update the mean line number */ + synctex_node_p node = _synctex_tree_child(parent); + synctex_node_p sibling = NULL; + /* Ignore the first node (a box_bdry) */ + if (node && (node = __synctex_tree_sibling(node))) { + unsigned int node_weight = 0; + unsigned int cumulated_line_numbers = 0; + do { + if (synctex_node_type(node)==synctex_node_type_hbox) { + if (_synctex_data_weight(node)) { + node_weight += _synctex_data_weight(node); + cumulated_line_numbers += _synctex_data_mean_line(node)*_synctex_data_weight(node); + } else { + ++node_weight; + cumulated_line_numbers += _synctex_data_mean_line(node); + } + } else { + ++node_weight; + cumulated_line_numbers += synctex_node_line(node); + } + } while ((node = __synctex_tree_sibling(node))); + _synctex_data_set_mean_line(parent,(cumulated_line_numbers + node_weight/2)/node_weight); + _synctex_data_set_weight(parent,node_weight); + } else { + _synctex_data_set_mean_line(parent,_synctex_data_line(parent)); + _synctex_data_set_weight(parent,1); + } + if ((sibling = _synctex_new_box_bdry(scanner))) { +# if defined(SYNCTEX_USE_CHARINDEX) + sibling->line_index=child->line_index; + sibling->char_index=child->char_index; +# endif + _synctex_node_set_sibling(child,sibling); + { + synctex_node_p N = child; + while (synctex_node_type(N) == synctex_node_type_ref) { + N = _synctex_tree_arg_sibling(N); + } + _synctex_data_set_tag(sibling,_synctex_data_tag(N)); + _synctex_data_set_line(sibling,_synctex_data_line(N)); + } + _synctex_data_set_h(sibling,_synctex_data_h_V(parent)+_synctex_data_width_V(parent)); + _synctex_data_set_v(sibling,_synctex_data_v_V(parent)); + child = sibling; + } else { + _synctex_error("Can't create box bdry record."); + } + sibling = _synctex_tree_child(parent); + _synctex_data_set_point(sibling,_synctex_data_point_V(parent)); + child = parent; + parent = _synctex_tree_parent(child); + _synctex_make_hbox_contain_box(parent,_synctex_data_box_V(child)); +# ifdef SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + } + if (_synctex_next_line(scanner)500 + synctex_node_display(parent); + synctex_node_display(child); +#endif + ns = _synctex_parse_new_ref(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (form) { + if (scanner->ref_in_form) { + synctex_tree_set_friend(child,scanner->ref_in_form); + } + scanner->ref_in_form = child; + } else { + if (scanner->ref_in_sheet) { + synctex_tree_set_friend(child,scanner->ref_in_sheet); + } + scanner->ref_in_sheet = child; + } +# ifdef SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(BOUNDARY)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN BOUNDARY +# endif + ns = _synctex_parse_new_boundary(scanner); + if (ns.status == SYNCTEX_STATUS_OK) { + if (child) { + _synctex_node_set_sibling(child,ns.node); + } else { + _synctex_node_set_child(parent,ns.node); + } + child = ns.node; + if (!form) { + __synctex_node_make_friend_tlc(child); + } + _synctex_make_hbox_contain_point(parent,_synctex_data_point(child)); +# ifdef SYNCTEX_VERBOSE + synctex_node_log(child); +# endif + input.node = _synctex_input_register_line(input.node,child); + goto content_loop; + } + } else if (SYNCTEX_START_SCAN(CHARACTER)) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN CHARACTER +# endif + ++SYNCTEX_CUR; + if (_synctex_next_line(scanner) 0) { +# ifdef SYNCTEX_NOTHING +# pragma mark + SCAN MROF +# endif + ++SYNCTEX_CUR; + --form_depth; + if (_synctex_next_line(scanner)(line %i)\n",SYNCTEX_CUR, scanner->reader->line_number+1); + if (_synctex_next_line(scanner)class->scanner, _synctex_data_tag(ref)); + /* The target is a single node (box) + * with children and no siblings. */ + if ((ns.node = __synctex_new_proxy_from_ref_to(ref, target))) { + /* Insert this proxy instead of ref. */ + _synctex_node_set_sibling(arg_sibling,ns.node); + /* Then append the original sibling of ref. */ + _synctex_node_set_sibling(ns.node,sibling); +# if defined(SYNCTEX_USE_CHARINDEX) + if (synctex_node_type(sibling) == synctex_node_type_box_bdry) { + /* The sibling is the last box boundary + * which may have a less accurate information */ + sibling->char_index = arg_sibling->char_index; + sibling->line_index = arg_sibling->line_index; + } +#endif +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! Ref replacement:\n"); + synctex_node_log(ref); + synctex_node_display(synctex_node_sibling(ref)); +#endif + } else /* simply remove ref */ { + _synctex_tree_set_sibling(arg_sibling,sibling); + } + __synctex_tree_reset_parent(ref); + } else { + _synctex_error("! Missing parent in __synctex_replace_ref. " + "Please report."); + ns.status = SYNCTEX_STATUS_BAD_ARGUMENT; + } + return ns; +} +/** + * - argument ref: is the starting point of a linked list + * of refs. The link is made through the friend field. + * - returns: the status and the list of all the proxies + * created. The link is made through the friend field. + * - note: All refs are freed + */ +SYNCTEX_INLINE static synctex_ns_s _synctex_post_process_ref(synctex_node_p ref) { + synctex_ns_s ns = {NULL, SYNCTEX_STATUS_OK}; + while (ref) { + synctex_node_p next_ref = _synctex_tree_reset_friend(ref); + synctex_ns_s sub_ns = __synctex_replace_ref(ref); + if (sub_ns.status < ns.status) { + ns.status = sub_ns.status; + } else { + /* Insert all the created proxies in the list + * sub_ns.node is the last friend, + */ + synctex_tree_set_friend(sub_ns.node,ns.node); + ns.node = sub_ns.node; + } + synctex_node_free(ref); + ref = next_ref; + } + return ns; +} +typedef synctex_node_p (* synctex_processor_f)(synctex_node_p node); +/** + * Apply the processor f to the tree hierarchy rooted at proxy. + * proxy has replaced a form ref, no children yet. + * As a side effect all the hierarchy of nodes will be created. + */ +SYNCTEX_INLINE static synctex_status_t _synctex_post_process_proxy(synctex_node_p proxy, synctex_processor_f f) { + while(proxy) { + synctex_node_p next_proxy = _synctex_tree_friend(proxy); + synctex_node_p halt = __synctex_tree_sibling(proxy); + /* if proxy is the last sibling, halt is NULL. + * Find what should be a next node, + * without creating new nodes. */ + if (!halt) { + synctex_node_p parent = _synctex_tree_parent(proxy); + halt = __synctex_tree_sibling(parent); + while (!halt && parent) { + parent = _synctex_tree_parent(parent); + halt = __synctex_tree_sibling(parent); + } + } + do { +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("POST PROCESSING %s\n",_synctex_node_abstract(proxy)); + { + int i,j = 0; + for (i=0;iclass->scanner->number_of_lists;++i) { + synctex_node_p N = proxy->class->scanner->lists_of_friends[i]; + do { + if (N==proxy) { + ++j; + printf("%s",_synctex_node_abstract(N)); + } + } while ((N = _synctex_tree_friend(N))); + } + if (j) { + printf("\nBeforehand %i match\n",j); + } + } +#endif + f(proxy); +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + { + int i,j = 0; + for (i=0;iclass->scanner->number_of_lists;++i) { + synctex_node_p N = proxy->class->scanner->lists_of_friends[i]; + do { + if (N==proxy) { + ++j; + printf("%s",_synctex_node_abstract(N)); + } + } while ((N = _synctex_tree_friend(N))); + } + if (j) { + printf("\n%i match\n",j); + } + } +#endif + /* Side effect: create the hierarchy on the fly */ + proxy = synctex_node_next(proxy); /* Change is here */ +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + if (proxy) { + int i,j = 0; + for (i=0;iclass->scanner->number_of_lists;++i) { + synctex_node_p N = proxy->class->scanner->lists_of_friends[i]; + do { + if (N==proxy) { + ++j; + printf("%s",_synctex_node_abstract(N)); + } + } while ((N = _synctex_tree_friend(N))); + } + if (j) { + printf("\nnext %i match\n",j); + } + } +#endif + } while (proxy && proxy != halt); + proxy = next_proxy; + } + return SYNCTEX_STATUS_OK; +} +/** + * Replace all the form refs by root box proxies. + * Create the node hierarchy and update the friends. + * On entry, the refs are collected as a friend list + * in either a form or a sheet + * - parameter: the owning scanner + */ +SYNCTEX_INLINE static synctex_status_t _synctex_post_process(synctex_scanner_p scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + synctex_ns_s ns = {NULL,SYNCTEX_STATUS_NOT_OK}; +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! entering _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +#endif + /* replace form refs inside forms by box proxies */ + ns = _synctex_post_process_ref(scanner->ref_in_form); + scanner->ref_in_form = NULL;/* it was just released */ + if (ns.status500 + printf("! ref replaced in form _synctex_post_process.\n"); + synctex_node_display(scanner->form); +#endif + /* Create all the form proxy nodes on the fly. + * ns.node is the root of the list of + * newly created proxies. + * There might be a problem with cascading proxies. + * In order to be properly managed, the data must + * be organized in the right way. + * The inserted form must be defined before + * the inserting one. *TeX will take care of that. */ + ns.status = _synctex_post_process_proxy(ns.node,&_synctex_tree_reset_friend); + if (ns.statusref_in_sheet); + if (ns.statusref_in_sheet = NULL; +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + printf("! ref replaced in sheet _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); +#endif +#if 0 + { + int i; + for (i=0;inumber_of_lists;++i) { + synctex_node_p P = ns.node; + do { + synctex_node_p N = scanner->lists_of_friends[i]; + do { + if (P == N) { + printf("Already registered.\n"); + synctex_node_display(N); + break; + } + } while ((N = _synctex_tree_friend(N))); + } while((P = _synctex_tree_friend(P))); + } + } +#endif +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>10000 + { + int i; + for (i=0;inumber_of_lists;++i) { + synctex_node_p P = scanner->lists_of_friends[i]; + int j = 0; + while (P) { + ++j; + synctex_node_log(P); + P = _synctex_tree_friend(P); + } + if (j) { + printf("friends %i -> # %i\n",i,j); + } + } + } +#endif + ns.status = _synctex_post_process_proxy(ns.node,&__synctex_proxy_make_friend_and_next_hbox); + if (ns.status500 + printf("! exiting _synctex_post_process.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); + printf("! display all.\n"); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +#endif + return status; +} +/* Used when parsing the synctex file + */ +static synctex_status_t _synctex_scan_content(synctex_scanner_p scanner) { + scanner->reader->lastv = -1; + synctex_status_t status = 0; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Find where this section starts */ +content_not_found: + status = _synctex_match_string(scanner,"Content:"); + if (statusreader = _synctex_malloc(sizeof(synctex_reader_s)))) { + _synctex_free(scanner); + return NULL; + } +# ifdef SYNCTEX_NOTHING +# pragma mark - +# endif +# define DEFINE_synctex_scanner_class(NAME)\ + scanner->class[synctex_node_type_##NAME] = synctex_class_##NAME;\ +(scanner->class[synctex_node_type_##NAME]).scanner = scanner + DEFINE_synctex_scanner_class(input); + DEFINE_synctex_scanner_class(sheet); + DEFINE_synctex_scanner_class(form); + DEFINE_synctex_scanner_class(hbox); + DEFINE_synctex_scanner_class(void_hbox); + DEFINE_synctex_scanner_class(vbox); + DEFINE_synctex_scanner_class(void_vbox); + DEFINE_synctex_scanner_class(kern); + DEFINE_synctex_scanner_class(glue); + DEFINE_synctex_scanner_class(rule); + DEFINE_synctex_scanner_class(math); + DEFINE_synctex_scanner_class(boundary); + DEFINE_synctex_scanner_class(box_bdry); + DEFINE_synctex_scanner_class(ref); + DEFINE_synctex_scanner_class(proxy_hbox); + DEFINE_synctex_scanner_class(proxy_vbox); + DEFINE_synctex_scanner_class(proxy); + DEFINE_synctex_scanner_class(proxy_last); + DEFINE_synctex_scanner_class(handle); + /* set up the lists of friends */ + scanner->number_of_lists = 1024; + scanner->lists_of_friends = (synctex_node_r)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_p)); + if (NULL == scanner->lists_of_friends) { + synctex_scanner_free(scanner); + _synctex_error("malloc:2"); + return NULL; + } + scanner->display_switcher = 100; + scanner->display_prompt = (char *)_synctex_display_prompt+strlen(_synctex_display_prompt)-1; + } + return scanner; +} +/* Where the synctex scanner is created. */ +synctex_scanner_p synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { + synctex_scanner_p scanner = synctex_scanner_new(); + if (NULL == scanner) { + _synctex_error("malloc problem"); + return NULL; + } + if ((scanner->reader = synctex_reader_init_with_output_file(scanner->reader, output, build_directory))) { + return parse? synctex_scanner_parse(scanner):scanner; + } +#if defined(SYNCTEX_DEBUG) + _synctex_error("No file?"); +#endif + synctex_scanner_free(scanner); + return NULL; +} + +/* The scanner destructor + */ +int synctex_scanner_free(synctex_scanner_p scanner) { + int node_count = 0; + if (scanner) { + if (scanner->reader && SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } + synctex_node_free(scanner->sheet); + synctex_node_free(scanner->form); + synctex_node_free(scanner->input); + synctex_reader_free(scanner->reader); + SYNCTEX_SCANNER_FREE_HANDLE(scanner); + synctex_iterator_free(scanner->iterator); + free(scanner->output_fmt); + free(scanner->lists_of_friends); +#if defined SYNCTEX_USE_NODE_COUNT && SYNCTEX_USE_NODE_COUNT>0 + node_count = scanner->node_count; +#endif + free(scanner); + } + return node_count; +} + +/* Where the synctex scanner parses the contents of the file. */ +synctex_scanner_p synctex_scanner_parse(synctex_scanner_p scanner) { + synctex_status_t status = 0; + if (!scanner || scanner->flags.has_parsed) { + return scanner; + } + scanner->flags.has_parsed=1; + scanner->pre_magnification = 1000; + scanner->pre_unit = 8192; + scanner->pre_x_offset = scanner->pre_y_offset = 578; + /* initialize the offset with a fake unprobable value, + * If there is a post scriptum section, this value will be overriden by the real life value */ + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->reader->line_number = 1; + + /* TODO: cleanup + * In some (all?) cases SYNCTEX_START is already initialized + * in synctex_reader_init_with_output_file(). Much of the + * following code seems like a duplicate and is perhaps a + * candidate for deletion. To be on the safe side though, we + * keep it for now and just free() any prior malloc() if + * existing. */ + _synctex_free(SYNCTEX_START); + + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if (NULL == SYNCTEX_START) { + _synctex_error("! malloc error in synctex_scanner_parse."); + bailey: +#ifdef SYNCTEX_DEBUG + return scanner; +#else + synctex_scanner_free(scanner); + return NULL; +#endif + } + synctex_scanner_set_display_switcher(scanner, 1000); + SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; + /* SYNCTEX_END always points to a null terminating character. + * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. + * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ + *SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_END; +# if defined(SYNCTEX_USE_CHARINDEX) + scanner->reader->charindex_offset = -SYNCTEX_BUFFER_SIZE; +# endif + status = _synctex_scan_preamble(scanner); + if (status500 + synctex_scanner_set_display_switcher(scanner, 100); + synctex_node_display(scanner->sheet); + synctex_node_display(scanner->form); +#endif + synctex_scanner_set_display_switcher(scanner, 1000); + /* Everything is finished, free the buffer, close the file */ + free((void *)SYNCTEX_START); + SYNCTEX_START = SYNCTEX_CUR = SYNCTEX_END = NULL; + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + /* Final tuning: set the default values for various parameters */ + /* 1 pre_unit = (scanner->pre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp + * 1 pt = 65536 sp */ + if (scanner->pre_unit<=0) { + scanner->pre_unit = 8192; + } + if (scanner->pre_magnification<=0) { + scanner->pre_magnification = 1000; + } + if (scanner->unit <= 0) { + /* no post magnification */ + scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ + } else { + /* post magnification */ + scanner->unit *= scanner->pre_unit / 65781.76; + } + scanner->unit *= scanner->pre_magnification / 1000.0; + if (scanner->x_offset > 6e23) { + /* no post offset */ + scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); + scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); + } else { + /* post offset */ + scanner->x_offset /= 65781.76f; + scanner->y_offset /= 65781.76f; + } + return scanner; +#undef SYNCTEX_FILE +} + +/* Scanner accessors. + */ +int synctex_scanner_pre_x_offset(synctex_scanner_p scanner){ + return scanner?scanner->pre_x_offset:0; +} +int synctex_scanner_pre_y_offset(synctex_scanner_p scanner){ + return scanner?scanner->pre_y_offset:0; +} +int synctex_scanner_x_offset(synctex_scanner_p scanner){ + return scanner?scanner->x_offset:0; +} +int synctex_scanner_y_offset(synctex_scanner_p scanner){ + return scanner?scanner->y_offset:0; +} +float synctex_scanner_magnification(synctex_scanner_p scanner){ + return scanner?scanner->unit:1; +} +void synctex_scanner_display(synctex_scanner_p scanner) { + if (NULL == scanner) { + return; + } + printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->reader->output,scanner->output_fmt,scanner->version); + printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); + printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", + scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); + printf("The input:\n"); + synctex_node_display(scanner->input); + if (scanner->count<1000) { + printf("The sheets:\n"); + synctex_node_display(scanner->sheet); + printf("The friends:\n"); + if (scanner->lists_of_friends) { + int i = scanner->number_of_lists; + synctex_node_p node; + while(i--) { + printf("Friend index:%i\n",i); + node = (scanner->lists_of_friends)[i]; + while(node) { + printf("%s:%i,%i\n", + synctex_node_isa(node), + _synctex_data_tag(node), + _synctex_data_line(node) + ); + node = _synctex_tree_friend(node); + } + } + } + } else { + printf("SyncTeX Warning: Too many objects\n"); + } +} +/* Public */ +const char * synctex_scanner_get_name(synctex_scanner_p scanner,int tag) { + synctex_node_p input = NULL; + if (NULL == scanner) { + return NULL; + } + if ((input = scanner->input)) {; + do { + if (tag == _synctex_data_tag(input)) { + return (_synctex_data_name(input)); + } + } while((input = __synctex_tree_sibling(input))); + } + return NULL; +} +const char * synctex_node_get_name(synctex_node_p node) { + if (node) { + return synctex_scanner_get_name(node->class->scanner,_synctex_data_tag(node)); + } + return NULL; +} + +static int _synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name); +static int _synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name) { + synctex_node_p input = NULL; + if (NULL == scanner) { + return 0; + } + if ((input = scanner->input)) { + do { + if (_synctex_is_equivalent_file_name(name,(_synctex_data_name(input)))) { + return _synctex_data_tag(input); + } + } while((input = __synctex_tree_sibling(input))); + } + /* 2011 version */ + name = _synctex_base_name(name); + if ((input = scanner->input)) { + do { + if (_synctex_is_equivalent_file_name(name,_synctex_base_name(_synctex_data_name(input)))) { + synctex_node_p other_input = input; + while((other_input = __synctex_tree_sibling(other_input))) { + if (_synctex_is_equivalent_file_name(name,_synctex_base_name(_synctex_data_name(other_input))) + && (strlen(_synctex_data_name(input))!=strlen(_synctex_data_name(other_input)) + || strncmp(_synctex_data_name(other_input),_synctex_data_name(input),strlen(_synctex_data_name(input))))) { + /* There is a second possible candidate */ + return 0; + } + } + return _synctex_data_tag(input); + } + } while((input = __synctex_tree_sibling(input))); + } + return 0; +} + +int synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name) { + size_t char_index = strlen(name); + if ((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { + /* the name is not void */ + char_index -= 1; + if (!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { + /* the last character of name is not a path separator */ + int result = _synctex_scanner_get_tag(scanner,name); + if (result) { + return result; + } else { + /* the given name was not the one known by TeX + * try a name relative to the enclosing directory of the scanner->output file */ + const char * relative = name; + const char * ptr = scanner->reader->output; + while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) + { + relative += 1; + ptr += 1; + } + /* Find the last path separator before relative */ + while(relative > name) { + if (SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { + break; + } + relative -= 1; + } + if ((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { + return result; + } + if (SYNCTEX_IS_PATH_SEPARATOR(name[0])) { + /* No tag found for the given absolute name, + * Try each relative path starting from the shortest one */ + while(0input:NULL; +} +synctex_node_p synctex_scanner_input_with_tag(synctex_scanner_p scanner, int tag) { + synctex_node_p input = scanner?scanner->input:NULL; + while (_synctex_data_tag(input)!=tag) { + if ((input = __synctex_tree_sibling(input))) { + continue; + } + break; + } + return input; +} +const char * synctex_scanner_get_output_fmt(synctex_scanner_p scanner) { + return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; +} +const char * synctex_scanner_get_output(synctex_scanner_p scanner) { + return NULL != scanner && scanner->reader->output?scanner->reader->output:""; +} +const char * synctex_scanner_get_synctex(synctex_scanner_p scanner) { + return NULL != scanner && scanner->reader->synctex?scanner->reader->synctex:""; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node attributes +# endif + +# define SYNCTEX_DEFINE_NODE_HVWHD(WHAT) \ +int synctex_node_##WHAT(synctex_node_p node) { \ + return (node && node->class->inspector->WHAT)? \ + node->class->inspector->WHAT(node): 0; \ +} +# define SYNCTEX_DEFINE_PROXY_HV(WHAT) \ +static int _synctex_proxy_##WHAT(synctex_proxy_p proxy) { \ + synctex_node_p target = _synctex_tree_target(proxy); \ + if (target) { \ + return _synctex_data_##WHAT(proxy)+synctex_node_##WHAT(target); \ + } else { \ + return proxy? _synctex_data_##WHAT(proxy): 0; \ + } \ +} +#define SYNCTEX_DEFINE_PROXY_TLCWVD(WHAT) \ +static int _synctex_proxy_##WHAT(synctex_proxy_p proxy) { \ + synctex_node_p target = _synctex_tree_target(proxy); \ + return target? synctex_node_##WHAT(target): 0; \ +} + +/** + * The horizontal location of the node. + * Idem for v, width, height and depth. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - requires: every proxy node has a target. + * - note: recursive call if the parameter has a proxy. + * - author: JL + */ +SYNCTEX_DEFINE_NODE_HVWHD(h) +SYNCTEX_DEFINE_NODE_HVWHD(v) +SYNCTEX_DEFINE_NODE_HVWHD(width) +SYNCTEX_DEFINE_NODE_HVWHD(height) +SYNCTEX_DEFINE_NODE_HVWHD(depth) +SYNCTEX_DEFINE_PROXY_TLCWVD(tag) +SYNCTEX_DEFINE_PROXY_TLCWVD(line) +SYNCTEX_DEFINE_PROXY_TLCWVD(column) +SYNCTEX_DEFINE_PROXY_HV(h) +SYNCTEX_DEFINE_PROXY_HV(v) +SYNCTEX_DEFINE_PROXY_TLCWVD(width) +SYNCTEX_DEFINE_PROXY_TLCWVD(height) +SYNCTEX_DEFINE_PROXY_TLCWVD(depth) + +/** + * Whether the argument is a box, + * either vertical or horizontal, + * either void or not, + * or a proxy to such a box. + * - parameter NODE: of type synctex_node_p + * - returns: yorn + */ + +SYNCTEX_INLINE static synctex_bool_t _synctex_node_is_box(synctex_node_p node) { + return node && + (node->class->type == synctex_node_type_hbox + || node->class->type == synctex_node_type_void_hbox + || node->class->type == synctex_node_type_vbox + || node->class->type == synctex_node_type_void_vbox + || _synctex_node_is_box(_synctex_tree_target(node))); +} + +/** + * Whether the argument is a handle. + * Handles are similar to proxies because they have a target. + * They are used for query results. + * - parameter NODE: of type synctex_node_p + * - returns: yorn + */ + +SYNCTEX_INLINE static synctex_bool_t _synctex_node_is_handle(synctex_node_p node) { + return node && + (node->class->type == synctex_node_type_handle); +} + +/** + * Resolves handle indirection. + * - parameter node: of type synctex_node_p + * - returns: node if it is not a handle, + * its target otherwise. + */ + +SYNCTEX_INLINE static synctex_node_p _synctex_node_or_handle_target(synctex_node_p node) { + return _synctex_node_is_handle(node)? + _synctex_tree_target(node):node; +} + +/** + * Whether the argument is an hbox. + * - parameter NODE: of type synctex_node_p + * - returns: yorn + */ + +SYNCTEX_INLINE static synctex_bool_t _synctex_node_is_hbox(synctex_node_p node) { + return node && + (node->class->type == synctex_node_type_hbox + || node->class->type == synctex_node_type_void_hbox + || _synctex_node_is_hbox(_synctex_tree_target(node))); +} + +/** + * The horizontal location of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_h(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_h(node); + } + return 0; +} +/** + * The vertical location of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_v(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_v(node); + } + return 0; +} +/** + * The width of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_width(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_width(node); + } + return 0; +} +/** + * The height of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_height(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_height(node); + } + return 0; +} +/** + * The depth of the first box enclosing node. + * - parameter node: a node with geometrical information. + * - returns: an integer. + * - author: JL + */ +int synctex_node_box_depth(synctex_node_p node) { + if (_synctex_node_is_box(node) || (node = _synctex_tree_parent(node))) { + return synctex_node_depth(node); + } + return 0; +} +/** + * The horizontal location of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - note: recursive call when node is an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_h(synctex_node_p node) { + switch(synctex_node_type(node)) { + case synctex_node_type_hbox: + return _synctex_data_h_V(node); + case synctex_node_type_proxy_hbox: + return _synctex_data_h(node)+synctex_node_hbox_h(_synctex_tree_target(node)); + default: + return 0; + } +} +/** + * The vertical location of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - note: recursive call when node is an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_v(synctex_node_p node) { + switch(synctex_node_type(node)) { + case synctex_node_type_hbox: + return _synctex_data_v_V(node); + case synctex_node_type_proxy_hbox: + return _synctex_data_v(node)+synctex_node_hbox_v(_synctex_tree_target(node)); + default: + return 0; + } +} +/** + * The width of an hbox, corrected with contents. + * - parameter node: an hbox node, 0 if node is not an hbox or an hbox proxy. + * - returns: an integer. + * - author: JL + */ +int synctex_node_hbox_width(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return synctex_node_type(node) == synctex_node_type_hbox? + _synctex_data_width_V(node): 0; +} +/** + * The height of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_height(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return synctex_node_type(node) == synctex_node_type_hbox? + _synctex_data_height_V(node): 0; +} +/** + * The depth of an hbox, corrected with contents. + * - parameter node: an hbox node. + * - returns: an integer, 0 if node is not an hbox or an hbox proxy. + * - note: recursive call when node is an hbox proxy. + * - author: JL + */ +int synctex_node_hbox_depth(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return synctex_node_type(node) == synctex_node_type_hbox? + _synctex_data_depth_V(node): 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node visible attributes +# endif + +#define SYNCTEX_VISIBLE_SIZE(node,s) \ +(s)*node->class->scanner->unit +#define SYNCTEX_VISIBLE_DISTANCE_h(node,d) \ +((d)*node->class->scanner->unit+node->class->scanner->x_offset) +#define SYNCTEX_VISIBLE_DISTANCE_v(node,d) \ +((d)*node->class->scanner->unit+node->class->scanner->y_offset) +static float __synctex_node_visible_h(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_h(node,synctex_node_h(node)); +} +static float __synctex_node_visible_v(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_v(node,synctex_node_v(node)); +} +static float __synctex_node_visible_width(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,synctex_node_width(node)); +} +static float __synctex_node_visible_height(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,synctex_node_height(node)); +} +static float __synctex_node_visible_depth(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,synctex_node_depth(node)); +} +static float __synctex_proxy_visible_h(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_h(node,synctex_node_h(node)); +} +static float __synctex_proxy_visible_v(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_v(node,synctex_node_v(node)); +} +static float __synctex_proxy_visible_width(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return __synctex_node_visible_width(target); +} +static float __synctex_proxy_visible_height(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return __synctex_node_visible_height(target); +} +static float __synctex_proxy_visible_depth(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return __synctex_node_visible_depth(target); +} +static float __synctex_kern_visible_h(synctex_noxy_p noxy) { + int h = _synctex_data_h(noxy); + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_DISTANCE_h(noxy, width>0?h-width:h); +} +static float __synctex_kern_visible_width(synctex_noxy_p noxy) { + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_SIZE(noxy, width>0?width:-width); +} +static float __synctex_rule_visible_h(synctex_noxy_p noxy) { + int h = _synctex_data_h(noxy); + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_DISTANCE_h(noxy, width>0?h:h-width); +} +static float __synctex_rule_visible_width(synctex_noxy_p noxy) { + int width = _synctex_data_width(noxy); + return SYNCTEX_VISIBLE_SIZE(noxy, width>0?width:-width); +} +static float __synctex_rule_visible_v(synctex_noxy_p noxy) { + return __synctex_node_visible_v(noxy); +} +static float __synctex_rule_visible_height(synctex_noxy_p noxy) { + return __synctex_node_visible_height(noxy); +} +static float __synctex_rule_visible_depth(synctex_noxy_p noxy) { + return __synctex_node_visible_depth(noxy); +} + +/** + * The horizontal location of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_h(synctex_node_p node){ + return node? node->class->vispector->h(node): 0; +} +/** + * The vertical location of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_v(synctex_node_p node){ + return node? node->class->vispector->v(node): 0; +} +/** + * The width of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_width(synctex_node_p node){ + return node? node->class->vispector->width(node): 0; +} +/** + * The height of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_height(synctex_node_p node){ + return node? node->class->vispector->height(node): 0; +} +/** + * The depth of node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_visible_depth(synctex_node_p node){ + return node? node->class->vispector->depth(node): 0; +} + +/** + * The V variant of geometrical information. + * - parameter node: a node. + * - returns: an integer. + * - author: JL + */ +#define SYNCTEX_DEFINE_V(WHAT)\ +SYNCTEX_INLINE static int _synctex_node_##WHAT##_V(synctex_node_p node) { \ + synctex_node_p target = _synctex_tree_target(node); \ + if (target) { \ + return _synctex_data_##WHAT(node)+_synctex_node_##WHAT##_V(target); \ + } else if (_synctex_data_has_##WHAT##_V(node)) { \ + return _synctex_data_##WHAT##_V(node); \ + } else { \ + return _synctex_data_##WHAT(node); \ + } \ +} +SYNCTEX_DEFINE_V(h) +SYNCTEX_DEFINE_V(v) +SYNCTEX_DEFINE_V(width) +SYNCTEX_DEFINE_V(height) +SYNCTEX_DEFINE_V(depth) + +SYNCTEX_INLINE static synctex_point_s _synctex_data_point(synctex_node_p node) { + return (synctex_point_s){synctex_node_h(node),synctex_node_v(node)}; +} +SYNCTEX_INLINE static synctex_point_s _synctex_data_point_V(synctex_node_p node) { + return (synctex_point_s){_synctex_node_h_V(node),_synctex_node_v_V(node)}; +} +SYNCTEX_INLINE static synctex_point_s _synctex_data_set_point(synctex_node_p node, synctex_point_s point) { + synctex_point_s old = _synctex_data_point(node); + _synctex_data_set_h(node,point.h); + _synctex_data_set_v(node,point.v); + return old; +} +SYNCTEX_INLINE static synctex_box_s _synctex_data_box(synctex_node_p node) { + synctex_box_s box = {{0,0},{0,0}}; + int n; + n = synctex_node_width(node); + if (n<0) { + box.max.h = synctex_node_h(node); + box.min.h = box.max.h + n; + } else { + box.min.h = synctex_node_h(node); + box.max.h = box.min.h + n; + } + n = synctex_node_v(node); + box.min.v = n - synctex_node_height(node); + box.max.v = n + synctex_node_depth(node); + return box; +} +SYNCTEX_INLINE static synctex_box_s _synctex_data_xob(synctex_node_p node) { + synctex_box_s box = {{0,0},{0,0}}; + int n; + n = synctex_node_width(node); + if (n>0) { + box.max.h = synctex_node_h(node); + box.min.h = box.max.h - n; + } else { + box.min.h = synctex_node_h(node); + box.max.h = box.min.h - n; + } + n = synctex_node_v(node); + box.min.v = n - synctex_node_height(node); + box.max.v = n + synctex_node_depth(node); + return box; +} +SYNCTEX_INLINE static synctex_box_s _synctex_data_box_V(synctex_node_p node) { + synctex_box_s box = {{0,0},{0,0}}; + int n; + n = _synctex_node_width_V(node); + if (n<0) { + box.max.h = _synctex_node_h_V(node); + box.min.h = box.max.h + n; + } else { + box.min.h = _synctex_node_h_V(node); + box.max.h = box.min.h + n; + } + n = _synctex_node_v_V(node); + box.min.v = n - _synctex_node_height_V(node); + box.max.v = n + _synctex_node_depth_V(node); + return box; +} + +/** + * The higher box node in the parent hierarchy which + * mean line number is the one of node ±1. + * This enclosing box is computed as follows + * 1) get the first hbox in the parent linked list + * starting at node. + * If there is none, simply return the parent of node. + * 2) compute the mean line number + * 3) scans up the tree for the higher hbox with + * the same mean line number, ±1 eventually +* - parameter node: a node. + * - returns: a (proxy to a) box node. + * - author: JL + */ +static synctex_node_p _synctex_node_box_visible(synctex_node_p node) { + if ((node = _synctex_node_or_handle_target(node))) { + int mean = 0; + int bound = 1500000/(node->class->scanner->pre_magnification/1000); + synctex_node_p parent = NULL; + /* get the first enclosing parent + * then get the highest enclosing parent with the same mean line ±1 */ + node = _synctex_node_or_handle_target(node); + if (!_synctex_node_is_box(node)) { + if ((parent = _synctex_tree_parent(node))) { + node = parent; + } else if ((node = _synctex_tree_target(node))) { + if (!_synctex_node_is_box(node)) { + if ((parent = _synctex_tree_parent(node))) { + node = parent; + } else { + return NULL; + } + } + } + } + parent = node; + mean = synctex_node_mean_line(node); + while ((parent = _synctex_tree_parent(parent))) { + if (_synctex_node_is_hbox(parent)) { + if (_synctex_abs(mean-synctex_node_mean_line(parent))>1) { + return node; + } else if (synctex_node_width(parent)>bound) { + return parent; + } else if (synctex_node_height(parent)+synctex_node_depth(parent)>bound) { + return parent; + } + node = parent; + } + } + } + return node; +} +/** + * The horizontal location of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_h(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_h(node,_synctex_node_h_V(_synctex_node_box_visible(node))); +} +/** + * The vertical location of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_v(synctex_node_p node) { + return SYNCTEX_VISIBLE_DISTANCE_v(node,_synctex_node_v_V(_synctex_node_box_visible(node))); +} +/** + * The width of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_width(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,_synctex_node_width_V(_synctex_node_box_visible(node))); +} +/** + * The height of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_height(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,_synctex_node_height_V(_synctex_node_box_visible(node))); +} +/** + * The depth of the first box enclosing node, in page coordinates. + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ +float synctex_node_box_visible_depth(synctex_node_p node) { + return SYNCTEX_VISIBLE_SIZE(node,_synctex_node_depth_V(_synctex_node_box_visible(node))); +} +# ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark SCANNER +# pragma mark Other public node attributes # endif -/* Here are gathered all the possible status that the next scanning functions will return. - * All these functions return a status, and pass their result through pointers. - * Negative values correspond to errors. - * The management of the buffer is causing some significant overhead. - * Every function that may access the buffer returns a status related to the buffer and file state. - * status >= SYNCTEX_STATUS_OK means the function worked as expected - * status < SYNCTEX_STATUS_OK means the function did not work as expected - * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. - * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. - * statusfile) +#if defined (SYNCTEX_USE_CHARINDEX) +synctex_charindex_t synctex_node_charindex(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + return target? SYNCTEX_CHARINDEX(target):(node?SYNCTEX_CHARINDEX(node):0); +} +#endif -/* Actually, the minimum buffer size is driven by integer and float parsing. - * ±0.123456789e123 +/** + * The tag of the node. + * - parameter node: a node. + * - returns: the tag or -1 if node is NULL. + * - author: JL */ -# define SYNCTEX_BUFFER_MIN_SIZE 16 -# define SYNCTEX_BUFFER_SIZE 32768 - +int synctex_node_tag(synctex_node_p node) { + return node? node->class->tlcpector->tag(node): -1; +} +/** + * The line of the node. + * - parameter node: a node. + * - returns: the line or -1 if node is NULL. + * - author: JL + */ +int synctex_node_line(synctex_node_p node) { + return node? node->class->tlcpector->line(node): -1; +} +/** + * The column of the node. + * - parameter node: a node. + * - returns: the column or -1 if node is NULL. + * - author: JL + */ +int synctex_node_column(synctex_node_p node) { + return node? node->class->tlcpector->column(node): -1; +} +/** + * The mean line number of the node. + * - parameter node: a node. + * - returns: the mean line or -1 if node is NULL. + * - author: JL + */ +int synctex_node_mean_line(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return _synctex_data_has_mean_line(node)? + _synctex_data_mean_line(node):_synctex_data_line(node); +} +/** + * The weight of the node. + * - parameter node: a node. + * - returns: the weight or -1 if node is NULL. + * - author: JL + */ +int synctex_node_weight(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return node?(synctex_node_type(node)==synctex_node_type_hbox?_synctex_data_weight(node):0):-1; +} +/** + * The number of children of the node. + * - parameter node: a node. + * - returns: the count or -1 if node is NULL. + * - author: JL + */ +int synctex_node_child_count(synctex_node_p node) { + synctex_node_p target = _synctex_tree_target(node); + if (target) { + node = target; + } + return node?(synctex_node_type(node)==synctex_node_type_hbox?_synctex_data_weight(node):0):-1; +} # ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark Prototypes +# pragma mark Sheet & Form # endif -void _synctex_log_void_box(synctex_node_t node); -void _synctex_log_box(synctex_node_t node); -void _synctex_log_horiz_box(synctex_node_t node); -void _synctex_log_input(synctex_node_t node); -synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr); -synctex_status_t _synctex_next_line(synctex_scanner_t scanner); -synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string); -synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref); -synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref); -synctex_status_t _synctex_scan_input(synctex_scanner_t scanner); -synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner); -synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref); -synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner); -int _synctex_scan_postamble(synctex_scanner_t scanner); -synctex_status_t _synctex_setup_visible_box(synctex_node_t box); -synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v); -synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t parent); -synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner); -synctex_status_t _synctex_scan_content(synctex_scanner_t scanner); -int synctex_scanner_pre_x_offset(synctex_scanner_t scanner); -int synctex_scanner_pre_y_offset(synctex_scanner_t scanner); -const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner); -int _synctex_node_is_box(synctex_node_t node); -int _synctex_bail(void); - -/* Try to ensure that the buffer contains at least size bytes. - * Passing a huge size argument means the whole buffer length. - * Passing a null size argument means return the available buffer length, without reading the file. - * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL, - * in which case, SYNCTEX_STATUS_BAD_ARGUMENT is returned. - * The value returned in size_ptr is the number of bytes now available in the buffer. - * This is a nonnegative integer, it may take the value 0. - * It is the responsibility of the caller to test whether this size is conforming to its needs. - * Negative values may return in case of error, actually - * when there was an error reading the synctex file. */ -synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr) { - size_t available = 0; - if (NULL == scanner || NULL == size_ptr) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } -# define size (* size_ptr) - if (size>SYNCTEX_BUFFER_SIZE){ - size = SYNCTEX_BUFFER_SIZE; - } - available = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ - if (size<=available) { - /* There are already sufficiently many characters in the buffer */ - size = available; - return SYNCTEX_STATUS_OK; - } - if (SYNCTEX_FILE) { - /* Copy the remaining part of the buffer to the beginning, - * then read the next part of the file */ - int already_read = 0; - if (available) { - memmove(SYNCTEX_START, SYNCTEX_CUR, available); - } - SYNCTEX_CUR = SYNCTEX_START + available; /* the next character after the move, will change. */ - /* Fill the buffer up to its end */ - already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,SYNCTEX_BUFFER_SIZE - available); - if (already_read>0) { - /* We assume that 0already_read) { - /* There is a possible error in reading the file */ - int errnum = 0; - const char * error_string = gzerror(SYNCTEX_FILE, &errnum); - if (Z_ERRNO == errnum) { - /* There is an error in zlib caused by the file system */ - _synctex_error("gzread error from the file system (%i)",errno); - return SYNCTEX_STATUS_ERROR; - } else if (errnum) { - _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); - return SYNCTEX_STATUS_ERROR; - } - } - /* Nothing was read, we are at the end of the file. */ - gzclose(SYNCTEX_FILE); - SYNCTEX_FILE = NULL; - SYNCTEX_END = SYNCTEX_CUR; - SYNCTEX_CUR = SYNCTEX_START; - * SYNCTEX_END = '\0';/* Terminate the string properly.*/ - size = SYNCTEX_END - SYNCTEX_CUR; - return SYNCTEX_STATUS_EOF; /* there might be a bit of text left */ + +/** + * The sheet of the scanner with a given page number. + * - parameter scanner: a scanner. + * - parameter page: a 1 based page number. + * If page == 0, returns the first sheet. + * - returns: a sheet or NULL. + * - author: JL + */ +synctex_node_p synctex_sheet(synctex_scanner_p scanner,int page) { + if (scanner) { + synctex_node_p sheet = scanner->sheet; + while(sheet) { + if (page == _synctex_data_page(sheet)) { + return sheet; + } + sheet = __synctex_tree_sibling(sheet); + } + if (page == 0) { + return scanner->sheet; + } } - /* We cannot enlarge the buffer because the end of the file was reached. */ - size = available; - return SYNCTEX_STATUS_EOF; -# undef size + return NULL; } - -/* Used when parsing the synctex file. - * Advance to the next character starting a line. - * Actually, only '\n' is recognized as end of line marker. - * On normal completion, the returned value is the number of unparsed characters available in the buffer. - * In general, it is a positive value, 0 meaning that the end of file was reached. - * -1 is returned in case of error, actually because there was an error while feeding the buffer. - * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. - * J. Laurens: Sat May 10 07:52:31 UTC 2008 +/** + * The form of the scanner with a given tag. + * - parameter scanner: a scanner. + * - parameter tag: an integer identifier. + * If tag == 0, returns the first form. + * - returns: a form. + * - author: JL */ -synctex_status_t _synctex_next_line(synctex_scanner_t scanner) { - synctex_status_t status = SYNCTEX_STATUS_OK; - size_t available = 0; - if (NULL == scanner) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } -infinite_loop: - while(SYNCTEX_CURform; + while(form) { + if (tag == _synctex_data_tag(form)) { + return form; + } + form = __synctex_tree_sibling(form); + } + if (tag == 0) { + return scanner->form; + } + } + return NULL; } -/* Scan the given string. - * Both scanner and the_string must not be NULL, and the_string must not be 0 length. - * SYNCTEX_STATUS_OK is returned if the string is found, - * SYNCTEX_STATUS_EOF is returned when the EOF is reached, - * SYNCTEX_STATUS_NOT_OK is returned is the string is not found, - * an error status is returned otherwise. - * This is a critical method because buffering renders things more difficult. - * The given string might be as long as the maximum size_t value. - * As side effect, the buffer state may have changed if the given argument string can't fit into the buffer. +/** + * The content of the sheet with given page number. + * - parameter scanner: a scanner. + * - parameter page: a 1 based page number. + * - returns: a (vertical) box node. + * - author: JL */ -synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string) { - size_t tested_len = 0; /* the number of characters at the beginning of the_string that match */ - size_t remaining_len = 0; /* the number of remaining characters of the_string that should match */ - size_t available = 0; - synctex_status_t status = 0; - if (NULL == scanner || NULL == the_string) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - remaining_len = strlen(the_string); /* All the_string should match */ - if (0 == remaining_len) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - /* How many characters available in the buffer? */ - available = remaining_len; - status = _synctex_buffer_get_available_size(scanner,&available); - if (status=remaining_len) { - /* The buffer is sufficiently big to hold the expected number of characters. */ - if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { - return SYNCTEX_STATUS_NOT_OK; - } -return_OK: - /* Advance SYNCTEX_CUR to the next character after the_string. */ - SYNCTEX_CUR += remaining_len; - return SYNCTEX_STATUS_OK; - } else if (strncmp((char *)SYNCTEX_CUR,the_string,available)) { - /* No need to goo further, this is not the expected string in the buffer. */ - return SYNCTEX_STATUS_NOT_OK; - } else if (SYNCTEX_FILE) { - /* The buffer was too small to contain remaining_len characters. - * We have to cut the string into pieces. */ - z_off_t offset = 0L; - /* the first part of the string is found, advance the_string to the next untested character. */ - the_string += available; - /* update the remaining length and the parsed length. */ - remaining_len -= available; - tested_len += available; - SYNCTEX_CUR += available; /* We validate the tested characters. */ - if (0 == remaining_len) { - /* Nothing left to test, we have found the given string, we return the length. */ - return tested_len; - } - /* We also have to record the current state of the file cursor because - * if the_string does not match, all this should be a totally blank operation, - * for which the file and buffer states should not be modified at all. - * In fact, the states of the buffer before and after this function are in general different - * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR - * can be safely discarded. */ - offset = gztell(SYNCTEX_FILE); - /* offset now corresponds to the first character of the file that was not buffered. */ - available = SYNCTEX_CUR - SYNCTEX_START; /* available can be used as temporary placeholder. */ - /* available now corresponds to the number of chars that where already buffered and - * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered - * because the buffer contents is completely replaced by _synctex_buffer_get_available_size. - * They were buffered from offset-len location in the file. */ - offset -= available; -more_characters: - /* There is still some work to be done, so read another bunch of file. - * This is the second call to _synctex_buffer_get_available_size, - * which means that the actual contents of the buffer will be discarded. - * We will definitely have to recover the previous state in case we do not find the expected string. */ - available = remaining_len; - status = _synctex_buffer_get_available_size(scanner,&available); - if (statusptr) { - SYNCTEX_CUR = end; - if (value_ref) { - * value_ref = result; - } - return SYNCTEX_STATUS_OK;/* Successfully scanned an int */ - } - return SYNCTEX_STATUS_NOT_OK;/* Could not scan an int */ +synctex_node_p synctex_form_content(synctex_scanner_p scanner,int tag) { + if (scanner) { + return _synctex_tree_child(synctex_form(scanner,tag)); + } + return NULL; } -/* The purpose of this function is to read a string. - * A string is an array of characters from the current parser location - * and before the next '\n' character. - * If a string was properly decoded, it is returned in value_ref and - * the cursor points to the new line marker. - * The returned string was alloced on the heap, the caller is the owner and - * is responsible to free it in due time. - * If no string is parsed, * value_ref is undefined. - * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. - * If you just want to blindly parse the file up to the end of the current line, - * use _synctex_next_line instead. - * On return, the scanner cursor is unchanged if a string could not be scanned or - * points to the terminating '\n' character otherwise. As a consequence, - * _synctex_next_line is necessary after. - * If either scanner or value_ref is NULL, it is considered as an error and - * SYNCTEX_STATUS_BAD_ARGUMENT is returned. - */ -synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref) { - char * end = NULL; - size_t current_size = 0; - size_t new_size = 0; - size_t len = 0;/* The number of bytes to copy */ - size_t available = 0; - synctex_status_t status = 0; - if (NULL == scanner || NULL == value_ref) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - /* The buffer must at least contain one character: the '\n' end of line marker */ - if (SYNCTEX_CUR>=SYNCTEX_END) { - available = 1; - status = _synctex_buffer_get_available_size(scanner,&available); - if (status < 0) { - return status; - } - if (0 == available) { - return SYNCTEX_STATUS_EOF; - } - } - /* Now we are sure that there is at least one available character, either because - * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ - /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ - end = SYNCTEX_CUR; - * value_ref = NULL;/* Initialize, it will be realloc'ed */ - /* We scan all the characters up to the next '\n' */ -next_character: - if (endUINT_MAX-len-1) { - /* But we have reached the limit: we do not have current_size+len+1>UINT_MAX. - * We return the missing amount of memory. - * This will never occur in practice. */ - return UINT_MAX-len-1 - current_size; - } - new_size = current_size+len; - /* We have current_size+len+1<=UINT_MAX - * or equivalently new_sizeUINT_MAX-len-1) { - /* We have reached the limit. */ - _synctex_error("limit reached (missing %lu).",current_size-(UINT_MAX-len-1)); - return SYNCTEX_STATUS_ERROR; - } - new_size = current_size+len; - if ((* value_ref = realloc(* value_ref,new_size+1)) != NULL) { - if (memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) { - (* value_ref)[new_size]='\0'; /* Terminate the string */ - SYNCTEX_CUR = SYNCTEX_END;/* Advance the cursor to the end of the bufer */ - return SYNCTEX_STATUS_OK; - } - free(* value_ref); - * value_ref = NULL; - _synctex_error("could not copy memory (2)."); - return SYNCTEX_STATUS_ERROR; - } - /* Huge memory problem */ - _synctex_error("could not allocate memory (2)."); - return SYNCTEX_STATUS_ERROR; - } +SYNCTEX_INLINE static synctex_node_p _synctex_scanner_friend(synctex_scanner_p scanner,int i) { + if (i>=0) { + i = _synctex_abs(i)%(scanner->number_of_lists); + return (scanner->lists_of_friends)[i]; + } + return NULL; +} +SYNCTEX_INLINE static synctex_bool_t _synctex_nodes_are_friend(synctex_node_p left, synctex_node_p right) { + return synctex_node_tag(left) == synctex_node_tag(right) && synctex_node_line(left) == synctex_node_line(right); +} +SYNCTEX_INLINE static synctex_node_p _synctex_vertically_sorted_v2(synctex_node_p sibling) { + synctex_node_p child = NULL; + synctex_node_p best_child = sibling; + synctex_node_p next_child = _synctex_tree_reset_child(best_child); + synctex_node_p target = _synctex_tree_target(best_child); + synctex_node_p parent = _synctex_tree_parent(target); + unsigned int best_count = 0; + unsigned int count = 0; + synctex_node_p N = _synctex_tree_child(parent); + do { + if (_synctex_nodes_are_friend(N,best_child)) { + ++best_count; + } + } while ((N = __synctex_tree_sibling(N))); + /* Navigate through the other children */ + while ((child = next_child)) { + next_child = _synctex_tree_reset_child(child); + target = _synctex_tree_target(child); + parent = _synctex_tree_parent(target); + count = 0; + N = _synctex_tree_child(parent); + do { + if (_synctex_nodes_are_friend(N,best_child)) { + ++count; + } + } while ((N = __synctex_tree_sibling(N))); + if (count>best_count) { + best_count = count; + synctex_node_free(best_child); + best_child = child; + } else { + synctex_node_free(child); + } + } + return best_child; } -/* Used when parsing the synctex file. - * Read an Input record. - */ -synctex_status_t _synctex_scan_input(synctex_scanner_t scanner) { - synctex_status_t status = 0; - size_t available = 0; - synctex_node_t input = NULL; - if (NULL == scanner) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - status = _synctex_match_string(scanner,"Input:"); - if (statusinput); - scanner->input = input; - return _synctex_next_line(scanner);/* read the line termination character, if any */ - /* Now, set up the path */ -} - -typedef synctex_status_t (*synctex_decoder_t)(synctex_scanner_t,void *); - -synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder); +SYNCTEX_INLINE static synctex_bool_t _synctex_point_in_box_v2(synctex_point_p hitP, synctex_node_p node); -/* Used when parsing the synctex file. - * Read one of the settings. - * On normal completion, returns SYNCTEX_STATUS_OK. - * On error, returns SYNCTEX_STATUS_ERROR. - * Both arguments must not be NULL. - * On return, the scanner points to the next character after the decoded object whatever it is. - * It is the responsibility of the caller to prepare the scanner for the next line. - */ -synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder) { - synctex_status_t status = 0; - if (NULL == scanner || NULL == name || NULL == value_ref || NULL == decoder) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } -not_found: - status = _synctex_match_string(scanner,name); - if (statusseed = iterator->next = result; + iterator->count0 = iterator->count = count; + } + return iterator; +}; + +void synctex_iterator_free(synctex_iterator_p iterator) { + if (iterator) { + synctex_node_free(iterator->seed); + _synctex_free(iterator); + } +} +synctex_bool_t synctex_iterator_has_next(synctex_iterator_p iterator) { + return iterator?iterator->count>0:0; +} +int synctex_iterator_count(synctex_iterator_p iterator) { + return iterator? iterator->count: 0; } -/* Used when parsing the synctex file. - * Read the preamble. +/** + * The next result of the iterator. + * Internally, the iterator stores handles to nodes. + * Externally, it returns the targets, + * such that the caller only sees nodes. */ -synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner) { - synctex_status_t status = 0; - if (NULL == scanner) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - status = _synctex_scan_named(scanner,"SyncTeX Version:",&(scanner->version),(synctex_decoder_t)&_synctex_decode_int); - if (statusoutput_fmt),(synctex_decoder_t)&_synctex_decode_string); - if (statuspre_magnification),(synctex_decoder_t)&_synctex_decode_int); - if (statuspre_unit),(synctex_decoder_t)&_synctex_decode_int); - if (statuspre_x_offset),(synctex_decoder_t)&_synctex_decode_int); - if (statuspre_y_offset),(synctex_decoder_t)&_synctex_decode_int); - if (statuscount>0) { + synctex_node_p N = iterator->next; + iterator->next = __synctex_tree_sibling(N); + --iterator->count; + return _synctex_tree_target(N); + } + return NULL; } - -/* parse a float with a dimension */ -synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref) { - synctex_status_t status = 0; - char * endptr = NULL; - float f = 0; -#ifdef HAVE_SETLOCALE - char * loc = setlocale(LC_NUMERIC, NULL); -#endif - size_t available = 0; - if (NULL == scanner || NULL == value_ref) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - available = SYNCTEX_BUFFER_MIN_SIZE; - status = _synctex_buffer_get_available_size(scanner, &available); - if (status= SYNCTEX_STATUS_OK) { - f *= 72.27f*65536; - } else if (status= SYNCTEX_STATUS_OK) { - f *= 72.27f*65536/2.54f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) { - f *= 72.27f*65536/25.4f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) { - f *= 65536.0f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) { - f *= 72.27f/72*65536.0f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) { - f *= 12.0*65536.0f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) { - f *= 1.0f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) { - f *= 1238.0f/1157*65536.0f; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) { - f *= 14856.0f/1157*65536; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) { - f *= 685.0f/642*65536; - } else if (status<0) { - goto report_unit_error; - } else if ((status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) { - f *= 1370.0f/107*65536; - } else if (status<0) { - goto report_unit_error; - } - *value_ref = f; - return SYNCTEX_STATUS_OK; +int synctex_iterator_reset(synctex_iterator_p iterator) { + if (iterator) { + iterator->next = iterator->seed; + return iterator->count = iterator->count0; + } + return 0; } -/* parse the post scriptum - * SYNCTEX_STATUS_OK is returned on completion - * a negative error is returned otherwise */ -synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner) { - synctex_status_t status = 0; - char * endptr = NULL; -#ifdef HAVE_SETLOCALE - char * loc = setlocale(LC_NUMERIC, NULL); +synctex_iterator_p synctex_iterator_new_edit(synctex_scanner_p scanner,int page,float h,float v){ + if (scanner) { + synctex_node_p sheet = NULL; + synctex_point_s hit; + synctex_node_p node = NULL; + synctex_nd_lr_s nds = {{NULL,0},{NULL,0}}; + if (NULL == (scanner = synctex_scanner_parse(scanner)) || 0 >= scanner->unit) {/* scanner->unit must be >0 */ + return NULL; + } + /* Find the proper sheet */ + sheet = synctex_sheet(scanner,page); + if (NULL == sheet) { + return NULL; + } + /* Now sheet points to the sheet node with proper page number. */ + /* Now that scanner has been initialized, we can convert + * the given point to scanner integer coordinates */ + hit = (synctex_point_s) + {(h-scanner->x_offset)/scanner->unit, + (v-scanner->y_offset)/scanner->unit}; + /* At first, we browse all the horizontal boxes of the sheet + * until we find one containing the hit point. */ + if ((node = _synctex_tree_next_hbox(sheet))) { + do { + if (_synctex_point_in_box_v2(&hit,node)) { + /* Maybe the hit point belongs to a contained vertical box. + * This is the most likely situation. + */ + synctex_node_p next = node; +#if defined(SYNCTEX_DEBUG) + printf("--- We are lucky\n"); #endif - if (NULL == scanner) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - /* Scan the file until a post scriptum line is found */ -post_scriptum_not_found: - status = _synctex_match_string(scanner,"Post scriptum:"); - if (statusnds.r.distance) { + node = nds.r.node; + nds.r.node = nds.l.node; + nds.l.node = node; + } + if((node = _synctex_new_handle_with_target(nds.l.node))) { + synctex_node_p other_handle; + if((other_handle = _synctex_new_handle_with_target(nds.r.node))) { + _synctex_tree_set_sibling(node,other_handle); + return _synctex_iterator_new(node,2); + } + return _synctex_iterator_new(node,1); + } + return NULL; + } + /* both nodes have the same input coordinates + * We choose the one closest to the hit point */ + if (nds.l.distance>nds.r.distance) { + nds.l.node = nds.r.node; + } + nds.r.node = NULL; + } else if (nds.r.node) { + nds.l = nds.r; + } else if (!nds.l.node) { + nds.l.node = node; + } + if((node = _synctex_new_handle_with_target(nds.l.node))) { + return _synctex_iterator_new(node,1); + } + return 0; + } + } while ((node = _synctex_tree_next_hbox(node))); + /* All the horizontal boxes have been tested, + * None of them contains the hit point. + */ + } + /* We are not lucky, + * we test absolutely all the node + * to find the closest... */ + if ((node = _synctex_tree_child(sheet))) { +#if defined(SYNCTEX_DEBUG) + printf("--- We are not lucky\n"); #endif - scanner->unit = strtod(SYNCTEX_CUR,&endptr); -#ifdef HAVE_SETLOCALE - setlocale(LC_NUMERIC, loc); + nds.l = __synctex_closest_deep_child_v2(&hit, node); +#if defined(SYNCTEX_DEBUG) + printf("Edit query best: %i\n", nds.l.distance); #endif - if (endptr == SYNCTEX_CUR) { - _synctex_error("bad magnification in the post scriptum, a float was expected."); - return SYNCTEX_STATUS_ERROR; - } - if (scanner->unit<=0) { - _synctex_error("bad magnification in the post scriptum, a positive float was expected."); - return SYNCTEX_STATUS_ERROR; - } - SYNCTEX_CUR = endptr; - goto next_line; - } - if (statusx_offset)); - if (statusy_offset)); - if (statuscount),(synctex_decoder_t)&_synctex_decode_int); - if (status < SYNCTEX_STATUS_EOF) { - return status; /* forward the error */ - } else if (status < SYNCTEX_STATUS_OK) { /* No Count record found */ - status = _synctex_next_line(scanner); /* Advance one more line */ - if (statusclass->type) { - case synctex_node_type_hbox: - if (SYNCTEX_INFO(box) != NULL) { - SYNCTEX_HORIZ_V(box) = SYNCTEX_HORIZ(box); - SYNCTEX_VERT_V(box) = SYNCTEX_VERT(box); - SYNCTEX_WIDTH_V(box) = SYNCTEX_WIDTH(box); - SYNCTEX_HEIGHT_V(box) = SYNCTEX_HEIGHT(box); - SYNCTEX_DEPTH_V(box) = SYNCTEX_DEPTH(box); - return SYNCTEX_STATUS_OK; - } - return SYNCTEX_STATUS_ERROR; - } - } - return SYNCTEX_STATUS_BAD_ARGUMENT; +synctex_iterator_p synctex_iterator_new_display(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint) { + (void)column; /* unused */ + if (scanner) { + int tag = synctex_scanner_get_tag(scanner,name);/* parse if necessary */ + int max_line = 0; + int line_offset = 1; + int try_count = 100; + synctex_node_p node = NULL; + synctex_node_p result = NULL; + if (tag == 0) { + printf("SyncTeX Warning: No tag for %s\n",name); + return NULL; + } + node = synctex_scanner_input_with_tag(scanner, tag); + max_line = _synctex_data_line(node); + /* node = NULL; */ + if (line>max_line) { + line = max_line; + } + while(try_count--) { + if (line<=max_line) { + /* This loop will only be performed once for advanced viewers */ + synctex_node_p friend = _synctex_scanner_friend(scanner,tag+line); + if ((node = friend)) { + result = _synctex_display_query_v2(node,tag,line,synctex_YES); + if (!result) { + /* We did not find any matching boundary, retry including boxes */ + node = friend;/* no need to test it again, already done */ + result = _synctex_display_query_v2(node,tag,line,synctex_NO); + } + /* Now reverse the order to have nodes in display order, and then keep just a few nodes. + * Order first the best node. */ + /* The result is a tree. At the root level, all nodes + * correspond to different page numbers. + * Each node has a child which corresponds to the same + * page number if relevant. + * Then reorder the nodes to put first the one which fits best. + * The idea is to count the number of nodes + * with the same tag and line number in the parents + * and choose the one with the biggest count. + */ + if (result) { + /* navigate through siblings, + then children */ + int count = 1; + synctex_node_p next_sibling = __synctex_tree_reset_sibling(result); + int best_match = abs(page_hint-_synctex_node_target_page(result)); + synctex_node_p sibling; + int match; + result = _synctex_vertically_sorted_v2(result); + while((sibling = next_sibling)) { + /* What is next? Do not miss that step! */ + next_sibling = __synctex_tree_reset_sibling(sibling); + sibling = _synctex_vertically_sorted_v2(sibling); + match = abs(page_hint-_synctex_node_target_page(sibling)); + if (matchbest_match)*/ { + __synctex_tree_set_sibling(sibling,__synctex_tree_sibling(result)); + __synctex_tree_set_sibling(result,sibling); + } + ++count; + } + /* Now order first the result closest to the page hint */ + return _synctex_iterator_new(result,count); + } + } +# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) + break; +# else + line += line_offset; + line_offset=line_offset<0?-(line_offset-1):-(line_offset+1); + if (line <= 0) { + line += line_offset; + line_offset=line_offset<0?-(line_offset-1):-(line_offset+1); + } +# endif + } + } + } + return NULL; } - -/* This method is sent to an horizontal box to setup the visible size - * Some box have 0 width but do contain text material. - * With this method, one can enlarge the box to contain the given point (h,v). +synctex_status_t synctex_display_query(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint) { + if (scanner) { + synctex_iterator_free(scanner->iterator); + scanner->iterator = synctex_iterator_new_display(scanner, name,line,column, page_hint); + return synctex_iterator_count(scanner->iterator); + } + return SYNCTEX_STATUS_ERROR; +} +synctex_status_t synctex_edit_query(synctex_scanner_p scanner,int page,float h,float v) { + if (scanner) { + synctex_iterator_free(scanner->iterator); + scanner->iterator = synctex_iterator_new_edit(scanner, page, h, v); + return synctex_iterator_count(scanner->iterator); + } + return SYNCTEX_STATUS_ERROR; +} +/** + * The next result of a query. */ -synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v) { - (void)v; /* unused */ - - int itsBtm, itsTop; - if (NULL == node || node->class->type != synctex_node_type_hbox) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - if (SYNCTEX_WIDTH_V(node)<0) { - itsBtm = SYNCTEX_HORIZ_V(node); - itsTop = SYNCTEX_HORIZ_V(node)-SYNCTEX_WIDTH_V(node); - if (hitsTop) { - SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - h; - } - } else { - itsBtm = SYNCTEX_HORIZ_V(node); - itsTop = SYNCTEX_HORIZ_V(node)+SYNCTEX_WIDTH_V(node); - if (hitsTop) { - SYNCTEX_WIDTH_V(node) = h - SYNCTEX_HORIZ_V(node); - } - } - return SYNCTEX_STATUS_OK; +synctex_node_p synctex_scanner_next_result(synctex_scanner_p scanner) { + return scanner? synctex_iterator_next_result(scanner->iterator): NULL; +} +synctex_status_t synctex_scanner_reset_result(synctex_scanner_p scanner) { + return scanner? synctex_iterator_reset(scanner->iterator): SYNCTEX_STATUS_ERROR; } -/* Here are the control characters that strat each line of the synctex output file. - * Their values define the meaning of the line. - */ -# define SYNCTEX_CHAR_BEGIN_SHEET '{' -# define SYNCTEX_CHAR_END_SHEET '}' -# define SYNCTEX_CHAR_BEGIN_VBOX '[' -# define SYNCTEX_CHAR_END_VBOX ']' -# define SYNCTEX_CHAR_BEGIN_HBOX '(' -# define SYNCTEX_CHAR_END_HBOX ')' -# define SYNCTEX_CHAR_ANCHOR '!' -# define SYNCTEX_CHAR_VOID_VBOX 'v' -# define SYNCTEX_CHAR_VOID_HBOX 'h' -# define SYNCTEX_CHAR_KERN 'k' -# define SYNCTEX_CHAR_GLUE 'g' -# define SYNCTEX_CHAR_MATH '$' -# define SYNCTEX_CHAR_BOUNDARY 'x' +synctex_node_p synctex_node_target(synctex_node_p node) { + return _synctex_tree_target(node); +} -# define SYNCTEX_RETURN(STATUS) return STATUS; +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Geometric utilities +# endif -/* Used when parsing the synctex file. A '{' character has just been parsed. - * The purpose is to gobble everything until the closing '}'. - * Actually only one nesting depth has been observed when using the clip option - * of \includegraphics option. Here we use arbitrary level of depth. +/** Rougly speaking, this is: + * node's h coordinate - hit point's h coordinate. + * If node is to the right of the hit point, then this distance is positive, + * if node is to the left of the hit point, this distance is negative. + * If the argument is a pdf form reference, then the child is used and returned instead. + * Last Revision: Mon Apr 24 07:05:27 UTC 2017 */ -synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner) { - unsigned int depth = 0; -deeper: - ++depth; - if (_synctex_next_line(scanner) + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + width = _synctex_data_width(node); + min = _synctex_data_h(node); + max = min + (width>0?width:-width); + /* We allways have min <= max */ + if (hit->hh; /* regions 1+4+7, result is > 0 */ + } else if (hit->h>max) { + nd.distance = max - hit->h; /* regions 3+6+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_proxy_vbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + width = synctex_node_width(node); + min = synctex_node_h(node); + max = min + (width>0?width:-width); + /* We allways have min <= max */ + if (hit->hh; /* regions 1+4+7, result is > 0 */ + } else if (hit->h>max) { + nd.distance = max - hit->h; /* regions 3+6+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_hbox: + case synctex_node_type_proxy_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + width = synctex_node_hbox_width(node); + min = synctex_node_hbox_h(node); + max = min + (width>0?width:-width); + /* We allways have min <= max */ + if (hit->hh; /* regions 1+4+7, result is > 0 */ + } else if (hit->h>max) { + nd.distance = max - hit->h; /* regions 3+6+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_kern: + /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. + * The distance to the kern is very special, + * in general, there is no text material in the kern, + * this is why we compute the offset relative to the closest edge of the kern.*/ + max = _synctex_data_width(node); + if (max<0) { + min = _synctex_data_h(node); + max = min - max; + } else { + min = -max; + max = _synctex_data_h(node); + min += max; + } + med = (min+max)/2; + /* positive kern: '.' means text, '>' means kern offset + * ............. + * min>>>>med>>>>max + * ............... + * negative kern: '.' means text, '<' means kern offset + * ............................ + * min<<<hh + 1; /* penalty to ensure other nodes are chosen first in case of overlapping ones */ + } else if (hit->h>max) { + nd.distance = max - hit->h - 1; /* same kind of penalty */ + } else if (hit->h>med) { + /* do things like if the node had 0 width and was placed at the max edge + 1*/ + nd.distance = max - hit->h + 1; /* positive, the kern is to the right of the hit point */ + } else { + nd.distance = min - hit->h - 1; /* negative, the kern is to the left of the hit point */ + } + break; + case synctex_node_type_rule:/* to do: special management */ + case synctex_node_type_glue: + case synctex_node_type_math: + case synctex_node_type_boundary: + case synctex_node_type_box_bdry: + nd.distance = _synctex_data_h(node) - hit->h; + break; + case synctex_node_type_ref: + nd.node = synctex_node_child(node); + nd = _synctex_point_h_ordered_distance_v2(hit,nd.node); + break; + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + { + /* shift the hit point to be relative to the proxy origin, + * then compute the distance to the target + */ + synctex_point_s otherHit = *hit; + otherHit.h -= _synctex_data_h(node); + otherHit.v -= _synctex_data_v(node); + nd.node = _synctex_tree_target(node); + nd = _synctex_point_h_ordered_distance_v2(&otherHit,nd.node); + nd.node = node; + } + default: + break; + } } -scan_next_line: - if (SYNCTEX_CUR0) { - goto scan_next_line; + return nd; +} +/** Rougly speaking, this is: + * node's v coordinate - hit point's v coordinate. + * If node is at the top of the hit point, then this distance is positive, + * if node is at the bottom of the hit point, this distance is negative. + */ +static synctex_nd_s _synctex_point_v_ordered_distance_v2 +(synctex_point_p hit, synctex_node_p node) { + synctex_nd_s nd = {node, INT_MAX}; + int min,max,depth,height; + switch(synctex_node_type(node)) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = synctex_node_v(node); + max = min + _synctex_abs(_synctex_data_depth(node)); + min -= _synctex_abs(_synctex_data_height(node)); + /* We allways have min <= max */ + if (hit->vv; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ } else { - SYNCTEX_RETURN(SYNCTEX_STATUS_OK); + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ } - } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_SHEET) { - ++SYNCTEX_CUR; - goto deeper; - - } else if (_synctex_next_line(scanner)vv; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_hbox: + case synctex_node_type_proxy_hbox: + /* getting the box bounds, taking into account negative height and depth. */ + min = synctex_node_hbox_v(node); + depth = synctex_node_hbox_depth(node); + max = min + (depth>0?depth:-depth); + height = synctex_node_hbox_height(node); + min -= (height>0?height:-height); + /* We allways have min <= max */ + if (hit->vv; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_rule:/* to do: special management */ + case synctex_node_type_kern: + case synctex_node_type_glue: + case synctex_node_type_math: + min = _synctex_data_v(node); + max = min + _synctex_abs(_synctex_data_depth(_synctex_tree_parent(node))); + min -= _synctex_abs(_synctex_data_height(_synctex_tree_parent(node))); + /* We allways have min <= max */ + if (hit->vv; /* regions 1+2+3, result is > 0 */ + } else if (hit->v>max) { + nd.distance = max - hit->v; /* regions 7+8+9, result is < 0 */ + } else { + nd.distance = 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_ref: + nd.node = synctex_node_child(node); + nd = _synctex_point_v_ordered_distance_v2(hit,nd.node); + break; + case synctex_node_type_proxy: + case synctex_node_type_proxy_last: + { + synctex_point_s otherHit = *hit; + otherHit.h -= _synctex_data_h(node); + otherHit.v -= _synctex_data_v(node); + nd.node = _synctex_tree_target(node); + nd = _synctex_point_v_ordered_distance_v2(&otherHit,nd.node); + nd.node = node; } + default: break; } - _synctex_error("Unexpected end of nested sheet (4)."); - SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + return nd; } - -/* Used when parsing the synctex file. - * The sheet argument is a newly created sheet node that will hold the contents. - * Something is returned in case of error. +/** + * The best is the one with the smallest area. + * The area is width*height where width and height may be big. + * So there is a real risk of overflow if we stick with ints. */ -synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t sheet) { - synctex_node_t parent = sheet; - synctex_node_t child = NULL; - synctex_node_t sibling = NULL; - synctex_node_t box = sheet; - int friend_index = 0; - synctex_info_t * info = NULL; - synctex_status_t status = 0; - size_t available = 0; - if ((NULL == scanner) || (NULL == sheet)) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - /* We MUST start with a box, so at this level, the unique possibility is '[', '(' or "}". */ -prepare_loop: - if (SYNCTEX_CURclass->type != synctex_node_type_sheet - || _synctex_next_line(scanner)0){ - _synctex_error("Uncomplete sheet(0)"); - SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); - } else { - goto prepare_loop; - } - } - _synctex_bail(); -/* The child loop means that we go do one level, when we just created a box node, - * the next node created is a child of this box. */ -child_loop: - if (SYNCTEX_CURclass->type == synctex_node_type_vbox) { - #define SYNCTEX_UPDATE_BOX_FRIEND(NODE)\ - friend_index = ((SYNCTEX_INFO(NODE))[SYNCTEX_TAG_IDX].INT+(SYNCTEX_INFO(NODE))[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\ - SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ - (scanner->lists_of_friends)[friend_index] = NODE; - if (NULL == SYNCTEX_CHILD(parent)) { - /* only void boxes are friends */ - SYNCTEX_UPDATE_BOX_FRIEND(parent); - } - child = parent; - parent = SYNCTEX_PARENT(child); - } else { - _synctex_error("Unexpected end of vbox, ignored."); - } - if (_synctex_next_line(scanner)class->type == synctex_node_type_hbox) { - if (NULL == child) { - /* Only boxes with no children are friends, - * boxes with children are indirectly friends through one of their descendants. */ - SYNCTEX_UPDATE_BOX_FRIEND(parent); - } - /* setting the next horizontal box at the end ensures that a child is recorded before any of its ancestors. */ - SYNCTEX_SET_NEXT_HORIZ_BOX(box,parent); - box = parent; - child = parent; - parent = SYNCTEX_PARENT(child); - } else { - _synctex_error("Unexpected enf of hbox, ignored."); - } - if (_synctex_next_line(scanner)number_of_lists);\ - SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ - (scanner->lists_of_friends)[friend_index] = NODE; - SYNCTEX_UPDATE_FRIEND(child); - goto sibling_loop; - } else { - _synctex_error("Can't create vbox record."); - SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); - } - } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_HBOX) { - ++SYNCTEX_CUR; - if (NULL != (child = _synctex_new_void_hbox(scanner)) - && NULL != (info = SYNCTEX_INFO(child))) { - if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) - || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) - || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) - || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) - || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) - || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) - || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) - || _synctex_next_line(scanner)0){ - _synctex_error("Uncomplete sheet(0)"); - SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); - } else { - goto child_loop; - } - } - _synctex_bail(); -/* The vertical loop means that we are on the same level, for example when we just ended a box. - * If a node is created now, it will be a sibling of the current node, sharing the same parent. */ -sibling_loop: - if (SYNCTEX_CUR0){ - goto sibling_loop; - } else { - _synctex_error("Uncomplete sheet(2)"); - SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); - } - } -# undef SYNCTEX_DECODE_FAILED +SYNCTEX_INLINE static synctex_node_p _synctex_smallest_container_v2(synctex_node_p node, synctex_node_p other_node) { + long total_height, other_total_height; + unsigned long area, other_area; + long width = synctex_node_hbox_width(node); + long other_width = synctex_node_hbox_width(other_node); + if (width<0) { + width = -width; + } + if (other_width<0) { + other_width = -other_width; + } + total_height = _synctex_abs(synctex_node_hbox_depth(node)) + _synctex_abs(synctex_node_hbox_height(node)); + other_total_height = _synctex_abs(synctex_node_hbox_depth(other_node)) + _synctex_abs(synctex_node_hbox_height(other_node)); + area = total_height*width; + other_area = other_total_height*other_width; + if (areaother_area) { + return other_node; + } + if (_synctex_abs(_synctex_data_width(node))>_synctex_abs(_synctex_data_width(other_node))) { + return node; + } + if (_synctex_abs(_synctex_data_width(node))<_synctex_abs(_synctex_data_width(other_node))) { + return other_node; + } + if (total_heightother_total_height) { + return other_node; + } + return node; } -/* Used when parsing the synctex file - */ -synctex_status_t _synctex_scan_content(synctex_scanner_t scanner) { - synctex_node_t sheet = NULL; - synctex_status_t status = 0; - if (NULL == scanner) { - return SYNCTEX_STATUS_BAD_ARGUMENT; - } - /* set up the lists of friends */ - if (NULL == scanner->lists_of_friends) { - scanner->number_of_lists = 1024; - scanner->lists_of_friends = (synctex_node_t *)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_t)); - if (NULL == scanner->lists_of_friends) { - _synctex_error("malloc:2"); - return SYNCTEX_STATUS_ERROR; - } - } - /* Find where this section starts */ -content_not_found: - status = _synctex_match_string(scanner,"Content:"); - if (statussheet); - scanner->sheet = sheet; - sheet = NULL; - /* Now read the list of Inputs between 2 sheets. */ - do { - status = _synctex_scan_input(scanner); - if (status= SYNCTEX_STATUS_OK); - goto next_sheet; -} - -int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); - -/* Where the synctex scanner is created. */ -synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { - gzFile file = NULL; - char * synctex = NULL; - synctex_scanner_t scanner = NULL; - synctex_io_mode_t io_mode = 0; - /* Here we assume that int are smaller than void * */ - if (sizeof(int)>sizeof(void*)) { - _synctex_error("INTERNAL INCONSISTENCY: int's are unexpectedly bigger than pointers, bailing out."); - return NULL; - } - /* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ - if (SYNCTEX_BUFFER_SIZE >= UINT_MAX) { - _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (1)"); - return NULL; - } - /* for integers: */ - if (SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE) { - _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (2)"); - return NULL; - } - /* now open the synctex file */ - if (_synctex_open(output,build_directory,&synctex,&file,synctex_ADD_QUOTES,&io_mode) || !file) { - /* reset the io_mode to the default, ie read */ - io_mode = 0; - if (_synctex_open(output,build_directory,&synctex,&file,synctex_DONT_ADD_QUOTES,&io_mode) || !file) { - return NULL; - } - } - scanner = (synctex_scanner_t)_synctex_malloc(sizeof(_synctex_scanner_t)); - if (NULL == scanner) { - _synctex_error("SyncTeX: malloc problem"); - free(synctex); - gzclose(file); - return NULL; - } - /* make a private copy of output for the scanner */ - if (NULL == (scanner->output = (char *)malloc(strlen(output)+1))){ - _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), scanner's output is not reliable."); - } else if (scanner->output != strcpy(scanner->output,output)) { - _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, scanner's output is not reliable."); - } - scanner->synctex = synctex;/* Now the scanner owns synctex */ - SYNCTEX_FILE = file; - return parse? synctex_scanner_parse(scanner):scanner; -} - -int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref); - -/* This functions opens the file at the "output" given location. - * It manages the problem of quoted filenames that appear with pdftex and filenames containing the space character. - * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. - * This function will remove them if possible. - * All the reference arguments will take a value on return. They must be non NULL. - * 0 on success, non 0 on error. */ -int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) { - if (synctex_name_ref && file_ref && io_mode_ref) { - /* 1 local variables that uses dynamic memory */ - char * synctex_name = NULL; - gzFile the_file = NULL; - char * quoteless_synctex_name = NULL; - size_t size = 0; - synctex_io_mode_t io_mode = *io_mode_ref; - const char * mode = _synctex_get_io_mode_name(io_mode); - /* now create the synctex file name */ - size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; - synctex_name = (char *)malloc(size); - if (NULL == synctex_name) { - _synctex_error("! __synctex_open: Memory problem (1)\n"); - return 1; - } - /* we have reserved for synctex enough memory to copy output (including its 2 eventual quotes), both suffices, - * including the terminating character. size is free now. */ - if (synctex_name != strcpy(synctex_name,output)) { - _synctex_error("! __synctex_open: Copy problem\n"); -return_on_error: - free(synctex_name); - free(quoteless_synctex_name); - return 2; - } - /* remove the last path extension if any */ - _synctex_strip_last_path_extension(synctex_name); - if (!strlen(synctex_name)) { - goto return_on_error; - } - /* now insert quotes. */ - if (add_quotes) { - char * quoted = NULL; - if (_synctex_copy_with_quoting_last_path_component(synctex_name,"ed,size) || (NULL == quoted)) { - /* There was an error or quoting does not make sense: */ - goto return_on_error; - } - quoteless_synctex_name = synctex_name; - synctex_name = quoted; - } - /* Now add to synctex_name the first path extension. */ - if (synctex_name != strcat(synctex_name,synctex_suffix)){ - _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); - goto return_on_error; - } - /* Add to quoteless_synctex_name as well, if relevant. */ - if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix))){ - free(quoteless_synctex_name); - quoteless_synctex_name = NULL; - } - if (NULL == (the_file = gzopen(synctex_name,mode))) { - /* Could not open this file */ - if (errno != ENOENT) { - /* The file does exist, this is a lower level error, I can't do anything. */ - _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); - goto return_on_error; - } - /* Apparently, there is no uncompressed synctex file. Try the compressed version */ - if (synctex_name != strcat(synctex_name,synctex_suffix_gz)){ - _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); - goto return_on_error; - } - io_mode |= synctex_io_gz_mask; - mode = _synctex_get_io_mode_name(io_mode); /* the file is a compressed and is a binary file, this caused errors on Windows */ - /* Add the suffix to the quoteless_synctex_name as well. */ - if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix_gz))){ - free(quoteless_synctex_name); - quoteless_synctex_name = NULL; - } - if (NULL == (the_file = gzopen(synctex_name,mode))) { - /* Could not open this file */ - if (errno != ENOENT) { - /* The file does exist, this is a lower level error, I can't do anything. */ - _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); - } - goto return_on_error; - } - } - /* At this point, the file is properly open. - * If we are in the add_quotes mode, we change the file name by removing the quotes. */ - if (quoteless_synctex_name) { - gzclose(the_file); - if (rename(synctex_name,quoteless_synctex_name)) { - _synctex_error("SyncTeX: could not rename %s to %s, error %i\n",synctex_name,quoteless_synctex_name,errno); - /* We could not rename, reopen the file with the quoted name. */ - if (NULL == (the_file = gzopen(synctex_name,mode))) { - /* No luck, could not re open this file, something has happened meanwhile */ - if (errno != ENOENT) { - /* The file does not exist any more, it has certainly be removed somehow - * this is a lower level error, I can't do anything. */ - _synctex_error("SyncTeX: could not open again %s, error %i\n",synctex_name,errno); - } - goto return_on_error; - } - } else { - /* The file has been successfully renamed */ - if (NULL == (the_file = gzopen(quoteless_synctex_name,mode))) { - /* Could not open this file */ - if (errno != ENOENT) { - /* The file does exist, this is a lower level error, I can't do anything. */ - _synctex_error("SyncTeX: could not open renamed %s, error %i\n",quoteless_synctex_name,errno); - } - goto return_on_error; - } - /* The quote free file name should replace the old one:*/ - free(synctex_name); - synctex_name = quoteless_synctex_name; - quoteless_synctex_name = NULL; - } - } - /* The operation is successfull, return the arguments by value. */ - * file_ref = the_file; - * io_mode_ref = io_mode; - * synctex_name_ref = synctex_name; - return 0; - } - return 3; /* Bad parameter. */ +SYNCTEX_INLINE static synctex_bool_t _synctex_point_in_box_v2(synctex_point_p hit, synctex_node_p node) { + if (node) { + if (0 == _synctex_point_h_ordered_distance_v2(hit,node).distance + && 0 == _synctex_point_v_ordered_distance_v2(hit,node).distance) { + return synctex_YES; + } + } + return synctex_NO; } -/* Opens the ouput file, taking into account the eventual build_directory. - * 0 on success, non 0 on error. */ -int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) { -# define synctex_name (*synctex_name_ref) -# define the_file (*file_ref) - int result = __synctex_open(output,synctex_name_ref,file_ref,add_quotes,io_mode_ref); - if ((result || !*file_ref) && build_directory && strlen(build_directory)) { - char * build_output; - const char *lpc; - size_t size; - synctex_bool_t is_absolute; - build_output = NULL; - lpc = _synctex_last_path_component(output); - size = strlen(build_directory)+strlen(lpc)+2; /* One for the '/' and one for the '\0'. */ - is_absolute = _synctex_path_is_absolute(build_directory); - if (!is_absolute) { - size += strlen(output); - } - if ((build_output = (char *)malloc(size))) { - if (is_absolute) { - build_output[0] = '\0'; - } else { - if (build_output != strcpy(build_output,output)) { - return -4; - } - build_output[lpc-output]='\0'; - } - if (build_output == strcat(build_output,build_directory)) { - /* Append a path separator if necessary. */ - if (!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { - if (build_output != strcat(build_output,"/")) { - return -2; - } - } - /* Append the last path component of the output. */ - if (build_output != strcat(build_output,lpc)) { - return -3; - } - return __synctex_open(build_output,synctex_name_ref,file_ref,add_quotes,io_mode_ref); - } - } - return -1; - } - return result; -# undef synctex_name -# undef the_file +static int _synctex_distance_to_box_v2(synctex_point_p hit,synctex_box_p box) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + * In each region, there is a different formula. + * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ + if (hit->vmin.v) { + /* Regions 1, 2 or 3 */ + if (hit->hmin.h) { + /* This is region 1. The distance to the box is the L1 distance PA. */ + return box->min.v - hit->v + box->min.h - hit->h;/* Integer overflow? probability epsilon */ + } else if (hit->h<=box->max.h) { + /* This is region 2. The distance to the box is the geometrical distance to the top edge. */ + return box->min.v - hit->v; + } else { + /* This is region 3. The distance to the box is the L1 distance PB. */ + return box->min.v - hit->v + hit->h - box->max.h; + } + } else if (hit->v<=box->max.v) { + /* Regions 4, 5 or 6 */ + if (hit->hmin.h) { + /* This is region 4. The distance to the box is the geometrical distance to the left edge. */ + return box->min.h - hit->h; + } else if (hit->h<=box->max.h) { + /* This is region 5. We are inside the box. */ + return 0; + } else { + /* This is region 6. The distance to the box is the geometrical distance to the right edge. */ + return hit->h - box->max.h; + } + } else { + /* Regions 7, 8 or 9 */ + if (hit->hmin.h) { + /* This is region 7. The distance to the box is the L1 distance PC. */ + return hit->v - box->max.v + box->min.h - hit->h; + } else if (hit->h<=box->max.h) { + /* This is region 8. The distance to the box is the geometrical distance to the top edge. */ + return hit->v - box->max.v; + } else { + /* This is region 9. The distance to the box is the L1 distance PD. */ + return hit->v - box->max.v + hit->h - box->max.h; + } + } } -/* The scanner destructor +/** + * The distance from the hit point to the node. */ -void synctex_scanner_free(synctex_scanner_t scanner) { - if (NULL == scanner) { - return; - } - if (SYNCTEX_FILE) { - gzclose(SYNCTEX_FILE); - SYNCTEX_FILE = NULL; - } - SYNCTEX_FREE(scanner->sheet); - SYNCTEX_FREE(scanner->input); - free(SYNCTEX_START); - free(scanner->output_fmt); - free(scanner->output); - free(scanner->synctex); - free(scanner->lists_of_friends); - free(scanner); +static int _synctex_point_node_distance_v2(synctex_point_p hit, synctex_node_p node) { + int d = INT_MAX; + if (node) { + synctex_box_s box = {{0,0},{0,0}}; + int dd = INT_MAX; + switch(synctex_node_type(node)) { + case synctex_node_type_vbox: + box.min.h = _synctex_data_h(node); + box.max.h = box.min.h + _synctex_abs(_synctex_data_width(node)); + box.min.v = synctex_node_v(node); + box.max.v = box.min.v + _synctex_abs(_synctex_data_depth(node)); + box.min.v -= _synctex_abs(_synctex_data_height(node)); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_proxy_vbox: + box.min.h = synctex_node_h(node); + box.max.h = box.min.h + _synctex_abs(synctex_node_width(node)); + box.min.v = synctex_node_v(node); + box.max.v = box.min.v + _synctex_abs(synctex_node_depth(node)); + box.min.v -= _synctex_abs(synctex_node_height(node)); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_hbox: + case synctex_node_type_proxy_hbox: + box.min.h = synctex_node_hbox_h(node); + box.max.h = box.min.h + _synctex_abs(synctex_node_hbox_width(node)); + box.min.v = synctex_node_hbox_v(node); + box.max.v = box.min.v + _synctex_abs(synctex_node_hbox_depth(node)); + box.min.v -= _synctex_abs(synctex_node_hbox_height(node)); + return _synctex_distance_to_box_v2(hit,&box); + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* best of distances from the left edge and right edge*/ + box.min.h = _synctex_data_h(node); + box.max.h = box.min.h; + box.min.v = _synctex_data_v(node); + box.max.v = box.min.v + _synctex_abs(_synctex_data_depth(node)); + box.min.v -= _synctex_abs(_synctex_data_height(node)); + d = _synctex_distance_to_box_v2(hit,&box); + box.min.h = box.min.h + _synctex_abs(_synctex_data_width(node)); + box.max.h = box.min.h; + dd = _synctex_distance_to_box_v2(hit,&box); + return d
flags.has_parsed) { - return scanner; - } - scanner->flags.has_parsed=1; - scanner->pre_magnification = 1000; - scanner->pre_unit = 8192; - scanner->pre_x_offset = scanner->pre_y_offset = 578; - /* initialize the offset with a fake unprobable value, - * If there is a post scriptum section, this value will be overriden by the real life value */ - scanner->x_offset = scanner->y_offset = 6.027e23f; - scanner->class[synctex_node_type_sheet] = synctex_class_sheet; - scanner->class[synctex_node_type_input] = synctex_class_input; - (scanner->class[synctex_node_type_input]).scanner = scanner; - (scanner->class[synctex_node_type_sheet]).scanner = scanner; - scanner->class[synctex_node_type_vbox] = synctex_class_vbox; - (scanner->class[synctex_node_type_vbox]).scanner = scanner; - scanner->class[synctex_node_type_void_vbox] = synctex_class_void_vbox; - (scanner->class[synctex_node_type_void_vbox]).scanner = scanner; - scanner->class[synctex_node_type_hbox] = synctex_class_hbox; - (scanner->class[synctex_node_type_hbox]).scanner = scanner; - scanner->class[synctex_node_type_void_hbox] = synctex_class_void_hbox; - (scanner->class[synctex_node_type_void_hbox]).scanner = scanner; - scanner->class[synctex_node_type_kern] = synctex_class_kern; - (scanner->class[synctex_node_type_kern]).scanner = scanner; - scanner->class[synctex_node_type_glue] = synctex_class_glue; - (scanner->class[synctex_node_type_glue]).scanner = scanner; - scanner->class[synctex_node_type_math] = synctex_class_math; - (scanner->class[synctex_node_type_math]).scanner = scanner; - scanner->class[synctex_node_type_boundary] = synctex_class_boundary; - (scanner->class[synctex_node_type_boundary]).scanner = scanner; - SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ - if (NULL == SYNCTEX_START) { - _synctex_error("SyncTeX: malloc error"); - synctex_scanner_free(scanner); - return NULL; - } - SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; - /* SYNCTEX_END always points to a null terminating character. - * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. - * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ - *SYNCTEX_END = '\0'; - SYNCTEX_CUR = SYNCTEX_END; - status = _synctex_scan_preamble(scanner); - if (statuspre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp - * 1 pt = 65536 sp */ - if (scanner->pre_unit<=0) { - scanner->pre_unit = 8192; - } - if (scanner->pre_magnification<=0) { - scanner->pre_magnification = 1000; - } - if (scanner->unit <= 0) { - /* no post magnification */ - scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ - } else { - /* post magnification */ - scanner->unit *= scanner->pre_unit / 65781.76; - } - scanner->unit *= scanner->pre_magnification / 1000.0; - if (scanner->x_offset > 6e23) { - /* no post offset */ - scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); - scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); - } else { - /* post offset */ - scanner->x_offset /= 65781.76f; - scanner->y_offset /= 65781.76f; - } - return scanner; - #undef SYNCTEX_FILE +static synctex_node_p _synctex_eq_deepest_container_v2(synctex_point_p hit, synctex_node_p node) { + if (node) { + /**/ + synctex_node_p child; + if ((child = synctex_node_child(node))) { + /* Non void hbox or vbox, form ref or proxy */ + /* We go deep first because some boxes have 0 dimensions + * despite they do contain some black material. + */ + do { + if ((_synctex_point_in_box_v2(hit,child))) { + synctex_node_p deep = _synctex_eq_deepest_container_v2(hit,child); + if (deep) { + /* One of the children contains the hit. */ + return deep; + } + } + } while((child = synctex_node_sibling(child))); + /* is the hit point inside the box? */ + if (synctex_node_type(node) == synctex_node_type_vbox + || synctex_node_type(node) == synctex_node_type_proxy_vbox) { + /* For vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children inside. */ + if ((child = _synctex_tree_child(node))) { + synctex_nd_s best = SYNCTEX_ND_0; + do { + if (_synctex_tree_child(child)) { + int d = _synctex_point_node_distance_v2(hit,child); + if (d <= best.distance) { + best = (synctex_nd_s){child, d}; + } + } + } while((child = __synctex_tree_sibling(child))); + if (best.node) { + return best.node; + } + } + } + if (_synctex_point_in_box_v2(hit,node)) { + return node; + } + } + } + return NULL; +} +static synctex_nd_s _synctex_eq_deepest_container_v3(synctex_point_p hit, synctex_node_p node) { + if (node) { + synctex_node_p child = NULL; + if ((child = synctex_node_child(node))) { + /* Non void hbox, vbox, box proxy or form ref */ + /* We go deep first because some boxes have 0 dimensions + * despite they do contain some black material. + */ + do { + synctex_nd_s deep = _synctex_eq_deepest_container_v3(hit, child); + if (deep.node) { + /* One of the children contains the hit-> */ + return deep; + } + } while((child = synctex_node_sibling(child))); + /* For vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children inside. */ + if (synctex_node_type(node) == synctex_node_type_vbox + || synctex_node_type(node) == synctex_node_type_proxy_vbox) { + if ((child = synctex_node_child(node))) { + synctex_nd_s best = SYNCTEX_ND_0; + do { + if (synctex_node_child(child)) { + int d = _synctex_point_node_distance_v2(hit,child); + if (d < best.distance) { + best = (synctex_nd_s){child,d}; + } + } + } while((child = synctex_node_sibling(child))); + if (best.node) { + return best; + } + } + } + /* is the hit point inside the box? */ + if (_synctex_point_in_box_v2(hit,node)) { + return (synctex_nd_s){node, 0}; + } + } + } + return SYNCTEX_ND_0; } -/* Scanner accessors. +/* Compares the locations of the hit point with the locations of + * the various nodes contained in the box. + * As it is an horizontal box, we only compare horizontal coordinates. */ -int synctex_scanner_pre_x_offset(synctex_scanner_t scanner){ - return scanner?scanner->pre_x_offset:0; -} -int synctex_scanner_pre_y_offset(synctex_scanner_t scanner){ - return scanner?scanner->pre_y_offset:0; -} -int synctex_scanner_x_offset(synctex_scanner_t scanner){ - return scanner?scanner->x_offset:0; -} -int synctex_scanner_y_offset(synctex_scanner_t scanner){ - return scanner?scanner->y_offset:0; -} -float synctex_scanner_magnification(synctex_scanner_t scanner){ - return scanner?scanner->unit:1; -} -void synctex_scanner_display(synctex_scanner_t scanner) { - if (NULL == scanner) { - return; - } - printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->output,scanner->output_fmt,scanner->version); - printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); - printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", - scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); - printf("The input:\n"); - SYNCTEX_DISPLAY(scanner->input); - if (scanner->count<1000) { - printf("The sheets:\n"); - SYNCTEX_DISPLAY(scanner->sheet); - printf("The friends:\n"); - if (scanner->lists_of_friends) { - int i = scanner->number_of_lists; - synctex_node_t node; - while(i--) { - printf("Friend index:%i\n",i); - node = (scanner->lists_of_friends)[i]; - while(node) { - printf("%s:%i,%i\n", - synctex_node_isa(node), - SYNCTEX_TAG(node), - SYNCTEX_LINE(node) - ); - node = SYNCTEX_FRIEND(node); - } - } - } - } else { - printf("SyncTeX Warning: Too many objects\n"); - } -} -/* Public*/ -const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag) { - synctex_node_t input = NULL; - if (NULL == scanner) { - return NULL; - } - input = scanner->input; - do { - if (tag == SYNCTEX_TAG(input)) { - return (SYNCTEX_NAME(input)); - } - } while((input = SYNCTEX_SIBLING(input)) != NULL); - return NULL; -} - -int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); -int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { - synctex_node_t input = NULL; - if (NULL == scanner) { - return 0; - } - input = scanner->input; - do { - if (_synctex_is_equivalent_file_name(name,(SYNCTEX_NAME(input)))) { - return SYNCTEX_TAG(input); - } - } while((input = SYNCTEX_SIBLING(input)) != NULL); - return 0; -} - -int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { - size_t char_index = strlen(name); - if ((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { - /* the name is not void */ - char_index -= 1; - if (!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { - /* the last character of name is not a path separator */ - int result = _synctex_scanner_get_tag(scanner,name); - if (result) { - return result; - } else { - /* the given name was not the one known by TeX - * try a name relative to the enclosing directory of the scanner->output file */ - const char * relative = name; - const char * ptr = scanner->output; - while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) - { - relative += 1; - ptr += 1; - } - /* Find the last path separator before relative */ - while(relative > name) { - if (SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { - break; - } - relative -= 1; - } - if ((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { - return result; - } - if (SYNCTEX_IS_PATH_SEPARATOR(name[0])) { - /* No tag found for the given absolute name, - * Try each relative path starting from the shortest one */ - while(0input:NULL; -} -const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner) { - return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; -} -const char * synctex_scanner_get_output(synctex_scanner_t scanner) { - return NULL != scanner && scanner->output?scanner->output:""; -} -const char * synctex_scanner_get_synctex(synctex_scanner_t scanner) { - return NULL != scanner && scanner->synctex?scanner->synctex:""; +SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_hbox_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_s childd = SYNCTEX_ND_0; + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + if ((childd.node = synctex_node_child(node))) { + synctex_nd_s nd = SYNCTEX_ND_0; + do { + childd = _synctex_point_h_ordered_distance_v2(hitP,childd.node); + if (childd.distance > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (nds.r.distance > childd.distance) { + nds.r = childd; + } else if (nds.r.distance == childd.distance && nds.r.node) { + if (_synctex_data_tag(nds.r.node) == _synctex_data_tag(childd.node) + && (_synctex_data_line(nds.r.node) > _synctex_data_line(childd.node) + || (_synctex_data_line(nds.r.node) == _synctex_data_line(childd.node) + && _synctex_data_column(nds.r.node) > _synctex_data_column(childd.node)))) { + nds.r = childd; + } + } + } else if (childd.distance == 0) { + /* hit point is inside node. */ + return _synctex_eq_get_closest_children_in_box_v2(hitP, childd.node); + } else { /* here childd.distance < 0, the hit point is to the right of node */ + childd.distance = -childd.distance; + if (nds.l.distance > childd.distance) { + nds.l = childd; + } else if (nds.l.distance == childd.distance && nds.l.node) { + if (_synctex_data_tag(nds.l.node) == _synctex_data_tag(childd.node) + && (_synctex_data_line(nds.l.node) > _synctex_data_line(childd.node) + || (_synctex_data_line(nds.l.node) == _synctex_data_line(childd.node) + && _synctex_data_column(nds.l.node) > _synctex_data_column(childd.node)))) { + nds.l = childd; + } + } + } + } while((childd.node = synctex_node_sibling(childd.node))); + if (nds.l.node) { + /* the left node is new, try to narrow the result */ + if ((nd = _synctex_eq_deepest_container_v3(hitP,nds.l.node)).node) { + nds.l = nd; + } + if((nd = __synctex_closest_deep_child_v2(hitP,nds.l.node)).node) { + nds.l.node = nd.node; + } + } + if (nds.r.node) { + /* the right node is new, try to narrow the result */ + if ((nd = _synctex_eq_deepest_container_v3(hitP,nds.r.node)).node) { + nds.r = nd; + } + if((nd = __synctex_closest_deep_child_v2(hitP,nds.r.node)).node) { + nds.r.node = nd.node; + } + } + } + return nds; } -# ifdef SYNCTEX_NOTHING -# pragma mark - -# pragma mark Public node attributes -# endif -int synctex_node_h(synctex_node_t node){ - if (!node) { - return 0; - } - return SYNCTEX_HORIZ(node); -} -int synctex_node_v(synctex_node_t node){ - if (!node) { - return 0; - } - return SYNCTEX_VERT(node); -} -int synctex_node_width(synctex_node_t node){ - if (!node) { - return 0; - } - return SYNCTEX_WIDTH(node); -} -int synctex_node_box_h(synctex_node_t node){ - if (!node) { - return 0; - } - if (SYNCTEX_IS_BOX(node)) { -result: - return SYNCTEX_HORIZ(node); - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -int synctex_node_box_v(synctex_node_t node){ - if (!node) { - return 0; - } - if (SYNCTEX_IS_BOX(node)) { -result: - return SYNCTEX_VERT(node); - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -int synctex_node_box_width(synctex_node_t node){ - if (!node) { - return 0; - } - if (SYNCTEX_IS_BOX(node)) { -result: - return SYNCTEX_WIDTH(node); - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -int synctex_node_box_height(synctex_node_t node){ - if (!node) { - return 0; - } - if (SYNCTEX_IS_BOX(node)) { -result: - return SYNCTEX_HEIGHT(node); - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -int synctex_node_box_depth(synctex_node_t node){ - if (!node) { - return 0; - } - if (SYNCTEX_IS_BOX(node)) { -result: - return SYNCTEX_DEPTH(node); - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; + +#if 0 +SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_hbox_v3(synctex_point_p hitP, synctex_node_p nodeP) { + synctex_nd_s nd = SYNCTEX_ND_0; + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + if ((nd.node = _synctex_tree_child(nodeP))) { + do { + nd = _synctex_point_h_ordered_distance_v2(hitP,nd.node); + if (nd.distance > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (nds.r.distance > nd.distance) { + nds.r = nd; + } else if (nds.r.distance == nd.distance && nds.r.node) { + if (_synctex_data_tag(nds.r.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.r.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.r.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.r.node) > _synctex_data_column(nd.node)))) { + nds.r = nd; + } + } + } else if (nd.distance == 0) { + /* hit point is inside node. */ + nds.l = nd; + } else { /* here nd.d < 0, the hit point is to the right of node */ + nd.distance = -nd.distance; + if (nds.l.distance > nd.distance) { + nds.l = nd; + } else if (nds.l.distance == nd.distance && nds.l.node) { + if (_synctex_data_tag(nds.l.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.l.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.l.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.l.node) > _synctex_data_column(nd.node)))) { + nds.l = nd; + } + } + } + } while((nd.node = __synctex_tree_sibling(nd.node))); + if (nds.l.node) { + /* the left node is new, try to narrow the result */ + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.l.node))) { + nds.l.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.l.node)).node) { + nds.l.node = nd.node; + } + } + if (nds.r.node) { + /* the right node is new, try to narrow the result */ + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.r.node))) { + nds.r.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.r.node)).node) { + nds.r.node = nd.node; + } + } + } + return nds; } -# ifdef SYNCTEX_NOTHING -# pragma mark - -# pragma mark Public node visible attributes -# endif -float synctex_node_visible_h(synctex_node_t node){ - if (!node) { - return 0; - } - return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; -} -float synctex_node_visible_v(synctex_node_t node){ - if (!node) { - return 0; - } - return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; -} -float synctex_node_visible_width(synctex_node_t node){ - if (!node) { - return 0; - } - return SYNCTEX_WIDTH(node)*node->class->scanner->unit; -} -float synctex_node_box_visible_h(synctex_node_t node){ - if (!node) { - return 0; - } - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; - case synctex_node_type_hbox: -result: - return SYNCTEX_HORIZ_V(node)*node->class->scanner->unit+node->class->scanner->x_offset; - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -float synctex_node_box_visible_v(synctex_node_t node){ - if (!node) { - return 0; - } - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; - case synctex_node_type_hbox: -result: - return SYNCTEX_VERT_V(node)*node->class->scanner->unit+node->class->scanner->y_offset; - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -float synctex_node_box_visible_width(synctex_node_t node){ - if (!node) { - return 0; - } - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - return SYNCTEX_WIDTH(node)*node->class->scanner->unit; - case synctex_node_type_hbox: -result: - return SYNCTEX_WIDTH_V(node)*node->class->scanner->unit; - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -float synctex_node_box_visible_height(synctex_node_t node){ - if (!node) { - return 0; - } - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - return SYNCTEX_HEIGHT(node)*node->class->scanner->unit; - case synctex_node_type_hbox: -result: - return SYNCTEX_HEIGHT_V(node)*node->class->scanner->unit; - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; -} -float synctex_node_box_visible_depth(synctex_node_t node){ - if (!node) { - return 0; - } - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - return SYNCTEX_DEPTH(node)*node->class->scanner->unit; - case synctex_node_type_hbox: -result: - return SYNCTEX_DEPTH_V(node)*node->class->scanner->unit; - } - if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { - goto result; - } - return 0; +#endif +SYNCTEX_INLINE static synctex_nd_lr_s __synctex_eq_get_closest_children_in_vbox_v2(synctex_point_p hitP, synctex_node_p nodeP) { + (void)nodeP; /* unused */ + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + synctex_nd_s nd = SYNCTEX_ND_0; + if ((nd.node = synctex_node_child(nd.node))) { + do { + nd = _synctex_point_v_ordered_distance_v2(hitP,nd.node); + /* this is what makes the difference with the h version above */ + if (nd.distance > 0) { + /* node is to the top of the hit point (below because TeX is oriented from top to bottom. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (nds.r.distance > nd.distance) { + nds.r = nd; + } else if (nds.r.distance == nd.distance && nds.r.node) { + if (_synctex_data_tag(nds.r.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.r.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.r.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.r.node) > _synctex_data_column(nd.node)))) { + nds.r = nd; + } + } + } else if (nd.distance == 0) { + nds.l = nd; + } else { /* here nd < 0 */ + nd.distance = -nd.distance; + if (nds.l.distance > nd.distance) { + nds.l = nd; + } else if (nds.l.distance == nd.distance && nds.l.node) { + if (_synctex_data_tag(nds.l.node) == _synctex_data_tag(nd.node) + && (_synctex_data_line(nds.l.node) > _synctex_data_line(nd.node) + || (_synctex_data_line(nds.l.node) == _synctex_data_line(nd.node) + && _synctex_data_column(nds.l.node) > _synctex_data_column(nd.node)))) { + nds.l = nd; + } + } + } + } while((nd.node = synctex_node_sibling(nd.node))); + if (nds.l.node) { + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.l.node))) { + nds.l.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.l.node)).node) { + nds.l.node = nd.node; + } + } + if (nds.r.node) { + if ((nd.node = _synctex_eq_deepest_container_v2(hitP,nds.r.node))) { + nds.r.node = nd.node; + } + if((nd = _synctex_eq_closest_child_v2(hitP,nds.r.node)).node) { + nds.r.node = nd.node; + } + } + } + return nds; } -# ifdef SYNCTEX_NOTHING -# pragma mark - -# pragma mark Other public node attributes -# endif -int synctex_node_page(synctex_node_t node){ - synctex_node_t parent = NULL; - if (!node) { - return -1; - } - parent = SYNCTEX_PARENT(node); - while(parent) { - node = parent; - parent = SYNCTEX_PARENT(node); - } - if (node->class->type == synctex_node_type_sheet) { - return SYNCTEX_PAGE(node); - } - return -1; -} -int synctex_node_tag(synctex_node_t node) { - return node?SYNCTEX_TAG(node):-1; -} -int synctex_node_line(synctex_node_t node) { - return node?SYNCTEX_LINE(node):-1; -} -int synctex_node_column(synctex_node_t node) { - (void)node; /* unused */ - return -1; +/** + * Get the child closest to the hit point. + * - parameter: hit point + * - parameter: containing node + * - returns: the child and the distance to the hit point. + * SYNCTEX_ND_0 if the parameter node has no children. + * - note: recursive call. + */ +static synctex_nd_s __synctex_closest_deep_child_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_s best = SYNCTEX_ND_0; + synctex_node_p child = NULL; + if ((child = synctex_node_child(node))) { +#if defined(SYNCTEX_DEBUG) + printf("Closest deep child on box at line %i\n", + SYNCTEX_LINEINDEX(node)); +#endif + do { +#if defined SYNCTEX_DEBUG && SYNCTEX_DEBUG>500 + synctex_node_display(child); +#endif + synctex_nd_s nd = SYNCTEX_ND_0; + if (_synctex_node_is_box(child)) { + nd = __synctex_closest_deep_child_v2(hitP,child); + } else { + nd = (synctex_nd_s) {child, _synctex_point_node_distance_v2(hitP,child)}; + } + if (nd.distance < best.distance ||(nd.distance == best.distance + && synctex_node_type(nd.node) != synctex_node_type_kern)) { +#if defined(SYNCTEX_DEBUG) + if(nd.node) { + printf("New best %i<=%i line %i\n",nd.distance, + best.distance,SYNCTEX_LINEINDEX(nd.node)); + } +#endif + best = nd; + } + } while((child = synctex_node_sibling(child))); +#if defined(SYNCTEX_DEBUG) + if(best.node) { + printf("Found new best %i line %i\n",best.distance,SYNCTEX_LINEINDEX(best.node)); + } +#endif + } + return best; } -# ifdef SYNCTEX_NOTHING -# pragma mark - -# pragma mark Sheet -# endif -synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page) { - if (scanner) { - synctex_node_t sheet = scanner->sheet; - while(sheet) { - if (page == SYNCTEX_PAGE(sheet)) { - return SYNCTEX_CHILD(sheet); - } - sheet = SYNCTEX_SIBLING(sheet); - } - } - return NULL; +/** + * Return the closest child. + * - parameter: a pointer to the hit point, + * - parameter: the container + * - return: SYNCTEX_ND_0 if node has no child, + * the __synctex_closest_deep_child_v2 otherwise. + */ +static synctex_nd_s _synctex_eq_closest_child_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_s nd = SYNCTEX_ND_0; + if (_synctex_node_is_box(node)) { + nd = __synctex_closest_deep_child_v2(hitP, node); + if (_synctex_node_is_box(nd.node)) { + synctex_node_p child = NULL; + if ((child = synctex_node_child(nd.node))) { + synctex_nd_s best = {child,_synctex_point_node_distance_v2(hitP,child)}; + while((child = synctex_node_sibling(child))) { + int d = _synctex_point_node_distance_v2(hitP,child); + if (d < best.distance) { + best = (synctex_nd_s){child,d}; + } else if (d == best.distance && synctex_node_type(child) != synctex_node_type_kern) { + best.node = child; + } + } + return best; + } + } + return nd; + } + return SYNCTEX_ND_0; +} +SYNCTEX_INLINE static synctex_nd_lr_s _synctex_eq_get_closest_children_in_box_v2(synctex_point_p hitP, synctex_node_p node) { + synctex_nd_lr_s nds = {SYNCTEX_ND_0,SYNCTEX_ND_0}; + if(_synctex_tree_has_child(node)) { /* node != NULL */ + if (node->class->type==synctex_node_type_hbox || + node->class->type==synctex_node_type_proxy_hbox) { + return __synctex_eq_get_closest_children_in_hbox_v2(hitP,node); + } else { + return __synctex_eq_get_closest_children_in_vbox_v2(hitP,node); + } + } + return nds; } +#ifndef SYNCTEX_NO_UPDATER + # ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark Query +# pragma mark Updater # endif -int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column) { - (void)column; /* unused */ +typedef int (*synctex_print_f)(synctex_updater_p, const char * , ...); /* print formatted to either FILE * or gzFile */ +typedef void (*synctex_close_f)(synctex_updater_p); /* close FILE * or gzFile */ - int tag = synctex_scanner_get_tag(scanner,name); - size_t size = 0; - int friend_index = 0; - int max_line = 0; - synctex_node_t node = NULL; - if (tag == 0) { - printf("SyncTeX Warning: No tag for %s\n",name); - return -1; - } - free(SYNCTEX_START); - SYNCTEX_CUR = SYNCTEX_END = SYNCTEX_START = NULL; - max_line = line < INT_MAX-scanner->number_of_lists ? line+scanner->number_of_lists:INT_MAX; - while(linenumber_of_lists); - if ((node = (scanner->lists_of_friends)[friend_index])) { - do { - if ((synctex_node_type(node)>=synctex_node_type_boundary) - && (tag == SYNCTEX_TAG(node)) - && (line == SYNCTEX_LINE(node))) { - if (SYNCTEX_CUR == SYNCTEX_END) { - size += 16; - SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); - SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; - SYNCTEX_START = SYNCTEX_END; - SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); - } - *(synctex_node_t *)SYNCTEX_CUR = node; - SYNCTEX_CUR += sizeof(synctex_node_t); - } - } while((node = SYNCTEX_FRIEND(node))); - if (SYNCTEX_START == NULL) { - /* We did not find any matching boundary, retry with glue or kern */ - node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ - do { - if ((synctex_node_type(node)>=synctex_node_type_kern) - && (tag == SYNCTEX_TAG(node)) - && (line == SYNCTEX_LINE(node))) { - if (SYNCTEX_CUR == SYNCTEX_END) { - size += 16; - SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); - SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; - SYNCTEX_START = SYNCTEX_END; - SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); - } - *(synctex_node_t *)SYNCTEX_CUR = node; - SYNCTEX_CUR += sizeof(synctex_node_t); - } - } while((node = SYNCTEX_FRIEND(node))); - if (SYNCTEX_START == NULL) { - /* We did not find any matching glue or kern, retry with boxes */ - node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ - do { - if ((tag == SYNCTEX_TAG(node)) - && (line == SYNCTEX_LINE(node))) { - if (SYNCTEX_CUR == SYNCTEX_END) { - size += 16; - SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); - SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; - SYNCTEX_START = SYNCTEX_END; - SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); - } - *(synctex_node_t *)SYNCTEX_CUR = node; - SYNCTEX_CUR += sizeof(synctex_node_t); - } - } while((node = SYNCTEX_FRIEND(node))); - } - } - SYNCTEX_END = SYNCTEX_CUR; - /* Now reverse the order to have nodes in display order, and keep just a few nodes */ - if ((SYNCTEX_START) && (SYNCTEX_END)) - { - synctex_node_t * start_ref = (synctex_node_t *)SYNCTEX_START; - synctex_node_t * end_ref = (synctex_node_t *)SYNCTEX_END; - end_ref -= 1; - while(start_ref < end_ref) { - node = *start_ref; - *start_ref = *end_ref; - *end_ref = node; - start_ref += 1; - end_ref -= 1; - } - /* Basically, we keep the first node for each parent. - * More precisely, we keep only nodes that are not descendants of - * their predecessor's parent. */ - start_ref = (synctex_node_t *)SYNCTEX_START; - end_ref = (synctex_node_t *)SYNCTEX_START; - next_end: - end_ref += 1; /* we allways have start_ref<= end_ref*/ - if (end_ref < (synctex_node_t *)SYNCTEX_END) { - node = *end_ref; - while((node = SYNCTEX_PARENT(node))) { - if (SYNCTEX_PARENT(*start_ref) == node) { - goto next_end; - } - } - start_ref += 1; - *start_ref = *end_ref; - goto next_end; - } - start_ref += 1; - SYNCTEX_END = (char *)start_ref; - SYNCTEX_CUR = NULL;/* added on behalf of Jose Alliste */ - return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);/* added on behalf Jan Sundermeyer */ - } - SYNCTEX_CUR = NULL; - /* return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); removed on behalf Jan Sundermeyer */ - } -# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) - break; -# else - ++line; -# endif - } - return 0; -} +# define SYNCTEX_BITS_PER_BYTE 8 + +typedef union { + gzFile as_gzFile; + FILE * as_FILE_p; + void * as_ptr; +} syncex_file_u; + +struct synctex_updater_t { + syncex_file_u file; + synctex_print_f print; + synctex_close_f close; + int length; /* the number of chars appended */ +}; -synctex_node_t synctex_next_result(synctex_scanner_t scanner) { - if (NULL == SYNCTEX_CUR) { - SYNCTEX_CUR = SYNCTEX_START; - } else { - SYNCTEX_CUR+=sizeof(synctex_node_t); - } - if (SYNCTEX_CURfile.as_FILE_p, + format, + va); + va_end(va); + } + return result; } +#if defined(_MSC_VER) +#include +#include +#include -/* This struct records a point in TeX coordinates.*/ -typedef struct { - int h; - int v; -} synctex_point_t; +static int vasprintf(char **ret, + const char *format, + va_list ap) +{ + int len; + len = _vsnprintf(NULL, 0, format, ap); + if (len < 0) return -1; + *ret = malloc(len + 1); + if (!*ret) return -1; + _vsnprintf(*ret, len+1, format, ap); + (*ret)[len] = '\0'; + return len; +} -/* This struct records distances, the left one is positive or 0 and the right one is negative or 0. - * When comparing the locations of 2 different graphical objects on the page, we will have to also record the - * horizontal distance as signed to keep track of the typesetting order.*/ -typedef struct { - int left; - int right; -} synctex_distances_t; +#endif -typedef struct { - synctex_point_t left; - synctex_point_t right; -} synctex_offsets_t; +/** + * gzvprintf is not available until OSX 10.10 + */ +static int _synctex_updater_print_gz(synctex_updater_p updater, const char * format, ...) SYNCTEX_PRINTF_FORMAT(2, 3); +static int _synctex_updater_print_gz(synctex_updater_p updater, const char * format, ...) { + int result = 0; + if (updater) { + char * buffer; + va_list va; + va_start(va, format); + if (vasprintf(&buffer, format, va) < 0) { + _synctex_error("Out of memory..."); + } else if ((result = (int)strlen(buffer))) { + result = gzwrite(updater->file.as_gzFile, buffer, (unsigned)result); + } + va_end(va); + free(buffer); + } + return result; +} +static void _synctex_updater_close(synctex_updater_p updater) { + if (updater) { + fclose(updater->file.as_FILE_p); + } +} -typedef struct { - synctex_node_t left; - synctex_node_t right; -} synctex_node_set_t; +static void _synctex_updater_close_gz(synctex_updater_p updater) { + if (updater) { + gzclose(updater->file.as_gzFile); + } +} -/* The smallest container between two has the smallest width or height. - * This comparison is used when there are 2 overlapping boxes that contain the hit point. - * For ConTeXt, the problem appears at each page. - * The chosen box is the one with the smallest height, then the smallest width. */ -SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node); +synctex_updater_p synctex_updater_new_with_output_file(const char * output, const char * build_directory) { + synctex_updater_p updater = NULL; + const char * mode = NULL; + synctex_open_s open; + /* prepare the updater, the memory is the only one dynamically allocated */ + updater = (synctex_updater_p)_synctex_malloc(sizeof(synctex_updater_s)); + if (NULL == updater) { + _synctex_error("! synctex_updater_new_with_file: malloc problem"); + return NULL; + } + open = _synctex_open_v2(output,build_directory,0,synctex_ADD_QUOTES); + if (open.status < SYNCTEX_STATUS_OK) { + open = _synctex_open_v2(output,build_directory,0,synctex_DONT_ADD_QUOTES); + if (open.status < SYNCTEX_STATUS_OK) { + return_on_error: + _synctex_free(updater); + return updater = NULL; + } + } + /* OK, the file exists, we close it and reopen it with the correct mode. + * The receiver is now the owner of the "synctex" variable. */ + gzclose(open.file); + updater->file.as_ptr = NULL; + mode = _synctex_get_io_mode_name(open.io_mode|synctex_io_append_mask);/* either "a" or "ab", depending on the file extension */ + if (open.io_mode&synctex_io_gz_mask) { + if (NULL == (updater->file.as_FILE_p = fopen(open.synctex,mode))) { + no_write_error: + _synctex_error("! synctex_updater_new_with_file: Can't append to %s",open.synctex); + free(open.synctex); + goto return_on_error; + } + updater->print = &_synctex_updater_print; + updater->close = &_synctex_updater_close; + } else { + if (NULL == (updater->file.as_gzFile = gzopen(open.synctex,mode))) { + goto no_write_error; + } + updater->print = &_synctex_updater_print_gz; + updater->close = &_synctex_updater_close_gz; + } + printf("SyncTeX: updating %s...",open.synctex); + _synctex_free(open.synctex); + return updater; +} -/* Returns the distance between the hit point hitPoint=(H,V) and the given node. */ -synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); -int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); +void synctex_updater_append_magnification(synctex_updater_p updater, char * magnification){ + if (NULL==updater) { + return; + } + if (magnification && strlen(magnification)) { + updater->length += + updater->print(updater,"Magnification:%s\n",magnification); + } +} -/* The best container is the deeper box that contains the hit point (H,V). - * _synctex_eq_deepest_container starts with node whereas - * _synctex_box_child_deepest starts with node's children, if any - * if node is not a box, or a void box, NULL is returned. - * We traverse the node tree in a deep first manner and stop as soon as a result is found. */ -static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible); +void synctex_updater_append_x_offset(synctex_updater_p updater, char * x_offset){ + if (NULL==updater) { + return; + } + if (x_offset && strlen(x_offset)) { + updater->length += updater->print(updater,"X Offset:%s\n",x_offset); + } +} -/* Once a best container is found, the closest children are the closest nodes to the left or right of the hit point. - * Only horizontal and vertical offsets are used to compare the positions of the nodes. */ -SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); +void synctex_updater_append_y_offset(synctex_updater_p updater, char * y_offset){ + if (NULL==updater) { + return; + } + if (y_offset && strlen(y_offset)) { + updater->length += updater->print(updater,"Y Offset:%s\n",y_offset); + } +} -/* The closest container is the box that is the one closest to the given point. - * The "visible" version takes into account the visible dimensions instead of the real ones given by TeX. */ -SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible); - -#define SYNCTEX_MASK_LEFT 1 -#define SYNCTEX_MASK_RIGHT 2 - -int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v) { - synctex_node_t sheet = NULL; - synctex_node_t node = NULL; /* placeholder */ - synctex_node_t other_node = NULL; /* placeholder */ - synctex_point_t hitPoint = {0,0}; /* placeholder */ - synctex_node_set_t bestNodes = {NULL,NULL}; /* holds the best node */ - synctex_distances_t bestDistances = {INT_MAX,INT_MAX}; /* holds the best distances for the best node */ - synctex_node_t bestContainer = NULL; /* placeholder */ - if (NULL == (scanner = synctex_scanner_parse(scanner)) || 0 >= scanner->unit) {/* scanner->unit must be >0 */ - return 0; - } - /* Convert the given point to scanner integer coordinates */ - hitPoint.h = (h-scanner->x_offset)/scanner->unit; - hitPoint.v = (v-scanner->y_offset)/scanner->unit; - /* We will store in the scanner's buffer the result of the query. */ - free(SYNCTEX_START); - SYNCTEX_START = SYNCTEX_END = SYNCTEX_CUR = NULL; - /* Find the proper sheet */ - sheet = scanner->sheet; - while((sheet) && SYNCTEX_PAGE(sheet) != page) { - sheet = SYNCTEX_SIBLING(sheet); - } - if (NULL == sheet) { - return -1; - } - /* Now sheet points to the sheet node with proper page number */ - /* Here is how we work: - * At first we do not consider the visible box dimensions. This will cover the most frequent cases. - * Then we try with the visible box dimensions. - * We try to find a non void box containing the hit point. - * We browse all the horizontal boxes until we find one containing the hit point. */ - if ((node = SYNCTEX_NEXT_HORIZ_BOX(sheet))) { - do { - if (_synctex_point_in_box(hitPoint,node,synctex_YES)) { - /* Maybe the hitPoint belongs to a contained vertical box. */ -end: - /* This trick is for catching overlapping boxes */ - if ((other_node = SYNCTEX_NEXT_HORIZ_BOX(node))) { - do { - if (_synctex_point_in_box(hitPoint,other_node,synctex_YES)) { - node = _synctex_smallest_container(other_node,node); - } - } while((other_node = SYNCTEX_NEXT_HORIZ_BOX(other_node))); - } - /* node is the smallest horizontal box that contains hitPoint. */ - if ((bestContainer = _synctex_eq_deepest_container(hitPoint,node,synctex_YES))) { - node = bestContainer; - } - _synctex_eq_get_closest_children_in_box(hitPoint,node,&bestNodes,&bestDistances,synctex_YES); - if (bestNodes.right && bestNodes.left) { - if ((SYNCTEX_TAG(bestNodes.right)!=SYNCTEX_TAG(bestNodes.left)) - || (SYNCTEX_LINE(bestNodes.right)!=SYNCTEX_LINE(bestNodes.left)) - || (SYNCTEX_COLUMN(bestNodes.right)!=SYNCTEX_COLUMN(bestNodes.left))) { - if ((SYNCTEX_START = malloc(2*sizeof(synctex_node_t)))) { - if (bestDistances.left>bestDistances.right) { - ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.right; - ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.left; - } else { - ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.left; - ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.right; - } - SYNCTEX_END = SYNCTEX_START + 2*sizeof(synctex_node_t); - SYNCTEX_CUR = NULL; - return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); - } - return SYNCTEX_STATUS_ERROR; - } - /* both nodes have the same input coordinates - * We choose the one closest to the hit point */ - if (bestDistances.left>bestDistances.right) { - bestNodes.left = bestNodes.right; - } - bestNodes.right = NULL; - } else if (bestNodes.right) { - bestNodes.left = bestNodes.right; - } else if (!bestNodes.left){ - bestNodes.left = node; - } - if ((SYNCTEX_START = malloc(sizeof(synctex_node_t)))) { - * (synctex_node_t *)SYNCTEX_START = bestNodes.left; - SYNCTEX_END = SYNCTEX_START + sizeof(synctex_node_t); - SYNCTEX_CUR = NULL; - return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); - } - return SYNCTEX_STATUS_ERROR; - } - } while ((node = SYNCTEX_NEXT_HORIZ_BOX(node))); - /* All the horizontal boxes have been tested, - * None of them contains the hit point. - */ - } - /* We are not lucky */ - if ((node = SYNCTEX_CHILD(sheet))) { - goto end; - } - return 0; +void synctex_updater_free(synctex_updater_p updater){ + if (NULL==updater) { + return; + } + if (updater->length>0) { + updater->print(updater,"!%i\n",updater->length); + } + updater->close(updater); + _synctex_free(updater); + printf("... done.\n"); + return; } +#endif +#if defined(SYNCTEX_TESTING) # ifdef SYNCTEX_NOTHING # pragma mark - -# pragma mark Utilities +# pragma mark Testers # endif - -int _synctex_bail(void) { - _synctex_error("SyncTeX ERROR\n"); - return -1; +static int _synctex_input_copy_name(synctex_node_p input, char * name) { + char * copy = _synctex_malloc(strlen(name)+1); + memcpy(copy,name,strlen(name)+1); + _synctex_data_set_name(input,copy); + return 0; } -/* Rougly speaking, this is: - * node's h coordinate - hitPoint's h coordinate. - * If node is to the right of the hit point, then this distance is positive, - * if node is to the left of the hit point, this distance is negative.*/ -int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); -int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { - if (node) { - int min,med,max; - switch(node->class->type) { - /* The distance between a point and a box is special. - * It is not the euclidian distance, nor something similar. - * We have to take into account the particular layout, - * and the box hierarchy. - * Given a box, there are 9 regions delimited by the lines of the edges of the box. - * The origin being at the top left corner of the page, - * we also give names to the vertices of the box. - * - * 1 | 2 | 3 - * ---A---B---> - * 4 | 5 | 6 - * ---C---D---> - * 7 | 8 | 9 - * v v - */ - case synctex_node_type_hbox: - /* getting the box bounds, taking into account negative width, height and depth. */ - min = visible?SYNCTEX_HORIZ_V(node):SYNCTEX_HORIZ(node); - max = min + (visible?SYNCTEX_ABS_WIDTH_V(node):SYNCTEX_ABS_WIDTH(node)); - /* We allways have min <= max */ - if (hitPoint.h 0 */ - } else if (hitPoint.h>max) { - return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ - } else { - return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ - } - break; - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - /* getting the box bounds, taking into account negative width, height and depth. - * For these boxes, no visible dimension available */ - min = SYNCTEX_HORIZ(node); - max = min + SYNCTEX_ABS_WIDTH(node); - /* We allways have min <= max */ - if (hitPoint.h 0 */ - } else if (hitPoint.h>max) { - return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ - } else { - return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ - } - break; - case synctex_node_type_kern: - /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. - * The distance to the kern is very special, - * in general, there is no text material in the kern, - * this is why we compute the offset relative to the closest edge of the kern.*/ - max = SYNCTEX_WIDTH(node); - if (max<0) { - min = SYNCTEX_HORIZ(node); - max = min - max; - } else { - min = -max; - max = SYNCTEX_HORIZ(node); - min += max; - } - med = (min+max)/2; - /* positive kern: '.' means text, '>' means kern offset - * ............. - * min>>>>med>>>>max - * ............... - * negative kern: '.' means text, '<' means kern offset - * ............................ - * min<<<max) { - return max - hitPoint.h - 1; /* same kind of penalty */ - } else if (hitPoint.h>med) { - /* do things like if the node had 0 width and was placed at the max edge + 1*/ - return max - hitPoint.h + 1; /* positive, the kern is to the right of the hitPoint */ - } else { - return min - hitPoint.h - 1; /* negative, the kern is to the left of the hitPoint */ - } - case synctex_node_type_glue: - case synctex_node_type_math: - return SYNCTEX_HORIZ(node) - hitPoint.h; - } - } - return INT_MAX;/* We always assume that the node is faraway to the right*/ -} -/* Rougly speaking, this is: - * node's v coordinate - hitPoint's v coordinate. - * If node is at the top of the hit point, then this distance is positive, - * if node is at the bottom of the hit point, this distance is negative.*/ -int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible); -int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible) { - (void)visible; /* unused */ - - if (node) { - int min,max; - switch(node->class->type) { - /* The distance between a point and a box is special. - * It is not the euclidian distance, nor something similar. - * We have to take into account the particular layout, - * and the box hierarchy. - * Given a box, there are 9 regions delimited by the lines of the edges of the box. - * The origin being at the top left corner of the page, - * we also give names to the vertices of the box. - * - * 1 | 2 | 3 - * ---A---B---> - * 4 | 5 | 6 - * ---C---D---> - * 7 | 8 | 9 - * v v - */ - case synctex_node_type_hbox: - /* getting the box bounds, taking into account negative width, height and depth. */ - min = SYNCTEX_VERT_V(node); - max = min + SYNCTEX_ABS_DEPTH_V(node); - min -= SYNCTEX_ABS_HEIGHT_V(node); - /* We allways have min <= max */ - if (hitPoint.v 0 */ - } else if (hitPoint.v>max) { - return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ - } else { - return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ - } - break; - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_void_hbox: - /* getting the box bounds, taking into account negative width, height and depth. */ - min = SYNCTEX_VERT(node); - max = min + SYNCTEX_ABS_DEPTH(node); - min -= SYNCTEX_ABS_HEIGHT(node); - /* We allways have min <= max */ - if (hitPoint.v 0 */ - } else if (hitPoint.v>max) { - return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ - } else { - return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ - } - break; - case synctex_node_type_kern: - case synctex_node_type_glue: - case synctex_node_type_math: - return SYNCTEX_VERT(node) - hitPoint.v; - } - } - return INT_MAX;/* We always assume that the node is faraway to the top*/ -} - -SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node) { - float height, other_height; - if (SYNCTEX_ABS_WIDTH(node)SYNCTEX_ABS_WIDTH(other_node)) { - return other_node; - } - height = SYNCTEX_ABS_DEPTH(node) + SYNCTEX_ABS_HEIGHT(node); - other_height = SYNCTEX_ABS_DEPTH(other_node) + SYNCTEX_ABS_HEIGHT(other_node); - if (heightother_height) { - return other_node; - } - return node; -} - -synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { - if (node) { - if (0 == _synctex_point_h_distance(hitPoint,node,visible) - && 0 == _synctex_point_v_distance(hitPoint,node,visible)) { - return synctex_YES; - } - } - return synctex_NO; -} - -int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { - (void)visible; - - int result = INT_MAX; /* when the distance is meaning less (sheet, input...) */ - if (node) { - int minH,maxH,minV,maxV; - switch(node->class->type) { - /* The distance between a point and a box is special. - * It is not the euclidian distance, nor something similar. - * We have to take into account the particular layout, - * and the box hierarchy. - * Given a box, there are 9 regions delimited by the lines of the edges of the box. - * The origin being at the top left corner of the page, - * we also give names to the vertices of the box. - * - * 1 | 2 | 3 - * ---A---B---> - * 4 | 5 | 6 - * ---C---D---> - * 7 | 8 | 9 - * v v - * In each region, there is a different formula. - * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ - case synctex_node_type_vbox: - case synctex_node_type_void_vbox: - case synctex_node_type_hbox: - case synctex_node_type_void_hbox: - /* getting the box bounds, taking into account negative widths. */ - minH = SYNCTEX_HORIZ(node); - maxH = minH + SYNCTEX_ABS_WIDTH(node); - minV = SYNCTEX_VERT(node); - maxV = minV + SYNCTEX_ABS_DEPTH(node); - minV -= SYNCTEX_ABS_HEIGHT(node); - /* In what region is the point hitPoint=(H,V) ? */ - if (hitPoint.vminV) { - result = hitPoint.v - minV + minH - hitPoint.h; - } else { - result = minV - hitPoint.v + minH - hitPoint.h; - } - } else if (hitPoint.h>maxH) { - if (hitPoint.v>minV) { - result = hitPoint.v - minV + hitPoint.h - maxH; - } else { - result = minV - hitPoint.v + hitPoint.h - maxH; - } - } else if (hitPoint.v>minV) { - result = hitPoint.v - minV; - } else { - result = minV - hitPoint.v; - } - break; - case synctex_node_type_glue: - case synctex_node_type_math: - minH = SYNCTEX_HORIZ(node); - minV = SYNCTEX_VERT(node); - if (hitPoint.hminV) { - result = hitPoint.v - minV + minH - hitPoint.h; - } else { - result = minV - hitPoint.v + minH - hitPoint.h; - } - } else if (hitPoint.v>minV) { - result = hitPoint.v - minV + hitPoint.h - minH; - } else { - result = minV - hitPoint.v + hitPoint.h - minH; - } - break; - } - } - return result; -} - -static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { - if (node) { - synctex_node_t result = NULL; - synctex_node_t child = NULL; - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_hbox: - /* test the deep nodes first */ - if ((child = SYNCTEX_CHILD(node))) { - do { - if ((result = _synctex_eq_deepest_container(hitPoint,child,visible))) { - return result; - } - } while((child = SYNCTEX_SIBLING(child))); - } - /* is the hit point inside the box? */ - if (_synctex_point_in_box(hitPoint,node,visible)) { - /* for vboxes we try to use some node inside. - * Walk through the list of siblings until we find the closest one. - * Only consider siblings with children. */ - if ((node->class->type == synctex_node_type_vbox) && (child = SYNCTEX_CHILD(node))) { - int bestDistance = INT_MAX; - do { - if (SYNCTEX_CHILD(child)) { - int distance = _synctex_node_distance_to_point(hitPoint,child,visible); - if (distance < bestDistance) { - bestDistance = distance; - node = child; - } - } - } while((child = SYNCTEX_SIBLING(child))); - } - return node; - } - } - } - return NULL; -} - -/* Compares the locations of the hitPoint with the locations of the various nodes contained in the box. - * As it is an horizontal box, we only compare horizontal coordinates. */ -SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); -SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible) { - int result = 0; - if ((node = SYNCTEX_CHILD(node))) { - do { - int off7 = _synctex_point_h_distance(hitPoint,node,visible); - if (off7 > 0) { - /* node is to the right of the hit point. - * We compare node and the previously recorded one, through the recorded distance. - * If the nodes have the same tag, prefer the one with the smallest line number, - * if the nodes also have the same line number, prefer the one with the smallest column. */ - if (bestDistancesRef->right > off7) { - bestDistancesRef->right = off7; - bestNodesRef->right = node; - result |= SYNCTEX_MASK_RIGHT; - } else if (bestDistancesRef->right == off7 && bestNodesRef->right) { - if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) - && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) - || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) - && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { - bestNodesRef->right = node; - result |= SYNCTEX_MASK_RIGHT; - } - } - } else if (off7 == 0) { - /* hitPoint is inside node. */ - bestDistancesRef->left = bestDistancesRef->right = 0; - bestNodesRef->left = node; - bestNodesRef->right = NULL; - result |= SYNCTEX_MASK_LEFT; - } else { /* here off7 < 0, hitPoint is to the right of node */ - off7 = -off7; - if (bestDistancesRef->left > off7) { - bestDistancesRef->left = off7; - bestNodesRef->left = node; - result |= SYNCTEX_MASK_LEFT; - } else if (bestDistancesRef->left == off7 && bestNodesRef->left) { - if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) - && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) - || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) - && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { - bestNodesRef->left = node; - result |= SYNCTEX_MASK_LEFT; - } - } - } - } while((node = SYNCTEX_SIBLING(node))); - if (result & SYNCTEX_MASK_LEFT) { - /* the left node is new, try to narrow the result */ - if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { - bestNodesRef->left = node; - } - if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { - bestNodesRef->left = node; - } - } - if (result & SYNCTEX_MASK_RIGHT) { - /* the right node is new, try to narrow the result */ - if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { - bestNodesRef->right = node; - } - if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { - bestNodesRef->right = node; - } - } - } - return result; -} -SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible); -SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { - int result = 0; - if ((node = SYNCTEX_CHILD(node))) { - do { - int off7 = _synctex_point_v_distance(hitPoint,node,visible);/* this is what makes the difference with the h version above */ - if (off7 > 0) { - /* node is to the top of the hit point (below because TeX is oriented from top to bottom. - * We compare node and the previously recorded one, through the recorded distance. - * If the nodes have the same tag, prefer the one with the smallest line number, - * if the nodes also have the same line number, prefer the one with the smallest column. */ - if (bestDistancesRef->right > off7) { - bestDistancesRef->right = off7; - bestNodesRef->right = node; - result |= SYNCTEX_MASK_RIGHT; - } else if (bestDistancesRef->right == off7 && bestNodesRef->right) { - if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) - && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) - || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) - && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { - bestNodesRef->right = node; - result |= SYNCTEX_MASK_RIGHT; - } - } - } else if (off7 == 0) { - bestDistancesRef->left = bestDistancesRef->right = 0; - bestNodesRef->left = node; - bestNodesRef->right = NULL; - result |= SYNCTEX_MASK_LEFT; - } else { /* here off7 < 0 */ - off7 = -off7; - if (bestDistancesRef->left > off7) { - bestDistancesRef->left = off7; - bestNodesRef->left = node; - result |= SYNCTEX_MASK_LEFT; - } else if (bestDistancesRef->left == off7 && bestNodesRef->left) { - if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) - && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) - || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) - && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { - bestNodesRef->left = node; - result |= SYNCTEX_MASK_LEFT; - } - } - } - } while((node = SYNCTEX_SIBLING(node))); - if (result & SYNCTEX_MASK_LEFT) { - /* the left node is new, try to narrow the result */ - if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { - bestNodesRef->left = node; - } - if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { - bestNodesRef->left = node; - } - } - if (result & SYNCTEX_MASK_RIGHT) { - /* the right node is new, try to narrow the result */ - if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { - bestNodesRef->right = node; - } - if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { - bestNodesRef->right = node; - } - } - } - return result; -} -SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { - if (node) { - switch(node->class->type) { - case synctex_node_type_hbox: - return __synctex_eq_get_closest_children_in_hbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); - case synctex_node_type_vbox: - return __synctex_eq_get_closest_children_in_vbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); - } - } - return 0; -} - -SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible); -SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible) { - synctex_node_t best_node = NULL; - if ((node = SYNCTEX_CHILD(node))) { - do { - int distance = _synctex_node_distance_to_point(hitPoint,node,visible); - synctex_node_t candidate = NULL; - if (distance<=*distanceRef) { - *distanceRef = distance; - best_node = node; - } - switch(node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_hbox: - if ((candidate = __synctex_eq_closest_child(hitPoint,node,distanceRef,visible))) { - best_node = candidate; - } - } - } while((node = SYNCTEX_SIBLING(node))); - } - return best_node; -} -SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { - if (node) { - switch(node->class->type) { - case synctex_node_type_hbox: - case synctex_node_type_vbox: - { - int best_distance = INT_MAX; - synctex_node_t best_node = __synctex_eq_closest_child(hitPoint,node,&best_distance,visible); - if ((best_node)) { - synctex_node_t child = NULL; - switch(best_node->class->type) { - case synctex_node_type_vbox: - case synctex_node_type_hbox: - if ((child = SYNCTEX_CHILD(best_node))) { - best_distance = _synctex_node_distance_to_point(hitPoint,child,visible); - while((child = SYNCTEX_SIBLING(child))) { - int distance = _synctex_node_distance_to_point(hitPoint,child,visible); - if (distance<=best_distance) { - best_distance = distance; - best_node = child; - } - } - } - } - } - return best_node; - } - } - } - return NULL; +int synctex_test_setup_scanner_sheets_421(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p sheet = synctex_node_new(scanner,synctex_node_type_sheet); + _synctex_data_set_page(sheet,4); + SYNCTEX_TEST_BODY(TC, _synctex_data_page(sheet)==4,""); + synctex_node_free(scanner->sheet); + scanner->sheet = sheet; + sheet = synctex_node_new(scanner,synctex_node_type_sheet); + _synctex_data_set_page(sheet,2); + SYNCTEX_TEST_BODY(TC, _synctex_data_page(sheet)==2,""); + __synctex_tree_set_sibling(sheet, scanner->sheet); + scanner->sheet = sheet; + sheet = synctex_node_new(scanner,synctex_node_type_sheet); + _synctex_data_set_page(sheet,1); + SYNCTEX_TEST_BODY(TC, _synctex_data_page(sheet)==1,""); + __synctex_tree_set_sibling(sheet, scanner->sheet); + scanner->sheet = sheet; + return TC; } - -# ifdef SYNCTEX_NOTHING -# pragma mark - -# pragma mark Updater -# endif - -typedef int (*synctex_fprintf_t)(void *, const char * , ...); /* print formatted to either FILE * or gzFile */ - -# define SYNCTEX_BITS_PER_BYTE 8 - -struct __synctex_updater_t { - struct gzFile_s *file; /* the foo.synctex or foo.synctex.gz I/O identifier */ - synctex_fprintf_t fprintf; /* either fprintf or gzprintf */ - int length; /* the number of chars appended */ - struct _flags { - unsigned int no_gz:1; /* Whether zlib is used or not */ - unsigned int reserved:SYNCTEX_BITS_PER_BYTE*sizeof(int)-1; /* Align */ - } flags; -}; -# define SYNCTEX_FILE updater->file -# define SYNCTEX_NO_GZ ((updater->flags).no_gz) -# define SYNCTEX_fprintf (*(updater->fprintf)) - -synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * build_directory) { - synctex_updater_t updater = NULL; - char * synctex = NULL; - synctex_io_mode_t io_mode = 0; - const char * mode = NULL; - /* prepare the updater, the memory is the only one dynamically allocated */ - updater = (synctex_updater_t)_synctex_malloc(sizeof(synctex_updater_t)); - if (NULL == updater) { - _synctex_error("! synctex_updater_new_with_file: malloc problem"); - return NULL; - } - if (_synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_ADD_QUOTES,&io_mode) - && _synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_DONT_ADD_QUOTES,&io_mode)) { -return_on_error: - free(updater); - updater = NULL; - return NULL; - } - /* OK, the file exists, we close it and reopen it with the correct mode. - * The receiver is now the owner of the "synctex" variable. */ - gzclose(SYNCTEX_FILE); - SYNCTEX_FILE = NULL; - SYNCTEX_NO_GZ = (io_mode&synctex_io_gz_mask)?synctex_NO:synctex_YES; - mode = _synctex_get_io_mode_name(io_mode|synctex_io_append_mask);/* either "a" or "ab", depending on the file extension */ - if (SYNCTEX_NO_GZ) { - if (NULL == (SYNCTEX_FILE = (void *)fopen(synctex,mode))) { -no_write_error: - _synctex_error("! synctex_updater_new_with_file: Can't append to %s",synctex); - free(synctex); - goto return_on_error; - } - updater->fprintf = (synctex_fprintf_t)(&fprintf); - } else { - if (NULL == (SYNCTEX_FILE = (void *)gzopen(synctex,mode))) { - goto no_write_error; - } - updater->fprintf = (synctex_fprintf_t)(&gzprintf); - } - printf("SyncTeX: updating %s...",synctex); - free(synctex); - return updater; -} - - -void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification){ - if (NULL==updater) { - return; - } - if (magnification && strlen(magnification)) { - updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Magnification:%s\n",magnification); - } -} - -void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset){ - if (NULL==updater) { - return; - } - if (x_offset && strlen(x_offset)) { - updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"X Offset:%s\n",x_offset); - } -} - -void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset){ - if (NULL==updater) { - return; - } - if (y_offset && strlen(y_offset)) { - updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Y Offset:%s\n",y_offset); - } -} - -void synctex_updater_free(synctex_updater_t updater){ - if (NULL==updater) { - return; - } - if (updater->length>0) { - SYNCTEX_fprintf(SYNCTEX_FILE,"!%i\n",updater->length); - } - if (SYNCTEX_NO_GZ) { - fclose((FILE *)SYNCTEX_FILE); - } else { - gzclose((gzFile)SYNCTEX_FILE); - } - free(updater); - printf("... done.\n"); - return; +int synctex_test_input(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p input = synctex_node_new(scanner,synctex_node_type_input); + _synctex_data_set_tag(input,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(input)==421,""); + _synctex_data_set_tag(input,124); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(input)==124,""); + _synctex_data_set_line(input,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==421,""); + _synctex_data_set_line(input,214); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==214,""); + _synctex_data_set_line(input,214); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==214,""); + _synctex_input_copy_name(input,"214"); + SYNCTEX_TEST_BODY(TC, 0==memcmp(_synctex_data_name(input),"214",4),""); + _synctex_input_copy_name(input,"421421"); + + SYNCTEX_TEST_BODY(TC, + 0==memcmp(_synctex_data_name(input), + "421421", + 4), + ""); + synctex_node_free(input); + return TC; +} +int synctex_test_proxy(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p proxy = synctex_node_new(scanner,synctex_node_type_proxy); + synctex_node_p target = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_tree_set_target(proxy,target); + _synctex_data_set_tag(target,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(proxy)==421,""); + synctex_node_free(proxy); + synctex_node_free(target); + return TC; +} +int synctex_test_handle(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p handle = synctex_node_new(scanner,synctex_node_type_handle); + synctex_node_p proxy = synctex_node_new(scanner, synctex_node_type_proxy); + synctex_node_p target = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_tree_set_target(handle,target); + _synctex_data_set_tag(target,421); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(target)==421,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(handle)==421,""); + _synctex_data_set_line(target,214); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(target)==214,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(target)==214,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(handle)==214,""); + _synctex_data_set_column(target,142); + SYNCTEX_TEST_BODY(TC, _synctex_data_column(target)==142,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(target)==142,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(handle)==142,""); + _synctex_tree_set_target(proxy,target); + _synctex_tree_set_target(handle,proxy); + _synctex_data_set_tag(target,412); + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(target)==412,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(target)==412,""); + SYNCTEX_TEST_BODY(TC, synctex_node_tag(handle)==412,""); + _synctex_data_set_line(target,124); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(target)==124,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(target)==124,""); + SYNCTEX_TEST_BODY(TC, synctex_node_line(handle)==124,""); + _synctex_data_set_column(target,241); + SYNCTEX_TEST_BODY(TC, _synctex_data_column(target)==241,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(target)==241,""); + SYNCTEX_TEST_BODY(TC, synctex_node_column(handle)==241,""); + synctex_node_free(handle); + synctex_node_free(proxy); + synctex_node_free(target); + return TC; +} +int synctex_test_setup_scanner_input(synctex_scanner_p scanner) { + int TC = 0; + synctex_node_p input = synctex_node_new(scanner,synctex_node_type_input); + _synctex_data_set_tag(input,4); + _synctex_input_copy_name(input,"21"); + _synctex_data_set_line(input,421); + synctex_node_free(scanner->input); + scanner->input = input; + SYNCTEX_TEST_BODY(TC, _synctex_data_tag(input)==4,""); + SYNCTEX_TEST_BODY(TC, strcmp(_synctex_data_name(input),"21")==0,""); + SYNCTEX_TEST_BODY(TC, _synctex_data_line(input)==421,""); + return TC; +} +int synctex_test_setup_nodes(synctex_scanner_p scanner, synctex_node_r nodes) { + int TC = 0; + int n; + for (n=0;nsheet; + synctex_node_p node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, synctex_node_page(node)==synctex_node_page(sheet),""); + return TC; +} +int synctex_test_display_query(synctex_scanner_p scanner) { + int TC = synctex_test_setup_scanner_sheets_421(scanner); + synctex_node_p sheet = scanner->sheet; + synctex_node_p node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, node==synctex_node_child(sheet),""); + __synctex_node_make_friend_tlc(node); + SYNCTEX_TEST_BODY(TC, _synctex_scanner_friend(scanner, 25)==node,""); + sheet = __synctex_tree_sibling(sheet); + node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, node==synctex_node_child(sheet),""); + __synctex_node_make_friend_tlc(node); + SYNCTEX_TEST_BODY(TC, _synctex_scanner_friend(scanner, 25)==node,""); + sheet = __synctex_tree_sibling(sheet); + node = synctex_node_new(scanner,synctex_node_type_rule); + _synctex_data_set_tag(node,4); + _synctex_data_set_line(node,21); + synctex_node_free(_synctex_node_set_child(sheet,node)); + SYNCTEX_TEST_BODY(TC, node==synctex_node_child(sheet),""); + __synctex_node_make_friend_tlc(node); + SYNCTEX_TEST_BODY(TC, (_synctex_scanner_friend(scanner, 25)==node),""); + synctex_test_setup_scanner_input(scanner); + scanner->flags.has_parsed = synctex_YES; +#if 1 + SYNCTEX_TEST_BODY(TC, (synctex_display_query(scanner,"21",21,4,-1)==3),""); +#endif + return TC; +} +typedef struct { + int s; /* status */ + char n[25]; /* name */ +} synctex_test_sn_s; + +synctex_test_sn_s synctex_test_tmp_sn(char * content) { + synctex_test_sn_s sn = {0, "/tmp/test.XXXXXX.synctex"}; + FILE *sfp; + int fd = mkstemps(sn.n,8); + if (fd < 0) { + fprintf(stderr, "%s: %s\n", sn.n, strerror(errno)); + sn.s = -1; + return sn; + } + if ((sfp = fdopen(fd, "w+")) == NULL) { + unlink(sn.n); + close(fd); + fprintf(stderr, "%s: %s\n", sn.n, strerror(errno)); + sn.s = -2; + return sn; + } + sn.s = fputs(content,sfp); + printf("temp:%s\n%i\n",sn.n,sn.s); + fclose(sfp); + if (sn.s==0) { + sn.s = -2; + unlink(sn.n); + } + return sn; +} +int synctex_test_sheet_1() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "[1,10:20,350:330,330,0 \n" /*60-89*/ + "] \n" /*90-99*/ + "} \n" /*00-09*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + printf("Created nodes:\n"); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = synctex_node_next(node); + } + synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +int synctex_test_sheet_2() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "(1,10:20,350:330,330,0 \n" /*60-89*/ + ") \n" /*90-99*/ + "} \n" /*00-09*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + printf("Created nodes:\n"); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = _synctex_node_next(node); + } + TC += synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +int synctex_test_charindex() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "[1,10:20,350:330,330,0 \n" /*60-89*/ + "(1,58:20,100:250,10,5 \n" /*90-119*/ + "f1000:50,100 \n" /*20-39*/ + ") \n" /*40-49*/ + "] \n" /*50-59*/ + "} \n" /*60-69*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + printf("Created nodes:\n"); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = synctex_node_next(node); + } + TC += synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; } +int synctex_test_form() { + int TC = 0; + char * content = + "SyncTeX Version:1 \n" /*00-19*/ + "Input:1:./1.tex \n" /*20-39*/ + "Output:pdf \n" /*40-59*/ + "Magnification:100000000 \n" /*60-89*/ + "Unit:1 \n" /*90-99*/ + "X Offset:0 \n" /*00-19*/ + "Y Offset:0 \n" /*20-39*/ + "Content: \n" /*40-49*/ + "{1 \n" /*50-59*/ + "[1,10:20,350:330,330,0 \n" /*60-89*/ + "(1,58:20,100:250,10,5 \n" /*90-119*/ + "f1000:50,100 \n" /*20-39*/ + ") \n" /*40-49*/ + "] \n" /*50-59*/ + "} \n" /*60-69*/ + "<1000 \n" /*70-79*/ + "(1,63:0,0:100,8,3 \n" /*80-99*/ + ") \n" /*00-09*/ + "> \n" /*10-19*/ + "Postamble:\n"; + synctex_test_sn_s sn = synctex_test_tmp_sn(content); + if (sn.s>0) { + synctex_scanner_p scanner = synctex_scanner_new_with_output_file(sn.n, NULL, synctex_YES); + synctex_node_p node = synctex_scanner_handle(scanner); + while (node) { + printf("%s\n",_synctex_node_abstract(node)); + node = _synctex_node_next(node); + } + TC += synctex_scanner_free(scanner); + unlink(sn.n); + } else { + ++TC; + } + return TC; +} +#endif diff --git a/core/synctex/synctex_parser.h b/core/synctex/synctex_parser.h index 4aca41501..3224ddec3 100644 --- a/core/synctex/synctex_parser.h +++ b/core/synctex/synctex_parser.h @@ -1,346 +1,424 @@ -/* -Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr - -This file is part of the SyncTeX package. - -Latest Revision: Tue Jun 14 08:23:30 UTC 2011 - -Version: 1.16 - -See synctex_parser_readme.txt for more details - -License: --------- -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE - -Except as contained in this notice, the name of the copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in this Software without prior written -authorization from the copyright holder. - -Acknowledgments: ----------------- -The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, -and significant help from XeTeX developer Jonathan Kew - -Nota Bene: ----------- -If you include or use a significant part of the synctex package into a software, -I would appreciate to be listed as contributor and see "SyncTeX" highlighted. - -Version 1 -Thu Jun 19 09:39:21 UTC 2008 - +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.19) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + + ## Acknowledgments: + + The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, + and significant help from __XeTeX__ developer Jonathan Kew. + + ## Nota Bene: + + If you include or use a significant part of the __SyncTeX__ package into a software, + I would appreciate to be listed as contributor and see "__SyncTeX__" highlighted. */ #ifndef __SYNCTEX_PARSER__ # define __SYNCTEX_PARSER__ #ifdef __cplusplus extern "C" { #endif - -/* synctex_node_t is the type for all synctex nodes. - * The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */ -typedef struct _synctex_node * synctex_node_t; - -/* The main synctex object is a scanner - * Its implementation is considered private. - * The basic workflow is - * - create a "synctex scanner" with the contents of a file - * - perform actions on that scanner like display or edit queries - * - free the scanner when the work is done - */ -typedef struct __synctex_scanner_t _synctex_scanner_t; -typedef _synctex_scanner_t * synctex_scanner_t; - -/* This is the designated method to create a new synctex scanner object. - * output is the pdf/dvi/xdv file associated to the synctex file. - * If necessary, it can be the tex file that originated the synctex file - * but this might cause problems if the \jobname has a custom value. - * Despite this method can accept a relative path in practice, - * you should only pass a full path name. - * The path should be encoded by the underlying file system, - * assuming that it is based on 8 bits characters, including UTF8, - * not 16 bits nor 32 bits. - * The last file extension is removed and replaced by the proper extension. - * Then the private method _synctex_scanner_new_with_contents_of_file is called. - * NULL is returned in case of an error or non existent file. - * Once you have a scanner, use the synctex_display_query and synctex_edit_query below. - * The new "build_directory" argument is available since version 1.5. - * It is the directory where all the auxiliary stuff is created. - * Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory. - * This is the case in MikTeX (I will include this into TeX Live). - * This directory path can be nil, it will be ignored then. - * It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file. - * If no synctex file is found in the same directory as the output file, then we try to find one in the build directory. - * Please note that this new "build_directory" is provided as a convenient argument but should not be used. - * In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file. - * The new "parse" argument is available since version 1.5. In general, use 1. - * Use 0 only if you do not want to parse the content but just check the existence. - */ -synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); - -/* This is the designated method to delete a synctex scanner object. - * Frees all the memory, you must call it when you are finished with the scanner. - */ -void synctex_scanner_free(synctex_scanner_t scanner); - -/* Send this message to force the scanner to parse the contents of the synctex output file. - * Nothing is performed if the file was already parsed. - * In each query below, this message is sent, but if you need to access information more directly, - * you must be sure that the parsing did occur. - * Usage: - * if((my_scanner = synctex_scanner_parse(my_scanner))) { - * continue with my_scanner... - * } else { - * there was a problem - * } - */ -synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner); - -/* The main entry points. - * Given the file name, a line and a column number, synctex_display_query returns the number of nodes - * satisfying the contrain. Use code like - * - * if(synctex_display_query(scanner,name,line,column)>0) { - * synctex_node_t node; - * while((node = synctex_next_result(scanner))) { - * // do something with node - * ... - * } - * } - * - * For example, one can - * - highlight each resulting node in the output, using synctex_node_h and synctex_node_v - * - highlight all the rectangles enclosing those nodes, using synctex_box_... functions - * - highlight just the character using that information - * - * Given the page and the position in the page, synctex_edit_query returns the number of nodes - * satisfying the contrain. Use code like - * - * if(synctex_edit_query(scanner,page,h,v)>0) { - * synctex_node_t node; - * while(node = synctex_next_result(scanner)) { - * // do something with node - * ... - * } - * } - * - * For example, one can - * - highlight each resulting line in the input, - * - highlight just the character using that information - * - * page is 1 based - * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. - * If you make a new query, the result of the previous one is discarded. - * If one of this function returns a non positive integer, - * it means that an error occurred. - * - * Both methods are conservative, in the sense that matching is weak. - * If the exact column number is not found, there will be an answer with the whole line. - * - * Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library. - * You can browse their code for a concrete implementation. - */ -int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column); -int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v); -synctex_node_t synctex_next_result(synctex_scanner_t scanner); - -/* Display all the information contained in the scanner object. - * If the records are too numerous, only the first ones are displayed. - * This is mainly for informatinal purpose to help developers. - */ -void synctex_scanner_display(synctex_scanner_t scanner); - -/* The x and y offset of the origin in TeX coordinates. The magnification - These are used by pdf viewers that want to display the real box size. - For example, getting the horizontal coordinates of a node would require - synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) - Getting its TeX width would simply require - synctex_node_box_width(node)*synctex_scanner_magnification(scanner) - but direct methods are available for that below. - */ -int synctex_scanner_x_offset(synctex_scanner_t scanner); -int synctex_scanner_y_offset(synctex_scanner_t scanner); -float synctex_scanner_magnification(synctex_scanner_t scanner); - -/* Managing the input file names. - * Given a tag, synctex_scanner_get_name will return the corresponding file name. - * Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag. - * The file name must be the very same as understood by TeX. - * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. - * No automatic path expansion is performed. - * Finally, synctex_scanner_input is the first input node of the scanner. - * To browse all the input node, use a loop like - * - * if((input_node = synctex_scanner_input(scanner))){ - * do { - * blah - * } while((input_node=synctex_node_sibling(input_node))); - * } - * - * The output is the name that was used to create the scanner. - * The synctex is the real name of the synctex file, - * it was obtained from output by setting the proper file extension. - */ -const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag); -int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); -synctex_node_t synctex_scanner_input(synctex_scanner_t scanner); -const char * synctex_scanner_get_output(synctex_scanner_t scanner); -const char * synctex_scanner_get_synctex(synctex_scanner_t scanner); - -/* Browsing the nodes - * parent, child and sibling are standard names for tree nodes. - * The parent is one level higher, the child is one level deeper, - * and the sibling is at the same level. - * The sheet of a node is the first ancestor, it is of type sheet. - * A node and its sibling have the same parent. - * A node is the parent of its child. - * A node is either the child of its parent, - * or belongs to the sibling chain of its parent's child. - * The next node is either the child, the sibling or the parent's sibling, - * unless the parent is a sheet. - * This allows to navigate through all the nodes of a given sheet node: - * - * synctex_node_t node = sheet; - * while((node = synctex_node_next(node))) { - * // do something with node - * } - * - * With synctex_sheet_content, you can retrieve the sheet node given the page. - * The page is 1 based, according to TeX standards. - * Conversely synctex_node_sheet allows to retrieve the sheet containing a given node. - */ -synctex_node_t synctex_node_parent(synctex_node_t node); -synctex_node_t synctex_node_sheet(synctex_node_t node); -synctex_node_t synctex_node_child(synctex_node_t node); -synctex_node_t synctex_node_sibling(synctex_node_t node); -synctex_node_t synctex_node_next(synctex_node_t node); -synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page); - -/* These are the types of the synctex nodes */ -typedef enum { - synctex_node_type_error = 0, - synctex_node_type_input, - synctex_node_type_sheet, - synctex_node_type_vbox, - synctex_node_type_void_vbox, - synctex_node_type_hbox, - synctex_node_type_void_hbox, - synctex_node_type_kern, - synctex_node_type_glue, - synctex_node_type_math, - synctex_node_type_boundary, - synctex_node_number_of_types -} synctex_node_type_t; - -/* synctex_node_type gives the type of a given node, - * synctex_node_isa gives the same information as a human readable text. */ -synctex_node_type_t synctex_node_type(synctex_node_t node); -const char * synctex_node_isa(synctex_node_t node); - -/* This is primarily used for debugging purpose. - * The second one logs information for the node and recursively displays information for its next node */ -void synctex_node_log(synctex_node_t node); -void synctex_node_display(synctex_node_t node); - -/* Given a node, access to its tag, line and column. - * The line and column numbers are 1 based. - * The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line. - * When the tag is known, the scanner of the node will give the corresponding file name. - * When the tag is known, the scanner of the node will give the name. - */ -int synctex_node_tag(synctex_node_t node); -int synctex_node_line(synctex_node_t node); -int synctex_node_column(synctex_node_t node); - -/* This is the page where the node appears. - * This is a 1 based index as given by TeX. - */ -int synctex_node_page(synctex_node_t node); - -/* For quite all nodes, horizontal, vertical coordinates, and width. - * These are expressed in TeX small points coordinates, with origin at the top left corner. - */ -int synctex_node_h(synctex_node_t node); -int synctex_node_v(synctex_node_t node); -int synctex_node_width(synctex_node_t node); - -/* For all nodes, dimensions of the enclosing box. - * These are expressed in TeX small points coordinates, with origin at the top left corner. - * A box is enclosing itself. - */ -int synctex_node_box_h(synctex_node_t node); -int synctex_node_box_v(synctex_node_t node); -int synctex_node_box_width(synctex_node_t node); -int synctex_node_box_height(synctex_node_t node); -int synctex_node_box_depth(synctex_node_t node); - -/* For quite all nodes, horizontal, vertical coordinates, and width. - * The visible dimensions are bigger than real ones to compensate 0 width boxes - * that do contain nodes. - * These are expressed in page coordinates, with origin at the top left corner. - * A box is enclosing itself. - */ -float synctex_node_visible_h(synctex_node_t node); -float synctex_node_visible_v(synctex_node_t node); -float synctex_node_visible_width(synctex_node_t node); -/* For all nodes, visible dimensions of the enclosing box. - * A box is enclosing itself. - * The visible dimensions are bigger than real ones to compensate 0 width boxes - * that do contain nodes. - */ -float synctex_node_box_visible_h(synctex_node_t node); -float synctex_node_box_visible_v(synctex_node_t node); -float synctex_node_box_visible_width(synctex_node_t node); -float synctex_node_box_visible_height(synctex_node_t node); -float synctex_node_box_visible_depth(synctex_node_t node); - -/* The main synctex updater object. - * This object is used to append information to the synctex file. - * Its implementation is considered private. - * It is used by the synctex command line tool to take into account modifications - * that could occur while postprocessing files by dvipdf like filters. - */ -typedef struct __synctex_updater_t _synctex_updater_t; -typedef _synctex_updater_t * synctex_updater_t; - -/* Designated initializer. - * Once you are done with your whole job, - * free the updater */ -synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory); - -/* Use the next functions to append records to the synctex file, - * no consistency tests made on the arguments */ -void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification); -void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset); -void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset); - -/* You MUST free the updater, once everything is properly appended */ -void synctex_updater_free(synctex_updater_t updater); + +# define SYNCTEX_VERSION_STRING "1.19" + + /* The main synctex object is a scanner. + * Its implementation is considered private. + * The basic workflow is + * - create a "synctex scanner" with the contents of a file + * - perform actions on that scanner like + synctex_display_query or synctex_edit_query below. + * - perform actions on nodes returned by the scanner + * - free the scanner when the work is done + */ + typedef struct synctex_scanner_t synctex_scanner_s; + typedef synctex_scanner_s * synctex_scanner_p; + + /** + * This is the designated method to create + * a new synctex scanner object. + * - argument output: the pdf/dvi/xdv file associated + * to the synctex file. + * If necessary, it can be the tex file that + * originated the synctex file but this might cause + * problems if the \jobname has a custom value. + * Despite this method can accept a relative path + * in practice, you should only pass full paths. + * The path should be encoded by the underlying + * file system, assuming that it is based on + * 8 bits characters, including UTF8, + * not 16 bits nor 32 bits. + * The last file extension is removed and + * replaced by the proper extension, + * either synctex or synctex.gz. + * - argument build_directory: It is the directory where + * all the auxiliary stuff is created. + * If no synctex file is found in the same directory + * as the output file, then we try to find one in + * this build directory. + * It is the directory where all the auxiliary + * stuff is created. Sometimes, the synctex output + * file and the pdf, dvi or xdv files are not + * created in the same location. See MikTeX. + * This directory path can be NULL, + * it will be ignored then. + * It can be either absolute or relative to the + * directory of the output pdf (dvi or xdv) file. + * Please note that this new argument is provided + * as a convenience but should not be used. + * Available since version 1.5. + * - argument parse: In general, use 1. + * Use 0 only if you do not want to parse the + * content but just check for existence. + * Available since version 1.5 + * - resturn: a scanner. NULL is returned in case + * of an error or non existent file. + */ + synctex_scanner_p synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); + + /** + * Designated method to delete a synctex scanner object, + * including all its internal resources. + * Frees all the memory, you must call it when you are finished with the scanner. + * - argument scanner: a scanner. + * - returns: an integer used for testing purposes. + */ + int synctex_scanner_free(synctex_scanner_p scanner); + + /** + * Send this message to force the scanner to + * parse the contents of the synctex output file. + * Nothing is performed if the file was already parsed. + * In each query below, this message is sent, + * but if you need to access information more directly, + * you must ensure that the parsing did occur. + * Usage: + * if((my_scanner = synctex_scanner_parse(my_scanner))) { + * continue with my_scanner... + * } else { + * there was a problem + * } + * - returns: the argument on success. + * On failure, frees scanner and returns NULL. + */ + synctex_scanner_p synctex_scanner_parse(synctex_scanner_p scanner); + + /* synctex_node_p is the type for all synctex nodes. + * Its implementation is considered private. + * The synctex file is parsed into a tree of nodes, either sheet, form, boxes, math nodes... */ + + typedef struct synctex_node_t synctex_node_s; + typedef synctex_node_s * synctex_node_p; + + /* The main entry points. + * Given the file name, a line and a column number, synctex_display_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_display_query(scanner,name,line,column,page_hint)>0) { + * synctex_node_p node; + * while((node = synctex_scanner_next_result(scanner))) { + * // do something with node + * ... + * } + * } + * + * Please notice that since version 1.19, + * there is a new argument page_hint. + * The results in pages closer to page_hint are given first. + * For example, one can + * - highlight each resulting node in the output, using synctex_node_visible_h and synctex_node_visible_v + * - highlight all the rectangles enclosing those nodes, using synctex_node_box_visible_... functions + * - highlight just the character using that information + * + * Given the page and the position in the page, synctex_edit_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_edit_query(scanner,page,h,v)>0) { + * synctex_node_p node; + * while(node = synctex_scanner_next_result(scanner)) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting line in the input, + * - highlight just the character using that information + * + * page is 1 based + * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. + * If you make a new query, the result of the previous one is discarded. If you need to make more than one query + * in parallel, use the iterator API exposed in + * the synctex_parser_private.h header. + * If one of this function returns a negative integer, + * it means that an error occurred. + * + * Both methods are conservative, in the sense that matching is weak. + * If the exact column number is not found, there will be an answer with the whole line. + * + * Sumatra-PDF, Skim, iTeXMac2, TeXShop and Texworks are examples of open source software that use this library. + * You can browse their code for a concrete implementation. + */ + typedef long synctex_status_t; + /* The page_hint argument is used to resolve ambiguities. + * Whenever, different matches occur, the ones closest + * to the page will be given first. Pass a negative number + * when in doubt. Using pdf forms may lead to ambiguities. + */ + synctex_status_t synctex_display_query(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint); + synctex_status_t synctex_edit_query(synctex_scanner_p scanner,int page,float h,float v); + synctex_node_p synctex_scanner_next_result(synctex_scanner_p scanner); + synctex_status_t synctex_scanner_reset_result(synctex_scanner_p scanner); + + /** + * The horizontal and vertical location, + * the width, height and depth of a box enclosing node. + * All dimensions are given in page coordinates + * as opposite to TeX coordinates. + * The origin is at the top left corner of the page. + * Code example for Qt5: + * (from TeXworks source TWSynchronize.cpp) + * QRectF nodeRect(synctex_node_box_visible_h(node), + * synctex_node_box_visible_v(node) - + * synctex_node_box_visible_height(node), + * synctex_node_box_visible_width(node), + * synctex_node_box_visible_height(node) + + * synctex_node_box_visible_depth(node)); + * Code example for Cocoa: + * NSRect bounds = [pdfPage + * boundsForBox:kPDFDisplayBoxMediaBox]; + * NSRect nodeRect = NSMakeRect( + * synctex_node_box_visible_h(node), + * NSMaxY(bounds)-synctex_node_box_visible_v(node) + + * synctex_node_box_visible_height(node), + * synctex_node_box_visible_width(node), + * synctex_node_box_visible_height(node) + + * synctex_node_box_visible_depth(node) + * ); + * The visible dimensions are bigger than real ones + * to compensate 0 width boxes or nodes intentionnaly + * put outside the box (using \kern for example). + * - parameter node: a node. + * - returns: a float. + * - author: JL + */ + float synctex_node_box_visible_h(synctex_node_p node); + float synctex_node_box_visible_v(synctex_node_p node); + float synctex_node_box_visible_width(synctex_node_p node); + float synctex_node_box_visible_height(synctex_node_p node); + float synctex_node_box_visible_depth(synctex_node_p node); + + /** + * For quite all nodes, horizontal and vertical coordinates, and width. + * All dimensions are given in page coordinates + * as opposite to TeX coordinates. + * The origin is at the top left corner of the page. + * The visible dimensions are bigger than real ones + * to compensate 0 width boxes or nodes intentionnaly + * put outside the box (using \kern for example). + * All nodes have coordinates, but all nodes don't + * have non null size. For example, math nodes + * have no width according to TeX, and in that case + * synctex_node_visible_width simply returns 0. + * The same holds for kern nodes that do not have + * height nor depth, etc... + */ + float synctex_node_visible_h(synctex_node_p node); + float synctex_node_visible_v(synctex_node_p node); + float synctex_node_visible_width(synctex_node_p node); + float synctex_node_visible_height(synctex_node_p node); + float synctex_node_visible_depth(synctex_node_p node); + + /** + * Given a node, access to its tag, line and column. + * The line and column numbers are 1 based. + * The latter is not yet fully supported in TeX, + * the default implementation returns 0 + * which means the whole line. + * synctex_node_get_name returns the path of the + * TeX source file that was used to create the node. + * When the tag is known, the scanner of the node + * will also give that same file name, see + * synctex_scanner_get_name below. + */ + int synctex_node_tag(synctex_node_p node); + int synctex_node_line(synctex_node_p node); + int synctex_node_column(synctex_node_p node); + const char * synctex_node_get_name(synctex_node_p node); + + + /** + This is the page where the node appears. + * This is a 1 based index as given by TeX. + */ + int synctex_node_page(synctex_node_p node); + + /** + * Display all the information contained in the scanner. + * If the records are too numerous, only the first ones are displayed. + * This is mainly for informational purpose to help developers. + */ + void synctex_scanner_display(synctex_scanner_p scanner); + + /* Managing the input file names. + * Given a tag, synctex_scanner_get_name will return the corresponding file name. + * Conversely, given a file name, synctex_scanner_get_tag will return, the corresponding tag. + * The file name must be the very same as understood by TeX. + * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. + * No automatic path expansion is performed. + * Finally, synctex_scanner_input is the first input node of the scanner. + * To browse all the input node, use a loop like + * ... + * synctex_node_p = input_node; + * ... + * if((input_node = synctex_scanner_input(scanner))) { + * do { + * blah + * } while((input_node=synctex_node_sibling(input_node))); + * } + * + * The output is the name that was used to create the scanner. + * The synctex is the real name of the synctex file, + * it was obtained from output by setting the proper file extension. + */ + const char * synctex_scanner_get_name(synctex_scanner_p scanner,int tag); + + int synctex_scanner_get_tag(synctex_scanner_p scanner,const char * name); + + synctex_node_p synctex_scanner_input(synctex_scanner_p scanner); + synctex_node_p synctex_scanner_input_with_tag(synctex_scanner_p scanner,int tag); + const char * synctex_scanner_get_output(synctex_scanner_p scanner); + const char * synctex_scanner_get_synctex(synctex_scanner_p scanner); + + /* The x and y offset of the origin in TeX coordinates. The magnification + These are used by pdf viewers that want to display the real box size. + For example, getting the horizontal coordinates of a node would require + synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) + Getting its TeX width would simply require + synctex_node_box_width(node)*synctex_scanner_magnification(scanner) + but direct methods are available for that below. + */ + int synctex_scanner_x_offset(synctex_scanner_p scanner); + int synctex_scanner_y_offset(synctex_scanner_p scanner); + float synctex_scanner_magnification(synctex_scanner_p scanner); + + /** + * ## Browsing the nodes + * parent, child and sibling are standard names for tree nodes. + * The parent is one level higher, + * the child is one level deeper, + * and the sibling is at the same level. + * A node and its sibling have the same parent. + * A node is the parent of its children. + * A node is either the child of its parent, + * or belongs to the sibling chain of its parent's child. + * The sheet or form of a node is the topmost ancestor, + * it is of type sheet or form. + * The next node is either the child, the sibling or the parent's sibling, + * unless the parent is a sheet, a form or NULL. + * This allows to navigate through all the nodes of a given sheet node: + * + * synctex_node_p node = sheet; + * while((node = synctex_node_next(node))) { + * // do something with node + * } + * + * With synctex_sheet_content and synctex_form_content, + * you can retrieve the sheet node given the page + * or form tag. + * The page is 1 based, according to TeX standards. + * Conversely synctex_node_parent_sheet or + * synctex_node_parent_form allows to retrieve + * the sheet or the form containing a given node. + * Notice that a node is not contained in a sheet + * and a form at the same time. + * Some nodes are not contained in either (handles). + */ + + synctex_node_p synctex_node_parent(synctex_node_p node); + synctex_node_p synctex_node_parent_sheet(synctex_node_p node); + synctex_node_p synctex_node_parent_form(synctex_node_p node); + synctex_node_p synctex_node_child(synctex_node_p node); + synctex_node_p synctex_node_last_child(synctex_node_p node); + synctex_node_p synctex_node_sibling(synctex_node_p node); + synctex_node_p synctex_node_last_sibling(synctex_node_p node); + synctex_node_p synctex_node_arg_sibling(synctex_node_p node); + synctex_node_p synctex_node_next(synctex_node_p node); + + /** + * Top level entry points. + * The scanner owns a list of sheet siblings and + * a list of form siblings. + * Sheets or forms have one child which is a box: + * theie contents. + * - argument page: 1 based sheet page number. + * - argument tag: 1 based form tag number. + */ + synctex_node_p synctex_sheet(synctex_scanner_p scanner,int page); + synctex_node_p synctex_sheet_content(synctex_scanner_p scanner,int page); + synctex_node_p synctex_form(synctex_scanner_p scanner,int tag); + synctex_node_p synctex_form_content(synctex_scanner_p scanner,int tag); + + /* This is primarily used for debugging purpose. + * The second one logs information for the node and recursively displays information for its next node */ + void synctex_node_log(synctex_node_p node); + void synctex_node_display(synctex_node_p node); + + /* For quite all nodes, horizontal, vertical coordinates, and width. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + */ + int synctex_node_h(synctex_node_p node); + int synctex_node_v(synctex_node_p node); + int synctex_node_width(synctex_node_p node); + int synctex_node_height(synctex_node_p node); + int synctex_node_depth(synctex_node_p node); + + /* For all nodes, dimensions of the enclosing box. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ + int synctex_node_box_h(synctex_node_p node); + int synctex_node_box_v(synctex_node_p node); + int synctex_node_box_width(synctex_node_p node); + int synctex_node_box_height(synctex_node_p node); + int synctex_node_box_depth(synctex_node_p node); #ifdef __cplusplus } #endif #endif diff --git a/core/synctex/synctex_parser_advanced.h b/core/synctex/synctex_parser_advanced.h new file mode 100644 index 000000000..e395aaff7 --- /dev/null +++ b/core/synctex/synctex_parser_advanced.h @@ -0,0 +1,552 @@ +/* + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.19) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. + */ + +#include "synctex_parser.h" +#include "synctex_parser_utils.h" + +#ifndef __SYNCTEX_PARSER_PRIVATE__ +# define __SYNCTEX_PARSER_PRIVATE__ + +#ifdef __cplusplus +extern "C" { +#endif + /* Reminder that the argument must not be NULL */ + typedef synctex_node_p synctex_non_null_node_p; + + /* Each node of the tree, except the scanner itself belongs to a class. + * The class object is just a struct declaring the owning scanner + * This is a pointer to the scanner as root of the tree. + * The type is used to identify the kind of node. + * The class declares pointers to a creator and a destructor method. + * The log and display fields are used to log and display the node. + * display will also display the child, sibling and parent sibling. + * parent, child and sibling are used to navigate the tree, + * from TeX box hierarchy point of view. + * The friend field points to a method which allows to navigate from friend to friend. + * A friend is a node with very close tag and line numbers. + * Finally, the info field point to a method giving the private node info offset. + */ + + /** + * These are the masks for the synctex node types. + * int's are 32 bits at leats. + */ + enum { + synctex_shift_root, + synctex_shift_no_root, + synctex_shift_void, + synctex_shift_no_void, + synctex_shift_box, + synctex_shift_no_box, + synctex_shift_proxy, + synctex_shift_no_proxy, + synctex_shift_h, + synctex_shift_v + }; + enum { + synctex_mask_root = 1, + synctex_mask_no_root = synctex_mask_root<<1, + synctex_mask_void = synctex_mask_no_root<<1, + synctex_mask_no_void = synctex_mask_void<<1, + synctex_mask_box = synctex_mask_no_void<<1, + synctex_mask_no_box = synctex_mask_box<<1, + synctex_mask_proxy = synctex_mask_no_box<<1, + synctex_mask_no_proxy = synctex_mask_proxy<<1, + synctex_mask_h = synctex_mask_no_proxy<<1, + synctex_mask_v = synctex_mask_h<<1, + }; + enum { + synctex_mask_non_void_hbox = synctex_mask_no_void + | synctex_mask_box + | synctex_mask_h, + synctex_mask_non_void_vbox = synctex_mask_no_void + | synctex_mask_box + | synctex_mask_v + }; + typedef enum { + synctex_node_mask_sf = + synctex_mask_root + |synctex_mask_no_void + |synctex_mask_no_box + |synctex_mask_no_proxy, + synctex_node_mask_vbox = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_v, + synctex_node_mask_hbox = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_h, + synctex_node_mask_void_vbox = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_v, + synctex_node_mask_void_hbox = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_box + |synctex_mask_no_proxy + |synctex_mask_h, + synctex_node_mask_vbox_proxy = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_proxy + |synctex_mask_v, + synctex_node_mask_hbox_proxy = + synctex_mask_no_root + |synctex_mask_no_void + |synctex_mask_box + |synctex_mask_proxy + |synctex_mask_h, + synctex_node_mask_nvnn = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_no_box + |synctex_mask_no_proxy, + synctex_node_mask_input = + synctex_mask_root + |synctex_mask_void + |synctex_mask_no_box + |synctex_mask_no_proxy, + synctex_node_mask_proxy = + synctex_mask_no_root + |synctex_mask_void + |synctex_mask_no_box + |synctex_mask_proxy + } synctex_node_mask_t; + + enum { + /* input */ + synctex_tree_sibling_idx = 0, + synctex_tree_s_input_max = 1, + /* All */ + synctex_tree_s_parent_idx = 1, + synctex_tree_sp_child_idx = 2, + synctex_tree_spc_friend_idx = 3, + synctex_tree_spcf_last_idx = 4, + synctex_tree_spcfl_vbox_max = 5, + /* hbox supplement */ + synctex_tree_spcfl_next_hbox_idx = 5, + synctex_tree_spcfln_hbox_max = 6, + /* hbox proxy supplement */ + synctex_tree_spcfln_target_idx = 6, + synctex_tree_spcflnt_proxy_hbox_max = 7, + /* vbox proxy supplement */ + synctex_tree_spcfl_target_idx = 5, + synctex_tree_spcflt_proxy_vbox_max = 6, + /* spf supplement*/ + synctex_tree_sp_friend_idx = 2, + synctex_tree_spf_max = 3, + /* box boundary supplement */ + synctex_tree_spf_arg_sibling_idx = 3, + synctex_tree_spfa_max = 4, + /* proxy supplement */ + synctex_tree_spf_target_idx = 3, + synctex_tree_spft_proxy_max = 4, + /* last proxy supplement */ + synctex_tree_spfa_target_idx = 4, + synctex_tree_spfat_proxy_last_max = 5, + /* sheet supplement */ + synctex_tree_s_child_idx = 1, + synctex_tree_sc_next_hbox_idx = 2, + synctex_tree_scn_sheet_max = 3, + /* form supplement */ + synctex_tree_sc_target_idx = 2, + synctex_tree_sct_form_max = 3, + /* spct */ + synctex_tree_spc_target_idx = 3, + synctex_tree_spct_handle_max = 4, + }; + + enum { + /* input */ + synctex_data_input_tag_idx = 0, + synctex_data_input_line_idx = 1, + synctex_data_input_name_idx = 2, + synctex_data_input_tln_max = 3, + /* sheet */ + synctex_data_sheet_page_idx = 0, + synctex_data_p_sheet_max = 1, + /* form */ + synctex_data_form_tag_idx = 0, + synctex_data_t_form_max = 1, + /* tlchv */ + synctex_data_tag_idx = 0, + synctex_data_line_idx = 1, + synctex_data_column_idx = 2, + synctex_data_h_idx = 3, + synctex_data_v_idx = 4, + synctex_data_tlchv_max = 5, + /* tlchvw */ + synctex_data_width_idx = 5, + synctex_data_tlchvw_max = 6, + /* box */ + synctex_data_height_idx = 6, + synctex_data_depth_idx = 7, + synctex_data_box_max = 8, + /* hbox supplement */ + synctex_data_mean_line_idx = 8, + synctex_data_weight_idx = 9, + synctex_data_h_V_idx = 10, + synctex_data_v_V_idx = 11, + synctex_data_width_V_idx = 12, + synctex_data_height_V_idx = 13, + synctex_data_depth_V_idx = 14, + synctex_data_hbox_max = 15, + /* ref */ + synctex_data_ref_tag_idx = 0, + synctex_data_ref_h_idx = 1, + synctex_data_ref_v_idx = 2, + synctex_data_ref_thv_max = 3, + /* proxy */ + synctex_data_proxy_h_idx = 0, + synctex_data_proxy_v_idx = 1, + synctex_data_proxy_hv_max = 2, + }; + + /* each synctex node has a class */ + typedef struct synctex_class_t synctex_class_s; + typedef synctex_class_s * synctex_class_p; + + + /* synctex_node_p is a pointer to a node + * synctex_node_s is the target of the synctex_node_p pointer + * It is a pseudo object oriented program. + * class is a pointer to the class object the node belongs to. + * implementation is meant to contain the private data of the node + * basically, there are 2 kinds of information: navigation information and + * synctex information. Both will depend on the type of the node, + * thus different nodes will have different private data. + * There is no inheritancy overhead. + */ + typedef union { + synctex_node_p as_node; + int as_integer; + char * as_string; + void * as_pointer; + } synctex_data_u; + typedef synctex_data_u * synctex_data_p; + +# if defined(SYNCTEX_USE_CHARINDEX) + typedef unsigned int synctex_charindex_t; + synctex_charindex_t synctex_node_charindex(synctex_node_p node); + typedef synctex_charindex_t synctex_lineindex_t; + synctex_lineindex_t synctex_node_lineindex(synctex_node_p node); + synctex_node_p synctex_scanner_handle(synctex_scanner_p scanner); +# define SYNCTEX_DECLARE_CHARINDEX \ + synctex_charindex_t char_index;\ + synctex_lineindex_t line_index; +# define SYNCTEX_DECLARE_CHAR_OFFSET \ + synctex_charindex_t charindex_offset; +# else +# define SYNCTEX_DECLARE_CHARINDEX +# define SYNCTEX_DECLARE_CHAR_OFFSET +# endif + struct synctex_node_t { + SYNCTEX_DECLARE_CHARINDEX + synctex_class_p class; +#ifdef DEBUG + synctex_data_u data[22]; +#else + synctex_data_u data[1]; +#endif + }; + + typedef synctex_node_p * synctex_node_r; + + typedef struct { + int h; + int v; + } synctex_point_s; + + typedef synctex_point_s * synctex_point_p; + + typedef struct { + synctex_point_s min; /* top left */ + synctex_point_s max; /* bottom right */ + } synctex_box_s; + + typedef synctex_box_s * synctex_box_p; + /** + * These are the types of the synctex nodes. + * No need to use them but the compiler needs them here. + * There are 3 kinds of nodes. + * - primary nodes + * - proxies + * - handles + * Primary nodes are created at parse time + * of the synctex file. + * Proxies are used to support pdf forms. + * The ref primary nodes are replaced by a tree + * of proxy nodes which duplicate the tree of primary + * nodes available in the refered form. + * Roughly speaking, the primary nodes of the form + * know what to display, the proxy nodes know where. + * Handles are used in queries. They point to either + * primary nodes or proxies. + */ + typedef enum { + synctex_node_type_none = 0, + synctex_node_type_input, + synctex_node_type_sheet, + synctex_node_type_form, + synctex_node_type_ref, + synctex_node_type_vbox, + synctex_node_type_void_vbox, + synctex_node_type_hbox, + synctex_node_type_void_hbox, + synctex_node_type_kern, + synctex_node_type_glue, + synctex_node_type_rule, + synctex_node_type_math, + synctex_node_type_boundary, + synctex_node_type_box_bdry, + synctex_node_type_proxy, + synctex_node_type_proxy_last, + synctex_node_type_proxy_vbox, + synctex_node_type_proxy_hbox, + synctex_node_type_handle, + synctex_node_number_of_types + } synctex_node_type_t; + /* synctex_node_type gives the type of a given node, + * synctex_node_isa gives the same information as a human readable text. */ + synctex_node_type_t synctex_node_type(synctex_node_p node); + const char * synctex_node_isa(synctex_node_p node); + + synctex_node_type_t synctex_node_target_type(synctex_node_p node); + + synctex_node_type_t synctex_node_type(synctex_node_p node); + const char * synctex_node_isa(synctex_node_p node); + + void synctex_node_log(synctex_node_p node); + void synctex_node_display(synctex_node_p node); + + /* Given a node, access to the location in the synctex file where it is defined. + */ + + int synctex_node_form_tag(synctex_node_p node); + + int synctex_node_mean_line(synctex_node_p node); + int synctex_node_weight(synctex_node_p node); + int synctex_node_child_count(synctex_node_p node); + + int synctex_node_h(synctex_node_p node); + int synctex_node_v(synctex_node_p node); + int synctex_node_width(synctex_node_p node); + + int synctex_node_box_h(synctex_node_p node); + int synctex_node_box_v(synctex_node_p node); + int synctex_node_box_width(synctex_node_p node); + int synctex_node_box_height(synctex_node_p node); + int synctex_node_box_depth(synctex_node_p node); + + int synctex_node_hbox_h(synctex_node_p node); + int synctex_node_hbox_v(synctex_node_p node); + int synctex_node_hbox_width(synctex_node_p node); + int synctex_node_hbox_height(synctex_node_p node); + int synctex_node_hbox_depth(synctex_node_p node); + + synctex_scanner_p synctex_scanner_new(); + synctex_node_p synctex_node_new(synctex_scanner_p scanner,synctex_node_type_t type); + + /** + * Scanner display switcher getter. + * If the switcher is 0, synctex_node_display is disabled. + * If the switcher is <0, synctex_node_display has no limit. + * If the switcher is >0, only the first switcher (as number) nodes are displayed. + * - parameter: a scanner + * - returns: an integer + */ + int synctex_scanner_display_switcher(synctex_scanner_p scanR); + void synctex_scanner_set_display_switcher(synctex_scanner_p scanR, int switcher); + + /** + * Iterator is the structure used to traverse + * the answer to client queries. + * First answers are the best matches, according + * to criteria explained below. + * Next answers are not ordered. + * Objects are handles to nodes in the synctex node tree starting at scanner. + */ + typedef struct synctex_iterator_t synctex_iterator_s; + typedef synctex_iterator_s * synctex_iterator_p; + + /** + * Designated creator for a display query, id est, + * forward navigation from source to output. + * Returns NULL if the query has no answer. + * Code example: + * synctex_iterator_p iterator = NULL; + * if ((iterator = synctex_iterator_new_display(...)) { + * synctex_node_p node = NULL; + * while((node = synctex_iterator_next_result(iterator))) { + * do something with node... + * } + */ + synctex_iterator_p synctex_iterator_new_display(synctex_scanner_p scanner,const char * name,int line,int column, int page_hint); + /** + * Designated creator for an edit query, id est, + * backward navigation from output to source. + * Code example: + * synctex_iterator_p iterator = NULL; + * if ((iterator = synctex_iterator_new_edit(...)) { + * synctex_node_p node = NULL; + * while((node = synctex_iterator_next_result(iterator))) { + * do something with node... + * } + */ + synctex_iterator_p synctex_iterator_new_edit(synctex_scanner_p scanner,int page,float h,float v); + /** + * Free all the resources. + * - argument iterator: the object to free... + * You should free the iterator before the scanner + * owning the nodes it iterates with. + */ + void synctex_iterator_free(synctex_iterator_p iterator); + /** + * Wether the iterator actually points to an object. + * - argument iterator: the object to iterate on... + */ + synctex_bool_t synctex_iterator_has_next(synctex_iterator_p iterator); + /** + * Returns the pointed object and advance the cursor + * to the next object. Returns NULL and does nothing + * if the end has already been reached. + * - argument iterator: the object to iterate on... + */ + synctex_node_p synctex_iterator_next_result(synctex_iterator_p iterator); + /** + * Reset the cursor position to the first result. + * - argument iterator: the object to iterate on... + */ + int synctex_iterator_reset(synctex_iterator_p iterator); + /** + * The number of objects left for traversal. + * - argument iterator: the object to iterate on... + */ + int synctex_iterator_count(synctex_iterator_p iterator); + + /** + * The target of the node, either a handle or a proxy. + */ + synctex_node_p synctex_node_target(synctex_node_p node); + +#ifndef SYNCTEX_NO_UPDATER + /* The main synctex updater object. + * This object is used to append information to the synctex file. + * Its implementation is considered private. + * It is used by the synctex command line tool to take into account modifications + * that could occur while postprocessing files by dvipdf like filters. + */ + typedef struct synctex_updater_t synctex_updater_s; + typedef synctex_updater_s * synctex_updater_p; + + /* Designated initializer. + * Once you are done with your whole job, + * free the updater */ + synctex_updater_p synctex_updater_new_with_output_file(const char * output, const char * directory); + + /* Use the next functions to append records to the synctex file, + * no consistency tests made on the arguments */ + void synctex_updater_append_magnification(synctex_updater_p updater, char * magnification); + void synctex_updater_append_x_offset(synctex_updater_p updater, char * x_offset); + void synctex_updater_append_y_offset(synctex_updater_p updater, char * y_offset); + + /* You MUST free the updater, once everything is properly appended */ + void synctex_updater_free(synctex_updater_p updater); +#endif + +#if defined(SYNCTEX_DEBUG) +# include "assert.h" +# define SYNCTEX_ASSERT assert +#else +# define SYNCTEX_ASSERT(UNUSED) +#endif + +#if defined(SYNCTEX_TESTING) +#warning TESTING IS PROHIBITED +#if __clang__ +#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wformat-extra-args\"") + +#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS _Pragma("clang diagnostic pop") +#else +#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS +#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS +#endif + +# define SYNCTEX_TEST_BODY(counter, condition, desc, ...) \ + do { \ + __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \ + if (!(condition)) { \ + ++counter; \ + printf("**** Test failed: %s\nfile %s\nfunction %s\nline %i\n",#condition,__FILE__,__FUNCTION__,__LINE__); \ + printf((desc), ##__VA_ARGS__); \ + } \ + __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \ + } while(0) + +# define SYNCTEX_TEST_PARAMETER(counter, condition) SYNCTEX_TEST_BODY(counter, (condition), "Invalid parameter not satisfying: %s", #condition) + + int synctex_test_input(synctex_scanner_p scanner); + int synctex_test_proxy(synctex_scanner_p scanner); + int synctex_test_tree(synctex_scanner_p scanner); + int synctex_test_page(synctex_scanner_p scanner); + int synctex_test_handle(synctex_scanner_p scanner); + int synctex_test_display_query(synctex_scanner_p scanner); + int synctex_test_charindex(); + int synctex_test_sheet_1(); + int synctex_test_sheet_2(); + int synctex_test_sheet_3(); + int synctex_test_form(); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/synctex/synctex_parser_local.h b/core/synctex/synctex_parser_local.h index 6573b2638..89fd13696 100644 --- a/core/synctex/synctex_parser_local.h +++ b/core/synctex/synctex_parser_local.h @@ -1,45 +1,45 @@ /* Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr This file is part of the SyncTeX package. Latest Revision: Tue Jun 14 08:23:30 UTC 2011 -Version: 1.16 +Version: 1.18 See synctex_parser_readme.txt for more details License: -------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE Except as contained in this notice, the name of the copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the copyright holder. */ /* This local header file is for TEXLIVE, use your own header to fit your system */ # include /* for inline && HAVE_xxx */ /* No inlining for synctex tool in texlive. */ # define SYNCTEX_INLINE diff --git a/core/synctex/synctex_parser_readme.md b/core/synctex/synctex_parser_readme.md new file mode 100644 index 000000000..7dae3a97d --- /dev/null +++ b/core/synctex/synctex_parser_readme.md @@ -0,0 +1,246 @@ +# __SyncTeX__ + +This file is part of the __SyncTeX__ package. + +[//]: # (Version: 1.19) +[//]: # (Date: Fri Jul 14 16:20:41 UTC 2017) + +## Important + +* Changes in version `1.19` + + A long standing bug affecting only pdftex has been fixed by adding support to pdf forms. As a side effect, the synchronization algorithm gains in accuracy for all the engines, especially from pdf to source. Of course, there is a small cost in both memory footprint and computational time, but none is noticeable. + + Some technical changes in the API. + * more consistent type naming convention + * `_s` suffix for struct's + * `_p` suffix for pointers + * `_t` suffix for standard types + * `_f` suffix for functions... + + thus `synctex_node_t` has become `synctex_node_p`. + + * supplemental argument in `synctex_display_query`. Set `page_hint` to the currently displayed pdf page number (0 based). If this info is not available, set `page_hint` to 0, it corresponds to the old behaviour. + * rename `synctex_next_result` to `synctex_scanner_next_result` + * additional `synctex_parser_private.h` header where + more API are exposed. For an advanced usage only (eg parallel synchronizations). + * `SYNCTEX_NO_UPDATER` preprocessor macro to deactivate some code useless to frontends. + +## What is it ? + +The *Synchronization TeXnology* named __SyncTeX__ is a new feature +of recent __TeX__ engines designed by Jérôme Laurens. +It allows to synchronize between input and output, which means to +navigate from the source document to the typeset material and vice versa. +Browse the source code for more information. + +## Contents +This package is mainly for developers, it contains the following files: + +* `synctex_parser_readme.md` (this file) +* `synctex_parser_version.txt` +* `synctex_parser_utils.c` +* `synctex_parser_utils.h` +* `synctex_parser_local.h` +* `synctex_parser.h` +* `synctex_parser_advanced.h` +* `synctex_parser.c` + +The file you are reading contains more informations about the __SyncTeX__ parser history. + +## Source + +All stable sources are available from [TeXLive repository](http://www.tug.org/svn/texlive/trunk/Build/source/texk/web2c/synctexdir/). + +Latest development sources are available from GitHub. + +Please notice that due to the complexity of the TeXLive TeX distribution, there might be some delay before synchronization occurs between various repositories. + +## Usage + +In order to support __SyncTeX__ in a viewer, it is sufficient to include +in the source the files `synctex_parser.c` and `synctex_parser_utils.c`. +The synctex parser usage is described in `synctex_parser.h` header file. + +More advanced usage is found in `synctex_parser_advanced.h` + +The other files are used by tex engines or by the synctex command line utility: + +``` +ChangeLog +README.txt +am +man1 +man5 +synctex-common.h +synctex-convert.sh +synctex-e-mem.ch0 +synctex-e-mem.ch1 +synctex-e-rec.ch0 +synctex-e-rec.ch1 +synctex-etex.h +synctex-mem.ch0 +synctex-mem.ch1 +synctex-mem.ch2 +synctex-pdf-rec.ch2 +synctex-pdftex.h +synctex-rec.ch0 +synctex-rec.ch1 +synctex-rec.ch2 +synctex-tex.h +synctex-xe-mem.ch2 +synctex-xe-rec.ch2 +synctex-xe-rec.ch3 +synctex-xetex.h +synctex.c +synctex.defines +synctex.h +synctex_main.c +tests +... +``` + +## Version: + +This is version 1, which refers to the synctex output file format. + +The files are identified by a build number. +In order to help developers to automatically manage the version and build numbers +and download the parser only when necessary, the `synctex_parser_version.txt` +is an ASCII text file just containing the current version and build numbers eg `1.19`. + +## History: + +* 1.1: Thu Jul 17 09:28:13 UTC 2008 + - First official version available in TeXLive 2008 DVD. + Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below. +* 1.2: Tue Sep 2 10:28:32 UTC 2008 + - Correction for ConTeXt support in the edit query. + The previous method was assuming that TeX boxes do not overlap, + which is reasonable for LaTeX but not for ConTeXt. + This assumption is no longer considered. +* 1.3: Fri Sep 5 09:39:57 UTC 2008 + - Local variable "read" renamed to "already_read" to avoid conflicts. + - "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance + - _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman) + - Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization +* 1.4: Fri Sep 12 08:12:34 UTC 2008 + - For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747). + As a consequence, a crash was observed. + - Some typos are fixed. +* 1.6: Mon Nov 3 20:20:02 UTC 2008 + - The bug that prevented synchronization with compressed files on windows has been fixed. + - New interface to allow system specific customization. + - Note that some APIs have changed. +* 1.8: Mer 8 jul 2009 11:32:38 UTC +Note that version 1.7 was delivered privately. + - bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation + - bug fix: the synctex command line tool was broken when updating a .synctex file + - enhancement: better accuracy of the synchronization process + - enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory. + The new -d option of the synctex command line tool manages this situation. + This is handy when using something like tex -output-directory=DIR ... +* 1.9: Wed Nov 4 11:52:35 UTC 2009 + - Various typo fixed + - OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing + - New conditional created because OutputDebugStringA is only available since Windows 2K professional +* 1.10: Sun Jan 10 10:12:32 UTC 2010 + - Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment. + Concerns the synctex tool. +* 1.11: Sun Jan 17 09:12:31 UTC 2010 + - Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'. + Only 3rd party tools are concerned. +* 1.12: Mon Jul 19 21:52:10 UTC 2010 + - Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return, +causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince. +* 1.13: Fri Mar 11 07:39:12 UTC 2011 + - Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388). + - Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior). + Only 3rd party tools are concerned. +* 1.14: Fri Apr 15 19:10:57 UTC 2011 + - taking output_directory into account + - Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file. + - Merging with LuaTeX's version of synctex.c +* 1.15: Fri Jun 10 14:10:17 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. + - Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned + - Support for LuaTeX convention of './' file prefixing +* 1.16: Tue Jun 14 08:23:30 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. + - Better forward search (thanks Jose Alliste) + - Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows +* 1.17: Fri Oct 14 08:15:16 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. + - synctex_parser.c: cosmetic changes to enhance code readability + - Better forward synchronization. + The problem occurs for example with LaTeX \item command. + The fact is that this command creates nodes at parse time but these nodes are used only + after the text material of the \item is displayed on the page. The consequence is that sometimes, + forward synchronization spots an irrelevant point from the point of view of the editing process. + This was due to some very basic filtering policy, where a somehow arbitrary choice was made when + many different possibilities where offered for synchronisation. + Now, forward synchronization prefers nodes inside an hbox with as many acceptable spots as possible. + This is achieved with the notion of mean line and node weight. + - Adding support for the new file naming convention with './' + - function `synctex_ignore_leading_dot_slash_in_path replaces` `synctex_ignore_leading_dot_slash` + - function `_synctex_is_equivalent_file_name` is more permissive + Previously, the function synctex_scanner_get_tag would give an answer only when + the given file name was EXACTLY one of the file names listed in the synctex file. + The we added some changes accepting for example 'foo.tex' instead of './foo.tex'. + Now we have an even looser policy for dealing with file names. + If the given file name does not match exactly one the file names of the synctex file, + then we try to match the base names. If there is only one match of the base names, + then it is taken as a match for the whole names. + The base name is defined as following: + + `./foo => foo + /my///.////foo => foo + /foo => /foo + /my//.foo => /my//.foo` + +* 1.17: Tue Mar 13 10:10:03 UTC 2012 + - minor changes, no version changes + - syntax man pages are fixed as suggested by M. Shimata + see mail to tex-live@tug.org titled "syntax.5 has many warnings from groff" and "syntax.1 use invalid macro for mdoc" + - 1.17: Tue Jan 14 09:55:00 UTC 2014, fixed a segfault, patch from Sebastian Ramacher + - 1.17: Mon Aug 04, fixed a memory leak +* 1.18: Thu Jun 25 11:36:05 UTC 2015 + - nested sheets now fully supported (does it make sense in TeX) + - cosmetic changes: uniform indentation + - suppression of warnings, mainly long/int ones. In short, zlib likes ints when size_t likes longs. + - CLI synctex tool can build out of TeXLive (modulo appropriate options passed to the compiler) +* 1.19: Mon Jul 3 20:03:56 UTC 2017 + - the nested sheets patch was not a good solution. + It has been moved from the parser to the engine. + See the `synctex.c` source file for detailed explanations. + - there is a new synctex format specification. + We can see that a `.synctex` file can contain many times + the same vertical position because many objects belong + to the same line. When the options read `-synctex=±2` or more, + a very basic compression algorithm is used: + if synctex is about to write the same number then it writes + an `=` sign instead. This saves approximately 10% of the + synctex output file, either compressed or not. + The new synctex parser has been updated accordingly. + Actual tex frontend won't see any difference with the + TeX engines that include this new feature. + Frontends with the new parser won't see any difference + with the older TeX engines. + Frontends with the new parser will only see a difference + with new TeX engines if `-synctex=±2` or more is used. + +## Acknowledgments: + +The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, +and significant help from __XeTeX__ developer Jonathan Kew. + +## Nota Bene: + +If you include or use a significant part of the __SyncTeX__ package into a software, +I would appreciate to be listed as contributor and see __SyncTeX__" highlighted. + +Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + diff --git a/core/synctex/synctex_parser_readme.txt b/core/synctex/synctex_parser_readme.txt deleted file mode 100644 index ebc06bb7b..000000000 --- a/core/synctex/synctex_parser_readme.txt +++ /dev/null @@ -1,141 +0,0 @@ -This file is part of the SyncTeX package. - -The Synchronization TeXnology named SyncTeX is a new feature -of recent TeX engines designed by Jerome Laurens. -It allows to synchronize between input and output, which means to -navigate from the source document to the typeset material and vice versa. -More informations on http://itexmac2.sourceforge.net/SyncTeX.html - -This package is mainly for developers, it mainly contains the following files: - -synctex_parser_readme.txt -synctex_parser_version.txt -synctex_parser_utils.c -synctex_parser_utils.h -synctex_parser_local.h -synctex_parser.h -synctex_parser.c - -The file you are reading contains more informations about the SyncTeX parser history. - -In order to support SyncTeX in a viewer, it is sufficient to include -in the source the files synctex_parser.h and synctex_parser.c. -The synctex parser usage is described in synctex_parser.h header file. - -The other files are used by tex engines or by the synctex command line utility: - -ChangeLog -README.txt -am -man1 -man5 -synctex-common.h -synctex-convert.sh -synctex-e-mem.ch0 -synctex-e-mem.ch1 -synctex-e-rec.ch0 -synctex-e-rec.ch1 -synctex-etex.h -synctex-mem.ch0 -synctex-mem.ch1 -synctex-mem.ch2 -synctex-pdf-rec.ch2 -synctex-pdftex.h -synctex-rec.ch0 -synctex-rec.ch1 -synctex-rec.ch2 -synctex-tex.h -synctex-xe-mem.ch2 -synctex-xe-rec.ch2 -synctex-xe-rec.ch3 -synctex-xetex.h -synctex.c -synctex.defines -synctex.h -synctex_main.c -tests - - -Version: --------- -This is version 1, which refers to the synctex output file format. -The files are identified by a build number. -In order to help developers to automatically manage the version and build numbers -and download the parser only when necessary, the synctex_parser.version -is an ASCII text file just containing the current version and build numbers. - -History: --------- -1.1: Thu Jul 17 09:28:13 UTC 2008 -- First official version available in TeXLive 2008 DVD. - Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below. -1.2: Tue Sep 2 10:28:32 UTC 2008 -- Correction for ConTeXt support in the edit query. - The previous method was assuming that TeX boxes do not overlap, - which is reasonable for LaTeX but not for ConTeXt. - This assumption is no longer considered. -1.3: Fri Sep 5 09:39:57 UTC 2008 -- Local variable "read" renamed to "already_read" to avoid conflicts. -- "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance -- _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman) -- Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization -1.4: Fri Sep 12 08:12:34 UTC 2008 -- For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747). - As a consequence, a crash was observed. -- Some typos are fixed. -1.6: Mon Nov 3 20:20:02 UTC 2008 -- The bug that prevented synchronization with compressed files on windows has been fixed. -- New interface to allow system specific customization. -- Note that some APIs have changed. -1.8: Mer 8 jul 2009 11:32:38 UTC -Note that version 1.7 was delivered privately. -- bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation -- bug fix: the synctex command line tool was broken when updating a .synctex file -- enhancement: better accuracy of the synchronization process -- enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory. - The new -d option of the synctex command line tool manages this situation. - This is handy when using something like tex -output-directory=DIR ... -1.9: Wed Nov 4 11:52:35 UTC 2009 -- Various typo fixed -- OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing -- New conditional created because OutputDebugStringA is only available since Windows 2K professional -1.10: Sun Jan 10 10:12:32 UTC 2010 -- Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment. - Concerns the synctex tool. -1.11: Sun Jan 17 09:12:31 UTC 2010 -- Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'. - Only 3rd party tools are concerned. -1.12: Mon Jul 19 21:52:10 UTC 2010 -- Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return, -causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince. -1.13: Fri Mar 11 07:39:12 UTC 2011 -- Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388). -- Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior). - Only 3rd party tools are concerned. -1.14: Fri Apr 15 19:10:57 UTC 2011 -- taking output_directory into account -- Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file. -- Merging with LuaTeX's version of synctex.c -1.15: Fri Jun 10 14:10:17 UTC 2011 -This concerns the synctex command line tool and 3rd party developers. -TeX and friends are not concerned by these changes. -- Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned -- Support for LuaTeX convention of './' file prefixing -1.16: Tue Jun 14 08:23:30 UTC 2011 -This concerns the synctex command line tool and 3rd party developers. -TeX and friends are not concerned by these changes. -- Better forward search (thanks Jose Alliste) -- Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows - -Acknowledgments: ----------------- -The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, -and significant help from XeTeX developer Jonathan Kew - -Nota Bene: ----------- -If you include or use a significant part of the synctex package into a software, -I would appreciate to be listed as contributor and see "SyncTeX" highlighted. - -Copyright (c) 2008-2011 jerome DOT laurens AT u-bourgogne DOT fr - diff --git a/core/synctex/synctex_parser_utils.c b/core/synctex/synctex_parser_utils.c index 03da40970..46f35bc8b 100644 --- a/core/synctex/synctex_parser_utils.c +++ b/core/synctex/synctex_parser_utils.c @@ -1,506 +1,590 @@ /* -Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr - -This file is part of the SyncTeX package. - -Latest Revision: Tue Jun 14 08:23:30 UTC 2011 - -Version: 1.16 - -See synctex_parser_readme.txt for more details - -License: --------- -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE - -Except as contained in this notice, the name of the copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in this Software without prior written -authorization from the copyright holder. + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.19) + + See `synctex_parser_readme.md` for more details + + ## License + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE + + Except as contained in this notice, the name of the copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization from the copyright holder. */ /* In this file, we find all the functions that may depend on the operating system. */ #include #include #include #include #include #include #include #include #include #if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) #define SYNCTEX_WINDOWS 1 #endif -#ifdef _WIN32_WINNT_WINXP +#if defined(__OS2__) +#define SYNCTEX_OS2 1 +#endif + +#if defined(_WIN32) #define SYNCTEX_RECENT_WINDOWS 1 #endif #ifdef SYNCTEX_WINDOWS #include +#include /* Use shlwapi.lib */ #endif void *_synctex_malloc(size_t size) { - void * ptr = malloc(size); - if(ptr) { -/* There used to be a switch to use bzero because it is more secure. JL */ - memset(ptr,0, size); - } - return (void *)ptr; + void * ptr = malloc(size); + if(ptr) { + memset(ptr,0, size);/* ensures null termination of strings */ + } + return (void *)ptr; } -int _synctex_error(const char * reason,...) { - va_list arg; +void _synctex_free(void * ptr) { + if (ptr) { + free(ptr); + } +} + +#if !defined(_WIN32) +# include +#endif + +int _synctex_log(int level, const char * prompt, const char * reason, ...) SYNCTEX_PRINTF_FORMAT(3, 4); +int _synctex_log(int level, const char * prompt, const char * reason, ...) { + va_list arg; int result; - va_start (arg, reason); # ifdef SYNCTEX_RECENT_WINDOWS {/* This code is contributed by William Blum. As it does not work on some older computers, the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx Minimum supported client Windows 2000 Professional Minimum supported server Windows 2000 Server People running Windows 2K standard edition will not have OutputDebugStringA. JL.*/ char *buff; size_t len; - OutputDebugStringA("SyncTeX ERROR: "); + OutputDebugStringA(prompt); # ifdef _MSC_VER len = _vscprintf(reason, arg) + 1; buff = (char*)malloc( len * sizeof(char) ); -#else /* MinGW */ +# else /* MinGW */ size_t buffersize = 1024; size_t max_buffersize = 1024 * buffersize; int result; buff = (char*)malloc(buffersize * sizeof(char)); result = _vsnprintf(buff, buffersize - 1, reason, arg); while(-1 == result && buffersize <= max_buffersize) { buffersize = buffersize * 2; buff = (char*)realloc(buff, buffersize * sizeof(char)); result = _vsnprintf(buff, buffersize - 1, reason, arg); } if(-1 == result) { // could not make the buffer big enough or simply could not write to it free(buff); return -1; } -#endif - result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: "); +# endif + result = vsprintf(buff, reason, arg) +strlen(prompt); OutputDebugStringA(buff); OutputDebugStringA("\n"); free(buff); } +# elif defined SYNCTEX_USE_SYSLOG + char * buffer1 = NULL; + char * buffer2 = NULL; + openlog ("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0); + if (vasprintf(&buffer1,reason,arg)>=0 + && asprintf(&buffer2,"%s%s",prompt, buffer1)>=0) { + syslog (level, "%s", buffer2); + result = (int)strlen(buffer2); + } else { + syslog (level, "%s",prompt); + vsyslog(level,reason,arg); + result = (int)strlen(prompt); + } + free(buffer1); + free(buffer2); + closelog(); # else - result = fprintf(stderr,"SyncTeX ERROR: "); - result += vfprintf(stderr, reason, arg); - result += fprintf(stderr,"\n"); + FILE * where = level == LOG_ERR? stderr: stdout; + result = fputs(prompt,where); + result += vfprintf(where, reason, arg); + result += fprintf(where,"\n"); # endif - va_end (arg); return result; } +int _synctex_error(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +#if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_ERR is not used */ + result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); +#else + result = _synctex_log(LOG_ERR, "! SyncTeX Error : ", reason, arg); +#endif + va_end (arg); + return result; +} + +int _synctex_debug(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +#if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_DEBUG is not used */ + result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); +#else + result = _synctex_log(LOG_DEBUG, "! SyncTeX Error : ", reason, arg); +#endif + va_end (arg); + return result; +} + /* strip the last extension of the given string, this string is modified! */ void _synctex_strip_last_path_extension(char * string) { if(NULL != string){ char * last_component = NULL; char * last_extension = NULL; +# if defined(SYNCTEX_WINDOWS) + last_component = PathFindFileName(string); + last_extension = PathFindExtension(string); + if(last_extension == NULL)return; + if(last_component == NULL)last_component = string; + if(last_extension>last_component){/* filter out paths like "my/dir/.hidden" */ + last_extension[0] = '\0'; + } +# else char * next = NULL; /* first we find the last path component */ if(NULL == (last_component = strstr(string,"/"))){ last_component = string; } else { ++last_component; while((next = strstr(last_component,"/"))){ last_component = next+1; } } -# ifdef SYNCTEX_WINDOWS - /* On Windows, the '\' is also a path separator. */ +# if defined(SYNCTEX_OS2) + /* On OS2, the '\' is also a path separator. */ while((next = strstr(last_component,"\\"))){ last_component = next+1; } -# endif +# endif /* SYNCTEX_OS2 */ /* then we find the last path extension */ if((last_extension = strstr(last_component,"."))){ ++last_extension; while((next = strstr(last_extension,"."))){ last_extension = next+1; } --last_extension;/* back to the "." */ if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ last_extension[0] = '\0'; } } +# endif /* SYNCTEX_WINDOWS */ } } -const char * synctex_ignore_leading_dot_slash(const char * name) +synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name_ref) { - while(SYNCTEX_IS_DOT(*name) && SYNCTEX_IS_PATH_SEPARATOR(name[1])) { - name += 2; - while (SYNCTEX_IS_PATH_SEPARATOR(*name)) { - ++name; - } + if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) { + do { + (*name_ref) += 2; + while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[0])) { + ++(*name_ref); + } + } while(SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])); + return synctex_YES; } - return name; + return synctex_NO; +} + +/* The base name is necessary to deal with the 2011 file naming convention... + * path is a '\0' terminated string + * The return value is the trailing part of the argument, + * just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/ +const char * _synctex_base_name(const char *path) { + const char * ptr = path; + do { + if (synctex_ignore_leading_dot_slash_in_path(&ptr)) { + return ptr; + } + do { + if (!*(++ptr)) { + return path; + } + } while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr)); + } while (*(++ptr)); + return path; } /* Compare two file names, windows is sometimes case insensitive... */ synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { /* Remove the leading regex '(\./+)*' in both rhs and lhs */ - lhs = synctex_ignore_leading_dot_slash(lhs); - rhs = synctex_ignore_leading_dot_slash(rhs); -# ifdef SYNCTEX_WINDOWS - /* On Windows, filename should be compared case insensitive. - * The characters '/' and '\' are both valid path separators. - * There will be a very serious problem concerning UTF8 because - * not all the characters must be toupper... - * I would like to have URL's instead of filenames. */ + synctex_ignore_leading_dot_slash_in_path(&lhs); + synctex_ignore_leading_dot_slash_in_path(&rhs); next_character: - if(SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ - if(!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ + if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ + if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ return synctex_NO; } - } else if(SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ + ++lhs; + ++rhs; + synctex_ignore_leading_dot_slash_in_path(&lhs); + synctex_ignore_leading_dot_slash_in_path(&rhs); + goto next_character; + } else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ return synctex_NO; - } else if(toupper(*lhs) != toupper(*rhs)){/* uppercase do not match */ + } else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs,*rhs)){/* uppercase do not match */ return synctex_NO; } else if (!*lhs) {/* lhs is at the end of the string */ return *rhs ? synctex_NO : synctex_YES; } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ return synctex_NO; } ++lhs; ++rhs; goto next_character; -# else - if (lhs[0] == '/' && rhs[0] == '/') { /* for absolute paths compare the real paths */ - char *lhsreal = realpath(lhs, 0); - char *rhsreal = realpath(rhs, 0); - synctex_bool_t result = lhsreal && rhsreal && 0 == strcmp(lhsreal,rhsreal)?synctex_YES:synctex_NO; - free (lhsreal); - free (rhsreal); - return result; - } else { - return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; - } -# endif } synctex_bool_t _synctex_path_is_absolute(const char * name) { if(!strlen(name)) { return synctex_NO; } -# ifdef SYNCTEX_WINDOWS +# if defined(SYNCTEX_WINDOWS) || defined(SYNCTEX_OS2) if(strlen(name)>2) { return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; } return synctex_NO; # else return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; # endif } /* We do not take care of UTF-8 */ const char * _synctex_last_path_component(const char * name) { const char * c = name+strlen(name); if(c>name) { if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { do { --c; if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { return c+1; } } while(c>name); } return c;/* the last path component is the void string*/ } return c; } int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { - const char * lpc; if(src && dest_ref) { + const char * lpc; # define dest (*dest_ref) dest = NULL; /* Default behavior: no change and sucess. */ lpc = _synctex_last_path_component(src); if(strlen(lpc)) { if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { /* We are in the situation where adding the quotes is allowed. */ /* Time to add the quotes. */ /* Consistency test: we must have dest+size>dest+strlen(dest)+2 * or equivalently: strlen(dest)+20) { char * result = NULL; ++size; /* Create the memory storage */ if(NULL!=(result = (char *)malloc(size))) { char * dest = result; va_start (arg, first); temp = first; do { if((size = strlen(temp))>0) { /* There is something to merge */ if(dest != strncpy(dest,temp,size)) { _synctex_error("! _synctex_merge_strings: Copy problem"); free(result); result = NULL; return NULL; } dest += size; } } while( (temp = va_arg(arg, const char *)) != NULL); va_end(arg); dest[0]='\0';/* Terminate the merged string */ return result; } _synctex_error("! _synctex_merge_strings: Memory problem"); return NULL; } return NULL; } /* The purpose of _synctex_get_name is to find the name of the synctex file. * There is a list of possible filenames from which we return the most recent one and try to remove all the others. * With two runs of pdftex or xetex we are sure the synctex file is really the most appropriate. */ int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref) { if(output && synctex_name_ref && io_mode_ref) { /* If output is already absolute, we just have to manage the quotes and the compress mode */ size_t size = 0; char * synctex_name = NULL; synctex_io_mode_t io_mode = *io_mode_ref; const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ /* Do we have a real base name ? */ if(strlen(base_name)>0) { /* Yes, we do. */ const char * temp = NULL; char * core_name = NULL; /* base name of output without path extension. */ char * dir_name = NULL; /* dir name of output */ char * quoted_core_name = NULL; char * basic_name = NULL; char * gz_name = NULL; char * quoted_name = NULL; char * quoted_gz_name = NULL; char * build_name = NULL; char * build_gz_name = NULL; char * build_quoted_name = NULL; char * build_quoted_gz_name = NULL; struct stat buf; time_t the_time = 0; /* Create core_name: let temp point to the dot before the path extension of base_name; * We start form the \0 terminating character and scan the string upward until we find a dot. * The leading dot is not accepted. */ if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) { /* There is a dot and it is not at the leading position */ if(NULL == (core_name = (char *)malloc(size+1))) { _synctex_error("! _synctex_get_name: Memory problem 1"); return -1; } if(core_name != strncpy(core_name,base_name,size)) { _synctex_error("! _synctex_get_name: Copy problem 1"); free(core_name); dir_name = NULL; return -2; } core_name[size] = '\0'; } else { /* There is no path extension, * Just make a copy of base_name */ core_name = _synctex_merge_strings(base_name); } /* core_name is properly set up, owned by "self". */ /* creating dir_name. */ size = strlen(output)-strlen(base_name); if(size>0) { /* output contains more than one path component */ if(NULL == (dir_name = (char *)malloc(size+1))) { _synctex_error("! _synctex_get_name: Memory problem"); free(core_name); - dir_name = NULL; return -1; } if(dir_name != strncpy(dir_name,output,size)) { _synctex_error("! _synctex_get_name: Copy problem"); free(dir_name); dir_name = NULL; free(core_name); dir_name = NULL; return -2; } dir_name[size] = '\0'; } /* dir_name is properly set up. It ends with a path separator, if non void. */ /* creating quoted_core_name. */ if(strchr(core_name,' ')) { quoted_core_name = _synctex_merge_strings("\"",core_name,"\""); } /* quoted_core_name is properly set up. */ if(dir_name &&strlen(dir_name)>0) { basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL); if(quoted_core_name && strlen(quoted_core_name)>0) { quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL); } } else { basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL); if(quoted_core_name && strlen(quoted_core_name)>0) { quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL); } } if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { temp = build_directory + size - 1; if(_synctex_path_is_absolute(temp)) { build_name = _synctex_merge_strings(build_directory,basic_name,NULL); if(quoted_core_name && strlen(quoted_core_name)>0) { build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL); } } else { build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL); if(quoted_core_name && strlen(quoted_core_name)>0) { build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL); } } } if(basic_name) { gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL); } if(quoted_name) { quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL); } if(build_name) { build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL); } if(build_quoted_name) { build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL); } /* All the others names are properly set up... */ /* retain the most recently modified file */ # define TEST(FILENAME,COMPRESS_MODE) \ if(FILENAME) {\ if (stat(FILENAME, &buf)) { \ free(FILENAME);\ FILENAME = NULL;\ } else if (buf.st_mtime>the_time) { \ the_time=buf.st_mtime; \ synctex_name = FILENAME; \ if (COMPRESS_MODE) { \ io_mode |= synctex_io_gz_mask; \ } else { \ io_mode &= ~synctex_io_gz_mask; \ } \ } \ } TEST(basic_name,synctex_DONT_COMPRESS); TEST(gz_name,synctex_COMPRESS); TEST(quoted_name,synctex_DONT_COMPRESS); TEST(quoted_gz_name,synctex_COMPRESS); TEST(build_name,synctex_DONT_COMPRESS); TEST(build_gz_name,synctex_COMPRESS); TEST(build_quoted_name,synctex_DONT_COMPRESS); TEST(build_quoted_gz_name,synctex_COMPRESS); # undef TEST /* Free all the intermediate filenames, except the one that will be used as returned value. */ # define CLEAN_AND_REMOVE(FILENAME) \ if(FILENAME && (FILENAME!=synctex_name)) {\ remove(FILENAME);\ printf("synctex tool info: %s removed\n",FILENAME);\ free(FILENAME);\ FILENAME = NULL;\ } CLEAN_AND_REMOVE(basic_name); CLEAN_AND_REMOVE(gz_name); CLEAN_AND_REMOVE(quoted_name); CLEAN_AND_REMOVE(quoted_gz_name); CLEAN_AND_REMOVE(build_name); CLEAN_AND_REMOVE(build_gz_name); CLEAN_AND_REMOVE(build_quoted_name); CLEAN_AND_REMOVE(build_quoted_gz_name); # undef CLEAN_AND_REMOVE /* set up the returned values */ * synctex_name_ref = synctex_name; + /* synctex_name won't always end in .gz, even when compressed. */ + FILE * F = fopen(synctex_name, "r"); + if (F != NULL) { + if (!feof(F) + && 31 == fgetc(F) + && !feof(F) + && 139 == fgetc(F)) { + io_mode = synctex_compress_mode_gz; + } + fclose(F); + } * io_mode_ref = io_mode; return 0; } return -1;/* bad argument */ } return -2; } const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) { static const char * synctex_io_modes[4] = {"r","rb","a","ab"}; unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);/* bug pointed out by Jose Alliste */ return synctex_io_modes[index]; } diff --git a/core/synctex/synctex_parser_utils.h b/core/synctex/synctex_parser_utils.h index 92184b8dd..be53cb4d2 100644 --- a/core/synctex/synctex_parser_utils.h +++ b/core/synctex/synctex_parser_utils.h @@ -1,147 +1,167 @@ /* -Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr - -This file is part of the SyncTeX package. - -Latest Revision: Tue Jun 14 08:23:30 UTC 2011 - -Version: 1.16 - -See synctex_parser_readme.txt for more details + Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr + + This file is part of the __SyncTeX__ package. + + [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) + [//]: # (Version: 1.19) + + See `synctex_parser_readme.md` for more details + + ## License -License: --------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE Except as contained in this notice, the name of the copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the copyright holder. */ +#ifndef SYNCTEX_PARSER_UTILS_H +#define SYNCTEX_PARSER_UTILS_H + /* The utilities declared here are subject to conditional implementation. * All the operating system special stuff goes here. * The problem mainly comes from file name management: path separator, encoding... */ -# define synctex_bool_t int -# define synctex_YES -1 +typedef int synctex_bool_t; +# define synctex_YES (0==0) +# define synctex_NO (0==1) + # define synctex_ADD_QUOTES -1 # define synctex_COMPRESS -1 -# define synctex_NO 0 # define synctex_DONT_ADD_QUOTES 0 # define synctex_DONT_COMPRESS 0 #ifndef __SYNCTEX_PARSER_UTILS__ # define __SYNCTEX_PARSER_UTILS__ #include #ifdef __cplusplus extern "C" { #endif -# ifdef _WIN32 +# if defined(_WIN32) || defined(__OS2__) +# define SYNCTEX_CASE_SENSITIVE_PATH 0 # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) # else +# define SYNCTEX_CASE_SENSITIVE_PATH 1 # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) # endif -# ifdef _WIN32 +# if defined(_WIN32) || defined(__OS2__) # define SYNCTEX_IS_DOT(c) ('.' == c) # else # define SYNCTEX_IS_DOT(c) ('.' == c) # endif + +# if SYNCTEX_CASE_SENSITIVE_PATH +# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (left != right) +# else +# define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right)) +# endif # ifdef __GNUC__ # define SYNCTEX_PRINTF_FORMAT(si, ftc) __attribute__ ((format (printf, si, ftc))) # else # define SYNCTEX_PRINTF_FORMAT(si, ftc) # endif - + /* This custom malloc functions initializes to 0 the newly allocated memory. * There is no bzero function on windows. */ void *_synctex_malloc(size_t size); +/* To balance _synctex_malloc. + * ptr might be NULL. */ +void _synctex_free(void * ptr); + /* This is used to log some informational message to the standard error stream. * On Windows, the stderr stream is not exposed and another method is used. * The return value is the number of characters printed. */ -int _synctex_error(const char * reason,...) SYNCTEX_PRINTF_FORMAT(1, 2); + int _synctex_error(const char * reason,...) SYNCTEX_PRINTF_FORMAT(1, 2); + int _synctex_debug(const char * reason,...) SYNCTEX_PRINTF_FORMAT(1, 2); /* strip the last extension of the given string, this string is modified! * This function depends on the OS because the path separator may differ. * This should be discussed more precisely. */ void _synctex_strip_last_path_extension(char * string); /* Compare two file names, windows is sometimes case insensitive... * The given strings may differ stricto sensu, but represent the same file name. * It might not be the real way of doing things. * The return value is an undefined non 0 value when the two file names are equivalent. * It is 0 otherwise. */ synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); /* Description forthcoming.*/ synctex_bool_t _synctex_path_is_absolute(const char * name); /* Description forthcoming...*/ const char * _synctex_last_path_component(const char * name); +/* Description forthcoming...*/ +const char * _synctex_base_name(const char *path); + /* If the core of the last path component of src is not already enclosed with double quotes ('"') * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. * In all other cases, no destination buffer is created and the src is not copied. * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces * were not managed in a standard way. * On success, the caller owns the buffer pointed to by dest_ref (is any) and * is responsible of freeing the memory when done. * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); /* These are the possible extensions of the synctex file */ extern const char * synctex_suffix; extern const char * synctex_suffix_gz; typedef unsigned int synctex_io_mode_t; typedef enum { synctex_io_append_mask = 1, synctex_io_gz_mask = synctex_io_append_mask<<1 } synctex_io_mode_masks_t; typedef enum { synctex_compress_mode_none = 0, synctex_compress_mode_gz = 1 } synctex_compress_mode_t; int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref); /* returns the correct mode required by fopen and gzopen from the given io_mode */ const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode); -const char * synctex_ignore_leading_dot_slash(const char * name); +synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name); #ifdef __cplusplus } #endif #endif +#endif /* SYNCTEX_PARSER_UTILS_H */ diff --git a/core/synctex/synctex_parser_version.txt b/core/synctex/synctex_parser_version.txt index 03ff89716..bc4493477 100644 --- a/core/synctex/synctex_parser_version.txt +++ b/core/synctex/synctex_parser_version.txt @@ -1 +1 @@ -1.16 \ No newline at end of file +1.19 diff --git a/generators/markdown/libokularGenerator_md.json b/generators/markdown/libokularGenerator_md.json index 106eafb27..53abd6595 100644 --- a/generators/markdown/libokularGenerator_md.json +++ b/generators/markdown/libokularGenerator_md.json @@ -1,53 +1,54 @@ { "KPlugin": { "Authors": [ { "Email": "wolff@julianwolff.de", "Name": "Julian Wolff", "Name[sr@ijekavian]": "Јулијан Волф", "Name[sr@ijekavianlatin]": "Julijan Volf", "Name[sr@latin]": "Julijan Volf", "Name[sr]": "Јулијан Волф", "Name[x-test]": "xxJulian Wolffxx" } ], "Copyright": "© 2017 Julian Wolff", "Copyright[sr@ijekavian]": "© 2017, Јулијан Волф", "Copyright[sr@ijekavianlatin]": "© 2017, Julijan Volf", "Copyright[sr@latin]": "© 2017, Julijan Volf", "Copyright[sr]": "© 2017, Јулијан Волф", "Copyright[uk]": "© Julian Wolff, 2017", "Copyright[x-test]": "xx© 2017 Julian Wolffxx", "Id": "okular_markdown", "License": "GPL", "MimeTypes": [ "text/markdown", "text/x-markdown" ], "Name": "Markdown Backend", "Name[ca@valencia]": "Dorsal de Markdown", "Name[ca]": "Dorsal de Markdown", "Name[cs]": "Podpůrná vrstva Markdown", "Name[es]": "Motor para Markdown", + "Name[fr]": "Moteur de markdown", "Name[it]": "Backend Markdown", "Name[nl]": "Markdown-backend", "Name[nn]": "Markdown-motor", "Name[pl]": "Silnik Markdown", "Name[pt]": "Infra-Estrutura de Markdown", "Name[sk]": "Markdown backend", "Name[sr@ijekavian]": "Позадина за маркдаун", "Name[sr@ijekavianlatin]": "Pozadina za Markdown", "Name[sr@latin]": "Pozadina za Markdown", "Name[sr]": "Позадина за маркдаун", "Name[sv]": "Markdown-gränssnitt", "Name[uk]": "Модуль Markdown", "Name[x-test]": "xxMarkdown Backendxx", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.0" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/markdown/okularApplication_md.desktop b/generators/markdown/okularApplication_md.desktop index c845ff5b6..c0405a103 100644 --- a/generators/markdown/okularApplication_md.desktop +++ b/generators/markdown/okularApplication_md.desktop @@ -1,187 +1,188 @@ [Desktop Entry] MimeType=text/markdown; Terminal=false Name=Okular Name[ar]=اوكلار Name[ast]=Okular Name[bg]=Okular Name[bs]=Okular Name[ca]=Okular Name[ca@valencia]=Okular Name[cs]=Okular Name[da]=Okular Name[de]=Okular Name[el]=Okular Name[en_GB]=Okular Name[eo]=Okular Name[es]=Okular Name[et]=Okular Name[eu]=Okular Name[fi]=Okular Name[fr]=Okular Name[ga]=Okular Name[gl]=Okular Name[hne]=ओकुलर Name[hr]=Okular Name[hu]=Okular Name[ia]=Okular Name[is]=Okular Name[it]=Okular Name[ja]=Okular Name[kk]=Okular Name[km]=Okular Name[ko]=Okular Name[ku]=Okular Name[lt]=Okular Name[lv]=Okular Name[mr]=ओक्युलर Name[nb]=Okular Name[nds]=Okular Name[nl]=Okular Name[nn]=Okular Name[pa]=ਓਕੁਲਾਰ Name[pl]=Okular Name[pt]=Okular Name[pt_BR]=Okular Name[ro]=Okular Name[ru]=Okular Name[si]=Okular Name[sk]=Okular Name[sl]=Okular Name[sq]=Okular Name[sr]=Окулар Name[sr@ijekavian]=Окулар Name[sr@ijekavianlatin]=Okular Name[sr@latin]=Okular Name[sv]=Okular Name[th]=โอกูลาร์ Name[tr]=Okular Name[ug]=Okular Name[uk]=Okular Name[x-test]=xxOkularxx Name[zh_CN]=Okular Name[zh_TW]=文件檢視_Okular GenericName=Document Viewer GenericName[ar]=عارض المستندات GenericName[ast]=Visor de documentos GenericName[bg]=Преглед на документи GenericName[bs]=Prikazivač dokumenata GenericName[ca]=Visualitzador de documents GenericName[ca@valencia]=Visualitzador de documents GenericName[cs]=Prohlížeč dokumentů GenericName[da]=Dokumentfremviser GenericName[de]=Dokumentenbetrachter GenericName[el]=Προβολέας εγγράφων GenericName[en_GB]=Document Viewer GenericName[eo]=Dokumenta rigardilo GenericName[es]=Visor de documentos GenericName[et]=Dokumendinäitaja GenericName[eu]=Dokumentu ikustailea GenericName[fa]=مشاهده‌گر سند GenericName[fi]=Asiakirjakatselin GenericName[fr]=Afficheur de documents GenericName[ga]=Amharcán Cáipéisí GenericName[gl]=Visor de documentos GenericName[hi]=दस्तावेज़ प्रदर्शक GenericName[hne]=कागद प्रदर्सक GenericName[hr]=Preglednik dokumenata GenericName[hu]=Dokumentumnézegető GenericName[ia]=Visor de documento GenericName[is]=Skjalaskoðari GenericName[it]=Visore di documenti GenericName[ja]=文書ビューア GenericName[kk]=Құжатты қарау құралы GenericName[km]=កម្មវិធី​មើល​ឯកសារ GenericName[ko]=문서 뷰어 GenericName[ku]=Nîşanderê Belgeyan GenericName[lt]=Dokumentų žiūryklė GenericName[lv]=Dokumentu skatītājs GenericName[mr]=दस्तऐवज प्रदर्शक GenericName[nb]=Dokumentviser GenericName[nds]=Dokmentkieker GenericName[ne]=कागजात दर्शक GenericName[nl]=Documentenviewer GenericName[nn]=Dokumentvisar GenericName[oc]=Visualizaire de documents GenericName[pa]=ਡੌਕੂਮੈਂਟ ਦਰਸ਼ਕ GenericName[pl]=Przeglądarka dokumentów GenericName[pt]=Visualizador de Documentos GenericName[pt_BR]=Visualizador de documentos GenericName[ro]=Vizualizor de documente GenericName[ru]=Просмотр документов GenericName[sk]=Prehliadač dokumentov GenericName[sl]=Pregledovalnik dokumentov GenericName[sq]=Shikues dokumentesh GenericName[sr]=Приказивач докумената GenericName[sr@ijekavian]=Приказивач докумената GenericName[sr@ijekavianlatin]=Prikazivač dokumenata GenericName[sr@latin]=Prikazivač dokumenata GenericName[sv]=Dokumentvisare GenericName[th]=เครื่องมือแสดงเอกสาร GenericName[tr]=Belge Gösterici GenericName[ug]=پۈتۈك كۆرگۈ GenericName[uk]=Переглядач документів GenericName[x-test]=xxDocument Viewerxx GenericName[zh_CN]=文档查看器 GenericName[zh_TW]=文件檢視器 Comment=Universal document viewer Comment[ast]=Visor universal de documentos Comment[ca]=Visualitzador universal de documents Comment[ca@valencia]=Visualitzador universal de documents Comment[cs]=Univerzální prohlížeč dokumentů Comment[da]=Universel dokumentfremviser Comment[de]=Universeller Dokumentenbetrachter Comment[el]=Καθολικός προβολέας εγγράφων Comment[en_GB]=Universal document viewer Comment[es]=Visor de documentos universal Comment[et]=Universaalne dokumendinäitaja Comment[fi]=Yleinen asiakirjakatselin Comment[fr]=Afficheur de document universel Comment[gl]=Visor de documentos universal Comment[ia]=Visor de documento universal Comment[is]=Fjölhæfur skjalaskoðari Comment[it]=Visore di documenti universale Comment[ko]=만능 문서 뷰어 Comment[nl]=Universele documentviewer Comment[nn]=Dokumentvisar for mange format Comment[pl]=Wszechstronna przeglądarka dokumentów Comment[pt]=Visualizador de documentos universal Comment[pt_BR]=Visualizador de documentos universal Comment[ru]=Универсальная программа просмотра документов Comment[sk]=Univerzálny prehliadač dokumentov Comment[sl]=Vsestranski pregledovalnik dokumentov Comment[sr]=Универзални приказивач докумената Comment[sr@ijekavian]=Универзални приказивач докумената Comment[sr@ijekavianlatin]=Univerzalni prikazivač dokumenata Comment[sr@latin]=Univerzalni prikazivač dokumenata Comment[sv]=Generell dokumentvisare Comment[tr]=Evrensel belge görüntüleyici Comment[uk]=Універсальний переглядач документів Comment[x-test]=xxUniversal document viewerxx Comment[zh_CN]=通用文档查看器 Comment[zh_TW]=通用文件檢視器 Exec=okular %U Icon=okular Type=Application InitialPreference=7 Categories=Qt;KDE;Graphics;Viewer; NoDisplay=true X-KDE-Keywords=Markdown X-KDE-Keywords[ca]=Markdown X-KDE-Keywords[ca@valencia]=Markdown X-KDE-Keywords[cs]=Markdown X-KDE-Keywords[es]=Markdown +X-KDE-Keywords[fr]=Markdown X-KDE-Keywords[it]=Markdown X-KDE-Keywords[nl]=Markdown X-KDE-Keywords[nn]=Markdown X-KDE-Keywords[pl]=Markdown X-KDE-Keywords[pt]=Markdown X-KDE-Keywords[sk]=Zrážka X-KDE-Keywords[sr]=Markdown,маркдаун X-KDE-Keywords[sr@ijekavian]=Markdown,маркдаун X-KDE-Keywords[sr@ijekavianlatin]=Markdown,Markdown X-KDE-Keywords[sr@latin]=Markdown,Markdown X-KDE-Keywords[sv]=Markdown X-KDE-Keywords[uk]=Markdown X-KDE-Keywords[x-test]=xxMarkdownxx X-KDE-Keywords[zh_CN]=Markdown diff --git a/generators/markdown/org.kde.mobile.okular_md.desktop b/generators/markdown/org.kde.mobile.okular_md.desktop index 433cb9fcb..e71f5e63f 100644 --- a/generators/markdown/org.kde.mobile.okular_md.desktop +++ b/generators/markdown/org.kde.mobile.okular_md.desktop @@ -1,176 +1,177 @@ [Desktop Entry] MimeType=text/markdown; Name=Reader Name[ar]=التصيير Name[ast]=Llector Name[bg]=Четец Name[bs]=Čitač Name[ca]=Lector Name[ca@valencia]=Lector Name[cs]=Čtečka Name[da]=Læser Name[de]=Lesegerät Name[el]=Πρόγραμμα ανάγνωσης Name[en_GB]=Reader Name[es]=Lector Name[et]=Lugeja Name[fi]=Lukija Name[fr]=Lecteur Name[ga]=Léitheoir Name[gl]=Lector Name[hu]=Olvasó Name[ia]=Lector Name[is]=Lesari Name[it]=Lettore Name[kk]=Оқу құралы Name[ko]=리더 Name[lt]=Skaitytuvas Name[mr]=वाचक Name[nb]=Leser Name[nds]=Leser Name[nl]=Lezer Name[nn]=Lesar Name[pa]=ਰੀਡਰ Name[pl]=Czytnik Name[pt]=Leitor Name[pt_BR]=Leitor Name[ro]=Cititor Name[ru]=Просмотрщик Name[sk]=Čítačka Name[sl]=Bralnik Name[sr]=Читач Name[sr@ijekavian]=Читач Name[sr@ijekavianlatin]=Čitač Name[sr@latin]=Čitač Name[sv]=Läsprogram Name[tr]=Okuyucu Name[ug]=ئوقۇغۇ Name[uk]=Переглядач Name[x-test]=xxReaderxx Name[zh_CN]=阅读器 Name[zh_TW]=閱讀器 GenericName=Document viewer GenericName[ar]=عارض المستندات GenericName[ast]=Visor de documentos GenericName[bg]=Преглед на документи GenericName[bs]=Prikazivač dokumenata GenericName[ca]=Visualitzador de documents GenericName[ca@valencia]=Visualitzador de documents GenericName[cs]=Prohlížeč dokumentů GenericName[da]=Dokumentfremviser GenericName[de]=Dokumentenbetrachter GenericName[el]=Προβολέας εγγράφων GenericName[en_GB]=Document Viewer GenericName[es]=Visor de documentos GenericName[et]=Dokumendinäitaja GenericName[fi]=Asiakirjakatselin GenericName[fr]=Afficheur de document GenericName[ga]=Amharcán cáipéisí GenericName[gl]=Visor de documentos GenericName[hu]=Dokumentummegjelenítő GenericName[ia]=Visor de documento GenericName[is]=Skjalaskoðari GenericName[it]=Visore di documenti GenericName[ja]=文書ビューア GenericName[kk]=Құжатты қарау құралы GenericName[ko]=문서 뷰어 GenericName[lt]=Dokumentų žiūryklė GenericName[mr]=दस्तऐवज प्रदर्शक GenericName[nb]=Dokumentviser GenericName[nds]=Dokmentkieker GenericName[nl]=Documentenviewer GenericName[nn]=Dokumentvisar GenericName[pa]=ਡੌਕੂਮੈਂਟ ਦਰਸ਼ਕ GenericName[pl]=Przeglądarka dokumentów GenericName[pt]=Visualizador de documentos GenericName[pt_BR]=Visualizador de documentos GenericName[ro]=Vizualizor de documente GenericName[ru]=Просмотр документов GenericName[sk]=Prehliadač dokumentov GenericName[sl]=Pregledovalnik dokumentov GenericName[sr]=Приказивач докумената GenericName[sr@ijekavian]=Приказивач докумената GenericName[sr@ijekavianlatin]=Prikazivač dokumenata GenericName[sr@latin]=Prikazivač dokumenata GenericName[sv]=Dokumentvisare GenericName[tr]=Belge görüntüleyici GenericName[uk]=Переглядач документів GenericName[x-test]=xxDocument viewerxx GenericName[zh_CN]=文档查看器 GenericName[zh_TW]=文件檢視器 Comment=Viewer for various types of documents Comment[ar]=عارض للعديد من أنواع المستندات Comment[ast]=Visor pa delles tribes de documentos Comment[bg]=Преглед на различни видове документи Comment[bs]=Pregledač raznih vrsta dokumenata Comment[ca]=Visualitzador de diversos tipus de documents Comment[ca@valencia]=Visualitzador de diversos tipus de documents Comment[cs]=Prohlížeč různých typů dokumentů Comment[da]=Fremviser af diverse dokumenttyper Comment[de]=Betrachter für verschiedene Arten von Dokumenten Comment[el]=Πρόγραμμα προβολής για διάφορους τύπους εγγράφων Comment[en_GB]=Viewer for various types of documents Comment[es]=Visor de diversos tipos de documentos Comment[et]=Eri tüüpi dokumentide näitaja Comment[fi]=Monenlaisten asiakirjojen katseluohjelma Comment[fr]=Afficheur pour différents types de documents Comment[ga]=Amharcán le haghaidh cáipéisí éagsúla Comment[gl]=Visor de varios tipos de documentos. Comment[hu]=Megjelenítő különféle típusú dokumentumokhoz Comment[ia]=Visor pro varie typos de documento Comment[is]=Skoðari fyrir ýmsar gerðir skjala Comment[it]=Visore per vari tipi di documenti Comment[kk]=Түрлі құжаттар қарау құралы Comment[ko]=여러 형식의 문서 뷰어 Comment[lt]=Žiūryklė įvairiems dokumentų tipams Comment[mr]=विविध प्रकारच्या दस्तऐवजांचा प्रदर्शक Comment[nb]=Framviser for forskjellige dokumenttyper Comment[nds]=Kieker för en Reeg Dokmenttypen Comment[nl]=Viewer voor verschillende typen documenten Comment[nn]=Framvisar for forskjellige dokumenttypar Comment[pa]=ਕਈ ਕਿਸਮ ਦੇ ਡੌਕੂਮੈਂਟ ਵੇਖਾਉਣ ਲਈ ਦਰਸ਼ਕ Comment[pl]=Przeglądarka dla różnych typów dokumentów Comment[pt]=Visualizador de vários tipos de documentos Comment[pt_BR]=Visualizador para vários tipos de documentos Comment[ro]=Vizualizor pentru diferite tipuri de documente Comment[ru]=Программа для просмотра различных типов документов Comment[sk]=Prehliadač pre rôzne typy dokumentov Comment[sl]=Pregledovalnik raznih vrst dokumentov Comment[sr]=Приказивач различитих врста докумената Comment[sr@ijekavian]=Приказивач различитих врста докумената Comment[sr@ijekavianlatin]=Prikazivač različitih vrsta dokumenata Comment[sr@latin]=Prikazivač različitih vrsta dokumenata Comment[sv]=Visningsprogram för diverse typer av dokument Comment[tr]=Çeşitli belge türü için görüntüleyici Comment[ug]=ھەر خىل تىپتىكى پۈتۈكلەرنى كۆرىدىغان پروگرامما Comment[uk]=Програма для перегляду документів різних типів Comment[x-test]=xxViewer for various types of documentsxx Comment[zh_CN]=可以查看多种文档的工具 Comment[zh_TW]=多種型態文件的檢視器 TryExec=kpackagelauncherqml -a org.kde.mobile.okular Exec=kpackagelauncherqml -a org.kde.mobile.okular %u Terminal=false Icon=okular Type=Application Categories=Qt;KDE;Graphics;Office;Viewer; InitialPreference=2 NoDisplay=true X-KDE-Keywords=Markdown X-KDE-Keywords[ca]=Markdown X-KDE-Keywords[ca@valencia]=Markdown X-KDE-Keywords[cs]=Markdown X-KDE-Keywords[es]=Markdown +X-KDE-Keywords[fr]=Markdown X-KDE-Keywords[it]=Markdown X-KDE-Keywords[nl]=Markdown X-KDE-Keywords[nn]=Markdown X-KDE-Keywords[pl]=Markdown X-KDE-Keywords[pt]=Markdown X-KDE-Keywords[sk]=Zrážka X-KDE-Keywords[sr]=Markdown,маркдаун X-KDE-Keywords[sr@ijekavian]=Markdown,маркдаун X-KDE-Keywords[sr@ijekavianlatin]=Markdown,Markdown X-KDE-Keywords[sr@latin]=Markdown,Markdown X-KDE-Keywords[sv]=Markdown X-KDE-Keywords[uk]=Markdown X-KDE-Keywords[x-test]=xxMarkdownxx X-KDE-Keywords[zh_CN]=Markdown diff --git a/generators/markdown/org.kde.okular-md.metainfo.xml b/generators/markdown/org.kde.okular-md.metainfo.xml index f48e50b70..94f6d505f 100644 --- a/generators/markdown/org.kde.okular-md.metainfo.xml +++ b/generators/markdown/org.kde.okular-md.metainfo.xml @@ -1,46 +1,48 @@ org.kde.okular-md org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Markdown Markdown Markdown Markdown Markdown + Markdown Markdown Markdown Markdown Markdown Zrážka Маркдаун Markdown Маркдаун Markdown Markdown Markdown xxMarkdownxx Markdown Adds support for reading Markdown documents Afegeix la implementació per a llegir documents de Markdown Afig la implementació per a llegir documents de Markdown Přidává podporu pro čtení dokumentů Markdown Permite la lectura de documentos Markdown + Permet la lecture des documents Markdown Voegt ondersteuning voor lezen van Markdown-documenten toe Legg til støtte for å lesa Markdown-dokument Dodaje obsługę odczytu dokumentów Markdown Adiciona o suporte para a leitura de documentos em Markdown Pridá podporu pre čítanie dokumentov Markdown Подршка за читање маркдаун докумената Podrška za čitanje Markdown dokumenata Подршка за читање маркдаун докумената Podrška za čitanje Markdown dokumenata Lägger till stöd för att läsa Markdown-dokument Додає підтримку читання документів Markdown xxAdds support for reading Markdown documentsxx text/markdown https://okular.kde.org