Este artigo foi traduzido por máquina.

Windows 8.1

Renderizando conteúdo de PDF em Aplicativos da Windows Store

Sridhar Poduri

Baixar o código de exemplo

PDF como um armazenamento de documentos e arquivamento formato está bem estabelecida no mundo de hoje. Documentos tais como livros, manuais técnicos, guias de usuário, relatórios e muito mais são armazenados em formato PDF. Ele permite que um documento ser consumido de várias plataformas, enquanto um visualizador de PDF com suporte está disponível. Enquanto exibindo documentos PDF é em grande parte irrelevante, apoiando a renderização de conteúdo PDF permanece um desafio, especialmente para os desenvolvedores de app Store do Windows. Com 8.1 do Windows, a Microsoft introduziu novas APIs que facilitam o processo de renderização de conteúdo PDF em apps da loja do Windows.

Neste artigo, examinarei as diferentes maneiras de fazer este processamento. Primeiro, vou me concentrar sobre as APIs que fazem parte do Windows Runtime (WinRT) e acessível para você via JavaScript, c#, .NET Visual Basic e C++. Então vou me concentrar sobre o nativo APIs que permitem que os desenvolvedores de C++ processar conteúdo PDF diretamente sobre uma superfície de desenho baseado no DirectX.

As APIs do Windows Runtime para renderização PDF

O tempo de execução do Windows para Windows 8.1 inclui um novo namespace, Windows.Data.Pdf, que contém as novas classes de tempo de execução e estruturas que oferecem suporte a renderização de PDF no Windows Store apps. Nesta seção, discutirei as diversas classes que compõem o namespace Windows.Data.Pdf, usado para abrir documentos PDF, manipulação de proteção por senha, processamento de documentos, personalizar o processo de renderização e muito mais.

Abrir documentos PDF abrir um documento PDF programaticamente é tão fácil quanto chamar o método estático LoadFromFileAsync da classe de tempo de execução PdfDocument. Essa classe é o ponto de entrada inicial para trabalhar com documentos PDF. O método LoadFromFileAsync aceita um objeto StorageFile e começa o processo de carregamento do PdfDocument. Carregar um documento PDF às vezes pode levar um longo tempo, portanto, a API retorna uma operação assíncrona. Quando a operação assíncrona for concluída, você tem uma instância válida do objeto PdfDocument, conforme mostrado aqui:

// 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.
});

Além do método de LoadFromFileAsync, a PdfDocument classe também contém um método estático do auxiliar para criar uma instância de PdfDocument de um objeto de fluxo. Se você já tem uma referência a um documento PDF como uma instância de RandomAccessStream, você pode simplesmente passar o objeto de fluxo para o método de LoadFromStreamAsync. Dependendo do seu cenário, você pode escolher usar o LoadFromFileAsync ou LoadFromStreamAsync métodos para criar uma instância do objeto PdfDocument. Uma vez que você tiver uma instância de PdfDocument válida, você pode acessar páginas individuais no documento.

Manipulação de documentos em PDF protegido por senha PDF docu­mentos são usados para armazenar uma grande variedade de informações, tais como crédito -­cartão de instruções ou outros dados confidenciais. Alguns editores não deseja que os usuários têm acesso irrestrito a esses tipos de documentos e protegê-los com senhas. O acesso é concedido somente para aplicativos cujos binários contêm a senha. Os métodos LoadFromFileAsync e LoadFromStreamAsync da classe de tempo de execução PdfDocument contêm versões sobrecarregadas dos métodos que aceitam uma senha através de um parâmetro de seqüência de caracteres:

// 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.
});

Se você tentar carregar um documento protegido por senha, sem especificar uma senha, os métodos LoadFromFileAsync e LoadFromStreamAsync lançará uma exceção.

Acessando as páginas em um documento PDF depois que você criar uma instância de um objeto PdfDocument, o Count propriedade retornará o número de páginas no documento PDF. Você simplesmente pode iterar de 0 para a "Contagem – 1" intervalo para obter acesso a páginas PDF individuais. Cada página é do tipo PdfPage classe de tempo de execução. A classe de tempo de execução de PdfPage tem um método chamado PreparePageAsync que começa o processo de preparar uma página PDF para renderização. Página preparação envolve a análise e a carregar a página, inicializando o Direct2D recursos para manipulação adequada de gráficos caminhos e formas, inicializando o DirectWrite para lidar com o conjunto correto de fontes para processamento de texto e assim por diante. Se você não chamar o PreparePageAsync antes de começar a processar páginas do PDF, o processo de renderização chama PreparePageAsync para você implicitamente. No entanto, você deve chamar PreparePageAsync e ter as páginas prontas para renderização em vez de deixar o processo de renderização preparar a página. Preparando a página à frente o processamento real economiza tempo no processo de renderização real e é uma técnica de otimização agradável.

