// // MainPage.xaml.cpp // Implementation of the MainPage class. // #include "pch.h" #include "MainPage.xaml.h" #define LOOK_AHEAD 0 /* A +/- count on the pages to pre-render */ #define THUMB_PREADD 10 #define MIN_SCALE 0.5 #define SCALE_THUMB 0.1 #define BLANK_WIDTH 17 #define BLANK_HEIGHT 22 #define KEYBOARD_ZOOM_STEP 0.25 #define ZOOM_MAX 4 #define ZOOM_MIN 0.25 #define KEY_PLUS 0xbb #define KEY_MINUS 0xbd #define ZOOM_IN 0 #define ZOOM_OUT 1 static float screenScale = 1; using namespace mupdf_cpp; using namespace Windows::Foundation; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Controls::Primitives; using namespace Windows::UI::Xaml::Data; using namespace Windows::UI::Xaml::Media; using namespace Windows::UI::Xaml::Navigation; using namespace Windows::Graphics::Display; //****************** Added ***************** using namespace Windows::Storage::Pickers; using namespace Windows::Devices::Enumeration; using namespace concurrency; using namespace Windows::Graphics::Imaging; //****************** End Add **************** #ifndef NDEBUG unsigned int _mainThreadId = 0U; #ifdef __cplusplus extern "C" { #endif // The IsMainThread function returns true if the current thread is the app's main thread and false otherwise. bool IsMainThread() { return (_mainThreadId == GetCurrentThreadId()); } // The IsBackgroundThread function returns false if the current thread is the app's main thread and true otherwise. bool IsBackgroundThread() { return (_mainThreadId != GetCurrentThreadId()); } // The RecordMainThread function registers the main thread ID for use by the IsMainThread and IsBackgroundThread functions. void RecordMainThread() { _mainThreadId = GetCurrentThreadId(); } #ifdef __cplusplus } #endif #endif /* not NDEBUG */ MainPage::MainPage() { InitializeComponent(); Application::Current->Suspending += ref new SuspendingEventHandler(this, &MainPage::App_Suspending); m_textcolor="#402572AC"; m_linkcolor="#40AC7225"; mu_doc = nullptr; m_docPages = ref new Platform::Collections::Vector(); m_thumbnails = ref new Platform::Collections::Vector(); m_page_link_list = ref new Platform::Collections::Vector^>(); m_text_list = ref new Platform::Collections::Vector(); m_linkset = ref new Platform::Collections::Vector(); CleanUp(); RecordMainThread(); } /// /// Invoked when this page is about to be displayed in a Frame. /// /// Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page. void MainPage::OnNavigatedTo(NavigationEventArgs^ e) { (void) e; // Unused parameter } void mupdf_cpp::MainPage::App_Suspending(Object^ sender, SuspendingEventArgs^ e) { } void mupdf_cpp::MainPage::ExitInvokedHandler(Windows::UI::Popups::IUICommand^ command) { } void mupdf_cpp::MainPage::OKInvokedHandler(Windows::UI::Popups::IUICommand^ command) { } void mupdf_cpp::MainPage::NotifyUser(String^ strMessage, NotifyType_t type) { MessageDialog^ msg = ref new MessageDialog(strMessage); UICommand^ ExitCommand = nullptr; UICommand^ OKCommand = nullptr; switch (type) { case StatusMessage: OKCommand = ref new UICommand("OK", ref new UICommandInvokedHandler(this, &mupdf_cpp::MainPage::OKInvokedHandler)); msg->Commands->Append(OKCommand); /// Set the command that will be invoked by default msg->DefaultCommandIndex = 0; // Set the command to be invoked when escape is pressed msg->CancelCommandIndex = 1; break; case ErrorMessage: ExitCommand = ref new UICommand("Exit", ref new UICommandInvokedHandler(this, &mupdf_cpp::MainPage::ExitInvokedHandler)); msg->Commands->Append(ExitCommand); /// Set the command that will be invoked by default msg->DefaultCommandIndex = 0; // Set the command to be invoked when escape is pressed msg->CancelCommandIndex = 1; break; default: break; } // Show the message dialog msg->ShowAsync(); } bool mupdf_cpp::MainPage::EnsureUnsnapped() { // FilePicker APIs will not work if the application is in a snapped state. // If an app wants to show a FilePicker while snapped, it must attempt to unsnap first bool unsnapped = (ApplicationView::Value != ApplicationViewState::Snapped || ApplicationView::TryUnsnap()); if (!unsnapped) { NotifyUser("Cannot unsnap the application", StatusMessage); } return unsnapped; } void mupdf_cpp::MainPage::Picker(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (!EnsureUnsnapped()) return; FileOpenPicker^ openPicker = ref new FileOpenPicker(); openPicker->ViewMode = PickerViewMode::List; openPicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary; openPicker->FileTypeFilter->Append(".pdf"); openPicker->FileTypeFilter->Append(".xps"); openPicker->FileTypeFilter->Append(".cbz"); openPicker->FileTypeFilter->Append(".oxps"); create_task(openPicker->PickSingleFileAsync()).then([this](StorageFile^ file) { if (file) { this->OpenDocumentPrep(file); } else { /* Nothing selected */ } }); } /* Set the page with the new raster information */ void MainPage::UpdatePage(int page_num, InMemoryRandomAccessStream^ ras, Point ras_size, Page_Content_t content_type) { assert(IsMainThread()); WriteableBitmap ^bmp = ref new WriteableBitmap(ras_size.X, ras_size.Y); bmp->SetSource(ras); DocumentPage^ doc_page = ref new DocumentPage(); doc_page->Image = bmp; if (content_type == THUMBNAIL) { doc_page->Height = ras_size.Y / SCALE_THUMB; doc_page->Width = ras_size.X / SCALE_THUMB; } else { doc_page->Height = ras_size.Y; doc_page->Width = ras_size.X; } doc_page->Content = content_type; /* We do not want flipview change notification to occur for ourselves */ m_page_update = true; this->m_docPages->SetAt(page_num, doc_page); m_page_update = false; } /* Set the page with the new raster information but only the image data */ void MainPage::ReplaceImage(int page_num, InMemoryRandomAccessStream^ ras, Point ras_size) { assert(IsMainThread()); WriteableBitmap ^bmp = ref new WriteableBitmap(ras_size.X, ras_size.Y); bmp->SetSource(ras); DocumentPage^ doc_page = this->m_docPages->GetAt(page_num); doc_page->Image = bmp; doc_page->Height = ras_size.Y; doc_page->Width = ras_size.X; } Point MainPage::ComputePageSize(spatial_info_t spatial_info, int page_num) { Point screenSize; Point pageSize; Point size = mu_doc->GetPageSize(page_num); screenSize = spatial_info.size; screenSize.Y *= screenScale; screenSize.X *= screenScale; float hscale = screenSize.X / size.X; float vscale = screenSize.Y / size.Y; float scale = min(hscale, vscale); pageSize.X = size.X * scale * spatial_info.scale_factor; pageSize.Y = size.Y * scale * spatial_info.scale_factor; return pageSize; } static Point fitPageToScreen(Point page, Point screen) { Point pageSize; float hscale = screen.X / page.X; float vscale = screen.Y / page.Y; float scale = min(hscale, vscale); pageSize.X = floorf(page.X * scale) / page.X; pageSize.Y = floorf(page.Y * scale) / page.Y; return pageSize; } spatial_info_t MainPage::InitSpatial(double scale) { spatial_info_t value; value.size.Y = this->ActualHeight; value.size.X = this->ActualWidth; value.scale_factor = scale; return value; } void Prepare_bmp(int width, int height, DataWriter ^dw) { int row_size = width * 4; int bmp_size = row_size * height + 54; dw->WriteString("BM"); dw->ByteOrder = ByteOrder::LittleEndian; dw->WriteInt32(bmp_size); dw->WriteInt16(0); dw->WriteInt16(0); dw->WriteInt32(54); dw->WriteInt32(40); dw->WriteInt32(width); dw->WriteInt32(height); dw->WriteInt16(1); dw->WriteInt16(32); dw->WriteInt32(0); dw->WriteInt32(row_size * height); dw->WriteInt32(2835); dw->WriteInt32(2835); dw->WriteInt32(0); dw->WriteInt32(0); } void MainPage::ReleasePages(int old_page, int new_page) { if (old_page == new_page) return; /* To keep from having memory issue reset the page back to the thumb if we are done rendering the thumbnails */ for (int k = old_page - LOOK_AHEAD; k <= old_page + LOOK_AHEAD; k++) { if (k < new_page - LOOK_AHEAD || k > new_page + LOOK_AHEAD) { if (k >= 0 && k < this->m_num_pages) { SetThumb(k, true); } } } } /* Return this page from a full res image to the thumb image or only set to thumb if it has not already been set */ void MainPage::SetThumb(int page_num, bool replace) { /* See what is there now */ auto doc = this->m_docPages->GetAt(page_num); if (doc->Content == THUMBNAIL) return; if (doc->Content == FULL_RESOLUTION && replace == false) return; if (this->m_thumbnails->Size > page_num) { m_page_update = true; this->m_docPages->SetAt(page_num, this->m_thumbnails->GetAt(page_num)); m_page_update = false; } } /* Create white image for us to use as place holder in large document for flip view filling instead of the thumbnail image */ void MainPage::CreateBlank(int width, int height) { Array^ bmp_data = ref new Array(height * 4 * width); /* Set up the memory stream */ WriteableBitmap ^bmp = ref new WriteableBitmap(width, height); InMemoryRandomAccessStream ^ras = ref new InMemoryRandomAccessStream(); DataWriter ^dw = ref new DataWriter(ras->GetOutputStreamAt(0)); /* Go ahead and write our header data into the memory stream */ Prepare_bmp(width, height, dw); /* Set the data to all white */ memset(bmp_data->Data, 255, height * 4 * width); /* Write the data */ dw->WriteBytes(bmp_data); DataWriterStoreOperation^ result = dw->StoreAsync(); /* Block on the Async call */ while(result->Status != AsyncStatus::Completed) { } /* And store in a the image brush */ bmp->SetSource(ras); m_BlankBmp = bmp; } void mupdf_cpp::MainPage::SetFlipView() { int height = this->ActualHeight; int width = this->ActualWidth; CreateBlank(BLANK_WIDTH, BLANK_HEIGHT); /* Set the current flip view mode */ if (height > width) this->m_curr_flipView = xaml_vert_flipView; else this->m_curr_flipView = xaml_horiz_flipView; } /* Clean up everything as we are opening a new document after having another one open */ void mupdf_cpp::MainPage::CleanUp() { m_init_done = false; /* Remove current pages in the flipviews */ if (m_docPages != nullptr && m_docPages->Size > 0) m_docPages->Clear(); if (m_thumbnails != nullptr && m_thumbnails->Size > 0) m_thumbnails->Clear(); /* With the ref counting this should not leak */ if (m_page_link_list != nullptr && m_page_link_list->Size > 0) m_page_link_list->Clear(); if (m_text_list->Size > 0) m_text_list->Clear(); if (m_linkset != nullptr && m_linkset->Size > 0) m_linkset->Clear(); if (this->mu_doc != nullptr) mu_doc->CleanUp(); mu_doc = ref new mudocument(); if (mu_doc == nullptr) throw ref new FailureException("Document allocation failed!"); this->m_curr_flipView = nullptr; m_currpage = -1; m_file_open = false; m_slider_min = 0; m_slider_max = 0; m_memory_use = 0; m_insearch = false; m_search_active = false; m_sliderchange = false; m_flip_from_searchlink = false; m_num_pages = -1; m_search_rect_count = 0; m_ren_status = REN_AVAILABLE; m_links_on = false; m_rectlist_page = -1; m_Progress = 0.0; this->xaml_PageSlider->Minimum = m_slider_min; this->xaml_PageSlider->Maximum = m_slider_max; this->xaml_PageSlider->IsEnabled = false; } /* Create the thumbnail images */ void mupdf_cpp::MainPage::RenderThumbs() { spatial_info_t spatial_info = this->InitSpatial(1); int num_pages = this->m_num_pages; cancellation_token_source cts; auto token = cts.get_token(); m_ThumbCancel = cts; auto ui = task_continuation_context::use_current(); this->m_ren_status = REN_THUMBS; Vector^ thumbnails = m_thumbnails; auto task_thumb = create_task([spatial_info, num_pages, thumbnails, this, ui, token]()-> int { spatial_info_t spatial_info_local = spatial_info; spatial_info_local.scale_factor = SCALE_THUMB; for (int k = 0; k < num_pages; k++) { Point ras_size = ComputePageSize(spatial_info_local, k); auto task2 = create_task(mu_doc->RenderPageAsync(k, ras_size.X, ras_size.Y, false)); task2.then([this, k, thumbnails, ras_size](InMemoryRandomAccessStream^ ras) { assert(IsMainThread()); WriteableBitmap ^bmp = ref new WriteableBitmap(ras_size.X, ras_size.Y); bmp->SetSource(ras); DocumentPage^ doc_page = ref new DocumentPage(); doc_page->Image = bmp; doc_page->Height = ras_size.Y / SCALE_THUMB; doc_page->Width = ras_size.X / SCALE_THUMB; doc_page->Content = THUMBNAIL; doc_page->TextBox = nullptr; doc_page->LinkBox = nullptr; if (m_init_done) { m_thumbnails->SetAt(k, doc_page); /* This avoids out of order returns from task */ if (k < THUMB_PREADD) /* Flip view gets overwhelmed if I don't do this */ SetThumb(k, false); } }, ui).then([this] (task t) { try { t.get(); } catch(Platform::InvalidArgumentException^ e) { //TODO handle error. } }, token); //end task chain */ /* If cancelled then save the last one as the continuation will not have occured. */ if (is_task_cancellation_requested()) { cancel_current_task(); } } return num_pages; /* all done with thumbnails! */ }, token).then([this](task the_task) { /* Finish adding them, but not if we were cancelled. */ bool is_cancelled = false; try { the_task.get(); } catch (const task_canceled& e) { (void) e; // Unused parameter is_cancelled = true; } if (!is_cancelled) { for (int k = THUMB_PREADD; k < m_num_pages; k++) SetThumb(k, false); } this->m_ren_status = REN_AVAILABLE; }, task_continuation_context::use_current()); } void mupdf_cpp::MainPage::OpenDocumentPrep(StorageFile^ file) { if (this->m_num_pages != -1) { m_init_done = false; /* Set the index to the start of the document */ this->xaml_vert_flipView->SelectedIndex = 0; this->xaml_horiz_flipView->SelectedIndex = 0; /* If the thumbnail thread is running then we need to end that first */ RenderingStatus_t *ren_status = &m_ren_status; cancellation_token_source *ThumbCancel = &m_ThumbCancel; /* Create a task to wait until the renderer is available, then clean up then open */ auto t = create_task([ren_status, ThumbCancel]()->int { if (*ren_status == REN_THUMBS) ThumbCancel->cancel(); while (*ren_status != REN_AVAILABLE) { } return 0; }).then([this](task the_task) { CleanUp(); return 0; }, task_continuation_context::use_current()).then([this, file](task the_task) { OpenDocument(file); }, task_continuation_context::use_current()); } else { OpenDocument(file); } } void mupdf_cpp::MainPage::OpenDocument(StorageFile^ file) { String^ path = file->Path; const wchar_t *w = path->Data(); int cb = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); char* name = new char[cb]; WideCharToMultiByte(CP_UTF8, 0, w ,-1 ,name ,cb ,nullptr, nullptr); char *ext = strrchr(name, '.'); this->SetFlipView(); /* Open document and when open, push on */ auto open_task = create_task(mu_doc->OpenFileAsync(file)); open_task.then([this](int code) -> int { assert(IsMainThread()); if (code != S_ISOK) { return code; } /* We need to check if password is required */ if (mu_doc->RequiresPassword()) { xaml_PasswordStack->Visibility = Windows::UI::Xaml::Visibility::Visible; return E_NEEDPASSWORD; } else { xaml_PasswordStack->Visibility = Windows::UI::Xaml::Visibility::Collapsed; return S_ISOK; } }).then([this](int code)->int { assert(IsMainThread()); if (code == S_ISOK) InitialRender(); return code; }, task_continuation_context::use_current()).then([this](int code) { if (code == S_ISOK) RenderThumbs(); else { if (code != E_NEEDPASSWORD) { NotifyUser("Sorry, an issue was encountered in opening file", StatusMessage); } } }, task_continuation_context::use_current()); } void mupdf_cpp::MainPage::InitialRender() { assert(IsMainThread()); m_num_pages = mu_doc->GetNumPages(); if ((m_currpage) >= m_num_pages) { m_currpage = m_num_pages - 1; } else if (m_currpage < 0) { m_currpage = 0; } /* Initialize all the flipvew items with blanks and the thumbnails. */ for (int k = 0; k < m_num_pages; k++) { /* Blank pages */ DocumentPage^ doc_page = ref new DocumentPage(); doc_page->Image = m_BlankBmp; doc_page->Height = BLANK_HEIGHT; doc_page->Width = BLANK_WIDTH; doc_page->Content = DUMMY; doc_page->TextBox = nullptr; doc_page->LinkBox = nullptr; m_docPages->Append(doc_page); m_thumbnails->Append(doc_page); /* Create empty lists for our links and specify that they have not been computed for these pages */ Vector^ temp_link = ref new Vector(); m_page_link_list->Append(temp_link); m_linkset->Append(false); } this->xaml_horiz_flipView->ItemsSource = m_docPages; this->xaml_vert_flipView->ItemsSource = m_docPages; /* Do the first few pages, then start the thumbs */ spatial_info_t spatial_info = InitSpatial(1); for (int k = 0; k < LOOK_AHEAD + 2; k++) { if (m_num_pages > k ) { Point ras_size = ComputePageSize(spatial_info, k); auto render_task = create_task(mu_doc->RenderPageAsync(k, ras_size.X, ras_size.Y, true)); render_task.then([this, k, ras_size] (InMemoryRandomAccessStream^ ras) { UpdatePage(k, ras, ras_size, FULL_RESOLUTION); }, task_continuation_context::use_current()); } } /* Update the slider settings, if more than one page */ if (m_num_pages > 1) { this->xaml_PageSlider->Maximum = m_num_pages; this->xaml_PageSlider->Minimum = 1; this->xaml_PageSlider->IsEnabled = true; } else { this->xaml_PageSlider->Maximum = 0; this->xaml_PageSlider->Minimum = 0; this->xaml_PageSlider->IsEnabled = false; } /* All done with initial pages */ this->m_init_done = true; } void mupdf_cpp::MainPage::RenderRange(int curr_page) { /* Render +/- the look ahead from where we are if blank page is present */ spatial_info_t spatial_info = InitSpatial(1); bool curr_page_rendered = true; int range = LOOK_AHEAD; assert(IsMainThread()); if (m_flip_from_searchlink) range = 0; for (int k = curr_page - LOOK_AHEAD; k <= curr_page + LOOK_AHEAD; k++) { if (k >= 0 && k < m_num_pages) { /* Check if page is already rendered */ auto doc = this->m_docPages->GetAt(k); if (doc->Content != FULL_RESOLUTION) { Point ras_size = ComputePageSize(spatial_info, k); auto render_task = create_task(mu_doc->RenderPageAsync(k, ras_size.X, ras_size.Y, true)); render_task.then([this, k, ras_size] (InMemoryRandomAccessStream^ ras) { UpdatePage(k, ras, ras_size, FULL_RESOLUTION); }, task_continuation_context::use_current()).then([this, k, curr_page]() { if (k == curr_page && this->m_links_on) AddLinkCanvas(); if (k == curr_page && this->m_text_list->Size > 0 && m_flip_from_searchlink) { AddTextCanvas(); m_flip_from_searchlink = false; } },task_continuation_context::use_current()); } else { /* We did not need to render the curr_page, so add links below if needed. Otherwise, we need to wait for the task above to complete before we add the links. */ if (k == curr_page) curr_page_rendered = false; } } } m_currpage = curr_page; if (this->m_links_on && !curr_page_rendered) AddLinkCanvas(); if (this->m_text_list->Size > 0 && !curr_page_rendered && m_flip_from_searchlink) { AddTextCanvas(); m_flip_from_searchlink = false; } } void mupdf_cpp::MainPage::Slider_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e) { int newValue = (int) this->xaml_PageSlider->Value - 1; /* zero based */ if (IsNotStandardView()) return; if (m_update_flip) { m_update_flip = false; return; } if (m_init_done && this->xaml_PageSlider->IsEnabled) { /* Make sure to clear any text search */ auto doc_old = this->m_docPages->GetAt(m_currpage); doc_old->TextBox = nullptr; auto doc = this->m_docPages->GetAt(newValue); if (doc->Content != FULL_RESOLUTION) { spatial_info_t spatial_info = InitSpatial(1); Point ras_size = ComputePageSize(spatial_info, newValue); auto render_task = create_task(mu_doc->RenderPageAsync(newValue, ras_size.X, ras_size.Y, true)); render_task.then([this, newValue, ras_size] (InMemoryRandomAccessStream^ ras) { UpdatePage(newValue, ras, ras_size, FULL_RESOLUTION); this->m_currpage = newValue; m_sliderchange = true; this->m_curr_flipView->SelectedIndex = newValue; }, task_continuation_context::use_current()); } } } void mupdf_cpp::MainPage::FlipView_SelectionChanged(Object^ sender, SelectionChangedEventArgs^ e) { if (m_init_done && !m_page_update) { int pos = this->m_curr_flipView->SelectedIndex; if (pos >= 0) { m_update_flip = true; if (xaml_PageSlider->IsEnabled) { xaml_PageSlider->Value = pos; } if (m_sliderchange) { m_sliderchange = false; return; } else { /* Make sure to clear any text search */ auto doc_old = this->m_docPages->GetAt(m_currpage); doc_old->TextBox = nullptr; } /* Get the current page */ int curr_page = this->m_currpage; this->RenderRange(pos); this->ReleasePages(curr_page, pos); } } } /* Search Related Code */ void mupdf_cpp::MainPage::Searcher(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { ShowSearchBox(); UpdateAppBarButtonViewState(); } void mupdf_cpp::MainPage::ShowSearchBox() { /* Update the app bar so that we can do the search */ StackPanel^ leftPanel = (StackPanel^) this->TopAppBar->FindName("LeftPanel"); if (leftPanel != nullptr && m_insearch) { m_insearch = false; FindBox->Visibility = Windows::UI::Xaml::Visibility::Collapsed; PrevSearch->Visibility = Windows::UI::Xaml::Visibility::Collapsed; NextSearch->Visibility = Windows::UI::Xaml::Visibility::Collapsed; } else if (leftPanel != nullptr && !m_insearch) { /* Search is not going to work in snapped view for now to simplify UI in this cramped case. So see if we can get out of snapped mode. */ if (!EnsureUnsnapped()) return; m_insearch = true; FindBox->Visibility = Windows::UI::Xaml::Visibility::Visible; PrevSearch->Visibility = Windows::UI::Xaml::Visibility::Visible; NextSearch->Visibility = Windows::UI::Xaml::Visibility::Visible; } } void mupdf_cpp::MainPage::ClearTextSearch() { /* Clear out any old search result */ if (m_text_list->Size > 0) m_text_list->Clear(); } void mupdf_cpp::MainPage::ShowSearchResults(int page_num, int box_count) { int old_page = this->m_currpage; int new_page = page_num; ClearTextSearch(); /* Compute any scalings */ Point screenSize; Point pageSize; Point scale; screenSize.Y = this->ActualHeight; screenSize.X = this->ActualWidth; screenSize.X *= screenScale; screenSize.Y *= screenScale; pageSize = mu_doc->GetPageSize(m_currpage); scale = fitPageToScreen(pageSize, screenSize); auto doc_page = this->m_docPages->GetAt(old_page); /* Construct our list of rectangles */ for (int k = 0; k < box_count; k++) { RectList^ rect_item = ref new RectList(); auto curr_box = mu_doc->GetTextSearch(k); rect_item->Color = m_textcolor; rect_item->Height = curr_box->LowerRight.Y - curr_box->UpperLeft.Y; rect_item->Width = curr_box->LowerRight.X - curr_box->UpperLeft.X; rect_item->X = curr_box->UpperLeft.X * scale.X; rect_item->Y = curr_box->UpperLeft.Y * scale.Y; rect_item->Width *= (scale.X * doc_page->Zoom); rect_item->Height *= (scale.Y * doc_page->Zoom); rect_item->Index = k.ToString(); m_text_list->Append(rect_item); } /* Make sure the current page has its text results cleared */ doc_page->TextBox = nullptr; /* Go ahead and set our doc item to this in the vertical and horizontal view */ m_searchpage = new_page; m_flip_from_searchlink = true; if (old_page == new_page) { FlipView_SelectionChanged(nullptr, nullptr); } else { this->m_curr_flipView->SelectedIndex = new_page; } return; } void mupdf_cpp::MainPage::SearchNext(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (IsNotStandardView()) return; StackPanel^ leftPanel = (StackPanel^) this->TopAppBar->FindName("LeftPanel"); TextBox^ findBox = (TextBox^) leftPanel->FindName("FindBox"); String^ textToFind = findBox->Text; if (this->m_search_active == false && textToFind != nullptr) SearchInDirection(1, textToFind); } void mupdf_cpp::MainPage::SearchPrev(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (IsNotStandardView()) return; StackPanel^ leftPanel = (StackPanel^) this->TopAppBar->FindName("LeftPanel"); TextBox^ findBox = (TextBox^) leftPanel->FindName("FindBox"); String^ textToFind = findBox->Text; if (this->m_search_active == false && textToFind != nullptr) SearchInDirection(-1, textToFind); } void mupdf_cpp::MainPage::CancelSearch(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { m_searchcts.cancel(); xaml_ProgressStack->Visibility = Windows::UI::Xaml::Visibility::Collapsed; this->m_search_active = false; } void mupdf_cpp::MainPage::AddTextCanvas() { /* Go ahead and set our doc item to this in the vertical and horizontal view */ auto doc_page = this->m_docPages->GetAt(m_currpage); assert(doc_page->Content == FULL_RESOLUTION); if (doc_page->Content == FULL_RESOLUTION) // We should not be doing links for thumbnails { doc_page->TextBox = m_text_list; } this->m_search_active = false; } void mupdf_cpp::MainPage::SearchProgress(IAsyncOperationWithProgress^ operation, double status) { xaml_Progress->Value = status; } void mupdf_cpp::MainPage::SearchInDirection(int dir, String^ textToFind) { cancellation_token_source cts; auto token = cts.get_token(); m_searchcts = cts; int pos = m_currpage; int start; if (m_searchpage == pos) start = pos + dir; else start = pos; if (start < 0) return; if (start > this->m_num_pages - 1) return; this->m_search_active = true; ProgressBar^ my_xaml_Progress = (ProgressBar^) (this->FindName("xaml_Progress")); xaml_ProgressStack->Visibility = Windows::UI::Xaml::Visibility::Visible; auto temp = mu_doc->SearchDocumentWithProgressAsync(textToFind, dir, start); temp->Progress = ref new AsyncOperationProgressHandler(this, &MainPage::SearchProgress); auto search_task = create_task(temp, token); /* Do the continuation on the ui thread */ auto con_task = search_task.then([this, textToFind](int page_num) { xaml_ProgressStack->Visibility = Windows::UI::Xaml::Visibility::Collapsed; if (page_num == TEXT_NOT_FOUND) { auto str1 = "\"" + textToFind + "\" Was Not Found In The Search"; NotifyUser(str1, StatusMessage); this->m_search_active = false; } else { int box_count = mu_doc->TextSearchCount(); if (box_count > 0) { this->ShowSearchResults(page_num, box_count); } } }, task_continuation_context::use_current()); } /* This is here to handle when we rotate or go into the snapview mode */ void mupdf_cpp::MainPage::GridSizeChanged() { int height = this->ActualHeight; int width = this->ActualWidth; FlipView^ old_flip = m_curr_flipView; if (TopAppBar1->IsOpen) { UpdateAppBarButtonViewState(); } if (height > width) { m_curr_flipView = this->xaml_vert_flipView; this->xaml_zoomCanvas->Height = height; this->xaml_zoomCanvas->Width = width; this->m_curr_flipView->Height = height; this->m_curr_flipView->Width = width; xaml_vert_flipView->IsEnabled = true; xaml_vert_flipView->Opacity = 1; xaml_horiz_flipView->IsEnabled = false; xaml_horiz_flipView->Opacity = 0; } else { m_curr_flipView = this->xaml_horiz_flipView; this->xaml_zoomCanvas->Height = height; this->xaml_zoomCanvas->Width = width; this->m_curr_flipView->Height = height; this->m_curr_flipView->Width = width; xaml_horiz_flipView->IsEnabled = true; xaml_horiz_flipView->Opacity = 1; xaml_vert_flipView->IsEnabled = false; xaml_vert_flipView->Opacity = 0; } if (xaml_WebView->Visibility == Windows::UI::Xaml::Visibility::Visible) { int height = xaml_OutsideGrid->ActualHeight; int height_app = TopAppBar1->ActualHeight; xaml_WebView->Height = height - height_app; } UpDatePageSizes(); if (m_num_pages > 0 && old_flip != m_curr_flipView && old_flip != nullptr) { /* If links are on or off, we need to invalidate */ ClearLinks(); InvalidateLinks(); auto doc = this->m_docPages->GetAt(m_currpage); doc->Content = OLD_RESOLUTION; /* To force a rerender */ this->m_curr_flipView->SelectedIndex = this->m_currpage; FlipView_SelectionChanged(nullptr, nullptr); } } void mupdf_cpp::MainPage::UpDatePageSizes() { /* Reset the thumb view scaling value */ if (m_num_pages > 0) { int num_items = m_thumbnails->Size; for (int i = 0; i < num_items; i++) { DocumentPage ^thumb_page = m_thumbnails->GetAt(i); if (thumb_page != nullptr && thumb_page->Image != nullptr) { int curr_height = thumb_page->Height; int curr_width = thumb_page->Width; double scale_x = (double) curr_height / (double) this->xaml_zoomCanvas->Height; double scale_y = (double) curr_width / (double) this->xaml_zoomCanvas->Width; double min_scale = max(scale_x, scale_y); thumb_page->Height = curr_height / min_scale; thumb_page->Width = curr_width / min_scale; } } } }; /* Link related code */ void mupdf_cpp::MainPage::Linker(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { m_links_on = !m_links_on; if (!m_init_done || IsNotStandardView()) return; if (m_links_on) AddLinkCanvas(); else ClearLinks(); } void mupdf_cpp::MainPage::ClearLinks() { /* Make sure surrounding render pages lose their links */ for (int k = m_currpage - LOOK_AHEAD; k <= m_currpage + LOOK_AHEAD; k++) { if (k >= 0 && k < m_num_pages) { auto doc_page = this->m_docPages->GetAt(k); if (doc_page->Content == FULL_RESOLUTION) { doc_page->LinkBox = nullptr; } } } } void mupdf_cpp::MainPage::InvalidateLinks() { for (int k = 0; k < m_num_pages; k++) m_linkset->SetAt(k, false); } /* Add in the link rects. If we have not already computed them then do that now */ void mupdf_cpp::MainPage::AddLinkCanvas() { /* See if the link object for this page has already been computed */ int link_page = m_linkset->GetAt(m_currpage); auto doc_page = this->m_docPages->GetAt(m_currpage); if (!link_page) { m_linkset->SetAt(m_currpage, true); int num_links = mu_doc->ComputeLinks(m_currpage); if (num_links == 0) return; Point screenSize; Point pageSize; Point scale; screenSize.Y = this->ActualHeight; screenSize.X = this->ActualWidth; screenSize.X *= screenScale; screenSize.Y *= screenScale; pageSize = mu_doc->GetPageSize(m_currpage); scale = fitPageToScreen(pageSize, screenSize); /* Create a new RectList collection */ auto link_list = ref new Platform::Collections::Vector(); /* Now add the rects */ for (int k = 0; k < num_links; k++) { auto curr_link = mu_doc->GetLink(k); if (curr_link->Type != NOT_SET) { RectList^ rect_item = ref new RectList(); rect_item->Color = m_linkcolor; rect_item->Height = curr_link->LowerRight.Y - curr_link->UpperLeft.Y; rect_item->Width = curr_link->LowerRight.X - curr_link->UpperLeft.X; rect_item->X = curr_link->UpperLeft.X * scale.X; rect_item->Y = curr_link->UpperLeft.Y * scale.Y; rect_item->Width *= (scale.X * doc_page->Zoom); rect_item->Height *= (scale.Y * doc_page->Zoom); rect_item->Type = curr_link->Type; rect_item->Urilink = curr_link->Uri; rect_item->PageNum = curr_link->PageNum; rect_item->Index = k.ToString(); link_list->Append(rect_item); } } /* Now set it in our list of links */ m_page_link_list->SetAt(m_currpage, link_list); } /* Go ahead and set our doc item to this in the vertical and horizontal view */ if (doc_page->LinkBox == nullptr) { if (doc_page->Content == FULL_RESOLUTION) // We should not be doing links for thumbnails { doc_page->LinkBox = m_page_link_list->GetAt(m_currpage); } } } /* A link was tapped */ void mupdf_cpp::MainPage::LinkTapped(Platform::Object^ sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs^ e) { Rectangle^ rect = safe_cast(e->OriginalSource); String^ str_index = safe_cast(rect->Tag); int index = _wtof(str_index->Data()); if (index >= 0 && index < m_num_pages) { auto link_list = m_page_link_list->GetAt(m_currpage); auto link = link_list->GetAt(index); if (link->Type == LINK_GOTO) { this->m_curr_flipView->SelectedIndex = link->PageNum; } else if (link->Type == LINK_URI) { // Set the option to show a warning auto launchOptions = ref new Windows::System::LauncherOptions(); launchOptions->TreatAsUntrusted = true; // Launch the URI with a warning prompt concurrency::task launchUriOperation(Windows::System::Launcher::LaunchUriAsync(link->Urilink, launchOptions)); launchUriOperation.then([](bool success) { if (success) { // URI launched } else { // URI launch failed } }); } } } /* Bring up the contents */ void mupdf_cpp::MainPage::ContentDisplay(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (this->m_num_pages < 0) return; if (IsNotStandardView() && !this->xaml_ListView->IsEnabled) return; if (this->xaml_ListView->IsEnabled) { this->xaml_ListView->Opacity = 0.0; this->xaml_ListView->IsEnabled = false; this->m_curr_flipView->Opacity = 1.0; this->m_curr_flipView->IsEnabled = true; this->xaml_PageSlider->IsEnabled = true; } else { if (xaml_ListView->Items->Size == 0) { int size_content = mu_doc->ComputeContents(); /* Bring up the content now */ for (int k = 0; k < size_content; k++) { ContentItem^ item = mu_doc->GetContent(k); this->xaml_ListView->Items->Append(item); } if (size_content > 0) { this->xaml_ListView->Opacity = 1.0; this->xaml_ListView->IsEnabled = true; this->m_curr_flipView->Opacity = 0.0; this->m_curr_flipView->IsEnabled = false; this->xaml_PageSlider->IsEnabled = false; } } else { this->xaml_ListView->Opacity = 1.0; this->xaml_ListView->IsEnabled = true; this->m_curr_flipView->Opacity = 0.0; this->m_curr_flipView->IsEnabled = false; this->xaml_PageSlider->IsEnabled = false; } } } void mupdf_cpp::MainPage::ContentSelected(Platform::Object^ sender, Windows::UI::Xaml::Controls::ItemClickEventArgs^ e) { ContentItem^ b = safe_cast(e->ClickedItem); int newpage = b->Page; if (newpage > -1 && newpage < this->m_num_pages) { this->xaml_ListView->Opacity = 0.0; this->xaml_ListView->IsEnabled = false; this->m_curr_flipView->Opacity = 1.0; this->m_curr_flipView->IsEnabled = true; this->xaml_PageSlider->IsEnabled = true; int old_page = this->m_currpage; this->m_curr_flipView->SelectedIndex = newpage; this->m_currpage = newpage; } } void mupdf_cpp::MainPage::Reflower(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (this->m_num_pages < 0) return; if (xaml_WebView->Visibility == Windows::UI::Xaml::Visibility::Visible) { /* Go back to flip view */ xaml_WebView->Visibility = Windows::UI::Xaml::Visibility::Collapsed; this->xaml_MainGrid->Opacity = 1.0; this->m_curr_flipView->IsEnabled = true; this->xaml_PageSlider->IsEnabled = true; xaml_WebView->Visibility = Windows::UI::Xaml::Visibility::Collapsed; xaml_WebView->Opacity = 0.0; } else if (this->m_curr_flipView->IsEnabled) { String^ html_string = mu_doc->ComputeHTML(this->m_currpage); xaml_WebView->Visibility = Windows::UI::Xaml::Visibility::Visible; this->xaml_MainGrid->Opacity = 0.0; this->m_curr_flipView->IsEnabled = false; this->xaml_PageSlider->IsEnabled = false; this->xaml_WebView->NavigateToString(html_string); this->xaml_WebView->Height = this->ActualHeight - 2 * this->BottomAppBar->ActualHeight; /* Check if thumb rendering is done. If not then restart */ } } /* Need to handle resizing of app bar to make sure everything fits */ void mupdf_cpp::MainPage::topAppBar_Loaded(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { /* Remove search box in snapped view as we don't have the room for it */ if (ApplicationView::Value == ApplicationViewState::Snapped && m_insearch) ShowSearchBox(); UpdateAppBarButtonViewState(); } void mupdf_cpp::MainPage::UpdateAppBarButtonViewState() { String ^viewState = Windows::UI::ViewManagement::ApplicationView::Value.ToString(); VisualStateManager::GoToState(Search, viewState, true); VisualStateManager::GoToState(Contents, viewState, true); VisualStateManager::GoToState(Links, viewState, true); VisualStateManager::GoToState(Reflow, viewState, true); VisualStateManager::GoToState(ZoomIn, viewState, true); VisualStateManager::GoToState(ZoomOut, viewState, true); VisualStateManager::GoToState(PrevSearch, viewState, true); VisualStateManager::GoToState(NextSearch, viewState, true); } /* Manipulation zooming with touch input */ void mupdf_cpp::MainPage::ScrollChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::ScrollViewerViewChangedEventArgs^ e) { ScrollViewer^ scrollviewer = safe_cast (sender); auto doc_page = this->m_docPages->GetAt(m_currpage); if (scrollviewer->ZoomFactor == doc_page->Zoom) return; if (!e->IsIntermediate) { doc_page->Zoom = scrollviewer->ZoomFactor; int page = m_currpage; /* Render at new resolution */ spatial_info_t spatial_info = InitSpatial(doc_page->Zoom); Point ras_size = ComputePageSize(spatial_info, page); /* Go ahead and create display list if we dont have one for this page */ auto render_task = create_task(mu_doc->RenderPageAsync(page, ras_size.X, ras_size.Y, true)); render_task.then([this, page, ras_size] (InMemoryRandomAccessStream^ ras) { ReplaceImage(page, ras, ras_size); }, task_continuation_context::use_current()); } } /* Needed to find scrollviewer child from template of flipview item */ Windows::UI::Xaml::FrameworkElement^ FindVisualChildByName(DependencyObject^ obj, String^ name) { FrameworkElement^ ret; if (obj == nullptr) return nullptr; int numChildren = VisualTreeHelper::GetChildrenCount(obj); for (int i = 0; i < numChildren; i++) { auto objChild = VisualTreeHelper::GetChild(obj, i); auto child = safe_cast(objChild); if (child != nullptr && child->Name == name) { return child; } ret = FindVisualChildByName(objChild, name); if (ret != nullptr) break; } return ret; } void mupdf_cpp::MainPage::ZoomInPress(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (!m_init_done || IsNotStandardView()) return; NonTouchZoom(ZOOM_IN); } void mupdf_cpp::MainPage::ZoomOutPress(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (!m_init_done || IsNotStandardView()) return; NonTouchZoom(ZOOM_OUT); } void MainPage::NonTouchZoom(int zoom) { ScrollViewer^ scrollviewer; FlipViewItem^ item = safe_cast (m_curr_flipView->ItemContainerGenerator->ContainerFromIndex(m_currpage)); auto item2 = m_curr_flipView->ItemContainerGenerator->ContainerFromIndex(m_currpage); /* We don't know which one so check for both */ ScrollViewer^ t1 = safe_cast (FindVisualChildByName(item2, "xaml_ScrollView_v")); ScrollViewer^ t2 = safe_cast (FindVisualChildByName(item2, "xaml_ScrollView_h")); if (t1 != nullptr) scrollviewer = t1; else scrollviewer = t2; if (scrollviewer == nullptr) return; double curr_zoom = scrollviewer->ZoomFactor; if (zoom == ZOOM_IN) { curr_zoom = curr_zoom + KEYBOARD_ZOOM_STEP; if (curr_zoom > ZOOM_MAX) curr_zoom = ZOOM_MAX; } else if (zoom == ZOOM_OUT) { curr_zoom = curr_zoom - KEYBOARD_ZOOM_STEP; if (curr_zoom < ZOOM_MIN) curr_zoom = ZOOM_MIN; } else return; scrollviewer->ZoomToFactor(curr_zoom); } /* Zoom in and out for keyboard only case. */ void MainPage::OnKeyDown(KeyRoutedEventArgs^ e) { if (!m_init_done || IsNotStandardView()) return; long val = (long) (e->Key); if (val == KEY_PLUS) NonTouchZoom(ZOOM_IN); else if (val == KEY_MINUS) NonTouchZoom(ZOOM_OUT); else return; } void mupdf_cpp::MainPage::PasswordOK(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { /* If password checks out then go ahead and start rendering */ if (mu_doc->ApplyPassword(xaml_password->Password)) { xaml_password->Password = nullptr; xaml_PasswordStack->Visibility = Windows::UI::Xaml::Visibility::Collapsed; InitialRender(); RenderThumbs(); } else NotifyUser("Incorrect Password", StatusMessage); } /* So that we know if we are in a standard view case and not in reflow, or * content type */ bool mupdf_cpp::MainPage::IsNotStandardView() { return (this->xaml_ListView->Opacity == 1.0 || xaml_WebView->Visibility == Windows::UI::Xaml::Visibility::Visible); }