使用 HTMLEditorKit 将 JTextPane 的内容写入剪贴板,不同颜色元素之间没有额外的空格

问题描述 投票:0回答:1

我在 Swing 中为我自己的编程语言构建了一个自定义语法高亮器 gui。我正在编写一个功能来将选定的文本复制到剪贴板,这样我就可以轻松地将自定义语法突出显示的格式化文本粘贴到 Web 浏览器或 Word 文档中。然而,这样做会在每个具有不同颜色的元素之间增加一个空间。

以下简化代码简洁地演示了这个问题。首先,我们有 Gui 类,使用包含文本

"A:b"
的 JTextPane,字符
':'
设置为红色。 JButton 调用一个方法,该方法使用 HTMLEditorKit 将文本窗格的 StyledDocument 内容写入一个字符串。然后将其放入自定义 HTMLTransferable 对象中并传递到剪贴板。

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class Gui {
    private JFrame window;
    private JTextPane textPane;
    private JButton copyButton;

    public Gui() {
        window = new JFrame();
        textPane = new JTextPane();
        textPane.setText("A:b");

        StyleContext styleContext = StyleContext.getDefaultStyleContext();
        AttributeSet attributeSet = styleContext.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.RED);

        StyledDocument styledDocument = textPane.getStyledDocument();
        styledDocument.setCharacterAttributes(1, 1, attributeSet, false);

        copyButton = new JButton("Copy");
        copyButton.addActionListener(event -> copyHtmlTextToClipboard());

        Container contentPane = window.getContentPane();
        contentPane.add(textPane, BorderLayout.CENTER);
        contentPane.add(copyButton, BorderLayout.SOUTH);
    }

    public void copyHtmlTextToClipboard() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        HTMLEditorKit htmlEditorKit = new HTMLEditorKit();

        int startIndex = textPane.getSelectionStart();
        int endIndex = textPane.getSelectionEnd();
        int length = endIndex - startIndex;

        StyledDocument styledDocument = textPane.getStyledDocument();
        try {
            htmlEditorKit.write(outputStream, styledDocument, startIndex, length);
            outputStream.flush();

            String contents = new String(outputStream.toByteArray());
            HTMLTransferable htmlTransferable = new HTMLTransferable(contents);
            clipboard.setContents(htmlTransferable, null);
        }
        catch (IOException | BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    public void start() {
        window.pack();
        window.setLocationRelativeTo(null);
        window.setVisible(true);
    }

    public static void main(String[] args) {
        Gui gui = new Gui();
        gui.start();
    }
}

这会产生以下 UI:

HTMLTransferable 类是准系统。它在我的系统(macOS 12.6)上确实可以使用粘贴将此数据传递给 Pages 应用程序。

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

public class HTMLTransferable implements Transferable {
    private String hmtlFormattedText;
    private DataFlavor[] dataFlavors;

