使用PDFBox处理许多unicode字符

Ccc*_*any 3 java fonts pdfbox

我正在编写一个Java函数,该函数将String作为参数,并使用PDFBox产生PDF作为输出。

只要我使用拉丁字符,一切都可以正常工作。但是,我事先不知道输入什么,可能是英文,中文或日文字符。

在非拉丁字符的情况下,这是我得到的错误:

Exception in thread "main" java.lang.IllegalArgumentException: U+3053 ('kohiragana') is not available in this font Helvetica encoding: WinAnsiEncoding
at org.apache.pdfbox.pdmodel.font.PDType1Font.encode(PDType1Font.java:426)
at org.apache.pdfbox.pdmodel.font.PDFont.encode(PDFont.java:324)
at org.apache.pdfbox.pdmodel.PDPageContentStream.showTextInternal(PDPageContentStream.java:509)
at org.apache.pdfbox.pdmodel.PDPageContentStream.showText(PDPageContentStream.java:471)
at com.mylib.pdf.PDFBuilder.generatePdfFromString(PDFBuilder.java:122)
at com.mylib.pdf.PDFBuilder.main(PDFBuilder.java:111)
Run Code Online (Sandbox Code Playgroud)

如果我理解正确,则必须对日语使用一种特定的字体,对中文使用另一种字体,依此类推,因为我使用的(Helvetiva)字体不能处理所有必需的unicode字符。

我还可以使用处理所有这些unicode字符的字体,例如Arial Unicode。但是,该字体受特定许可使用,因此我无法使用它,也没有找到另一种。

我找到了一些想要解决此问题的项目,例如Google NOTO项目。但是,该项目提供了多个字体文件。因此,我将不得不在运行时根据我的输入选择要加载的正确文件。

因此,我面临两个选择,其中一个我不知道如何正确实现:

  1. 一直在寻找可以处理几乎所有unicode字符的字体(我急切地希望在哪里找到这个字体?

  2. 尝试检测使用哪种语言,然后根据需要选择一种字体。尽管我还不知道该怎么做,但我仍然认为它不是一个干净的实现,因为输入和字体文件之间的映射将被硬编码,这意味着我将必须对所有硬编码可能的映射。

  3. 还有其他解决方案吗?

  4. 我完全偏离轨道了吗?

在此先感谢您的帮助和指导!

这是我用来生成PDF的代码:

public static void main(String args[]) throws IOException {
    String latinText = "This is latin text";
    String japaneseText = "????????";

    // This works good
    generatePdfFromString(latinText);

    // This generate an error
    generatePdfFromString(japaneseText);
}

private static OutputStream generatePdfFromString(String content) throws IOException {
    PDPage page = new PDPage();

    try (PDDocument doc = new PDDocument();
         PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
        doc.addPage(page);
        contentStream.setFont(PDType1Font.HELVETICA, 12);

        // Or load a specific font from a file
        // contentStream.setFont(PDType0Font.load(this.doc, new File("/fontPath.ttf")), 12);

        contentStream.beginText();
        contentStream.showText(content);
        contentStream.endText();
        contentStream.close();
        OutputStream os = new ByteArrayOutputStream();
        doc.save(os);
        return os;
    }
}
Run Code Online (Sandbox Code Playgroud)

mkl*_*mkl 6

与等待字体或猜测文本的语言相比,一种更好的解决方案是使用多种字体并在逐个字形的基础上选择正确的字体。

您已经找到了Google Noto字体,它是完成此任务的很好的字体基础。

不过,不幸的是,Google仅将Noto CJK字体发布为OpenType字体(.otf),而不发布为TrueType字体(.ttf),这种策略不太可能更改,请参见。Noto字体第249期及其他。另一方面,PDFBox不支持OpenType字体,也不能在OpenType支持上积极工作,请参阅cf。PDFBOX-2482

因此,必须以某种方式将OpenType字体转换为TrueType。我只是把djmilch共享的文件放在TTF中的免费字体NOTO SANS CJK中

每个字符的字体选择

因此,您实质上需要一种方法来逐个字符检查文本字符并将其分解为大块,然后可以使用相同的字体进行绘制。

不幸的是,我没有看到PDFont比实际尝试对字符进行编码并考虑IllegalArgumentException为“ no” 更好的方法来询问PDFBox 是否知道给定字符的字形。

因此,我使用以下帮助程序类TextWithFont和方法实现了该功能fontify

class TextWithFont {
    final String text;
    final PDFont font;

    TextWithFont(String text, PDFont font) {
        this.text = text;
        this.font = font;
    }

    public void show(PDPageContentStream canvas, float fontSize) throws IOException {
        canvas.setFont(font, fontSize);
        canvas.showText(text);
    }
}
Run Code Online (Sandbox Code Playgroud)

AddTextWithDynamicFonts内部类)

