JPedal provides a parser and validator for the Arlington PDF Model
How to use the validator
JPedal comes bundled with a copy of the Arlington PDF Model for use with the Java API. However, if you want to use the command line, or use a custom version of the model, you will need to download a copy of the model in TSV format, which you can find on the PDF Association’s GitHub repo: https://github.com/pdf-association/arlington-pdf-model. Clone/download the repo and note the location of the tsv/latest directory.
Command line
The command line application allows you to validate one or more files against the model. You may specify a level filter to only show validation issues of a specific or worse severity.
java org.jpedal.arlington.validate.PdfArlingtonValidator --tsv <path-to-arlington-tsv-dir> [--level LEVEL] file1.pdf [file2.pdf ...]
All issues are printed in the following format: [level] path-in-pdf (arlington-object-definition): issue-message.
Example output:
[ERROR] Trailer/Root/Pages/Kids[0]/Resources/Font/F1/Encoding/Type (Encoding): Value of 'Type' (/Font) is not one of the permitted values [Encoding]
[ERROR] Trailer/Root/Pages/Kids[0]/Resources/Font/F1/Widths (FontType3): SpecialCase constraint violated: fn:Eval((fn:ArrayLength(Widths) == (1.0 + (@LastChar - @FirstChar))))
[ERROR] Trailer/Root/Pages/Kids[0]/Resources/Font/F2/FontDescriptor (FontCIDType0): Value must be an indirect reference (fn:MustBeIndirect) but is a direct object
[ERROR] Trailer/Root/Pages/Kids[0]/Resources/Font/F2/FontDescriptor/Style/Panose (StyleDict): SpecialCase constraint violated: fn:Eval((fn:StringLength(Panose) == 12.0))
[ERROR] Trailer/Root/Pages/Kids[0]/Resources/Font/F2/FontDescriptor/Descent (FontDescriptorCIDType0): Value of 'Descent' (123) is not one of the permitted values [fn:Eval((@Descent <= 0.0))]
[ERROR] Trailer/Root/PageLayout (Catalog): Value of 'PageLayout' (/AnUndefinedOption) is not one of the permitted values [SinglePage, OneColumn, TwoColumnLeft, TwoColumnRight, TwoPageLeft, TwoPageRight]
[INFO] Trailer/Root/CatalogCustomKey (Catalog): Key 'CatalogCustomKey' is not defined by the Arlington model for Catalog and no wildcard entry is present
[ERROR] Trailer/Root/MarkInfo/Suspects (MarkInfo): Value has type integer but the model expects one of boolean for key/index 'Suspects'
[ERROR] Trailer/Root/Lang (Catalog): Value has type name but the model expects one of string-text for key/index 'Lang'
[WARNING] Trailer/Root/OpenAction/URI (ActionURI): Value of 'URI' ((\003\006Bad ASCII)) contains unprintable ASCII characters
Java
The Java methods allow you to validate one or more files against the model. You may also load password-protected/encrypted files.
// Load model from Jar resources (the bundled model is at "arlington-pdf-model")
final Map<String, ArlingtonObject> model = ArlingtonModelLoader.loadClasspath("arlington-pdf-model");
// Load model from custom location on disk
final Map<String, ArlingtonObject> model = ArlingtonModelLoader.load(Path.of("~/Documents/tsv/latest"));
// Load file
final PdfArlingtonValidator.Result result = validateFile(new File("inputFile.pdf"), model);
// Load file with password
final PdfArlingtonValidator.Result result = validateFile(new File("inputFile.pdf"), "password".getBytes(), model);
Any issues are returned as a list which you can then iterate over and inspect.
Example output:
for (final ValidationIssue issue : result.issues()) {
// Iterate over issues
}
Fix mode
The validator can also fix some issues, rather than just reporting them, and write out a fixed copy of the file. In fix mode the validator will:
- Add a missing required key or array element, if the model gives it a default value
- Remove an optional key with an invalid value (the wrong type, not a permitted value, or failing its special case)
- Move a direct object to a new indirect object, where the model requires it to be indirect
Everything else is still reported as an issue. Name and number trees which contain entries, and the keys which describe how a stream is encoded, are never removed, as doing so would lose content.
Once the fixes have been made, the fixed file is validated again. It is only written out if no errors remain, so the issues reported are those in the fixed file, not the original. The fixed file is written out even if nothing needed fixing.
Encrypted files cannot be fixed. Attempting to fix one will throw an
IOException.
Command line
Pass --fix with a directory to write the fixed files to. Each fixed file is given the same name as its input file, and the input file is never overwritten.
java org.jpedal.arlington.validate.PdfArlingtonValidator --tsv <path-to-arlington-tsv-dir> [--level LEVEL] --fix <output-dir> file1.pdf [file2.pdf ...]
Each fix is printed in the following format: [FIXED] path-in-pdf (arlington-object-definition): fix-message, followed by any issues which remain in the fixed file.
Example output:
[FIXED] Trailer/Root/PageLayout (Catalog): Removed optional key 'PageLayout' with an invalid value (Value of 'PageLayout' (/AnUndefinedOption) is not one of the permitted values [SinglePage, OneColumn, TwoColumnLeft, TwoColumnRight, TwoPageLeft, TwoPageRight])
[FIXED] Trailer/Root/Pages/Kids[0]/Resources/Font/F2/FontDescriptor (FontCIDType0): Moved direct object to indirect object 12 (Value must be an indirect reference (fn:MustBeIndirect) but is a direct object)
[FIXED] Trailer/Root/AF[0] (AFFileSpecification): Added default value /Unspecified (Required key/element 'AFRelationship' does not exist)
3 fix(es) applied, fixed file written to "fixed/file1.pdf"
Java
Use one of the fixFile() methods and provide the location to write the fixed file to, which must not be the input file. Files may be loaded from a File, byte[], or InputStream.
final Map<String, ArlingtonObject> model = ArlingtonModelLoader.loadClasspath("arlington-pdf-model");
final PdfArlingtonValidator.FixResult result = fixFile(new File("inputFile.pdf"), model, new File("outputFile.pdf"));
for (final AppliedFix fix : result.fixes()) {
// Iterate over the fixes which were made
}
for (final ValidationIssue issue : result.issues()) {
// Iterate over the issues which remain in the fixed file
}
if (!result.written()) {
// Errors remain, so the fixed file was not written
}
Fixed files are held in memory while they are validated again, unless they are larger than the memory cache threshold, in which case they are written to an encrypted temporary file in the output directory. The default is 58MB.
PdfArlingtonValidator.setMemoryCacheThreshold(10_000_000); // 10MB