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

The PDF App UI
图 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

Native PDF API App UI
图 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 下的应用程序。