La justification complète est la disposition qui permet à une colonne de texte de s'aligner à la fois sur les bords gauche et droit, l'aspect que vous attendez d'un livre imprimé ou d'un rapport formel. C'est facile à décrire et étonnamment facile à rater, car la réponse à la question "où va l'espace supplémentaire" n'est pas la même pour l'anglais que pour le japonais, et parce que la manière naïve de mesurer chaque ligne transforme une page rapide en une page lente. HotPDF vous offre une justification adaptée au script via un seul appel de mise en page en boîte, et sous cet appel se trouve un correctif de performance classique qui vaut la peine d'être compris par lui-même
Cet article aborde les deux. Premièrement, la règle typographique qui décide de la façon dont le jeu (espace) est réparti pour les scripts avec des espaces entre les mots par rapport aux scripts qui n'en ont pas. Deuxièmement, le changement de mesure qui a réduit le coût par page de la justification d'environ quatre-vingts fois sans différence visible dans le résultat. Les deux sont importants si vous générez des documents en volume et souhaitez qu'ils se lisent comme une véritable composition plutôt que comme une sortie à espacement fixe étirée pour s'adapter
Ce que requiert réellement la justification complète
Une ligne de texte dessinée à sa largeur naturelle n'atteint presque jamais le bord droit de sa colonne. Il y a toujours un reste, le jeu (slack), entre l'endroit où se termine le dernier glyphe et l'endroit où se trouve la limite de la colonne. L'alignement à gauche laisse ce jeu sur la droite. L'alignement à droite le déplace vers la gauche. Le centrage le divise. La justification complète le supprime en élargissant la ligne elle-même jusqu'à ce que les deux bords rencontrent la boîte, et la seule manière honnête de le faire est d'écarter les glyphes de l'intérieur
La règle qui sépare la bonne justification de la mauvaise est l'endroit où vous placez le jeu. Un script qui écrit des mots avec des espaces entre eux, comme l'anglais et le reste de la famille latine, a des jointures naturelles à chaque espace inter-mots. L'élargissement de ces espaces est invisible à l'œil car les lecteurs acceptent déjà que les espaces entre les mots varient. Un script qui écrit sans espaces entre les mots, comme les caractères chinois Han, les kanas japonais ou le hangeul coréen, n'a pas de telles jointures. Là, le jeu doit être réparti uniformément entre les glyphes adjacents, ce qui est le principe que les typographes japonais appellent kintou-waritsuke, un espacement régulier. Appliquer un étirement d'espace de mot de style latin sur une ligne CJK, ou entasser tout le jeu à l'endroit où une ligne CJK se trouve par hasard contenir un espace, produit les lézardes et les vides qui marquent un travail d'amateur
Comment HotPDF décide où va l'espace
HotPDF prend cette décision par espace, et non par ligne. Lorsqu'il justifie une ligne, il parcourt chaque paire de glyphes adjacente et demande si une limite extensible se trouve entre eux. Une limite est extensible lorsque l'un ou l'autre côté est un espace ou une tabulation, le cas latin, ou lorsque les deux côtés sont des caractères séparables CJK, le cas de l'espacement régulier. Il compte ces limites, divise le jeu de la ligne également entre elles et ajoute cette part à chaque espace admissible
La conséquence en découle naturellement. Une ligne anglaise n'a de limites extensibles qu'à ses espaces de mots, donc tout le jeu y atterrit et les mots s'écartent tandis que les lettres à l'intérieur de chaque mot conservent leur espacement naturel. Une ligne Han ou kana a une limite extensible entre presque chaque paire de glyphes, de sorte que le jeu se répartit uniformément sur toute la ligne, exactement l'espacement inter-glyphes régulier que ces scripts requièrent. Une ligne qui est un seul long mot latin sans espace interne n'a aucune limite extensible du tout, alors HotPDF la laisse à sa largeur naturelle plutôt que de déchirer le mot lettre par lettre. La même logique gère des passages mixtes latins et CJK sur une même ligne sans traitement spécial, car la décision est locale à chaque limite
Une limite est délibérément exclue partout. La position après le dernier glyphe d'une ligne n'est jamais traitée comme un espace, car s'étirer là ne ferait que réintroduire un reste à droite, ce qui est le contraire de la justification
Pourquoi la dernière ligne est laissée seule
La dernière ligne d'un paragraphe est spéciale, et se tromper à ce sujet est le bogue de justification le plus courant. La dernière ligne d'un paragraphe est généralement courte, souvent seulement quelques mots, et l'étirer sur toute la largeur de la colonne entraîne ces mots à travers la page dans une ligne clairsemée et brisée. Une typographie correcte laisse la dernière ligne à sa largeur naturelle, alignée à gauche
HotPDF détecte la ligne de fin par sa position. Au fur et à mesure qu'il enveloppe le texte en lignes, il sait quand la ligne qu'il vient de diviser atteint la fin de la chaîne fournie. Cette ligne finale est émise avec un simple alignement à gauche et conserve sa largeur naturelle. Chaque ligne qui la précède est justifiée sur les deux bords. Les sauts de ligne forcés que vous écrivez dans le texte sont respectés tels qu'ils sont écrits, de sorte qu'une ligne courte intentionnelle n'est jamais étirée non plus. Le lecteur voit un bloc de texte rectangulaire propre dont la dernière ligne se termine naturellement, ce qui est ce que l'œil attend
Le coût de mesure qui rendait la justification lente
Pour justifier une ligne, vous devez connaître sa largeur exacte, et vous devez connaître l'avance (advance) de chaque glyphe afin de pouvoir placer précisément l'espace supplémentaire. La première implémentation obtenait ces nombres de la manière évidente. Elle mesurait la ligne entière avec une requête de largeur Unicode complète, puis mesurait préfixe après préfixe pour récupérer l'avance de chaque glyphe par différence. Pour une ligne de N glyphes, cela représente N+1 appels dans le moteur de mesure, et chaque appel est un aller-retour GDI complet, demandant au système d'exploitation de mettre en forme et de mesurer le texte et de renvoyer la réponse
Par ligne, cela semble peu coûteux. À l'échelle d'une page, ça ne l'est pas. Prenez une page A4 dense de texte de corps, soit environ quarante-cinq lignes d'environ quatre-vingts caractères chacune. À N+1 allers-retours par ligne, cela représente environ 81 allers-retours pour chaque ligne et à peu près 3 645 pour la page, la quasi-totalité d'entre eux étant passés à remesurer le texte que le moteur avait déjà examiné quelques instants plus tôt. Sur un travail par lots (batch) produisant des milliers de pages, cette surcharge domine le temps de mise en page, et chaque aller-retour franchit la frontière entre votre processus et le sous-système graphique
Un appel au lieu de N plus un
Le correctif est le genre de changement qui semble petit et rapporte gros. Le GDI peut déjà signaler la largeur totale d'une chaîne et la position de chaque glyphe en une seule requête. HotPDF l'expose via GetWideCharAdvances, qui remplit un tableau avec l'avance naturelle de chaque glyphe, crénage inclus, et renvoie la largeur totale, en un seul appel plutôt que N+1. La routine de justification, _HPDFEmitJustifiedWideLine en interne, demande toutes les avances une fois, calcule le jeu, le répartit sur les limites extensibles et émet la ligne
Pour cette même page A4, la mesure par ligne chute d'environ 81 allers-retours à un seul, de sorte que la page passe d'environ 3 645 allers-retours à environ 45, soit près d'une réduction par quatre-vingts. La sortie est identique octet par octet, car rien n'a changé concernant la mesure, si ce n'est le nombre de fois où elle est demandée. Le même moteur GDI, les mêmes métriques de police, le même crénage fournissent les mêmes nombres. Seul le nombre d'allers-retours a diminué. Lorsqu'une mesure est déjà correcte, la bonne optimisation consiste à cesser de la demander à plusieurs reprises, et non à l'approximer
Comment la ligne atteint la page
Une fois le jeu réparti, HotPDF émet la ligne avec ExtTextOut et un tableau d'avances par glyphe, le tableau Dx. Chaque entrée est la distance de l'origine d'un glyphe au suivant, ce qui correspond à l'avance naturelle de ce glyphe plus sa part du jeu lorsqu'une limite extensible le suit. Cela correspond directement au modèle d'imagerie PDF. Le texte positionné est écrit avec l'opérateur TJ, un tableau qui entrelace les suites de glyphes avec des ajustements horizontaux explicites, et les valeurs Dx deviennent exactement ces ajustements. C'est pourquoi l'espace supplémentaire atterrit entre les glyphes à des positions précises au sous-point près, plutôt que d'être simulé avec des caractères de remplissage, et c'est pourquoi une ligne HotPDF justifiée se mesure correctement si un outil en aval la relit
Vous n'appelez pas ExtTextOut vous-même pour les paragraphes justifiés. Le point d'entrée est WideTextOutBox, qui enveloppe une chaîne Unicode dans une boîte et applique l'alignement que vous demandez. Il divise le texte en lignes qui correspondent à la largeur de la boîte, place chaque ligne le long de la hauteur de la boîte et renvoie le nombre de caractères qu'il a réussi à faire tenir avant de manquer d'espace vertical. L'alignement est choisi par l'énumération de justification
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
Les trois premiers s'expliquent d'eux-mêmes : alignement à gauche, au centre et à droite. Le quatrième, jtJustify, est la justification complète sur les deux bords décrite ici, et c'est la valeur que WideTextOutBox lit pour activer l'espacement adapté au script
Justifier un paragraphe en pratique
Un exemple complet crée un document, définit une police et verse un paragraphe dans une boîte avec justification complète. Le même code justifie les textes latin et CJK sans changement d'indicateur (flag), car l'adaptation au script vit en dessous de l'API
uses
HPDFDoc;
procedure JustifyParagraph;
var
Pdf: THotPDF;
Body: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Justified.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', 11);
Body :=
'La justification complète répartit le jeu sur chaque ligne remplie de sorte que les deux ' +
'bords rencontrent la colonne, tandis que la dernière ligne conserve sa largeur naturelle. ' +
'Pour les scripts avec des espaces entre les mots, l''espace atterrit entre les mots ; pour ' +
'les scripts qui n''en ont pas, il se répartit uniformément entre les glyphes.';
// X, Y, Interligne, LargeurBoîte, HauteurBoîte, Texte, Alignement
Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Pour dessiner le même bloc aligné à gauche, centré ou aligné à droite, modifiez uniquement le dernier argument en jtLeft, jtCenter ou jtRight. L'enveloppement, le placement des lignes et la valeur de retour restent les mêmes. La largeur mesurée qui pilote les quatre chemins provient de GetWideTextWidth, la requête de largeur compatible Unicode qui mesure correctement un WideString là où l'ancienne mesure par octet dimensionnerait mal tout ce qui dépasse Latin-1, ce qui permet à la boîte d'envelopper le texte CJK et les paires de substitution (surrogate-pair) au bon endroit pour commencer
La justification est une couche d'une pile de mise en forme de texte plus large. Lorsqu'une ligne contient des scripts qui réorganisent ou joignent leurs glyphes, les décisions d'espacement ici se superposent au travail décrit dans notre article sur la mise en forme de textes à scripts complexes, et lorsqu'une police comporte des variantes typographiques que vous souhaitez sélectionner, découvrez comment piloter les variantes stylistiques OpenType GSUB. Tout cela est livré dans le composant HotPDF pour Delphi et C++Builder, aux côtés des API de texte, de mise en page et de document plus larges couvertes sur ce blog