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
@@ -0,0 +1,213 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.pdfbox.examples.pdmodel.glyphlayout;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.pdfbox.examples.pdmodel.BengaliPdfGenerationHelloWorld;
import org.apache.pdfbox.pdmodel.GlyphLayoutProcessor;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;

/**
* DoGlyphLayoutBengali
*
* This class is adapted from:
* BengaliPdfGenerationHelloWorld
* Inspired from <a href=
* "https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java?view=markup">PdfBox
* Example</a>. This attempts to correctly demonstrate to what extent Bengali text rendering is
* supported. We read large amount of text from a file and try to render it properly.
*
* @author Palash Ray
*
*/
public class DoGlyphLayoutBengali
{
private static final int LINE_GAP = 5;
private static final String LOHIT_BENGALI_TTF = "/org/apache/pdfbox/resources/ttf/Lohit-Bengali.ttf";
private static final String TEXT_SOURCE_FILE = "/org/apache/pdfbox/resources/ttf/bengali-samples.txt";
private static final int FONT_SIZE = 20;
private static final int MARGIN = 20;

public static void main(String[] args) {
try {
new DoGlyphLayoutBengali().test();
} catch (IOException e) {
e.printStackTrace();
}
}

public void test() throws IOException {
String filename = "DoGlyphLayoutBengali.pdf";

System.out.println("The generated pdf filename is: " + filename);

try (PDDocument doc = new PDDocument())
{
// Create GlyphLayoutProcessor load font
GlyphLayoutProcessor glyphLayoutProcessor = new GlyphLayoutProcessor();
PDType0Font font = glyphLayoutProcessor.loadFont(doc, this.getClass().
getResourceAsStream(LOHIT_BENGALI_TTF));

PDRectangle rectangle = getPageSize();
float workablePageWidth = rectangle.getWidth() - 2 * MARGIN;
float workablePageHeight = rectangle.getHeight() - 2 * MARGIN;

List<List<String>> pagedTexts = getReAlignedTextBasedOnPageHeight(
getReAlignedTextBasedOnPageWidth(getBengaliTextFromFile(), font,
workablePageWidth),
font, workablePageHeight);

for (List<String> linesForPage : pagedTexts)
{
PDPage page = new PDPage(getPageSize());
doc.addPage(page);

try (PDPageContentStream contents = new PDPageContentStream(doc, page))
{
// set GlyphLayoutProcessor for content stream
contents.setGlyphLayoutProcessor(glyphLayoutProcessor);

contents.beginText();
contents.setFont(font, FONT_SIZE);
contents.newLineAtOffset(rectangle.getLowerLeftX() + MARGIN,
rectangle.getUpperRightY() - MARGIN);

for (String line : linesForPage)
{
contents.showText(line);
contents.newLineAtOffset(0, -(FONT_SIZE + LINE_GAP));
}

contents.endText();

}
}

doc.save(filename);
}
}

private static List<List<String>> getReAlignedTextBasedOnPageHeight(List<String> originalLines,
PDFont font, float workablePageHeight)
{
final float newLineHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000
* FONT_SIZE + LINE_GAP;
List<List<String>> realignedTexts = new ArrayList<>();
float consumedHeight = 0;
List<String> linesInAPage = new ArrayList<>();
for (String line : originalLines)
{
if (newLineHeight + consumedHeight < workablePageHeight)
{
consumedHeight += newLineHeight;
}
else
{
consumedHeight = newLineHeight;
realignedTexts.add(linesInAPage);
linesInAPage = new ArrayList<>();
}

linesInAPage.add(line);
}
realignedTexts.add(linesInAPage);
return realignedTexts;
}

private static List<String> getReAlignedTextBasedOnPageWidth(List<String> originalLines,
PDFont font, float workablePageWidth) throws IOException
{
List<String> uniformlyWideTexts = new ArrayList<>();
float consumedWidth = 0;
StringBuilder sb = new StringBuilder();
for (String line : originalLines)
{
float newTokenWidth = 0;
StringTokenizer st = new StringTokenizer(line, " ", true);
while (st.hasMoreElements())
{
String token = st.nextToken();
newTokenWidth = font.getStringWidth(token) / 1000 * FONT_SIZE;
if (newTokenWidth + consumedWidth < workablePageWidth)
{
consumedWidth += newTokenWidth;
}
else
{
// add a new text chunk
uniformlyWideTexts.add(sb.toString());
consumedWidth = newTokenWidth;
sb = new StringBuilder();
}

sb.append(token);
}

// add a new text chunk
uniformlyWideTexts.add(sb.toString());
consumedWidth = newTokenWidth;
sb = new StringBuilder();
}

return uniformlyWideTexts;
}

private static PDRectangle getPageSize()
{
return PDRectangle.A4;
}

private static List<String> getBengaliTextFromFile() throws IOException
{
List<String> lines = new ArrayList<>();

try (BufferedReader br = new BufferedReader(new InputStreamReader(
BengaliPdfGenerationHelloWorld.class.getResourceAsStream(TEXT_SOURCE_FILE), StandardCharsets.UTF_8)))
{
while (true)
{
String line = br.readLine();

if (line == null)
{
break;
}

if (!line.startsWith("#"))
{
lines.add(line);
}
}
}

return lines;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.pdfbox.examples.pdmodel.glyphlayout;

import org.apache.pdfbox.pdmodel.GlyphLayoutProcessor;
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 java.io.IOException;
import java.io.InputStream;

/**
* Examples for bidirectional text with GlyphLayoutProcessor
*
* @author Volker Kunert
* @version 2026-05
*/
public class DoGlyphLayoutBidi {
public static final String TEXT1 = "نحن الآن في شهر رمضان 1447 هجري";
public static final String TEXT2 = "Guten Tag ";
public static final String TEXT3 = "السلام عليكم";
public static final String TEXT4 = " Good afternoon";

/*
* Main
*/
public static void main(String[] args) {
new DoGlyphLayoutBidi().test();
}

/*
* show one line
*/
public static float showLine(PDPageContentStream cs, PDType0Font font, float fontSize,
float x, float y, String text) throws IOException {
return showLine(cs, new PDType0Font[]{font}, fontSize, x, y, new String[]{text});
}

/*
* show one line
*/
public static float showLine(PDPageContentStream cs, PDType0Font[] fonts, float fontSize,
float x, float y, String[] texts) throws IOException {
cs.beginText();
cs.newLineAtOffset(x, y);

if (fonts.length != texts.length) {
throw new IllegalArgumentException("Size of fonts and texts is different");
}
for (int i = 0; i < texts.length; i++) {
cs.setFont(fonts[i], fontSize);
cs.showText(texts[i]);
}
cs.endText();

float height = fonts[0].getBoundingBox().getHeight();
y -= height / 1000f * fontSize;
return y;
}

/**
* Start the example
*/
public void test() {
try {
GlyphLayoutProcessor glyphLayoutProcessor = new GlyphLayoutProcessor();

String outputFilename = "DoGlyphLayoutBidi.pdf";
String arabicPath = "/org/apache/pdfbox/examples/glyphlayout/noto/NotoSansArabic-Regular.ttf";
String lgcPath = "/org/apache/pdfbox/examples/glyphlayout/noto/NotoSans-Regular.ttf";

float fontSize = 12.0f;

PDDocument pdDocument = new PDDocument();

PDType0Font arabicFont = createPdType0Font(glyphLayoutProcessor, pdDocument, arabicPath);
PDType0Font lgcFont = createPdType0Font(glyphLayoutProcessor, pdDocument, lgcPath);

PDPage blankPage = new PDPage();
pdDocument.addPage(blankPage);
PDPageContentStream cs = new PDPageContentStream(pdDocument, pdDocument.getPage(0),
PDPageContentStream.AppendMode.APPEND, true);
cs.setGlyphLayoutProcessor(glyphLayoutProcessor);

float x = blankPage.getBBox().getLowerLeftX() + fontSize;
float y = blankPage.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});
cs.close();
pdDocument.save(outputFilename);
pdDocument.close();
} catch (Exception e) {
e.printStackTrace();
}
}

/*
* Create the PDType0Font font
*/
private PDType0Font createPdType0Font(GlyphLayoutProcessor glyphLayoutProcessor, PDDocument pdDocument,
String fontPath) {
InputStream fontStream = this.getClass().
getResourceAsStream(fontPath);
PDType0Font font = glyphLayoutProcessor.loadFont(pdDocument, fontStream);
return font;
}
}
Loading