// // MainPage.xaml.cpp // Implementation of the MainPage class. // #include "pch.h" #include "MainPage.xaml.h" #include #include #include "DXGI1_3.h" #define LOOK_AHEAD 1 /* A +/- count on the pages to pre-render */ #define THUMB_PREADD 10 #define MIN_SCALE 0.5 #define SCALE_THUMB 0.1 #define PRINT_PREVIEW_SCALE 0.5 #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 #define SEARCH_FIT 672 #define VS_LARGE 1366 #define VS_SMALL 500 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; using namespace Windows::Graphics::Printing; using namespace Windows::UI::Core; //****************** 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); Application::Current->UnhandledException += ref new UnhandledExceptionEventHandler(this, &MainPage::ExceptionHandler); 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(); if (m_docPages == nullptr || m_thumbnails == nullptr || m_page_link_list == nullptr || m_text_list == nullptr || m_linkset == nullptr) throw ref new FailureException("Document allocation failed!"); SetUpDirectX(); RegisterForPrinting(); CleanUp(); #ifndef NDEBUG RecordMainThread(); #endif /* So that we can catch special loading events (e.g. open with) */ _pageLoadedHandlerToken = Loaded += ref new RoutedEventHandler(this, &MainPage::Page_Loaded); } /* You need a Direct3D device to create a Direct2D device. This gets stuff set up for Direct2D printing support */ void MainPage::SetUpDirectX() { UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; ComPtr dxgi_device; D2D1_FACTORY_OPTIONS options; ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS)); D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 }; ComPtr device; ComPtr context; #if defined(_DEBUG) options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; #endif ThrowIfFailed(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, 0, creation_flags, feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, &device, &m_featureLevel, &context)); ThrowIfFailed(device.As(&m_d3d_device)); ThrowIfFailed(context.As(&m_d3d_context)); ThrowIfFailed(m_d3d_device.As(&dxgi_device)); ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, __uuidof(ID2D1Factory1), &options, &m_d2d_factory)); ThrowIfFailed(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_wic_factory))); m_d2d_factory->CreateDevice(dxgi_device.Get(), &m_d2d_device); } /* Used during launch of application from file when application was not already running */ void MainPage::Page_Loaded(Object^ sender, RoutedEventArgs^ e) { MainPage^ rootPage = dynamic_cast(sender); if (rootPage->FileEvent != nullptr) { /* Launched with an "open with", or as default app */ if (rootPage->FileEvent->Files->Size > 0) { IStorageItem ^file = rootPage->FileEvent->Files->GetAt(0); StorageFile ^sfile = safe_cast(file); OpenDocumentPrep(sfile); } } } /* Used during launch of application from file when application was already running */ void MainPage::FromFile() { if (this->FileEvent != nullptr) { /* Launched with an "open with", or as default app */ if (this->FileEvent->Files->Size > 0) { IStorageItem ^file = this->FileEvent->Files->GetAt(0); StorageFile ^sfile = safe_cast(file); OpenDocumentPrep(sfile); } } } /// /// 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 MainPage::ExceptionHandler(Object^ sender, UnhandledExceptionEventArgs^ e) { if (!this->m_init_done) { /* Windows 8.1 has some weird issues that occur before we have even tried to open a document. For example rolling the mouse wheel throws an exception in 8.1 but not 8.0. This is clearly a windows issue. For now mark as handled and move on which seems to be fine */ e->Handled = true; } else { e->Handled = true; NotifyUser("An error was encountered", ErrorMessage); } } /* We need to clean up (Trim) the directX memory on suspension */ void MainPage::App_Suspending(Object^ sender, SuspendingEventArgs^ e) { ComPtr pDXGIDevice; ThrowIfFailed(m_d3d_device.As(&pDXGIDevice)); pDXGIDevice->Trim(); } void MainPage::ExitInvokedHandler(Windows::UI::Popups::IUICommand^ command) { } void MainPage::OKInvokedHandler(Windows::UI::Popups::IUICommand^ command) { } void MainPage::NotifyUser(String^ strMessage, int 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, &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, &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(); } void MainPage::Picker(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { /* If we are actively rendering a document to the print thread then notify the user that they will need to wait */ if (m_print_active == PRINT_ACTIVE) { int total_pages = GetPrintPageCount(); auto str1 = "Cannot open new file. Currently rendering page " + m_curr_print_count + " of " + total_pages + " for print queue"; NotifyUser(str1, StatusMessage); 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, double zoom_in) { assert(IsMainThread()); WriteableBitmap ^bmp = ref new WriteableBitmap((int)ras_size.X, (int) ras_size.Y); if (bmp == nullptr) { #ifdef _DEBUG NotifyUser("BMP UpdatePage Failed Page " + page_num, ErrorMessage); #endif return; } bmp->SetSource(ras); DocumentPage^ doc_page = ref new DocumentPage(); if (doc_page == nullptr) { #ifdef _DEBUG NotifyUser("doc_page UpdatePage Failed Page " + page_num, ErrorMessage); #endif return; } doc_page->Image = bmp; if (content_type == THUMBNAIL) { doc_page->Height = (int) (ras_size.Y / SCALE_THUMB); doc_page->Width = (int) (ras_size.X / SCALE_THUMB); } else { doc_page->Height = (int) ras_size.Y; doc_page->Width = (int) ras_size.X; } doc_page->Content = content_type; doc_page->PageZoom = zoom_in; /* 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, double page_zoom) { assert(IsMainThread()); WriteableBitmap ^bmp = ref new WriteableBitmap((int) ras_size.X, (int) ras_size.Y); if (bmp == nullptr) { #ifdef _DEBUG NotifyUser("BMP ReplaceImage Failed Page " + page_num, ErrorMessage); #endif return; } bmp->SetSource(ras); DocumentPage^ doc_page = this->m_docPages->GetAt(page_num); if (doc_page == nullptr) { #ifdef _DEBUG NotifyUser("doc_page ReplaceImage Failed Page " + page_num, ErrorMessage); #endif return; } doc_page->Image = bmp; doc_page->Height = (int) ras_size.Y; doc_page->Width = (int) ras_size.X; doc_page->PageZoom = page_zoom; } int MainPage::ComputePageSize(spatial_info_t spatial_info, int page_num, Point *render_size, float *scale_factor) { Point screenSize; Point renpageSize; Point size; try { size = mu_doc->GetPageSize(page_num); } catch (Exception ^except) { #ifdef _DEBUG NotifyUser(except->Message, ErrorMessage); #endif return E_FAILURE; } 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); renpageSize.X = (float)(size.X * scale * spatial_info.scale_factor); renpageSize.Y = (float)(size.Y * scale * spatial_info.scale_factor); *scale_factor = (float) (scale * spatial_info.scale_factor); *render_size = renpageSize; return S_ISOK; } 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 = (float) (this->ActualHeight); value.size.X = (float) (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); } } } } /* 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(unsigned int page_num) { /* See what is there now */ auto doc = this->m_docPages->GetAt(page_num); if (doc->Content == THUMBNAIL && doc->PageZoom == m_doczoom) return; if (this->m_thumbnails->Size > page_num) { m_page_update = true; auto thumb_page = this->m_thumbnails->GetAt(page_num); thumb_page->Height = (int)(thumb_page->NativeHeight * m_doczoom); thumb_page->Width = (int)(thumb_page->NativeWidth * m_doczoom); thumb_page->PageZoom = 1.0; this->m_docPages->SetAt(page_num, thumb_page); m_page_update = false; } } /* Initializes the flipview items with the thumb pages as they become available */ void MainPage::SetThumbInit(unsigned int page_num) { /* See what is there now */ auto doc = this->m_docPages->GetAt(page_num); if (doc->Content == THUMBNAIL || doc->Content == FULL_RESOLUTION) return; if (this->m_thumbnails->Size > page_num) { doc->Content = THUMBNAIL; auto thumb_page = this->m_thumbnails->GetAt(page_num); thumb_page->Height = (int)(thumb_page->NativeHeight); thumb_page->Width = (int)(thumb_page->NativeWidth); doc->Image = thumb_page->Image; doc->Height = thumb_page->Height; doc->Width = thumb_page->Width; doc->PageZoom = 1.0; } } /* 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); if (bmp_data == nullptr) { #ifdef _DEBUG NotifyUser("CreateBlank failed", ErrorMessage); #endif return; } /* Set up the memory stream */ WriteableBitmap ^bmp = ref new WriteableBitmap(width, height); InMemoryRandomAccessStream ^ras = ref new InMemoryRandomAccessStream(); if (bmp == nullptr || ras == nullptr) { #ifdef _DEBUG NotifyUser("CreateBlank failed", ErrorMessage); #endif return; } DataWriter ^dw = ref new DataWriter(ras->GetOutputStreamAt(0)); if (dw == nullptr) { #ifdef _DEBUG NotifyUser("CreateBlank failed", ErrorMessage); #endif return; } /* 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 != Windows::Foundation::AsyncStatus::Completed) { } /* And store in a the image brush */ bmp->SetSource(ras); m_BlankBmp = bmp; } void MainPage::SetFlipView() { int height = (int) (this->ActualHeight); int width = (int) (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 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(); m_ppage_num_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; m_doczoom = 1.0; m_print_active = PRINT_INACTIVE; m_curr_print_count = 1; this->xaml_PageSlider->Minimum = m_slider_min; this->xaml_PageSlider->Maximum = m_slider_max; this->xaml_PageSlider->IsEnabled = false; } /* Create the thumbnail images */ void 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; auto task_thumb = create_task([spatial_info, num_pages, this, ui, token]()-> int { spatial_info_t spatial_info_local = spatial_info; Point ras_size; Array^ bmp_data; int code; float scale_factor; /* The renderings run on a background thread */ assert(IsBackgroundThread()); spatial_info_local.scale_factor = SCALE_THUMB; for (int k = 0; k < num_pages; k++) { if (ComputePageSize(spatial_info_local, k, &ras_size, &scale_factor) == S_ISOK) { code = mu_doc->RenderPageBitmapSync(k, (int)ras_size.X, (int)ras_size.Y, scale_factor, false, true, false, { 0, 0 }, { ras_size.X, ras_size.Y }, &bmp_data); DocumentPage^ doc_page = ref new DocumentPage(); doc_page->Height = (int)(ras_size.Y / SCALE_THUMB); doc_page->Width = (int)(ras_size.X / SCALE_THUMB); doc_page->NativeHeight = (int)(ras_size.Y / SCALE_THUMB); doc_page->NativeWidth = (int)(ras_size.X / SCALE_THUMB); doc_page->TextBox = nullptr; doc_page->LinkBox = nullptr; doc_page->Content = THUMBNAIL; InMemoryRandomAccessStream ^ras = ref new InMemoryRandomAccessStream(); DataWriter ^dw = ref new DataWriter(ras->GetOutputStreamAt(0)); Prepare_bmp((int)ras_size.X, (int)ras_size.Y, dw); dw->WriteBytes(bmp_data); auto t = create_task(dw->StoreAsync()); t.wait(); /* The update with the WriteableBitmap has to take place in the UI thread. The fact that you cannot create a WriteableBitmap object execept in the UI thread is a poor design in WinRT. We will do the callback but with a low priority */ this->Dispatcher->RunAsync(CoreDispatcherPriority::Low, ref new DispatchedHandler([this, ras_size, k, ras, doc_page]() { assert(IsMainThread()); WriteableBitmap ^bmp = ref new WriteableBitmap((int)ras_size.X, (int)ras_size.Y); bmp->SetSource(ras); doc_page->Image = bmp; m_thumbnails->SetAt(k, doc_page); SetThumbInit((unsigned int) k); })); } } return num_pages; /* all done with thumbnails! */ }, token).then([this](task the_task) { /* Finish adding them, but not if we were cancelled. */ this->m_ren_status = REN_AVAILABLE; bool is_cancelled = false; try { the_task.get(); } catch (const task_canceled& e) { (void)e; // Unused parameter is_cancelled = true; } }, task_continuation_context::use_current()); } void 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 MainPage::OpenDocument(StorageFile^ file) { 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 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(); Vector^ temp_link = ref new Vector(); if (doc_page == nullptr || temp_link == nullptr) throw ref new FailureException("Document allocation failed!"); doc_page->Image = m_BlankBmp; doc_page->Height = BLANK_HEIGHT; doc_page->Width = BLANK_WIDTH; doc_page->NativeHeight = BLANK_HEIGHT; doc_page->NativeWidth = 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 */ 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; float scale_factor; if (ComputePageSize(spatial_info, k, &ras_size, &scale_factor) == S_ISOK) { auto render_task = create_task(mu_doc->RenderPageAsync(k, (int)ras_size.X, (int)ras_size.Y, true, scale_factor)); render_task.then([this, k, ras_size](InMemoryRandomAccessStream^ ras) { if (ras != nullptr) UpdatePage(k, ras, ras_size, FULL_RESOLUTION, 1.0); }, 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 MainPage::RenderRange(int curr_page) { /* Render +/- the look ahead from where we are if blank page is present */ spatial_info_t spatial_info = InitSpatial(m_doczoom); 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 || doc->PageZoom != m_doczoom) { Point ras_size; float scale_factor; if (ComputePageSize(spatial_info, k, &ras_size, &scale_factor) == S_ISOK) { double zoom = m_doczoom; auto render_task = create_task(mu_doc->RenderPageAsync(k, (int)ras_size.X, (int)ras_size.Y, true, scale_factor)); render_task.then([this, k, ras_size, zoom, curr_page](InMemoryRandomAccessStream^ ras) { if (ras != nullptr) { Point new_ras_size = ras_size; /* This is so that the scroll update will apply the zoom keeping us in-sync. And making sure that we can't exceed our limits with keyboard vs touch. I.e. any resolution changes must go through the scrollviewer. It makes the upcoming page appear to come in at its zoom level of 1.0 but it is smoothly scaled to the current scale resolution. */ new_ras_size.X = (float) (new_ras_size.X / zoom); new_ras_size.Y = (float)(new_ras_size.Y / zoom); UpdatePage(k, ras, new_ras_size, FULL_RESOLUTION, zoom); } }, 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; } if (k == curr_page) { m_curr_flipView->UpdateLayout(); UpdateZoom(); } }, 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; UpdateZoom(); } } } } 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 MainPage::FlipView_SelectionChanged(Object^ sender, SelectionChangedEventArgs^ e) { if (m_init_done && !m_page_update) { int pos = this->m_curr_flipView->SelectedIndex; if (pos >= 0) { if (xaml_PageSlider->IsEnabled) { xaml_PageSlider->Value = pos + 1; } 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->m_currpage = pos; this->RenderRange(pos); this->ReleasePages(curr_page, pos); } } } /* Slider via drag */ void MainPage::Slider_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs^ e) { Slider_Common(); } /* Slider via keyboard */ void MainPage::Slider_Key(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e) { Slider_Common(); } void MainPage::Slider_Common() { if (IsNotStandardView() || m_currpage == this->xaml_PageSlider->Value - 1) return; int newValue = (int) this->xaml_PageSlider->Value - 1; /* zero based */ if (m_init_done && this->xaml_PageSlider->IsEnabled) { this->m_curr_flipView->SelectedIndex = (int) (this->xaml_PageSlider->Value - 1); } return; } /* Search Related Code */ void MainPage::Searcher(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { ShowSearchBox(); UpdateAppBarButtonViewState(); } void 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 the squashed view */ if (this->ActualWidth < SEARCH_FIT) { NotifyUser("Please enlarge application to use search", StatusMessage); 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 MainPage::ClearTextSearch() { /* Clear out any old search result */ if (m_text_list->Size > 0) m_text_list->Clear(); } void MainPage::ShowSearchResults(int page_num, unsigned 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 = (float) (this->ActualHeight); screenSize.X = (float) (this->ActualWidth); screenSize.X *= screenScale; screenSize.Y *= screenScale; try { pageSize = mu_doc->GetPageSize(m_currpage); } catch (Exception ^except) { #ifdef _DEBUG NotifyUser(except->Message, ErrorMessage); #endif return; } scale = fitPageToScreen(pageSize, screenSize); auto doc_page = this->m_docPages->GetAt(old_page); /* Construct our list of rectangles */ for (unsigned int k = 0; k < box_count; k++) { RectList^ rect_item = ref new RectList(); if (rect_item == nullptr) { break; } auto curr_box = mu_doc->GetTextSearch(k); rect_item->Color = m_textcolor; rect_item->Height = (int) (curr_box->LowerRight.Y - curr_box->UpperLeft.Y); rect_item->Width = (int) (curr_box->LowerRight.X - curr_box->UpperLeft.X); rect_item->X = (int) (curr_box->UpperLeft.X * scale.X); rect_item->Y = (int) (curr_box->UpperLeft.Y * scale.Y); rect_item->Width = (int)((double)rect_item->Width * scale.X); rect_item->Height = (int)((double)rect_item->Height * scale.Y); 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 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 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 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 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 MainPage::SearchProgress(IAsyncOperationWithProgress^ operation, double status) { xaml_Progress->Value = status; } void 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, m_num_pages); 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, (unsigned int) box_count); } } }, task_continuation_context::use_current()); } /* This is here to handle when we rotate or go into the snapview mode */ void MainPage::GridSizeChanged() { int height = (int) (this->ActualHeight); int width = (int) (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) xaml_WebView->Height = xaml_OutsideGrid->ActualHeight; UpdateThumbSizes(); 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(); /* And force a rerender */ for (int k = m_currpage - LOOK_AHEAD; k <= m_currpage + LOOK_AHEAD; k++) { if (k >= 0 && k < m_num_pages) { DocumentPage ^doc = this->m_docPages->GetAt(k); doc->Content = OLD_RESOLUTION; } } this->m_curr_flipView->SelectedIndex = this->m_currpage; FlipView_SelectionChanged(nullptr, nullptr); } } void MainPage::UpdatePreRenderedPageSizes() { if (m_num_pages > 0) { for (int k = m_currpage - LOOK_AHEAD; k <= m_currpage + LOOK_AHEAD; k++) { if (k >= 0 && k < m_num_pages && k != m_currpage) { DocumentPage ^doc = this->m_docPages->GetAt(k); doc->Content = OLD_RESOLUTION; int curr_height = doc->Height; int curr_width = doc->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); doc->Height = (int) (curr_height * m_doczoom / min_scale); doc->Width = (int) (curr_width * m_doczoom / min_scale); } } } } void MainPage::UpdateThumbSizes() { /* Reset the thumbview scaling values */ if (m_num_pages > 0) { int num_items = m_docPages->Size; for (int i = 0; i < num_items; i++) { DocumentPage ^thumb_page = m_docPages->GetAt(i); if (thumb_page != nullptr && thumb_page->Image != nullptr && thumb_page->Content == THUMBNAIL) { int curr_height = thumb_page->NativeHeight; int curr_width = thumb_page->NativeWidth; 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 = (int)(curr_height / min_scale); thumb_page->Width = (int)(curr_width / min_scale); } } } }; /* Link related code */ void 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 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 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 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); unsigned int num_links = mu_doc->ComputeLinks(m_currpage); if (num_links == 0) return; Point screenSize; Point pageSize; Point scale; screenSize.Y = (float) (this->ActualHeight); screenSize.X = (float)(this->ActualWidth); screenSize.X *= screenScale; screenSize.Y *= screenScale; try { pageSize = mu_doc->GetPageSize(m_currpage); } catch (Exception ^except) { #ifdef _DEBUG NotifyUser(except->Message, ErrorMessage); #endif return; } scale = fitPageToScreen(pageSize, screenSize); /* Create a new RectList collection */ auto link_list = ref new Platform::Collections::Vector(); if (link_list == nullptr) return; /* Now add the rects */ for (unsigned 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(); if (rect_item == nullptr) break; rect_item->Color = m_linkcolor; rect_item->Height = (int) (curr_link->LowerRight.Y - curr_link->UpperLeft.Y); rect_item->Width = (int) (curr_link->LowerRight.X - curr_link->UpperLeft.X); rect_item->X = (int) (curr_link->UpperLeft.X * scale.X); rect_item->Y = (int) (curr_link->UpperLeft.Y * scale.Y); rect_item->Width = (int)((double)rect_item->Width * scale.X); rect_item->Height = (int)((double)rect_item->Height * scale.Y); 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 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 = (int) (_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 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) { unsigned int size_content = mu_doc->ComputeContents(); /* Bring up the content now */ for (unsigned 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 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 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; } 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; } } /* Need to handle resizing of app bar to make sure everything fits */ void 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 */ int temp = (int) (this->ActualWidth); if (this->ActualWidth < SEARCH_FIT && m_insearch) ShowSearchBox(); UpdateAppBarButtonViewState(); /* This is needed to make sure we get the proper state during start-up. The object has to be visible to set the state. So that is the way we start */ if (!m_insearch && FindBox->Visibility == Windows::UI::Xaml::Visibility::Visible) { FindBox->Visibility = Windows::UI::Xaml::Visibility::Collapsed; PrevSearch->Visibility = Windows::UI::Xaml::Visibility::Collapsed; NextSearch->Visibility = Windows::UI::Xaml::Visibility::Collapsed; } } String^ MainPage::GetVisualState() { String^ visualstate = "FullScreenLandscape"; int width = (int) (this->ActualWidth); int height = (int) (this->ActualHeight); if (width < VS_SMALL) { visualstate = "Snapped"; } else if (width < VS_LARGE) { if (width < height) { visualstate = "FullScreenPortrait"; } else { visualstate = "Snapped"; } } return visualstate; } void MainPage::UpdateAppBarButtonViewState() { String ^viewState = GetVisualState(); 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); } /* Scroll viewer scale changes. If first time to this page, then we essentially have our scroll setting set at 1.0. */ void 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); double new_scroll_zoom = scrollviewer->ZoomFactor; /* Check if we are already at this resolution with this page */ if (new_scroll_zoom == doc_page->PageZoom) return; if (!e->IsIntermediate) { int page = m_currpage; m_doczoom = new_scroll_zoom; if (m_doczoom > ZOOM_MAX) { m_doczoom = ZOOM_MAX; } if (m_doczoom < ZOOM_MIN) { m_doczoom = ZOOM_MIN; } /* Render at new resolution. */ spatial_info_t spatial_info = InitSpatial(m_doczoom); Point ras_size; float scale_factor; if (ComputePageSize(spatial_info, page, &ras_size, &scale_factor) == S_ISOK) { doc_page->PageZoom = m_doczoom; auto render_task = create_task(mu_doc->RenderPageAsync(page, (int)ras_size.X, (int)ras_size.Y, true, scale_factor)); render_task.then([this, page, ras_size, scrollviewer](InMemoryRandomAccessStream^ ras) { if (ras != nullptr) ReplaceImage(page, ras, ras_size, m_doczoom); }, 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 MainPage::ZoomInPress(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (!m_init_done || IsNotStandardView()) return; NonTouchZoom(ZOOM_IN); } void MainPage::ZoomOutPress(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { if (!m_init_done || IsNotStandardView()) return; NonTouchZoom(ZOOM_OUT); } void MainPage::NonTouchZoom(int zoom) { auto doc_page = this->m_docPages->GetAt(m_currpage); double curr_zoom = doc_page->PageZoom; ScrollViewer^ scrollviewer; FlipViewItem^ item = safe_cast (m_curr_flipView->ContainerFromIndex(m_currpage)); auto item2 = m_curr_flipView->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; 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; /* It all needs to be driven by the scroll viewer otherwise we end up out of sync */ Platform::Object^ obj_zoom = (float)curr_zoom; Platform::IBox^ box_zoom; box_zoom = safe_cast^>(obj_zoom); scrollviewer->ChangeView(nullptr, nullptr, box_zoom, false); } /* Adjust the page scrollviewer to the current zoom level */ void MainPage::UpdateZoom() { ScrollViewer^ scrollviewer; FlipViewItem^ item = safe_cast (m_curr_flipView->ContainerFromIndex(m_currpage)); auto item2 = m_curr_flipView->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; float curr_zoom = scrollviewer->ZoomFactor; Platform::Object^ obj_zoom = (float)m_doczoom; Platform::IBox^ box_zoom; box_zoom = safe_cast^>(obj_zoom); scrollviewer->ChangeView(nullptr, nullptr, box_zoom, false); } /* 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 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 MainPage::IsNotStandardView() { return (this->xaml_ListView->Opacity == 1.0 || xaml_WebView->Visibility == Windows::UI::Xaml::Visibility::Visible); } /* The following code is for print support. */ void MainPage::RegisterForPrinting() { m_print_manager = Windows::Graphics::Printing::PrintManager::GetForCurrentView(); m_print_manager->PrintTaskRequested += ref new TypedEventHandler(this, &MainPage::SetPrintTask); } void MainPage::SetPrintTask(PrintManager^ sender, PrintTaskRequestedEventArgs^ args) { PrintTaskSourceRequestedHandler^ source_handler = ref new PrintTaskSourceRequestedHandler([this](PrintTaskSourceRequestedArgs^ args)-> void{ Microsoft::WRL::ComPtr document_source; ThrowIfFailed(Microsoft::WRL::MakeAndInitialize(&document_source, reinterpret_cast(this))); IPrintDocumentSource^ objSource(reinterpret_cast(document_source.Get())); args->SetSource(objSource); }); PrintTask^ print_task = args->Request->CreatePrintTask(L"MuPDF WinRT Print", source_handler); /* Call backs so that we know when we are all done with the printing */ print_task->Progressing += ref new TypedEventHandler(this, &MainPage::PrintProgress); print_task->Completed += ref new TypedEventHandler(this, &MainPage::PrintCompleted); m_print_active = PRINT_ACTIVE; m_curr_print_count = 0; PrintTaskOptionDetails^ printDetailedOptions = PrintTaskOptionDetails::GetFromPrintTaskOptions(print_task->Options); // Some standard printer options printDetailedOptions->DisplayedOptions->Clear(); printDetailedOptions->DisplayedOptions->Append(Windows::Graphics::Printing::StandardPrintTaskOptions::MediaSize); printDetailedOptions->DisplayedOptions->Append(Windows::Graphics::Printing::StandardPrintTaskOptions::Copies); // Our custom options PrintCustomItemListOptionDetails^ resolution = printDetailedOptions->CreateItemListOption("resolution", "Render Resolution"); resolution->AddItem("sres96", "96dpi"); resolution->AddItem("sres150", "150 dpi"); resolution->AddItem("sres300", "300 dpi"); resolution->AddItem("sres600", "600 dpi"); resolution->TrySetValue("sres600"); m_printresolution = 600; printDetailedOptions->DisplayedOptions->Append("resolution"); PrintCustomItemListOptionDetails^ location = printDetailedOptions->CreateItemListOption("location", "Location"); location->AddItem("sCenter", "Center"); location->AddItem("sTopleft", "Top Left"); // Add the custom option to the option list. printDetailedOptions->DisplayedOptions->Append("location"); location->TrySetValue("sCenter"); m_centerprint = true; print_task->Options->MediaSize = PrintMediaSize::NorthAmericaLetter; PrintCustomItemListOptionDetails^ pageFormat = printDetailedOptions->CreateItemListOption(L"PageRange", L"Page Range"); pageFormat->AddItem(L"PrintAll", L"Print all"); pageFormat->AddItem(L"PrintRange", L"Print Range"); printDetailedOptions->DisplayedOptions->Append(L"PageRange"); PrintCustomTextOptionDetails^ pageRangeEdit = printDetailedOptions->CreateTextOption(L"PageRangeEdit", L"Range"); printDetailedOptions->OptionChanged += ref new TypedEventHandler(this, &MainPage::PrintOptionsChanged); } int MainPage::GetPrintPageCount() { if (m_ppage_num_list.size() > 0) return (int) m_ppage_num_list.size(); else return m_num_pages; } void MainPage::PrintOptionsChanged(PrintTaskOptionDetails^ sender, PrintTaskOptionChangedEventArgs^ args) { bool force_reset = false; if (args->OptionId == nullptr) return; String^ optionId = safe_cast(args->OptionId); if (optionId == "resolution") { IPrintOptionDetails^ resolution = sender->Options->Lookup(optionId); String^ resolutionValue = safe_cast(resolution->Value); if (resolutionValue == "sres96") { m_printresolution = 96; } else if (resolutionValue == "sres150") { m_printresolution = 150; } else if (resolutionValue == "sres300") { m_printresolution = 300; } else if(resolutionValue == "sres600") { m_printresolution = 600; } } /* Need to update preview with a change of this one */ if (optionId == "location") { IPrintOptionDetails^ scaling = sender->Options->Lookup(optionId); String^ scaleValue = safe_cast(scaling->Value); if (scaleValue == "sCenter") { m_centerprint = true; } if (scaleValue == "sTopleft") { m_centerprint = false; } force_reset = true; } if (optionId == L"PageRange") { IPrintOptionDetails^ pagerange = sender->Options->Lookup(optionId); String^ pageRangeValue = pagerange->Value->ToString(); if(pageRangeValue == L"PrintRange") { sender->DisplayedOptions->Append(L"PageRangeEdit"); m_pageRangeEditVisible = true; } else { RemovePageRangeEdit(sender); } RefreshPreview(); } if (optionId == L"PageRangeEdit") { IPrintOptionDetails^ pagerange = sender->Options->Lookup(optionId); std::wregex rangePattern(L"^\\s*\\d+\\s*(\\-\\s*\\d+\\s*)?(\\,\\s*\\d+\\s*(\\-\\s*\\d+\\s*)?)*$"); std::wstring pageRangeValue(pagerange->Value->ToString()->Data()); if(!std::regex_match(pageRangeValue.begin(), pageRangeValue.end(), rangePattern)) { pagerange->ErrorText = L"Invalid Page Range (eg: 1-3, 5)"; } else { pagerange->ErrorText = L""; try { GetPagesInRange(pagerange->Value->ToString()); } catch(PageRangeException* rangeException) { pagerange->ErrorText = ref new String(rangeException->get_DisplayMessage().data()); delete rangeException; } force_reset = true; } } if (force_reset) { RefreshPreview(); } } void MainPage::SplitString(String^ string, wchar_t delimiter, std::vector& words) { std::wistringstream iss(string->Data()); std::wstring part; while(std::getline(iss, part, delimiter)) { words.push_back(part); }; } void MainPage::GetPagesInRange(String^ pagerange) { std::vector vector_range; SplitString(pagerange, ',', vector_range); m_ppage_num_list.clear(); for(std::vector::iterator it = vector_range.begin(); it != vector_range.end(); ++ it) { int intervalPos = static_cast((*it).find('-')); if( intervalPos != -1) { int start = _wtoi((*it).substr(0, intervalPos).data()); int end = _wtoi((*it).substr(intervalPos + 1, (*it).length() - intervalPos - 1).data()); if ((start < 1) || (end > static_cast(m_num_pages)) || (start >= end)) { std::wstring message(L"Invalid page(s) in range "); message.append(std::to_wstring(start)); message.append(L" - "); message.append(std::to_wstring(end)); throw new PageRangeException(message); } for(int intervalPage=start; intervalPage <= end; ++intervalPage) { m_ppage_num_list.push_back(intervalPage); } } else { int pageNr = _wtoi((*it).data()); std::wstring message(L"Invalid page "); if (pageNr < 1) { message.append(std::to_wstring(pageNr)); throw new PageRangeException(message); } if (pageNr > static_cast(m_num_pages)) { message.append(std::to_wstring(pageNr)); throw new PageRangeException(message); } m_ppage_num_list.push_back(pageNr); } } std::sort(m_ppage_num_list.begin(), m_ppage_num_list.end(), std::less()); std::unique(m_ppage_num_list.begin(), m_ppage_num_list.end()); } void MainPage::RemovePageRangeEdit(PrintTaskOptionDetails^ printTaskOptionDetails) { if (m_pageRangeEditVisible) { unsigned int index; if(printTaskOptionDetails->DisplayedOptions->IndexOf(ref new String(L"PageRangeEdit"), &index)) { printTaskOptionDetails->DisplayedOptions->RemoveAt(index); } m_pageRangeEditVisible = false; } } void MainPage::CreatePrintControl(_In_ IPrintDocumentPackageTarget* docPackageTarget, _In_ D2D1_PRINT_CONTROL_PROPERTIES* printControlProperties) { m_d2d_printcontrol = nullptr; ThrowIfFailed(m_d2d_device->CreatePrintControl(m_wic_factory.Get(), docPackageTarget, printControlProperties, &m_d2d_printcontrol)); } void MainPage::DrawPreviewSurface(float width, float height, float scale_in, D2D1_RECT_F contentBox, uint32 page_num, IPrintPreviewDxgiPackageTarget* previewTarget) { int dpi = 96; int index_page_num = page_num - 1; int ren_page_num = index_page_num; if (m_ppage_num_list.size() > 0) ren_page_num = m_ppage_num_list[page_num - 1] - 1; /* This goes on in a background thread. Hence is non-blocking for UI */ assert(IsBackgroundThread()); /* Set up all the DirectX stuff */ CD3D11_TEXTURE2D_DESC textureDesc(DXGI_FORMAT_B8G8R8A8_UNORM, static_cast(ceil(width * dpi / 96)), static_cast(ceil(height * dpi / 96)), 1, 1, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE); ComPtr texture; ThrowIfFailed(m_d3d_device->CreateTexture2D(&textureDesc, nullptr, &texture)); ComPtr dxgi_surface; ThrowIfFailed(texture.As(&dxgi_surface)); // Create a new D2D device context for rendering the preview surface. D2D // device contexts are stateful, and hence a unique device context must be // used on each thread. ComPtr d2d_context; ThrowIfFailed(m_d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d_context)); // Update DPI for preview surface as well. d2d_context->SetDpi(96, 96); D2D1_BITMAP_PROPERTIES1 bitmap_properties = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)); // Create surface bitmap on which page content is drawn. ComPtr d2d_surfacebitmap; ThrowIfFailed(d2d_context->CreateBitmapFromDxgiSurface(dxgi_surface.Get(), &bitmap_properties, &d2d_surfacebitmap)); d2d_context->SetTarget(d2d_surfacebitmap.Get()); /* Figure out all the sizing */ spatial_info_t spatial_info; spatial_info.scale_factor = 1.0; spatial_info.size.X = width; spatial_info.size.Y = height; Point ras_size; float scale_factor; if (ComputePageSize(spatial_info, ren_page_num, &ras_size, &scale_factor) != S_ISOK) return; ras_size.X = ceil(ras_size.X); ras_size.Y = ceil(ras_size.Y); Array^ bmp_data; int code = mu_doc->RenderPageBitmapSync(ren_page_num, (int) ras_size.X, (int)ras_size.Y, scale_factor, true, false, false, { 0, 0 }, { ras_size.X, ras_size.Y }, &bmp_data); if (bmp_data == nullptr) return; D2D1_SIZE_U bit_map_rect; bit_map_rect.width = (UINT32) (ras_size.X); bit_map_rect.height = (UINT32) (ras_size.Y); D2D1_BITMAP_PROPERTIES1 bitmap_prop = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)); ID2D1Bitmap1 *bit_map; ThrowIfFailed(d2d_context->CreateBitmap(bit_map_rect, &(bmp_data[0]), (UINT32) (ras_size.X * 4), &bitmap_prop, &bit_map)); D2D1_SIZE_F size = bit_map->GetSize(); /* Handle centering */ float y_offset = 0; float x_offset = 0; if (m_centerprint) { y_offset = (float) ((height - size.height) / 2.0); x_offset = (float) ((width - size.width) / 2.0); } d2d_context->BeginDraw(); d2d_context->DrawBitmap(bit_map, D2D1::RectF(x_offset, y_offset, size.width + x_offset, size.height + y_offset)); ThrowIfFailed(d2d_context->EndDraw()); ThrowIfFailed(previewTarget->DrawPage(page_num, dxgi_surface.Get(), (float) dpi, (float) dpi)); } HRESULT MainPage::ClosePrintControl() { return (m_d2d_printcontrol == nullptr) ? S_OK : m_d2d_printcontrol->Close(); } /* To support high resolution printing, we tile renderings at the maxbitmap size allowed with DirectX for this particular device. e.g the low end surface will have a smaller maxbitmap size compared to a laptop or desktop. */ void MainPage::PrintPage(uint32 page_num, D2D1_RECT_F image_area, D2D1_SIZE_F page_area, float device_dpi, IStream* print_ticket) { int dpi = m_printresolution; int index_page_num = page_num - 1; int ren_page_num = index_page_num; bool tile = false; Point tile_count; D2D1_SIZE_U bit_map_rect; Array^ bmp_data; if (index_page_num == 0) { this->Dispatcher->RunAsync(CoreDispatcherPriority::Low, ref new DispatchedHandler([this]() { xaml_PrintStack->Visibility = Windows::UI::Xaml::Visibility::Visible; })); } /* Windoze seems to hand me a bogus dpi. Need to follow up on this */ device_dpi = 96; if (m_ppage_num_list.size() > 0) ren_page_num = m_ppage_num_list[page_num - 1] - 1; /* This goes on in a background thread. Hence is non-blocking for UI */ assert(IsBackgroundThread()); /* Print command list set up */ ComPtr d2d_context; ThrowIfFailed(m_d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d_context)); /* This should let us work in pixel dimensions but after much testing it clearly has some issues. May investigate this further later. */ //d2d_context->SetUnitMode(D2D1_UNIT_MODE_PIXELS); ComPtr clist; ThrowIfFailed(d2d_context->CreateCommandList(&clist)); d2d_context->SetTarget(clist.Get()); /* Width and height here are at 96 dpi */ float width = image_area.right - image_area.left; float height = image_area.bottom - image_area.top; /* MuPDF native resolution is 72dpi */ spatial_info_t spatial_info; spatial_info.scale_factor = 1.0; spatial_info.size.X = (width / device_dpi) * (m_printresolution); spatial_info.size.Y = (height /device_dpi) * (m_printresolution); Point ras_size; float scale_factor; if (ComputePageSize(spatial_info, ren_page_num, &ras_size, &scale_factor) != S_ISOK) return; ras_size.X = ceil(ras_size.X); ras_size.Y = ceil(ras_size.Y); /* Determine if we need to do any tiling */ int tile_size = d2d_context->GetMaximumBitmapSize(); tile_count.Y = 1; if (ras_size.X > tile_size) { tile = true; tile_count.X = (float) ceil((float) ras_size.X / (float) tile_size); bit_map_rect.width = (UINT32) (tile_size); } else { tile_count.X = 1; bit_map_rect.width = (UINT32) (ras_size.X); } if (ras_size.Y > tile_size) { tile = true; tile_count.Y = (float) ceil((float) ras_size.Y / (float) tile_size); bit_map_rect.height = (UINT32) (tile_size); } else { tile_count.Y = 1; bit_map_rect.height = (UINT32) (ras_size.Y); } /* Adjust for centering in media page */ float y_offset = 0; float x_offset = 0; if (m_centerprint) { y_offset = (float)round(((page_area.height - (ras_size.Y) * device_dpi / m_printresolution) / 2.0)); x_offset = (float)round(((page_area.width - (ras_size.X) * device_dpi / m_printresolution) / 2.0)); } D2D1_BITMAP_PROPERTIES1 bitmap_prop = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), (float) m_printresolution, (float) m_printresolution); ID2D1Bitmap1 *bit_map = NULL; Point top_left, top_left_dip; Point bottom_right, bottom_right_dip; /* Initialize X location */ top_left.X = 0; bottom_right.X = (float) bit_map_rect.width; d2d_context->BeginDraw(); /* Useful for debugging */ //d2d_context->Clear(D2D1::ColorF(D2D1::ColorF::Coral)); int total_tile = (int) (tile_count.X * tile_count.Y); for (int x = 0; x < tile_count.X; x++) { /* Reset Y location */ top_left.Y = 0; bottom_right.Y = (float) bit_map_rect.height; for (int y = 0; y < tile_count.Y; y++) { int code = mu_doc->RenderPageBitmapSync(ren_page_num, (int)bit_map_rect.width, (int)bit_map_rect.height, scale_factor, true, false, tile, top_left, bottom_right, &bmp_data); if (bmp_data == nullptr || code != 0) break; ThrowIfFailed(d2d_context->CreateBitmap(bit_map_rect, &(bmp_data[0]), (UINT32)(bit_map_rect.width * 4), &bitmap_prop, &bit_map)); // This is where D2D1_UNIT_MODE_PIXELS fails to work. Essentially, // DirectX ends up clipping based upon the origin still in DIPS // instead of actual pixel positions. top_left_dip.X = (float)((double) top_left.X * (double)device_dpi / (double)m_printresolution + x_offset - 0.5); top_left_dip.Y = (float)((double)top_left.Y * (double)device_dpi / (double)m_printresolution + y_offset - 0.5); bottom_right_dip.X = (float)((double)bottom_right.X * (double)device_dpi / (double)m_printresolution + x_offset + 0.5); bottom_right_dip.Y = (float)((double)bottom_right.Y * (double)device_dpi / (double)m_printresolution + y_offset + 0.5); d2d_context->DrawBitmap(bit_map, D2D1::RectF(top_left_dip.X, top_left_dip.Y, bottom_right_dip.X, bottom_right_dip.Y)); bit_map->Release(); /* Increment Y location */ top_left.Y += (float) bit_map_rect.height; bottom_right.Y += (float) bit_map_rect.height; PrintProgressTile(total_tile); } /* Increment X location */ top_left.X += (float) bit_map_rect.width; bottom_right.X += (float) bit_map_rect.width; } ThrowIfFailed(d2d_context->EndDraw()); ThrowIfFailed(clist->Close()); ThrowIfFailed(m_d2d_printcontrol->AddPage(clist.Get(), page_area, print_ticket)); } void MainPage::RefreshPreview() { PrintPages *p_struct = (PrintPages*) m_print_struct; p_struct->ResetPreview(); } /* This reference is needed so that we can reset preview when changes occur on options */ void MainPage::SetPrintTarget(void *print_struct) { m_print_struct = print_struct; } void MainPage::PrintProgress(PrintTask^ sender, PrintTaskProgressingEventArgs^ args) { assert(IsBackgroundThread()); this->m_curr_print_count = args->DocumentPageCount; /* Update the progress bar if it is still active */ this->Dispatcher->RunAsync(CoreDispatcherPriority::Low, ref new DispatchedHandler([this]() { if (this->xaml_PrintStack->Visibility != Windows::UI::Xaml::Visibility::Collapsed) { xaml_PrintProgress->Value = 100.0 * (double)m_curr_print_count / (double)GetPrintPageCount(); } })); } void MainPage::PrintProgressTile(int total_tiles) { assert(IsBackgroundThread()); double step_size = 100.0 / ((double)GetPrintPageCount() * (double)total_tiles); /* Update the progress bar if it is still active. The tiling of each page can be slow on the surface if the resolution is high, hence the need for this feedback */ this->Dispatcher->RunAsync(CoreDispatcherPriority::Low, ref new DispatchedHandler([this, step_size]() { if (this->xaml_PrintStack->Visibility != Windows::UI::Xaml::Visibility::Collapsed) { xaml_PrintProgress->Value += step_size; } })); } void MainPage::PrintCompleted(PrintTask^ sender, PrintTaskCompletedEventArgs^ args) { assert(IsBackgroundThread()); m_print_active = PRINT_INACTIVE; this->Dispatcher->RunAsync(CoreDispatcherPriority::Low, ref new DispatchedHandler([this]() { xaml_PrintStack->Visibility = Windows::UI::Xaml::Visibility::Collapsed; xaml_PrintProgress->Value = 0; })); } void mupdf_cpp::MainPage::HideProgress(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { xaml_PrintStack->Visibility = Windows::UI::Xaml::Visibility::Collapsed; }