void CmdLineParserTest() { WStrVec args; ParseCmdLine(L"test.exe -arg foo.pdf", args); utassert(3 == args.size()); utassert(str::Eq(args.at(0), L"test.exe")); utassert(str::Eq(args.at(1), L"-arg")); utassert(str::Eq(args.at(2), L"foo.pdf")); args.Reset(); ParseCmdLine(L"test.exe \"foo \\\" bar \\\\.pdf\" un\\\"quoted.pdf", args); utassert(3 == args.size()); utassert(str::Eq(args.at(0), L"test.exe")); utassert(str::Eq(args.at(1), L"foo \" bar \\\\.pdf")); utassert(str::Eq(args.at(2), L"un\"quoted.pdf")); args.Reset(); ParseCmdLine(L"test.exe \"foo\".pdf foo\" bar.pdf ", args); utassert(3 == args.size()); utassert(str::Eq(args.at(0), L"test.exe")); utassert(str::Eq(args.at(1), L"foo.pdf")); utassert(str::Eq(args.at(2), L"foo bar.pdf ")); args.Reset(); ParseCmdLine(L"test.exe -arg \"%1\" -more", args, 2); utassert(2 == args.size()); utassert(str::Eq(args.at(0), L"test.exe")); utassert(str::Eq(args.at(1), L"-arg \"%1\" -more")); args.Reset(); }
static void SetCloseProcessMsg() { AutoFreeW procNames(str::Dup(ReadableProcName(gProcessesToClose.at(0)))); for (size_t i = 1; i < gProcessesToClose.size(); i++) { const WCHAR* name = ReadableProcName(gProcessesToClose.at(i)); if (i < gProcessesToClose.size() - 1) procNames.Set(str::Join(procNames, L", ", name)); else procNames.Set(str::Join(procNames, L" and ", name)); } AutoFreeW s(str::Format(_TR("Please close %s to proceed!"), procNames)); SetMsg(s, COLOR_MSG_FAILED); }
// Find a record corresponding to the given source file, line number and optionally column number. // (at the moment the column parameter is ignored) // // If there are several *consecutively declared* records for the same line then they are all returned. // The list of records is added to the vector 'records' // // If there is no record for that line, the record corresponding to the nearest line is selected // (within a range of EPSILON_LINE) // // The function returns PDFSYNCERR_SUCCESS if a matching record was found. UINT Pdfsync::SourceToRecord(const WCHAR* srcfilename, UINT line, UINT col, Vec<size_t>& records) { UNUSED(col); if (!srcfilename) return PDFSYNCERR_INVALID_ARGUMENT; AutoFreeW srcfilepath; // convert the source file to an absolute path if (PathIsRelative(srcfilename)) srcfilepath.Set(PrependDir(srcfilename)); else srcfilepath.SetCopy(srcfilename); if (!srcfilepath) return PDFSYNCERR_OUTOFMEMORY; // find the source file entry size_t isrc; for (isrc = 0; isrc < srcfiles.size(); isrc++) if (path::IsSame(srcfilepath, srcfiles.at(isrc))) break; if (isrc == srcfiles.size()) return PDFSYNCERR_UNKNOWN_SOURCEFILE; if (fileIndex.at(isrc).start == fileIndex.at(isrc).end) return PDFSYNCERR_NORECORD_IN_SOURCEFILE; // there is not any record declaration for that particular source file // look for sections belonging to the specified file // starting with the first section that is declared within the scope of the file. UINT min_distance = EPSILON_LINE; // distance to the closest record size_t lineIx = (size_t)-1; // closest record-line index for (size_t isec = fileIndex.at(isrc).start; isec < fileIndex.at(isrc).end; isec++) { // does this section belong to the desired file? if (lines.at(isec).file != isrc) continue; UINT d = abs((int)lines.at(isec).line - (int)line); if (d < min_distance) { min_distance = d; lineIx = isec; if (0 == d) break; // We have found a record for the requested line! } } if (lineIx == (size_t)-1) return PDFSYNCERR_NORECORD_FOR_THATLINE; // we read all the consecutive records until we reach a record belonging to another line for (size_t i = lineIx; i < lines.size() && lines.at(i).line == lines.at(lineIx).line; i++) records.Push(lines.at(i).record); return PDFSYNCERR_SUCCESS; }
// Select random files to test. We want to test each file type equally, so // we first group them by file extension and then select up to maxPerType // for each extension, randomly, and inter-leave the files with different // extensions, so their testing is evenly distributed. // Returns result in <files>. static void RandomizeFiles(WStrVec& files, int maxPerType) { WStrVec fileExts; Vec<WStrVec*> filesPerType; for (size_t i = 0; i < files.size(); i++) { const WCHAR* file = files.at(i); const WCHAR* ext = path::GetExt(file); CrashAlwaysIf(!ext); int typeNo = fileExts.FindI(ext); if (-1 == typeNo) { fileExts.Append(str::Dup(ext)); filesPerType.Append(new WStrVec()); typeNo = (int)filesPerType.size() - 1; } filesPerType.at(typeNo)->Append(str::Dup(file)); } for (size_t j = 0; j < filesPerType.size(); j++) { WStrVec* all = filesPerType.at(j); WStrVec* random = new WStrVec(); for (int n = 0; n < maxPerType && all->size() > 0; n++) { int idx = rand() % all->size(); WCHAR* file = all->at(idx); random->Append(file); all->RemoveAtFast(idx); } filesPerType.at(j) = random; delete all; } files.Reset(); bool gotAll = false; while (!gotAll) { gotAll = true; for (size_t j = 0; j < filesPerType.size(); j++) { WStrVec* random = filesPerType.at(j); if (random->size() > 0) { gotAll = false; WCHAR* file = random->at(0); files.Append(file); random->RemoveAtFast(0); } } } for (size_t j = 0; j < filesPerType.size(); j++) { delete filesPerType.at(j); } }
WCHAR* DirFileProvider::NextFile() { if (filesToOpen.size() > 0) { return filesToOpen.PopAt(0); } if (dirsToVisit.size() > 0) { // test next directory AutoFreeW path(dirsToVisit.PopAt(0)); OpenDir(path); return NextFile(); } return nullptr; }
static void BenchDir(WCHAR* dir) { WStrVec files; CollectFilesToBench(dir, files); for (size_t i = 0; i < files.size(); i++) { BenchFile(files.at(i), nullptr); } }
FilesProvider(WStrVec& newFiles, int n, int offset) { // get every n-th file starting at offset for (size_t i = offset; i < newFiles.size(); i += n) { const WCHAR* f = newFiles.at(i); files.Append(str::Dup(f)); } provided = 0; }
static size_t GetAllMatchingFiles(const WCHAR* dir, const WCHAR* filter, WStrVec& files, bool showProgress) { WStrVec dirsToVisit; dirsToVisit.Append(str::Dup(dir)); while (dirsToVisit.size() > 0) { if (showProgress) { wprintf(L"."); fflush(stdout); } AutoFreeW path(dirsToVisit.PopAt(0)); CollectStressTestSupportedFilesFromDirectory(path, filter, files); AutoFreeW pattern(str::Format(L"%s\\*", path)); CollectPathsFromDirectory(pattern, dirsToVisit, true); } return files.size(); }
/* The html looks like: <li> <object type="text/sitemap"> <param name="Keyword" value="- operator"> <param name="Name" value="Subtraction Operator (-)"> <param name="Local" value="html/vsoprsubtract.htm"> <param name="Name" value="Subtraction Operator (-)"> <param name="Local" value="html/js56jsoprsubtract.htm"> </object> <ul> ... optional children ... </ul> <li> ... siblings ... */ static bool VisitChmIndexItem(EbookTocVisitor* visitor, HtmlElement* el, UINT cp, int level) { CrashIf(el->tag != Tag_Object || level > 1 && (!el->up || el->up->tag != Tag_Li)); WStrVec references; AutoFreeW keyword, name; for (el = el->GetChildByTag(Tag_Param); el; el = el->next) { if (Tag_Param != el->tag) continue; AutoFreeW attrName(el->GetAttribute("name")); AutoFreeW attrVal(el->GetAttribute("value")); if (attrName && attrVal && cp != CP_CHM_DEFAULT) { OwnedData bytes(str::conv::ToCodePage(attrVal, CP_CHM_DEFAULT)); attrVal.Set(str::conv::FromCodePage(bytes.Get(), cp)); } if (!attrName || !attrVal) /* ignore incomplete/unneeded <param> */; else if (str::EqI(attrName, L"Keyword")) keyword.Set(attrVal.StealData()); else if (str::EqI(attrName, L"Name")) { name.Set(attrVal.StealData()); // some CHM documents seem to use a lonely Name instead of Keyword if (!keyword) keyword.SetCopy(name); } else if (str::EqI(attrName, L"Local") && name) { // remove the ITS protocol and any filename references from the URLs if (str::Find(attrVal, L"::/")) attrVal.SetCopy(str::Find(attrVal, L"::/") + 3); references.Append(name.StealData()); references.Append(attrVal.StealData()); } } if (!keyword) return false; if (references.size() == 2) { visitor->Visit(keyword, references.at(1), level); return true; } visitor->Visit(keyword, nullptr, level); for (size_t i = 0; i < references.size(); i += 2) { visitor->Visit(references.at(i), references.at(i + 1), level + 1); } return true; }
static void ParseCommandLine(WCHAR* cmdLine) { WStrVec argList; ParseCmdLine(cmdLine, argList); #define is_arg(param) str::EqI(arg + 1, TEXT(param)) #define is_arg_with_param(param) (is_arg(param) && i < argList.size() - 1) // skip the first arg (exe path) for (size_t i = 1; i < argList.size(); i++) { WCHAR* arg = argList.at(i); if ('-' != *arg && '/' != *arg) continue; if (is_arg("s")) gGlobalData.silent = true; else if (is_arg_with_param("d")) str::ReplacePtr(&gGlobalData.installDir, argList.at(++i)); #ifndef BUILD_UNINSTALLER else if (is_arg("register")) gGlobalData.registerAsDefault = true; else if (is_arg_with_param("opt")) { WCHAR* opts = argList.at(++i); str::ToLowerInPlace(opts); str::TransChars(opts, L" ;", L",,"); WStrVec optlist; optlist.Split(opts, L",", true); if (optlist.Contains(L"pdffilter")) gGlobalData.installPdfFilter = true; if (optlist.Contains(L"pdfpreviewer")) gGlobalData.installPdfPreviewer = true; // uninstall the deprecated browser plugin if it's not // explicitly listed (only applies if the /opt flag is used) if (!optlist.Contains(L"plugin")) gGlobalData.keepBrowserPlugin = false; } else if (is_arg("x")) { gGlobalData.justExtractFiles = true; // silently extract files to the current directory (if /d isn't used) gGlobalData.silent = true; if (!gGlobalData.installDir) str::ReplacePtr(&gGlobalData.installDir, L"."); } else if (is_arg("autoupdate")) { gGlobalData.autoUpdate = true; } #endif else if (is_arg("h") || is_arg("help") || is_arg("?")) gGlobalData.showUsageAndQuit = true; #ifdef ENABLE_CRASH_TESTING else if (is_arg("crash")) { // will induce crash when 'Install' button is pressed // for testing crash handling gForceCrash = true; } #endif } }
bool DirFileProvider::OpenDir(const WCHAR* dirPath) { AssertCrash(filesToOpen.size() == 0); bool hasFiles = CollectStressTestSupportedFilesFromDirectory(dirPath, fileFilter, filesToOpen); filesToOpen.SortNatural(); AutoFreeW pattern(str::Format(L"%s\\*", dirPath)); bool hasSubDirs = CollectPathsFromDirectory(pattern, dirsToVisit, true); return hasFiles || hasSubDirs; }
// removes thumbnails that don't belong to any frequently used item in file history void CleanUpThumbnailCache(const FileHistory& fileHistory) { AutoFreeW thumbsPath(AppGenDataFilename(THUMBNAILS_DIR_NAME)); if (!thumbsPath) return; AutoFreeW pattern(path::Join(thumbsPath, L"*.png")); WStrVec files; WIN32_FIND_DATA fdata; HANDLE hfind = FindFirstFile(pattern, &fdata); if (INVALID_HANDLE_VALUE == hfind) return; do { if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) files.Append(str::Dup(fdata.cFileName)); } while (FindNextFile(hfind, &fdata)); FindClose(hfind); Vec<DisplayState*> list; fileHistory.GetFrequencyOrder(list); for (size_t i = 0; i < list.size() && i < FILE_HISTORY_MAX_FREQUENT * 2; i++) { AutoFreeW bmpPath(GetThumbnailPath(list.at(i)->filePath)); if (!bmpPath) continue; int idx = files.Find(path::GetBaseName(bmpPath)); if (idx != -1) { CrashIf(idx < 0 || files.size() <= (size_t)idx); free(files.PopAt(idx)); } } for (size_t i = 0; i < files.size(); i++) { AutoFreeW bmpPath(path::Join(thumbsPath, files.at(i))); file::Delete(bmpPath); } }
bool CheckInstallUninstallPossible(bool silent) { gProcessesToClose.Reset(); ProcessesUsingInstallation(gProcessesToClose); bool possible = gProcessesToClose.size() == 0; if (possible) { SetDefaultMsg(); } else { SetCloseProcessMsg(); if (!silent) MessageBeep(MB_ICONEXCLAMATION); } InvalidateFrame(); return possible; }
void BenchFileOrDir(WStrVec& pathsToBench) { gLog = new slog::StderrLogger(); size_t n = pathsToBench.size() / 2; for (size_t i = 0; i < n; i++) { WCHAR* path = pathsToBench.at(2 * i); if (file::Exists(path)) BenchFile(path, pathsToBench.at(2 * i + 1)); else if (dir::Exists(path)) BenchDir(path); else logbench(L"Error: file or dir %s doesn't exist", path); } delete gLog; }
// parses a list of page ranges such as 1,3-5,7- (i..e all but pages 2 and 6) // into an interable list (returns nullptr on parsing errors) // caller must delete the result bool ParsePageRanges(const WCHAR* ranges, Vec<PageRange>& result) { if (!ranges) return false; WStrVec rangeList; rangeList.Split(ranges, L",", true); rangeList.SortNatural(); for (size_t i = 0; i < rangeList.size(); i++) { int start, end; if (str::Parse(rangeList.at(i), L"%d-%d%$", &start, &end) && 0 < start && start <= end) result.Append(PageRange(start, end)); else if (str::Parse(rangeList.at(i), L"%d-%$", &start) && 0 < start) result.Append(PageRange(start, INT_MAX)); else if (str::Parse(rangeList.at(i), L"%d%$", &start) && 0 < start) result.Append(PageRange(start, start)); else return false; } return result.size() > 0; }
/* parse argument list. we assume that all unrecognized arguments are file names. */ void CommandLineInfo::ParseCommandLine(const WCHAR* cmdLine) { WStrVec argList; ParseCmdLine(cmdLine, argList); size_t argCount = argList.size(); #define is_arg_with_param(_argNo) (param && _argNo == arg) #define additional_param() argList.at(n + 1) #define has_additional_param() ((argCount > n + 1) && ('-' != additional_param()[0])) #define handle_string_param(name) name.SetCopy(argList.at(++n)) #define handle_int_param(name) name = _wtoi(argList.at(++n)) for (size_t n = 1; n < argCount; n++) { WCHAR* argName = argList.at(n); int arg = GetArgNo(argName); WCHAR* param = nullptr; if (argCount > n + 1) { param = argList.at(n + 1); } if (RegisterForPdf == arg) { makeDefault = true; exitImmediately = true; return; } else if (Silent == arg) { // silences errors happening during -print-to and -print-to-default silent = true; } else if (PrintToDefault == arg) { printerName.Set(GetDefaultPrinterName()); if (!printerName) printDialog = true; exitWhenDone = true; } else if (is_arg_with_param(PrintTo)) { handle_string_param(printerName); exitWhenDone = true; } else if (PrintDialog == arg) { printDialog = true; } else if (is_arg_with_param(PrintSettings)) { // argument is a comma separated list of page ranges and // advanced options [even|odd], [noscale|shrink|fit] and [autorotation|portrait|landscape] // e.g. -print-settings "1-3,5,10-8,odd,fit" handle_string_param(printSettings); str::RemoveChars(printSettings, L" "); str::TransChars(printSettings, L";", L","); } else if (ExitWhenDone == arg || ExitOnPrint == arg) { // only affects -print-dialog (-print-to and -print-to-default // always exit on print) and -stress-test (useful for profiling) exitWhenDone = true; } else if (is_arg_with_param(InverseSearch)) { inverseSearchCmdLine.SetCopy(argList.at(++n)); } else if ((is_arg_with_param(ForwardSearch) || is_arg_with_param(FwdSearch)) && argCount > n + 2) { // -forward-search is for consistency with -inverse-search // -fwdsearch is for consistency with -fwdsearch-* handle_string_param(forwardSearchOrigin); handle_int_param(forwardSearchLine); } else if (is_arg_with_param(NamedDest) || is_arg_with_param(NamedDest2)) { // -nameddest is for backwards compat (was used pre-1.3) // -named-dest is for consistency handle_string_param(destName); } else if (is_arg_with_param(Page)) { handle_int_param(pageNumber); } else if (Restrict == arg) { restrictedUse = true; } else if (InvertColors1 == arg || InvertColors2 == arg) { // -invertcolors is for backwards compat (was used pre-1.3) // -invert-colors is for consistency // -invert-colors used to be a shortcut for -set-color-range 0xFFFFFF 0x000000 // now it non-permanently swaps textColor and backgroundColor invertColors = true; } else if (Presentation == arg) { enterPresentation = true; } else if (Fullscreen == arg) { enterFullScreen = true; } else if (is_arg_with_param(View)) { ParseViewMode(&startView, param); ++n; } else if (is_arg_with_param(Zoom)) { ParseZoomValue(&startZoom, param); ++n; } else if (is_arg_with_param(Scroll)) { ParseScrollValue(&startScroll, param); ++n; } else if (Console == arg) { showConsole = true; } else if (is_arg_with_param(AppData)) { appdataDir.SetCopy(param); ++n; } else if (is_arg_with_param(Plugin)) { // -plugin [<URL>] <parent HWND> if (argCount > n + 2 && !str::IsDigit(*argList.at(n + 1)) && *argList.at(n + 2) != '-') handle_string_param(pluginURL); // the argument is a (numeric) window handle to // become the parent of a frameless SumatraPDF // (used e.g. for embedding it into a browser plugin) hwndPluginParent = (HWND)(INT_PTR)_wtol(argList.at(++n)); } else if (is_arg_with_param(StressTest)) { // -stress-test <file or dir path> [<file filter>] [<page/file range(s)>] [<cycle // count>x] // e.g. -stress-test file.pdf 25x for rendering file.pdf 25 times // -stress-test file.pdf 1-3 render only pages 1, 2 and 3 of file.pdf // -stress-test dir 301- 2x render all files in dir twice, skipping first 300 // -stress-test dir *.pdf;*.xps render all files in dir that are either PDF or XPS handle_string_param(stressTestPath); int num; if (has_additional_param() && str::FindChar(additional_param(), '*')) handle_string_param(stressTestFilter); if (has_additional_param() && IsValidPageRange(additional_param())) handle_string_param(stressTestRanges); if (has_additional_param() && str::Parse(additional_param(), L"%dx%$", &num) && num > 0) { stressTestCycles = num; n++; } } else if (is_arg_with_param(ArgN)) { handle_int_param(stressParallelCount); } else if (is_arg_with_param(Render)) { handle_int_param(pageNumber); testRenderPage = true; } else if (is_arg_with_param(ExtractText)) { handle_int_param(pageNumber); testExtractPage = true; } else if (Rand == arg) { stressRandomizeFiles = true; } else if (is_arg_with_param(Bench)) { WCHAR* s = str::Dup(param); ++n; pathsToBenchmark.Push(s); s = nullptr; if (has_additional_param() && IsBenchPagesInfo(additional_param())) { s = str::Dup(argList.at(++n)); } pathsToBenchmark.Push(s); exitImmediately = true; } else if (CrashOnOpen == arg) { // to make testing of crash reporting system in pre-release/release // builds possible crashOnOpen = true; } else if (ReuseInstance == arg) { // for backwards compatibility, -reuse-instance reuses whatever // instance has registered as DDE server reuseDdeInstance = true; } // TODO: remove the following deprecated options within a release or two else if (is_arg_with_param(Lang)) { auto tmp = str::conv::ToAnsi(param); lang.Set(tmp.StealData()); ++n; } else if (EscToExit == arg) { globalPrefArgs.Append(str::Dup(argList.at(n))); } else if (is_arg_with_param(BgColor) || is_arg_with_param(BgColor2) || is_arg_with_param(FwdSearchOffset) || is_arg_with_param(FwdSearchWidth) || is_arg_with_param(FwdSearchColor) || is_arg_with_param(FwdSearchPermanent) || is_arg_with_param(MangaMode)) { globalPrefArgs.Append(str::Dup(argList.at(n))); globalPrefArgs.Append(str::Dup(argList.at(++n))); } else if (SetColorRange == arg && argCount > n + 2) { globalPrefArgs.Append(str::Dup(argList.at(n))); globalPrefArgs.Append(str::Dup(argList.at(++n))); globalPrefArgs.Append(str::Dup(argList.at(++n))); } #ifdef DEBUG else if (ArgEnumPrinters == arg) { EnumeratePrinters(); /* this is for testing only, exit immediately */ exitImmediately = true; return; } #endif // this should have been handled already by AutoUpdateMain else if (is_arg_with_param(AutoUpdate)) { n++; } else { // Remember this argument as a filename to open WCHAR* filePath = nullptr; if (str::EndsWithI(argName, L".lnk")) filePath = ResolveLnk(argName); if (!filePath) filePath = str::Dup(argName); fileNames.Push(filePath); } } #undef is_arg_with_param #undef additional_param #undef has_additional_param #undef handle_string_param #undef handle_int_param }
virtual WCHAR* NextFile() { if (provided >= files.size()) return nullptr; return str::Dup(files.at(provided++)); }
int Count() { return (int)text.size(); }
void StartStressTest(CommandLineInfo* i, WindowInfo* win) { gIsStressTesting = true; // TODO: for now stress testing only supports the non-ebook ui gGlobalPrefs->ebookUI.useFixedPageUI = true; gGlobalPrefs->chmUI.useFixedPageUI = true; // TODO: make stress test work with tabs? gGlobalPrefs->useTabs = false; // forbid entering sleep mode during tests SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); srand((unsigned int)time(nullptr)); // redirect stderr to NUL to disable (MuPDF) logging FILE* nul; freopen_s(&nul, "NUL", "w", stderr); int n = i->stressParallelCount; if (n > 1 || i->stressRandomizeFiles) { WindowInfo** windows = AllocArray<WindowInfo*>(n); windows[0] = win; for (int j = 1; j < n; j++) { windows[j] = CreateAndShowWindowInfo(); if (!windows[j]) return; } WStrVec filesToTest; wprintf(L"Scanning for files in directory %s\n", i->stressTestPath.Get()); fflush(stdout); size_t filesCount = GetAllMatchingFiles(i->stressTestPath, i->stressTestFilter, filesToTest, true); if (0 == filesCount) { wprintf(L"Didn't find any files matching filter '%s'\n", i->stressTestFilter.Get()); return; } wprintf(L"Found %d files", (int)filesCount); fflush(stdout); if (i->stressRandomizeFiles) { // TODO: should probably allow over-writing the 100 limit RandomizeFiles(filesToTest, 100); filesCount = filesToTest.size(); wprintf(L"\nAfter randomization: %d files", (int)filesCount); } wprintf(L"\n"); fflush(stdout); for (int j = 0; j < n; j++) { // dst will be deleted when the stress ends win = windows[j]; StressTest* dst = new StressTest(win, i->exitWhenDone); win->stressTest = dst; // divide filesToTest among each window FilesProvider* filesProvider = new FilesProvider(filesToTest, n, j); dst->Start(filesProvider, i->stressTestCycles); } free(windows); } else { // dst will be deleted when the stress ends StressTest* dst = new StressTest(win, i->exitWhenDone); win->stressTest = dst; dst->Start(i->stressTestPath, i->stressTestFilter, i->stressTestRanges, i->stressTestCycles); } }
// see http://itexmac.sourceforge.net/pdfsync.html for the specification int Pdfsync::RebuildIndex() { OwnedData data(file::ReadFile(syncfilepath)); if (!data.data) { return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; } // convert the file data into a list of zero-terminated strings str::TransChars(data.data, "\r\n", "\0\0"); // parse preamble (jobname and version marker) char* line = data.data; char* dataEnd = data.data + data.size; // replace star by spaces (TeX uses stars instead of spaces in filenames) str::TransChars(line, "*/", " \\"); AutoFreeW jobName(str::conv::FromAnsi(line)); jobName.Set(str::Join(jobName, L".tex")); jobName.Set(PrependDir(jobName)); line = Advance0Line(line, dataEnd); UINT versionNumber = 0; if (!line || !str::Parse(line, "version %u", &versionNumber) || versionNumber != 1) { return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; } // reset synchronizer database srcfiles.Reset(); lines.Reset(); points.Reset(); fileIndex.Reset(); sheetIndex.Reset(); Vec<size_t> filestack; UINT page = 1; sheetIndex.Append(0); // add the initial tex file to the source file stack filestack.Push(srcfiles.size()); srcfiles.Append(jobName.StealData()); PdfsyncFileIndex findex = {0}; fileIndex.Append(findex); PdfsyncLine psline; PdfsyncPoint pspoint; // parse data UINT maxPageNo = engine->PageCount(); while ((line = Advance0Line(line, dataEnd)) != nullptr) { if (!line) break; switch (*line) { case 'l': psline.file = filestack.Last(); if (str::Parse(line, "l %u %u %u", &psline.record, &psline.line, &psline.column)) lines.Append(psline); else if (str::Parse(line, "l %u %u", &psline.record, &psline.line)) { psline.column = 0; lines.Append(psline); } // else dbg("Bad 'l' line in the pdfsync file"); break; case 's': if (str::Parse(line, "s %u", &page)) sheetIndex.Append(points.size()); // else dbg("Bad 's' line in the pdfsync file"); // if (0 == page || page > maxPageNo) // dbg("'s' line with invalid page number in the pdfsync file"); break; case 'p': pspoint.page = page; if (0 == page || page > maxPageNo) /* ignore point for invalid page number */; else if (str::Parse(line, "p %u %u %u", &pspoint.record, &pspoint.x, &pspoint.y)) points.Append(pspoint); else if (str::Parse(line, "p* %u %u %u", &pspoint.record, &pspoint.x, &pspoint.y)) points.Append(pspoint); // else dbg("Bad 'p' line in the pdfsync file"); break; case '(': { AutoFreeW filename(str::conv::FromAnsi(line + 1)); // if the filename contains quotes then remove them // TODO: this should never happen!? if (filename[0] == '"' && filename[str::Len(filename) - 1] == '"') filename.Set(str::DupN(filename + 1, str::Len(filename) - 2)); // undecorate the filepath: replace * by space and / by \ (backslash) str::TransChars(filename, L"*/", L" \\"); // if the file name extension is not specified then add the suffix '.tex' if (str::IsEmpty(path::GetExt(filename))) filename.Set(str::Join(filename, L".tex")); // ensure that the path is absolute if (PathIsRelative(filename)) filename.Set(PrependDir(filename)); filestack.Push(srcfiles.size()); srcfiles.Append(filename.StealData()); findex.start = findex.end = lines.size(); fileIndex.Append(findex); } break; case ')': if (filestack.size() > 1) fileIndex.at(filestack.Pop()).end = lines.size(); // else dbg("Unbalanced ')' line in the pdfsync file"); break; default: // dbg("Ignoring invalid pdfsync line starting with '%c'", *line); break; } } fileIndex.at(0).end = lines.size(); AssertCrash(filestack.size() == 1); return Synchronizer::RebuildIndex(); }