Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,27 @@
*/
package org.apache.pdfbox.glyphlayout.awt;

import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.AbstractGlyphLayoutProcessor;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.text.PDFTextStripper;
import org.junit.jupiter.api.Test;

import java.awt.FontFormatException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Examples for bidirectional text with GlyphLayoutProcessorAwt
Expand Down Expand Up @@ -73,37 +84,111 @@ private float showLine(PDPageContentStream cs, PDType0Font[] fonts, float fontSi
return y;
}

/**
* Test, no ActualText
*
* @throws IOException
* @throws FontFormatException
* @throws URISyntaxException
*/
@Test
void testGlyphLayoutBidiNoActualText() throws IOException, FontFormatException, URISyntaxException
{
testGlyphLayoutBidi(false, "");
}

/**
* Test with ActualText
*
* @throws IOException
* @throws FontFormatException
* @throws URISyntaxException
*/
@Test
void testGlyphLayoutBidi() throws IOException, FontFormatException, URISyntaxException
void testGlyphLayoutBidiActualText() throws IOException, FontFormatException, URISyntaxException
{
GlyphLayoutProcessorAwt glyphLayoutProcessorAwt = new GlyphLayoutProcessorAwt();
testGlyphLayoutBidi(true, "_ActualText");
}

/**
* Test
* @param useActualText
* @throws IOException
* @throws FontFormatException
* @throws URISyntaxException
*/
void testGlyphLayoutBidi(boolean useActualText, String sActualText) throws IOException, FontFormatException, URISyntaxException
{
AbstractGlyphLayoutProcessor.GlyphLayoutProcessorOptions options = new AbstractGlyphLayoutProcessor.GlyphLayoutProcessorOptions();
if (useActualText)
{
options.useActualText();
}
GlyphLayoutProcessorAwt glyphLayoutProcessor = new GlyphLayoutProcessorAwt(options);

String outputBaseName = String.format("GlyphLayoutBidi%s", sActualText);
String outputPDFFilename = "target/" + outputBaseName + ".pdf";
String outputTextFilename = "target/" + outputBaseName + ".txt";

String outputName = "GlyphLayoutBidi.pdf";
String outputFilename = "target/" + outputName;
String arabicPath = "/ttf/NotoSansArabic-Regular.ttf";
String lgcPath = "/ttf/DejaVuSans.ttf";

float fontSize = 12.0f;

try (PDDocument doc = new PDDocument())
{
PDType0Font arabicFont = createPdType0Font(glyphLayoutProcessorAwt, doc, arabicPath);
PDType0Font lgcFont = createPdType0Font(glyphLayoutProcessorAwt, doc, lgcPath);
PDType0Font arabicFont = createPdType0Font(glyphLayoutProcessor, doc, arabicPath);
PDType0Font lgcFont = createPdType0Font(glyphLayoutProcessor, doc, lgcPath);

PDPage page = new PDPage();
doc.addPage(page);
try (PDPageContentStream cs = new PDPageContentStream(doc, page))
{
cs.setGlyphLayoutProcessor(glyphLayoutProcessorAwt);
cs.setGlyphLayoutProcessor(glyphLayoutProcessor);

float x = page.getBBox().getLowerLeftX() + fontSize;
float y = page.getBBox().getUpperRightY() - fontSize;

y = showLine(cs, arabicFont, fontSize, x, y, TEXT1);
showLine(cs, new PDType0Font[]{ lgcFont, arabicFont, lgcFont }, fontSize, x, y, new String[]{ TEXT2, TEXT3, TEXT4 });
}
doc.save(outputFilename);
doc.save(outputPDFFilename);
}

checkRenderIdent(outputBaseName + ".pdf");

// Extract text
try (PDDocument doc = Loader.loadPDF(new File(outputPDFFilename))) {
assertEquals(1, doc.getNumberOfPages());

PDFTextStripper stripper = new PDFTextStripper();
String s = stripper.getText(doc);
String sStripped = s.replace("\r", "").replaceAll(" +", " ")
.replace(" \n", "\n")
.strip();

String text =
TEXT1 + "\n" + TEXT2 + TEXT3 + TEXT4;

if (useActualText) {
// Not correct as of 2026-09-27
// PDFTextStripper should take the text from ActualText as is and not reorder the text
assertEquals(text, sStripped, "Extracted text should equal the written text for " + outputPDFFilename);
} else {
// Extracted text is wrong
assertEquals(text, sStripped, "Extracted text should equal the written text for " + outputPDFFilename);
}

try (OutputStream os = new FileOutputStream(outputTextFilename)) {
os.write(0xEF);
os.write(0xBB);
os.write(0xBF);

try (Writer writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
// The output is not yet correct as of 27.9.2026
writer.write(s);
}
}
}
checkRenderIdent(outputName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,31 @@
package org.apache.pdfbox.glyphlayout.awt;

import java.awt.FontFormatException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;

import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.AbstractGlyphLayoutProcessor;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;
import org.apache.pdfbox.text.PDFTextStripper;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

/*
* Example of formatting for letters defined in:
* DIN 91379: Characters and defined character sequences in Unicode for the electronic processing of names
Expand Down Expand Up @@ -84,15 +95,51 @@ class GlyphLayoutDin91379FormTest extends TestBase
+ "ƒ ʰ ʳ ˆ ˜ ˢ ᵈ ᵗ ‘ ‚ “ ” „ † … ‰ ′ ″ ‹ › ⁰ ⁴ ⁵ ⁶ ⁷ ⁸ "
+ "⁹ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ™ ∞ ≤ ≥ \n"
+ "Additional non-letters (not included in DIN 91379): – — •�";
/**
* Test, no ActualText
*
* @throws IOException
* @throws FontFormatException
* @throws URISyntaxException
*/
@Test
void testGlyphLayoutDin91379FormNoActualText() throws IOException, FontFormatException, URISyntaxException
{
testGlyphLayoutDin91379Form(false, "");
}

/**
* Test with ActualText
*
* @throws IOException
* @throws FontFormatException
* @throws URISyntaxException
*/
@Test
void testGlyphLayoutDin91379Form() throws IOException, FontFormatException, URISyntaxException
void testGlyphLayoutDin91379FormActualText() throws IOException, FontFormatException, URISyntaxException
{
GlyphLayoutProcessorAwt glyphLayoutProcessor = new GlyphLayoutProcessorAwt();
testGlyphLayoutDin91379Form(true, "_ActualText");
}

String outputName = "GlyphLayoutDIN91379Form.pdf";
String outputFilename = "target/" + outputName;
/**
* Test
* @param useActualText
* @throws IOException
* @throws FontFormatException
* @throws URISyntaxException
*/
void testGlyphLayoutDin91379Form(boolean useActualText, String sActualText) throws IOException, FontFormatException, URISyntaxException
{
AbstractGlyphLayoutProcessor.GlyphLayoutProcessorOptions options = new AbstractGlyphLayoutProcessor.GlyphLayoutProcessorOptions();
if (useActualText)
{
options.useActualText();
}
GlyphLayoutProcessorAwt glyphLayoutProcessor = new GlyphLayoutProcessorAwt(options);

String outputBaseName = String.format("GlyphLayoutDIN91379Form%s", sActualText);
String outputPDFFilename = "target/" + outputBaseName + ".pdf";
String outputTextFilename = "target/" + outputBaseName + ".txt";
String fontSizeString = "12";

InputStream fontStream = GlyphLayoutDin91379FormTest.class.getResourceAsStream("/ttf/Arimo-Regular.ttf");
Expand Down Expand Up @@ -125,8 +172,47 @@ void testGlyphLayoutDin91379Form() throws IOException, FontFormatException, URIS
}
acroForm.refreshAppearances();
acroForm.flatten();
doc.save(outputFilename);
doc.save(outputPDFFilename);
}

checkRenderIdent(outputBaseName + ".pdf");

// Extract text
try (PDDocument doc = Loader.loadPDF(new File(outputPDFFilename))) {
assertEquals(1, doc.getNumberOfPages());

PDFTextStripper stripper = new PDFTextStripper();
String s = stripper.getText(doc);
String sStripped = s.replace('\n', ' ')
.replaceAll(" +", " ")
.replace(" ", "\n")
.strip();

String expectedText = ("Test form for PDFBox glyph layout\n" + LATIN_CHARS_DIN_91379)
.replace('\n', ' ')
.replaceAll(" +", " ")
.replace(" ", "\n")
.strip();

try (OutputStream os = new FileOutputStream(outputTextFilename)) {
os.write(0xEF);
os.write(0xBB);
os.write(0xBF);

try (Writer writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
// The output without ActualText is not yet correct
writer.write(s);
}
}

if (useActualText) {
// ok with ActualText
assertEquals(expectedText, sStripped, "Extracted text should equal the written text for " + outputPDFFilename);
} else {
// not ok without ActualText
assertEquals(expectedText, sStripped, "Extracted text should equal the written text for " + outputPDFFilename);
}

}
checkRenderIdent(outputName);
}
}
Loading