0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PDFの並べ替えは「同じページ数」だけでは確認できない:順列の検査と保存後の検査を分ける

0
Posted at

AI利用と関係の開示:本稿とJavaScriptの例は、Yan Wang / LensUp向けにCodexが生成しました。Codexが実行したローカル検査の範囲を明記します。人間のアカウント所有者が自ら実験・独立検証したという意味ではありません。PDFを書き出すコードや製品修正ではなく、並べ替え計画を検査する独立した例です。

ページを移動した後もサムネイルが四つある。それだけでは、元の四ページが一度ずつ残っていると判断できません。同じページを二回含め、別のページを落としても、配列の長さは四のままだからです。

ここでは、一つのPDFの全ページを残して順序だけ変える操作を対象にします。削除、複製、別ファイルの追加を含める場合は、この契約をそのまま使えません。

最初に検査するのはページ内容ではなく計画

例のインデックスは、元ファイルの物理ページをゼロから数えたものです。四ページなら有効な値は0から3までです。印刷されたページ番号やPDFのページラベルは入力に使いません。

function validateReorder(order, pageCount) {
  if (!Number.isSafeInteger(pageCount) || pageCount < 1) {
    throw new RangeError("pageCount must be a positive safe integer");
  }
  if (!Array.isArray(order) || order.length !== pageCount) {
    throw new TypeError("order must contain one slot per source page");
  }

  const seen = new Set();
  for (let i = 0; i < order.length; i += 1) {
    const page = order[i];
    if (!Number.isInteger(page) ||
        page < 0 || page >= pageCount || seen.has(page)) {
      throw new RangeError("order must be a permutation of source indexes");
    }
    seen.add(page);
  }
  return order.slice();
}

配列の長さ、各値の型と範囲、重複をまとめて検査します。通常の数値配列を想定した例であり、悪意のあるProxyやgetterを防ぐ境界ではありません。

添字で全スロットを読むのも意図的です。疎配列の穴は、長さには含まれても、配列メソッドによっては処理されません。このループでは穴を読むとundefinedになり、整数検査で失敗します。

通してはいけない反例を先に置く

次のブロックを同じESモジュールで関数の後に置けます。

import assert from "node:assert/strict";

assert.deepEqual(validateReorder([3, 0, 1, 2], 4), [3, 0, 1, 2]);

for (const order of [
  [0, 1, 1, 3],
  [0, 1, 2, 4],
  [0, 1, 2, "3"],
  [0, 1, , 3],
]) {
  assert.throws(() => validateReorder(order, 4));
}

const sourcePlan = [1, 0];
const acceptedPlan = validateReorder(sourcePlan, 2);
sourcePlan.reverse();
assert.deepEqual(acceptedPlan, [1, 0]);

重複、範囲外、文字列、穴は、どれも「長さは四」という検査を通せます。ここで失敗させたいのは、そうした計画です。返す配列をコピーにしているため、元の配列を後から変更しても、受け取った計画は変わりません。

本稿のローカル検査は、これらに加え、不正なページ数、非配列、負数、小数、NaN、Infinity、短い配列も対象にしています。実行結果は原稿と一緒に保存しています。PDFの生成、ブラウザー操作、暗号化文書、フォーム互換性を試したという意味ではありません。

表示した二つのJavaScriptブロックは、macOSのNode.js v22.23.4で一つのESモジュールとして実行しました。再現するには、順にreorder-check.mjsへ保存し、node reorder-check.mjsを実行します。本文とは別の保存済みハーネスで、上記の追加反例も検査しています。

正しい順列でも、利用者の希望と違うことがある

[3, 0, 1, 2]も[0, 1, 2, 3]も、有効な順列です。表紙を先頭にしたい利用者にとって、後者が不正解であっても、この関数は拒否しません。

その判定には、利用者が決めた順序が必要です。「表紙、説明、申請用紙、添付資料」のように、ページ内容で期待値を残します。書き出し後のPDFを開き、その順序と比較します。元ページ数が同じという確認だけで終わらせません。

保存処理の入力と結果を混同しない

この関数はPDFのバイト列を読みません。書き出し処理が古い計画を使った、移動先を取り違えた、ページの内容を欠落させた、といった不具合は発見できません。

また、配列のコピーは文書全体のスナップショットではありません。保存中に元文書へページが追加される設計なら、計画と元文書の版を結び付ける必要があります。

保存後には実際のファイルを開き、ページ内容の順序を確かめます。リンクやフォームが必要なら、それぞれ別に検査します。デジタル署名付き原本は保存しておき、変更後の見た目から署名の検証結果を推測しません。

この例で分けたいのは、入力計画の構造と、保存されたファイルの結果です。前者の検査が通っても、後者の検査は残っています。

参考:Number.isInteger、配列の空スロット。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?