Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3f63544
GlyphLayoutProcessor for correct glyph layout and support of DIN 91379
Apr 5, 2026
e7b0027
Merge branch 'apache:trunk' into trunk
vk-github18 Apr 6, 2026
ebf5a94
Merge branch 'apache:trunk' into trunk
vk-github18 Apr 7, 2026
d0080e6
Corrections, added showText() state validation, delete superfluous me…
May 19, 2026
c6f0625
Add ligatures and kerning
May 23, 2026
c7371b2
Add ligatures and kerning
May 23, 2026
08197bd
Merge remote-tracking branch 'origin/trunk-05-24' into trunk-05-24
May 24, 2026
e4e591f
Add line end to .gitignore
vk-github18 May 24, 2026
884fbcb
Added ligatures and kerning
vk-github18 May 24, 2026
c49cdee
Bidirectional text
May 25, 2026
b49411c
Merge remote-tracking branch 'origin/trunk-05-24' into trunk-05-24
May 25, 2026
202c67e
Bidirectional text
vk-github18 May 25, 2026
1e0889e
More examples, Exception if glyph is missing
May 27, 2026
8cda044
More examples, Exception if glyph is missing
vk-github18 May 27, 2026
9c0b39d
simplified exception message
May 28, 2026
8ad3618
Merge pull request #4 from vk-github18/trunk-05-27
vk-github18 May 28, 2026
9bc1d9a
changed <p/> to <p>
May 31, 2026
348f56d
Merge pull request #5 from vk-github18/trunk-05-31-2
vk-github18 May 31, 2026
a26da4e
use try with ressources, rethink public/protected, add Null checks, d…
Jun 1, 2026
7641621
use try with ressources, rethink public/protected, add Null checks, d…
vk-github18 Jun 1, 2026
e71586d
javadoc
Jun 1, 2026
e67461b
javadoc additions
vk-github18 Jun 1, 2026
75544fe
refactorings
Jun 2, 2026
8f32ce2
renaming
Jun 2, 2026
0259b4f
refactorings
Jun 3, 2026
795e804
refactorings
Jun 3, 2026
43532de
Merge remote-tracking branch 'origin/trunk-05-31-2' into trunk-05-31-2
Jun 3, 2026
b59a31c
refactorings
Jun 3, 2026
da83fdf
Merge remote-tracking branch 'origin/trunk-05-31-2' into trunk-05-31-2
Jun 3, 2026
c26ea01
no import of java.awt.*
Jun 3, 2026
aa4d263
Merge pull request #8 from vk-github18/trunk-05-31-2
vk-github18 Jun 3, 2026
556ba0c
Use Glyphvector method to determine if positioning is needed, other s…
Jun 4, 2026
f4dd0d0
Use Glyphvector method to determine if positioning is needed, other s…
vk-github18 Jun 4, 2026
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,215 @@
/*
* 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.examples.pdmodel.BengaliPdfGenerationHelloWorld;
import org.apache.pdfbox.pdmodel.GlyphLayoutProcessorAwt;
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;

import java.awt.FontFormatException;
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;

/**
* DoGlyphLayoutBengali
* <p>
* 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 (Exception e) {
e.printStackTrace();
}
}

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

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

try (PDDocument doc = new PDDocument())
{
// Create GlyphLayoutProcessorAwt load font
GlyphLayoutProcessorAwt glyphLayoutProcessorAwt = new GlyphLayoutProcessorAwt();
PDType0Font font = glyphLayoutProcessorAwt.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 GlyphLayoutProcessorAwt for content stream
contents.setGlyphLayoutProcessor(glyphLayoutProcessorAwt);

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,126 @@
/*
* 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.GlyphLayoutProcessorAwt;
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.awt.FontFormatException;
import java.io.IOException;
import java.io.InputStream;

/**
* Examples for bidirectional text with GlyphLayoutProcessorAwt
*
* @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 {
GlyphLayoutProcessorAwt glyphLayoutProcessorAwt = new GlyphLayoutProcessorAwt();

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(glyphLayoutProcessorAwt, pdDocument, arabicPath);
PDType0Font lgcFont = createPdType0Font(glyphLayoutProcessorAwt, pdDocument, lgcPath);

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

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(GlyphLayoutProcessorAwt glyphLayoutProcessorAwt, PDDocument pdDocument,
String fontPath) throws IOException, FontFormatException {
InputStream fontStream = this.getClass().
getResourceAsStream(fontPath);
PDType0Font font = glyphLayoutProcessorAwt.loadFont(pdDocument, fontStream);
return font;
}
}
Loading