java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java RTF文件查看器

java实现RTF文件查看器(附带源码)

作者:Katie。

这篇文章主要介绍了一个基于 Java Swing 的 RTF 文件查看器,既可作为轻量级桌面应用,也可嵌入到更大规模的管理系统中,满足用户对 RTF 文档的即时预览、样式保真和简单导航需求

一、项目背景详细介绍

在企业级办公软件、文档管理系统以及教育培训平台中,富文本格式(RTF,Rich Text Format)是一种历史悠久且跨平台兼容性强的文档格式。它支持文字样式(粗体、斜体、下划线)、段落格式、列表、表格、图片等丰富排版元素,且不依赖专有软件即可被大多数文本编辑器读取。

虽然现代办公自动化场景越来越多地转向 Office Open XML(.docx)、PDF 等格式,但在以下场景中,RTF 仍发挥着重要作用:

基于以上背景,设计并实现一个基于 Java Swing 的 RTF 文件查看器,既可作为轻量级桌面应用,也可嵌入到更大规模的管理系统中,满足用户对 RTF 文档的即时预览、样式保真和简单导航需求。

二、项目需求详细介绍

1. 功能需求

打开 RTF 文件:支持通过菜单或工具栏的“打开”按钮,弹出文件选择对话框,选择 .rtf 文件。

RTF 渲染与显示

滚动与缩放:支持文档滚动查看,并可通过快捷键或菜单放大/缩小字体(最小 8pt,最大 48pt)。

查找与导航:提供简单的查找框,输入关键词后高亮所有匹配,并支持“下一处”跳转。

只读模式:文档仅用于查看,不允许用户编辑;界面上去除编辑光标与输入焦点。

2. 非功能需求

易用性与可扩展性:界面简洁明了,上手即会;模块化设计,后续可增加“打印”、“导出为 PDF”等功能。

可测试性:对加载、渲染、查找等核心逻辑编写单元测试,确保功能稳定。

跨平台兼容:依赖纯 Java Swing,无需额外本地库,支持 Windows、macOS、Linux。

性能要求

项目文档:提供 README,包括快速使用指南和常见问题。

3. 工程化需求

Maven 构建

目录结构

rtf-viewer/
├── pom.xml
├── src/main/java/com/example/rtfviewer/
│   ├── RtfViewer.java
│   ├── RtfPanel.java
│   ├── FindDialog.java
│   └── DemoMain.java
└── src/test/java/com/example/rtfviewer/
    └── RtfViewerTest.java

代码规范

CI/CD

建议在 GitHub Actions 上执行 mvn test,确保回归稳定。

三、相关技术详细介绍

1.Swing 文本组件

2.RTFEditorKit

3.查找与高亮

4.字体缩放

5.文件对话框

四、实现思路详细介绍

1.主界面 RtfViewer.java

2.文档面板 RtfPanel.java

内部使用 JTextPane,并设置为只读模式:

textPane.setEditable(false); textPane.setEditorKit(new RTFEditorKit());

提供 loadRtf(File file) 方法:清空当前文档模型,用 RTFEditorKit 读取文件流,并在加载完成后滚动至顶部;

提供 zoomIn()、zoomOut() 方法:调整字体大小,并重新应用到文档所有段落。

3.查找对话框 FindDialog.java

4.事件与监听

5.日志与异常处理

6.Demo 主类 DemoMain.java

提供 main() 方法,一行代码启动:

SwingUtilities.invokeLater(() -> new RtfViewer().setVisible(true));

确保所有 UI 操作都在 EDT(事件分发线程)执行。

五、完整实现代码

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.rtfviewer</groupId>
  <artifactId>rtf-viewer</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencies>
    <!-- JUnit 5 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.9.2</version>
      <scope>test</scope>
    </dependency>
    <!-- SLF4J + Logback -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.4.8</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M7</version>
      </plugin>
    </plugins>
  </build>
</project>