    public HTMLTransferable(String hmtlFormattedText) {
        this.hmtlFormattedText = hmtlFormattedText;
        this.dataFlavors = new DataFlavor[] {
                DataFlavor.allHtmlFlavor
        };
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return dataFlavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        for (DataFlavor supportedFlavor : dataFlavors) {
            if (supportedFlavor.equals(flavor)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object getTransferData(DataFlavor flavor) {
        if (flavor == DataFlavor.allHtmlFlavor) {
            return hmtlFormattedText;
        }
        return null;
    }
}

最后,HTMLEditorKit 的原始输出写入 JTextPane 的 StyledDocument 的内容。使用 span 元素将格式应用于文本。但是,每个跨度的内容都列在自己单独的行上。

<html>
  <head>
    <style>
      <!--
        p.default {
          family:Lucida Grande;
          size:4;
          bold:normal;
          italic:;
        }
      -->
    </style>
  </head>
  <body>
    <p class=default>
      <span style="font-size: 13pt; font-family: Lucida Grande">
        A
      </span>
      <span style="color: #ff0000; font-size: 13pt; font-family: Lucida Grande">
        :
      </span>
      <span style="font-size: 13pt; font-family: Lucida Grande">
        b
      </span>
    </p>
  </body>
</html>

当粘贴到 Pages 中或在网络浏览器(例如 Chrome)中查看时,这会在唯一跨度中包含的每个元素之间添加一个空格:

对于我的语法高亮代码,这增加了很多不必要的空格。所以我正在寻找删除这些空格的最佳方法。我真的不想自己解析输出的 HTML。理想情况下,HTMLEditorKit 上有一些设置或类似的东西我只是忽略了。此外,当格式化文本包含需要在最终结果中保留的空格时,它会变得更加棘手。

java swing clipboard jtextpane htmleditorkit
1个回答
0
投票

仅对

span
标签禁用换行符和缩进似乎很难,那么使用MinimalHTMLWriter输出html并禁用所有换行符和缩进怎么样?

public void copyHtmlTextToClipboard() {
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    // HTMLEditorKit htmlEditorKit = new HTMLEditorKit();
    int startIndex = textPane.getSelectionStart();
    int endIndex = textPane.getSelectionEnd();
    int length = endIndex - startIndex;
    StyledDocument styledDocument = textPane.getStyledDocument();
    try {
        // htmlEditorKit.write(outputStream, styledDocument, startIndex, length);
        OutputStreamWriter osw = new OutputStreamWriter(outputStream);
        MinimalHTMLWriter w = new MinimalHTMLWriter(
          osw, styledDocument, startIndex, length) {
            @Override
            public String getLineSeparator() {
                return "";
            }

            @Override
            protected int getIndentSpace() {
                return 0;
            }
        };
        w.write();
        osw.flush();
        String contents = outputStream.toString();
        System.out.println(contents);
        HTMLTransferable htmlTransferable = new HTMLTransferable(contents);
        clipboard.setContents(htmlTransferable, null);
    } catch (IOException | BadLocationException e) {
        throw new RuntimeException(e);
    }
}

Gui2.java

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.MinimalHTMLWriter;

public class Gui2 {
    private JFrame window;
    private JTextPane textPane;
    private JButton copyButton;

    public Gui2() {
        window = new JFrame();
        window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        textPane = new JTextPane();
        textPane.setText("A:b");

        StyleContext styleContext = StyleContext.getDefaultStyleContext();
        AttributeSet attributeSet = styleContext.addAttribute(
          SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.RED);

        StyledDocument styledDocument = textPane.getStyledDocument();
        styledDocument.setCharacterAttributes(1, 1, attributeSet, false);

        copyButton = new JButton("Copy");
        copyButton.addActionListener(event -> copyHtmlTextToClipboard());

        Container contentPane = window.getContentPane();
        contentPane.add(textPane, BorderLayout.CENTER);
        contentPane.add(copyButton, BorderLayout.SOUTH);
    }

    public void copyHtmlTextToClipboard() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        // HTMLEditorKit htmlEditorKit = new HTMLEditorKit();
        int startIndex = textPane.getSelectionStart();
        int endIndex = textPane.getSelectionEnd();
        int length = endIndex - startIndex;
        StyledDocument styledDocument = textPane.getStyledDocument();
        try {
            // htmlEditorKit.write(outputStream, styledDocument, startIndex, length);
            OutputStreamWriter osw = new OutputStreamWriter(outputStream);
            MinimalHTMLWriter w = new MinimalHTMLWriter(
              osw, styledDocument, startIndex, length) {
                @Override
                public String getLineSeparator() {
                    return "";
                }

                @Override
                protected int getIndentSpace() {
                    return 0;
                }
            };
            w.write();
            osw.flush();
            String contents = outputStream.toString();
            System.out.println(contents);
            HTMLTransferable htmlTransferable = new HTMLTransferable(contents);
            clipboard.setContents(htmlTransferable, null);
        } catch (IOException | BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    public void start() {
        window.pack();
        window.setLocationRelativeTo(null);
        window.setVisible(true);
    }

    public static void main(String[] args) {
        Gui2 gui = new Gui2();
        gui.start();
    }
}

class HTMLTransferable implements Transferable {
    private String hmtlFormattedText;
    private DataFlavor[] dataFlavors;

    public HTMLTransferable(String hmtlFormattedText) {
        this.hmtlFormattedText = hmtlFormattedText;
        this.dataFlavors = new DataFlavor[] {
            DataFlavor.allHtmlFlavor
        };
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return dataFlavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        for (DataFlavor supportedFlavor : dataFlavors) {
            if (supportedFlavor.equals(flavor)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object getTransferData(DataFlavor flavor) {
        if (flavor == DataFlavor.allHtmlFlavor) {
            return hmtlFormattedText;
        }
        return null;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.