Manejar muchos carácteres Unicode con PDFBox

Cccompany:

Estoy escribiendo una función de Java, que toma una cadena como parámetro y producir un PDF como una salida con PDFBox .

Todo funciona bien siempre y cuando utilizo caracteres latinos. Sin embargo, no sé de antemano lo que será la entrada , y podría ser algo de Inglés, así como los caracteres chinos o japoneses.

En el caso de caracteres no latinos, aquí está el error que consigo:

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)

Si he entendido bien, tengo que utilizar una fuente específica para el japonés, otro para el chino y así sucesivamente, porque el que estoy usando (Helvetiva) no maneja todos los caracteres Unicode necesarias.

También podría utilizar una fuente que manejan todos estos caracteres Unicode, como Arial Unicode . Sin embargo esta fuente está bajo una licencia específica, así que no puedo usarlo y no he encontrado otro.

He encontrado algunos proyectos que quieren superar este problema, al igual que el proyecto de Google Noto . Sin embargo, este proyecto ofrece múltiples ficheros . Así que tendría que elegir, en tiempo de ejecución, el archivo correcto para cargar dependiendo de la entrada que tengo.

Así que estoy frente a 2 opciones, una de las cuales no sé cómo implementar correctamente:

  1. Mantener la búsqueda de una fuente que asa casi todos los caracteres Unicode (donde está presente grial estoy buscando desesperadamente ?!)

  2. Tratar de detectar el idioma utilizado y seleccione un tipo de letra que dependen de ella. A pesar de que no sé (aún) cómo hacer eso, no me parece que sea una aplicación limpia, ya que será codificado el mapeo entre la entrada y el archivo de fuente, lo que significa que tendrá que codificar todos las posibles asignaciones.

  3. ¿Hay alguna otra solución?

  4. Estoy completamente fuera de las pistas?

Gracias de antemano por su ayuda y guía!

Aquí está el código que utilizo para generar el 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;
    }
}
MKL:

Una solución mejor que esperar para una fuente o adivinar el lenguaje de un texto es tener una multitud de fuentes y seleccionar la fuente correcta en una base de glifo por glifo.

Ya encontrado el Google Noto Fuentes que son una colección de base bien de fuentes para esta tarea.

Lamentablemente, sin embargo, Google publica las fuentes Noto CJC sólo como fuentes OpenType (.otf), no como fuentes TrueType (.ttf), una política que no es probable que cambie, cf. Noto las fuentes emiten 249 y otros. Por otro lado PDFBox no soporta fuentes OpenType y no está trabajando activamente en el apoyo OpenType o bien, cf. PDFBox-2482 .

Por lo tanto, uno tiene que convertir la fuente OpenType alguna manera a TrueType. Yo simplemente tomé el archivo compartido por djmilch en su entrada de blog gratuito de fuente NOTO SANS CJK EN TTF .

Selección de fuente por carácter

Por lo que necesita esencialmente un método que comprueba su texto carácter a carácter y disecciona en trozos que pueden extraerse utilizando la misma fuente.

Por desgracia, no veo un mejor método para hacer una PDFBox PDFontsi se conoce un glifo para un carácter determinado que para realmente tratar de codificar el carácter y considerar un IllegalArgumentException"no".

Yo, por lo tanto, que implemente la funcionalidad utilizando la siguiente clase de ayuda TextWithFonty el método 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);
    }
}

( AddTextWithDynamicFonts clase interna)

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;
}

( AddTextWithDynamicFonts método)

Ejemplo uso

Usando el método y la clase anterior como esto

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"));

( AddTextWithDynamicFonts prueba 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;
    }
}

( AddTextWithDynamicFonts helper método)

yo obtengo

  • para latinText = "This is latin text"

    captura de pantalla de América

  • para japaneseText = "これは日本語です"

    captura de pantalla japonesa

  • y para mixedText = "Tこhれiはs日 本i語sで すlatin text"

    captura de pantalla mixta

algunos apartes

  • Recuperé las fuentes como los recursos de Java, pero se puede utilizar cualquier tipo de InputStreampara ellos.

  • El mecanismo de selección de la fuente anteriormente pueden ser fácilmente combinado con el mecanismo de línea de rotura se muestra en esta respuesta y la extensión justificación de las mismas en esta respuesta

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=201399&siteId=1
Recomendado
Clasificación