本文章是由機器翻譯。

Windows 8.1

在 Windows 市集應用程式中轉譯 PDF 內容

Sridhar Poduri

下载代码示例

PDF 作為文檔存儲和存檔格式是公認的在今天的世界。PDF 格式存儲檔,如圖書、 技術手冊、 使用者指南、 報告和更多。它可以讓被消耗從多個平臺,只要支援的 PDF 檢視器是可用的文檔。查看 PDF 文檔時很大程度上不成問題支援 PDF 內容的渲染仍然是一種挑戰,尤其對於 Windows 應用商店應用程式開發人員。與 Windows 8.1,Microsoft 引入新的 Api,可緩和呈現 PDF 內容在 Windows 應用商店的應用程式的過程。

在本文中,我會看的不同的方式來做這種呈現方式。第一,我會集中是 Windows 運行庫 (WinRT) 的一部分,對你通過 JavaScript、 C#、Visual Basic的.NET 和 c + + 可訪問的 Api。然後我會著重讓 c + + 開發人員直接在基於 DirectX 的繪圖圖面上的 PDF 內容呈現的本機 Api。

Windows 運行時 Api 為 PDF 呈現的

Windows 8.1 Windows 運行時為包括一個新的命名空間,Windows.Data.Pdf,其中包含的新的運行時類和結構,在 Windows 應用商店的應用程式中支援 PDF 呈現。在此部分中,我將討論群組成 Windows.Data.Pdf 命名空間,為打開 PDF 文檔、 使用的各類處理密碼保護文檔,呈現自訂呈現過程和更多。

打開 PDF 文檔以程式設計方式打開的 PDF 文檔是從 PdfDocument 運行庫類中調用靜態方法 LoadFromFileAsync 一樣容易。此類是使用 PDF 文檔的初始進入點。LoadFromFileAsync 方法接受一個 StorageFile 物件,並開始載入 PdfDocument 的過程。載入 PDF 文檔有時可以很長時間,因此,API 返回的非同步作業。在非同步作業完成後,您有有效的 PdfDocument 物件的實例,如下所示:

// Obtain a StorageFile object by prompting the user to select a .pdf file
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.List;
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
openPicker.FileTypeFilter.Add(".pdf");
StorageFile pdfFile = await openPicker.PickSingleFileAsync();
// Load a PdfDocument from the selected file
create_task(PdfDocument::LoadFromFileAsync(pdfFile)).then(
  [this](PdfDocument^ pdfDoc)
{
  // Handle opened Pdf document here.
});

除了 LoadFromFileAsync 方法中,PdfDocument 類還包含一個説明器靜態方法從流物件中創建一個 PdfDocument 實例。如果您已持有到 PDF 文檔作為一個 RandomAccessStream 實例的引用,你可以簡單地將流物件傳遞給 LoadFromStreamAsync 方法。根據您的方案,您可以選擇使用 LoadFromFileAsync 或 LoadFromStreamAsync 方法來創建一個 PdfDocument 物件實例。一旦你有了一個有效的 PdfDocument 實例,您可以訪問文檔中的單個頁面。

處理 Password-Protected PDF 檔 PDF 藍本­發言用來存儲各種資訊,如信貸-­卡的語句或其他機密資料。一些出版商不想讓使用者不受限制地訪問這些類型的文檔和保護他們的密碼。存取權限僅授予應用程式的二進位檔案包含的密碼。PdfDocument 運行時類的 LoadFromFileAsync 和 LoadFromStreamAsync 方法包含接受密碼通過一個字串參數的方法的重載的版本:

// Load a PdfDocument that's protected by a password
// Load a PdfDocument from the selected file
create_task(PdfDocument::LoadFromFileAsync(
  pdfFile, "password")).then([this](PdfDocument^ pdfDoc){
  Handle opened Pdf document here.
});

如果您嘗試載入受密碼保護的文檔,而無需指定一個密碼,LoadFromFileAsync 和 LoadFromStreamAsync 方法將引發異常。

訪問的 PDF 文檔中的頁面創建 PdfDocument 物件的實例後,Count 屬性將返回的頁面數目在 PDF 文檔中。你可以簡單地迴圈從 0"計數 — — 1"範圍,以獲得對單個 PDF 頁面的訪問。每個頁面是類型的 PdfPage 運行時類。PdfPage 運行時類具有一個名為 PreparePageAsync,開始籌備進程的 PDF 頁面呈現方法。頁面編寫涉及分析和載入頁面,初始化為適當處理的圖形的路徑和形狀,對處理正確的字體集,所以呈現文本初始化 DirectWrite Direct2D 資源。如果你不在開始要呈現的 PDF 頁面之前調用 PreparePageAsync,渲染程序呼叫 PreparePageAsync 以隱式。然而,您應該調用 PreparePageAsync 和已準備好呈現頁而不是讓準備頁面的呈現過程。準備前的實際呈現頁面的實際呈現過程中節省時間和是一種很好的優化技術。