Renderização de páginas do PDF uma vez que os objetos de PdfPage são preparados, eles podem ser processados. A API de renderização codifica a PdfPage como uma imagem e grava os dados da imagem em um fluxo fornecido pelo desenvolvedor. O fluxo pode ser definido como a fonte para um controle de imagem no aplicativo interface do usuário ou ser usado para gravar os dados em disco para uso mais tarde.

O processamento acontece depois que você chamar o método RenderToStreamAsync da classe de tempo de execução PdfPage. O método RenderToStreamAsync aceita uma instância de um IRandomAccessStream ou um dos seus tipos derivados e grava os dados codificados no fluxo.

Personalizando a renderização da página o processo de renderização padrão envolve a codificação do PdfPage como uma imagem PNG nas dimensões reais da página do documento PDF. Você pode alterar o padrão de codificação de PNG para BMP ou JPEG, embora altamente recomendável que você use PNG codificação para processar conteúdo na tela, porque é sem perdas e também não gera bitmaps grandes. No entanto, se você quiser gerar imagens nas páginas de PDF e armazená-los em disco para acesso mais tarde, você deve considerar usando a codificação JPEG porque gera arquivos de imagem menores com uma resolução aceitável. Você também pode especificar o SourceRect e DestinationWidth para processar apenas uma parte de uma página PDF — por exemplo, em resposta a uma operação de zoom-in. Você também pode verificar para renderização em modo de alto contraste usando o sinalizador booleano IsHighContrastEnabled e definir esse sinalizador como true. Você pode criar uma instância da estrutura PdfPageRenderOptions e passá-lo para a versão sobrecarregada do método RenderToStreamAsync da classe PdfPage.

O código do cliente um app simples demonstra como é fácil usar essas APIs para processar PDF conteúdo. Meu aplicativo de amostra (PdfAPISample no download do código fornecido) contém uma MainPage com dois controles de botão, conforme mostrado no Figura 1.

The PDF App UI
Figura 1: A interface do usuário do App PDF

Os manipuladores de eventos para os dois botões solicitará que o usuário selecione um documento PDF e processar a primeira página. O manipulador de eventos para o botão "Processar PDF w/opções" usará o método sobrecarregado de RenderToStreamAsync e alterar a cor de fundo da página.

O manipulador de eventos Button_Click está listado em Figura 2.

Figura 2 o manipulador de eventos Button_Click abrir e processar um documento 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;
                  });
                });
            });
          });
        });
    });
  });
}

As APIs nativas para renderização PDF

O WinRT APIs permitem fácil integração de conteúdo em PDF em apps da loja do Windows e são destinadas a ser acessível a partir de todas as linguagens suportadas pelo WinRT criando um arquivo de imagem ou o fluxo de cada página em um documento PDF. No entanto, certas classes de apps da loja do Windows tem uma exigência para processar conteúdo PDF diretamente na tela. Esses aplicativos geralmente são escritos usando C++ nativo e utilizam XAML ou DirectX. Para estas aplicações, Windows 8.1 também inclui APIs nativas para renderização de conteúdo PDF sobre uma superfície de DirectX.

As APIs nativas para renderização de PDF estão presentes na vitória­dows.data.pdf.interop.h cabeçalho arquivo e exigem a ligação com a biblioteca estática de windows.data.pdf.lib. Essas APIs estão disponíveis exclusivamente para desenvolvedores de C++ que deseja processar conteúdo PDF diretamente na tela.

PdfCreateRenderer PdfCreateRenderer a função é o ponto de entrada para a API nativa. Aceita uma instância de um IDXGIDevice como entrada e retorna uma instância de uma interface IPdfRendererNative. O parâmetro de entrada de IDXGIDevice pode ser obtido por um SurfaceImageSource do XAML, um VirtualSurfaceImageSource ou um SwapChainBackgroundPanel de XAML. Sabe que estes são os tipos de interoperabilidade que o XAML oferece suporte para DirectX conteúdo em um quadro XAML de mistura. Chamar a função PdfCreateRenderer é fácil. Certifique-se de incluir windows.data.pdf.interop.h e link com a biblioteca estática windows.data.pdf.lib. Obter uma instância de um IDXGIDevice da instância do D3DDevice subjacente. Passe a instância de IDXGIDevice para a função de PdfCreateRenderer. Se a função for bem-sucedido, ele retorna uma instância válida de uma interface IPdfRendererNative:

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

