1、简介
在文档处理和数据报告中,上下标(Subscript 和 Superscript)是非常常见的格式需求,特别是在涉及数学公式、化学公式和其他需要特殊标记的领域。本文将介绍如何在 Excel 和 PDF 文档中实现上下标功能,分别使用 Apache POI 和 iText 库。
2、在 Excel 中实现上下标
我们将使用 Apache POI 库来处理 Excel 文件。Apache POI 是一个功能强大的 Java API,可以读取和写入微软的 OLE2 和 OOXML 文件格式,如 Excel、Word 和 PowerPoint。
首先,在你的 Maven 项目中添加 Apache POI 的依赖::
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.2</version>
</dependency>
实现上下标代码:
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Sheet1");
XSSFRow row = sheet.createRow(0);
XSSFCell cell = row.createCell(0);
XSSFFont font = workbook.createFont();
font.setTypeOffset(XSSFFont.SS_SUB); // 上标
font.setTypeOffset(XSSFFont.SS_SUPER);//下标
XSSFRichTextString richTextString = new XSSFRichTextString("Hcu");
richTextString.applyFont(1, 2, font); // 设置第二个字符为上标 "c"
richTextString.applyFont(2, 3, font); // 设置第三个字符为下标 "u"
cell.setCellValue(richTextString);
Path tempFile = Paths.get("E:\\dist\\pdf0.xlsx");
try(OutputStream os = Files.newOutputStream(tempFile)){
workbook.write(os);
logger.error("xssfWorkbook-end:" + tempFile.toAbsolutePath());
}
3、造字
因某些字母没有对应的上下标字形,所以通过FontCreate软件来造上下标,至于软件可以去网上下载破解版,还有就是Unicode指定的数量就那么多,所以我们可以通过改变已有Unicode编码字符来作为我们上下标的编码。
可以通过找到当前分支少的Unicode字符做插入:比如选中西里尔字母这个分类点击 插入->字符:
然后我们对已有的字符做修改和做删除自己造:
最后形成我们自己所需要的字符:
4、在 PDF 中实现上下标
生成的PDF,采用开源的是开放源码的站点sourceforge一个项目itextpdf,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。因项目通过Excel来转PDF,但是因itextpdf无法识别Excel上下标,并且缺少了关键上下标。
首先,在你的 Maven 项目中添加 iText 的依赖:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
输出中文,还要引入下面itext-asian.jar包:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
通过以上造好的字母上下标,直接通过加载指定的Unicode来实现实现加载:
String value = "设计强度ƒ\u0460\u0461";
File pdfFile = new File("E:\\dist\\pdf1.pdf");
//字符和Unicode组合格式化
value = StringEscapeUtils.unescapeJava(value);
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFile));
document.open();
// 将 Excel 单元格内容写入 PDF 文档
PdfPTable table = new PdfPTable(1);
BaseFont bf = BaseFont.createFont("E:\\cell\\Merge.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
com.itextpdf.text.Font f = new com.itextpdf.text.Font(bf, 10);
Paragraph p = new Paragraph(value, f);
table.addCell(p);
document.add(table);
document.close();
writer.close();
5、字符映射
因设计可以通过造字来实现上下标字母,现在我们可以通过指定字符来实现当前上下标的标签替换,设计上下标通过标签来包裹,类似:下标:<sub> </sub> 上标:<sup><sup>
来标签指定上下标。
通过解析当前字符串上下标标签来实现字符替换:
private static final String SUB_START = "<sub>"; //下标
private static final String SUB_END = "</sub>";
private static final String SUP_START = "<sup>"; //上标
private static final String SUP_END = "</sup>";
/**
* 获取下一对标签的index,不存在这些标签就返回null
* @param s
* @param tag SUB_START或者SUP_START
* @return int[]中有两个元素,第一个是开始标签的index,第二个元素是结束标签的index
*/
public static int[] getNextTagsIndex(String s, String tag) {
int firstStart = s.indexOf(tag);
if (firstStart > -1) {
int firstEnd = 0;
if (tag.equals(SUB_START)) {
firstEnd = s.indexOf(SUB_END);
String ssString = s.substring(firstStart, firstEnd+SUB_END.length());
if (ssString.contains(SUP_START)) {
ssString = ssString.replace(SUP_START, "").replaceAll(SUP_END, "");
firstEnd = firstStart + ssString.indexOf(SUB_END);
}
}else if (tag.equals(SUP_START)) {
firstEnd = s.indexOf(SUP_END);
String ssString = s.substring(firstStart, firstEnd+SUP_END.length());
if (ssString.contains(SUB_START)) {
ssString = ssString.replace(SUP_START, "").replaceAll(SUP_END, "");
firstEnd = firstStart + ssString.indexOf(SUP_END);
}
}
if (firstEnd > firstStart) {
return new int[] { firstStart, firstEnd };
}
}
return null;
}
/**移除下一对sub或者sup标签,返回移除后的字符串
* @param s
* @param tag SUB_START或者SUP_START
* @return
*/
public static String removeNextTags(String s, String tag) {
s = s.replaceFirst(tag, "");
if (tag.equals(SUB_START)) {
s = s.replaceFirst(SUB_END, "");
}else if (tag.equals(SUP_START)) {
s = s.replaceFirst(SUP_END, "");
}
return s;
}
/**
* 判断是不是包含sub、sup标签
* @param s
* @return
*/
public static boolean containTag(String s) {
return (s.contains(SUB_START) && s.contains(SUB_END)) || (s.contains(SUP_START) && s.contains(SUP_END));
}
/**
* 处理字符串,得到每个sub、sup标签的开始和对应的结束的标签的index
* @param s
* @param tagIndexList 传一个新建的空list进来,方法结束的时候会存储好标签位置信息。
* <br>tagIndexList.get(0)存放的sub
* <br>tagIndexList.get(1)存放的是sup
*
* @return 返回sub、sup处理完之后的字符串
*/
public static String getIndexs(String s, List<List<int[]>> tagIndexList) {
List<int[]> subs = Lists.newArrayList();
List<int[]> sups = Lists.newArrayList();
while (true) {
int[] sub_pair = getNextTagsIndex(s, SUB_START);
if (Objects.nonNull(sub_pair)) {
int firstStart = sub_pair[0];
if (firstStart > -1) {
int firstEnd = s.indexOf(SUB_END);
String startString = s.substring(0, firstStart);
String centreString = s.substring(firstStart, firstEnd);
String endString = s.substring(firstEnd, s.length());
if (centreString.contains(SUP_START)) {
centreString = centreString.replaceAll(SUP_START, "").replaceAll(SUP_END, "");
s = startString + centreString + endString;
}
}
}
int[] sup_pair = getNextTagsIndex(s, SUP_START);
if (Objects.nonNull(sup_pair)) {
int firstStart = sup_pair[0];
if (firstStart > -1) {
int firstEnd = s.indexOf(SUP_END);
String startString = s.substring(0, firstStart);
String centreString = s.substring(firstStart, firstEnd);
String endString = s.substring(firstEnd, s.length());
if (centreString.contains(SUB_START)) {
centreString = centreString.replaceAll(SUB_START, "").replaceAll(SUB_END, "");
s = startString + centreString + endString;
}
}
}
boolean subFirst = false;
boolean supFirst = false;
List a = new ArrayList();
if (Objects.nonNull(sub_pair)) {
a.add(sub_pair[0]);
}
if (Objects.nonNull(sup_pair)) {
a.add(sup_pair[0]);
}
Collections.sort(a);
if (Objects.nonNull(sub_pair)) {
if (sub_pair[0] == Integer.parseInt(a.get(0).toString())) {
subFirst = true;
}
}
if (Objects.nonNull(sup_pair)) {
if (sup_pair[0] == Integer.parseInt(a.get(0).toString())) {
supFirst = true;
}
}
if (sub_pair != null && subFirst) {
s = removeNextTags(s, SUB_START);
//<sub>标签被去掉之后,结束标签需要相应往前移动
sub_pair[1] = sub_pair[1] - SUB_START.length();
subs.add(sub_pair);
continue;
}
if (sup_pair != null && supFirst) {
s = removeNextTags(s, SUP_START);
//<sup>标签被去掉之后,结束标签需要相应往前移动
sup_pair[1] = sup_pair[1] - SUP_START.length();
sups.add(sup_pair);
continue;
}
if (sub_pair == null && sup_pair == null ) {
break;
}
}
tagIndexList.add(subs);
tagIndexList.add(sups);
return s;
}
然后我们通过获取上下标标签的下标,来完成字符的替换,至于要替换的Unicode是存在哪里,这个自己设计。
首先我们要把指定的字符转成Unicode的:
unicode = StringEscapeUtils.unescapeJava(unicode);
然后通过全局的字符builder来重新构造字符串:
StringBuilder sb = new StringBuilder(value);
unicode = StringEscapeUtils.unescapeJava(unicode);
sb.replace(pair[0], pair[1], unicode);
6、结论
本文介绍了如何使用 Apache POI 和 iText 库在 Excel 和 PDF 文件中实现上下标功能。通过这些库,你可以灵活地处理各种文档格式,满足不同的业务需求。希望本文对你有所帮助!
评论区