[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// L'aide d'extraction des PDF (test/pdf.js), éprouvée sur des documents
// faits à la main par pdf-lib, dont chaque cote se calcule de tête : la
// boîte d'un texte tourné, le mode de rendu des textes, les segments sous
2026-10-10 18:50:40 -04:00
// la matrice courante et leur épaisseur, les remplissages, les tirets et les
// couleurs, les boîtes de page, les polices, leur ToUnicode et la longueur de
// leur programme embarqué, le dictionnaire /Info et l'identifiant du fichier.
[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
import assert from 'node:assert/strict' ;
import fontkit from '@pdf-lib/fontkit' ;
import {
PDFArray ,
PDFDocument ,
PDFHexString ,
PDFName ,
PDFNumber ,
PDFString ,
StandardFonts ,
concatTransformationMatrix ,
degrees ,
grayscale ,
2026-10-10 18:50:40 -04:00
lineTo ,
moveTo ,
[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
popGraphicsState ,
pushGraphicsState ,
rgb ,
2026-10-10 18:50:40 -04:00
setLineWidth ,
[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
setTextRenderingMode ,
2026-10-10 18:50:40 -04:00
stroke ,
[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
TextRenderingMode ,
} from 'pdf-lib' ;
import { lirePdf } from './pdf.js' ;
import { lirePolices } from './polices.js' ;
import { describe , test } from './lanceur.js' ;
/** Le point typographique en millimètres. */
const MM _PAR _PT = 25.4 / 72 ;
const proche = ( a , b , tolerance = 1e-6 ) => Math . abs ( a - b ) <= tolerance ;
/** Un document d'une page de largeur × hauteur points, que `dessiner` remplit. */
async function document ( dessiner , { largeur = 600 , hauteur = 800 , avant = async ( ) => { } } = { } ) {
const pdf = await PDFDocument . create ( { updateMetadata : false } ) ;
pdf . registerFontkit ( fontkit ) ;
await avant ( pdf ) ;
const page = pdf . addPage ( [ largeur , hauteur ] ) ;
await dessiner ( page , pdf ) ;
return pdf . save ( ) ;
}
const noto = ( pdf ) => pdf . embedFont ( lirePolices ( ) . regulier , { subset : true } ) ;
describe ( 'lirePdf : le texte, en millimètres depuis le coin haut gauche' , ( ) => {
test ( "un texte droit : sa boîte part de sa ligne de base, sa largeur est l'avance, sa hauteur le corps" , async ( ) => {
let avance = 0 ;
const octets = await document ( async ( page , pdf ) => {
const police = await noto ( pdf ) ;
avance = police . widthOfTextAtSize ( 'Iris Ombrelle' , 20 ) ;
page . drawText ( 'Iris Ombrelle' , { x : 100 , y : 700 , size : 20 , font : police } ) ;
} ) ;
const [ page ] = ( await lirePdf ( octets ) ) . pages ;
assert . equal ( page . textes . length , 1 ) ;
const [ fragment ] = page . textes ;
assert . equal ( fragment . texte , 'Iris Ombrelle' ) ;
assert . ok ( proche ( fragment . x , 100 * MM _PAR _PT ) , ` x ${ fragment . x } ` ) ;
assert . ok ( proche ( fragment . y , ( 800 - 720 ) * MM _PAR _PT ) , ` y ${ fragment . y } ` ) ;
assert . ok ( proche ( fragment . l , avance * MM _PAR _PT , 1e-4 ) , ` l ${ fragment . l } ` ) ;
assert . ok ( proche ( fragment . h , 20 * MM _PAR _PT ) , ` h ${ fragment . h } ` ) ;
} ) ;
test ( 'un texte tourné de 90° : la boîte est l\u{2019}englobant des quatre coins du rectangle tourné, haute de son avance' , async ( ) => {
let avance = 0 ;
const octets = await document ( async ( page , pdf ) => {
const police = await noto ( pdf ) ;
avance = police . widthOfTextAtSize ( 'Théo Pervenche' , 20 ) ;
page . drawText ( 'Théo Pervenche' , { x : 300 , y : 300 , size : 20 , font : police , rotate : degrees ( 90 ) } ) ;
} ) ;
const [ fragment ] = ( await lirePdf ( octets ) ) . pages [ 0 ] . textes ;
// Le texte monte depuis (300, 300) ; son haut regarde vers la gauche :
// il couvre x de 280 à 300 et y de 300 à 300 + avance, en points.
assert . ok ( proche ( fragment . x , 280 * MM _PAR _PT ) , ` x ${ fragment . x } ` ) ;
assert . ok ( proche ( fragment . l , 20 * MM _PAR _PT ) , ` l ${ fragment . l } ` ) ;
assert . ok ( proche ( fragment . y , ( 800 - 300 - avance ) * MM _PAR _PT , 1e-4 ) , ` y ${ fragment . y } ` ) ;
assert . ok ( proche ( fragment . h , avance * MM _PAR _PT , 1e-4 ) , ` h ${ fragment . h } ` ) ;
} ) ;
test ( "deux textes d'une même ligne de base, écartés : deux fragments, sans l'espace que le lecteur intercale entre eux" , async ( ) => {
const octets = await document ( async ( page , pdf ) => {
const police = await noto ( pdf ) ;
page . drawText ( 'Iris' , { x : 50 , y : 400 , size : 12 , font : police } ) ;
page . drawText ( 'Ombrelle' , { x : 300 , y : 400 , size : 12 , font : police } ) ;
} ) ;
assert . deepEqual ( ( await lirePdf ( octets ) ) . pages [ 0 ] . textes . map ( ( { texte } ) => texte ) , [ 'Iris' , 'Ombrelle' ] ) ;
} ) ;
test ( "le mode de rendu de chaque texte montré, dans l'ordre : 0 par défaut, 1 au trait, rendu par Q" , async ( ) => {
const octets = await document ( async ( page , pdf ) => {
const police = await noto ( pdf ) ;
page . drawText ( 'Iris' , { x : 50 , y : 500 , size : 12 , font : police } ) ;
page . pushOperators ( pushGraphicsState ( ) , setTextRenderingMode ( TextRenderingMode . Outline ) ) ;
page . drawText ( 'Ombrelle' , { x : 50 , y : 400 , size : 12 , font : police } ) ;
page . pushOperators ( popGraphicsState ( ) ) ;
page . drawText ( 'Pervenche' , { x : 50 , y : 300 , size : 12 , font : police } ) ;
} ) ;
assert . deepEqual ( ( await lirePdf ( octets ) ) . pages [ 0 ] . modesTexte , [ 0 , 1 , 0 ] ) ;
assert . deepEqual ( ( await lirePdf ( await document ( ( ) => { } ) ) ) . pages [ 0 ] . modesTexte , [ ] ) ;
} ) ;
test ( 'les accents et les capitales liées se lisent caractère par caractère, par la ToUnicode' , async ( ) => {
const octets = await document ( async ( page , pdf ) => {
page . drawText ( 'Ôjgq Æ Œuvre' , { x : 50 , y : 400 , size : 12 , font : await noto ( pdf ) } ) ;
} ) ;
const [ fragment ] = ( await lirePdf ( octets ) ) . pages [ 0 ] . textes ;
assert . deepEqual ( [ ... fragment . texte ] , [ ... 'Ôjgq Æ Œuvre' ] ) ;
} ) ;
} ) ;
describe ( 'lirePdf : les chemins, sous la matrice courante' , ( ) => {
test ( 'un trait se lit en millimètres, haut gauche ; une mise à l\u{2019}échelle posée dans le flux le déplace' , async ( ) => {
const octets = await document ( ( page ) => {
page . drawLine ( { start : { x : 72 , y : 700 } , end : { x : 144 , y : 700 } , thickness : 0.5 , color : grayscale ( 0.5 ) } ) ;
page . pushOperators ( pushGraphicsState ( ) , concatTransformationMatrix ( 2 , 0 , 0 , 2 , 0 , 0 ) ) ;
page . drawLine ( { start : { x : 72 , y : 100 } , end : { x : 72 , y : 172 } , thickness : 0.5 } ) ;
page . pushOperators ( popGraphicsState ( ) ) ;
page . drawLine ( { start : { x : 10 , y : 10 } , end : { x : 20 , y : 10 } , thickness : 0.5 } ) ;
} ) ;
const { segments } = ( await lirePdf ( octets ) ) . pages [ 0 ] ;
const attendus = [
{ x1 : 25.4 , y1 : ( 800 - 700 ) * MM _PAR _PT , x2 : 50.8 , y2 : ( 800 - 700 ) * MM _PAR _PT } ,
{ x1 : 50.8 , y1 : ( 800 - 200 ) * MM _PAR _PT , x2 : 50.8 , y2 : ( 800 - 344 ) * MM _PAR _PT } ,
{ x1 : 10 * MM _PAR _PT , y1 : ( 800 - 10 ) * MM _PAR _PT , x2 : 20 * MM _PAR _PT , y2 : ( 800 - 10 ) * MM _PAR _PT } ,
] ;
assert . equal ( segments . length , attendus . length ) ;
segments . forEach ( ( segment , i ) => {
for ( const cle of [ 'x1' , 'y1' , 'x2' , 'y2' ] ) {
assert . ok ( proche ( segment [ cle ] , attendus [ i ] [ cle ] ) , ` segment ${ i } ${ cle } : ${ segment [ cle ] } contre ${ attendus [ i ] [ cle ] } ` ) ;
}
} ) ;
} ) ;
2026-10-10 18:50:40 -04:00
test ( "l'épaisseur d'un trait se lit en millimètres : celle de setLineWidth sous la matrice courante, mesurée en travers du trait ; 1 pt par défaut, rendue par Q" , async ( ) => {
const octets = await document ( ( page ) => {
page . drawLine ( { start : { x : 72 , y : 700 } , end : { x : 144 , y : 700 } , thickness : 0.5 } ) ;
page . pushOperators ( pushGraphicsState ( ) , concatTransformationMatrix ( 2 , 0 , 0 , 2 , 0 , 0 ) ) ;
page . drawLine ( { start : { x : 72 , y : 100 } , end : { x : 72 , y : 172 } , thickness : 0.5 } ) ;
page . pushOperators ( popGraphicsState ( ) , pushGraphicsState ( ) , concatTransformationMatrix ( 3 , 0 , 0 , 1 , 0 , 0 ) ) ;
// Une échelle de 3 en x seulement : un trait horizontal garde son
// épaisseur, un trait vertical la triple.
page . drawLine ( { start : { x : 10 , y : 600 } , end : { x : 50 , y : 600 } , thickness : 0.5 } ) ;
page . drawLine ( { start : { x : 10 , y : 500 } , end : { x : 10 , y : 550 } , thickness : 0.5 } ) ;
page . pushOperators ( popGraphicsState ( ) ) ;
// Hors de tout drawLine : l'épaisseur par défaut, puis celle posée.
page . pushOperators ( moveTo ( 10 , 300 ) , lineTo ( 20 , 300 ) , stroke ( ) ) ;
page . pushOperators ( setLineWidth ( 2 ) , moveTo ( 10 , 200 ) , lineTo ( 20 , 200 ) , stroke ( ) ) ;
} ) ;
const { segments } = ( await lirePdf ( octets ) ) . pages [ 0 ] ;
const attendues = [ 0.5 , 1 , 0.5 , 1.5 , 1 , 2 ] . map ( ( pt ) => pt * MM _PAR _PT ) ;
assert . equal ( segments . length , attendues . length ) ;
segments . forEach ( ( { epaisseur } , i ) => assert . ok ( proche ( epaisseur , attendues [ i ] ) , ` segment ${ i } : ${ epaisseur } contre ${ attendues [ i ] } ` ) ) ;
} ) ;
[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
test ( "un rectangle tracé donne ses quatre côtés ; un rectangle rempli compte un remplissage et aucun segment" , async ( ) => {
const octets = await document ( ( page ) => {
page . drawRectangle ( { x : 72 , y : 72 , width : 72 , height : 144 , borderWidth : 1 , borderColor : grayscale ( 0 ) } ) ;
page . drawRectangle ( { x : 300 , y : 300 , width : 10 , height : 10 , color : rgb ( 1 , 0 , 0 ) } ) ;
} ) ;
const page = ( await lirePdf ( octets ) ) . pages [ 0 ] ;
assert . equal ( page . remplissages , 1 ) ;
assert . equal ( page . segments . length , 4 ) ;
const longueurs = page . segments . map ( ( { x1 , y1 , x2 , y2 } ) => Math . hypot ( x2 - x1 , y2 - y1 ) ) . sort ( ( a , b ) => a - b ) ;
[ 25.4 , 25.4 , 50.8 , 50.8 ] . forEach ( ( attendue , i ) => assert . ok ( proche ( longueurs [ i ] , attendue ) , ` côté ${ i } : ${ longueurs [ i ] } ` ) ) ;
} ) ;
test ( 'les couleurs posées se lisent en [r, g, b] de 0 à 255 ; les tirets se voient, un motif vide est un trait continu' , async ( ) => {
const continu = await document ( ( page ) => {
page . drawLine ( { start : { x : 0 , y : 0 } , end : { x : 10 , y : 0 } , color : grayscale ( 0.5 ) } ) ;
} ) ;
const tirete = await document ( ( page ) => {
page . drawLine ( { start : { x : 0 , y : 0 } , end : { x : 10 , y : 0 } , color : rgb ( 1 , 0 , 0 ) , dashArray : [ 3 , 2 ] } ) ;
} ) ;
const [ a ] = ( await lirePdf ( continu ) ) . pages ;
const [ b ] = ( await lirePdf ( tirete ) ) . pages ;
assert . equal ( a . tirets , false ) ;
assert . deepEqual ( a . couleurs , [ [ 128 , 128 , 128 ] ] ) ;
assert . equal ( b . tirets , true ) ;
assert . deepEqual ( b . couleurs , [ [ 255 , 0 , 0 ] ] ) ;
} ) ;
} ) ;
describe ( 'lirePdf : les boîtes, les polices, /Info et /ID' , ( ) => {
test ( 'la boîte de média en millimètres ; ni CropBox ni UserUnit par défaut' , async ( ) => {
const page = ( await lirePdf ( await document ( ( ) => { } , { largeur : 595.28 , hauteur : 841.89 } ) ) ) . pages [ 0 ] ;
assert . ok ( proche ( page . boite . largeur , 595.28 * MM _PAR _PT ) && proche ( page . boite . hauteur , 841.89 * MM _PAR _PT ) ) ;
assert . equal ( page . boite . x , 0 ) ;
assert . equal ( page . boite . y , 0 ) ;
assert . equal ( page . cropBox , null ) ;
assert . equal ( page . userUnit , null ) ;
} ) ;
test ( 'une CropBox et un UserUnit posés se lisent' , async ( ) => {
const octets = await document ( ( page ) => {
page . setCropBox ( 0 , 0 , 72 , 72 ) ;
page . node . set ( PDFName . of ( 'UserUnit' ) , PDFNumber . of ( 2 ) ) ;
} ) ;
const page = ( await lirePdf ( octets ) ) . pages [ 0 ] ;
assert . deepEqual ( page . cropBox , { x : 0 , y : 0 , largeur : 25.4 , hauteur : 25.4 } ) ;
assert . equal ( page . userUnit , 2 ) ;
} ) ;
test ( "chaque police de page se nomme, avec ou sans ToUnicode : une police embarquée en porte une, une police standard non" , async ( ) => {
const octets = await document ( async ( page , pdf ) => {
page . drawText ( 'a' , { x : 10 , y : 10 , size : 10 , font : await noto ( pdf ) } ) ;
page . drawText ( 'b' , { x : 10 , y : 30 , size : 10 , font : await pdf . embedFont ( StandardFonts . Helvetica ) } ) ;
} ) ;
const { polices } = await lirePdf ( octets ) ;
assert . equal ( polices . length , 2 ) ;
const parNom = ( motif ) => polices . find ( ( { nom } ) => motif . test ( nom ) ) ;
assert . equal ( parNom ( /NotoSans/ ) . toUnicode , true ) ;
assert . equal ( parNom ( /^Helvetica$/ ) . toUnicode , false ) ;
} ) ;
2026-10-10 18:50:40 -04:00
test ( "chaque police de page rend la longueur de son programme FontFile2, décodé : le .ttf entier hors sous-ensemble, moins en sous-ensemble ; null sans programme" , async ( ) => {
const { regulier , gras } = lirePolices ( ) ;
const octets = await document ( async ( page , pdf ) => {
page . drawText ( 'a' , { x : 10 , y : 10 , size : 10 , font : await pdf . embedFont ( regulier , { subset : true } ) } ) ;
page . drawText ( 'b' , { x : 10 , y : 30 , size : 10 , font : await pdf . embedFont ( gras , { subset : false } ) } ) ;
page . drawText ( 'c' , { x : 10 , y : 50 , size : 10 , font : await pdf . embedFont ( StandardFonts . Helvetica ) } ) ;
} ) ;
const { polices } = await lirePdf ( octets ) ;
const parNom = ( motif ) => polices . find ( ( { nom } ) => motif . test ( nom ) ) ;
assert . equal ( parNom ( /NotoSans-Bold/ ) . fontFile2 , gras . length ) ;
const sousEnsemble = parNom ( /NotoSans-Regular/ ) . fontFile2 ;
assert . ok ( sousEnsemble > 0 && sousEnsemble < regulier . length / 10 , ` sous-ensemble de ${ sousEnsemble } octets ` ) ;
assert . equal ( parNom ( /^Helvetica$/ ) . fontFile2 , null ) ;
} ) ;
[ADD] pdf: badge sheet production with control page and draft mark
The badge sheet must print at true scale and read back exactly what the
model says. Page 1 is the control sheet: badge size, paper, counts,
namesakes, missing glyphs and a 100 mm ruler; it continues on extra control
pages when a long reprint overflows. One page per sheet follows, with cut
lines, embedded Noto Sans subsets and a ToUnicode map, no metadata, so the
same model yields the same bytes. A draft carries BROUILLON in outline on
every page; the word comes from the caller, never from src/pdf.
Checked: red first, text read back through pdfjs; 3541 node and 101 long
tests green from the index alone.
--- FR ---
[ADD] pdf : production de la planche, page de contrôle, brouillon
La planche de badges doit s'imprimer à l'échelle et se relire exactement
comme le modèle le dit. La page 1 est la feuille de contrôle : taille du
badge, papier, comptes, homonymes, glyphes absents et règle de 100 mm ; elle
continue sur des pages de contrôle quand une longue réédition déborde. Suit
une page par planche, avec traits de coupe, sous-ensembles de Noto Sans et
table ToUnicode, sans métadonnées : le même modèle rend les mêmes octets.
Un brouillon porte BROUILLON au trait sur chaque page ; le mot vient de
l'appelant, jamais de src/pdf.
Vérifié : rouge d'abord, texte relu par pdfjs ; 3541 node et 101 longues
vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 15:26:53 -04:00
test ( "/Info absent : [] ; posé : ses clés ; /ID se voit dans la remorque" , async ( ) => {
assert . deepEqual ( await lirePdf ( await document ( ( ) => { } ) ) . then ( ( { info , id } ) => ( { info , id } ) ) , { info : [ ] , id : false } ) ;
const octets = await document (
( ) => { } ,
{
avant : async ( pdf ) => {
pdf . getInfoDict ( ) . set ( PDFName . of ( 'CreationDate' ) , PDFString . of ( "D:20261009190000-04'00'" ) ) ;
const identifiant = PDFHexString . of ( '00112233445566778899aabbccddeeff' ) ;
pdf . context . trailerInfo . ID = PDFArray . withContext ( pdf . context ) ;
pdf . context . trailerInfo . ID . push ( identifiant ) ;
pdf . context . trailerInfo . ID . push ( identifiant ) ;
} ,
} ,
) ;
const lu = await lirePdf ( octets ) ;
assert . deepEqual ( lu . info , [ 'CreationDate' ] ) ;
assert . equal ( lu . id , true ) ;
} ) ;
test ( "les octets reçus ne sont pas détachés par la lecture : une seconde lecture des mêmes octets rend la même chose" , async ( ) => {
const octets = await document ( async ( page , pdf ) => {
page . drawText ( 'Ondine Lacasse' , { x : 10 , y : 10 , size : 10 , font : await noto ( pdf ) } ) ;
} ) ;
const premiere = await lirePdf ( octets ) ;
assert . deepEqual ( await lirePdf ( octets ) , premiere ) ;
} ) ;
} ) ;