印刷用のPDFで問題が途中のページで切れる|HTMLから作るときの直し方と、切れていないか自分で数える手順

AI・テクノロジー

印刷して使う問題集を、HTMLからPDFにして渡しました。すると受け取った側から「問題が中途半端に次のページに載ってしまう」と言われました。しかも、言われたのは2回目でした。1回目に「直しました」と答えたのに、直っていなかったのです。

この記事は、その原因を突き止めて直すまでに実際にやったことの記録です。PDFにするのはブラウザ(Google Chrome)で、直したのはCSSと、HTMLに足した箱ひとつだけ。有料のソフトは使っていません。最後に「もう割れていないか」を自分で数える方法も書きますが、そこだけは Python と PyMuPDF を使います(どちらも無料です)。

⚠️ここで確かめたのは Chrome での結果だけです。ほかのブラウザや印刷会社の処理系で同じになるかは確かめていません。ただ、離れては困る物がある印刷物(学校のプリント、社内の手順書、レジュメ、確認テストなど)で同じ形の困りごとは起きうるので、考え方は使えると思います。

何が起きていたのか

作っていたのは、4択の問題が50問ならんだ問題集です。1問はこういう形をしています。

  • 問番号(問1、問2…)
  • テーマ(【テーマ:○○】)
  • 問題文(1〜2行)
  • 選択肢が4つ

紙に印刷したとき、この4つはひとかたまりで同じページに載っていてほしいものです。ところが出来上がったPDFでは、こうなっていました。

  • あるページの最後が「4. 従業者名簿は、最終の記載をした日から10年間保」で終わっていた(文の途中で切れて、続きは次のページ)
  • 別のページは、選択肢の1〜3番までで終わり、4番だけが次のページにいた
  • さらに別のページは、問番号がひとつも無いページになっていた。選択肢だけが3つ並んでいる(2番から4番まで)
  • あるページの最後は「【テーマ:委任・請負】」という見出しだけで終わり、問題文は次のページだった

印刷して解こうとすると、1問を読むのに紙を1回めくることになります。解いている途中で選択肢を見比べられないので、問題集としては使えません。

1回目に渡したとき、わたしは1ページ目しか見ていなかった

最初に「印刷できるようにしてほしい」と頼まれたとき、印刷用のCSSを書きました。実際に書いたのは、こういう内容です。

@media print {
  @page { size: A4; margin: 14mm 12mm; }
  .question {
    page-break-inside: avoid;   /* 中で割るな */
    page-break-after:  avoid;   /* 後ろで割るな ← これが後で効いてくる */
  }
}

2行目の page-break-inside: avoid は「この箱の途中で改ページしないで」という意味で、考え方は合っています。問題は3行目です。ただ、このときは2つを同時に書いていて、区別していませんでした。あとから1行だけ足したのではなく、最初から両方書いていた、という形です。

そして、出来上がったPDFを1ページ目だけ開いて「きれいに出ている」と判断し、渡しました。24ページのうち1ページしか見ていないのに、全部を見たつもりになっていたのです。あとで全ページを数えたら、2ページ目の終わりですでに文が途中で切れていました。

ここが最初の反省点です。先頭だけ見て全部を判断していた。印刷物は先頭ほどきれいに出ます。文章が増えて、ページの継ぎ目が来るところから崩れ始めるからです。

目で見るのをやめて、機械に数えさせた

2回目に言われたとき、方針を変えました。全ページを目で確かめるのは、24ページでもつらいですし、見落とします。そこでPDFからページごとの文字を取り出して、割れているかどうかを数えることにしました。

PythonでPDFの文字を取り出します。ここでは PyMuPDF(fitz)を使いました。

import fitz

d = fitz.open("問題集.pdf")
for i, page in enumerate(d, 1):
    t = page.get_text().strip()
    if not t:                      # 文字の無いページで落ちないように
        print(i, "(文字なし)")
        continue
    gyou = [x for x in t.splitlines() if x.strip()]
    print(f"p{i:>2} 頭「{gyou[0][:26]}」 末「{gyou[-1][:26]}」")

これを流すと、各ページの最初の行最後の行が一覧で出ます。実際の出力がこうでした。

p 2 頭「【テーマ:専任の宅建士の設置】」 末「4. 従業者名簿は、最終の記載をした日から10年間保」
p 4 頭「問9」                        末「3. 業務処理状況の報告は、1週間に1回以上行わなけ」
p 5 頭「4. 指定流通機構に登録したときは、その登録を証する」
p 8 頭「2. 宅建業者が、取引の相手方に対し、利益を生ずるこ」
p11 末「【テーマ:委任・請負】」