List<TextWithFont> fontify(List<PDFont> fonts, String text) throws IOException {
    List<TextWithFont> result = new ArrayList<>();
    if (text.length() > 0) {
        PDFont currentFont = null;
        int start = 0;
        for (int i = 0; i < text.length(); ) {
            int codePoint = text.codePointAt(i);
            int codeChars = Character.charCount(codePoint);
            String codePointString = text.substring(i, i + codeChars);
            boolean canEncode = false;
            for (PDFont font : fonts) {
                try {
                    font.encode(codePointString);
                    canEncode = true;
                    if (font != currentFont) {
                        if (currentFont != null) {
                            result.add(new TextWithFont(text.substring(start, i), currentFont));
                        }
                        currentFont = font;
                        start = i;
                    }
                    break;
                } catch (Exception ioe) {
                    // font cannot encode codepoint
                }
            }
            if (!canEncode) {
                throw new IOException("Cannot encode '" + codePointString + "'.");
            }
            i += codeChars;
        }
        result.add(new TextWithFont(text.substring(start, text.length()), currentFont));
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

AddTextWithDynamicFonts方法)

使用范例

像这样使用上面的方法和类

String latinText = "This is latin text";
String japaneseText = "????????";
String mixedText = "T?h?i?s? ?i?s? ?latin text";

generatePdfFromStringImproved(latinText).writeTo(new FileOutputStream("Cccompany-Latin-Improved.pdf"));
generatePdfFromStringImproved(japaneseText).writeTo(new FileOutputStream("Cccompany-Japanese-Improved.pdf"));
generatePdfFromStringImproved(mixedText).writeTo(new FileOutputStream("Cccompany-Mixed-Improved.pdf"));
Run Code Online (Sandbox Code Playgroud)

AddTextWithDynamicFonts测试testAddLikeCccompanyImproved

ByteArrayOutputStream generatePdfFromStringImproved(String content) throws IOException {
    try (   PDDocument doc = new PDDocument();
            InputStream notoSansRegularResource = AddTextWithDynamicFonts.class.getResourceAsStream("NotoSans-Regular.ttf");
            InputStream notoSansCjkRegularResource = AddTextWithDynamicFonts.class.getResourceAsStream("NotoSansCJKtc-Regular.ttf")   ) {
        PDType0Font notoSansRegular = PDType0Font.load(doc, notoSansRegularResource);
        PDType0Font notoSansCjkRegular = PDType0Font.load(doc, notoSansCjkRegularResource);
        List<PDFont> fonts = Arrays.asList(notoSansRegular, notoSansCjkRegular);

        List<TextWithFont> fontifiedContent = fontify(fonts, content);

        PDPage page = new PDPage();
        doc.addPage(page);
        try (   PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
            contentStream.beginText();
            for (TextWithFont textWithFont : fontifiedContent) {
                textWithFont.show(contentStream, 12);
            }
            contentStream.endText();
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        doc.save(os);
        return os;
    }
}
Run Code Online (Sandbox Code Playgroud)

AddTextWithDynamicFonts辅助方法)

我懂了

  • 对于 latinText = "This is latin text"

    拉丁屏幕截图

  • 对于 japaneseText = "????????"

    日文屏幕截图

  • 和为 mixedText = "T?h?i?s? ?i?s? ?latin text"

    混合屏幕截图

一些助手

  • 我将字体检索为Java资源,但可以InputStream对它们使用任何一种字体。

  • 上面的字体选择机制可以很容易地与该答案中所示的换行机制及其在该答案中的对齐扩展相结合