Von Asciidoc zu PDF/A-2b
Warum Asciidoc? Und warum PDF/A-2b?
Immer wieder verfasse ich Dokumentationen in Asciidoc, da diese Auszeichnungssprache es erlaubt, automatisch in verschiedene Zielformate zu rendern, darüberhinaus einfach in Git verwaltbar ist, der Quelltext lesbar bleibt und es einen zwingt nicht an Featuritis zu erkranken.
Beruflich arbeite ich in der digitalen Langzeitarchivierung und PDF/A ist eine Reihe von Standards für archivfähige PDFs. Bisher gelang leidlich die Erzeugung von PDF/A-1b, welches folgende Probleme aufwies:
- sehr große Dateien, da Grafiken nur deflate (ZIP) komprimiert
- keine Unterstützung von Transparenz und Ebenen
Daher sollte mindestens PDF/A-2b erzeugt werden, da dieses einige Vorteile hat:
- bessere Kompression von Grafiken
- Unterstützung von Transparenz und Ebenen
Asciidoc nach PDF
asciidoctor-pdf generiert von Haus aus PDF1.4. Die PDF/A-Generierung funktioniert nicht wirklich. Als Vorbereitung generieren wir in erstem Schritt ein PDF mit folgenden Parametern:
asciidoctor-pdf -v -a data-uri -a media=prepress -a pdf-version=1.7 --failure-level=ERROR input.asciidoc output1.pdf
Ersetzen PDFMark
Für die Generierung von PDF/A benötigen wir korrekte PDFMARK-Einträge im PDF. asciidoctor-pdf generiert diese aus dem Titel der Dokumente automatisch. Das Problem ist, dass laut Spezifikation die PDFMARK Einträge nur US-ASCII Zeichen enthalten dürfen. Daher wird die PDFMARK wie folgt generiert:
use strict;
use warnings;
use v5.32;
# filter script to create pdfmark-files from asciidoc-files
# call it: perl gen_pdfmark.pl <in.adoc >out.pdfmark
# further doc: https://www.meadowmead.com/wp-content/uploads/2011/04/PDFMarkRecipes.pdf
my $max_length = 127; # see https://opensource.adobe.com/dc-acrobat-sdk-docs/library/pdfmark/pdfmark_Basic.html
my %values;
my $title_candidate;
binmode(STDIN, ":encoding(UTF-8)");
while (<>) {
chomp;
next if m/^#/;
s/Ä/Ae/g;
s/Ö/Oe/g;
s/Ü/Ue/g;
s/ä/ae/g;
s/ö/oe/g;
s/ü/ue/g;
s/ß/ss/g;
s/\(/ -/g;
s/\)/- /g;
#warn "line='$_' (tc=$title_candidate)\n";
# find title
if (!exists $values{title}) {
if (m/^=+$/ && defined $title_candidate) {
# limit to maximum length
$title_candidate =~ m/^(.{1,$max_length})/;
$values{title} = $1;
# warn "TITLE=>>$title_candidate<<";
if ($title_candidate =~ m/Handreichung/) { $values{subject} = "Handreichung"; }
elsif ($title_candidate =~ m/Diskussion/) { $values{subject} = "Diskussionspapier"; }
elsif ($title_candidate =~ m/Spezifikation/) { $values{subject} = "Spezifikation"; }
else {$values{subject} = "Hinweise";}
}
$title_candidate = $_;
}
if (!exists $values{author}) {
if (m/^(:author: )(.{1,$max_length})$/) {
$values{author} = $2;
}
}
if (!exists $values{email}) {
if (m/^(:email: )(.{1,$max_length})$/) {
$values{email} = $2;
}
}
if (!exists $values{creation_date}) {
if (m/^(:date: )(\d{4})-(\d{2})-(\d{2})$/) {
$values{creation_date} = "D:$2$3${4}000000+01'00";
}
}
if (!exists $values{language}) {
if (m/^(:lang: )(.*)$/) {
$values{language} = $2;
}
}
}
$values{producer} = 'Art1Pirat';
open(my $fh, "asciidoctor-pdf -v|");
my $line = <$fh>;
chomp $line;
$values{creator} = $line;
close $fh;
say <<"PDFMARK";
[ /Title ($values{title})
/Producer ($values{producer})
/Subject ($values{subject})
/CreationDate ($values{creation_date})
/Author ($values{author})
/Creator ($values{creator})
/DOCINFO pdfmark
PDFMARK
1;
Mit dem Aufruf von Ghostscript wird ein PDF erzeugt, in dem das PDFMARK ersetzt wird:
gs \
-dBATCH -dNOPAUSE\
-sDEVICE=pdfwrite\
-sOutputFile=output2.pdf\
output1.pdf output1.pdfmark
Erzeugen des PDF/A Dokuments
Im letzten Schritt wird das PDF/A erzeugt, dazu benötigen wir die Datei PDFA_def.ps, die bei Ghostscript mitgeliefert wird.
gs \
-dPDFA=2\
-dPreserveAnnots=false \
-dEmbedAllFonts=true \
-dSubsetFonts=true \
-dCompressFonts=true \
-dBATCH -dNOPAUSE\
-sProcessColorModel=DeviceRGB \
-sColorConversionStrategy=RGB \
--permit-file-read=./ \
-sDEVICE=pdfwrite \
-sOutputFile=output3.pdf \
PDFA_def.ps output2.pdf
Voila!, nun noch veraPDF (ein Validator für PDF/A) aufgerufen:
Prüfen mit veraPDF
verapdf --loglevel=1 output3.pdf