これで言い訳のしようがなくなりました。ページの頭が「4.」で始まっているということは、1〜3番が前のページにあるということです。8ページ目は問番号がありません。

原因は、自分が書いたCSSの1行でした

問題集は2種類作っていました。

  • 解答つき:問題のすぐ下に答えと解説がある(覚えるとき用)
  • 問題編:問題だけが50問ならび、答えは最後にまとめてある(印刷して解く用)

そして、両方に同じCSSを使っていました。その中にこの1行がありました。

.question { page-break-after: avoid; }

page-break-after: avoid は「この箱の後ろで改ページしないで」という意味です。解答つきの版では、これは正しい指定です。問題の直後に答えが来るので、「問題の後ろで改ページするな=答えと離すな」という意味になります。

ところが問題編には答えがありません。問題の後ろに来るのは、次の問題です。つまりこの1行は、問題編では

どの問題の後ろでも改ページしてはいけない

という指示になります。50問ぜんぶの後ろで改ページ禁止なら、そもそも改ページする場所がありません。紙は有限なので、これは守れない指示です。

このとき、ブラウザはエラーを出して止まったりしません。黙って、どこかで割ります。

⚠️ここから先は推測です。「守れない指示を受け取ったので、page-break-inside: avoid(中で割るな)まで一緒に諦めた」という説明が、いちばん筋が通ります。ただしブラウザの中で何が起きているかは測っていません。わたしが測ったのは次の2つだけです。

  • page-break-after: avoidあるとき … 割れている所 10件
  • page-break-after: avoid外したとき … 0件

ここが2つ目の学びです。印刷のCSSは、守れない指示を書いてもエラーが出ません。だから書いた本人は気づけず、出来上がった物を見るまで分かりません。

直した手順(3つ)

① 問題と答えを「1つの箱」で包む

解答つきの版では、問題と答えが離れては困ります。そこで、両方をひとつの箱で包み、箱ごと割らせない形にしました。

<div class="kumi">
  <div class="question">…問題…</div>
  <div class="answer">…答えと解説…</div>
</div>
@media print {
  .kumi     { page-break-inside: avoid; break-inside: avoid; }
  .question { page-break-inside: avoid; break-inside: avoid; }
  .answer   { page-break-inside: avoid; break-inside: avoid; }
}

ポイントは、「後ろで割るな」ではなく「中で割るな」だけを言うことです。離したくない物どうしは、外側の箱でまとめてしまえば、後ろを縛る必要がありません。

② 問題編からは「後ろの縛り」を外す

問題だけが並ぶ版では、問題の後ろは改ページしてよい場所です。むしろ、そこでしか改ページできません。なので外します。

@media print {
  .question {
    page-break-after: auto;   /* ← 後ろは自由にさせる */
    break-after: auto;
    page-break-inside: avoid; /* ← 中だけ割らせない */
    break-inside: avoid;
  }
}

同じHTMLの作りでも、中身の並び方が違えば、正しい印刷指定も違います。ここを同じCSSで済ませようとしたのが、そもそもの間違いでした。

③ 段落の1行だけが取り残されるのを防ぐ

問題の中で割れなくなっても、長い説明文の最後の1行だけが次のページに送られると読みにくくなります。これは別の指定で防げます。

@media print {
  p, li { orphans: 3; widows: 3; }
}
  • orphans:ページのに残してよい最少の行数
  • widows:ページのに送ってよい最少の行数

3にしておくと「2行だけ置き去り」が起きにくくなります。⚠️日本語でも効くと言われていますが、今回 数字を変えて比べてはいません

おまけ:章ごとに改ページする

分野ごとに分けて綴じたい場合は、見出しの前で改ページします。ただし、1つ目の見出しだけは例外にしないと、1ページ目が白紙になります。

@media print {
  h2 { page-break-before: always; page-break-after: avoid; }
  h2:first-of-type { page-break-before: auto; }
}

見出しについている page-break-after: avoid は、こちらは正しい使い方です。見出しの後ろには必ず本文が来るので、「見出しだけがページの最後に取り残される」のを防げます。

直ったかどうかを、自分で数える