Java

// 文件:src/main/java/com/example/rtfviewer/RtfViewer.java
package com.example.rtfviewer;
 
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
 
/**
 * 主界面:RTF 文件查看器
 */
public class RtfViewer extends JFrame {
    private final RtfPanel rtfPanel;
    private final FindDialog findDialog;
 
    public RtfViewer() {
        super("RTF 文件查看器");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(800, 600);
        setLocationRelativeTo(null);
 
        rtfPanel = new RtfPanel();
        findDialog = new FindDialog(this, rtfPanel);
 
        setJMenuBar(createMenuBar());
        add(createToolBar(), BorderLayout.NORTH);
        add(new JScrollPane(rtfPanel), BorderLayout.CENTER);
    }
 
    private JMenuBar createMenuBar() {
        JMenuBar mb = new JMenuBar();
        JMenu file = new JMenu("文件");
        JMenuItem open = new JMenuItem("打开");
        open.addActionListener(e -> openFile());
        file.add(open);
        mb.add(file);
 
        JMenu view = new JMenu("查看");
        JMenuItem zoomIn = new JMenuItem("放大");
        zoomIn.addActionListener(e -> rtfPanel.zoomIn());
        JMenuItem zoomOut = new JMenuItem("缩小");
        zoomOut.addActionListener(e -> rtfPanel.zoomOut());
        view.add(zoomIn); view.add(zoomOut);
        JMenuItem find = new JMenuItem("查找");
        find.addActionListener(e -> findDialog.setVisible(true));
        view.add(find);
        mb.add(view);
        return mb;
    }
 
    private JToolBar createToolBar() {
        JToolBar tb = new JToolBar();
        JButton btnOpen = new JButton("打开");
        btnOpen.addActionListener(e -> openFile());
        JButton btnZoomIn = new JButton("放大");
        btnZoomIn.addActionListener(e -> rtfPanel.zoomIn());
        JButton btnZoomOut = new JButton("缩小");
        btnZoomOut.addActionListener(e -> rtfPanel.zoomOut());
        JButton btnFind = new JButton("查找");
        btnFind.addActionListener(e -> findDialog.setVisible(true));
        tb.add(btnOpen); tb.add(btnZoomIn); tb.add(btnZoomOut); tb.add(btnFind);
        return tb;
    }
 
    private void openFile() {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("RTF 文档", "rtf"));
        int ret = chooser.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = chooser.getSelectedFile();
            rtfPanel.loadRtf(file);
        }
    }
}
 
// 文件:src/main/java/com/example/rtfviewer/RtfPanel.java
package com.example.rtfviewer;
 
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.rtf.RTFEditorKit;
import java.awt.*;
import java.io.*;
import java.util.*;
import org.slf4j.*;
 
/**
 * RTF 文档显示面板
 */
public class RtfPanel extends JTextPane {
    private static final Logger logger = LoggerFactory.getLogger(RtfPanel.class);
    private float fontSize = 12f;
 
    public RtfPanel() {
        super();
        setEditable(false);
        setEditorKit(new RTFEditorKit());
        setFont(getFont().deriveFont(fontSize));
    }
 
