PDFlibPas peut encoder des images binaires en JBIG2 par deux backends différents. L'un est un encodeur MMR natif en Object Pascal, toujours présent. L'autre est un encodeur externe à dictionnaire de symboles qui produit une sortie nettement plus petite sur du texte numérisé, et il est optionnel : un projet doit lier l'unité backend pour qu'il existe du tout. Cette distinction est la source de la surprise la plus courante avec cette fonctionnalité, donc elle mérite d'être énoncée d'abord : DefaultJBIG2EncodeOptions demande l'encodeur externe par défaut, et lorsque l'unité backend n'est pas liée, la demande retombe silencieusement sur la voie MMR Pascal
Sous Delphi et C++Builder, le backend externe est un ensemble d'objets statiques préconstruits. Sous Free Pascal, il a dû devenir une DLL, et le chemin vers cette conclusion est une histoire d'éditeur de liens utile à quiconque a essayé de lier des objets C++ dans un programme Free Pascal
L'enregistrement est le contrat
L'unité backend s'enregistre depuis sa section d'initialisation en appelant RegisterJBIG2EncoderBackend. Les appelants la demandent soit par le bit d'options, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, qui vaut 4, soit par le paramètre UseExternalEncoder des points d'entrée d'image étendus. Le parapluie de la bibliothèque ne tire délibérément pas l'unité backend, car porter un grand jeu d'objets doit être la décision de chaque projet ; dans l'arborescence C++Builder, par exemple, elle est incluse explicitement par les projets qui la veulent
La conséquence pour les appelants est que demander l'encodeur externe est une préférence, pas une garantie, et une compilation qui oublie l'unité produit des fichiers plus gros plutôt qu'une erreur. Si la taille de sortie compte assez pour demander le meilleur encodeur, elle compte assez pour vérifier que vous l'avez obtenu
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend dynamique pour Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // jeu d'objets statiques pour Delphi / C++Builder
{$ENDIF}
var
Pdf: TPDFlib;
ImageId: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.NewPage;
// Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
// BlackDotSize, LossyLevel
ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
0, 1, 1, 0, 0, 0);
if ImageId = 0 then
raise Exception.Create('JBIG2 encoding failed');
Pdf.SaveToFile('archive.pdf');
finally
Pdf.Free;
end;
end;
Compiler l'unité a pris deux lignes. Les symboles ont été le travail
Faire compiler l'unité backend elle-même sous Free Pascal a pris exactement deux changements : régler le dialecte assembleur, et remplacer un constructeur de réglages de format à base d'enregistrement par la variable globale par défaut. Cela reflète bien à quel point le Pascal simple est portable entre les deux compilateurs
Le côté symboles a été le vrai travail. Le jeu d'objets référence 176 symboles C. Parmi eux, 128 avaient déjà des implémentations Pascal dans l'unité et n'avaient besoin que de noms d'export attachés, car Delphi utilise le nom de fonction comme nom de symbole tandis que Free Pascal exige une déclaration explicite de nom public. Vingt-sept étaient partagés avec le codec JPEG 2000 et devaient être exportés depuis exactement un endroit, car les définir deux fois casse tout programme qui lie les deux. Les 21 restants étaient des entrées de plateforme et d'exécution C, seize fonctions de fichiers Win32 plus une poignée d'appels de bibliothèque standard, et ils sont allés dans une nouvelle unité de compatibilité
Rien de tout cela n'est conceptuellement difficile, et tout est nécessaire avant que l'éditeur de liens n'essaie seulement. C'est à l'éditeur de liens que ça s'est arrêté
Trois voies d'édition de liens, trois impasses
L'éditeur de liens interne de Free Pascal ne peut pas lire les fichiers objets, car ils ont été produits par un compilateur qui émet des sections COMDAT associatives et l'éditeur de liens interne rapporte qu'il ne les prend pas en charge. C'est un refus sec, pas un avertissement
Passer à un éditeur de liens externe semblait être la réponse. L'éditeur de liens binutils livré avec Free Pascal plante carrément en appliquant le ramasse-miettes de sections à cette archive, et ce drapeau fait partie du jeu de paramètres fixe que Free Pascal passe pour la cible Windows 64 bits, donc il ne peut pas être retiré de la ligne de commande ; les commutateurs documentés pour le supprimer sont ignorés sur cette voie. Fournir un binutils bien plus récent échoue différemment : il ne peut pas du tout traiter le script d'édition de liens Free Pascal, produisant une sortie vide sans le script et un mur d'erreurs de réadressage avec
Une limite découverte en chemin vaut la peine d'être connue même si vous ne rencontrez jamais le problème d'éditeur de liens. L'éditeur de liens externe résout les chemins des fichiers objets par rapport au répertoire de sortie de l'exécutable plutôt que par rapport à l'arborescence source, donc une directive d'inclusion d'objet relative ne fonctionne que lorsque le répertoire de sortie arrive à coïncider avec le répertoire de travail de compilation. Une bibliothèque ne peut pas présumer cela du projet d'un consommateur, ce qui est en soi une raison de préférer une bibliothèque liée à des objets en vrac
Pourquoi un autre compilateur C++ n'aide pas
L'idée suivante évidente est de reconstruire le côté C++ avec un compilateur dont les objets sont lisibles par Free Pascal. Cela ne marche pas non plus, et la raison est fondamentale plutôt qu'une affaire de commutateurs. Une unité de traduction C++ minimale contenant un template, compilée avec toutes les fonctionnalités de génération de code désactivées, émet quand même des symboles externes faibles, car l'instanciation de templates et d'inline en produit par construction. Free Pascal rejette carrément cette classe de symboles. La direction inverse échoue aussi : un éditeur de liens C++ grand public ne peut pas consommer les objets de l'autre compilateur à cause de la même gestion des sections COMDAT
Le code C++ ne peut donc pas être livré en objets à Free Pascal par aucune voie disponible. Il peut être livré en DLL, et c'est ce qui s'est passé : l'encodeur et sa dépendance de traitement d'image sont construits dans une seule bibliothèque exposant deux points d'entrée C plats, et l'unité backend Free Pascal les lie dynamiquement et s'enregistre exactement comme le fait le backend statique. La voie Delphi et C++Builder n'a pas été touchée du tout, ce qui est le bon résultat ; un problème de portabilité sur une chaîne d'outils ne doit pas perturber la chaîne qui fonctionnait déjà
La polarité est la chose qui va vous jouer un tour
Entre un bitmap binaire Windows et un encodeur JBIG2, il y a un décalage de convention qu'aucun système de types n'attrapera. Une scanline de bitmap indépendant du périphérique à un bit par pixel traite un bit allumé comme blanc. L'encodeur traite un bit allumé comme noir. Passez les scanlines telles quelles et vous obtenez un flux JBIG2 parfaitement valide du négatif photographique de votre page
// DIB à un bit : bit allumé signifie blanc. Encodeur JBIG2 : bit allumé
// signifie noir. Inversez chaque octet en entrée
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
La méthode de vérification compte autant que la correction. Comparer les longueurs des flux compressés ne vous apprend rien, car une image négative se compresse à une taille similaire. Regarder la page prouve seulement qu'elle n'est pas manifestement inversée. La vérification fiable est de rendre la sortie des deux voies d'encodage, Pascal natif et externe, en PNG et de les comparer octet par octet : les deux encodeurs sont sans perte sur la même image source, donc tout autre chose qu'une correspondance exacte est un bug dans l'un d'eux. Cette comparaison est maintenant un test de régression permanent, et c'est le genre d'assertion qui vaut la peine d'être construite chaque fois que deux implémentations sont censées coïncider exactement
Quel backend utiliser
Pour du contenu binaire général, demi-teintes tramées, dessin au trait, graphiques mixtes, l'encodeur MMR Pascal natif est adéquat et n'a aucun coût de déploiement. Pour le texte numérisé, qui est le cas pour lequel JBIG2 a été conçu, l'encodeur externe à dictionnaire de symboles est là où vit la réduction de taille, car il factorise les formes de glyphes répétées dans un dictionnaire au lieu de réencoder chaque occurrence. Si vous produisez des archives de documents numérisés, cette différence est assez grande pour changer la planification du stockage
La question amont, comment l'image binaire est produite en premier lieu, compte tout autant pour la taille de sortie ; le rendu monochrome par région est couvert dans l'article sur le rendu monochrome par région, et la stratégie de taille à l'échelle du document dans l'optimisation de la taille des fichiers PDF et le sous-ensemble de polices. Pour des ensembles de numérisations avec pages répétées, la déduplication bat souvent une meilleure compression, ce qui fait l'objet de la déduplication perceptuelle d'images. La disponibilité des chaînes d'outils et des backends par plateforme est listée sur la page produit de la losLab PDF Developer Library