我在 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 上有一些设置或类似的东西我只是忽略了。此外,当格式化文本包含需要在最终结果中保留的空格时,它会变得更加棘手。
仅对
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;
}
}