Как создать PDF-файл с текстом и изображениями

Следующий фрагмент создает PDF-файл с текстом, но изображение искажено вот так

Кажется, требуемое изображение должно быть .jpg. Когда я использовал .png, в PDF-файле не отображалось даже искаженное изображение.

public static void createPdf() {
    try {
        //Image image = Image.createImage("/icon.png");
        Image image = Image.createImage("/Logo.jpg");
        EncodedImage encodedImage = EncodedImage.createFromImage(image, true);
        byte[] imageData = encodedImage.getImageData();

        StringBuilder content = new StringBuilder();
        content.append("%PDF-1.4\n");
        content.append("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
        content.append("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
        content.append("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
        content.append("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R>>>>endobj\n");
        content.append("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
        content.append("6 0 obj<</Length 219>>stream\n");
        content.append("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
        content.append("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
        content.append("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
        content.append("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
        content.append("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
        content.append("512 0 0 512 4 50 cm\n");
        content.append("/Im0 Do\n");
        content.append("endstream\n");
        content.append("endobj\n");
        content.append("7 0 obj<</Type/XObject/Subtype/Image/Width 512/Height 512/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length 132959>>stream\n");
        
        content.append(new String(imageData));

        content.append("endstream\n");
        content.append("endobj\n");
        content.append("\n");
        content.append("xref\n");
        content.append("0 8\n");
        content.append("0000000000 65535 f \n");
        content.append("0000000009 00000 n \n");
        content.append("0000000052 00000 n \n");
        content.append("0000000102 00000 n \n");
        content.append("0000000197 00000 n \n");
        content.append("0000000255 00000 n \n");
        content.append("0000000316 00000 n \n");
        content.append("0000000609 00000 n \n");
        content.append("trailer\n");
        content.append("<</Size 8/Root 1 0 R>>\n");
        content.append("startxref\n");
        content.append("133724\n");
        content.append("%%EOF\n");
        
        FileSystemStorage fss = FileSystemStorage.getInstance();
        String pdfPath = fss.getAppHomePath() + "Test2.pdf";
        try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath))) {
            w.write(content.toString());
        } catch (Exception e) {
            Log.p("Error " + e);
        }
    } catch (Exception e) {
        Log.p("Error " + e);
    }
}

Сгенерированный PDF-файл сохраняется в папке хранилища приложения home/.cn1

Как можно извлечь из изображения необходимые необработанные данные изображения, чтобы изображение отображалось без искажений?

Обновлено: этот вопрос немного отличается от этого, поскольку мне нужно, чтобы двоичные данные изображения были сгенерированы/созданы и вставлены в PDF-файл программно, а не вручную.

РЕДАКТИРОВАТЬ

Написание содержимого PDF напрямую без добавления результатов StringBuilder в этот Test7.pdf. То есть

private void createPdf7() {
    try {
        FileSystemStorage fss = FileSystemStorage.getInstance();
        String pdfPath = fss.getAppHomePath() + "Test7.pdf";
        try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath))) {

            w.write("%PDF-1.4\n");
            w.write("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
            w.write("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
            w.write("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
            w.write("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R>>>>endobj\n");
            w.write("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
            w.write("6 0 obj<</Length 219>>stream\n");
            w.write("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
            w.write("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
            w.write("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
            w.write("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
            w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
            w.write("512 0 0 512 4 50 cm\n");
            w.write("/Im0 Do\n");
            w.write("endstream\n");
            w.write("endobj\n");
            w.write("7 0 obj<</Type/XObject/Subtype/Image/Width 50/Height 50/ColorSpace/DeviceRGB/BitsPerComponent 8/Length 132959>>stream\n");

            InputStream is = Display.getInstance().getResourceAsStream(this.getClass(), "/Logo.jpg");
            //InputStream is = Display.getInstance().getResourceAsStream(this.getClass(), "/icon.jpeg");
            //InputStream is = Display.getInstance().getResourceAsStream(this.getClass(), "/icon.png");

            int nextChar = is.read();
            if (nextChar == -1) {
                //return null;
            }

            while (nextChar > -1) {
                //Log.p("Char " + (char) nextChar);
                char[] charArray = {(char) nextChar};
                w.write(charArray);
                nextChar = is.read();
            }
            w.write("\nendstream\n");
            w.write("endobj\n");
            w.write("\n");
            w.write("xref\n");
            w.write("0 8\n");
            w.write("0000000000 65535 f \n");
            w.write("0000000009 00000 n \n");
            w.write("0000000052 00000 n \n");
            w.write("0000000102 00000 n \n");
            w.write("0000000197 00000 n \n");
            w.write("0000000255 00000 n \n");
            w.write("0000000316 00000 n \n");
            w.write("0000000609 00000 n \n");
            w.write("trailer\n");
            w.write("<</Size 8/Root 1 0 R>>\n");
            w.write("startxref\n");
            w.write("133724\n");
            w.write("%%EOF\n");

        } catch (Exception e) {
            Log.p("Error " + e);
        }
    } catch (Exception e) {
        Log.p("Error " + e);
    }
}

Чего мне еще не хватает, чтобы изображение отображалось правильно?

РЕДАКТИРОВАТЬ Открытие этого JSjpegSample-with image.pdf с помощью текстового редактора Linux показывает следующую кодировку потока данных изображения. Обратите внимание на читаемый текст, например JFIFPhotoshop 7.0.

В симуляторе Codename One эта кодировка близка к кодировке файла базы данных SQLite. База данных SQLite, открытая с помощью текстового редактора Linux, показывает следующее. Обратите внимание на читаемый текст, например SQLite format 3CREATE TABLE.

Как можно добавить .jpeg в PDF-файл так же, как файл базы данных SQLite?

Этот вопрос похож на: Как вставить изображение в исходный код PDF. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.

Luuk 14.07.2024 11:10

Информации о размере изображения, которое использует Эрик, мало или вообще нет. Итак, как кто-нибудь узнает, правильно ли это? Type/XObject/Subtype/Image/Width 512/Height 512

Luuk 14.07.2024 11:41

Я думаю, что @KJ прав. Я предлагаю вместо создания строки и использования добавления писать непосредственно в InputStream. Преобразуйте начальную часть в байты UTF-8, а затем запишите байты изображения вручную. Затем добавьте остальную часть текста в виде массива байтов.

Shai Almog 15.07.2024 03:17

В блокноте я только что открыл эти PDF-файлы , которыми вы поделились, и сравнил их с моим последним Test7.pdf, которым я поделился выше. Кажется, данные изображения почти одинаковы. Возможно ли, что с данными изображения теперь все в порядке, а причина искажения изображения заключается в чем-то другом?

Eric 16.07.2024 21:46

Блокнот находится в ОС Windows. Однако в текстовом редакторе Linux поток изображений, которым вы поделились, начинается с \FF\D8\FF\E0\00JFIF\00\00H\00H\00\00\FF\E1wExif\00\00MM\00*\‌​00\00\00\00, но то, чем я поделился, начинается с ÿØÿà JFIF H H ÿØÿà wExif MM * b j( 1 .

Eric 16.07.2024 22:09

@Eric JFIF — это стандартный заголовок JPEG. Судя по перечисленным вами байтам, PDF кодирует байты, а не записывает их напрямую. Я недостаточно знаком с кодировкой, но похоже, что код пишет \FF вместо байтового значения 255 и т. д. Кажется, что он записывает допустимые символы диапазона ascii, такие как JFIF, «как есть». Это должно быть довольно просто закодировать, проверив диапазон и используя Integer.toHex().

Shai Almog 17.07.2024 03:11

@KJ Это не «ложь». Значения записываются в виде байтов, а не кодируются. Простым решением, вероятно, было бы записать каждый байт как stream.write("\"); stream.write(Integer.toHexString(byteValue & 0xff)). Это упрощенное решение может работать и так.

Shai Almog 18.07.2024 03:22

@ShaiAlmog записывает байты, используя byte[] imageArr = encodedImage.getImageData(); for (int i = 0; i < imageArr.length; i++) { w.write("\\"); w.write(Integer.toHexString(imageArr[i] & 0xff)); } результаты в этот Test8HexDecode.pdf, где изображение все еще искажено.

Eric 18.07.2024 09:46

@KJ Я не знаю, так как понятия не имею о формате PDF. Однако похоже, что файл должен быть двоичным, и вы используете Writer вместо потока. Писатель всегда будет манипулировать данными и кодировать символы Юникода определенным образом. Я предполагаю, что вы откуда-то взяли этот код. Если у вас есть ссылка на исходный код, я мог бы помочь с эквивалентом Java.

Shai Almog 19.07.2024 04:26

Кодировка @KJ Windows-1252, установленная в этом примере, пытается, но некоторые шестнадцатеричные числа читаются как ?. Но это натолкнуло меня на мысль попробовать другие кодировки, например ISO-8859-1, которые сработали. Спасибо, что приняли участие в обсуждении и поделились знаниями о PDF.

Eric 19.07.2024 15:52

И найдите ссылку (например: Как просмотреть внутреннюю структуру PDF в Adobe Acrobat?), чтобы узнать, как изображения являются частью структуры PDF.

Luuk 19.07.2024 20:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
11
163
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Следующее успешно добавляет изображение в документ PDF. Требования включают в себя:

  1. Создайте изображение jpeg, используя EncodedImage.createFromImage(image, true); Это полезно, поскольку изображения png можно конвертировать в jpeg.

  2. Создайте ByteArrayInputStream из байтов изображения. Используя ByteArrayInputStream, байты изображения напрямую записываются как символы в PDF-файл.

  3. Установите кодировку ISO-8859-1 в OutputStreamWriter. Эта кодировка гарантировала, что все символы в байтах изображения читаются так, как ожидалось Filter/DCTDecode.

    Кодировка Windows-1252 также пытается, но некоторые шестнадцатеричные числа читаются как ?.


    private void createPdf11() {
        try {
            //Image image = Image.createImage("/Logo.jpg");
            Image image = Image.createImage("/android_logo.png");
            //Image image = Image.createImage("/ios_logo.png");
            
            //create a jpeg encoded image
            EncodedImage encodedImage = EncodedImage.createFromImage(image, true);
            byte[] imageData = encodedImage.getImageData();

            //get image width, height & length
            int imageWidth = encodedImage.getWidth();
            int imageHeight = encodedImage.getHeight();
            int imageLength = imageData.length;
            
            //create ByteArrayInputStream from image byte array
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);

            FileSystemStorage fss = FileSystemStorage.getInstance();
            String pdfPath = fss.getAppHomePath() + "Test12.pdf";
            try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath), "ISO-8859-1")) {
                w.write("%PDF-1.7\n");
                w.write("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
                w.write("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
                w.write("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
                w.write("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R>>>>endobj\n");
                w.write("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
                w.write("6 0 obj<</Length 219>>stream\n");
                w.write("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
                w.write("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
                w.write("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
                w.write("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
                w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
                w.write("50 0 0 50 4 540 cm\n");
                w.write("/Im0 Do\n");
                w.write("endstream\n");
                w.write("endobj\n");
                w.write("7 0 obj<</Type/XObject/Subtype/Image/Width " + imageWidth + "/Height " + imageHeight + "/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length " + imageLength + ">>stream\n");

                //read the next byte of data from ByteArrayInputStream in int range 0 - 255
                int nextChar = byteArrayInputStream.read();
                while (nextChar > -1) {
                    //convert int to char and add it to char array
                    char[] charArray = {(char) nextChar};
                    //write char array
                    w.write(charArray);
                    nextChar = byteArrayInputStream.read();
                }

                w.write("\nendstream\n");
                w.write("endobj\n");
                w.write("\n");
                w.write("xref\n");
                w.write("0 8\n");
                w.write("0000000000 65535 f \n");
                w.write("0000000009 00000 n \n");
                w.write("0000000052 00000 n \n");
                w.write("0000000102 00000 n \n");
                w.write("0000000197 00000 n \n");
                w.write("0000000255 00000 n \n");
                w.write("0000000316 00000 n \n");
                w.write("0000000609 00000 n \n");
                w.write("trailer\n");
                w.write("<</Size 8/Root 1 0 R>>\n");
                w.write("startxref\n");
                w.write("133724\n");
                w.write("%%EOF\n");

            } catch (Exception e) {
                Log.p("Error " + e);
            }
        } catch (Exception e) {
            Log.p("Error " + e);
        }
    }


При этом создается Test11.pdf с текстом и изображением в каталоге домашнего пути приложения home/.cn1

Чтобы добавить второе изображение, необходим другой объект, содержащий его, например 8 0 obj. Этот номер объекта затем добавляется к 4 0 obj как /Im1 8 0 R. Наконец, положение второго изображения также устанавливается в 6 0 obj как w.write("q 50 0 0 50 4 460 cm /Im1 Do Q\n");. То есть,

private void createPdf13() {
    try {
        Image image = Image.createImage("/Logo.jpg");
        //create a jpeg encoded image
        EncodedImage encodedImage = EncodedImage.createFromImage(image, true);
        byte[] imageData = encodedImage.getImageData();

        int imageWidth = encodedImage.getWidth();
        int imageHeight = encodedImage.getHeight();
        int imageLength = imageData.length;
        //create ByteArrayInputStream from image byte array
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);

        Image image2 = Image.createImage("/android_logo.png");
        //create a jpeg encoded image
        EncodedImage encodedImage2 = EncodedImage.createFromImage(image2, true);
        byte[] imageData2 = encodedImage2.getImageData();
        int imageWidth2 = encodedImage2.getWidth();
        int imageHeight2 = encodedImage2.getHeight();
        int imageLength2 = imageData2.length;
        //create ByteArrayInputStream from image byte array
        ByteArrayInputStream byteArrayInputStream2 = new ByteArrayInputStream(imageData2);

        FileSystemStorage fss = FileSystemStorage.getInstance();
        String pdfPath = fss.getAppHomePath() + "Test13.pdf";
        try (Writer w = new OutputStreamWriter(fss.openOutputStream(pdfPath), "ISO-8859-1")) {
            w.write("%PDF-1.7\n");
            w.write("1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n");
            w.write("2 0 obj<</Type/Pages/Kids [3 0 R]/Count 1>>endobj\n");
            w.write("3 0 obj<</Type/Page/Parent 2 0 R/Resources 4 0 R/MediaBox [0 0 520 800]/Contents 6 0 R>>endobj\n");
            w.write("4 0 obj<</Font<</F1 5 0 R>>/XObject<</Im0 7 0 R/Im1 8 0 R>>>>endobj\n");
            w.write("5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n");
            w.write("6 0 obj<</Length 219>>stream\n");
            w.write("q\n");
            w.write("BT /F1 24 Tf 175 720 Td (Codename One)Tj ET\n");
            w.write("BT /F1 20 Tf 100 700 Td (Using one codebase, build)Tj ET\n");
            w.write("BT /F1 20 Tf 0 660 Td (Android apps)Tj ET\n");
            w.write("BT /F1 20 Tf 0 640 Td (iOS apps)Tj ET\n");
            w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");
            w.write("q 50 0 0 50 4 540 cm /Im0 Do Q\n");
            w.write("q 50 0 0 50 4 460 cm /Im1 Do Q\n");
            w.write("Q\n");
            w.write("endstream\n");
            w.write("endobj\n");

            w.write("7 0 obj<</Type/XObject/Subtype/Image/Width " + imageWidth + "/Height " + imageHeight + "/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length " + imageLength + ">>stream\n");
            //read the next byte of data from ByteArrayInputStream in int range 0 - 255
            int nextChar = byteArrayInputStream.read();
            while (nextChar > -1) {
                //convert int to char and add it to char array
                char[] charArray = {(char) nextChar};
                //write char array
                w.write(charArray);
                nextChar = byteArrayInputStream.read();
            }
            w.write("\nendstream\n");
            w.write("endobj\n");

            w.write("8 0 obj<</Type/XObject/Subtype/Image/Width " + imageWidth2 + "/Height " + imageHeight2 + "/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length " + imageLength2 + ">>stream\n");
            //read the next byte of data from ByteArrayInputStream in int range 0 - 255
            int nextChar2 = byteArrayInputStream2.read();
            while (nextChar2 > -1) {
                //convert int to char and add it to char array
                char[] charArray2 = {(char) nextChar2};
                //write char array
                w.write(charArray2);
                nextChar2 = byteArrayInputStream2.read();
            }
            w.write("\nendstream\n");
            w.write("endobj\n");

            w.write("\n");
            w.write("xref\n");
            w.write("0 9\n");
            w.write("0000000000 65535 f \n");
            w.write("0000000009 00000 n \n");
            w.write("0000000052 00000 n \n");
            w.write("0000000102 00000 n \n");
            w.write("0000000197 00000 n \n");
            w.write("0000000255 00000 n \n");
            w.write("0000000316 00000 n \n");
            w.write("0000000609 00000 n \n");
            w.write("trailer\n");
            w.write("<</Size 9/Root 1 0 R>>\n");
            w.write("startxref\n");
            w.write("133724\n");
            w.write("%%EOF\n");

        } catch (Exception e) {
            Log.p("Error " + e);
        }
    } catch (Exception e) {
        Log.p("Error " + e);
    }
}

Что генерирует Test13.pdf

Я могу программно получить длину массива байтов изображения, равную 21223. Я также могу программно установить ее как длину потока изображений. Я также могу программно получить и установить высоту и ширину изображения.

Eric 19.07.2024 19:03

Что такое значение 746? Что такое значение 19?

Eric 19.07.2024 19:04

Просто чтобы подтвердить, понимаю ли я. Вы имеете в виду 746 = длина в байтах от строки w.write("%PDF-1.7\n") до строки w.write("BT /F1 20 Tf 0 600 Td (UWP apps)Tj ET\n");

Eric 19.07.2024 19:48

Вы имеете в виду 19 = длина байта от строки w.write("\nendstream\n") до строки w.write("\n");?

Eric 19.07.2024 19:50

@KJ Мне еще предстоит рассчитать и установить реальный размер xref. Но прямо сейчас я отредактировал ответ, установив фактическую ширину, высоту и длину изображения в сгенерированный PDF-файл. Например. Test12.pdf

Eric 20.07.2024 17:49

Тем временем я также безуспешно пытаюсь добавить несколько изображений в один и тот же PDF-файл. Я попытался создать 2 потока изображений, но отображается только 1-е изображение. Как добавить несколько изображений?

Eric 20.07.2024 17:51

@KJ Я следую вашим инструкциям по добавлению второго изображения, добавив 8-й объект, но отображается только первое изображение, как в этом Test13.pdf . Я также пытался добавить еще один объект перед вторым изображением, но только первое изображение отображается, как в этом Test14.pdf. Что не так в этих pdf? Можете ли вы отредактировать PDF-файл и исправить неправильные части?

Eric 20.07.2024 20:33
Writer класс не имеет функции для чтения длины записанных байтов. Мне придется придумать, как это прочитать.
Eric 20.07.2024 20:43

Спасибо за помощь и совет. Оба теперь успешно генерируются с несколькими изображениями. Я отредактирую ответ, чтобы добавить эту функцию. Я также поработаю над расчетом фактической xref длины.

Eric 20.07.2024 22:14

@KJ Я создал этот отдельный вопрос, чтобы обсудить, как рассчитать xref длину.

Eric 21.07.2024 10:46

Спасибо. Я посмотрю на это Тест 20 вопросов.pdf

Eric 21.07.2024 10:51

Другие вопросы по теме