コンテンツにスキップ

2. ドキュメント型

すべてのスクリプトは doc 変数を自動的に受け取ります。これが解析対象の PDF です。ここからページ、フォント、画像にたどり着きます。


プロパティ 内容
doc.page_count 数値 ページ数
doc.title 文字列 メタデータのタイトル(無い場合は空)
doc.author 文字列 メタデータの著者(無い場合は空)
doc.filename 文字列 解析対象のファイル名
doc.pages リスト すべてのページ
doc.fonts リスト 使用されているすべてのフォント
doc.images リスト 全ページのすべての画像
check "Document properties" {
print("file:", doc.filename)
print("pages:", doc.page_count)
print("title:", doc.title)
// コレクションは通常のリストです — すべてのリストメソッドが使えます
require doc.pages.length == doc.page_count
require doc.fonts.length > 0
print("images in the whole document:", doc.images.length)
}

ドキュメント全体のテキスト。ページは改行で区切られます。

check "Document text" {
text = doc.extract_text()
assert text.trim() != "", "PDF has no extractable text (images only?)"
require text.contains("Agreement")
print("total characters:", text.length)
}

ページは doc.pages(ブロック内)または page 変数(rule 内)から得ます。

プロパティ 内容
page.number 数値 ページ番号(1 起点)
page.index 数値 ページ索引(0 起点)
page.width 数値 幅(ポイント)
page.height 数値 高さ(ポイント)
page.images リスト このページの画像
page.tac 数値 推定インク総量の最大値(%)
page.ink_coverage 数値 推定インク総量の平均(%)
page.min_stroke_width 数値/null 最も細い線幅(pt)。線が無ければ null
page.has_media_box 真偽値 MediaBox が定義されている
page.has_crop_box 真偽値 CropBox が定義されている
page.has_trim_box 真偽値 TrimBox が定義されている
page.has_bleed_box 真偽値 BleedBox が定義されている
page.has_art_box 真偽値 ArtBox が定義されている
check "Page format" {
doc.pages.each { |page|
// number は人が見る番号、index は内部計算用
assert page.width > 100mm,
"page #{page.number} is too narrow: #{page.width}pt"
// ボックス:印刷には必須です
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)"
}
}
check "Ink and strokes" {
doc.pages.each { |page|
assert page.tac <= 300,
"page #{page.number}: #{page.tac}% ink (limit 300%)"
// min_stroke_width は null になり得ます(線の無いページ)。
// null は偽なので、この書き方は安全です:
assert !page.min_stroke_width || page.min_stroke_width >= 0.25,
"page #{page.number} has a hairline stroke"
}
}

このページだけのテキスト。

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

フォントは doc.fonts から得ます。

プロパティ 内容
font.name 文字列 フォント名
font.is_embedded 真偽値 ファイルに埋め込まれているか
check "Embedded fonts" {
// 埋め込まれていないフォントはリーダーが代替します — 見た目が変わります
doc.fonts.each { |font|
assert font.is_embedded,
"font '#{font.name}' is not embedded in the PDF"
}
}
check "Font report" {
print("fonts in use:", doc.fonts.map { |f| f.name }.join(", "))
missing = doc.fonts.filter { |f| !f.is_embedded }
print("not embedded:", missing.length)
}

画像は doc.images(全体)または page.images(1ページ分)から得ます。

プロパティ 内容
image.width 数値 幅(ピクセル
image.height 数値 高さ(ピクセル
image.dpi 数値 実効解像度(dpi_x と dpi_y の小さい方)
image.dpi_x 数値 水平方向の実効解像度
image.dpi_y 数値 垂直方向の実効解像度
image.color_space 文字列 DeviceRGBDeviceCMYKIndexed
image.page_number 数値 配置されているページ(1 起点)
image.bits_per_pixel 数値 ビット深度

DPI は実効値です。ピクセル数 ÷ ページ上の印刷サイズで計算され、 メタデータの公称値ではありません。印刷品質に効くのはこちらです。1000 px の画像を 20 cm に引き伸ばせば、メタデータが何と言おうと DPI は低くなります。

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" {
// オフセット印刷は CMYK です。RGB は変換が必要です
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|
// page.images はそのページの画像だけを持ちます
print("page", page.number, "has", page.images.length, "image(s)")
}
}
}

領域は矩形でページの一部を指定し、フッター、ヘッダー、バーコード欄、医薬品の 表示帯などを個別に検証できます。

// region(x, y, 幅, 高さ [, "名前"])
// 原点 (0,0) は PDF と同じく左下です。
header = region(0, 742, 595, 100, "header")
footer = region(0, 0, 595, 60, "footer")
band = region(20mm, 250mm, 60mm, 15mm, "red band")
プロパティ 内容
region.name 作成時に付けた名前(省略時は空)
region.x / region.y 左下の座標
region.width / region.height 寸法
region.right / region.top 右辺と上辺(計算値)
region.area 面積(平方ポイント)
メソッド 動作
region.contains_point(x, y) その点は内側か
region.intersects(other) 2つの領域が重なるか
region.expand(pt) 全方向に広げた新しい領域
region.inset(pt) 全方向に縮めた新しい領域
region.export_coordinates() [x0, y0, x1, y1]
check "Working with regions" {
footer = region(0, 0, 595, 60, "footer")
require footer.name == "footer"
require footer.top == 60.0
require footer.right == 595.0
require footer.area == 35700.0
// 点がフッター内にあるか
require footer.contains_point(300, 30)
require !footer.contains_point(300, 500)
// 重なり:要素が禁止領域に侵入していないかの確認に使えます
header = region(0, 780, 595, 62)
require !footer.intersects(header)
// expand/inset は「新しい」領域を返します(元は変わりません)
slack = footer.expand(5mm) // 各辺 5mm 広い
safe = footer.inset(3mm) // 各辺 3mm 狭い
require slack.area > footer.area
require safe.area < footer.area
}
profile "medicine-label" {
check "Prescription band" {
// 表示帯は上部にあり、法定文言を含む必要があります
band = region(0, 700, 595, 142, "band")
content = text::extract_from_region(1, band)
assert content.contains("PRESCRIPTION ONLY"),
"band is missing the mandatory text"
}
check "Ink in the fold area" {
// 折り目のインクが多すぎると加工で割れます
fold = region(290, 0, 15, 842, "center fold")
measured = prepress::calculate_tac_by_region(1, fold)
assert measured.first() < 240,
"too much ink on the fold: #{measured.first()}%"
}
check "Barcode in the right place" {
code_area = region(400, 20, 180, 80, "barcode area")
assert codes::validate_barcode_position(code_area),
"barcode outside the reserved area"
}
}

← 言語 · 目次 · 次:text::

DigitalOceanこのサイトのホスティングを提供してくださる DigitalOcean に感謝します。