diff --git a/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java b/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java index ab8b2eeb8e7..9c3517c32f6 100644 --- a/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java +++ b/pdfbox-layout-awt/src/main/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutProcessorAwt.java @@ -23,11 +23,12 @@ 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 +44,7 @@ * * @author Volker Kunert */ -public class GlyphLayoutProcessorAwt implements GlyphLayoutProcessorInterface +public class GlyphLayoutProcessorAwt extends AbstractGlyphLayoutProcessor implements GlyphLayoutProcessorInterface { private final GlyphLayoutFontLoaderAwt glyphLayoutFontLoaderAwt; @@ -132,7 +133,7 @@ public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, bool * * @param pdDocument document * @param inputStream of the font - * @param fontOptions + * @param fontOptions options for font loading * * @return a PDType0Font font. * @@ -140,7 +141,7 @@ public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, bool * @throws FontFormatException if the font is bad */ public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, - GlyphLayoutFontLoaderAwt.FontOptions fontOptions) throws IOException, FontFormatException + GlyphLayoutFontLoaderAwt.FontOptions fontOptions) throws IOException, FontFormatException { return glyphLayoutFontLoaderAwt.loadFont(pdDocument, inputStream, fontOptions); } @@ -154,12 +155,12 @@ public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, * @param fontOptions options for font * * @return a PDType0Font font. - * + * * @throws IOException if font can not be loaded * @throws FontFormatException if the font is bad */ public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, boolean embedSubset, - GlyphLayoutFontLoaderAwt.FontOptions fontOptions) throws IOException, FontFormatException + GlyphLayoutFontLoaderAwt.FontOptions fontOptions) throws IOException, FontFormatException { return glyphLayoutFontLoaderAwt.loadFont(pdDocument, inputStream, embedSubset, fontOptions); } @@ -194,62 +195,22 @@ protected GlyphVector computeGlyphVector(PDType0Font font, float fontSize, Strin } /** - * 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(); } + /** * Shows a text using glyph positioning (if needed) This text must have a uniform run direction. * @@ -261,13 +222,14 @@ public void showText(ContentStreamForGlyphLayoutInterface contentStream, PDType0 * @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 diff --git a/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java b/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java index 6f3953e49d6..a38d338ec1b 100644 --- a/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java +++ b/pdfbox-layout-awt/src/test/java/org/apache/pdfbox/glyphlayout/awt/GlyphLayoutLigaturesAndKerningTest.java @@ -30,6 +30,7 @@ import java.awt.FontFormatException; import java.io.IOException; import java.net.URISyntaxException; +import java.text.Bidi; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -52,7 +53,7 @@ class GlyphLayoutLigaturesAndKerningTest extends TestBase * Check that missing glyph is caught like in main pdfbox. * * @throws IOException - * @throws FontFormatException + * @throws FontFormatException */ @Test void testMissingGlyph() throws IOException, FontFormatException @@ -63,8 +64,8 @@ void testMissingGlyph() throws IOException, FontFormatException 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 +73,7 @@ void testMissingGlyph() throws IOException, FontFormatException { 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 +81,7 @@ void testMissingGlyph() throws IOException, FontFormatException } } } - + @Test void testLigaturesAndKerning() throws IOException, FontFormatException, URISyntaxException { @@ -100,15 +101,15 @@ void testLigaturesAndKerning() throws IOException, FontFormatException, URISynta 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()); @@ -117,13 +118,22 @@ void testLigaturesAndKerning() throws IOException, FontFormatException, URISynta PDType0Font lohitBengaliFont = createPdType0Font(glyphLayoutProcessor, doc, lohitBengaliPath, new GlyphLayoutFontLoaderAwt.FontOptions()); - + + + float f1 = dejavuFont.getStringWidth(DEJAVU_STRING) * dejavuFont.getFontMatrix().getScaleX() * fontSize; + float f2 = dejavuLigKernFont.getStringWidth(DEJAVU_STRING) * dejavuLigKernFont.getFontMatrix().getScaleX() * fontSize; + float f3 = glyphLayoutProcessor.getStringWidth(dejavuFont, fontSize, DEJAVU_STRING); + float f4 = glyphLayoutProcessor.getStringWidth(dejavuLigKernFont, fontSize, DEJAVU_STRING); + + System.out.println("widths: " + f1 + " " + f2); + System.out.println("widths: " + f3 + " " + f4); + PDPage page = new PDPage(); doc.addPage(page); 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,7 +164,7 @@ void testLigaturesAndKerning() throws IOException, FontFormatException, URISynta * 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", " "); diff --git a/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java b/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java index f3aae40e1b5..b4f42cf6718 100644 --- a/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java +++ b/pdfbox-layout-fop/src/main/java/org/apache/pdfbox/glyphlayout/fop/GlyphLayoutProcessorFop.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.text.Bidi; +import java.util.Arrays; import java.util.Objects; import org.apache.fop.fonts.Font; @@ -26,6 +27,7 @@ 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 +43,7 @@ * * @author Volker Kunert */ -public class GlyphLayoutProcessorFop implements GlyphLayoutProcessorInterface +public class GlyphLayoutProcessorFop extends AbstractGlyphLayoutProcessor implements GlyphLayoutProcessorInterface { private final GlyphLayoutFontLoaderFop glyphLayoutFontLoaderFop; @@ -51,7 +53,7 @@ public class GlyphLayoutProcessorFop implements GlyphLayoutProcessorInterface 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 @@ -124,15 +126,58 @@ public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream) thro return glyphLayoutFontLoaderFop.loadFont(pdDocument, inputStream); } + + /** + * 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); + return font.getStringWidth(textAndGpa.getText()); + } + + + /** + * 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 +191,22 @@ protected GlyphMapping computeGlyphAndPositions(PDType0Font font, float fontSize 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 +223,18 @@ protected int[] convertCharsToGlyphIds(PDType0Font font, String text) } /** - * 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 +242,20 @@ protected void showTextUni(ContentStreamForGlyphLayoutInterface contentStream, P 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; + TextAndGpa textAndGpa = computeGlyphsAndPositions(font, fontSize, text, bidiLevel); + text = textAndGpa.getText(); + int[][] gpa = textAndGpa.getGpa(); + boolean hasPositioning = textAndGpa.hasPositioning(); - int[][] gpa = glyphMapping.gposAdjustments != null ? glyphMapping.gposAdjustments : createZeroGpa(text.length()); - - 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 +267,7 @@ protected void showTextUni(ContentStreamForGlyphLayoutInterface contentStream, P 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) { @@ -335,10 +320,7 @@ 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; - } + Arrays.fill(gpa, z4); return gpa; } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/AbstractGlyphLayoutProcessor.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/AbstractGlyphLayoutProcessor.java new file mode 100644 index 00000000000..52dd42120d1 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/AbstractGlyphLayoutProcessor.java @@ -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 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 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 doBidiSplittingAndReordering(String text) + { + ArrayList 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); + } +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java index 218a1fa3478..4570509505d 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessorInterface.java @@ -38,6 +38,16 @@ public interface GlyphLayoutProcessorInterface */ 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) *