A IPdfRendererNative Interface o IPdfRendererNative é a única interface oferece suportada a API nativa. A interface contém dois métodos auxiliares: RenderPageToSurface e RenderPageToDeviceContext.

RenderPageToSurface é o método correto para usar durante a renderização de conteúdo PDF para XAML SurfaceImageSource ou um VirtualSurfaceImageSource. O método RenderPageToSurface leva um PdfPage como um parâmetro de entrada junto com uma instância de DXGISurface para o qual desenhar o conteúdo, um deslocamento sobre o dispositivo para desenhar e uma estrutura PDF_RENDER_PARAMS opcional. Como você examinar o método de RenderPageToSurface, você pode se surpreender ao ver a entrada de PdfPage sendo do tipo IUnknown. Como conseguir um PdfPage do tipo IUnknown. Acontece que com a API do WinRT, uma vez que você obter uma instância de PdfPage válida de um objeto PdfDocument, você pode usar safe_cast para lançar um PdfPage de IUnknown.

Quando você usa o SurfaceImageSource ou VirtualSurface­tipos de interoperabilidade ImageSource, chamando BeginDraw retorna um deslocamento para o atlas que o quadro XAML fornece seu aplicativo para desenhar o conteúdo. Você deve passar esse deslocamento para RenderPageToSurface para garantir que o desenho acontece na posição correta.

O RenderPageToDeviceContext é o método correto para usar durante a renderização de conteúdo PDF para uma SwapChainBackgroundPanel de XAML. O RenderPageToDeviceContext leva um PdfPage como um parâmetro de entrada junto com uma instância de ID2D1DeviceContext para o qual desenhar o conteúdo e uma estrutura PDF_RENDER_PARAMS opcional.

Além de desenhar diretamente na tela, a renderização também pode ser personalizada usando a estrutura PDF_RENDER_PARAMS. O RenderPageToSurface e o RenderPageToDeviceContext aceitam uma instância de PDF_RENDER_PARAMS. As opções fornecidas no PDF_RENDER_PARAMS são similares à estrutura do WinRT PDFPageRenderOptions. Observe que nenhuma das APIs nativos são assíncrona métodos. O processamento acontece diretamente na tela sem incorrer no custo de codificação e decodificação.

Novamente, um app simples demonstra como usar essas APIs para processar conteúdo em PDF. Este aplicativo de amostra (DxPdfApp no download do código fornecido) contém uma MainPage com um button controle, conforme mostrado no Figura 3.

Native PDF API App UI
Figura 3 PDF nativo API App UI

O código para abrir um documento e acessar uma página PDF permanece a mesma entre o WinRT API e o API nativa. A grande mudança é no processo de renderização. Enquanto as APIs WinRT codificar a página PDF como uma imagem e gravar o arquivo de imagem de disco, as APIs nativas usam um processador baseado em DirectX para desenhar o conteúdo do PDF na tela, como mostrado em Figura 4.

Figura 4 o método de RenderPageRect desenho PDF conteúdo na tela

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());
}
}

Mais informações

Eu discuti o WinRT PDF API no Windows 8.1 que permite incorporar conteúdo PDF em apps da loja do Windows. Também discuti as diferenças entre usar a API do WinRT ou o C + + / DirectX API e os benefícios de cada abordagem. Finalmente, eu forneci um conjunto de recomendações sobre qual API deve ser usado em determinados cenários. Para obter mais informações sobre as APIs de PDF no Windows 8.1, confira a documentação e exemplo de vitrine do visualizador PDF no MSDN em bit.ly/1bD72TO.

 

Sridhar Poduri é um gerente de programa da Microsoft. Um desenvolvedor aficionado pelo C++ e autor do livro “Modern C++ and Windows Store Apps” (Sridhar Poduri, 2013), ele mantém um blog sobre o C++ e o Tempo de Execução do Windows em sridharpoduri.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Subramanian Iyer (Microsoft)
Subramanian Iyer é um desenvolvedor na equipe do Windows da Microsoft e tem estado envolvido com o desenvolvimento do Windows durante os três últimos lançamentos. Ele tem sido uma parte da equipe do leitor, que desenvolveu um do primeiro C + + / XAML apps para Windows 8. Um programador e um novo pai, ele tinha encontrado algum tempo para publicar alguns apps na loja de Windows, sob o nome LSubs.