在JavaFX中显示PDF

22

JavaFX中开发桌面应用程序,需要显示PDF。我了解到当前版本的JavaFX不支持PDF查看/显示,我也了解了JPedal

现在,有几个问题:

  1. 是否有外部组件或库可以在JavaFX中查看PDF?它应该是免费的。
  2. (如果我必须使用JPedal)如何将其嵌入我的应用程序。
4个回答

13

JPedalFX示例代码及使用

使用JPedalFX的示例代码可在JPedalFX下载页面中找到。

我这边可能有些不太好,但我会在此处粘贴一些从JPedalFX库提供的示例查看器中复制的代码片段。该代码依赖于在类路径上(或应用程序jar清单中引用的库路径)包含在JPedalFX分发中包含的jpedal_lgpl.jar文件。

如果您对JPedalFX的使用有任何疑问,请建议直接联系IDR Solutions(他们曾经对我进行过响应)。

// get file path.
FileChooser fc = new FileChooser();
fc.setTitle("Open PDF file...");
fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF Files", "*.pdf"));     
File f = fc.showOpenDialog(stage.getOwner());
String filename = file.getAbsolutePath();

// open file.
PdfDecoder pdf = new PdfDecoder();
pdf.openPdfFile(filename);
showPage(1);
pdf.closePdfFile();

. . . 

/**
 * Update the GUI to show a specified page.
 * @param page 
 */
private void showPage(int page) {

    //Check in range
    if (page > pdf.getPageCount())
        return;
    if (page < 1)
        return;

    //Store
    pageNumber = page;


    //Show/hide buttons as neccessary
    if (page == pdf.getPageCount())
        next.setVisible(false);
    else
        next.setVisible(true);

    if (page == 1)
        back.setVisible(false);
    else
        back.setVisible(true);


    //Calculate scale
    int pW = pdf.getPdfPageData().getCropBoxWidth(page);
    int pH = pdf.getPdfPageData().getCropBoxHeight(page);

    Dimension s = Toolkit.getDefaultToolkit().getScreenSize();

    s.width -= 100;
    s.height -= 100;

    double xScale = (double)s.width / pW;
    double yScale = (double)s.height / pH;
    double scale = xScale < yScale ? xScale : yScale;

    //Work out target size
    pW *= scale;
    pH *= scale;

    //Get image and set
    Image i = getPageAsImage(page,pW,pH);
    imageView.setImage(i);

    //Set size of components
    imageView.setFitWidth(pW);
    imageView.setFitHeight(pH);
    stage.setWidth(imageView.getFitWidth()+2);
    stage.setHeight(imageView.getFitHeight()+2);
    stage.centerOnScreen();
}

/**
 * Wrapper for usual method since JFX has no BufferedImage support.
 * @param page
 * @param width
 * @param height
 * @return 
 */
private Image getPageAsImage(int page, int width, int height) {

    BufferedImage img;
    try {
        img = pdf.getPageAsImage(page);

        //Use deprecated method since there's no real alternative 
        //(for JavaFX 2.2+ can use SwingFXUtils instead).
        if (Image.impl_isExternalFormatSupported(BufferedImage.class))
            return javafx.scene.image.Image.impl_fromExternalImage(img);

    } catch(Exception e) {
        e.printStackTrace();
    }

    return null;
}

/**
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.jpedal.org
 * (C) Copyright 1997-2008, IDRsolutions and Contributors.
 *
 *  This file is part of JPedal
 *
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * JPedalFX.java
 * ---------------
 */

SwingLabs PDF Renderer

此外,我过去曾使用过一个基于SwingLabs Swing的老式pdf渲染器和JavaFX一起为我的JavaFX Web浏览器渲染pdf。尽管当时我开发浏览器时,Swing/JavaFX集成不是JavaFX的支持功能,但对我来说仍然很有效。 集成代码在PDFViewer.javaBrowserWindow.java中。

请注意,将JavaFX嵌入Swing应用程序在Java 2.2中受到支持,并且在Java 8中支持在JavaFX中嵌入Swing应用程序