直したあと、また「直したつもり」で渡さないために、数える道具を作りました。やっていることは単純で、ページごとの文字を見て、次の3つを探します。

  1. ページのが「1.」「2.」など選択肢で始まっている → 前のページから割れてきた
  2. ページの中に問番号が1つも無いのに選択肢がある → 問題の頭が別のページにある
  3. ページの終わりが見出しだけ → 見出しが取り残された
import sys, re, fitz

def miru(path):
    d = fitz.open(path)
    warui = []
    for i, page in enumerate(d, 1):
        t = page.get_text().strip()
        if not t:
            continue
        gyou = [x for x in t.splitlines() if x.strip()]
        atama, owari = gyou[0].strip(), gyou[-1].strip()
        mon = re.findall(r"問\s*\d+", t)
        sel = re.findall(r"^\s*[1-4]\.\s", t, re.M)
        if re.match(r"^[1-4]\.\s", atama):
            warui.append(f"p{i}: 頭が選択肢「{atama[:28]}」")
        if sel and not mon:
            warui.append(f"p{i}: 問番号が無いのに選択肢が{len(sel)}個")
        if owari.startswith("【テーマ"):
            warui.append(f"p{i}: 終わりが見出しだけ")
    n = len(d)
    d.close()
    return n, warui

for p in sys.argv[1:]:
    n, warui = miru(p)
    if warui:
        print(f"🔴 {p}({n}ページ)… {len(warui)}件")
        for w in warui[:10]:
            print("   ", w)
    else:
        print(f"⭕ {p}({n}ページ)… 割れている所はありません")

この道具で、直す前と直した後を実際に測りました。

ページ数 割れている所
壊れた状態(作り直した写し) 24ページ 10件
直した後(実物) 26ページ 0件

⚠️ひとつ断っておきます。「10件」は、最初に渡したPDFそのものを測った数ではありません。そのPDFは直したときに上書きしてしまい、もう残っていないからです。代わりに、直したHTMLにわざと元の指定を書き戻してPDFを作り直し、それを測りました。最初のPDFの中身は、ページごとの文字の記録が残っていたので、同じ形の割れが起きていたことは確かめられます。

ページが2つ増えているのは、割らずに次のページへ送ったぶんです。紙が2枚増えるのと、1問を読むのに紙をめくるのとでは、後者のほうが困ります。

大事なのは「わざと壊して、鳴るか見る」

道具を作ったら、それが本当に見つけられるのかを確かめないと意味がありません。作ったばかりの道具が「0件です」と言っても、それは見つけられないだけかもしれないからです。

そこで、直したHTMLをコピーして、わざと元の壊れ方に戻しpage-break-after: avoid を書き戻す)、PDFを作り直して同じ道具にかけました。結果は10件見つかりました。

🔴 warui.pdf(24ページ)… 割れている所 10件
    p5: 頭が選択肢「4. 指定流通機構に登録したときは…」
    p8: 問番号が無いのに選択肢が4個
    …

これで、この道具は「壊れていれば鳴る」「直っていれば黙る」の両方を確かめられました。片方だけでは足りません。いつも0件を返すだけの道具でも、直っているときは正しく見えてしまうからです。

HTMLからPDFを作るときの、実際のコマンド

PDFにするのは Chrome の機能を使いました。追加のソフトは入れていません。

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --headless --disable-gpu --no-sandbox \
  --user-data-dir=/tmp/chrome-pdf \
  --print-to-pdf="/path/to/出力.pdf" \
  --no-pdf-header-footer \
  "file:///path/to/入力.html"

--no-pdf-header-footer を付けると、ページの上下に日付とURLが入らなくなります。配る物では、たいてい邪魔になります。

つまずいた点も書いておきます。この方法だと、PDFが出来上がったあともChromeが終わらないことがありました。1回目は待ちきれずに打ち切っています(⚠️そのときの記録は残しそびれたので、原因までは確かめていません)。PDF自体は正しく出来ていたので、2本目からは「ファイルが出来たら止める」形にしました。

chrome … --print-to-pdf="$OUT" "file://$IN" &
P=$!
for i in $(seq 1 60); do [ -s "$OUT" ] && break; sleep 1; done
sleep 2
kill $P

行儀は良くありませんが、確実です。大事なのは「起動した」を「出来た」の証拠にしないことで、ファイルが実際に出来ているかを見てから次へ進みます。

印刷のときの設定(渡す相手に伝えること)

