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