呈現 PDF 頁面 PdfPage 物件有準備,一旦它們可以呈現。渲染 API PdfPage 作為圖像進行編碼,並將圖像資料寫入到流由開發商提供的。則設置為應用程式 UI 中的一個影像控制的源或用於稍後將資料寫入磁片,使用可以流。

一旦你叫 PdfPage 運行時類的 RenderToStreamAsync 方法,就會發生呈現。RenderToStreamAsync 方法接受 IRandomAccessStream 的實例或其派生類型之一,並向流中寫入的編碼的資料。

自訂頁面呈現預設呈現過程涉及到編碼 PdfPage 作為一個 PNG 圖像在文檔中的 PDF 頁面的實際尺寸。您可以更改預設的編碼從 PNG BMP 或 JPEG,雖然我強烈建議你使用 PNG 編碼為螢幕上呈現的內容,因為它是無損,也不會產生較大的點陣圖。然而,如果你想要從 PDF 頁面生成的圖像,並將它們稍後訪問磁片存儲,您應該考慮使用 JPEG 編碼,因為它會生成較小的影像檔與可接受的解決。您還可以指定的 SourceRect 和 DestinationWidth,要呈現的 PDF 頁面僅一個部分 — — 例如,在縮放的操作回應。您還可以通過使用 Boolean 標誌 IsHighContrastEnabled 並將此標誌設置為 true 來檢查在高對比模式下呈現。您可以創建 PdfPageRenderOptions 結構的一個實例,並將它傳遞給 PdfPage 類的 RenderToStreamAsync 方法的重載版本。

用戶端代碼一個簡單的應用程式演示如何使用這些 Api 來呈現 PDF 內容是多麼容易。我的應用程式範例 (在附帶的代碼下載中的 PdfAPISample) 包含兩個按鈕控制項,首頁中所示圖 1


圖 1 PDF 的應用程式 UI

這兩個按鈕的按一下事件處理常式將提示使用者選擇一個 PDF 文檔,並呈現的第一頁。"呈現 PDF w / 選項"按鈕的事件處理常式將使用重載的 RenderToStreamAsync 方法並更改頁面背景顏色。

Button_Click 事件處理常式中列出圖 2

圖 2 Button_Click 事件處理常式來打開並呈現一個 PDF 文檔

void MainPage::Button_Click(Platform::Object^ sender,
  Windows::UI::Xaml::RoutedEventArgs^ args)
{
  m_streamVec->Clear();
  FileOpenPicker^ openPicker = ref new FileOpenPicker();
  openPicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
  openPicker->ViewMode = PickerViewMode::List;
  openPicker->FileTypeFilter->Clear();
  openPicker->FileTypeFilter->Append(L".pdf");
  create_task(openPicker->PickSingleFileAsync())
  .then([this](StorageFile^ pdfFile)
  {
    m_ImagefileName = pdfFile->Name;
    create_task(PdfDocument::LoadFromFileAsync(pdfFile))
    .then([this](PdfDocument^ pdfDoc)
    {
      auto page = pdfDoc->GetPage(0);
      auto stream = ref new InMemoryRandomAccessStream();
      IAsyncAction^ action = page->RenderToStreamAsync(stream);
      auto actionTask = create_task(action);
      actionTask.then([this, stream, page]()
      {
        String^ img_name = m_ImagefileName + ".png";
        task<StorageFolder^> writeFolder(
          KnownFolders::PicturesLibrary->GetFolderAsync("Output"));
          writeFolder
          .then([this, img_name, stream, page](StorageFolder^ outputFolder)
          {
            task<StorageFile^> file(
            outputFolder->CreateFileAsync(img_name, 
            CreationCollisionOption::ReplaceExisting));
        file.then([this, stream, page](StorageFile^ file1) {
          task<IRandomAccessStream^> writeStream(
            file1->OpenAsync(FileAccessMode::ReadWrite));
          writeStream.then(
          [this, stream, page](IRandomAccessStream^ fileStream) {
            IAsyncOperationWithProgress<unsigned long long
,             unsigned long long>^ progress
              = RandomAccessStream::CopyAndCloseAsync(
                stream->GetInputStreamAt(0),
                fileStream->GetOutputStreamAt(0));
                auto copyTask = create_task(progress);
                copyTask.then(
                   [this, stream, page, fileStream](
                   unsigned long long bytesWritten) {
                  stream->Seek(0);
                  auto bmp = ref new BitmapImage();
                  bmp->SetSource(fileStream);
                  auto page1 = ref new PdfPageAsImage();
                  page1->PdfPageImage = bmp;
                  m_streamVec->Append(page1);
                  pageView->ItemsSource = ImageCollection;
                  delete stream;
                  delete page;
                  });
                });
            });
          });
        });
    });
  });
}

