diff --git a/fontbox/src/main/java/org/apache/fontbox/type1/Type1Font.java b/fontbox/src/main/java/org/apache/fontbox/type1/Type1Font.java index 66e452b9760..44f6677298a 100644 --- a/fontbox/src/main/java/org/apache/fontbox/type1/Type1Font.java +++ b/fontbox/src/main/java/org/apache/fontbox/type1/Type1Font.java @@ -85,6 +85,137 @@ public static Type1Font createWithSegments(byte[] segment1, byte[] segment2) thr return parser.parse(segment1, segment2); } + /** + * Reads font attributes and assigns values to respective fields of a font object. + * @param key The attribute key indicating the specific font attribute. + * @param value list of tokens representing the value(s) associated with the attribute. + * @throws IOException If an input/output exception occurs during the process. + */ + public void readFontAttributes(String key, List value) throws IOException + { + switch (key) + { + case "version": + this.version = value.get(0).getText(); + break; + case "Notice": + this.notice = value.get(0).getText(); + break; + case "FullName": + this.fullName = value.get(0).getText(); + break; + case "FamilyName": + this.familyName = value.get(0).getText(); + break; + case "Weight": + this.weight = value.get(0).getText(); + break; + case "ItalicAngle": + this.italicAngle = value.get(0).floatValue(); + break; + case "isFixedPitch": + this.isFixedPitch = value.get(0).booleanValue(); + break; + case "UnderlinePosition": + this.underlinePosition = value.get(0).floatValue(); + break; + case "UnderlineThickness": + this.underlineThickness = value.get(0).floatValue(); + break; + case "FontName": + this.fontName = value.get(0).getText(); + break; + case "PaintType": + this.paintType = value.get(0).intValue(); + break; + case "FontType": + this.fontType = value.get(0).intValue(); + break; + case "FontMatrix": + this.fontMatrix = arrayToNumbers(value); + break; + case "FontBBox": + this.fontBBox = arrayToNumbers(value); + break; + case "UniqueID": + this.uniqueID = value.get(0).intValue(); + break; + case "StrokeWidth": + this.strokeWidth = value.get(0).floatValue(); + break; + case "FID": + this.fontID = value.get(0).getText(); + break; + case "BlueValues": + this.blueValues = arrayToNumbers(value); + break; + case "OtherBlues": + this.otherBlues = arrayToNumbers(value); + break; + case "FamilyBlues": + this.familyBlues = arrayToNumbers(value); + break; + case "FamilyOtherBlues": + this.familyOtherBlues = arrayToNumbers(value); + break; + case "BlueScale": + this.blueScale = value.get(0).floatValue(); + break; + case "BlueShift": + this.blueShift = value.get(0).intValue(); + break; + case "BlueFuzz": + this.blueFuzz = value.get(0).intValue(); + break; + case "StdHW": + this.stdHW = arrayToNumbers(value); + break; + case "StdVW": + this.stdVW = arrayToNumbers(value); + break; + case "StemSnapH": + this.stemSnapH = arrayToNumbers(value); + break; + case "StemSnapV": + this.stemSnapV = arrayToNumbers(value); + break; + case "ForceBold": + this.forceBold = value.get(0).booleanValue(); + break; + case "LanguageGroup": + this.languageGroup = value.get(0).intValue(); + break; + default: + break; + } + } + + /** + * Extracts values from an array as numbers. + */ + private List arrayToNumbers(List value) throws IOException + { + List numbers = new ArrayList<>(); + for (int i = 1, size = value.size() - 1; i < size; i++) + { + Token token = value.get(i); + if (token.getKind() == Token.REAL) + { + numbers.add(token.floatValue()); + } + else if (token.getKind() == Token.INTEGER) + { + numbers.add(token.intValue()); + } + else + { + throw new IOException("Expected INTEGER or REAL but got " + token + + " at array position " + i); + } + } + return numbers; + } + // font dictionary String fontName = ""; Encoding encoding = null; diff --git a/fontbox/src/main/java/org/apache/fontbox/type1/Type1Lexer.java b/fontbox/src/main/java/org/apache/fontbox/type1/Type1Lexer.java index 56aaf64adaa..7512edb1909 100644 --- a/fontbox/src/main/java/org/apache/fontbox/type1/Type1Lexer.java +++ b/fontbox/src/main/java/org/apache/fontbox/type1/Type1Lexer.java @@ -22,6 +22,9 @@ import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; /** @@ -76,6 +79,93 @@ public Token nextToken() throws IOException return curToken; } + /** + * Reads a procedure. + */ + public List readProc() throws IOException + { + List value = new ArrayList<>(); + + int openProc = 1; + while (true) + { + if (this.peekToken() == null) + { + throw new IOException("Malformed procedure: missing token"); + } + + if (this.peekKind(Token.START_PROC)) + { + openProc++; + } + + Token token = this.nextToken(); + value.add(token); + + if (token.getKind() == Token.END_PROC) + { + openProc--; + if (openProc == 0) + { + break; + } + } + } + Token executeonly = readMaybe(Token.NAME, "executeonly"); + if (executeonly != null) + { + value.add(executeonly); + } + + return value; + } + + /** + * Reads a procedure but without returning anything. + */ + public void readProcVoid() throws IOException + { + int openProc = 1; + while (true) + { + if (this.peekToken() == null) + { + throw new IOException("Malformed procedure: missing token"); + } + if (this.peekKind(Token.START_PROC)) + { + openProc++; + } + + Token token = this.nextToken(); + + if (token.getKind() == Token.END_PROC) + { + openProc--; + if (openProc == 0) + { + break; + } + } + } + readMaybe(Token.NAME, "executeonly"); + } + + /** + * Reads the next token if and only if it is of the given kind and + * has the given value. + * + * @return token or null if not the expected one + */ + public Token readMaybe(Token.Kind kind, String name) throws IOException + { + if (this.peekKind(kind) && this.peekToken().getText().equals(name)) + { + return this.nextToken(); + } + return null; + } + /** * Returns the next token without consuming it. * @return The next token diff --git a/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java b/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java index addca852482..974adb2e6c2 100644 --- a/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java +++ b/fontbox/src/main/java/org/apache/fontbox/type1/Type1Parser.java @@ -99,9 +99,9 @@ private void parseASCII(byte[] bytes) throws IOException read(Token.LITERAL); // font name read(Token.NAME, "known"); read(Token.START_PROC); - readProcVoid(); + lexer.readProcVoid(); read(Token.START_PROC); - readProcVoid(); + lexer.readProcVoid(); read(Token.NAME, "ifelse"); } @@ -109,7 +109,7 @@ private void parseASCII(byte[] bytes) throws IOException int length = read(Token.INTEGER).intValue(); read(Token.NAME, "dict"); // found in some TeX fonts - readMaybe(Token.NAME, "dup"); + lexer.readMaybe(Token.NAME, "dup"); // if present, the "currentdict" is not required read(Token.NAME, "begin"); @@ -147,7 +147,7 @@ private void parseASCII(byte[] bytes) throws IOException } } - readMaybe(Token.NAME, "currentdict"); + lexer.readMaybe(Token.NAME, "currentdict"); read(Token.NAME, "end"); read(Token.NAME, "currentfile"); @@ -158,35 +158,7 @@ private void readSimpleValue(String key) throws IOException { List value = readDictValue(); - switch (key) - { - case "FontName": - font.fontName = value.get(0).getText(); - break; - case "PaintType": - font.paintType = value.get(0).intValue(); - break; - case "FontType": - font.fontType = value.get(0).intValue(); - break; - case "FontMatrix": - font.fontMatrix = arrayToNumbers(value); - break; - case "FontBBox": - font.fontBBox = arrayToNumbers(value); - break; - case "UniqueID": - font.uniqueID = value.get(0).intValue(); - break; - case "StrokeWidth": - font.strokeWidth = value.get(0).floatValue(); - break; - case "FID": - font.fontID = value.get(0).getText(); - break; - default: - break; - } + font.readFontAttributes(key, value); } private void readEncoding() throws IOException @@ -203,13 +175,13 @@ private void readEncoding() throws IOException { throw new IOException("Unknown encoding: " + name); } - readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "readonly"); read(Token.NAME, "def"); } else { read(Token.INTEGER).intValue(); - readMaybe(Token.NAME, "array"); + lexer.readMaybe(Token.NAME, "array"); // 0 1 255 {1 index exch /.notdef put } for // we have to check "readonly" and "def" too @@ -236,79 +208,22 @@ private void readEncoding() throws IOException codeToName.put(code, name); } font.encoding = new BuiltInEncoding(codeToName); - readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "readonly"); read(Token.NAME, "def"); } } - /** - * Extracts values from an array as numbers. - */ - private List arrayToNumbers(List value) throws IOException - { - List numbers = new ArrayList<>(); - for (int i = 1, size = value.size() - 1; i < size; i++) - { - Token token = value.get(i); - if (token.getKind() == Token.REAL) - { - numbers.add(token.floatValue()); - } - else if (token.getKind() == Token.INTEGER) - { - numbers.add(token.intValue()); - } - else - { - throw new IOException("Expected INTEGER or REAL but got " + token + - " at array position " + i); - } - } - return numbers; - } - /** * Extracts values from the /FontInfo dictionary. */ - private void readFontInfo(Map> fontInfo) + private void readFontInfo(Map> fontInfo) throws IOException { for (Map.Entry> entry : fontInfo.entrySet()) { String key = entry.getKey(); List value = entry.getValue(); - switch (key) - { - case "version": - font.version = value.get(0).getText(); - break; - case "Notice": - font.notice = value.get(0).getText(); - break; - case "FullName": - font.fullName = value.get(0).getText(); - break; - case "FamilyName": - font.familyName = value.get(0).getText(); - break; - case "Weight": - font.weight = value.get(0).getText(); - break; - case "ItalicAngle": - font.italicAngle = value.get(0).floatValue(); - break; - case "isFixedPitch": - font.isFixedPitch = value.get(0).booleanValue(); - break; - case "UnderlinePosition": - font.underlinePosition = value.get(0).floatValue(); - break; - case "UnderlineThickness": - font.underlineThickness = value.get(0).floatValue(); - break; - default: - break; - } + font.readFontAttributes(key, value); } } @@ -322,7 +237,7 @@ private Map> readSimpleDict() throws IOException int length = read(Token.INTEGER).intValue(); read(Token.NAME, "dict"); - readMaybe(Token.NAME, "dup"); + lexer.readMaybe(Token.NAME, "dup"); read(Token.NAME, "begin"); for (int i = 0; i < length; i++) @@ -354,7 +269,7 @@ private Map> readSimpleDict() throws IOException } read(Token.NAME, "end"); - readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "readonly"); read(Token.NAME, "def"); return dict; @@ -414,7 +329,7 @@ private List readValue() throws IOException } else if (token.getKind() == Token.START_PROC) { - value.addAll(readProc()); + value.addAll(lexer.readProc()); } else if (token.getKind() == Token.START_DICT) { @@ -441,10 +356,10 @@ private void readPostScriptWrapper(List value) throws IOException read(Token.NAME, "known"); read(Token.START_PROC); - readProcVoid(); + lexer.readProcVoid(); read(Token.START_PROC); - readProcVoid(); + lexer.readProcVoid(); read(Token.NAME, "ifelse"); @@ -459,78 +374,6 @@ private void readPostScriptWrapper(List value) throws IOException } } - /** - * Reads a procedure. - */ - private List readProc() throws IOException - { - List value = new ArrayList<>(); - - int openProc = 1; - while (true) - { - if (lexer.peekToken() == null) - { - throw new IOException("Malformed procedure: missing token"); - } - - if (lexer.peekKind(Token.START_PROC)) - { - openProc++; - } - - Token token = lexer.nextToken(); - value.add(token); - - if (token.getKind() == Token.END_PROC) - { - openProc--; - if (openProc == 0) - { - break; - } - } - } - Token executeonly = readMaybe(Token.NAME, "executeonly"); - if (executeonly != null) - { - value.add(executeonly); - } - - return value; - } - - /** - * Reads a procedure but without returning anything. - */ - private void readProcVoid() throws IOException - { - int openProc = 1; - while (true) - { - if (lexer.peekToken() == null) - { - throw new IOException("Malformed procedure: missing token"); - } - if (lexer.peekKind(Token.START_PROC)) - { - openProc++; - } - - Token token = lexer.nextToken(); - - if (token.getKind() == Token.END_PROC) - { - openProc--; - if (openProc == 0) - { - break; - } - } - } - readMaybe(Token.NAME, "executeonly"); - } - /** * Parses the binary portion of a Type 1 font. */ @@ -569,7 +412,7 @@ private void parseBinary(byte[] bytes) throws IOException read(Token.NAME, "dict"); // actually could also be "/Private 10 dict def Private begin" // instead of the "dup" - readMaybe(Token.NAME, "dup"); + lexer.readMaybe(Token.NAME, "dup"); read(Token.NAME, "begin"); int lenIV = 4; // number of random bytes at start of charstring @@ -599,29 +442,29 @@ private void parseBinary(byte[] bytes) throws IOException case "ND": read(Token.START_PROC); // the access restrictions are not mandatory - readMaybe(Token.NAME, "noaccess"); + lexer.readMaybe(Token.NAME, "noaccess"); read(Token.NAME, "def"); read(Token.END_PROC); - readMaybe(Token.NAME, "executeonly"); - readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "executeonly"); + lexer.readMaybe(Token.NAME, "readonly"); read(Token.NAME, "def"); break; case "NP": read(Token.START_PROC); - readMaybe(Token.NAME, "noaccess"); + lexer.readMaybe(Token.NAME, "noaccess"); read(Token.NAME); read(Token.END_PROC); - readMaybe(Token.NAME, "executeonly"); - readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "executeonly"); + lexer.readMaybe(Token.NAME, "readonly"); read(Token.NAME, "def"); break; case "RD": // /RD {string currentfile exch readstring pop} bind executeonly def read(Token.START_PROC); - readProcVoid(); - readMaybe(Token.NAME, "bind"); - readMaybe(Token.NAME, "executeonly"); - readMaybe(Token.NAME, "readonly"); + lexer.readProcVoid(); + lexer.readMaybe(Token.NAME, "bind"); + lexer.readMaybe(Token.NAME, "executeonly"); + lexer.readMaybe(Token.NAME, "readonly"); read(Token.NAME, "def"); break; default: @@ -652,50 +495,7 @@ private void parseBinary(byte[] bytes) throws IOException */ private void readPrivate(String key, List value) throws IOException { - switch (key) - { - case "BlueValues": - font.blueValues = arrayToNumbers(value); - break; - case "OtherBlues": - font.otherBlues = arrayToNumbers(value); - break; - case "FamilyBlues": - font.familyBlues = arrayToNumbers(value); - break; - case "FamilyOtherBlues": - font.familyOtherBlues = arrayToNumbers(value); - break; - case "BlueScale": - font.blueScale = value.get(0).floatValue(); - break; - case "BlueShift": - font.blueShift = value.get(0).intValue(); - break; - case "BlueFuzz": - font.blueFuzz = value.get(0).intValue(); - break; - case "StdHW": - font.stdHW = arrayToNumbers(value); - break; - case "StdVW": - font.stdVW = arrayToNumbers(value); - break; - case "StemSnapH": - font.stemSnapH = arrayToNumbers(value); - break; - case "StemSnapV": - font.stemSnapV = arrayToNumbers(value); - break; - case "ForceBold": - font.forceBold = value.get(0).booleanValue(); - break; - case "LanguageGroup": - font.languageGroup = value.get(0).intValue(); - break; - default: - break; - } + font.readFontAttributes(key, value); } /** @@ -816,8 +616,8 @@ private void readCharStrings(int lenIV) throws IOException */ private void readDef() throws IOException { - readMaybe(Token.NAME, "readonly"); - readMaybe(Token.NAME, "noaccess"); // allows "noaccess ND" (not in the Type 1 spec) + lexer.readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "noaccess"); // allows "noaccess ND" (not in the Type 1 spec) Token token = read(Token.NAME); switch (token.getText()) @@ -844,7 +644,7 @@ private void readDef() throws IOException */ private void readPut() throws IOException { - readMaybe(Token.NAME, "readonly"); + lexer.readMaybe(Token.NAME, "readonly"); Token token = read(Token.NAME); switch (token.getText()) @@ -896,21 +696,6 @@ private void read(Token.Kind kind, String name) throws IOException } } - /** - * Reads the next token if and only if it is of the given kind and - * has the given value. - * - * @return token or null if not the expected one - */ - private Token readMaybe(Token.Kind kind, String name) throws IOException - { - if (lexer.peekKind(kind) && lexer.peekToken().getText().equals(name)) - { - return lexer.nextToken(); - } - return null; - } - /** * Type 1 Decryption (eexec, charstring). *