2. Types du document

← Le langage · Sommaire · Suivant : text::

Tout script reçoit automatiquement la variable doc, qui représente le PDF analysé. À partir d'elle, on atteint les pages, les polices et les images.


2.1 doc — le document

Propriété Type Contenu
doc.page_count nombre Nombre de pages
doc.title texte Titre des métadonnées (vide s'il manque)
doc.author texte Auteur des métadonnées (vide s'il manque)
doc.filename texte Nom du fichier analysé
doc.pages liste Toutes les pages
doc.fonts liste Toutes les polices employées
doc.images liste Toutes les images, toutes pages confondues

Méthode : doc.extract_text() — le texte de tout le document, pages séparées par des sauts de ligne.

check "Document properties" {
  print("file:", doc.filename)
  print("pages:", doc.page_count)

  // Ces collections sont des listes ordinaires — toutes les méthodes marchent
  require doc.pages.length == doc.page_count
  require doc.fonts.length > 0

  texte = doc.extract_text()
  assert texte.trim() != "", "PDF has no extractable text (images only?)"
  print("total characters:", texte.length)
}

2.2 page — la page

Les pages viennent de doc.pages (dans un bloc) ou de la variable page (dans une rule).

Propriété Type Contenu
page.number nombre Numéro de page, à partir de 1
page.index nombre Indice, à partir de 0
page.width / page.height nombre Largeur / hauteur en points
page.images liste Images de cette page
page.tac nombre Encrage total maximal estimé (%)
page.ink_coverage nombre Encrage moyen estimé (%)
page.min_stroke_width nombre/null Filet le plus fin (pt) ; null s'il n'y a aucun trait
page.has_media_box etc. booléen has_crop_box, has_trim_box, has_bleed_box, has_art_box

Méthode : page.extract_text() — le texte de cette page seulement.

check "Page format" {
  doc.pages.each { |page|
    // number est le numéro que lisent les gens, index sert aux calculs internes
    assert page.width > 100mm,
      "page #{page.number} is too narrow: #{page.width}pt"

    // Les boîtes : indispensables pour l'impression
    assert page.has_trim_box, "page #{page.number} has no TrimBox (trim area)"
    assert page.has_bleed_box, "page #{page.number} has no BleedBox (bleed area)"

    assert page.tac <= 300,
      "page #{page.number}: #{page.tac}% ink (limit 300%)"

    // min_stroke_width peut être null (aucun trait sur la page).
    // null est faux, donc ceci est sûr :
    assert !page.min_stroke_width || page.min_stroke_width >= 0.25,
      "page #{page.number} has a hairline stroke"
  }
}

check "Blank pages" {
  vides = doc.pages.filter { |p| p.extract_text().trim() == "" }
  assert vides.length == 0,
    "#{vides.length} blank page(s): #{vides.map { |p| p.number }.join(", ")}"
}

2.3 font — la police

Vient de doc.fonts. Propriétés : font.name (nom) et font.is_embedded (incorporée ou non).

check "Embedded fonts" {
  // Une police non incorporée est remplacée par le lecteur — le texte change
  doc.fonts.each { |font|
    assert font.is_embedded,
      "font '#{font.name}' is not embedded in the PDF"
  }
  print("fonts in use:", doc.fonts.map { |f| f.name }.join(", "))
}

2.4 image — l'image

Vient de doc.images (toutes) ou de page.images (celles d'une page).

Propriété Contenu
image.width / image.height Largeur / hauteur en pixels
image.dpi Résolution effective (la plus petite de dpi_x et dpi_y)
image.dpi_x / image.dpi_y Résolution effective horizontale / verticale
image.color_space DeviceRGB, DeviceCMYK, Indexed
image.page_number Page où elle se trouve (à partir de 1)
image.bits_per_pixel Profondeur de bits

Le DPI est effectif, calculé comme « pixels ÷ taille imprimée sur la page », et non la valeur nominale des métadonnées. C'est ce chiffre-là qui décide de la qualité d'impression : une image de 1000 px étirée sur 20 cm a un DPI bas, quoi que disent ses métadonnées.

profile "images-for-offset" {
  const MIN_DPI = 300

  check "Resolution" {
    doc.images.each { |img|
      assert img.dpi >= MIN_DPI,
        "image #{img.width}x#{img.height}px on page #{img.page_number}: #{img.dpi} DPI (minimum #{MIN_DPI})"
    }
  }

  check "Color space" {
    // L'offset travaille en CMJN ; le RVB doit être converti
    doc.images.each { |img|
      assert img.color_space != "DeviceRGB",
        "RGB image on page #{img.page_number} — convert to CMYK"
    }
  }

  check "Images per page" {
    doc.pages.each { |page|
      print("page", page.number, "has", page.images.length, "image(s)")
    }
  }
}

2.5 region — une zone de la page

Une région délimite une partie de la page par un rectangle. Elle sert à valider un pied de page, un en-tête, l'emplacement d'un code-barres, un bandeau réglementaire.

Création : region(x, y, largeur, hauteur [, "nom"]), l'origine (0,0) étant en bas à gauche, comme dans le PDF.

Propriété Contenu Méthode Rôle
region.name Nom donné à la création contains_point(x, y) Le point est-il dedans ?
region.x / region.y Coin inférieur gauche intersects(autre) Les deux régions se recouvrent-elles ?
region.width / region.height Dimensions expand(pt) Nouvelle région élargie de chaque côté
region.right / region.top Bord droit / haut (calculés) inset(pt) Nouvelle région rétrécie de chaque côté
region.area Surface (points carrés) export_coordinates() [x0, y0, x1, y1]
check "Working with regions" {
  pied = region(0, 0, 595, 60, "footer")

  require pied.name == "footer"
  require pied.top == 60.0
  require pied.right == 595.0
  require pied.area == 35700.0
  require pied.contains_point(300, 30)
  require !pied.contains_point(300, 500)

  // Détection de recouvrement : utile pour repérer un élément
  // qui empiète sur une zone réservée
  entete = region(0, 780, 595, 62)
  require !pied.intersects(entete)

  // expand/inset renvoient une NOUVELLE région (l'originale ne change pas)
  require pied.expand(5mm).area > pied.area
  require pied.inset(3mm).area < pied.area
}

profile "medicine-label" {
  check "Prescription band" {
    // Le bandeau doit être en haut et porter la mention légale
    bandeau = region(0, 700, 595, 142, "band")
    assert text::extract_from_region(1, bandeau).contains("PRESCRIPTION ONLY"),
      "band is missing the mandatory text"
  }

  check "Ink in the fold area" {
    // Trop d'encre sur le pli craque au façonnage
    pli = region(290, 0, 15, 842, "center fold")
    mesure = prepress::calculate_tac_by_region(1, pli)
    assert mesure.first() < 240,
      "too much ink on the fold: #{mesure.first()}%"
  }

  check "Barcode in the right place" {
    zone_code = region(400, 20, 180, 80, "barcode area")
    assert codes::validate_barcode_position(zone_code),
      "barcode outside the reserved area"
  }
}

← Le langage · Sommaire · Suivant : text::