本機 Api,可用於 PDF 呈現

WinRT Api 允許 PDF 內容更方便地集成在 Windows 應用商店的應用程式中,並且註定要從 WinRT 支援的所有語言訪問通過從 PDF 文檔中的每個頁創建的影像檔或流。 但是,某些 Windows 應用商店的應用程式的類具有直接螢幕上呈現 PDF 內容的要求。 此類應用程式通常使用本機 c + + 進行編寫和使用 XAML 或 DirectX。 對於這些應用程式,Windows 8.1 還包括本機 Api,可用於在 DirectX 的表面上呈現 PDF 內容。

本機 Api,可用於 PDF 呈現目前在贏­dows.data.pdf.interop.h 標頭檔,需要用 windows.data.pdf.lib 靜態程式庫連結。 專為那些希望直接螢幕上呈現 PDF 內容的 c + + 開發人員可以使用這些 Api。

PdfCreateRenderer PdfCreateRenderer 函數是本機 API 的進入點。 它接受一個實例 IDXGIDevice 作為輸入並返回 IPdfRendererNative 介面的實例。 可以從 XAML SurfaceImageSource、 VirtualSurfaceImageSource 或 XAML SwapChainBackgroundPanel 獲得 IDXGIDevice 的輸入的參數。 您可能知道這些都是 XAML 支援為混合的 DirectX 內容在 XAML 的框架內的互操作類型。 調用 PdfCreateRenderer 函數是很容易的。 請確保包括 windows.data.pdf.interop.h 和反對 windows.data.pdf.lib 靜態程式庫的連結。 從底層 D3DDevice 實例獲得 IDXGIDevice 的實例。 IDXGIDevice 實例傳遞給 PdfCreateRenderer 函數。 如果此函數成功,它返回一個有效的 IPdfRendererNative 介面實例:

ComPtr<IDXGIDevice> dxgiDevice;
d3dDevice.As(&dxgiDevice)
ComPtr<IPdfRendererNative> pdfRenderer;
PdfCreateRenderer(dxgiDevice.Get(), &pdfRenderer)

IPdfRendererNative 介面 IPdfRendererNative 介面是在本機 API 中支援的唯一介面。該介面包含兩個説明器方法:RenderPageToSurface 和 RenderPageToDeviceCoNtext。

RenderPageToSurface 是要使用呈現 PDF 內容到 XAML SurfaceImageSource 或 VirtualSurfaceImageSource 時的正確方法。RenderPageToSurface 方法採用 PdfPage 作為輸入參數與要繪製的內容、 要繪製的設備和一個可選的 PDF_RENDER_PARAMS 結構的偏移到 DXGISurface 的實例。RenderPageToSurface 方法檢查時,你可能會驚訝,請參見類型 IUnknown PdfPage 輸入正。PdfPage 的類型 IUnknown 你怎麼做?事實證明與 WinRT API,一旦你從一個 PdfDocument 物件,該物件的一個有效的 PdfPage 實例可以使用 safe_cast 投到 IUnknown PdfPage。

當您使用 SurfaceImageSource 或 VirtualSurface­看來互操作類型,調用 BeginDraw 返回到 XAML 框架提供您的應用程式來繪製內容的 atlas 一個偏移量。應將該偏移量傳遞給 RenderPageToSurface,以確保繪圖發生在正確的位置。

RenderPageToDeviceCoNtext 是用於向 XAML SwapChainBackgroundPanel 呈現 PDF 內容時的正確方法。RenderPageToDeviceCoNtext 採用 PdfPage 作為輸入參數與要繪製的內容和一個可選的 PDF_RENDER_PARAMS 結構的 ID2D1DeviceCoNtext 的實例。

除了螢幕上直接繪製,也可以通過使用 PDF_RENDER_PARAMS 結構自訂呈現。RenderPageToSurface 和 RenderPageToDeviceCoNtext 接受 PDF_RENDER_PARAMS 的一個實例。在 PDF_RENDER_PARAMS 中提供的選項是類似于的 WinRT PDFPageRenderOptions 結構。請注意既不本機 Api 是非同步方法。呈現發生直接螢幕上而不會引起成本的編碼和解碼。

