Author: tilman
Date: Mon Aug 10 11:01:35 2026
New Revision: 1937028
Log:
PDFBOX-4951: Refactoring, separate Bidi from showText, introduce
glyphLayoutProcessor.getStringWidth, by Volker Kunert; closes #493
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/AbstractGlyphLayoutProcessor.java
(contents, props changed)
Modified:
pdfbox/trunk/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutDin91379Test.java
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutSMPTest.java
pdfbox/trunk/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutDin91379Test.java
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutLigaturesAndKerningTest.java
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutSMPTest.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java
Modified:
pdfbox/trunk/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.pdfbox.glyphlayout.awt;
@@ -23,11 +24,12 @@ import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InputStream;
-import java.text.Bidi;
import java.util.Objects;
+import org.apache.pdfbox.pdmodel.AbstractGlyphLayoutProcessor;
import org.apache.pdfbox.pdmodel.ContentStreamForGlyphLayoutInterface;
import org.apache.pdfbox.pdmodel.GlyphLayoutProcessorInterface;
import org.apache.pdfbox.pdmodel.GlyphsAndPositions;
@@ -43,7 +45,7 @@ import org.apache.pdfbox.pdmodel.font.PD
*
* @author Volker Kunert
*/
-public class GlyphLayoutProcessorAwt implements GlyphLayoutProcessorInterface
+public class GlyphLayoutProcessorAwt extends AbstractGlyphLayoutProcessor
implements GlyphLayoutProcessorInterface
{
private final GlyphLayoutFontLoaderAwt glyphLayoutFontLoaderAwt;
@@ -132,7 +134,7 @@ public class GlyphLayoutProcessorAwt imp
*
* @param pdDocument document
* @param inputStream of the font
- * @param fontOptions
+ * @param fontOptions options for font loading
*
* @return a PDType0Font font.
*
@@ -154,7 +156,7 @@ public class GlyphLayoutProcessorAwt imp
* @param fontOptions options for font
*
* @return a PDType0Font font.
- *
+ *
* @throws IOException if font can not be loaded
* @throws FontFormatException if the font is bad
*/
@@ -194,60 +196,19 @@ public class GlyphLayoutProcessorAwt imp
}
/**
- * Shows a text using glyph positioning (if needed)
- *
- * @param contentStream the content stream
+ * Compute the string width for a unidirectional string
* @param font to be used
* @param fontSize font size
- * @param text text to show
- *
- * @throws IOException if an I/O exception occurs
- * @throws IllegalArgumentException if glyphs are missing
+ * @param text text
+ * @param bidiLevel Bidi Level
+ * @return string width
*/
@Override
- public void showText(ContentStreamForGlyphLayoutInterface contentStream,
PDType0Font font, float fontSize, String text) throws IOException
+ protected float getStringWidthUni(PDType0Font font, float fontSize, String
text, int bidiLevel)
{
- Objects.requireNonNull(text, "Text must be set");
-
- if (Bidi.requiresBidi(text.toCharArray(), 0, text.length()))
- {
- Bidi bidi = new Bidi(text, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
- if (bidi.isMixed())
- {
- // Split and Reorder
- // See PDFTextStripper.handleDirection
- // collect individual bidi information
- int runCount = bidi.getRunCount();
- byte[] levels = new byte[runCount];
- Integer[] runs = new Integer[runCount];
-
- for (int i = 0; i < runCount; i++)
- {
- levels[i] = (byte) bidi.getRunLevel(i);
- runs[i] = i;
- }
- // reorder individual parts based on their levels
- Bidi.reorderVisually(levels, 0, runs, 0, runCount);
-
- for (int i = 0; i < runCount; i++)
- {
- int index = runs[i];
- int start = bidi.getRunStart(index);
- int limit = bidi.getRunLimit(index);
- int bidiLevel = levels[index];
- String part = text.substring(start, limit);
- showTextUni(contentStream, font, fontSize, part,
bidiLevel);
- }
- }
- else
- {
- showTextUni(contentStream, font, fontSize, text,
bidi.getBaseLevel());
- }
- }
- else
- {
- showTextUni(contentStream, font, fontSize, text,
Bidi.DIRECTION_LEFT_TO_RIGHT);
- }
+ GlyphVector glyphVector = computeGlyphVector(font, fontSize, text,
bidiLevel);
+ Rectangle2D rect = glyphVector.getLogicalBounds();
+ return (float) rect.getWidth();
}
/**
@@ -261,13 +222,14 @@ public class GlyphLayoutProcessorAwt imp
* @throws IOException if an IO-exception occurs
* @throws IllegalArgumentException if glyphs are missing
*/
+ @Override
protected void showTextUni(ContentStreamForGlyphLayoutInterface
contentStream, PDType0Font font, float fontSize, String text, int bidiLevel)
throws IOException
{
Objects.requireNonNull(text, "Text must be set");
Objects.requireNonNull(contentStream, "contentStream must be set");
GlyphVector glyphVector = computeGlyphVector(font, fontSize, text,
bidiLevel);
-
+
// check for adjustment not needed:
// glyphVector.getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS is
always true
// because of horizontal adjustments in every string except one
character string
Modified:
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutDin91379Test.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutDin91379Test.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutDin91379Test.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -150,7 +150,7 @@ class GlyphLayoutDin91379Test extends Te
float x, float y, String s) throws IOException
{
- s = s.replaceAll("\t", " ");
+ s = s.replace("\t", " ");
String[] lines = s.split("[\n]");
for (String line : lines)
Modified:
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -17,16 +17,6 @@
package org.apache.pdfbox.glyphlayout.awt;
-/**
- * Examples for ligatures and kerning
- * See <a
href="https://issues.apache.org/jira/browse/PDFBOX-4951">PDFBOX-4951</a>
- *
- * The default processing of GlyphLayoutProcessor is with ligatures and
kerning disabled.
- * You can enable ligatures and kerning using FontOptions, see below.
- *
- * @author Volker Kunert
- */
-
import java.awt.FontFormatException;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -40,6 +30,16 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
+/**
+ * Examples for ligatures and kerning
+ * See <a
href="https://issues.apache.org/jira/browse/PDFBOX-4951">PDFBOX-4951</a>
+ *
+ * The default processing of GlyphLayoutProcessor is with ligatures and
kerning disabled.
+ * You can enable ligatures and kerning using FontOptions, see below.
+ *
+ * @author Volker Kunert
+ * @author Tilman Hausherr
+ */
class GlyphLayoutLigaturesAndKerningTest extends TestBase
{
static final String FIRACODE_STRING = "!= == === >= <=";
@@ -52,7 +52,7 @@ class GlyphLayoutLigaturesAndKerningTest
* Check that missing glyph is caught like in main pdfbox.
*
* @throws IOException
- * @throws FontFormatException
+ * @throws FontFormatException
*/
@Test
void testMissingGlyph() throws IOException, FontFormatException
@@ -63,8 +63,8 @@ class GlyphLayoutLigaturesAndKerningTest
try (PDDocument doc = new PDDocument())
{
- PDType0Font lohitBengaliFont =
createPdType0Font(glyphLayoutProcessor, doc, lohitBengaliPath,
- new GlyphLayoutFontLoaderAwt.FontOptions());
+ PDType0Font lohitBengaliFont =
createPdType0Font(glyphLayoutProcessor, doc, lohitBengaliPath,
+ new GlyphLayoutFontLoaderAwt.FontOptions());
PDPage page = new PDPage();
doc.addPage(page);
@@ -72,7 +72,7 @@ class GlyphLayoutLigaturesAndKerningTest
{
cs.setGlyphLayoutProcessor(glyphLayoutProcessor);
- IllegalArgumentException ex =
assertThrows(IllegalArgumentException.class, () ->
+ IllegalArgumentException ex =
assertThrows(IllegalArgumentException.class, () ->
showComposites(cs, lohitBengaliFont, 1, 0, 0,
"123ABC"));
assertEquals("Missing glyph in font 'Lohit Bengali' for the
character 'A', codePoint: 65 (U+0041).", ex.getMessage());
@@ -80,7 +80,7 @@ class GlyphLayoutLigaturesAndKerningTest
}
}
}
-
+
@Test
void testLigaturesAndKerning() throws IOException, FontFormatException,
URISyntaxException
{
@@ -100,15 +100,15 @@ class GlyphLayoutLigaturesAndKerningTest
PDType0Font firaFont = createPdType0Font(glyphLayoutProcessor,
doc, firaPath);
PDType0Font firaLigFont = createPdType0Font(glyphLayoutProcessor,
doc, firaPath,
new
GlyphLayoutFontLoaderAwt.FontOptions().setLigaturesOn());
-
+
PDType0Font dejavuFont = createPdType0Font(glyphLayoutProcessor,
doc, dejavuPath);
-
+
PDType0Font dejavuLigFont =
createPdType0Font(glyphLayoutProcessor, doc, dejavuPath,
new
GlyphLayoutFontLoaderAwt.FontOptions().setLigaturesOn());
-
+
PDType0Font dejavuKernFont =
createPdType0Font(glyphLayoutProcessor, doc, dejavuPath,
new GlyphLayoutFontLoaderAwt.FontOptions().setKerningOn());
-
+
PDType0Font dejavuLigKernFont =
createPdType0Font(glyphLayoutProcessor, doc, dejavuPath,
new
GlyphLayoutFontLoaderAwt.FontOptions().setLigaturesOn().setKerningOn());
@@ -123,7 +123,7 @@ class GlyphLayoutLigaturesAndKerningTest
try (PDPageContentStream cs = new PDPageContentStream(doc, page))
{
cs.setGlyphLayoutProcessor(glyphLayoutProcessor);
-
+
float x = page.getBBox().getLowerLeftX() + fontSize;
float y = page.getBBox().getUpperRightY() - fontSize;
y = showComposites(cs, firaFont, fontSize, x, y,
FIRACODE_STRING);
@@ -154,11 +154,11 @@ class GlyphLayoutLigaturesAndKerningTest
* break the text into lines and show them
*/
private float showComposites(PDPageContentStream cs, PDType0Font font,
float fontSize,
- float x, float y, String s) throws IOException
+ float x, float y, String s) throws IOException
{
- s = s.replaceAll("\t", " ");
- String[] lines = s.split("[\n]");
+ s = s.replace("\t", " ");
+ String[] lines = s.split("[\\n]");
float height = font.getBoundingBox().getHeight();
Modified:
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutSMPTest.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutSMPTest.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutSMPTest.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -65,8 +65,8 @@ class GlyphLayoutSMPTest extends TestBas
float x, float y, String s) throws IOException
{
- s = s.replaceAll("\t", " ");
- String[] lines = s.split("[\n]");
+ s = s.replace("\t", " ");
+ String[] lines = s.split("[\\n]");
for (String line : lines)
{
Modified:
pdfbox/trunk/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -26,6 +26,7 @@ import org.apache.fop.fonts.GlyphMapping
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.traits.MinOptMax;
+import org.apache.pdfbox.pdmodel.AbstractGlyphLayoutProcessor;
import org.apache.pdfbox.pdmodel.ContentStreamForGlyphLayoutInterface;
import org.apache.pdfbox.pdmodel.GlyphLayoutProcessorInterface;
import org.apache.pdfbox.pdmodel.GlyphsAndPositions;
@@ -41,7 +42,7 @@ import org.apache.pdfbox.pdmodel.font.PD
*
* @author Volker Kunert
*/
-public class GlyphLayoutProcessorFop implements GlyphLayoutProcessorInterface
+public class GlyphLayoutProcessorFop extends AbstractGlyphLayoutProcessor
implements GlyphLayoutProcessorInterface
{
private final GlyphLayoutFontLoaderFop glyphLayoutFontLoaderFop;
@@ -51,7 +52,7 @@ public class GlyphLayoutProcessorFop imp
the font size must be multiplied by this factor. Otherwise, the
positioning is wrong.
*/
- private static final float FOP_FONTSIZE_FACTOR = 1e3f;
+ private static final float FOP_FONTSIZE_FACTOR = 1000f;
/**
* Constructs a GlyphLayoutProcessorFop
@@ -125,14 +126,66 @@ public class GlyphLayoutProcessorFop imp
}
/**
+ * Compute the string width for a unidirectional string
+ *
+ * @param font to be used
+ * @param fontSize font size
+ * @param text text
+ * @param bidiLevel Bidi Level
+ * @return string width
+ */
+ @Override
+ protected float getStringWidthUni(PDType0Font font, float fontSize, String
text, int bidiLevel) throws IOException
+ {
+ TextAndGpa textAndGpa = computeGlyphsAndPositions(font, fontSize,
text, bidiLevel);
+ // PDType0Font.getStringWidth returns glyph widths in 1000-units.
Convert to user space using font matrix and fontSize
+ float raw = font.getStringWidth(textAndGpa.getText());
+ float scaleX = font.getFontMatrix().getScaleX();
+ return raw * scaleX * fontSize;
+ }
+
+ /**
+ * Internal class for text, positioning and GPA
+ */
+ protected static class TextAndGpa
+ {
+
+ private final String text;
+ private final boolean positioning;
+ private final int[][] gpa;
+
+ public TextAndGpa(String text, boolean positioning, int[][] gpa)
+ {
+ this.text = text;
+ this.positioning = positioning;
+ this.gpa = gpa;
+ }
+
+ public String getText()
+ {
+ return text;
+ }
+
+ public boolean hasPositioning()
+ {
+ return positioning;
+ }
+
+ public int[][] getGpa()
+ {
+ return gpa;
+ }
+ }
+
+ /**
* Computes glyph positioning
*
* @param font to be used
* @param fontSize font size
* @param text text to show
- * @param bidiLevel
+ * @param bidiLevel Bidi level
*/
- protected GlyphMapping computeGlyphAndPositions(PDType0Font font, float
fontSize, String text, int bidiLevel)
+ protected TextAndGpa computeGlyphsAndPositions(PDType0Font font, float
fontSize, String text, int bidiLevel)
{
Objects.requireNonNull(font, "Font must be set");
Objects.requireNonNull(text, "Text must be set");
@@ -146,11 +199,22 @@ public class GlyphLayoutProcessorFop imp
org.apache.fop.fonts.Font fopFont = new Font(mbf.getFontName(), null,
mbf, (int) (fontSize * FOP_FONTSIZE_FACTOR));
- return GlyphMapping.doGlyphMapping(
+ GlyphMapping glyphMapping = GlyphMapping.doGlyphMapping(
new FopStringTextFragment(text), 0, text.length() - 1, fopFont,
letterSpaceIPD, letterSpaceAdjustArray, ' ',
' ', //?
false, bidiLevel, false, true, false);
+
+ text = glyphMapping.mapping != null ? glyphMapping.mapping : text;
+ boolean positioning = glyphMapping.gposAdjustments != null;
+ int[][] gpa = positioning ? glyphMapping.gposAdjustments :
createZeroGpa(text.length());
+
+ if (bidiLevel % 2 == Bidi.DIRECTION_RIGHT_TO_LEFT)
+ {
+ gpa = reverseGpa(gpa);
+ text = new StringBuilder(text).reverse().toString();
+ }
+ return new TextAndGpa(text, positioning, gpa);
}
/**
@@ -167,76 +231,18 @@ public class GlyphLayoutProcessorFop imp
}
/**
- * Shows a text using glyph positioning (if needed)
+ * Shows unidirectional text using positioning
*
* @param contentStream the content stream
* @param font to be used
* @param fontSize font size
* @param text text to show
+ * @param bidiLevel Bidi level*
+ *
* @throws IOException if an I/O exception occurs
* @throws IllegalArgumentException if glyphs are missing
*/
@Override
- public void showText(ContentStreamForGlyphLayoutInterface contentStream,
PDType0Font font, float fontSize, String text) throws IOException
- {
- Objects.requireNonNull(text, "Text must be set");
-
- if (Bidi.requiresBidi(text.toCharArray(), 0, text.length()))
- {
- Bidi bidi = new Bidi(text, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
- if (bidi.isMixed())
- {
- // Split and Reorder
- // See PDFTextStripper.handleDirection
- // collect individual bidi information
- int runCount = bidi.getRunCount();
- byte[] levels = new byte[runCount];
- Integer[] runs = new Integer[runCount];
-
- for (int i = 0; i < runCount; i++)
- {
- levels[i] = (byte) bidi.getRunLevel(i);
- runs[i] = i;
- }
- // reorder individual parts based on their levels
- Bidi.reorderVisually(levels, 0, runs, 0, runCount);
-
- for (int i = 0; i < runCount; i++)
- {
- int index = runs[i];
- int start = bidi.getRunStart(index);
- int limit = bidi.getRunLimit(index);
- int bidiLevel = levels[index];
-
- // Map to correct value for GlyphMapping.doGlyphMapping
- // 0 LTR equal in java.text.Bidi and
org.apache.fop.traits.Direction
- // 1 RTL equal in java.text.Bidi and
org.apache.fop.traits.Direction
- bidiLevel = bidiLevel % 2; // even LTR, odd RTL
- String part = text.substring(start, limit);
- showTextUni(contentStream, font, fontSize, part,
bidiLevel);
- }
- }
- else
- {
- showTextUni(contentStream, font, fontSize, text,
bidi.getBaseLevel());
- }
- }
- else
- {
- showTextUni(contentStream, font, fontSize, text,
Bidi.DIRECTION_LEFT_TO_RIGHT);
- }
- }
-
- /**
- * Shows a text using FOP positioning
- *
- * @param contentStream
- * @param font
- * @param text the text to show
- * @param fontSize
- * @param bidiLevel
- * @throws IOException if an I/O error occurs
- */
protected void showTextUni(ContentStreamForGlyphLayoutInterface
contentStream, PDType0Font font, float fontSize,
String text, int bidiLevel) throws IOException
{
@@ -244,33 +250,20 @@ public class GlyphLayoutProcessorFop imp
Objects.requireNonNull(text, "Text must be set");
Objects.requireNonNull(contentStream, "contentStream must be set");
- GlyphMapping glyphMapping = computeGlyphAndPositions(font, fontSize,
text, bidiLevel);
-
- text = glyphMapping.mapping == null ? text : glyphMapping.mapping;
-
- int[][] gpa = glyphMapping.gposAdjustments != null ?
glyphMapping.gposAdjustments : createZeroGpa(text.length());
+ TextAndGpa textAndGpa = computeGlyphsAndPositions(font, fontSize,
text, bidiLevel);
+ text = textAndGpa.getText();
+ int[][] gpa = textAndGpa.getGpa();
+ boolean hasPositioning = textAndGpa.hasPositioning();
- if (bidiLevel == Bidi.DIRECTION_RIGHT_TO_LEFT)
- {
- gpa = reverseGpa(gpa);
- text = new StringBuilder(text).reverse().toString();
- }
int[] glyphIds = convertCharsToGlyphIds(font, text);
- if (glyphIds.length != text.length())
+ if (glyphIds.length != text.length() && hasPositioning)
{
- if (glyphMapping.gposAdjustments == null)
- {
- gpa = createZeroGpa(glyphIds.length);
- }
- else
- {
- // This case:
- // letters from the supplementary multilingual plane AND
position adjustments
- // is not implemented
- throw new IllegalStateException("glyphIds.length !=
text.length() and gposAdjustments!=null"
- + glyphIds.length + " " + text.length() + " " + text);
- }
+ // This case:
+ // letters from the supplementary multilingual plane AND position
adjustments
+ // is not implemented
+ throw new IllegalStateException("glyphIds.length != text.length()
and gposAdjustments!=null"
+ + glyphIds.length + " " + text.length() + " " + text);
}
final float delta = Math.ulp(fontSize); // do only adjustments bigger
than or equal to one ulp of the font size
@@ -282,7 +275,7 @@ public class GlyphLayoutProcessorFop imp
float px = gpa[i][0] / fontSize * 1000f / FOP_FONTSIZE_FACTOR;
float py = gpa[i][1] / FOP_FONTSIZE_FACTOR;
float ax = gpa[i][2] / fontSize * 1000f / FOP_FONTSIZE_FACTOR;
- float ay = gpa[i][3] / FOP_FONTSIZE_FACTOR; // ignored in
horizontal typesetting
+ // float ay = gpa[i][3] / FOP_FONTSIZE_FACTOR; // ignored in
horizontal typesetting
if (Math.abs(py) >= delta)
{
@@ -334,10 +327,9 @@ public class GlyphLayoutProcessorFop imp
protected int[][] createZeroGpa(int length)
{
int[][] gpa = new int[length][];
- int[] z4 = new int[] { 0, 0, 0, 0 };
for (int i = 0; i < length; i++)
{
- gpa[i] = z4;
+ gpa[i] = new int[] { 0, 0, 0, 0 };
}
return gpa;
}
Modified:
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutDin91379Test.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutDin91379Test.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutDin91379Test.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -149,8 +149,8 @@ class GlyphLayoutDin91379Test extends Te
float x, float y, String s) throws IOException
{
- s = s.replaceAll("\t", " ");
- String[] lines = s.split("[\n]");
+ s = s.replace("\t", " ");
+ String[] lines = s.split("[\\n]");
for (String line : lines)
{
Modified:
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutLigaturesAndKerningTest.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutLigaturesAndKerningTest.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutLigaturesAndKerningTest.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -148,8 +148,8 @@ class GlyphLayoutLigaturesAndKerningTest
float x, float y, String s) throws IOException
{
- s = s.replaceAll("\t", " ");
- String[] lines = s.split("[\n]");
+ s = s.replace("\t", " ");
+ String[] lines = s.split("[\\n]");
float height = font.getBoundingBox().getHeight();
Modified:
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutSMPTest.java
==============================================================================
---
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutSMPTest.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox-layout-fop/src/test/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutSMPTest.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -64,8 +64,8 @@ class GlyphLayoutSMPTest extends TestBas
float x, float y, String s) throws IOException
{
- s = s.replaceAll("\t", " ");
- String[] lines = s.split("[\n]");
+ s = s.replace("\t", " ");
+ String[] lines = s.split("[\\n]");
for (String line : lines)
{
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/AbstractGlyphLayoutProcessor.java
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/AbstractGlyphLayoutProcessor.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -0,0 +1,179 @@
+/*
+ * 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.pdmodel;
+
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
+
+import java.io.IOException;
+import java.text.Bidi;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Abstract super class for classes implementing GlyphLayoutProcessorInterface
+ *
+ * @author Volker Kunert
+ */
+public abstract class AbstractGlyphLayoutProcessor implements
GlyphLayoutProcessorInterface
+{
+ /**
+ * Class for text and Bidi-Level
+ */
+ protected static class TextAndBidiLevel
+ {
+ private final String text;
+ private final int bidiLevel;
+
+ TextAndBidiLevel(String text, int bidiLevel)
+ {
+ this.text = text;
+ this.bidiLevel = bidiLevel;
+ }
+
+ public String getText()
+ {
+ return text;
+ }
+
+ public int getBidiLevel()
+ {
+ return bidiLevel;
+ }
+ }
+
+ /**
+ * Compute the string width for a unidirectional string
+ * @param font to be used
+ * @param fontSize font size
+ * @param text text
+ * @param bidiLevel Bidi Level
+ * @return string width
+ */
+ protected abstract float getStringWidthUni(PDType0Font font, float
fontSize, String text, int bidiLevel)
+ throws IOException;
+
+
+ /**
+ * Compute the width for a text
+ * @param font to be used
+ * @param fontSize font size
+ * @param text text
+ * @return string width
+ */
+ public float getStringWidth(PDType0Font font, float fontSize, String text)
throws IOException
+ {
+ float width = 0f;
+ List<TextAndBidiLevel> textAndBidiLevels =
doBidiSplittingAndReordering(text);
+ for (TextAndBidiLevel textAndBidiLevel: textAndBidiLevels)
+ {
+ width += getStringWidthUni(font, fontSize,
textAndBidiLevel.getText(), textAndBidiLevel.getBidiLevel());
+ }
+ return width;
+ }
+
+ /**
+ * Shows unidirectional text using glyph positioning (if needed)
+ *
+ * @param contentStream the content stream
+ * @param font to be used
+ * @param fontSize font size
+ * @param text text to show
+ * @param bidiLevel Bidi level*
+ *
+ * @throws IOException if an I/O exception occurs
+ * @throws IllegalArgumentException if glyphs are missing
+ */
+ protected abstract void showTextUni(ContentStreamForGlyphLayoutInterface
contentStream, PDType0Font font, float fontSize,
+ String text, int bidiLevel) throws IOException;
+
+ /**
+ * Shows a text using glyph positioning (if needed)
+ *
+ * @param contentStream the content stream
+ * @param font to be used
+ * @param fontSize font size
+ * @param text text to show
+ *
+ * @throws IOException if an I/O exception occurs
+ * @throws IllegalArgumentException if glyphs are missing
+ */
+ public void showText(ContentStreamForGlyphLayoutInterface contentStream,
PDType0Font font, float fontSize, String text)
+ throws IOException
+ {
+ List<TextAndBidiLevel> textAndBidiLevels =
doBidiSplittingAndReordering(text);
+ for (TextAndBidiLevel textAndBidiLevel: textAndBidiLevels)
+ {
+ showTextUni(contentStream, font, fontSize,
textAndBidiLevel.getText(), textAndBidiLevel.getBidiLevel());
+ }
+ }
+
+ /**
+ * Do Bidi splitting and reordering
+ * @param text text
+ * @return list of texts and bidi levels
+ */
+ protected List<TextAndBidiLevel> doBidiSplittingAndReordering(String text)
+ {
+ ArrayList<TextAndBidiLevel> textAndBidiLevels = new ArrayList<>();
+
+ Objects.requireNonNull(text, "Text must be set");
+
+ if (Bidi.requiresBidi(text.toCharArray(), 0, text.length()))
+ {
+ Bidi bidi = new Bidi(text, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);
+ if (bidi.isMixed())
+ {
+ // Split and Reorder
+ // See PDFTextStripper.handleDirection
+ // collect individual bidi information
+ int runCount = bidi.getRunCount();
+ byte[] levels = new byte[runCount];
+ Integer[] runs = new Integer[runCount];
+
+ for (int i = 0; i < runCount; i++)
+ {
+ levels[i] = (byte) bidi.getRunLevel(i);
+ runs[i] = i;
+ }
+ // reorder individual parts based on their levels
+ Bidi.reorderVisually(levels, 0, runs, 0, runCount);
+
+ for (int i = 0; i < runCount; i++)
+ {
+ int index = runs[i];
+ int start = bidi.getRunStart(index);
+ int limit = bidi.getRunLimit(index);
+ int bidiLevel = levels[index];
+ String part = text.substring(start, limit);
+ textAndBidiLevels.add(new TextAndBidiLevel(part,
bidiLevel));
+ }
+ }
+ else
+ {
+ textAndBidiLevels.add(new TextAndBidiLevel(text,
bidi.getBaseLevel()));
+ }
+ }
+ else
+ {
+ textAndBidiLevels.add(new TextAndBidiLevel(text,
Bidi.DIRECTION_LEFT_TO_RIGHT));
+ }
+ return Collections.unmodifiableList(textAndBidiLevels);
+ }
+}
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java
==============================================================================
---
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java
Mon Aug 10 11:01:27 2026 (r1937027)
+++
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java
Mon Aug 10 11:01:35 2026 (r1937028)
@@ -39,6 +39,16 @@ public interface GlyphLayoutProcessorInt
boolean supportsFont(PDFont font);
/**
+ * Compute the width for a text
+ * @param font to be used
+ * @param fontSize font size
+ * @param text text
+ * @return string width
+ */
+ float getStringWidth(PDType0Font font, float fontSize, String text) throws
IOException;
+
+
+ /**
* Shows a text using glyph positioning (if needed)
*
* @param contentStream the content stream