PDFを渡すときは、印刷側の設定も一緒に伝えたほうが親切です。せっかく直しても、設定ひとつで崩れることがあります。

  • 用紙:A4・たて(CSSで @page { size: A4; } を指定していても、印刷側で変えられます)
  • 余白:「デフォルト」のまま(「なし」にすると端が切れる機種があります)
  • 背景のグラフィック:オンにすると、色分けや枠が出ます。オフでも読めるように作っておくのが安全です
  • ヘッダーとフッター:オフ(日付とURLが入りません)

背景の色を出すかどうかは、CSS側でも指定できます。

@media print {
  .question, .answer {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

ただし、これは「出せ」という指示であって、印刷する人が設定でオフにすれば消えます。色が消えても意味が通じる作りにしておくのが前提です。色だけで正解を示す、といった作り方は避けます。

字の大きさは、まだ「決めていない」

印刷用のCSSでは、本文の大きさを 11.5pt にしました。

@media print {
  body { font-size: 11.5pt; line-height: 1.6; }
}

ただし正直に書くと、この数字は比べて決めたものではありません。画面用の 14px をそのまま紙に持っていくと小さいので、少し上げた、という程度の決め方です。10.5pt と 12pt を刷り比べたわけではありません。

大きさを決めるのに、ひとつ確かなことがあります。読む人の手元で、紙に出して見てもらうまでは決まらないということです。画面で見た感じと、紙にしたときの感じは違います。さらに、読む人の目の具合によっても変わります。わたしはこの問題集を渡す前に「読みにくければ大きい版を作り直します」と伝えました。そう言えるようにしておくのが、いまのところ一番まともなやり方だと思っています。

もうひとつ、印刷物で気をつけている点があります。薄い色の字を既定にしないことです。画面では opacity: 0.75 のような薄い字が上品に見えますが、紙にすると読みにくくなると言われています。⚠️これは刷り比べて確かめたわけではありません。ただ、薄くして得をする場面が思いつかないので、補足の行こそ濃さを落とさないようにしています。

同じことが起きる場面

今回は問題集でしたが、原因は問題集に固有のものではありません。「離れては困る物のかたまり」がある印刷物なら、同じ形の困りごとが起きえます。⚠️下に挙げるのは、実際に測ったものではなく同じ理屈が当てはまりそうな場面です。

  • 学校のプリント(設問と解答欄が別ページに分かれる)
  • 料理のレシピ(材料と手順が分かれる)
  • 社内の手順書(手順の番号と本文が分かれる)
  • 申込書(項目名と記入欄が分かれる)
  • 表(見出し行だけが前のページに残る)

表については、見出し行を繰り返す指定もあります。⚠️これは今回 試していません(一般に使われている書き方です)。

@media print {
  table { page-break-inside: auto; }
  thead { display: table-header-group; }  /* 各ページに見出し行を出す */
  tr    { page-break-inside: avoid; }
}

分からなかったこと・確かめていないこと

正直に書いておきます。

  • ここで確かめたのは Chrome での結果だけです。Safari や Firefox、印刷会社の処理系で同じになるかは確かめていません
  • ページ数(24→26)はこの問題集での数字です。文章量が違えば変わります
  • 道具が見つけるのは、上に書いた3つの形だけです。たとえば「図の下のキャプションだけが次のページへ行く」は、この道具では見つかりません。探す形を足す必要があります
  • orphans / widows を3にした根拠は、一般的な目安であって、この問題集で比べて決めたものではありません

まとめ:手順だけ並べると

  1. 離したくない物どうしを、ひとつの箱で包む
  2. その箱に page-break-inside: avoid を付ける(「後ろで割るな」は原則つけない
  3. 後ろを縛るのは、直後に必ず何かが続く物だけ(見出しなど)
  4. orphans / widows を3にする
  5. PDFを作ったら、全ページの文字を取り出して数える
  6. その数える道具を、わざと壊して鳴るか確かめる
  7. 渡す相手に、印刷設定(余白・背景・ヘッダー)を伝える
  8. 字の大きさは、読む人に紙で見てもらって決める

1回目に直したつもりで直っていなかったのは、5番が抜けていたからでした。見た目で判断できるのは先頭の1ページだけです。残りは数えるしかありません。

✍️ この記事を書いた人

チケットナビ編集部

先払い買取・金券売買の最新情報を初心者にもわかりやすくお届けします。業者の比較、買取率、トラブル対策など、安全に現金化するための情報を徹底調査して発信しています。

コメント

タイトルとURLをコピーしました