再次,一個簡單的應用程式演示如何使用這些 Api 來呈現 PDF 內容。此應用程式範例 (在附帶的代碼下載中的 DxPdfApp) 包含首頁與一個按鈕控制項,如中所示圖 3


圖 3 本機 PDF API 的應用程式使用者介面

要打開的文檔和訪問 PDF 頁的代碼保持不變,WinRT API 和本機 API 之間。主要的變化是在渲染過程中。在 WinRT Api 同時編碼作為圖像的 PDF 頁面和影像檔寫到磁片上,本機 Api 使用基於 DirectX 的呈現器螢幕上,如中所示繪製 PDF 內容圖 4

圖 4 RenderPageRect 方法繪圖 PDF 內容螢幕上

void PageImageSource::RenderPageRect(RECT rect)
{
  m_spRenderTask = m_spRenderTask.then([this, rect]() -> VSISData {
    VSISData vsisData;
    if (!is_task_cancellation_requested())
    {
      HRESULT hr = m_vsisNative->BeginDraw(
        rect,
        &(vsisData.dxgiSurface),
        &(vsisData.offset));
      if (SUCCEEDED(hr))
      {
        vsisData.fContinue = true;
      }
      else
      {
        vsisData.fContinue = false;
      }
    }
    else
    {
      cancel_current_task();
    }
    return vsisData;
  }, m_cts.get_token(), task_continuation_context::use_current())
  .then([this, rect](task<VSISData> beginDrawTask) -> VSISData {
    VSISData vsisData;
    try
    {
      vsisData = beginDrawTask.get();
      if ((m_pdfPage != nullptr) && vsisData.fContinue)
      {
        ComPtr<IPdfRendererNative> pdfRendererNative;
        m_renderer->GetPdfNativeRenderer(&pdfRendererNative);
        Windows::Foundation::Size pageSize = m_pdfPage->Size;
        float scale = min(static_cast<float>(
          m_width) / pageSize.Width,
          static_cast<float>(m_height) / pageSize.Height);
          IUnknown* pdfPageUnknown = (IUnknown*)
          reinterpret_cast<IUnknown*>(m_pdfPage);
          auto params = PdfRenderParams(D2D1::RectF((rect.left / scale),
            (rect.top / scale),
            (rect.right / scale),
            (rect.bottom / scale)),
            rect.right - rect.left,
            rect.bottom - rect.top,
            D2D1::ColorF(D2D1::ColorF::White),
            FALSE
            );
          pdfRendererNative->RenderPageToSurface(
            pdfPageUnknown,
            vsisData.dxgiSurface.Get(),
            vsisData.offset, &params);
      }
    }
    catch (task_canceled&)
    {
    }
    return vsisData;
  }, cancellation_token::none(), task_continuation_context::use_arbitrary())
  .then([this](task<VSISData> drawTask) {
    VSISData vsisData;
    try
    {
      vsisData = drawTask.get();
      if (vsisData.fContinue)
        m_vsisNative->EndDraw();
    }
    catch (task_canceled&)
    {
    }
  }, cancellation_token::none(), task_continuation_context::use_current());
}
}

了解更多資訊

我討論了 WinRT PDF API 允許您將在 Windows 應用商店的應用程式中的 PDF 內容納入的 Windows 8.1 中。 我也討論了的區別使用 WinRT API 或 C + + / DirectX API,以及每種方法的好處。 最後,我提供了一套建議,應在某些情形下使用 API。 關於 Windows 8.1 中的 PDF Api 的詳細資訊,簽出的文檔和 PDF 檢視器展示在 MSDN 上的示例 bit.ly/1bD72TO

 

Sridhar Poduri 是在微軟的專案經理。他是一位 C++ 迷,著有《Modern C++ and Windows Store Apps》(Sridhar Poduri,2013 年)一书,经常在 sridharpoduri.com 上就 C++ 和 Windows 运行时发表博文。

衷心感谢以下技术专家对本文的审阅:艾耶勃拉曼尼亞 (Microsoft)
勃拉曼尼亞艾耶是在 Microsoft Windows 團隊的開發人員,已經涉及 Windows 發展與結束的最後三個版本。 他一直是一部分的讀者團隊,開發的第一個 C + + 為 Windows 8 的 XAML 應用程式。 一個程式師和一個新的父,他找到了一段時間來發佈幾個 Windows 商店的名稱 LSubs 下的應用程式。