10
JPedalFX现在不是免费的:/ - GlacialMan

12

好的,这是我的50美分。除了@ALabrosik和@ReneEnriquez的答案外。

下载pdf.js dist并将其放置在src/main/resources下。

├── pom.xml
├── src
│   └── main
│       ├── java
│       │   └── me
│       │       └── example
│       │           ├── JSLogListener.java
│       │           ├── Launcher.java
│       │           └── WebController.java
│       └── resources
│           ├── build
│           │   ├── pdf.js
│           │   └── pdf.worker.js
│           ├── main.fxml
│           ├── web
│           │   ├── cmaps
│           │   ├── compatibility.js
│           │   ├── debugger.js
│           │   ├── images
│           │   ├── l10n.js
│           │   ├── locale
│           │   ├── viewer.css
│           │   ├── viewer.html
│           │   └── viewer.js
创建以下fxml文件(您应该在TabPane或类似容器中包装WebView,以避免滚动支持问题)。
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.web.WebView?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="576.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="me.example.WebController">
    <center>
      <TabPane>
         <tabs>
            <Tab text="PDF test">
               <content>
                    <WebView fx:id="web" minHeight="-1.0" minWidth="-1.0" />
               </content>
            </Tab>
         </tabs>
      </TabPane>
    </center>
   <bottom>
      <Button fx:id="btn" mnemonicParsing="false" text="Open another file" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

为了防止pdf.js在启动时打开演示pdf文件,请打开web/viewer.js并清除DEFAULT_URL的值。

var DEFAULT_URL = '';

打开 web/viewer.html 文件,并添加脚本块:

<head>

<!-- ... -->
<script src="viewer.js"></script>

<!-- CUSTOM BLOCK -->
<script>
    var openFileFromBase64 = function(data) {
        var arr = base64ToArrayBuffer(data);
        console.log(arr);
        PDFViewerApplication.open(arr);
    }

    function base64ToArrayBuffer(base64) {
      var binary_string = window.atob(base64);
      var len = binary_string.length;
      var bytes = new Uint8Array( len );
      for (var i = 0; i < len; i++)        {
          bytes[i] = binary_string.charCodeAt(i);
      }
      return bytes.buffer;
    }
</script>
<!-- end of CUSTOM BLOCK -->

</head>

