pdfsync.cpp.svn-base

来自「SumatraPDF是一款小型开源的pdf阅读工具。虽然玲珑小巧(只有800多K」· SVN-BASE 代码 · 共 809 行 · 第 1/2 页

SVN-BASE
809
字号
// Copyright William Blum 2008 http://william.famille-blum.org/
// PDF-source synchronizer based on .pdfsync file
// License: GPLv2
#include "SumatraPDF.h"
#include "PdfSync.h"
#include <assert.h>
#include <stdio.h>
#include "tstr_util.h"
#include "str_util.h"
#include <sys/stat.h>
#include <shlwapi.h>

// convert a coordinate from the sync file into a PDF coordinate
#define SYNCCOORDINATE_TO_PDFCOORDINATE(c)          (c/65781.76)

// convert a PDF coordinate into a sync file coordinate
#define PDFCOORDINATE_TO_SYNCCOORDINATE(p)          (p*65781.76)

// Test if the file 'filename' exists
bool FileExists( LPCTSTR filename ) {
    struct _stat buffer ;
    return 0 == _tstat( filename, &buffer );
}

Synchronizer *CreateSynchronizer(LPCTSTR pdffilename)
{
    TCHAR syncfile[_MAX_PATH];
    size_t n = _tcslen(pdffilename);
    size_t u = dimof(PDF_EXTENSION)-1;
    if (n>u && _tcsicmp(pdffilename+(n-u), PDF_EXTENSION) == 0 ) {
        // Check if a PDFSYNC file is present
        tstr_copyn(syncfile, dimof(syncfile), pdffilename, n-u);
        tstr_cat_s(syncfile, dimof(syncfile), PDFSYNC_EXTENSION);
        if (FileExists(syncfile)) 
            return new Pdfsync(syncfile);

        #ifdef SYNCTEX_FEATURE
            // check if a compressed SYNCTEX file is present
            tstr_copyn(syncfile, dimof(syncfile), pdffilename, n-u);
            tstr_cat_s(syncfile, dimof(syncfile), SYNCTEXGZ_EXTENSION);
            bool exist = FileExists(syncfile);

            // check if a SYNCTEX file is present
            tstr_copyn(syncfile, dimof(syncfile), pdffilename, n-u);
            tstr_cat_s(syncfile, dimof(syncfile), SYNCTEX_EXTENSION);
            exist |= FileExists(syncfile);

            if(exist)
                return new SyncTex(syncfile); // due to a bug with synctex_parser.c, this must always be 
                                              // the path to the .synctex file (even if a .synctex.gz file is used instead)
        #endif
        return NULL;
    }
    else {
        DBG_OUT("Bad PDF filename! (%s)\n", pdffilename);
        return NULL;
    }
}

// Replace in 'pattern' the macros %f %l %c by 'filename', 'line' and 'col'
// the result is stored in cmdline
UINT Synchronizer::prepare_commandline(LPCTSTR pattern, LPCTSTR filename, UINT line, UINT col, PTSTR cmdline, UINT cchCmdline)
{
    LPCTSTR perc;
    size_t len = 0;
    cmdline[0] = '\0';
    LPTSTR out = cmdline;
    size_t cchOut = cchCmdline;
    while (perc = tstr_find_char(pattern, '%')) {
        int u = perc-pattern;
        
        tstr_copyn(out, cchOut, pattern, u);
        len = tstr_len(out);
        out += len;
        cchOut -= len;

        perc++;
        if (*perc == 'f') {
            tstr_copy(out, cchOut, filename);
        }
        else if (*perc == 'l') {
            _sntprintf(out, cchOut, "%d", line);
        }
        else if (*perc == 'c') {
            _sntprintf(out, cchOut, "%d", col);
        }
        else {
            tstr_copyn(out, cchOut, perc-1, 2);
        }
        len = tstr_len(out);
        out += len;
        cchOut -= len;

        pattern = perc+1;
    }
    
    tstr_cat_s(cmdline, cchCmdline, pattern);

    return 1;
}

// PDFSYNC synchronizer
int Pdfsync::get_record_section(int record_index)
{
    int leftsection = 0,
        rightsection = record_sections.size()-1;
    if (rightsection < 0)
        return -1; // no section in the table
    while (1) {
        int n = rightsection-leftsection+1;
        // a single section?
        if (n == 1)
            return leftsection;
        else {
            int split = leftsection + (n>>1);
            int splitvalue = record_sections[split].firstrecord;
            if (record_index >= splitvalue)
                leftsection=split;
            else
                rightsection = split-1;
        }
    }
    assert(0);
    return -1;
}

FILE *Pdfsync::opensyncfile()
{
    FILE *fp;
    fp = fopen(syncfilename, "rb");
    if (NULL == fp) {
        DBG_OUT("The syncfile %s cannot be opened\n", syncfilename);
        return NULL;
    }
    return fp;
}

// read a line from a stream (exclude the end-of-line mark)
LPTSTR ftgetline(LPTSTR dst, size_t cchDst, FILE *fp)
{
    if (!_fgetts(dst, cchDst, fp))
        return NULL;

    LPTSTR end =  dst+tstr_len(dst)-1;
    while (*end == _T('\n') || *end == _T('\r'))
        *(end--) = 0;
    return dst;
}

int Pdfsync::scan_and_build_index(FILE *fp)
{
    TCHAR jobname[_MAX_PATH];
    
    ftgetline(jobname, dimof(jobname), fp); // get the job name from the first line
    // replace star by spaces (somehow tex replaces spaces by stars in the jobname)
    for(PTSTR rep = jobname; *rep; rep++) {
        if (*rep==_T('*'))
            *rep=_T(' ');
    }
    tstr_cat_s(jobname, dimof(jobname), _T(".tex")); 

    UINT versionNumber = 0;
    int ret = _ftscanf(fp, "version %u\n", &versionNumber);
    if (ret==EOF)
        return 1; // bad line format
    else if (versionNumber != 1)
        return 2; // unknown version

    srcfiles.clear();

    // add the initial tex file to the file stack
    src_file s;
    s.first_recordsection = (size_t)-1;
    s.last_recordsection = (size_t)-1;
    tstr_copy(s.filename, dimof(s.filename), jobname);
#ifndef NDEBUG    
    s.closeline_pos = -1;
    fgetpos(fp, &s.openline_pos);
#endif
    srcfiles.push_back(s);

    stack<size_t> incstack; // stack of included files
    incstack.push(srcfiles.size()-1);

    UINT cur_sheetNumber = (UINT)-1;
    int cur_plinesec = -1; // index of the p-line-section currently being created.
    int cur_recordsec=-1; // l-line-section currenlty created
    record_sections.clear();
    pdfsheet_index.clear();


    CHAR buff[_MAX_PATH];
    fpos_t linepos;
    fgetpos(fp, &linepos);
    char c;
    while ((c = fgetc(fp)) && !feof(fp)) {
        if (c!='l' && cur_recordsec!=-1) { // if a section of contiguous 'l' lines finished then create the corresponding section
#ifndef NDEBUG
            this->record_sections[cur_recordsec].endpos = linepos;
#endif
            cur_recordsec = -1;
        }
        if (c!='p' && cur_plinesec!=-1) { // if a section of contiguous 'p' lines finished then update the size of the corresponding p-line section
#ifndef NDEBUG
            this->pline_sections[cur_plinesec].endpos = linepos;
#endif
            cur_plinesec = -1;
        }
        switch (c) {
        case '(': 
            {
                // read the filename
                ftgetline(buff, dimof(buff), fp);
                PTSTR pfilename = buff;
                int len = tstr_len(buff);
                // if the filename contains quotes then remove them
                if (str_startswithi(buff, "\"") && str_endswith_char(buff,'"')) {
                    pfilename++;
                    len-=2;
                }

                src_file s;
                s.first_recordsection = (size_t)-1;
                s.last_recordsection = (size_t)-1;
                tstr_copyn(s.filename, dimof(s.filename), pfilename, len);
                // if the file name extension is not specified then add the suffix '.tex'
                if (tstr_find_char(pfilename, '.') == NULL) {
                     tstr_cat_s(s.filename, dimof(s.filename), _T(".tex"));
                }
#ifndef NDEBUG
                s.openline_pos = linepos;
                s.closeline_pos = -1;
#endif
                this->srcfiles.push_back(s);
                incstack.push(this->srcfiles.size()-1);
            }
            break;

        case ')':
#ifndef NDEBUG
            if (incstack.top() != (size_t)-1)
                this->srcfiles[incstack.top()].closeline_pos = linepos;
#endif
            incstack.pop();
            fscanf(fp, "\n");
            break;
        case 'l':
            {
                UINT columnNumber = 0, lineNumber = 0, recordNumber = 0;
                if (fscanf(fp, " %u %u %u\n", &recordNumber, &lineNumber, &columnNumber) <2)
                    DBG_OUT("Bad 'l' line in the pdfsync file\n");
                else {
                    if (cur_recordsec==-1){ // section not initiated yet?
                        record_section sec;
                        sec.srcfile = incstack.top();
                        sec.startpos = linepos;
                        sec.firstrecord = recordNumber;
                        record_sections.push_back(sec);
                        cur_recordsec = record_sections.size()-1;
                    }
#ifndef NDEBUG
                    record_sections[cur_recordsec].highestrecord = recordNumber;
#endif
                    assert(incstack.top() != (size_t)-1);
                    if (this->srcfiles[incstack.top()].first_recordsection == (size_t)-1)
                        this->srcfiles[incstack.top()].first_recordsection = cur_recordsec;
                    
                    this->srcfiles[incstack.top()].last_recordsection = cur_recordsec;
                }
            }
            break;
        case 'p':
            {
                if (fgetc(fp)=='*')
                    fgetc(fp);

                UINT recordNumber = 0, xPosition = 0, yPosition = 0;
                fscanf(fp, "%u %u %u\n", &recordNumber, &xPosition, &yPosition);

                if (cur_plinesec==-1){ // section not initiated yet?
                    plines_section sec;
                    sec.startpos = linepos;
#ifndef NDEBUG
                    sec.endpos = -1;
#endif
                    pline_sections.push_back(sec);
                    cur_plinesec = pline_sections.size()-1;

                    assert(cur_sheetNumber != (UINT)-1);
                    pdfsheet_index[cur_sheetNumber] = cur_plinesec;
                }
            }
            break;
        case 's':
            {
                fscanf(fp, " %u\n", &cur_sheetNumber);
                size_t maxsheet = pdfsheet_index.size();
                if (cur_sheetNumber>=maxsheet) {
                    pdfsheet_index.resize(cur_sheetNumber+1);
                    for(size_t s=maxsheet;s<=cur_sheetNumber;s++)
                        pdfsheet_index[s] = (size_t)-1;
                }
                break;
            }
        default:
            DBG_OUT("Malformed pdfsync file: unknown command '%c'\n",c);;
            break;
        }
        fgetpos(fp, &linepos);
    }
#ifndef NDEBUG
    if (cur_recordsec!=-1)
        this->record_sections[cur_recordsec].endpos = linepos;
    if (cur_plinesec!=-1)
        this->pline_sections[cur_plinesec].endpos = linepos;
#endif

    assert(incstack.size()==1);

    return 0;
}

int Pdfsync::rebuild_index()
{
    FILE *fp = opensyncfile();
    if (!fp)
        return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED;

    scan_and_build_index(fp);
    fclose(fp);
    
    return Synchronizer::rebuild_index();
}

UINT Pdfsync::pdf_to_source(UINT sheet, UINT x, UINT y, PTSTR srcfilepath, UINT cchFilepath, UINT *line, UINT *col)
{
    if (this->is_index_discarded())
        rebuild_index();

    FILE *fp = opensyncfile();
    if (!fp)
        return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED;

    // distance to the closest pdf location (in the range <PDFSYNC_EPSILON_SQUARE)
    UINT closest_xydist = (UINT)-1,
        closest_xydist_record = (UINT)-1;

    // If no record is found within a distance^2 of PDFSYNC_EPSILON_SQUARE
    // (closest_xydist_record==-1) then we pick up the record that is closest 
    // vertically to the hit-point.
    UINT closest_ydist = (UINT)-1, // vertical distance between the hit point and the vertically-closest record
        closest_xdist = (UINT)-1, // horizontal distance between the hit point and the vertically-closest record
        closest_ydist_record = (UINT)-1; // vertically-closest record

    // find the entry in the index corresponding to this page
    if (sheet>=pdfsheet_index.size()) {
        fclose(fp);
        return PDFSYNCERR_INVALID_PAGE_NUMBER;
    }

    // read all the sections of 'p' declarations for this pdf sheet
    fpos_t linepos;
    for(size_t cur_psection = pdfsheet_index[sheet];
        (cur_psection<this->pline_sections.size())
        && ((sheet<pdfsheet_index.size()-1 && cur_psection < pdfsheet_index[sheet+1])
                || sheet==pdfsheet_index.size()-1) ;
        cur_psection++) {
        
        linepos = this->pline_sections[cur_psection].startpos;
        fsetpos(fp, &linepos);
        int c;
        while ((c = fgetc(fp))=='p' && !feof(fp)) {
            // skip the optional star
            if (fgetc(fp)=='*')
                fgetc(fp);
            // read the location
            UINT recordNumber = 0, xPosition = 0, yPosition = 0;
            fscanf(fp, "%u %u %u\n", &recordNumber, &xPosition, &yPosition);
            // check whether it is closer that the closest point found so far
            UINT dx = abs((int)x - (int)SYNCCOORDINATE_TO_PDFCOORDINATE(xPosition));
            UINT dy = abs((int)y - (int)SYNCCOORDINATE_TO_PDFCOORDINATE(yPosition));
            UINT dist = dx*dx + dy*dy;
            if (dist<PDFSYNC_EPSILON_SQUARE && dist<closest_xydist) {
                closest_xydist_record = recordNumber;
                closest_xydist = dist;
            }
            else if ((closest_xydist == (UINT)-1) && ( dy < PDFSYNC_EPSILON_Y ) && (dy < closest_ydist || (dy==closest_ydist && dx<closest_xdist))) {                closest_ydist_record = recordNumber;
                closest_ydist = dy;
                closest_xdist = dx;
            }
            fgetpos(fp, &linepos);
        }
        assert(linepos == this->pline_sections[cur_psection].endpos);
    }

    UINT selected_record = closest_xydist_record!=(UINT)-1 ? closest_xydist_record : closest_ydist_record;
    if (selected_record == (UINT)-1) {
        fclose(fp);
        return PDFSYNCERR_NO_SYNC_AT_LOCATION; // no record was found close enough to the hit point
    }

    // We have a record number, we need to find its declaration ('l ...') in the syncfile

    // get the record section containing the record declaration
    int sec = this->get_record_section(selected_record);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?