    /**
     * 加载并显示 RTF 文件
     */
    public void loadRtf(File file) {
        try (InputStream in = new FileInputStream(file)) {
            Document doc = getEditorKit().createDefaultDocument();
            getEditorKit().read(in, doc, 0);
            setDocument(doc);
            setCaretPosition(0);
            logger.info("成功加载 RTF 文件:{}", file.getAbsolutePath());
        } catch (Exception ex) {
            logger.error("加载 RTF 文件失败", ex);
            JOptionPane.showMessageDialog(this, "加载失败:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
        }
    }
 
    /** 放大字体 */
    public void zoomIn() {
        fontSize = Math.min(fontSize + 2f, 48f);
        setFont(getFont().deriveFont(fontSize));
    }
 
    /** 缩小字体 */
    public void zoomOut() {
        fontSize = Math.max(fontSize - 2f, 8f);
        setFont(getFont().deriveFont(fontSize));
    }
 
    /**
     * 查找并高亮关键词
     * @param keyword 关键词
     * @return 所有匹配位置列表
     */
    public List<MatchPosition> find(String keyword) {
        List<MatchPosition> result = new ArrayList<>();
        removeHighlights();
        if (keyword == null || keyword.isEmpty()) return result;
        try {
            StyledDocument doc = getStyledDocument();
            String text = doc.getText(0, doc.getLength()).toLowerCase();
            String key = keyword.toLowerCase();
            int index = 0;
            Highlighter hilite = getHighlighter();
            Highlighter.HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
            while ((index = text.indexOf(key, index)) >= 0) {
                hilite.addHighlight(index, index + key.length(), painter);
                result.add(new MatchPosition(index, key.length()));
                index += key.length();
            }
        } catch (BadLocationException e) {
            logger.warn("查找出错", e);
        }
        return result;
    }
 
    /** 清除所有高亮 */
    public void removeHighlights() {
        getHighlighter().removeAllHighlights();
    }
}
 
// 文件:src/main/java/com/example/rtfviewer/FindDialog.java
package com.example.rtfviewer;
 
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
 
/**
 * 查找对话框
 */
public class FindDialog extends JDialog {
    private final JTextField tfKeyword = new JTextField(20);
    private final JButton btnNext = new JButton("下一个");
    private final JButton btnPrev = new JButton("上一个");
    private List<MatchPosition> matches = Collections.emptyList();
    private int currentIndex = -1;
    private final RtfPanel rtfPanel;
 
    public FindDialog(JFrame owner, RtfPanel panel) {
        super(owner, "查找", false);
        this.rtfPanel = panel;
        setLayout(new FlowLayout(FlowLayout.LEFT, 5, 10));
        add(new JLabel("关键词:"));
        add(tfKeyword);
        add(btnNext);
        add(btnPrev);
        setSize(350, 120);
        setLocationRelativeTo(owner);
 
        tfKeyword.addActionListener(e -> doSearch());
        btnNext.addActionListener(e -> moveTo(true));
        btnPrev.addActionListener(e -> moveTo(false));
    }
 
    private void doSearch() {
        matches = rtfPanel.find(tfKeyword.getText());
        currentIndex = -1;
        if (!matches.isEmpty()) {
            moveTo(true);
        }
    }
 
    private void moveTo(boolean forward) {
        if (matches.isEmpty()) return;
        currentIndex = forward ? (currentIndex + 1) % matches.size()
                               : (currentIndex - 1 + matches.size()) % matches.size();
        MatchPosition pos = matches.get(currentIndex);
        rtfPanel.setCaretPosition(pos.getOffset());
        rtfPanel.requestFocusInWindow();
    }
}
 
// 文件:src/main/java/com/example/rtfviewer/MatchPosition.java
package com.example.rtfviewer;
 
/**
 * 匹配位置记录
 */
public class MatchPosition {
    private final int offset;
    private final int length;
    public MatchPosition(int offset, int length) {
        this.offset = offset;
        this.length = length;
    }
    public int getOffset() { return offset; }
    public int getLength() { return length; }
}
 
// 文件:src/main/java/com/example/rtfviewer/DemoMain.java
package com.example.rtfviewer;
 
import javax.swing.*;
 
/**
 * 启动类
 */
public class DemoMain {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new RtfViewer().setVisible(true));
    }
}
 
// 文件:src/test/java/com/example/rtfviewer/RtfViewerTest.java
package com.example.rtfviewer;
 
import org.junit.jupiter.api.*;
import javax.swing.text.*;
import java.io.*;
import static org.junit.jupiter.api.Assertions.*;
 
/**
 * 单元测试 RtfPanel 加载与查找
 */
