void AddFavorite(WindowInfo* win) { TabInfo* tab = win->currentTab; CrashIf(!tab); int pageNo = win->currPageNo; AutoFreeW name; if (tab->ctrl->HasTocTree()) { // use the current ToC heading as default name DocTocItem* root = tab->ctrl->GetTocTree(); DocTocItem* item = TocItemForPageNo(root, pageNo); if (item) { name.SetCopy(item->title); } delete root; } AutoFreeW pageLabel(tab->ctrl->GetPageLabel(pageNo)); bool shouldAdd = Dialog_AddFavorite(win->hwndFrame, pageLabel, name); if (!shouldAdd) { return; } AutoFreeW plainLabel(str::Format(L"%d", pageNo)); bool needsLabel = !str::Eq(plainLabel, pageLabel); RememberFavTreeExpansionStateForAllWindows(); gFavorites.AddOrReplace(tab->filePath, pageNo, name, needsLabel ? pageLabel.Get() : nullptr); // expand newly added favorites by default DisplayState* fav = gFavorites.GetFavByFilePath(tab->filePath); if (fav && fav->favorites->size() == 2) { win->expandedFavorites.Append(fav); } UpdateFavoritesTreeForAllWindows(); prefs::Save(); }
void CopySelectionToClipboard(WindowInfo* win) { if (!win->currentTab || !win->currentTab->selectionOnPage) return; CrashIf(win->currentTab->selectionOnPage->size() == 0 && win->mouseAction != MouseAction::SelectingText); if (win->currentTab->selectionOnPage->size() == 0) return; CrashIf(!win->AsFixed()); if (!win->AsFixed()) return; if (!OpenClipboard(nullptr)) return; EmptyClipboard(); DisplayModel* dm = win->AsFixed(); #ifndef DISABLE_DOCUMENT_RESTRICTIONS if (!dm->GetEngine()->AllowsCopyingText()) win->ShowNotification(_TR("Copying text was denied (copying as image only)")); else #endif if (!dm->GetEngine()->IsImageCollection()) { AutoFreeW selText; bool isTextSelection = dm->textSelection->result.len > 0; if (isTextSelection) { selText.Set(dm->textSelection->ExtractText(L"\r\n")); } else { WStrVec selections; for (SelectionOnPage& sel : *win->currentTab->selectionOnPage) { WCHAR* text = dm->GetTextInRegion(sel.pageNo, sel.rect); if (text) selections.Push(text); } selText.Set(selections.Join()); } // don't copy empty text if (!str::IsEmpty(selText.Get())) CopyTextToClipboard(selText, true); if (isTextSelection) { // don't also copy the first line of a text selection as an image CloseClipboard(); return; } } /* also copy a screenshot of the current selection to the clipboard */ SelectionOnPage* selOnPage = &win->currentTab->selectionOnPage->at(0); RenderedBitmap* bmp = dm->GetEngine()->RenderBitmap(selOnPage->pageNo, dm->GetZoomReal(), dm->GetRotation(), &selOnPage->rect, RenderTarget::Export); if (bmp) CopyImageToClipboard(bmp->GetBitmap(), true); delete bmp; CloseClipboard(); }
// For easy access, we try to show favorites in the menu, similar to a list of // recently opened files. // The first menu items are for currently opened file (up to MAX_FAV_MENUS), based // on the assumption that user is usually interested in navigating current file. // Then we have a submenu for each file for which there are bookmarks (up to // MAX_FAV_SUBMENUS), each having up to MAX_FAV_MENUS menu items. // If not all favorites can be shown, we also enable "Show all favorites" menu which // will provide a way to see all favorites. // Note: not sure if that's the best layout. Maybe we should always use submenu and // put the submenu for current file as the first one (potentially named as "Current file" // or some such, to make it stand out from other submenus) static void AppendFavMenus(HMENU m, const WCHAR* currFilePath) { // To minimize mouse movement when navigating current file via favorites // menu, put favorites for current file first DisplayState* currFileFav = nullptr; if (currFilePath) { currFileFav = gFavorites.GetFavByFilePath(currFilePath); } // sort the files with favorites by base file name of file path Vec<const WCHAR*> filePathsSorted; if (HasPermission(Perm_DiskAccess)) { // only show favorites for other files, if we're allowed to open them GetSortedFilePaths(filePathsSorted, currFileFav); } if (currFileFav && currFileFav->favorites->size() > 0) { filePathsSorted.InsertAt(0, currFileFav->filePath); } if (filePathsSorted.size() == 0) { return; } AppendMenu(m, MF_SEPARATOR, 0, nullptr); gFavorites.ResetMenuIds(); UINT menuId = IDM_FAV_FIRST; size_t menusCount = filePathsSorted.size(); if (menusCount > MAX_FAV_MENUS) { menusCount = MAX_FAV_MENUS; } for (size_t i = 0; i < menusCount; i++) { const WCHAR* filePath = filePathsSorted.at(i); DisplayState* f = gFavorites.GetFavByFilePath(filePath); CrashIf(!f); HMENU sub = m; bool combined = (f->favorites->size() == 1); if (!combined) { sub = CreateMenu(); } AppendFavMenuItems(sub, f, menuId, combined, f == currFileFav); if (!combined) { if (f == currFileFav) { AppendMenu(m, MF_POPUP | MF_STRING, (UINT_PTR)sub, _TR("Current file")); } else { AutoFreeW tmp; tmp.SetCopy(path::GetBaseName(filePath)); auto fileName = win::menu::ToSafeString(tmp); AppendMenuW(m, MF_POPUP | MF_STRING, (UINT_PTR)sub, fileName); } } } }
// 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; }
WCHAR* ChmDoc::GetProperty(DocumentProperty prop) { AutoFreeW result; if (DocumentProperty::Title == prop && title) result.Set(ToStr(title)); else if (DocumentProperty::CreatorApp == prop && creator) result.Set(ToStr(creator)); // TODO: shouldn't it be up to the front-end to normalize whitespace? if (result) { // TODO: original code called str::RemoveChars(result, "\n\r\t") str::NormalizeWS(result); } return result.StealData(); }
int Pdfsync::DocToSource(UINT pageNo, PointI pt, AutoFreeW& filename, UINT* line, UINT* col) { if (IsIndexDiscarded()) if (RebuildIndex() != PDFSYNCERR_SUCCESS) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; // find the entry in the index corresponding to this page if (pageNo <= 0 || pageNo >= sheetIndex.size() || pageNo > (UINT)engine->PageCount()) return PDFSYNCERR_INVALID_PAGE_NUMBER; // PdfSync coordinates are y-inversed RectI mbox = engine->PageMediabox(pageNo).Round(); pt.y = mbox.dy - pt.y; // distance to the closest pdf location (in the range <PDFSYNC_EPSILON_SQUARE) UINT closest_xydist = UINT_MAX; UINT selected_record = UINT_MAX; // If no record is found within a distance^2 of PDFSYNC_EPSILON_SQUARE // (selected_record == -1) then we pick up the record that is closest // vertically to the hit-point. UINT closest_ydist = UINT_MAX; // vertical distance between the hit point and the vertically-closest record UINT closest_xdist = UINT_MAX; // horizontal distance between the hit point and the vertically-closest record UINT closest_ydist_record = UINT_MAX; // vertically-closest record // read all the sections of 'p' declarations for this pdf sheet for (size_t i = sheetIndex.at(pageNo); i < points.size() && points.at(i).page == pageNo; i++) { // check whether it is closer than the closest point found so far UINT dx = abs(pt.x - (int)SYNC_TO_PDF_COORDINATE(points.at(i).x)); UINT dy = abs(pt.y - (int)SYNC_TO_PDF_COORDINATE(points.at(i).y)); UINT dist = dx * dx + dy * dy; if (dist < PDFSYNC_EPSILON_SQUARE && dist < closest_xydist) { selected_record = points.at(i).record; closest_xydist = dist; } else if ((closest_xydist == UINT_MAX) && dy < PDFSYNC_EPSILON_Y && (dy < closest_ydist || (dy == closest_ydist && dx < closest_xdist))) { closest_ydist_record = points.at(i).record; closest_ydist = dy; closest_xdist = dx; } } if (selected_record == UINT_MAX) selected_record = closest_ydist_record; if (selected_record == UINT_MAX) 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 PdfsyncLine cmp; cmp.record = selected_record; PdfsyncLine* found = (PdfsyncLine*)bsearch(&cmp, lines.LendData(), lines.size(), sizeof(PdfsyncLine), cmpLineRecords); AssertCrash(found); if (!found) return PDFSYNCERR_NO_SYNC_AT_LOCATION; filename.SetCopy(srcfiles.at(found->file)); *line = found->line; *col = found->column; return PDFSYNCERR_SUCCESS; }
static void AppendFavMenuItems(HMENU m, DisplayState* f, UINT& idx, bool combined, bool isCurrent) { for (size_t i = 0; i < f->favorites->size(); i++) { if (i >= MAX_FAV_MENUS) { return; } Favorite* fn = f->favorites->at(i); fn->menuId = idx++; AutoFreeW s; if (combined) { s.Set(FavCompactReadableName(f, fn, isCurrent)); } else { s.Set(FavReadableName(fn)); } auto str = win::menu::ToSafeString(s); AppendMenuW(m, MF_STRING, (UINT_PTR)fn->menuId, str); } }
int SyncTex::DocToSource(UINT pageNo, PointI pt, AutoFreeW& filename, UINT* line, UINT* col) { if (IsIndexDiscarded()) { if (RebuildIndex() != PDFSYNCERR_SUCCESS) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; } CrashIf(!this->scanner); // Coverity: at this point, this->scanner->flags.has_parsed == 1 and thus // synctex_scanner_parse never gets the chance to freeing the scanner if (synctex_edit_query(this->scanner, pageNo, (float)pt.x, (float)pt.y) <= 0) return PDFSYNCERR_NO_SYNC_AT_LOCATION; synctex_node_t node = synctex_next_result(this->scanner); if (!node) return PDFSYNCERR_NO_SYNC_AT_LOCATION; const char* name = synctex_scanner_get_name(this->scanner, synctex_node_tag(node)); if (!name) return PDFSYNCERR_UNKNOWN_SOURCEFILE; bool isUtf8 = true; filename.Set(str::conv::FromUtf8(name)); TryAgainAnsi: if (!filename) return PDFSYNCERR_OUTOFMEMORY; // undecorate the filepath: replace * by space and / by \ (backslash) str::TransChars(filename, L"*/", L" \\"); // Convert the source filepath to an absolute path if (PathIsRelative(filename)) filename.Set(PrependDir(filename)); // recent SyncTeX versions encode in UTF-8 instead of ANSI if (isUtf8 && !file::Exists(filename)) { isUtf8 = false; filename.Set(str::conv::FromAnsi(name)); goto TryAgainAnsi; } *line = synctex_node_line(node); *col = synctex_node_column(node); return PDFSYNCERR_SUCCESS; }
HRESULT CPdfFilter::GetNextChunkValue(CChunkValue& chunkValue) { AutoFreeW str; switch (m_state) { case STATE_PDF_START: m_state = STATE_PDF_AUTHOR; chunkValue.SetTextValue(PKEY_PerceivedType, L"document"); return S_OK; case STATE_PDF_AUTHOR: m_state = STATE_PDF_TITLE; str.Set(m_pdfEngine->GetProperty(DocumentProperty::Author)); if (!str::IsEmpty(str.Get())) { chunkValue.SetTextValue(PKEY_Author, str); return S_OK; } // fall through case STATE_PDF_TITLE: m_state = STATE_PDF_DATE; str.Set(m_pdfEngine->GetProperty(DocumentProperty::Title)); if (!str) str.Set(m_pdfEngine->GetProperty(DocumentProperty::Subject)); if (!str::IsEmpty(str.Get())) { chunkValue.SetTextValue(PKEY_Title, str); return S_OK; } // fall through case STATE_PDF_DATE: m_state = STATE_PDF_CONTENT; str.Set(m_pdfEngine->GetProperty(DocumentProperty::ModificationDate)); if (!str) str.Set(m_pdfEngine->GetProperty(DocumentProperty::CreationDate)); if (!str::IsEmpty(str.Get())) { SYSTEMTIME systime; FILETIME filetime; if (PdfDateParse(str, &systime) && SystemTimeToFileTime(&systime, &filetime)) { chunkValue.SetFileTimeValue(PKEY_ItemDate, filetime); return S_OK; } } // fall through case STATE_PDF_CONTENT: while (++m_iPageNo <= m_pdfEngine->PageCount()) { str.Set(m_pdfEngine->ExtractPageText(m_iPageNo, L"\r\n")); if (str::IsEmpty(str.Get())) continue; chunkValue.SetTextValue(PKEY_Search_Contents, str, CHUNK_TEXT); return S_OK; } m_state = STATE_PDF_END; // fall through case STATE_PDF_END: default: return FILTER_E_END_OF_CHUNKS; } }
void SetMsg(const WCHAR* msg, Color color) { gMsg.SetCopy(msg); gMsgColor = color; }
HRESULT CEpubFilter::GetNextChunkValue(CChunkValue& chunkValue) { AutoFreeW str; switch (m_state) { case STATE_EPUB_START: m_state = STATE_EPUB_AUTHOR; chunkValue.SetTextValue(PKEY_PerceivedType, L"document"); return S_OK; case STATE_EPUB_AUTHOR: m_state = STATE_EPUB_TITLE; str.Set(m_epubDoc->GetProperty(DocumentProperty::Author)); if (!str::IsEmpty(str.Get())) { chunkValue.SetTextValue(PKEY_Author, str); return S_OK; } // fall through case STATE_EPUB_TITLE: m_state = STATE_EPUB_DATE; str.Set(m_epubDoc->GetProperty(DocumentProperty::Title)); if (!str) str.Set(m_epubDoc->GetProperty(DocumentProperty::Subject)); if (!str::IsEmpty(str.Get())) { chunkValue.SetTextValue(PKEY_Title, str); return S_OK; } // fall through case STATE_EPUB_DATE: m_state = STATE_EPUB_CONTENT; str.Set(m_epubDoc->GetProperty(DocumentProperty::ModificationDate)); if (!str) str.Set(m_epubDoc->GetProperty(DocumentProperty::CreationDate)); if (!str::IsEmpty(str.Get())) { SYSTEMTIME systime; if (IsoDateParse(str, &systime)) { FILETIME filetime; SystemTimeToFileTime(&systime, &filetime); chunkValue.SetFileTimeValue(PKEY_ItemDate, filetime); return S_OK; } } // fall through case STATE_EPUB_CONTENT: m_state = STATE_EPUB_END; str.Set(ExtractHtmlText(m_epubDoc)); if (!str::IsEmpty(str.Get())) { chunkValue.SetTextValue(PKEY_Search_Contents, str, CHUNK_TEXT); return S_OK; } // fall through case STATE_EPUB_END: default: return FILTER_E_END_OF_CHUNKS; } }
int SyncTex::SourceToDoc(const WCHAR* srcfilename, UINT line, UINT col, UINT* page, Vec<RectI>& rects) { if (IsIndexDiscarded()) { if (RebuildIndex() != PDFSYNCERR_SUCCESS) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; } AssertCrash(this->scanner); 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; bool isUtf8 = true; char* mb_srcfilepath = str::conv::ToUtf8(srcfilepath).StealData(); TryAgainAnsi: if (!mb_srcfilepath) return PDFSYNCERR_OUTOFMEMORY; int ret = synctex_display_query(this->scanner, mb_srcfilepath, line, col); free(mb_srcfilepath); // recent SyncTeX versions encode in UTF-8 instead of ANSI if (isUtf8 && -1 == ret) { isUtf8 = false; mb_srcfilepath = str::conv::ToAnsi(srcfilepath).StealData(); goto TryAgainAnsi; } if (-1 == ret) return PDFSYNCERR_UNKNOWN_SOURCEFILE; if (0 == ret) return PDFSYNCERR_NOSYNCPOINT_FOR_LINERECORD; synctex_node_t node; int firstpage = -1; rects.Reset(); while ((node = synctex_next_result(this->scanner)) != nullptr) { if (firstpage == -1) { firstpage = synctex_node_page(node); if (firstpage <= 0 || firstpage > engine->PageCount()) continue; *page = (UINT)firstpage; } if (synctex_node_page(node) != firstpage) continue; RectD rc; rc.x = synctex_node_box_visible_h(node); rc.y = synctex_node_box_visible_v(node) - synctex_node_box_visible_height(node); rc.dx = synctex_node_box_visible_width(node), rc.dy = synctex_node_box_visible_height(node) + synctex_node_box_visible_depth(node); rects.Push(rc.Round()); } if (firstpage <= 0) return PDFSYNCERR_NOSYNCPOINT_FOR_LINERECORD; return PDFSYNCERR_SUCCESS; }