2. Tipos del documento
← El lenguaje · Índice · Siguiente: text:: →
Todo script recibe automáticamente la variable doc, que representa el PDF en
análisis. A partir de ella llegas a las páginas, las fuentes y las imágenes.
2.1 doc — el documento
Propiedades
| Propiedad | Tipo | Qué es |
|---|---|---|
doc.page_count |
número | Cantidad de páginas |
doc.title |
texto | Título de los metadatos (vacío si no existe) |
doc.author |
texto | Autor de los metadatos (vacío si no existe) |
doc.filename |
texto | Nombre del archivo analizado |
doc.pages |
lista | Todas las páginas |
doc.fonts |
lista | Todas las fuentes usadas |
doc.images |
lista | Todas las imágenes de todas las páginas |
check "Propiedades del documento" {
print("archivo:", doc.filename)
print("páginas:", doc.page_count)
print("título:", doc.title)
// Las colecciones son listas normales: aceptan todos los métodos de lista
require doc.pages.length == doc.page_count
require doc.fonts.length > 0
print("imágenes en todo el documento:", doc.images.length)
}
Métodos
doc.extract_text()
Todo el texto del documento, con las páginas separadas por saltos de línea.
check "Texto del documento" {
texto = doc.extract_text()
assert texto.trim() != "", "PDF sin texto extraíble (¿solo imágenes?)"
require texto.contains("Contrato")
print("caracteres en total:", texto.length)
}
2.2 page — la página
Las páginas vienen de doc.pages (dentro de bloques) o de la variable page
(dentro de una rule).
Propiedades
| Propiedad | Tipo | Qué es |
|---|---|---|
page.number |
número | Número de página, empezando en 1 |
page.index |
número | Índice de la página, empezando en 0 |
page.width |
número | Anchura en puntos |
page.height |
número | Altura en puntos |
page.images |
lista | Imágenes de esta página |
page.tac |
número | Cobertura de tinta máxima estimada (%) |
page.ink_coverage |
número | Cobertura media de tinta estimada (%) |
page.min_stroke_width |
número/null | Grosor de filete mínimo (pt); null si no hay filetes |
page.has_media_box |
booleano | Tiene MediaBox definida |
page.has_crop_box |
booleano | Tiene CropBox definida |
page.has_trim_box |
booleano | Tiene TrimBox definida |
page.has_bleed_box |
booleano | Tiene BleedBox definida |
page.has_art_box |
booleano | Tiene ArtBox definida |
check "Formato de las páginas" {
doc.pages.each { |page|
// number es lo que ve el usuario; index sirve para cálculos internos
assert page.width > 100mm,
"la página #{page.number} es demasiado estrecha: #{page.width}pt"
// Cajas: imprescindibles para impresión
assert page.has_trim_box,
"la página #{page.number} no tiene TrimBox (área de corte)"
assert page.has_bleed_box,
"la página #{page.number} no tiene BleedBox (sangrado)"
}
}
check "Tinta y filetes" {
doc.pages.each { |page|
assert page.tac <= 300,
"página #{page.number}: #{page.tac}% de tinta (límite 300%)"
// min_stroke_width puede ser null (página sin filetes);
// null es falso, así que esta comprobación es segura:
assert !page.min_stroke_width || page.min_stroke_width >= 0.25,
"la página #{page.number} tiene un filete demasiado fino"
}
}
Métodos
page.extract_text()
El texto solo de esta página.
check "Páginas en blanco" {
blancas = doc.pages.filter { |p| p.extract_text().trim() == "" }
assert blancas.length == 0,
"#{blancas.length} página(s) en blanco: #{blancas.map { |p| p.number }.join(", ")}"
}
2.3 font — la fuente
Las fuentes vienen de doc.fonts.
| Propiedad | Tipo | Qué es |
|---|---|---|
font.name |
texto | Nombre de la fuente |
font.is_embedded |
booleano | Está incrustada en el archivo |
check "Fuentes incrustadas" {
// Una fuente no incrustada la sustituye el lector: el texto cambia de aspecto
doc.fonts.each { |font|
assert font.is_embedded,
"la fuente '#{font.name}' no está incrustada en el PDF"
}
}
check "Informe de fuentes" {
print("fuentes usadas:", doc.fonts.map { |f| f.name }.join(", "))
faltan = doc.fonts.filter { |f| !f.is_embedded }
print("no incrustadas:", faltan.length)
}
2.4 image — la imagen
Las imágenes vienen de doc.images (todas) o de page.images (las de una
página).
| Propiedad | Tipo | Qué es |
|---|---|---|
image.width |
número | Anchura en píxeles |
image.height |
número | Altura en píxeles |
image.dpi |
número | Resolución efectiva (la menor entre dpi_x y dpi_y) |
image.dpi_x |
número | Resolución horizontal efectiva |
image.dpi_y |
número | Resolución vertical efectiva |
image.color_space |
texto | DeviceRGB, DeviceCMYK, Indexed... |
image.page_number |
número | Página donde aparece (1-based) |
image.bits_per_pixel |
número | Bits por píxel |
El DPI es el efectivo, calculado como píxeles ÷ tamaño impreso en la página, no el valor nominal grabado en los metadatos. Es el número que importa para la calidad de impresión: una imagen de 1000 px estirada hasta ocupar 20 cm tiene un DPI bajo, aunque los metadatos digan otra cosa.
profile "imagenes-para-offset" {
const DPI_MINIMO = 300
check "Resolución" {
doc.images.each { |img|
assert img.dpi >= DPI_MINIMO,
"imagen #{img.width}x#{img.height}px en la página #{img.page_number}: #{img.dpi} DPI (mínimo #{DPI_MINIMO})"
}
}
check "Espacio de color" {
// La impresión offset trabaja en CMYK; el RGB necesita conversión
doc.images.each { |img|
assert img.color_space != "DeviceRGB",
"imagen RGB en la página #{img.page_number} — convertir a CMYK"
}
}
check "Imágenes por página" {
doc.pages.each { |page|
// page.images trae solo las imágenes de esa página
print("la página", page.number, "tiene", page.images.length, "imagen(es)")
}
}
}
2.5 region — área de la página
Las regiones delimitan áreas rectangulares para validar partes concretas de la página: pie, cabecera, área del código de barras, franja de un medicamento.
Creación
// region(x, y, anchura, altura [, "nombre"])
// El origen (0,0) es la esquina INFERIOR izquierda, como en el PDF.
cabecera = region(0, 742, 595, 100, "cabecera")
pie = region(0, 0, 595, 60, "pie")
franja = region(20mm, 250mm, 60mm, 15mm, "franja roja")
Propiedades
| Propiedad | Qué es |
|---|---|
region.name |
Nombre dado al crearla (vacío si se omite) |
region.x / region.y |
Esquina inferior izquierda |
region.width / region.height |
Dimensiones |
region.right / region.top |
Bordes derecho y superior (calculados) |
region.area |
Área en puntos cuadrados |
Métodos
| Método | Qué hace |
|---|---|
region.contains_point(x, y) |
¿El punto está dentro? |
region.intersects(otra) |
¿Las dos regiones se solapan? |
region.expand(pt) |
Nueva región mayor por todos los lados |
region.inset(pt) |
Nueva región menor por todos los lados |
region.export_coordinates() |
[x0, y0, x1, y1] |
check "Trabajando con regiones" {
pie = region(0, 0, 595, 60, "pie")
require pie.name == "pie"
require pie.top == 60.0
require pie.right == 595.0
require pie.area == 35700.0
// ¿Un punto en el pie?
require pie.contains_point(300, 30)
require !pie.contains_point(300, 500)
// Solapamiento: útil para detectar elementos que invaden áreas
cabecera = region(0, 780, 595, 62)
require !pie.intersects(cabecera)
// expand/inset devuelven regiones NUEVAS (la original no cambia)
holgura = pie.expand(5mm) // 5mm mayor por cada lado
seguro = pie.inset(3mm) // 3mm menor por cada lado
require holgura.area > pie.area
require seguro.area < pie.area
}
Usar regiones en las validaciones
profile "prospecto-farmaceutico" {
check "Franja roja" {
// La franja debe estar arriba, con el texto legal
franja = region(0, 700, 595, 142, "franja")
contenido = text::extract_from_region(1, franja)
assert contenido.contains("VENDA SOB PRESCRIÇÃO"),
"la franja no lleva el texto obligatorio"
}
check "Tinta en la zona del plegado" {
// El exceso de tinta en el plegado da problemas de acabado
plegado = region(290, 0, 15, 842, "plegado central")
medida = prepress::calculate_tac_by_region(1, plegado)
assert medida.first() < 240,
"demasiada tinta en el plegado: #{medida.first()}%"
}
check "Código de barras en su sitio" {
area_codigo = region(400, 20, 180, 80, "área del código")
assert codes::validate_barcode_position(area_codigo),
"código de barras fuera del área reservada"
}
}