class RtfViewerTest {
    private RtfPanel panel;
 
    @BeforeEach
    void setUp() {
        panel = new RtfPanel();
    }
 
    @Test
    void testLoadEmpty() {
        File tmp = new File("src/test/resources/empty.rtf");
        panel.loadRtf(tmp);
        try {
            String text = panel.getDocument().getText(0, panel.getDocument().getLength());
            assertTrue(text.trim().isEmpty());
        } catch (Exception e) {
            fail(e);
        }
    }
 
    @Test
    void testFind() {
        // 向文档插入测试字符串
        try {
            StyledDocument doc = (StyledDocument) panel.getDocument();
            doc.insertString(0, "Hello RTF Viewer Hello", null);
        } catch (BadLocationException e) {
            fail(e);
        }
        List<MatchPosition> matches = panel.find("Hello");
        assertEquals(2, matches.size());
        assertEquals(0, matches.get(0).getOffset());
        assertEquals(18, matches.get(1).getOffset());
    }
}

六、代码详细解读

本节对第一部分的完整代码进行逐模块、逐方法的功能说明,不复写代码,仅阐述其设计意图与作用。

1.RtfViewer(主窗口)

2.RtfPanel(文档显示面板)

继承 JTextPane 并初始化:设置只读模式、使用 RTFEditorKit 解析 RTF、设置初始字体大小。

loadRtf(File):

zoomIn()/zoomOut():分别将字体大小在 [8pt, 48pt] 范围内加/减 2pt,并通过 deriveFont 应用到文本面板。

find(String):

removeHighlights():一键清除所有高亮标记。

3.FindDialog(查找对话框)

4.MatchPosition(匹配位置记录)

简单的值对象,保存 offset(起始位置)和 length(匹配长度),供查找导航时使用。

5.DemoMain(启动类)

在 EDT 中执行,通过一行代码 new RtfViewer().setVisible(true) 启动整个应用,确保 UI 线程安全。

6.RtfViewerTest(单元测试)

七、项目详细总结

1.简洁易用

.2模块化设计

3.丰富交互

4.日志与异常处理

5.可测试性

八、项目常见问题及解答

问:为什么要使用 JTextPane 而不是 JEditorPane?

答:JTextPane 是 JEditorPane 的子类,默认提供了 StyledDocument 支持,更适合处理富文本高亮和格式化操作。

问:放大/缩小时为什么不直接修改 StyledDocument 中的 StyleConstants?

答:逐段修改样式会非常繁琐且性能开销大,统一通过更改组件字体(setFont)可以一次性作用于所有文本,效率更高。

问:查找后高亮会影响后续渲染吗?

答:高亮是通过 Highlighter 层叠在文档渲染之上,不会改变底层文档内容;调用 removeHighlights 可清除所有标记。

问:如何支持大文件(>10MB)无卡顿查看?

答:可引入分页加载或流式渲染机制,分批次解析和渲染文档内容;或者在后台线程异步加载并在部分区域先行显示。

问:如何导出当前查看的 RTF 文档为 PDF?

答:可结合 iText、Apache PDFBox 等库,将 StyledDocument 转换为 PDF 流式写出,或先将 RTF 转为 HTML,再使用 HTML 转 PDF 工具链。

九、扩展方向与性能优化

1.打印功能

使用 JTextPane.print() 或自定义 PrinterJob,支持页眉页脚、自定义分页。

2.导出为其他格式

3.语义化导航

在加载时解析 RTF 的结构化信息,如标题、段落、书签,提供目录树视图,点击可快速跳转。

4.多文档标签页

将 RtfViewer 扩展为支持多标签的文档查看器,方便批量打开多个 RTF 文件并在标签间切换。

5.实时协作与注释

6.性能监控与优化

7.跨平台 UI 统一

到此这篇关于java实现RTF文件查看器(附带源码)的文章就介绍到这了,更多相关java RTF文件查看器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文