diff --git a/.gitignore b/.gitignore index 4054f4999fd..5b6ce09637b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ bin/ .project .idea/ *.iml +/DoGlyphPositionDinSpec91379.pdf +/DoGlyphPositionDinSpec91379Form.pdf +/DoGlyphLayoutDin91379.pdf +/DoGlyphLayoutDin91379Form.pdf diff --git a/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/DoGlyphLayoutDin91379.java b/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/DoGlyphLayoutDin91379.java new file mode 100644 index 00000000000..6a8e3714e83 --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/DoGlyphLayoutDin91379.java @@ -0,0 +1,148 @@ +/* + * 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; + +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; + +/** + * Example of formatting for letters defined in: DIN 91379: Characters in Unicode for the electronic processing + * of names and data exchange in Europe + *

+ * Font used: Arimo-Regular.ttf, see + * https://fonts.google.com/specimen/Arimo + *

+ * Use of the positioning features of Java. + * + * @author Volker Kunert + * @version 2026-04-04 + */ +public class DoGlyphLayoutDin91379 { + public static String LATIN_CHARS_DIN_91379 = + "DIN 91379: Characters in Unicode for the electronic processing of names \n" + + "and data exchange in Europe\n" + + "Font used: Arimo-Regular.ttf\n" + + "\n" + + "bll; Latin Letters (normative)\n" + + "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z\n" + + "À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó\n" + + "ô õ ö ø ù ú û ü ý þ ÿ Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ\n" + + "ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş\n" + + "ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž Ƈ ƈ Ə Ɨ Ơ ơ Ư ư Ʒ Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ\n" + + "Ǘ ǘ Ǚ ǚ Ǜ ǜ Ǟ ǟ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ Ǵ ǵ Ǹ ǹ Ǻ ǻ Ǽ ǽ Ǿ ǿ Ȓ ȓ Ș ș Ț ț Ȟ ȟ ȧ Ȩ ȩ Ȫ ȫ Ȭ ȭ\n" + + "Ȯ ȯ Ȱ ȱ Ȳ ȳ ə ɨ ʒ Ḃ ḃ Ḇ ḇ Ḋ ḋ Ḍ ḍ Ḏ ḏ Ḑ ḑ ḗ Ḝ ḝ Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ḯ Ḱ ḱ Ḳ ḳ Ḵ ḵ Ḷ ḷ Ḻ ḻ Ṁ\n" + + "ṁ Ṃ ṃ Ṅ ṅ Ṇ ṇ Ṉ ṉ Ṓ ṓ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṛ ṛ Ṟ ṟ Ṡ ṡ Ṣ ṣ Ṫ ṫ Ṭ ṭ Ṯ ṯ Ẁ ẁ Ẃ ẃ Ẅ ẅ Ẇ ẇ Ẍ ẍ Ẏ ẏ Ẑ ẑ Ẓ ẓ Ẕ ẕ\n" + + "ẖ ẗ ẞ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ Ẹ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ề Ể ể Ễ ễ Ệ ệ Ỉ ỉ Ị ị Ọ ọ Ỏ ỏ Ố\n" + + "ố Ồ ồ Ổ ổ Ỗ ỗ Ộ ộ Ớ ớ Ờ ờ Ở ở Ỡ ỡ Ợ ợ Ụ ụ Ủ ủ Ứ ứ Ừ ừ Ử ử Ữ ữ Ự ự Ỳ ỳ Ỵ ỵ Ỷ ỷ Ỹ ỹ\n" + + "Sequences\n" + + "A̋ C̀ C̄ C̆ C̈ C̕ C̣ C̦ C̨̆ D̂ F̀ F̄ G̀ H̄ H̦ H̱ J́ J̌ K̀ K̂ K̄ K̇ K̕ K̛ K̦ K͟H \n" + + "K͟h L̂ L̥ L̥̄ L̦ M̀ M̂ M̆ M̐ N̂ N̄ N̆ N̦ P̀ P̄ P̕ P̣ R̆ R̥ R̥̄ S̀ S̄ S̛̄ S̱ T̀ T̄ \n" + + "T̈ T̕ T̛ U̇ Z̀ Z̄ Z̆ Z̈ Z̧ a̋ c̀ c̄ c̆ c̈ c̕ c̣ c̦ c̨̆ d̂ f̀ f̄ g̀ h̄ h̦ j́ k̀ \n" + + "k̂ k̄ k̇ k̕ k̛ k̦ k͟h l̂ l̥ l̥̄ l̦ m̀ m̂ m̆ m̐ n̂ n̄ n̆ n̦ p̀ p̄ p̕ p̣ r̆ r̥ r̥̄ \n" + + "s̀ s̄ s̛̄ s̱ t̀ t̄ t̕ t̛ u̇ z̀ z̄ z̆ z̈ z̧ Ç̆ Û̄ ç̆ û̄ ÿ́ Č̕ Č̣ č̕ č̣ ē̍ Ī́ ī́ \n" + + "ō̍ Ž̦ Ž̧ ž̦ ž̧ Ḳ̄ ḳ̄ Ṣ̄ ṣ̄ Ṭ̄ ṭ̄ Ạ̈ ạ̈ Ọ̈ ọ̈ Ụ̄ Ụ̈ ụ̄ ụ̈ \n" + + "bnlreq; Non-Letters N1 (normative)\n" + + " ' , - . ` ~ ¨ ´ · ʹ ʺ ʾ ʿ ˈ ˌ ’ ‡ \n" + + "bnl; Non-Letters N2 (normative)\n" + + "! \" # $ % & ( ) * + / 0 1 2 3 4 5 6 7 8 9 : ; < = > \n" + + "? @ [ \\ ] ^ _ { | } ¡ ¢ £ ¥ § © ª « ¬ ® ¯ ° ± ² ³ µ \n" + + "¶ ¹ º » ¿ × ÷ € \n" + + "bnlopt; Non-Letters N3 (normative)\n" + + "¤ ¦ ¸ ¼ ½ ¾ \n" + + "gl; Greek Letters (extended)\n" + + "Ά Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ \n" + + "Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν \n" + + "ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ \n" + + "cl; Cyrillic Letters (extended)\n" + + "Ѝ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш \n" + + "Щ Ъ Ь Ю Я а б в г д е ж з и й к л м н о п р с т у ф \n" + + "х ц ч ш щ ъ ь ю я ѝ \n" + + "enl; Non-Letters E1 (extended)\n" + + "ƒ ʰ ʳ ˆ ˜ ˢ ᵈ ᵗ ‘ ‚ “ ” „ † … ‰ ′ ″ ‹ › ⁰ ⁴ ⁵ ⁶ ⁷ ⁸ \n" + + "⁹ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ™ ∞ ≤ ≥ \n" + + "Additional non-letters (not included in DIN 91379): – — •�"; + + /* + * Start the test + */ + public static void main(String[] args) { + try { + GlyphLayoutProcessor glyphLayoutProcessor = new GlyphLayoutProcessor(); + + String outputFilename = "DoGlyphLayoutDin91379.pdf"; + float fontSize = 12.0f; + + PDDocument pdDocument = new PDDocument(); + InputStream fontStream = DoGlyphLayoutDin91379Form.class. + getResourceAsStream("/org/apache/pdfbox/examples/glyphlayout/arimo/Arimo-Regular.ttf"); + PDType0Font font = glyphLayoutProcessor.loadFont(pdDocument, fontStream); + + 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; + showComposites(cs, font, fontSize, x, y, LATIN_CHARS_DIN_91379); + cs.close(); + pdDocument.save(outputFilename); + pdDocument.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /* + * break the text into lines and show them + */ + public static void showComposites(PDPageContentStream cs, PDType0Font font, float fontSize, + float x, float y, String s) throws Exception { + + s = s.replaceAll("\t", " "); + String[] lines = s.split("[\n]"); + + float height = font.getBoundingBox().getHeight(); + + for (String line : lines) { + if (!line.isEmpty()) { + showCompositesLine(cs, font, fontSize, x, y, line); + y -= height / 1000f * fontSize; + } + } + } + + /* + * show one line + */ + public static void showCompositesLine(PDPageContentStream cs, PDType0Font font, float fontSize, + float x, float y, String line) throws IOException { + cs.beginText(); + cs.setFont(font, fontSize); + cs.newLineAtOffset(x, y); + cs.showText(line); + cs.endText(); + } +} diff --git a/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/DoGlyphLayoutDin91379Form.java b/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/DoGlyphLayoutDin91379Form.java new file mode 100644 index 00000000000..f7cd6e95541 --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/DoGlyphLayoutDin91379Form.java @@ -0,0 +1,134 @@ +/* + * 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; + +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.*; +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 java.io.File; +import java.io.InputStream; +import java.util.Objects; + +/* + * Example of formatting for letters defined in: + * DIN 91379: Characters and defined character sequences in Unicode for the electronic processing of names + * and data exchange in Europe + *

+ * Font used: Arimo-Regular.ttf, see https://fonts.google.com/specimen/Arimo + *

+ * Use of the glyph layout features of Java that are based on HarfBuzz + * + * @author Volker Kunert + * @date 2026-04-04 + */ + +public class DoGlyphLayoutDin91379Form { + + public static String LATIN_CHARS_DIN_91379 = + "DIN 91379: Characters in Unicode for the electronic processing of names " + + "and data exchange in Europe\n" + + "bll; Latin Letters (normative)\n" + + "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z " + + "À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó " + + "ô õ ö ø ù ú û ü ý þ ÿ Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ " + + "ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş " + + "ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž Ƈ ƈ Ə Ɨ Ơ ơ Ư ư Ʒ Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ " + + "Ǘ ǘ Ǚ ǚ Ǜ ǜ Ǟ ǟ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ Ǵ ǵ Ǹ ǹ Ǻ ǻ Ǽ ǽ Ǿ ǿ Ȓ ȓ Ș ș Ț ț Ȟ ȟ ȧ Ȩ ȩ Ȫ ȫ Ȭ ȭ " + + "Ȯ ȯ Ȱ ȱ Ȳ ȳ ə ɨ ʒ Ḃ ḃ Ḇ ḇ Ḋ ḋ Ḍ ḍ Ḏ ḏ Ḑ ḑ ḗ Ḝ ḝ Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ ḯ Ḱ ḱ Ḳ ḳ Ḵ ḵ Ḷ ḷ Ḻ ḻ Ṁ " + + "ṁ Ṃ ṃ Ṅ ṅ Ṇ ṇ Ṉ ṉ Ṓ ṓ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṛ ṛ Ṟ ṟ Ṡ ṡ Ṣ ṣ Ṫ ṫ Ṭ ṭ Ṯ ṯ Ẁ ẁ Ẃ ẃ Ẅ ẅ Ẇ ẇ Ẍ ẍ Ẏ ẏ Ẑ ẑ Ẓ ẓ Ẕ ẕ " + + "ẖ ẗ ẞ Ạ ạ Ả ả Ấ ấ Ầ ầ Ẩ ẩ Ẫ ẫ Ậ ậ Ắ ắ Ằ ằ Ẳ ẳ Ẵ ẵ Ặ ặ Ẹ ẹ Ẻ ẻ Ẽ ẽ Ế ế Ề ề Ể ể Ễ ễ Ệ ệ Ỉ ỉ Ị ị Ọ ọ Ỏ ỏ Ố " + + "ố Ồ ồ Ổ ổ Ỗ ỗ Ộ ộ Ớ ớ Ờ ờ Ở ở Ỡ ỡ Ợ ợ Ụ ụ Ủ ủ Ứ ứ Ừ ừ Ử ử Ữ ữ Ự ự Ỳ ỳ Ỵ ỵ Ỷ ỷ Ỹ ỹ\n" + + "Sequences\n" + + "A̋ C̀ C̄ C̆ C̈ C̕ C̣ C̦ C̨̆ D̂ F̀ F̄ G̀ H̄ H̦ H̱ J́ J̌ K̀ K̂ K̄ K̇ K̕ K̛ K̦ K͟H " + + "K͟h L̂ L̥ L̥̄ L̦ M̀ M̂ M̆ M̐ N̂ N̄ N̆ N̦ P̀ P̄ P̕ P̣ R̆ R̥ R̥̄ S̀ S̄ S̛̄ S̱ T̀ T̄ " + + "T̈ T̕ T̛ U̇ Z̀ Z̄ Z̆ Z̈ Z̧ a̋ c̀ c̄ c̆ c̈ c̕ c̣ c̦ c̨̆ d̂ f̀ f̄ g̀ h̄ h̦ j́ k̀ " + + "k̂ k̄ k̇ k̕ k̛ k̦ k͟h l̂ l̥ l̥̄ l̦ m̀ m̂ m̆ m̐ n̂ n̄ n̆ n̦ p̀ p̄ p̕ p̣ r̆ r̥ r̥ " + + "s̀ s̄ s̛̄ s̱ t̀ t̄ t̕ t̛ u̇ z̀ z̄ z̆ z̈ z̧ Ç̆ Û̄ ç̆ û̄ ÿ́ Č̕ Č̣ č̕ č̣ ē̍ Ī́ ī́ " + + "ō̍ Ž̦ Ž̧ ž̦ ž̧ Ḳ̄ ḳ̄ Ṣ̄ ṣ̄ Ṭ̄ ṭ̄ Ạ̈ ạ̈ Ọ̈ ọ̈ Ụ̄ Ụ̈ ụ̄ ụ̈\n" + + "bnlreq; Non-Letters N1 (normative)\n" + + " ' , - . ` ~ ¨ ´ · ʹ ʺ ʾ ʿ ˈ ˌ ’ ‡\n" + + "bnl; Non-Letters N2 (normative)\n" + + "! \" # $ % & ( ) * + / 0 1 2 3 4 5 6 7 8 9 : ; < = > " + + "? @ [ \\ ] ^ _ { | } ¡ ¢ £ ¥ § © ª « ¬ ® ¯ ° ± ² ³ µ " + + "¶ ¹ º » ¿ × ÷ € \n" + + "bnlopt; Non-Letters N3 (normative)\n" + + "¤ ¦ ¸ ¼ ½ ¾ \n" + + "gl; Greek Letters (extended)\n" + + "Ά Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ " + + "Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν " + + "ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ \n" + + "cl; Cyrillic Letters (extended)\n" + + "Ѝ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш " + + "Щ Ъ Ь Ю Я а б в г д е ж з и й к л м н о п р с т у ф " + + "х ц ч ш щ ъ ь ю я ѝ \n" + + "enl; Non-Letters E1 (extended)\n" + + "ƒ ʰ ʳ ˆ ˜ ˢ ᵈ ᵗ ‘ ‚ “ ” „ † … ‰ ′ ″ ‹ › ⁰ ⁴ ⁵ ⁶ ⁷ ⁸ " + + "⁹ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ™ ∞ ≤ ≥ \n" + + "Additional non-letters (not included in DIN 91379): – — •�"; + + + /* + * Greek and Cyrillic letters are not shown in the example. + */ + + public static void main(String[] args) { + try { + GlyphLayoutProcessor glyphLayoutProcessor = new GlyphLayoutProcessor(); + + String outputFilename = "DoGlyphLayoutDin91379Form.pdf"; + + String fontSizeString = "12"; + + InputStream fontStream = DoGlyphLayoutDin91379Form.class. + getResourceAsStream("/org/apache/pdfbox/examples/glyphlayout/arimo/Arimo-Regular.ttf"); + File pdfForm = new File(Objects.requireNonNull(DoGlyphLayoutDin91379Form.class. + getResource("/org/apache/pdfbox/examples/glyphlayout/PdfForm.pdf")).toURI()); + + PDDocument pdDocument = Loader.loadPDF(pdfForm); + + PDType0Font font = glyphLayoutProcessor.loadFont(pdDocument, fontStream, false); // embesSubset has to be set to false + + PDDocumentCatalog docCatalog = pdDocument.getDocumentCatalog(); + PDAcroForm acroForm = docCatalog.getAcroForm(); + + acroForm.setGlyphLayoutProcessor(glyphLayoutProcessor); + + PDResources resources = new PDResources(); + String resourceFontName = resources.add(font).getName(); + acroForm.setDefaultResources(resources); + for (PDField field : acroForm.getFields()) { + if (field instanceof PDTextField) { + PDTextField textfield = (PDTextField) field; + textfield.setDefaultAppearance( + String.format("/%s %s Tf 0 g ", resourceFontName, fontSizeString)); + textfield.setValue(LATIN_CHARS_DIN_91379); + } + } + acroForm.refreshAppearances(); + acroForm.flatten(); + pdDocument.save(outputFilename); + pdDocument.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/PdfForm.odt b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/PdfForm.odt new file mode 100644 index 00000000000..f61b63c03e1 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/PdfForm.odt differ diff --git a/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/PdfForm.pdf b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/PdfForm.pdf new file mode 100644 index 00000000000..21c5c488824 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/PdfForm.pdf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/arimo/Arimo-Regular.ttf b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/arimo/Arimo-Regular.ttf new file mode 100644 index 00000000000..c14e0a41892 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/arimo/Arimo-Regular.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/arimo/LICENSE.txt b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/arimo/LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/examples/src/main/resources/org/apache/pdfbox/examples/glyphlayout/arimo/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutFontLoader.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutFontLoader.java new file mode 100644 index 00000000000..d12f50066ca --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutFontLoader.java @@ -0,0 +1,130 @@ +/* + * 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.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType0Font; + +import java.awt.Font; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Provides glyph positioning e.g. for accented Latin letters. + * + * @author Volker Kunert + */ +public class GlyphLayoutFontLoader { + + private final Map awtFontMap = new ConcurrentHashMap<>(); + + /** + * Loads the AWT font needed for layout + * + * @param pdDocument document + * @param inputStream of the font + * @return pdType0Font PDFBox font + * @throws RuntimeException if font can not be loaded + */ + public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, boolean embedSubset) { + PDType0Font pdType0Font = null; + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[2048]; + int bytes_read = -1; + while ((bytes_read = inputStream.read(buffer)) > 0) { + baos.write(buffer, 0, bytes_read); + } + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + pdType0Font = PDType0Font.load(pdDocument, bais, embedSubset); + bais.reset(); + loadAwtFont(pdType0Font, bais); + + } catch (Exception e) { + throw new RuntimeException("Font creation failed.", e); + } + return pdType0Font; + } + + /** + * Loads the AWT font needed for layout + * + * @param pdDocument document + * @param inputStream of the font + * @return pdType0Font PDFBox font + * @throws RuntimeException if font can not be loaded + */ + public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream) { + return loadFont(pdDocument, inputStream, true); + } + + /** + * Loads the AWT font needed for layout + * + * @param pdType0Font OpenPdf base font + * @param inputStream of the font file + * @throws RuntimeException if font can not be loaded + */ + public void loadAwtFont(PDType0Font pdType0Font, InputStream inputStream) { + Font awtFont = null; + try { + if (!awtFontMap.containsKey(pdType0Font)) { + awtFont = Font.createFont(java.awt.Font.TRUETYPE_FONT, inputStream); + if (awtFont == null) { + throw new RuntimeException("Font is null"); + } + awtFontMap.put(pdType0Font, awtFont); + } + } catch (Exception e) { + throw new RuntimeException(String.format("AWT Font creation failed for %s.", pdType0Font.getName()), e); + } finally { + try { + inputStream.close(); + } catch (Exception e) { + //ignore + } + } + } + + /** + * Determines if glyph layout is supported for this font + * + * @param font PDFBox font + * @return true if glyph layout is supported for this font + */ + public boolean supportsFont(PDFont font) { + boolean supports = font instanceof PDType0Font && + awtFontMap.containsKey((PDType0Font)font); + return supports; + } + + /** + * Gets the corresponding AWT-font for the given PDFBox-font + * + * @param font PDFBox font + * @return AWT font if available + */ + public Font getAwtFont(PDType0Font font) { + return awtFontMap.get(font); + } +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessor.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessor.java new file mode 100644 index 00000000000..42b75567141 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphLayoutProcessor.java @@ -0,0 +1,209 @@ +/* + * 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.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType0Font; + +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.io.IOException; +import java.io.InputStream; +import java.text.AttributedString; +import java.text.Bidi; + +/** + * Worker class for glyph positioning + * + * @author Volker Kunert + */ +public class GlyphLayoutProcessor { + private final GlyphLayoutFontLoader glyphLayoutFontLoader; + private PDType0Font font; + private float fontSize; + private PDAbstractContentStream contentStream; + + /** + * Constructs a GlyphLayoutWorker + * + */ + public GlyphLayoutProcessor() { + this.glyphLayoutFontLoader = new GlyphLayoutFontLoader(); + } + + void setContentStream(PDAbstractContentStream contentStream) { + this.contentStream = contentStream; + } + + + /** + * + * @param font to be checked + * @return true if font is supported + */ + public boolean supportsFont(PDFont font) { + return glyphLayoutFontLoader.supportsFont(font); + } + + + /** + * Loads the AWT font needed for layout + * + * @param pdDocument document + * @param inputStream of the font + * @param embedSubset must be false for PDF forms + * @throws RuntimeException if font can not be loaded + */ + public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream, boolean embedSubset) { + return glyphLayoutFontLoader.loadFont(pdDocument, inputStream, embedSubset); + } + + + /** + * Loads the AWT font needed for layout + * + * @param pdDocument document + * @param inputStream of the font + * @throws RuntimeException if font can not be loaded + */ + public PDType0Font loadFont(PDDocument pdDocument, InputStream inputStream) { + return glyphLayoutFontLoader.loadFont(pdDocument, inputStream, true); + } + + + /** + * Checks if the glyphVector contains adjustments + * that make advanced layout necessary + * + * @param glyphVector glyph vector containing the positions + * @return true if the glyphVector contains adjustments + */ + private static boolean hasAdjustments(GlyphVector glyphVector) { + boolean retVal = false; + float lastX = 0f; + float lastY = 0f; + + for (int i = 0; i < glyphVector.getNumGlyphs(); i++) { + Point2D p = glyphVector.getGlyphPosition(i); + float dx = (float) p.getX() - lastX; + float dy = (float) p.getY() - lastY; + + float ax = (i == 0) ? 0.0f : glyphVector.getGlyphMetrics(i - 1).getAdvanceX(); + float ay = (i == 0) ? 0.0f : glyphVector.getGlyphMetrics(i - 1).getAdvanceY(); + + if (dx != ax || dy != ay) { + retVal = true; + break; + } + lastX = (float) p.getX(); + lastY = (float) p.getY(); + } + return retVal; + } + + /** + * Computes glyph positioning + * + * @param text input text + * @return glyph vector containing reordered text, width and positioning info + */ + private GlyphVector computeGlyphVector(String text) { + char[] chars = text.toCharArray(); + + FontRenderContext fontRenderContext = new FontRenderContext(new AffineTransform(), false, true); + // use fractional metrics + AttributedString as = new AttributedString(text); + Bidi bidi = new Bidi(as.getIterator()); + int localFlags = bidi.isLeftToRight() ? java.awt.Font.LAYOUT_LEFT_TO_RIGHT : java.awt.Font.LAYOUT_RIGHT_TO_LEFT; + + java.awt.Font awtFont = glyphLayoutFontLoader.getAwtFont(font).deriveFont(fontSize); + GlyphVector glyphVector = awtFont.layoutGlyphVector(fontRenderContext, chars, 0, chars.length, localFlags); + + return glyphVector; + } + + + /** + * Sets the font and fontSize + * + * @param font to be set + * @param fontSize font size + */ + void setFontAndSize(PDType0Font font, float fontSize) { + this.font = font; + this.fontSize = fontSize; + } + + + /** + * Shows a text using glyph positioning (if needed) + * + * @param text text to show + * @throws IOException + */ + void showText(String text) throws IOException { + GlyphVector glyphVector = computeGlyphVector(text); + if (!hasAdjustments(glyphVector)) { + contentStream.showTextPDType0Font(glyphVector, 0, glyphVector.getNumGlyphs()); + return; + } + + final float delta = 1e-5f; + final float factorX = 1000f / fontSize; + float lastX = 0f; + + GlyphsAndPositions ga = new GlyphsAndPositions(); + + for (int i = 0; i < glyphVector.getNumGlyphs(); i++) { + Point2D p = glyphVector.getGlyphPosition(i); + float ax = (i == 0) ? 0.0f : glyphVector.getGlyphMetrics(i - 1).getAdvanceX(); + float dx = (float) p.getX() - lastX - ax; + float py = (float) p.getY(); + + if (Math.abs(py) >= delta) { + if (!ga.isEmpty()) { + contentStream.showGlyphsWithPositioning(ga); + ga.clear(); + } + contentStream.setTextRise(-py); + } + if (Math.abs(dx) >= delta) { + ga.add(-dx * factorX); + } + ga.add(glyphVector.getGlyphCode(i)); + if (Math.abs(py) >= delta) { + contentStream.showGlyphsWithPositioning(ga); + ga.clear(); + contentStream.setTextRise(0.0f); + } + lastX = (float) p.getX(); + } + // adjust the end position + Point2D p = glyphVector.getGlyphPosition(glyphVector.getNumGlyphs()); + float ax = (glyphVector.getNumGlyphs() == 0) ? 0.0f + : glyphVector.getGlyphMetrics(glyphVector.getNumGlyphs() - 1).getAdvanceX(); + float dx = (float) p.getX() - lastX - ax; + if (Math.abs(dx) >= delta) { + ga.add(-dx * factorX); + } + contentStream.showGlyphsWithPositioning(ga); + ga.clear(); + } +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphsAndPositions.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphsAndPositions.java new file mode 100644 index 00000000000..2a5a4eefc9e --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/GlyphsAndPositions.java @@ -0,0 +1,91 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Stores sublists of glyphs and positions in a list + */ +public class GlyphsAndPositions { + private final ArrayList list = new ArrayList<>(); + + /** + * Sublist to store adjacent glyphs + */ + public static class GlyphSubList extends ArrayList { + } + + + /** + * Adds a glyph + * @param glyph to be added + */ + public void add(Integer glyph) { + Object last = !list.isEmpty() ? list.get(list.size() - 1) : null; + GlyphSubList glyphSubList; + if (!(last instanceof GlyphSubList)) { + glyphSubList = new GlyphSubList(); + list.add(glyphSubList); + } else { + glyphSubList = (GlyphSubList) last; + } + glyphSubList.add(glyph); + } + + /** + * Add a position + * @param position to be added + */ + public void add(Float position) { + list.add(position); + } + + /** + * Checks if the list is empty + * @return true if it is empty + */ + public boolean isEmpty() { + return list.isEmpty(); + } + + /** + * Clears the list + */ + public void clear() { + list.clear(); + } + + /** + * Converts GlyphsAndPositions to a list of objects (GlyphSubList and Float) + * @return the list + */ + public List toList() { + return Collections.unmodifiableList(list); + } + + /** + * Converts GlyphsAndPositions to an array of objects (GlyphSubList and Float) + * @return the array + */ + public Object[] toArray() { + return Collections.unmodifiableList(list).toArray(); + } +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java index d57ab7699a4..649c1b07a82 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java @@ -17,6 +17,7 @@ package org.apache.pdfbox.pdmodel; import java.awt.Color; +import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -95,6 +96,7 @@ abstract class PDAbstractContentStream implements Closeable private final Map gsubWorkers = new HashMap<>(); private final GsubWorkerFactory gsubWorkerFactory = new GsubWorkerFactory(); + private GlyphLayoutProcessor glyphLayoutProcessor; /** * Create a new appearance stream. @@ -113,6 +115,23 @@ abstract class PDAbstractContentStream implements Closeable formatDecimal.setGroupingUsed(false); } + /** + * Sets the glyph layout processor + * @param glyphLayoutProcessor lyph layout processor + */ + public void setGlyphLayoutProcessor(GlyphLayoutProcessor glyphLayoutProcessor) { + this.glyphLayoutProcessor = glyphLayoutProcessor; + glyphLayoutProcessor.setContentStream(this); + } + + /** + * Returns the glyph layout processor or null + * @return the glyph layout processor or null + */ + public GlyphLayoutProcessor getGlyphLayoutProcessor() { + return glyphLayoutProcessor; + } + /** * Sets the maximum number of digits allowed for fractional numbers. * @@ -210,8 +229,11 @@ else if (!font.isEmbedded() && !font.isStandard14()) { LOG.info("No GSUB data found in font {}", font.getName()); } + if(glyphLayoutProcessor != null) + { + glyphLayoutProcessor.setFontAndSize(type0Font, fontSize); + } } - writeOperand(resources.add(font)); writeOperand(fontSize); writeOperator(OperatorName.SET_FONT_AND_SIZE); @@ -252,6 +274,37 @@ else if (obj instanceof Float) writeOperator(OperatorName.SHOW_TEXT_ADJUSTED); } + /** + * Show the given glyphs at the specofierd positions + * @param glyphsAndPositions List of glyphs and positions + * @throws IOException if en IO-error occurs + */ + public void showGlyphsWithPositioning(GlyphsAndPositions glyphsAndPositions) throws IOException { + write("["); + + for (Object obj : glyphsAndPositions.toArray()) { + if (obj instanceof GlyphsAndPositions.GlyphSubList) { + GlyphsAndPositions.GlyphSubList glyphSubList = (GlyphsAndPositions.GlyphSubList) obj; + int[] intGlyphArray = new int[glyphSubList.size()]; + // Convert Type to int[] + for (int i = 0; i < intGlyphArray.length; i++) { + intGlyphArray[i] = glyphSubList.get(i); + } + writeTextPDType0Font(intGlyphArray); + } else if (obj instanceof Float) { + writeOperand((Float) obj); + } else { + if (obj == null) { + throw new NullPointerException("Argument contains null entry"); + } + throw new IllegalArgumentException("Argument must consist of array of Float and GlyphsAndPositions.GlyphSubList types, not " + obj.getClass().getName()); + } + } + write("] "); + writeOperator(OperatorName.SHOW_TEXT_ADJUSTED); + } + + /** * Shows the given text at the location specified by the current text matrix. * @@ -261,7 +314,71 @@ else if (obj instanceof Float) */ public void showText(String text) throws IOException { - showTextInternal(text); + PDFont font = fontStack.peek(); + if(glyphLayoutProcessor != null && glyphLayoutProcessor.supportsFont(font)) + { + glyphLayoutProcessor.showText(text); + } + else + { + showTextInternal(text); + write(" "); + writeOperator(OperatorName.SHOW_TEXT); + } + } + + + /** + * Outputs part of a glyphVector - only for PDType0Font + * + * @param glyphCodes The glyph codes to write + * + * @throws IOException + */ + void writeTextPDType0Font(int[] glyphCodes) throws IOException { + if (!inTextMode) { + throw new IllegalStateException("Must call beginText() before showText()"); + } + if (fontStack.isEmpty()) { + throw new IllegalStateException("Must call setFont() before showText()"); + } + PDFont font = fontStack.peek(); + if (!(font instanceof PDType0Font)) { + throw new IllegalStateException("Must be called with current font instance of PDType0Font"); + + } + PDType0Font pdType0Font = (PDType0Font) font; + + // encode glyphs, update set of used glyphs + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Set glyphIds = new HashSet<>(); + + for (int glyphCode: glyphCodes) { + out.write(pdType0Font.encodeGlyphId(glyphCode)); + if (glyphCode < 0xFFFF) { + glyphIds.add(glyphCode); + } + } + byte[] encodedText = out.toByteArray(); + + // add glyphs to subset + if (pdType0Font.willBeSubset()) { + pdType0Font.addGlyphsToSubset(glyphIds); + } + // write encoded text and the PDF operator + COSWriter.writeString(encodedText, outputStream); + } + + /** + * + * @param glyphVector + * @param start + * @param end + * @throws IOException + */ + void showTextPDType0Font(GlyphVector glyphVector, int start, int end) throws IOException { + int[] glyphCodes = glyphVector.getGlyphCodes(start, end - start, new int[end - start]); + writeTextPDType0Font(glyphCodes); write(" "); writeOperator(OperatorName.SHOW_TEXT); } @@ -371,6 +488,26 @@ public void newLineAtOffset(float tx, float ty) throws IOException writeOperator(OperatorName.MOVE_TEXT); } + /** + * The Td operator. + * Move to the start of the next line, offset from the start of the current line by (tx, ty). + * + * @param tx The x translation. + * @param ty The y translation. + * @throws IOException If there is an error writing to the stream. + * @throws IllegalStateException If the method was not allowed to be called at this time. + */ + void newLineAtOffsetBasic(float tx, float ty) throws IOException + { + if (!inTextMode) + { + throw new IllegalStateException("Error: must call beginText() before newLineAtOffset()"); + } + writeOperand(tx); + writeOperand(ty); + writeOperator(OperatorName.MOVE_TEXT); + } + /** * The Tm operator. Sets the text matrix to the given values. * A current text matrix will be replaced with the new one. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java index 84d118bcc58..f0ddbd65e5e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java @@ -34,6 +34,7 @@ import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.pdfparser.PDFStreamParser; import org.apache.pdfbox.pdfwriter.ContentStreamWriter; +import org.apache.pdfbox.pdmodel.GlyphLayoutProcessor; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; @@ -492,6 +493,10 @@ private void insertGeneratedAppearance(PDAnnotationWidget widget, { try (PDAppearanceContentStream contents = new PDAppearanceContentStream(appearanceStream, output)) { + GlyphLayoutProcessor glyphLayoutProcessor = field.getAcroForm().getGlyphLayoutProcessor(); + if (glyphLayoutProcessor != null) { + contents.setGlyphLayoutProcessor(glyphLayoutProcessor); + } PDRectangle bbox = resolveBoundingBox(widget, appearanceStream); // Acrobat calculates the left and right padding dependent on the offset of the border edge diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java index 178b30615ed..80409945bb0 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroForm.java @@ -37,6 +37,7 @@ import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.GlyphLayoutProcessor; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; @@ -78,6 +79,8 @@ public final class PDAcroForm implements COSObjectable private final Map> directFontCache = new HashMap<>(); + private GlyphLayoutProcessor glyphLayoutProcessor; + /** * Constructor. * @@ -102,6 +105,22 @@ public PDAcroForm(PDDocument doc, COSDictionary form) dictionary = form; } + /** + * Sets the glyph layout processor + * @param glyphLayoutProcessor lyph layout processor + */ + public void setGlyphLayoutProcessor(GlyphLayoutProcessor glyphLayoutProcessor) { + this.glyphLayoutProcessor = glyphLayoutProcessor; + } + + /** + * Returns the glyph layout processor or null + * @return the glyph layout processor or null + */ + public GlyphLayoutProcessor getGlyphLayoutProcessor() { + return glyphLayoutProcessor; + } + /** * This will get the document associated with this form. * @@ -281,6 +300,9 @@ else if (isVisibleAnnotation(annotation)) try (PDPageContentStream contentStream = new PDPageContentStream( document, page, AppendMode.APPEND, true, !isContentStreamWrapped)) { + if (glyphLayoutProcessor != null) { + contentStream.setGlyphLayoutProcessor(glyphLayoutProcessor); + } isContentStreamWrapped = true; PDAppearanceStream appearanceStream = annotation.getNormalAppearanceStream();