现在是控制器(请参阅代码注释以了解说明)。

 public class WebController implements Initializable {

    @FXML
    private WebView web;

    @FXML
    private Button btn;

    public void initialize(URL location, ResourceBundle resources) {
        WebEngine engine = web.getEngine();
        String url = getClass().getResource("/web/viewer.html").toExternalForm();

        // connect CSS styles to customize pdf.js appearance
        engine.setUserStyleSheetLocation(getClass().getResource("/web.css").toExternalForm());

        engine.setJavaScriptEnabled(true);
        engine.load(url);

        engine.getLoadWorker()
                .stateProperty()
                .addListener((observable, oldValue, newValue) -> {
                    // to debug JS code by showing console.log() calls in IDE console
                    JSObject window = (JSObject) engine.executeScript("window");
                    window.setMember("java", new JSLogListener());
                    engine.executeScript("console.log = function(message){ java.log(message); };");

                    // this pdf file will be opened on application startup
                    if (newValue == Worker.State.SUCCEEDED) {
                        try {
                            // readFileToByteArray() comes from commons-io library
                            byte[] data = FileUtils.readFileToByteArray(new File("/path/to/file"));
                            String base64 = Base64.getEncoder().encodeToString(data);
                            // call JS function from Java code
                            engine.executeScript("openFileFromBase64('" + base64 + "')");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

        // this file will be opened on button click
        btn.setOnAction(actionEvent -> {
            try {
                byte[] data = FileUtils.readFileToByteArray(new File("/path/to/another/file"));
                String base64 = Base64.getEncoder().encodeToString(data);
                engine.executeScript("openFileFromBase64('" + base64 + "')");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

pdf.js的某些功能将无法使用:打开文件(因为pdf.js无法访问JAR外部的URL)、打印等。要隐藏相应的工具栏按钮,您可以将以下行添加到web.css中:

#toolbarViewerRight {
    display:none;
}

就这些。剩下的代码都很简单。

public class JSLogListener {

    public void log(String text) {
        System.out.println(text);
    }
}

public class Launcher extends Application {

    public static void main(String[] args) {
        Application.launch();
    }

    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/main.fxml"));
        primaryStage.setTitle("PDF test app");
        primaryStage.setScene(new Scene(root, 1280, 576));
        primaryStage.show();
    }
}

希望这对某人有所帮助。


1
非常感谢您的解释和示例!我已经在我的应用程序上使其工作。我只是进行了一些修改,因为相对的“..”链接在JAR中无法工作。因此,当所有内容都被捆绑在一起时,查看器无法正常工作。我根据您的评论进行了快速文章撰写,并进行了修改,这里是链接:https://blog.samirhadzic.com/2017/02/09/show-pdf-in-your-application/ 谢谢! - Maxoudela
展示你的 WebController 类的 imports - SedJ601
非常感谢,我按照评论和博客文章的方法解决了问题,这是一个好的解决方案。但是,如果有人需要帮助,请允许我补充一些内容。如果您要使用PDF.js,请不要使用当前版本(v2.0.943),因为它无法呈现PDF文档的文本。请使用当前的beta 2.1.266或之前的版本1.10.100,否则文本将无法呈现。 https://dev59.com/SLDla4cB1Zd3GeqP946Q#54767956 - oizulain

11

我建议使用PDF JS JavaScript库。

创建一个WebView并静态加载这个JavaScript pdf viewer example project的HTML/JavaScript内容。创建一个JavaScript函数,可以将PDF字节数组发送到该函数来显示。

这样PDF浏览器的所有逻辑都已经存在。你甚至可以修改视图器的HTML以删除一些功能。

同时要注意JPedalFX,因为我发现它在渲染添加到PDF文件中的图片时不太可靠。在我的情况下,JPedalFX无法渲染使用jfreechart生成的图表图像。


1
你能否详细说明一下这个问题? - Tommo
2
你可以在下面的答案中看到解决方案(https://dev59.com/XWMl5IYBdhLWcg3wr4rn#42040344)。无论如何,老实说,我们最终使用了[PDFBox](https://pdfbox.apache.org/)。客户想要左<->右滑动来更改页面,所以结果发现操作pdf.js有点困难。制作我们自己的自定义pdf阅读器对我们和我们的客户都有效 :) - ALabrosk

-3
package de.vogella.itext.write;

import java.io.FileOutputStream;
import java.util.Date;

import com.itextpdf.text.Anchor;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chapter;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.List;
import com.itextpdf.text.ListItem;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Section;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;


public class FirstPdf {
  private static String FILE = "c:/temp/FirstPdf.pdf";
  private static Font catFont = new Font(Font.FontFamily.TIMES_ROMAN, 18,
      Font.BOLD);
  private static Font redFont = new Font(Font.FontFamily.TIMES_ROMAN, 12,
      Font.NORMAL, BaseColor.RED);
  private static Font subFont = new Font(Font.FontFamily.TIMES_ROMAN, 16,
      Font.BOLD);
  private static Font smallBold = new Font(Font.FontFamily.TIMES_ROMAN, 12,
      Font.BOLD);

  public static void main(String[] args) {
    try {
      Document document = new Document();
      PdfWriter.getInstance(document, new FileOutputStream(FILE));
      document.open();
      addMetaData(document);
      addTitlePage(document);
      addContent(document);
      document.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // iText allows to add metadata to the PDF which can be viewed in your Adobe
  // Reader
  // under File -> Properties
  private static void addMetaData(Document document) {
    document.addTitle("My first PDF");
    document.addSubject("Using iText");
    document.addKeywords("Java, PDF, iText");
    document.addAuthor("Lars Vogel");
    document.addCreator("Lars Vogel");
  }

  private static void addTitlePage(Document document)
      throws DocumentException {
    Paragraph preface = new Paragraph();
    // We add one empty line
    addEmptyLine(preface, 1);
    // Lets write a big header
    preface.add(new Paragraph("Title of the document", catFont));

    addEmptyLine(preface, 1);
    // Will create: Report generated by: _name, _date
    preface.add(new Paragraph("Report generated by: " + System.getProperty("user.name") + ", " + new Date(), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        smallBold));
    addEmptyLine(preface, 3);
    preface.add(new Paragraph("This document describes something which is very important ",
        smallBold));

    addEmptyLine(preface, 8);

    preface.add(new Paragraph("This document is a preliminary version and not subject to your license agreement or any other agreement with vogella.com ;-).",
        redFont));

    document.add(preface);
    // Start a new page
    document.newPage();
  }

  private static void addContent(Document document) throws DocumentException {
    Anchor anchor = new Anchor("First Chapter", catFont);
    anchor.setName("First Chapter");

    // Second parameter is the number of the chapter
    Chapter catPart = new Chapter(new Paragraph(anchor), 1);

    Paragraph subPara = new Paragraph("Subcategory 1", subFont);
    Section subCatPart = catPart.addSection(subPara);
    subCatPart.add(new Paragraph("Hello"));

    subPara = new Paragraph("Subcategory 2", subFont);
    subCatPart = catPart.addSection(subPara);
    subCatPart.add(new Paragraph("Paragraph 1"));
    subCatPart.add(new Paragraph("Paragraph 2"));
    subCatPart.add(new Paragraph("Paragraph 3"));

    // add a list
    createList(subCatPart);
    Paragraph paragraph = new Paragraph();
    addEmptyLine(paragraph, 5);
    subCatPart.add(paragraph);

    // add a table
    createTable(subCatPart);

    // now add all this to the document
    document.add(catPart);

    // Next section
    anchor = new Anchor("Second Chapter", catFont);
    anchor.setName("Second Chapter");

    // Second parameter is the number of the chapter
    catPart = new Chapter(new Paragraph(anchor), 1);

    subPara = new Paragraph("Subcategory", subFont);
    subCatPart = catPart.addSection(subPara);
    subCatPart.add(new Paragraph("This is a very important message"));

    // now add all this to the document
    document.add(catPart);

  }

  private static void createTable(Section subCatPart)
      throws BadElementException {
    PdfPTable table = new PdfPTable(3);

    // t.setBorderColor(BaseColor.GRAY);
    // t.setPadding(4);
    // t.setSpacing(4);
    // t.setBorderWidth(1);

    PdfPCell c1 = new PdfPCell(new Phrase("Table Header 1"));
    c1.setHorizontalAlignment(Element.ALIGN_CENTER);
    table.addCell(c1);

    c1 = new PdfPCell(new Phrase("Table Header 2"));
    c1.setHorizontalAlignment(Element.ALIGN_CENTER);
    table.addCell(c1);

    c1 = new PdfPCell(new Phrase("Table Header 3"));
    c1.setHorizontalAlignment(Element.ALIGN_CENTER);
    table.addCell(c1);
    table.setHeaderRows(1);

    table.addCell("1.0");
    table.addCell("1.1");
    table.addCell("1.2");
    table.addCell("2.1");
    table.addCell("2.2");
    table.addCell("2.3");

    subCatPart.add(table);

  }

  private static void createList(Section subCatPart) {
    List list = new List(true, false, 10);
    list.add(new ListItem("First point"));
    list.add(new ListItem("Second point"));
    list.add(new ListItem("Third point"));
    subCatPart.add(list);
  }

  private static void addEmptyLine(Paragraph paragraph, int number) {
    for (int i = 0; i < number; i++) {
      paragraph.add(new Paragraph(" "));
    }
  }
} 

2
问题是查看现有的还是嵌入它。 - diego matos - keke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接