To split a game-publisher agreement into commercial, data-processing, or territory schedules in Node.js, treat every chapter start you parsed as untrusted. Convert the starts into half-open page intervals. Validate the whole interval set before copying a single page. Only then copy pages, check each output, hash it, and hand it to the signing step with an audit record.
The ordering matters because a digital signature can’t catch a bad split. It protects the bytes you give it. If a worker quietly drops page 12 of a revenue-share schedule, the signature on the shortened file will still verify.
Why a valid signature doesn’t prove the right pages were signed
PDF 32000-1:2008 describes signature verification as a digest comparison: “To verify the signature, the digest shall be re-computed and compared with the one stored in the document.” The standard also describes a byte-range digest. It recommends covering the entire file except the signature value, because other ranges don’t detect all changes.
That is an integrity guarantee about a byte state. It says nothing about whether your application selected the pages the parties meant to sign. Page selection happens before the signature exists, so the check has to happen there too. Missing, duplicated, or reordered pages are all valid PDFs. A signer will sign them without complaint.
#1 Best Overall
- Create a mix using audio, music and voice tracks and recordings.
- Customize your tracks with amazing effects and helpful editing tools.
- Use tools like the Beat Maker and Midi Creator.
- Work efficiently by using Bookmarks and tools like Effect Chain, which allow you to apply multiple effects at a time
- Use one of the many other NCH multimedia applications that are integrated with MixPad.
This article’s workflow follows the design described in a DEV article with this title: treat parsed table-of-contents starts as untrusted evidence, use half-open intervals, validate all intervals before copying, and link each output to a signing audit record through a cryptographic digest. That design is the article’s proposal, not behavior we have independently benchmarked. The code below is an illustrative sketch of the idea, and you should test it against your own documents before relying on it.
Step 1: Pick one index convention and convert once
Use half-open intervals, [start, end), with zero-based page indexes internally. The interval length is simply end - start. Adjacent chapters meet at the same number ([0, 11) then [11, 18)) without overlapping, so a gap or overlap shows up as a plain inequality.
Humans and table-of-contents data use one-based page labels, usually inclusive (“pages 12–18”). Convert at a single boundary layer and unit-test it. Don’t let both conventions travel through the code. Here is a worked example for a 30-page agreement:
| Schedule | One-based labels (inclusive) | Internal interval (zero-based, half-open) | Length |
|---|---|---|---|
| Main terms | 1–11 | [0, 11) | 11 |
| Commercial schedule | 12–18 | [11, 18) | 7 |
| Data-processing schedule | 19–25 | [18, 25) | 7 |
| Territory schedule | 26–30 | [25, 30) | 5 |
The lengths sum to 30, the source page count. That sum is one of the cheapest checks you can run.
Rank #2
- Transform audio playing via your speakers and headphones
- Improve sound quality by adjusting it with effects
- Take control over the sound playing through audio hardware
Step 2: Turn chapter starts into intervals without sorting them
A common shortcut is to sort the parsed starts and derive each end from the next start. That hides a reordered outline. If the parser reports starts of [0, 18, 11, 25], sorting produces a plausible plan and masks a parsing fault. Validate the starts in the order they arrived first.
export class SplitPlanError extends Error {
constructor(code, detail) {
super(code);
this.code = code; // machine-readable, safe to log
this.detail = detail; // indexes and ids only, never contract text
}
}
// starts: zero-based first page of each chapter, in outline order
export function startsToIntervals(chapterIds, starts, pageCount) {
if (chapterIds.length !== starts.length) {
throw new SplitPlanError('ID_START_MISMATCH', { ids: chapterIds.length, starts: starts.length });
}
const intervals = starts.map((start, i) => ({
id: chapterIds[i],
start,
end: i + 1 < starts.length ? starts[i + 1] : pageCount,
}));
validateIntervals(intervals, pageCount);
return intervals;
}
Step 3: Validate the entire plan before copying any page
Run every check over the full manifest and stop on the first violation. If you validate lazily while copying, a late failure leaves partial output. That means cleanup, and possibly queue messages, object-store writes, or log lines that downstream systems have already seen.
export function validateIntervals(intervals, pageCount, { requireContiguous = true } = {}) {
if (!Number.isInteger(pageCount) || pageCount < 1) {
throw new SplitPlanError('BAD_PAGE_COUNT', { pageCount });
}
const seen = new Set();
let covered = 0;
intervals.forEach((c, i) => {
if (seen.has(c.id)) throw new SplitPlanError('DUPLICATE_CHAPTER_ID', { id: c.id });
seen.add(c.id);
if (!Number.isInteger(c.start) || !Number.isInteger(c.end)) {
throw new SplitPlanError('NON_INTEGER_BOUNDARY', { id: c.id });
}
if (c.start < 0 || c.end > pageCount) {
throw new SplitPlanError('OUT_OF_BOUNDS', { id: c.id, start: c.start, end: c.end, pageCount });
}
if (c.start >= c.end) {
throw new SplitPlanError('EMPTY_OR_INVERTED', { id: c.id, start: c.start, end: c.end });
}
if (i > 0) {
const prev = intervals[i - 1];
if (c.start < prev.end) {
throw new SplitPlanError('OVERLAP_OR_REORDER', { id: c.id, prevEnd: prev.end, start: c.start });
}
if (requireContiguous && c.start > prev.end) {
throw new SplitPlanError('GAP', { id: c.id, prevEnd: prev.end, start: c.start });
}
}
covered += c.end - c.start;
});
if (requireContiguous) {
if (intervals[0].start !== 0) {
throw new SplitPlanError('UNCLAIMED_FRONT_PAGES', { firstStart: intervals[0].start });
}
if (covered !== pageCount) {
throw new SplitPlanError('COVERAGE_MISMATCH', { covered, pageCount });
}
}
}
Set requireContiguous to false only when the plan intentionally leaves pages out, such as a cover sheet or a superseded annex. In that case, list the excluded pages explicitly in the manifest instead of inferring them from what’s left over.
What each check catches
| Failure | Typical cause | Check that stops it |
|---|---|---|
| Missing page | Outline entry skipped, or an end boundary off by one | GAP, COVERAGE_MISMATCH |
| Duplicated page | Two chapters share a boundary page, or an inclusive end is read as exclusive | OVERLAP_OR_REORDER |
| Reordered chapters | Unordered outline traversal, or sorting that hides a parser fault | OVERLAP_OR_REORDER on unsorted input |
| One-based/zero-based confusion | Label conversion done in two places | Boundary-layer unit tests; UNCLAIMED_FRONT_PAGES |
| Outline points past the end | Stale outline after pages were removed | OUT_OF_BOUNDS |
| Same chapter twice | Duplicate bookmark titles or IDs | DUPLICATE_CHAPTER_ID |
These checks prove the plan is internally consistent. They can’t prove it matches the contract’s meaning. An outline whose “Territory” bookmark points to the wrong page produces a perfectly valid plan. For that, add an anchor check. Store, per chapter, an expected fingerprint of the first page’s heading text (a hash of normalized text, not the text itself), and compare it after extraction. This is an engineering control we recommend, not something the cited design specifies, and it adds parsing work you’ll need to maintain.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteRank #3
- Intuitive interface of a conventional FTP client
- Easy and Reliable FTP Site Maintenance.
- FTP Automation and Synchronization
Step 4: Copy the pages and verify each output
pdf-lib runs in Node.js and supports page copying between documents. Copy by explicit page indexes, then confirm the output page count before the part goes anywhere near signing.
import { createHash } from 'node:crypto';
import { PDFDocument } from 'pdf-lib';
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
export async function splitByIntervals(sourceBytes, intervals) {
const src = await PDFDocument.load(sourceBytes);
const pageCount = src.getPageCount();
validateIntervals(intervals, pageCount); // nothing is copied before this passes
const parts = [];
for (const { id, start, end } of intervals) {
const out = await PDFDocument.create();
const indexes = Array.from({ length: end - start }, (_, i) => start + i);
const pages = await out.copyPages(src, indexes);
pages.forEach((p) => out.addPage(p));
if (out.getPageCount() !== end - start) {
throw new SplitPlanError('OUTPUT_PAGE_COUNT', { id, expected: end - start, actual: out.getPageCount() });
}
const bytes = await out.save();
parts.push({ id, start, end, bytes, sha256: sha256(bytes) });
}
return { sourceSha256: sha256(sourceBytes), pageCount, parts };
}
Keep the parts in memory (or a staging location nobody consumes from) until the whole batch succeeds. Publish to the signer only after every part has passed.
A few cautions apply to any page-copy approach. The new file is a different document from the source, so any existing signature on the source does not carry over. Also test whether anything your workflow depends on survives the copy, such as bookmarks, form fields, or attachments. Don’t assume it does.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Step 5: Write an audit record that ties output to source
Each output should be traceable to the exact source pages it came from. A record per bundle might look like this:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #4
- Mix an audio, music and voice tracks
- Record single or multiple tracks simultaneously
- Intuitive tools to split, trim, join, and many other editing features
- Loaded with audio effects including EQ, compression, reverb, and more.
- Load an audio file and export to all popular audio formats from studio quality wav to high compression formats
{
"bundleId": "bnd_2026_0412",
"sourceSha256": "…",
"sourcePageCount": 30,
"chapterStarts": [0, 11, 18, 25],
"emitted": [
{ "id": "commercial", "interval": [11, 18], "outputSha256": "…", "signerJobId": "job_…" }
],
"firstViolatedInvariant": null
}
Log identifiers, indexes, digests, and the first violated invariant code. Don’t put contract text, party names, or personal data in alerts or logs. A message like OVERLAP_OR_REORDER id=territory prevEnd=25 start=24 is enough for an engineer to act on.
The signing job then receives bytes whose digest you already recorded. If the signer’s returned document refers to a different digest than the one you submitted, you have a mismatch worth alerting on. That check closes the loop between your page selection and the signature.
Choosing a splitting route: local library or hosted API
| Route | What the official documentation establishes | What to compare before choosing |
|---|---|---|
| pdf-lib (local JavaScript library) | Runs in Node.js; supports page manipulation including copying, splitting and merging. | Document compatibility, memory and file-size behavior, who owns the validation code, data residency, maintenance status. |
| Adobe PDF Services API (hosted) | Has an official Node.js sample and a range-based split operation. | Upload and data-handling terms, credential management, usage limits, error handling, range semantics, current pricing. |
A local library means the documented route doesn’t upload the agreement to an API. That doesn’t by itself establish every security property of your deployment. A hosted service can reduce PDF-handling code, but you need to review credentials, data processing, limits, and pricing in the vendor’s current terms. The documentation we reviewed gives no performance benchmark and no complete privacy or price comparison, so none is claimed here.
If you use a hosted range-split operation, confirm how its page ranges are numbered and whether ends are inclusive. Then adapt your boundary layer to that convention. Run the same manifest validation before the request and the same page-count and digest checks on what comes back. Validation belongs to your application whichever engine does the copying.
Free tools Windows power users keep installed
One-click scans. No signup required.
Scope: technical integrity, not legal validity
This workflow protects against mechanical page-selection errors. It doesn’t establish that a signed schedule is enforceable, which jurisdiction governs, or whether your signature provider meets any regulatory standard. Those questions need legal and compliance review for the contract in question.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




