extract-pdf 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/dist/models/annotation.d.ts +20 -0
- package/dist/models/block-type.d.ts +10 -0
- package/dist/models/headline-finder.d.ts +11 -0
- package/dist/models/line-converter.d.ts +10 -0
- package/dist/models/line-item-block.d.ts +14 -0
- package/dist/models/line-item.d.ts +25 -0
- package/dist/models/metadata.d.ts +23 -0
- package/dist/models/page-item.d.ts +21 -0
- package/dist/models/page.d.ts +14 -0
- package/dist/models/parse-result.d.ts +24 -0
- package/dist/models/parsed-elements.d.ts +20 -0
- package/dist/models/stashing-stream.d.ts +23 -0
- package/dist/models/text-item-line-grouper.d.ts +8 -0
- package/dist/models/text-item.d.ts +29 -0
- package/dist/models/word.d.ts +27 -0
- package/dist/pdf-to-html.cjs.js +1 -1
- package/dist/pdf-to-html.d.ts +36 -41
- package/dist/pdf-to-html.es.js +1 -1
- package/dist/transforms/base/to-line-item-block-transform.d.ts +6 -0
- package/dist/transforms/base/to-line-item-transform.d.ts +6 -0
- package/dist/transforms/base/to-text-item-transform.d.ts +6 -0
- package/dist/transforms/base/transformation.d.ts +8 -0
- package/dist/transforms/block/detect-code-quote-blocks.d.ts +6 -0
- package/dist/transforms/block/detect-list-levels.d.ts +6 -0
- package/dist/transforms/block/gather-blocks.d.ts +6 -0
- package/dist/transforms/calculate-global-stats.d.ts +11 -0
- package/dist/transforms/line-item/compact-lines.d.ts +6 -0
- package/dist/transforms/line-item/detect-headers.d.ts +6 -0
- package/dist/transforms/line-item/detect-list-items.d.ts +6 -0
- package/dist/transforms/line-item/detect-toc.d.ts +6 -0
- package/dist/transforms/line-item/remove-repetitive-elements.d.ts +6 -0
- package/dist/transforms/line-item/vertical-to-horizontal.d.ts +6 -0
- package/dist/transforms/to-html.d.ts +6 -0
- package/dist/transforms/to-text-blocks.d.ts +6 -0
- package/dist/utils/is-url-pdf.d.ts +1 -0
- package/dist/utils/page-item-functions.d.ts +8 -0
- package/dist/utils/page-number-functions.d.ts +14 -0
- package/dist/utils/string-functions.d.ts +14 -0
- package/package.json +8 -11
- package/src/models/annotation.ts +41 -0
- package/src/models/block-type.ts +203 -0
- package/src/models/headline-finder.ts +53 -0
- package/src/models/line-converter.ts +224 -0
- package/src/models/line-item-block.ts +51 -0
- package/src/models/line-item.ts +59 -0
- package/src/models/metadata.ts +29 -0
- package/src/models/page-item.ts +36 -0
- package/src/models/page.ts +16 -0
- package/src/models/parse-result.ts +32 -0
- package/src/models/parsed-elements.ts +29 -0
- package/src/models/stashing-stream.ts +86 -0
- package/src/models/text-item-line-grouper.ts +41 -0
- package/src/models/text-item.ts +50 -0
- package/src/models/word.ts +31 -0
- package/src/pdf-to-html.ts +225 -0
- package/src/transforms/base/to-line-item-block-transform.ts +29 -0
- package/src/transforms/base/to-line-item-transform.ts +29 -0
- package/src/transforms/base/to-text-item-transform.ts +28 -0
- package/src/transforms/base/transformation.ts +35 -0
- package/src/transforms/block/detect-code-quote-blocks.ts +57 -0
- package/src/transforms/block/detect-list-levels.ts +64 -0
- package/src/transforms/block/gather-blocks.ts +113 -0
- package/src/transforms/calculate-global-stats.ts +132 -0
- package/src/transforms/line-item/compact-lines.ts +92 -0
- package/src/transforms/line-item/detect-headers.ts +173 -0
- package/src/transforms/line-item/detect-list-items.ts +68 -0
- package/src/transforms/line-item/detect-toc.ts +459 -0
- package/src/transforms/line-item/remove-repetitive-elements.ts +101 -0
- package/src/transforms/line-item/vertical-to-horizontal.ts +90 -0
- package/src/transforms/to-html.ts +46 -0
- package/src/transforms/to-text-blocks.ts +38 -0
- package/src/utils/is-url-pdf.ts +33 -0
- package/src/utils/page-item-functions.ts +35 -0
- package/src/utils/page-number-functions.ts +109 -0
- package/src/utils/string-functions.ts +124 -0
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
# pdf
|
|
1
|
+
# extract-pdf
|
|
2
2
|
|
|
3
3
|
Converts a PDF (URL or `ArrayBuffer`) into clean HTML with structural tagging — headings, lists, footnotes, code blocks, bold/italic, and Table of Contents entries. Works in Node.js, Cloudflare Workers, and browser environments via [pdfjs-serverless](https://github.com/johannschopplich/pdfjs-serverless).
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun add extract-pdf
|
|
9
|
+
```
|
|
10
|
+
|
|
5
11
|
## Usage
|
|
6
12
|
|
|
7
13
|
```ts
|
|
8
|
-
import { convertPDFToHTML } from "
|
|
14
|
+
import { convertPDFToHTML } from "extract-pdf";
|
|
9
15
|
|
|
10
16
|
const { html, title, author } = await convertPDFToHTML(
|
|
11
17
|
"https://example.com/paper.pdf",
|
|
@@ -50,18 +56,18 @@ Raw pdfjs text spans
|
|
|
50
56
|
## Folder structure
|
|
51
57
|
|
|
52
58
|
```
|
|
53
|
-
|
|
59
|
+
src/
|
|
54
60
|
pdf-to-html.ts — main entry point (convertPDFToHTML)
|
|
55
61
|
models/ — data classes: Page, ParseResult, TextItem,
|
|
56
62
|
│ LineItem, LineItemBlock, Word, BlockType, …
|
|
57
|
-
|
|
58
|
-
│
|
|
59
|
-
│ CalculateGlobalStats.ts
|
|
60
|
-
│ ToTextBlocks.ts
|
|
61
|
-
│ ToHTML.ts
|
|
63
|
+
transforms/
|
|
64
|
+
│ base/ — abstract Transformation, ToLineItem*, ToLineItemBlock*
|
|
62
65
|
│ line-item/ — per-line-item transformations
|
|
63
|
-
│
|
|
64
|
-
|
|
66
|
+
│ block/ — per-block transformations
|
|
67
|
+
│ calculate-global-stats.ts
|
|
68
|
+
│ to-text-blocks.ts
|
|
69
|
+
│ to-html.ts
|
|
70
|
+
utils/
|
|
65
71
|
string-functions.ts
|
|
66
72
|
page-item-functions.ts
|
|
67
73
|
page-number-functions.ts
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Diff-annotation markers used to tag document items during pipeline
|
|
3
|
+
* transformations. Each `Annotation` carries a human-readable `category` and a
|
|
4
|
+
* display `color`. Pre-built singletons (`ADDED_ANNOTATION`, `REMOVED_ANNOTATION`,
|
|
5
|
+
* `UNCHANGED_ANNOTATION`, `DETECTED_ANNOTATION`, `MODIFIED_ANNOTATION`) are
|
|
6
|
+
* attached to page items so debug views can highlight what each stage changed.
|
|
7
|
+
*/
|
|
8
|
+
export default class Annotation {
|
|
9
|
+
category: string;
|
|
10
|
+
color: string;
|
|
11
|
+
constructor(options: {
|
|
12
|
+
category: string;
|
|
13
|
+
color: string;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export declare const ADDED_ANNOTATION: Annotation;
|
|
17
|
+
export declare const REMOVED_ANNOTATION: Annotation;
|
|
18
|
+
export declare const UNCHANGED_ANNOTATION: Annotation;
|
|
19
|
+
export declare const DETECTED_ANNOTATION: Annotation;
|
|
20
|
+
export declare const MODIFIED_ANNOTATION: Annotation;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Enum-like registry of semantic document block types (H1–H6, TOC,
|
|
3
|
+
* FOOTNOTES, CODE, LIST, PARAGRAPH). Each entry carries a `toText()` serializer
|
|
4
|
+
* that converts a `LineItemBlock` to HTML. Helper methods `isHeadline`,
|
|
5
|
+
* `blockToText`, and `headlineByLevel` are mixed in on the registry object.
|
|
6
|
+
* The inner `linesToText` function handles inline bold/italic/link/footnote
|
|
7
|
+
* formatting by tracking open `WordFormat` spans across words and lines.
|
|
8
|
+
*/
|
|
9
|
+
declare const BlockTypes: Record<string, any>;
|
|
10
|
+
export default BlockTypes;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { default as LineItem } from './line-item';
|
|
2
|
+
export default class HeadlineFinder {
|
|
3
|
+
headlineCharCodes: number[];
|
|
4
|
+
stackedLineItems: LineItem[];
|
|
5
|
+
stackedChars: number;
|
|
6
|
+
constructor(options: {
|
|
7
|
+
headline: string;
|
|
8
|
+
});
|
|
9
|
+
consume(lineItem: LineItem): LineItem[] | null;
|
|
10
|
+
matchAll(normalizedCharCodes: number[]): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as TextItem } from './text-item';
|
|
2
|
+
import { WordFormatEntry, WordTypeEntry } from './word';
|
|
3
|
+
import { default as LineItem } from './line-item';
|
|
4
|
+
export declare const WordFormat: Record<string, WordFormatEntry>;
|
|
5
|
+
export declare const WordType: Record<string, WordTypeEntry>;
|
|
6
|
+
export default class LineConverter {
|
|
7
|
+
fontToFormats: Map<string, string>;
|
|
8
|
+
constructor(fontToFormats: Map<string, string>);
|
|
9
|
+
compact(textItems: TextItem[]): LineItem;
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { default as PageItem, BlockTypeEntry } from './page-item';
|
|
2
|
+
import { default as Annotation } from './annotation';
|
|
3
|
+
import { default as ParsedElements } from './parsed-elements';
|
|
4
|
+
import { default as LineItem } from './line-item';
|
|
5
|
+
export default class LineItemBlock extends PageItem {
|
|
6
|
+
items: LineItem[];
|
|
7
|
+
constructor(options: {
|
|
8
|
+
items?: LineItem[];
|
|
9
|
+
type?: BlockTypeEntry | null;
|
|
10
|
+
annotation?: Annotation | null;
|
|
11
|
+
parsedElements?: ParsedElements | null;
|
|
12
|
+
});
|
|
13
|
+
addItem(item: LineItem): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { default as PageItem, BlockTypeEntry } from './page-item';
|
|
2
|
+
import { default as Annotation } from './annotation';
|
|
3
|
+
import { default as ParsedElements } from './parsed-elements';
|
|
4
|
+
import { default as Word } from './word';
|
|
5
|
+
export default class LineItem extends PageItem {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
words: Word[];
|
|
11
|
+
constructor(options: {
|
|
12
|
+
x?: number;
|
|
13
|
+
y?: number;
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
words?: Word[];
|
|
17
|
+
text?: string;
|
|
18
|
+
type?: BlockTypeEntry | null;
|
|
19
|
+
annotation?: Annotation | null;
|
|
20
|
+
parsedElements?: ParsedElements | null;
|
|
21
|
+
font?: string;
|
|
22
|
+
});
|
|
23
|
+
text(): string;
|
|
24
|
+
wordStrings(): string[];
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Normalised PDF document metadata (title, author, creator, producer).
|
|
3
|
+
* Handles both the modern XMP `metadata` API and the legacy `info` dictionary
|
|
4
|
+
* returned by pdfjs, so callers always receive a consistent shape regardless of
|
|
5
|
+
* which metadata format the PDF uses.
|
|
6
|
+
*/
|
|
7
|
+
export default class Metadata {
|
|
8
|
+
title?: string;
|
|
9
|
+
creator?: string;
|
|
10
|
+
producer?: string;
|
|
11
|
+
author?: string;
|
|
12
|
+
constructor(originalMetadata: {
|
|
13
|
+
metadata?: {
|
|
14
|
+
get(key: string): string | undefined;
|
|
15
|
+
};
|
|
16
|
+
info?: {
|
|
17
|
+
Title?: string;
|
|
18
|
+
Author?: string;
|
|
19
|
+
Creator?: string;
|
|
20
|
+
Producer?: string;
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { default as Annotation } from './annotation';
|
|
2
|
+
import { default as ParsedElements } from './parsed-elements';
|
|
3
|
+
export interface BlockTypeEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
headline?: boolean;
|
|
6
|
+
headlineLevel?: number;
|
|
7
|
+
mergeToBlock?: boolean;
|
|
8
|
+
mergeFollowingNonTypedItems?: boolean;
|
|
9
|
+
mergeFollowingNonTypedItemsWithSmallDistance?: boolean;
|
|
10
|
+
toText?(block: any): string;
|
|
11
|
+
}
|
|
12
|
+
export default class PageItem {
|
|
13
|
+
type: BlockTypeEntry | null;
|
|
14
|
+
annotation: Annotation | null;
|
|
15
|
+
parsedElements: ParsedElements | null;
|
|
16
|
+
constructor(options: {
|
|
17
|
+
type?: BlockTypeEntry | null;
|
|
18
|
+
annotation?: Annotation | null;
|
|
19
|
+
parsedElements?: ParsedElements | null;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Simple container for a single PDF page. Holds a 0-based `index`
|
|
3
|
+
* and an `items` array whose element type evolves through the transformation
|
|
4
|
+
* pipeline: raw `TextItem[]` → compacted `LineItem[]` → grouped `LineItemBlock[]`
|
|
5
|
+
* → final serialized text objects.
|
|
6
|
+
*/
|
|
7
|
+
export default class Page {
|
|
8
|
+
index: number;
|
|
9
|
+
items: any[];
|
|
10
|
+
constructor(options: {
|
|
11
|
+
index: number;
|
|
12
|
+
items?: any[];
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { default as Page } from './page';
|
|
2
|
+
export interface ParseGlobals {
|
|
3
|
+
mostUsedHeight?: number;
|
|
4
|
+
mostUsedFont?: string;
|
|
5
|
+
mostUsedDistance?: number;
|
|
6
|
+
maxHeight?: number;
|
|
7
|
+
maxHeightFont?: string;
|
|
8
|
+
fontToFormats?: Map<string, string>;
|
|
9
|
+
tocPages?: number[];
|
|
10
|
+
headlineTypeToHeightRange?: Record<string, {
|
|
11
|
+
min: number;
|
|
12
|
+
max: number;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export default class ParseResult {
|
|
16
|
+
pages: Page[];
|
|
17
|
+
globals: ParseGlobals;
|
|
18
|
+
messages: string[];
|
|
19
|
+
constructor(options: {
|
|
20
|
+
pages?: Page[];
|
|
21
|
+
globals?: ParseGlobals;
|
|
22
|
+
messages?: string[];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Aggregated inline-element metadata for a `LineItem` or
|
|
3
|
+
* `LineItemBlock`. Tracks detected footnote link numbers, footnote definitions,
|
|
4
|
+
* whether any hyperlinks are present, and a count of bold/italic formatted words.
|
|
5
|
+
* The `add()` method merges a child item's `ParsedElements` into the parent
|
|
6
|
+
* block's running totals as lines are gathered into blocks.
|
|
7
|
+
*/
|
|
8
|
+
export default class ParsedElements {
|
|
9
|
+
footnoteLinks: number[];
|
|
10
|
+
footnotes: string[];
|
|
11
|
+
containLinks: boolean;
|
|
12
|
+
formattedWords: number;
|
|
13
|
+
constructor(options: {
|
|
14
|
+
footnoteLinks?: number[];
|
|
15
|
+
footnotes?: string[];
|
|
16
|
+
containLinks?: boolean;
|
|
17
|
+
formattedWords?: number;
|
|
18
|
+
});
|
|
19
|
+
add(parsedElements: ParsedElements): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Abstract stream processor that buffers ("stashes") consecutive
|
|
3
|
+
* items matching a predicate before flushing them as a group. Subclasses
|
|
4
|
+
* implement `shouldStash`, `doMatchesStash`, and `doFlushStash` to define what
|
|
5
|
+
* constitutes a run. Used by `WordDetectionStream` in `LineConverter` (grouping
|
|
6
|
+
* same-format text spans) and `VerticalsStream` in `VerticalToHorizontal`
|
|
7
|
+
* (merging single-character vertical lines into one horizontal line).
|
|
8
|
+
*/
|
|
9
|
+
export default class StashingStream {
|
|
10
|
+
results: any[];
|
|
11
|
+
stash: any[];
|
|
12
|
+
constructor();
|
|
13
|
+
consumeAll(items: any[]): void;
|
|
14
|
+
consume(item: any): void;
|
|
15
|
+
pushOnStash(item: any): void;
|
|
16
|
+
complete(): any[];
|
|
17
|
+
matchesStash(item: any): boolean;
|
|
18
|
+
flushStash(): void;
|
|
19
|
+
onPushOnStash(item: any): void;
|
|
20
|
+
shouldStash(item: any): boolean;
|
|
21
|
+
doMatchesStash(lastItem: any, item: any): boolean;
|
|
22
|
+
doFlushStash(stash: any[], results: any[]): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { default as PageItem, BlockTypeEntry } from './page-item';
|
|
2
|
+
import { default as Annotation } from './annotation';
|
|
3
|
+
import { default as ParsedElements } from './parsed-elements';
|
|
4
|
+
import { WordFormatEntry } from './word';
|
|
5
|
+
export default class TextItem extends PageItem {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
text: string;
|
|
11
|
+
font: string;
|
|
12
|
+
lineFormat: WordFormatEntry | null;
|
|
13
|
+
unopenedFormat: WordFormatEntry | null;
|
|
14
|
+
unclosedFormat: WordFormatEntry | null;
|
|
15
|
+
constructor(options: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
text: string;
|
|
21
|
+
font: string;
|
|
22
|
+
lineFormat?: WordFormatEntry | null;
|
|
23
|
+
unopenedFormat?: WordFormatEntry | null;
|
|
24
|
+
unclosedFormat?: WordFormatEntry | null;
|
|
25
|
+
type?: BlockTypeEntry | null;
|
|
26
|
+
annotation?: Annotation | null;
|
|
27
|
+
parsedElements?: ParsedElements | null;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Minimal token model for a single word within a `LineItem`.
|
|
3
|
+
* Carries the text `string`, an optional `type` (`WordType`: LINK,
|
|
4
|
+
* FOOTNOTE_LINK, FOOTNOTE) for semantic rendering, and an optional `format`
|
|
5
|
+
* (`WordFormat`: BOLD, OBLIQUE, BOLD_OBLIQUE) for inline HTML styling.
|
|
6
|
+
*/
|
|
7
|
+
export interface WordFormatEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
startSymbol: string;
|
|
10
|
+
endSymbol: string;
|
|
11
|
+
}
|
|
12
|
+
export interface WordTypeEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
attachWithoutWhitespace?: boolean;
|
|
15
|
+
plainTextFormat?: boolean;
|
|
16
|
+
toText(string: string): string;
|
|
17
|
+
}
|
|
18
|
+
export default class Word {
|
|
19
|
+
string: string;
|
|
20
|
+
type: WordTypeEntry | null;
|
|
21
|
+
format: WordFormatEntry | null;
|
|
22
|
+
constructor(options: {
|
|
23
|
+
string: string;
|
|
24
|
+
type?: WordTypeEntry | null;
|
|
25
|
+
format?: WordFormatEntry | null;
|
|
26
|
+
});
|
|
27
|
+
}
|
package/dist/pdf-to-html.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty;Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("grab-url");function e(t){for(;32===t.charCodeAt(0);)t=t.substring(1,t.length);return t}function s(t){for(;32===t.charCodeAt(t.length-1);)t=t.substring(0,t.length-1);return t}function n(t){return t>=48&&t<=57}function o(t){for(var e=0;e<t.length;e++){if(!n(t.charCodeAt(e)))return!1}return!0}function r(t,e){const s=e.charCodeAt(0);for(var n=0;n<t.length;n++){if(t.charCodeAt(n)!==s)return!1}return!0}function a(t){return function(t){const e=[];for(var s=0;s<t.length;s++)e.push(t.charCodeAt(s));return e}(t=t.toUpperCase()).filter(t=>32!==t&&9!==t&&46!==t)}function i(t){if(t.length>1)return!1;const e=t.charAt(0);return"-"===e||"•"===e||"–"===e}function h(t){return/^[\s]*[-•–][\s].*$/g.test(t)}function l(t,e){const s=new Set(t.toUpperCase().split(" ")),n=new Set(e.toUpperCase().split(" "));return new Set([...s].filter(t=>n.has(t))).size/Math.max(s.size,n.size)}const c=(t,e,s)=>Math.floor(t/e*s),m=(t,n,r)=>{for(const{str:a}of t){const t=s(e(a));o(t)&&(n[r]||(n[r]=[]),n[r].push(Number(t)))}return n},u=(t,e,s)=>{const n=c(1,6,s.length),o=c(5,6,s.length),r=m(s.slice(0,n),t,e);return m(s.slice(o),r,e)},f=t=>{let e=0;const s=Object.keys(t);if(0!==s.length&&1!==s.length)for(let n=0;n<s.length-1;n++){const o=t[s[n]],r=t[s[n+1]],a=e;for(let t=0;t<o.length&&e<2;t++)for(let a=0;a<r.length&&e<2;a++){const i=Number(s[n+1])-Number(s[n]);(o[t]+1===r[a]||i>1&&o[t]+i===r[a])&&e++}let i=n>0?Object.entries(t)[n-1]:Object.entries(t)[n];if(a===e)e=0,i=Object.entries(t)[n];else if(e>=2)return{pageIndex:Number(i[0]),pageNum:i[1][0]}}},p=(t,e)=>{const s={items:[...t.items]},n=c(1,6,s.items.length),o=c(5,6,s.items.length);return s.items=s.items.filter((t,r)=>{const a=r>0&&r<n,i=r>o&&r<s.items.length;return a||i?Number(t.str)!==Number(e):t}),s};class d{type;annotation;parsedElements;constructor(t){if(this.constructor===d)throw new TypeError("Can not construct abstract class.");this.type=t.type??null,this.annotation=t.annotation??null,this.parsedElements=t.parsedElements??null}}class g extends d{x;y;width;height;text;font;lineFormat;unopenedFormat;unclosedFormat;constructor(t){super(t),this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height,this.text=t.text,this.font=t.font,this.lineFormat=t.lineFormat??null,this.unopenedFormat=t.unopenedFormat??null,this.unclosedFormat=t.unclosedFormat??null}}class y{index;items;constructor(t){this.index=t.index,this.items=t.items||[]}}class x{name;itemType;constructor(t,e){if(this.constructor===x)throw new TypeError("Can not construct abstract class.");if(this.transform===x.prototype.transform)throw new TypeError("Please implement abstract method 'transform()'.");this.name=t,this.itemType=e}transform(t){throw new TypeError("Do not call abstract method foo from child.")}completeTransform(t){return t.messages=[],t}}class w{category;color;constructor(t){this.category=t.category,this.color=t.color}}const E=new w({category:"Added",color:"green"}),T=new w({category:"Removed",color:"red"}),v=new w({category:"Unchanged",color:"brown"}),b=new w({category:"Detected",color:"green"}),O=new w({category:"Modified",color:"green"});class L extends x{constructor(t){if(super(t,g.name),this.constructor===L)throw new TypeError("Can not construct abstract class.")}completeTransform(t){return t.messages=[],t.pages.forEach(t=>{t.items=t.items.filter(t=>!t.annotation||t.annotation!==T),t.items.forEach(t=>t.annotation=null)}),t}}class I{pages;globals;messages;constructor(t){this.pages=t.pages||[],this.globals=t.globals||{},this.messages=t.messages||[]}}class S{string;type;format;constructor(t){this.string=t.string,this.type=t.type??null,this.format=t.format??null}}class k extends d{x;y;width;height;words;constructor(t){super(t),this.x=t.x??0,this.y=t.y??0,this.width=t.width??0,this.height=t.height??0,this.words=t.words||[],t.text&&!t.words&&(this.words=t.text.split(" ").filter(t=>t.trim().length>0).map(t=>new S({string:t})))}text(){return this.wordStrings().join(" ")}wordStrings(){return this.words.map(t=>t.string)}}class F{results;stash;constructor(){if(this.constructor===F)throw new TypeError("Can not construct abstract class.");this.results=[],this.stash=[]}consumeAll(t){t.forEach(t=>this.consume(t))}consume(t){this.shouldStash(t)?(this.matchesStash(t)||this.flushStash(),this.pushOnStash(t)):(this.stash.length>0&&this.flushStash(),this.results.push(t))}pushOnStash(t){this.onPushOnStash(t),this.stash.push(t)}complete(){return this.stash.length>0&&this.flushStash(),this.results}matchesStash(t){if(0===this.stash.length)return!0;const e=this.stash[this.stash.length-1];return this.doMatchesStash(e,t)}flushStash(){this.stash.length>0&&(this.doFlushStash(this.stash,this.results),this.stash=[])}onPushOnStash(t){}shouldStash(t){throw new TypeError(" Do not call abstract method foo from child."+t)}doMatchesStash(t,e){throw new TypeError(" Do not call abstract method foo from child."+t+e)}doFlushStash(t,e){throw new TypeError(" Do not call abstract method foo from child."+t+e)}}class C{footnoteLinks;footnotes;containLinks;formattedWords;constructor(t){this.footnoteLinks=t.footnoteLinks||[],this.footnotes=t.footnotes||[],this.containLinks=t.containLinks??!1,this.formattedWords=t.formattedWords??0}add(t){this.footnoteLinks=this.footnoteLinks.concat(t.footnoteLinks),this.footnotes=this.footnotes.concat(t.footnotes),this.containLinks=this.containLinks||t.containLinks,this.formattedWords+=t.formattedWords}}function N(t){t.sort((t,e)=>t.x-e.x)}const D={BOLD:{name:"BOLD",startSymbol:"<strong>",endSymbol:"</strong>"},OBLIQUE:{name:"OBLIQUE",startSymbol:"<em>",endSymbol:"</em>"},BOLD_OBLIQUE:{name:"BOLD_OBLIQUE",startSymbol:"<strong><em>",endSymbol:"</em></strong>"}},M={LINK:{name:"LINK",toText:t=>`<a href="${t}">${t}</a>`},FOOTNOTE_LINK:{name:"FOOTNOTE_LINK",attachWithoutWhitespace:!0,plainTextFormat:!0,toText:t=>`<sup><a href="#${t}">${t}</a></sup>`},FOOTNOTE:{name:"FOOTNOTE",toText:t=>`<p id="${t}">^${t}</p>`}};class H{fontToFormats;constructor(t){this.fontToFormats=t}compact(t){N(t);const e=new B(this.fontToFormats);e.consumeAll(t.map(t=>new g({...t})));const s=e.complete();var n=0,o=0;return t.forEach(t=>{n=Math.max(n,t.height),o+=t.width}),new k({x:t[0].x,y:t[0].y,height:n,width:o,words:s,parsedElements:new C({footnoteLinks:e.footnoteLinks,footnotes:e.footnotes,containLinks:e.containLinks,formattedWords:e.formattedWords})})}}class B extends F{fontToFormats;footnoteLinks;footnotes;formattedWords;containLinks;stashedNumber;firstY;currentItem;constructor(t){super(),this.fontToFormats=t,this.footnoteLinks=[],this.footnotes=[],this.formattedWords=0,this.containLinks=!1,this.stashedNumber=!1,this.currentItem=null}shouldStash(t){return this.firstY||(this.firstY=t.y),this.currentItem=t,!0}onPushOnStash(t){this.stashedNumber=o(t.text.trim())}doMatchesStash(t,e){if(this.fontToFormats.get(t.font)!==this.fontToFormats.get(e.font))return!1;const s=o(e.text.trim());return this.stashedNumber===s}doFlushStash(t,e){if(this.stashedNumber){const s=t.map(t=>t.text).join("").trim();t[0].y>this.firstY?(e.push(new S({string:`${s}`,type:M.FOOTNOTE_LINK})),this.footnoteLinks.push(parseInt(s))):this.currentItem&&this.currentItem.y<t[0].y?(e.push(new S({string:`${s}`,type:M.FOOTNOTE})),this.footnotes.push(s)):this.copyStashItemsAsText(t,e)}else this.copyStashItemsAsText(t,e)}copyStashItemsAsText(t,e){const s=this.fontToFormats.get(t[0].font);e.push(...this.itemsToWords(t,s))}itemsToWords(t,e){var s,n;const o=(s="",n=null,t.forEach(t=>{var e=t.text;s.endsWith(" ")||e.startsWith(" ")||(n?t.x-n.x-n.width>5&&(s+=" "):i(t.text)&&(e+=" ")),s+=e,n=t}),s).split(" "),r=e?D[e]:null;return o.filter(t=>t.trim().length>0).map(t=>{var e=null;return t.startsWith("http:")?(this.containLinks=!0,e=M.LINK):t.startsWith("www.")&&(this.containLinks=!0,t=`http://${t}`,e=M.LINK),r&&this.formattedWords++,new S({string:t,type:e,format:r})})}}class U extends L{fontMap;constructor(t){super("Calculate Global Stats"),this.fontMap=t}transform(t){const e={},s={};var n,o=0;t.pages.forEach(t=>{t.items.forEach(t=>{t.height&&(e[t.height]=e[t.height]?e[t.height]+1:1,s[t.font]=s[t.font]?s[t.font]+1:1,t.height>o&&(o=t.height,n=t.font))})});const r=parseInt(A(e)),a=A(s),i={};t.pages.forEach(t=>{var e;t.items.forEach(t=>{if(t.height===r&&t.text.trim().length>0){if(e&&t.y!==e.y){const s=e.y-t.y;s>0&&(i[s]=i[s]?i[s]+1:1)}e=t}else e=null})});const h=parseInt(A(i)),l=[],c=new Map;this.fontMap?.forEach(function(t,e){l.push(e+" = "+t.name);const s=t.name.toLowerCase();var o=null;e===a?o=null:s.includes("bold")&&(s.includes("oblique")||s.includes("italic"))?o=D.BOLD_OBLIQUE:s.includes("bold")?o=D.BOLD:s.includes("oblique")||s.includes("italic")?o=D.OBLIQUE:s===n&&(o=D.BOLD),o&&c.set(e,o.name)}),l.sort();const m=t.pages.map(t=>({...t,items:t.items.map(t=>({...t}))}));return new I({...t,pages:m,globals:{mostUsedHeight:r,mostUsedFont:a,mostUsedDistance:h,maxHeight:o,maxHeightFont:n,fontToFormats:c},messages:["Items per height: "+JSON.stringify(e),"Items per font: "+JSON.stringify(s),"Items per distance: "+JSON.stringify(i),"Fonts:"+JSON.stringify(l)]})}}function A(t){var e=0,s="";return Object.keys(t).map(n=>{(!s||t[n]>e)&&(e=t[n],s=n)}),s}class P extends x{constructor(t){if(super(t,k.name),this.constructor===P)throw new TypeError("Can not construct abstract class.")}completeTransform(t){return t.messages=[],t.pages.forEach(t=>{t.items=t.items.filter(t=>!t.annotation||t.annotation!==T),t.items.forEach(t=>t.annotation=null)}),t}}class W{mostUsedDistance;constructor(t){this.mostUsedDistance=t.mostUsedDistance||12}group(t){const e=[];var s=[];return t.forEach(t=>{s.length>0&&Math.abs(s[0].y-t.y)>=this.mostUsedDistance/2&&(e.push(s),s=[]),s.push(t)}),e.push(s),e.forEach(t=>{N(t)}),e}}const j={H1:{name:"H1",headline:!0,headlineLevel:1,toText:t=>"<h1>"+$(t.items,!0)+"</h1>"},H2:{name:"H2",headline:!0,headlineLevel:2,toText:t=>"<h2>"+$(t.items,!0)+"</h2>"},H3:{name:"H3",headline:!0,headlineLevel:3,toText:t=>"<h3>"+$(t.items,!0)+"</h3>"},H4:{name:"H4",headline:!0,headlineLevel:4,toText:t=>"<h4>"+$(t.items,!0)+"</h4>"},H5:{name:"H5",headline:!0,headlineLevel:5,toText:t=>"<h5>"+$(t.items,!0)+"</h5>"},H6:{name:"H6",headline:!0,headlineLevel:6,toText:t=>"<h6>"+$(t.items,!0)+"</h6>"},TOC:{name:"TOC",mergeToBlock:!0,toText:t=>$(t.items,!0)},FOOTNOTES:{name:"FOOTNOTES",mergeToBlock:!0,mergeFollowingNonTypedItems:!0,toText:t=>"<p>"+$(t.items,!1)+"</p>"},CODE:{name:"CODE",mergeToBlock:!0,toText:t=>"<code>"+$(t.items,!0)+"</code>"},LIST:{name:"LIST",mergeToBlock:!1,mergeFollowingNonTypedItemsWithSmallDistance:!0,toText:t=>"<ul>\n"+$(t.items,!1)+"</ul>"},PARAGRAPH:{name:"PARAGRAPH",toText:t=>"<p>"+$(t.items,!1)+"</p>"}};function $(t,e){var s,n="";const o=()=>{n+=s.endSymbol,s=null};return t.forEach((r,a)=>{var i;r&&(r.words.forEach((t,r)=>{const a=t.type,i=t.format;var h;!s||i&&i===s||o(),r>0&&(!a||!a.attachWithoutWhitespace)&&(1!==(h=t.string).length||"."!==h[0]&&"!"!==h[0]&&"?"!==h[0])&&(n+=" "),!i||s||e||(n+=(s=i).startSymbol),!a||e&&!a.plainTextFormat?n+=t.string:n+=a.toText(t.string)}),!s||a!==t.length-1&&(0===(i=t[a+1]).words.length?null:i.words[0].format)===s||o(),n+="\n")}),n}j.enumValueOf=function(t){return j[t]},j.isHeadline=function(t){return t&&2===t.name.length&&"H"===t.name[0]},j.blockToText=function(t){return t.type?t.type.toText(t):$(t.items,!1)},j.headlineByLevel=function(t){return 1===t?j.H1:2===t?j.H2:3===t?j.H3:4===t?j.H4:5===t?j.H5:(t>6&&console.warn("Unsupported headline level: "+t+" (supported are 1-6), defaulting to level 6"),j.H6)};class q extends P{constructor(){super("Compact To Lines")}transform(t){const{mostUsedDistance:e,fontToFormats:s}=t.globals,n=[],o=[];var r=0,a=0;const i=new W({mostUsedDistance:e}),h=new H(s);return t.pages.forEach(t=>{if(t.items.length>0){const e=[];i.group(t.items).forEach(s=>{const i=h.compact(s);if(s.length>1&&(i.annotation=E,s.forEach(t=>{t.annotation=T,e.push(new k({...t}))})),0===i.words.length&&(i.annotation=T),e.push(i),i.parsedElements.formattedWords&&(a+=i.parsedElements.formattedWords),i.parsedElements.containLinks&&r++,i.parsedElements.footnoteLinks.length>0){const e=i.parsedElements.footnoteLinks.map(e=>({footnoteLink:e,page:t.index+1}));o.push.apply(o,e)}if(i.parsedElements.footnotes.length>0){i.type=j.FOOTNOTES;const e=i.parsedElements.footnotes.map(e=>({footnote:e,page:t.index+1}));n.push.apply(n,e)}}),t.items=e}}),new I({...t,messages:["Detected "+a+" formatted words","Found "+r+" links","Detected "+o.length+" footnotes links","Detected "+n.length+" footnotes"]})}}function Y(t){var e=0;if(0===t.trim().length)return e;for(var s=0;s<t.length;s++){const o=t.charCodeAt(s);n(o)||32===o||160===o||(e=(e<<5)-e+o,e|=0)}return e}class R extends P{constructor(){super("Remove Repetitive Elements")}transform(t){const e=[],s={},n={};t.pages.forEach(t=>{const o=t.items.reduce((t,e)=>(e.y<t.minY?(t.minElements=[e],t.minY=e.y):e.y===t.minY&&t.minElements.push(e),e.y>t.maxY?(t.maxElements=[e],t.maxY=e.y):e.y===t.maxY&&t.maxElements.push(e),t),{minY:999,maxY:0,minElements:[],maxElements:[]}),r=Y(o.minElements.reduce((t,e)=>t+e.text().toUpperCase(),"")),a=Y(o.maxElements.reduce((t,e)=>t+e.text().toUpperCase(),""));e.push({minElements:o.minElements,maxElements:o.maxElements,minLineHash:r,maxLineHash:a}),s[r]=s[r]?s[r]+1:1,n[a]=n[a]?n[a]+1:1});var o=0,r=0;return t.pages.forEach((a,i)=>{s[e[i].minLineHash]>=Math.max(3,2*t.pages.length/3)&&(e[i].minElements.forEach(t=>{t.annotation=T}),r++),n[e[i].maxLineHash]>=Math.max(3,2*t.pages.length/3)&&(e[i].maxElements.forEach(t=>{t.annotation=T}),o++)}),new I({...t,messages:["Removed Header: "+o,"Removed Footers: "+r]})}}class V extends P{constructor(){super("Vertical to Horizontal Text")}transform(t){var e=0;return t.pages.forEach(t=>{const s=new K;s.consumeAll(t.items),t.items=s.complete(),e+=s.foundVerticals}),new I({...t,messages:["Converted "+e+" verticals"]})}}class K extends F{foundVerticals;constructor(){super(),this.foundVerticals=0}shouldStash(t){return 1===t.words.length&&1===t.words[0].string.length}doMatchesStash(t,e){return t.y-e.y>5&&t.words[0].type===e.words[0].type}doFlushStash(t,e){if(t.length>5){var s=[],n=999,o=0,r=0,a=0;t.forEach(t=>{t.annotation=T,e.push(t),s.push(t.words[0]),n=Math.min(n,t.x),o=Math.max(o,t.y),r+=t.width,a=Math.max(a,t.height)}),e.push(new k({...t[0],x:n,y:o,width:r,height:a,words:s,annotation:E})),this.foundVerticals++}else e.push(...t)}}class J{headlineCharCodes;stackedLineItems;stackedChars;constructor(t){this.headlineCharCodes=a(t.headline),this.stackedLineItems=[],this.stackedChars=0}consume(t){const e=a(t.text());if(this.matchAll(e)){if(this.stackedLineItems.push(t),this.stackedChars+=e.length,this.stackedChars===this.headlineCharCodes.length)return this.stackedLineItems}else this.stackedChars>0&&(this.stackedChars=0,this.stackedLineItems=[],this.consume(t));return null}matchAll(t){for(var e=0;e<t.length;e++){const s=this.headlineCharCodes[this.stackedChars+e];if(t[e]!==s)return!1}return!0}}class Q extends P{constructor(){super("Detect TOC")}transform(t){const e=[],s=Math.min(20,t.pages.length),a=new G;var i,h,c=[];t.pages.slice(0,s).forEach(t=>{var s=0;const l=new Set,m=[];var u,f;if(t.items.filter(t=>t.words.includes(t=>r(t.string,"."))).forEach(t=>{var e=t.words.filter(t=>!r(t.string,"."));const a=[];for(;e.length>0&&o(e[e.length-1].string);){const t=e.pop();a.unshift(t.string)}if(0===a.length&&e.length>0){const t=e[e.length-1];for(;n(t.string.charCodeAt(t.string.length-1));)a.unshift(t.string.charAt(t.string.length-1)),t.string=t.string.substring(0,t.string.length-1)}var i=a.length>0;i?(i=!0,u&&(e.push(...u),u=null),m.push(new X({pageNumber:parseInt(a.join("")),lineItem:new k({...t,words:e})})),s++):h?(u&&l.add(f),u=e,f=t):h=t}),100*s/t.items.length>75){e.push(t.index+1),i=t,a.levelPageItems(m),c.push(...m);const s=[];t.items.forEach(t=>{l.has(t)||(t.annotation=T),s.push(t),t===h&&s.push(new k({...t,type:j.H2,annotation:E}))}),t.items=s}else h=null});var m=c.length;const u=[],f=[],p={};if(e.length>0){c.forEach(t=>{i.items.push(new k({words:[new S({string:" ".repeat(3*t.level)+"-"})].concat(t.lineItem.words),type:j.TOC,annotation:E}))});const e=function(t,e){for(var s of e){const e=_(t,s.lineItem.text());if(e)return e.index-s.pageNumber}return null}(t.pages.filter(t=>t.index>i.index),c);c.forEach(s=>{var n,o=t.pages[s.pageNumber+e];o&&((n=z(o,s.lineItem.text()))||(o=t.pages[s.pageNumber+e+1])&&(n=z(o,s.lineItem.text()))),n?function(t,e,s,n){s.headlineItems.forEach(t=>t.annotation=T);const o=j.headlineByLevel(e.level+2),r=s.headlineItems.reduce((t,e)=>Math.max(t,e.height),0);t.items.splice(s.lineIndex+1,0,new k({...s.headlineItems[0],words:e.lineItem.words,height:r,type:o,annotation:E}));var a=n[o.name];a?(a.min=Math.min(a.min,r),a.max=Math.max(a.max,r)):(a={min:r,max:r},n[o.name]=a)}(o,s,n,p):u.push(s)});var d=i.index+2,g=[];const s=e=>{g.length>0&&(g.forEach(s=>{const n=j.headlineByLevel(s.level+2),o=p[n.name];if(o){const[r,a]=function(t,e,s,n,o){const r=e.lineItem.text().toUpperCase();for(var a=n;a<=o;a++){const e=t[a-1];if(e){const t=e.items.findIndex(t=>{if(!t.type&&!t.annotation&&t.height>=s.min&&t.height<=s.max){return l(r,t.text())>=.5}return!1});if(t>-1)return[a-1,t]}}return[-1,-1]}(t.pages,s,o,d,e);if(a>-1){const e=t.pages[r];e.items[a].annotation=T,e.items.splice(a+1,0,new k({...s.lineItem,type:n,annotation:E})),f.push(s)}}}),g=[])};u.length>0&&(c.forEach(t=>{u.includes(t)?g.push(t):(s(t.pageNumber),d=t.pageNumber)}),g.length>0&&s(t.pages.length))}const y=[];return y.push("Detected "+e.length+" table of content pages"),e.length>0&&(y.push("TOC headline heights: "+JSON.stringify(p)),y.push("Found TOC headlines: "+(m-u.length+f.length)+"/"+m)),u.length>0&&(y.push("Found TOC headlines (by size): "+f.map(t=>t.lineItem.text())),y.push("Missing TOC headlines: "+u.filter(t=>!f.includes(t)).map(t=>t.lineItem.text()+"=>"+t.pageNumber))),new I({...t,globals:{...t.globals,tocPages:e,headlineTypeToHeightRange:p},messages:y})}}function _(t,e){for(var s of t)if(z(s,e))return s;return null}function z(t,e){const s=new J({headline:e});var n=0;for(var o of t.items){const t=s.consume(o);if(t)return{lineIndex:n,headlineItems:t};n++}return null}class G{levelByMethod;uniqueFonts;constructor(){this.levelByMethod=null,this.uniqueFonts=[]}levelPageItems(t){if(!this.levelByMethod){if(this.calculateUniqueX(t).length>1)this.levelByMethod=this.levelByXDiff;else{const e=this.calculateUniqueFonts(t);e.length>1?(this.uniqueFonts=e,this.levelByMethod=this.levelByFont):this.levelByMethod=this.levelToZero}}this.levelByMethod(t)}levelByXDiff(t){const e=this.calculateUniqueX(t);t.forEach(t=>{t.level=e.indexOf(t.lineItem.x)})}levelByFont(t){t.forEach(t=>{t.level=this.uniqueFonts.indexOf(t.lineItem.font)})}levelToZero(t){t.forEach(t=>{t.level=0})}calculateUniqueX(t){var e=t.reduce(function(t,e){return t.indexOf(e.lineItem.x)<0&&t.push(e.lineItem.x),t},[]);return e.sort((t,e)=>t-e),e}calculateUniqueFonts(t){return t.reduce(function(t,e){return t.indexOf(e.lineItem.font)<0&&t.push(e.lineItem.font),t},[])}}class X{lineItem;pageNumber;level;constructor(t){this.lineItem=t.lineItem,this.pageNumber=t.pageNumber,this.level=0}}class Z extends P{constructor(){super("Detect List Items")}transform(t){var e=0,s=0;return t.pages.forEach(t=>{const n=[];t.items.forEach(t=>{if(n.push(t),!t.type){var o=t.text();if(i(t.words[0].string))if(e++,"-"===t.words[0].string)t.annotation=b,t.type=j.LIST;else{t.annotation=T;const e=t.words.map(t=>new S({...t}));e[0].string="-",n.push(new k({...t,words:e,annotation:E,type:j.LIST}))}else/^[\s]*[\d]*[.][\s].*$/g.test(o)&&(s++,t.annotation=b,t.type=j.LIST)}}),t.items=n}),new I({...t,messages:["Detected "+e+" plain list items.","Detected "+s+" numbered list items."]})}}class tt extends P{constructor(){super("Detect Headers")}transform(t){const{tocPages:e,headlineTypeToHeightRange:s,mostUsedHeight:n,mostUsedDistance:o,mostUsedFont:r,maxHeight:a}=t.globals,i=e.length>0;var l=0;const c=function(t,e){const s=new Set;return t.forEach(t=>{t.items.forEach(n=>{n.type||n.height!==e||s.add(t)})}),s}(t.pages,a),m=n+(a-n)/4;if(c.forEach(t=>{t.items.forEach(t=>{const e=t.height;!t.type&&e>m&&(t.type=e===a?j.H1:j.H2,t.annotation=b,l++)})}),i){Object.keys(s).forEach(e=>{var o=s[e];o.max>n&&t.pages.forEach(t=>{t.items.forEach(t=>{t.type||t.height!==o.max||(t.annotation=b,t.type=j.enumValueOf(e),l++)})})})}else{const e=[];t.pages.forEach(t=>{t.items.forEach(t=>{!t.type&&t.height>n&&!h(t.text())&&(e.includes(t.height)||e.push(t.height))})}),e.sort((t,e)=>e-t),e.forEach((e,s)=>{if(s+2<=6){const n=j.headlineByLevel(2+s);t.pages.forEach(t=>{t.items.forEach(t=>{t.type||t.height!==e||h(t.text())||(l++,t.annotation=b,t.type=n)})})}})}var u=1;if(t.pages.forEach(t=>{t.items.forEach(t=>{t.type&&t.type.headline&&(u=Math.max(u,t.type.headlineLevel))})}),u<6){const e=j.headlineByLevel(u+1);t.pages.forEach(t=>{var s;t.items.forEach(t=>{!t.type&&t.height===n&&t.font!==r&&(!s||s.y<t.y||s.type&&s.type.headline||s.y-t.y>2*o)&&t.text()===t.text().toUpperCase()&&(l++,t.annotation=b,t.type=e),s=t})})}return new I({...t,messages:["Detected "+l+" headlines."]})}}class et extends d{items;constructor(t){super(t),this.items=[],t.items&&t.items.forEach(t=>this.addItem(t))}addItem(t){if(this.type&&t.type&&this.type!==t.type)throw new Error(`Adding item of type ${t.type.name} to block of type ${this.type.name}`);this.type||(this.type=t.type),t.parsedElements&&(this.parsedElements?this.parsedElements.add(t.parsedElements):this.parsedElements=t.parsedElements);const e=new k({...t});e.type=null,this.items.push(e)}}class st extends x{constructor(t){if(super(t,et.name),this.constructor===st)throw new TypeError("Can not construct abstract class.")}completeTransform(t){return t.messages=[],t.pages.forEach(t=>{t.items=t.items.filter(t=>!t.annotation||t.annotation!==T),t.items.forEach(t=>t.annotation=null)}),t}}class nt extends st{constructor(){super("Gather Blocks")}transform(t){const{mostUsedDistance:e}=t.globals;var s=0,n=0;return t.pages.map(t=>{n+=t.items.length;const o=[];var r=new et({});const a=()=>{r.items.length>1&&(r.annotation=b),o.push(r),r=new et({}),s++};var i=function(t){var e=999;return t.forEach(t=>{e=Math.min(e,t.x)}),999===e?null:e}(t.items);t.items.forEach(t=>{r.items.length>0&&function(t,e,s,n){if(t.type&&t.type.mergeFollowingNonTypedItems&&!e.type)return!1;const o=t.items[t.items.length-1],r=function(t,e,s,n){const o=t.y-e.y;if(o<0-n/2)return!0;var r=n+1;t.x>s&&e.x>s&&(r=n+n/2);if(o>r)return!0;return!1}(o,e,s,n);if(t.type&&t.type.mergeFollowingNonTypedItemsWithSmallDistance&&!e.type&&!r)return!1;if(e.type!==t.type)return!0;return e.type?!e.type.mergeToBlock:r}(r,t,i,e)&&a(),r.addItem(t)}),r.items.length>0&&a(),t.items=o}),new I({...t,messages:["Gathered "+s+" blocks out of "+n+" line items"]})}}class ot extends st{constructor(){super("$1")}transform(t){const e=t.globals.mostUsedHeight??0;var s=0;return t.pages.forEach(t=>{var n=function(t){var e=999;return t.forEach(t=>{t.items.forEach(t=>{e=Math.min(e,t.x)})}),999===e?null:e}(t.items);t.items.forEach(t=>{!t.type&&function(t,e,s){if(0===e.length)return!1;if(1===e.length)return e[0].x>t&&e[0].height<=s+1;for(var n of e)if(n.x===t)return!1;return!0}(n,t.items,e)&&(t.annotation=b,t.type=j.CODE,s++)})}),new I({...t,messages:["Detected "+s+" code/quote items."]})}}class rt extends st{constructor(){super("Level Lists")}transform(t){var e=0,s=0;return t.pages.forEach(t=>{t.items.filter(t=>t.type===j.LIST).forEach(t=>{var n,o=0;const r={};var a=!1;t.items.forEach(t=>{n?t.x>n?(o++,r[t.x]=o):t.x<n&&(o=r[t.x]):r[t.x]=0,o>0&&(t.words=[new S({string:" ".repeat(3*o)})].concat(t.words),a=!0),n=t.x}),e++,a?(s++,t.annotation=O):t.annotation=v})}),new I({...t,messages:["Modified "+s+" / "+e+" list blocks."]})}}class at extends x{constructor(){super("To Text Blocks","TextBlock")}transform(t){return t.pages.forEach(t=>{const e=[];t.items.forEach(t=>{const s=t.type?t.type.name:"Unknown";e.push({category:s,text:j.blockToText(t)})}),t.items=e}),new I({...t})}}class it extends x{constructor(){super("To HTML","String")}transform(t){return t.pages.forEach(t=>{var e="";t.items.forEach(t=>{let s;s="TOC"===t.category?t.text:t.text.replace(/(\r\n|\n|\r)/gm,"\n"),"LIST"!==t.category&&(s=s.split("- ").join("")),"CODE"===t.category&&(s=s.split("<code>").join("").split("</code>").join("")),e+=`<p>${s}</p>\n\n`}),t.items=[e]}),new I({...t})}}exports.convertPDFToHTML=async function(e,s={}){var{addPageNumbers:n=!1,addCitation:o=!0}=s,r="string"==typeof e?await t(e,{responseType:"arraybuffer",timeout:10}):e;let a;try{let{resolvePDFJS:t}=await import("https://cdn.jsdelivr.net/npm/pdfjs-serverless@1.1.0/+esm");const{getDocument:e}=await t();a=await e({data:new Uint8Array(r),useSystemFonts:!0,verbosity:0}).promise}catch(b){return{error:b.message}}const i=[...Array(a.numPages).keys()].map(t=>new y({index:t}));let h,l={};for(let t=1;t<=a.numPages;t++){const e=await a.getPage(t),s=await e.getTextContent();if(!(Object.keys(l).length<10)){h=f(l);break}l=u(l,e.pageNumber-1,s.items)}let c=h?h.pageNum:0;for(let t=1;t<=a.numPages;t++){const e=await a.getPage(t);await e.getOperatorList();const s=1,n=e.getViewport({scale:s});let o=await e.getTextContent();h&&e.pageIndex>=h.pageIndex&&(o=p(o,c),c++);const r=o.items.map(t=>{const e=[1,0,0,1,0,0];for(let r=0;r<6;r++)e[r]+=t.transform[r]*n.transform[r%2?3:0],r%2&&(e[r+1]+=t.transform[r]*n.transform[1]);const s=Math.sqrt(e[2]*e[2]+e[3]*e[3]),o=t.height/s;return new g({x:Math.round(t.transform[4]),y:Math.round(t.transform[5]),width:Math.round(t.width),height:Math.round(o<=1?t.height:o),text:t.str,font:t.fontName})});i[e.pageNumber-1].items=r}var m=new I({pages:i});let d,x=[new U,new q,new R,new V,new Q,new tt,new Z,new nt,new ot,new rt,new at,new it];x?.forEach(t=>{d&&(m=d.completeTransform(m)),m=t.transform(m),d=t});var w=m.pages.reduce((t,e,s)=>t+`<p id="page-${s+1}">${n?` [${s+1}] `:""}${e.items.join('</p><p id="page-'+s+'">')}</p>`,"");if(o){var E=await a.getMetadata(),{Author:T,Title:v}=E.info;v=w.slice(0,400).match(/<h[0-9]>(.*?)<\/h[0-9]>/)?.[1]||v}return{author:T,title:v,html:w,format:"pdf"}};
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function t(t){for(;32===t.charCodeAt(0);)t=t.substring(1,t.length);return t}function e(t){for(;32===t.charCodeAt(t.length-1);)t=t.substring(0,t.length-1);return t}function s(t){return t>=48&&t<=57}function n(t){for(var e=0;e<t.length;e++)if(!s(t.charCodeAt(e)))return!1;return!0}function o(t,e){const s=e.charCodeAt(0);for(var n=0;n<t.length;n++)if(t.charCodeAt(n)!==s)return!1;return!0}function r(t){return function(t){const e=[];for(var s=0;s<t.length;s++)e.push(t.charCodeAt(s));return e}(t=t.toUpperCase()).filter(t=>32!==t&&9!==t&&46!==t)}function a(t){if(t.length>1)return!1;const e=t.charAt(0);return"-"===e||"•"===e||"–"===e}function i(t){return/^[\s]*[-•–][\s].*$/g.test(t)}function h(t,e){const s=new Set(t.toUpperCase().split(" ")),n=new Set(e.toUpperCase().split(" "));return new Set([...s].filter(t=>n.has(t))).size/Math.max(s.size,n.size)}var l=(t,e,s)=>Math.floor(t/e*s),c=(s,o,r)=>{for(const{str:a}of s){const s=e(t(a));n(s)&&(o[r]||(o[r]=[]),o[r].push(Number(s)))}return o},m=(t,e,s)=>{const n=l(1,6,s.length),o=l(5,6,s.length),r=c(s.slice(0,n),t,e);return c(s.slice(o),r,e)},u=t=>{let e=0;const s=Object.keys(t);if(0!==s.length&&1!==s.length)for(let n=0;n<s.length-1;n++){const o=t[s[n]],r=t[s[n+1]],a=e;for(let t=0;t<o.length&&e<2;t++)for(let a=0;a<r.length&&e<2;a++){const i=Number(s[n+1])-Number(s[n]);(o[t]+1===r[a]||i>1&&o[t]+i===r[a])&&e++}let i=n>0?Object.entries(t)[n-1]:Object.entries(t)[n];if(a===e)e=0,i=Object.entries(t)[n];else if(e>=2)return{pageIndex:Number(i[0]),pageNum:i[1][0]}}},f=(t,e)=>{const s={items:[...t.items]},n=l(1,6,s.items.length),o=l(5,6,s.items.length);return s.items=s.items.filter((t,r)=>{const a=r>0&&r<n,i=r>o&&r<s.items.length;return a||i?Number(t.str)!==Number(e):t}),s},p=class t{type;annotation;parsedElements;constructor(e){if(this.constructor===t)throw new TypeError("Can not construct abstract class.");this.type=e.type??null,this.annotation=e.annotation??null,this.parsedElements=e.parsedElements??null}},d=class extends p{x;y;width;height;text;font;lineFormat;unopenedFormat;unclosedFormat;constructor(t){super(t),this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height,this.text=t.text,this.font=t.font,this.lineFormat=t.lineFormat??null,this.unopenedFormat=t.unopenedFormat??null,this.unclosedFormat=t.unclosedFormat??null}},g=class{index;items;constructor(t){this.index=t.index,this.items=t.items||[]}},y=class t{name;itemType;constructor(e,s){if(this.constructor===t)throw new TypeError("Can not construct abstract class.");if(this.transform===t.prototype.transform)throw new TypeError("Please implement abstract method 'transform()'.");this.name=e,this.itemType=s}transform(t){throw new TypeError("Do not call abstract method foo from child.")}completeTransform(t){return t.messages=[],t}},w=class{category;color;constructor(t){this.category=t.category,this.color=t.color}},x=new w({category:"Added",color:"green"}),E=new w({category:"Removed",color:"red"}),T=new w({category:"Unchanged",color:"brown"}),v=new w({category:"Detected",color:"green"}),b=new w({category:"Modified",color:"green"}),L=class t extends y{constructor(e){if(super(e,d.name),this.constructor===t)throw new TypeError("Can not construct abstract class.")}completeTransform(t){return t.messages=[],t.pages.forEach(t=>{t.items=t.items.filter(t=>!t.annotation||t.annotation!==E),t.items.forEach(t=>t.annotation=null)}),t}},I=class{pages;globals;messages;constructor(t){this.pages=t.pages||[],this.globals=t.globals||{},this.messages=t.messages||[]}},O=class{string;type;format;constructor(t){this.string=t.string,this.type=t.type??null,this.format=t.format??null}},S=class extends p{x;y;width;height;words;constructor(t){super(t),this.x=t.x??0,this.y=t.y??0,this.width=t.width??0,this.height=t.height??0,this.words=t.words||[],t.text&&!t.words&&(this.words=t.text.split(" ").filter(t=>t.trim().length>0).map(t=>new O({string:t})))}text(){return this.wordStrings().join(" ")}wordStrings(){return this.words.map(t=>t.string)}},k=class t{results;stash;constructor(){if(this.constructor===t)throw new TypeError("Can not construct abstract class.");this.results=[],this.stash=[]}consumeAll(t){t.forEach(t=>this.consume(t))}consume(t){this.shouldStash(t)?(this.matchesStash(t)||this.flushStash(),this.pushOnStash(t)):(this.stash.length>0&&this.flushStash(),this.results.push(t))}pushOnStash(t){this.onPushOnStash(t),this.stash.push(t)}complete(){return this.stash.length>0&&this.flushStash(),this.results}matchesStash(t){if(0===this.stash.length)return!0;const e=this.stash[this.stash.length-1];return this.doMatchesStash(e,t)}flushStash(){this.stash.length>0&&(this.doFlushStash(this.stash,this.results),this.stash=[])}onPushOnStash(t){}shouldStash(t){throw new TypeError(" Do not call abstract method foo from child."+t)}doMatchesStash(t,e){throw new TypeError(" Do not call abstract method foo from child."+t+e)}doFlushStash(t,e){throw new TypeError(" Do not call abstract method foo from child."+t+e)}},C=class{footnoteLinks;footnotes;containLinks;formattedWords;constructor(t){this.footnoteLinks=t.footnoteLinks||[],this.footnotes=t.footnotes||[],this.containLinks=t.containLinks??!1,this.formattedWords=t.formattedWords??0}add(t){this.footnoteLinks=this.footnoteLinks.concat(t.footnoteLinks),this.footnotes=this.footnotes.concat(t.footnotes),this.containLinks=this.containLinks||t.containLinks,this.formattedWords+=t.formattedWords}};function F(t){t.sort((t,e)=>t.x-e.x)}var N={BOLD:{name:"BOLD",startSymbol:"<strong>",endSymbol:"</strong>"},OBLIQUE:{name:"OBLIQUE",startSymbol:"<em>",endSymbol:"</em>"},BOLD_OBLIQUE:{name:"BOLD_OBLIQUE",startSymbol:"<strong><em>",endSymbol:"</em></strong>"}},D={LINK:{name:"LINK",toText:t=>`<a href="${t}">${t}</a>`},FOOTNOTE_LINK:{name:"FOOTNOTE_LINK",attachWithoutWhitespace:!0,plainTextFormat:!0,toText:t=>`<sup><a href="#${t}">${t}</a></sup>`},FOOTNOTE:{name:"FOOTNOTE",toText:t=>`<p id="${t}">^${t}</p>`}},H=class{fontToFormats;constructor(t){this.fontToFormats=t}compact(t){F(t);const e=new M(this.fontToFormats);e.consumeAll(t.map(t=>new d({...t})));const s=e.complete();var n=0,o=0;return t.forEach(t=>{n=Math.max(n,t.height),o+=t.width}),new S({x:t[0].x,y:t[0].y,height:n,width:o,words:s,parsedElements:new C({footnoteLinks:e.footnoteLinks,footnotes:e.footnotes,containLinks:e.containLinks,formattedWords:e.formattedWords})})}},M=class extends k{fontToFormats;footnoteLinks;footnotes;formattedWords;containLinks;stashedNumber;firstY;currentItem;constructor(t){super(),this.fontToFormats=t,this.footnoteLinks=[],this.footnotes=[],this.formattedWords=0,this.containLinks=!1,this.stashedNumber=!1,this.currentItem=null}shouldStash(t){return this.firstY||(this.firstY=t.y),this.currentItem=t,!0}onPushOnStash(t){this.stashedNumber=n(t.text.trim())}doMatchesStash(t,e){if(this.fontToFormats.get(t.font)!==this.fontToFormats.get(e.font))return!1;const s=n(e.text.trim());return this.stashedNumber===s}doFlushStash(t,e){if(this.stashedNumber){const s=t.map(t=>t.text).join("").trim();t[0].y>this.firstY?(e.push(new O({string:`${s}`,type:D.FOOTNOTE_LINK})),this.footnoteLinks.push(parseInt(s))):this.currentItem&&this.currentItem.y<t[0].y?(e.push(new O({string:`${s}`,type:D.FOOTNOTE})),this.footnotes.push(s)):this.copyStashItemsAsText(t,e)}else this.copyStashItemsAsText(t,e)}copyStashItemsAsText(t,e){const s=this.fontToFormats.get(t[0].font);e.push(...this.itemsToWords(t,s))}itemsToWords(t,e){const s=(n=t,o="",r=null,n.forEach(t=>{var e=t.text;o.endsWith(" ")||e.startsWith(" ")||(r?t.x-r.x-r.width>5&&(o+=" "):a(t.text)&&(e+=" ")),o+=e,r=t}),o).split(" ");var n,o,r;const i=e?N[e]:null;return s.filter(t=>t.trim().length>0).map(t=>{var e=null;return t.startsWith("http:")?(this.containLinks=!0,e=D.LINK):t.startsWith("www.")&&(this.containLinks=!0,t=`http://${t}`,e=D.LINK),i&&this.formattedWords++,new O({string:t,type:e,format:i})})}};var B=class extends L{fontMap;constructor(t){super("Calculate Global Stats"),this.fontMap=t}transform(t){const e={},s={};var n,o=0;t.pages.forEach(t=>{t.items.forEach(t=>{t.height&&(e[t.height]=e[t.height]?e[t.height]+1:1,s[t.font]=s[t.font]?s[t.font]+1:1,t.height>o&&(o=t.height,n=t.font))})});const r=parseInt(U(e)),a=U(s),i={};t.pages.forEach(t=>{var e;t.items.forEach(t=>{if(t.height===r&&t.text.trim().length>0){if(e&&t.y!==e.y){const s=e.y-t.y;s>0&&(i[s]=i[s]?i[s]+1:1)}e=t}else e=null})});const h=parseInt(U(i)),l=[],c=new Map;this.fontMap?.forEach(function(t,e){l.push(e+" = "+t.name);const s=t.name.toLowerCase();var o=null;e===a?o=null:s.includes("bold")&&(s.includes("oblique")||s.includes("italic"))?o=N.BOLD_OBLIQUE:s.includes("bold")?o=N.BOLD:s.includes("oblique")||s.includes("italic")?o=N.OBLIQUE:s===n&&(o=N.BOLD),o&&c.set(e,o.name)}),l.sort();const m=t.pages.map(t=>({...t,items:t.items.map(t=>({...t}))}));return new I({...t,pages:m,globals:{mostUsedHeight:r,mostUsedFont:a,mostUsedDistance:h,maxHeight:o,maxHeightFont:n,fontToFormats:c},messages:["Items per height: "+JSON.stringify(e),"Items per font: "+JSON.stringify(s),"Items per distance: "+JSON.stringify(i),"Fonts:"+JSON.stringify(l)]})}};function U(t){var e=0,s="";return Object.keys(t).map(n=>{(!s||t[n]>e)&&(e=t[n],s=n)}),s}var A=class t extends y{constructor(e){if(super(e,S.name),this.constructor===t)throw new TypeError("Can not construct abstract class.")}completeTransform(t){return t.messages=[],t.pages.forEach(t=>{t.items=t.items.filter(t=>!t.annotation||t.annotation!==E),t.items.forEach(t=>t.annotation=null)}),t}},W=class{mostUsedDistance;constructor(t){this.mostUsedDistance=t.mostUsedDistance||12}group(t){const e=[];var s=[];return t.forEach(t=>{s.length>0&&Math.abs(s[0].y-t.y)>=this.mostUsedDistance/2&&(e.push(s),s=[]),s.push(t)}),e.push(s),e.forEach(t=>{F(t)}),e}},P={H1:{name:"H1",headline:!0,headlineLevel:1,toText:t=>"<h1>"+$(t.items,!0)+"</h1>"},H2:{name:"H2",headline:!0,headlineLevel:2,toText:t=>"<h2>"+$(t.items,!0)+"</h2>"},H3:{name:"H3",headline:!0,headlineLevel:3,toText:t=>"<h3>"+$(t.items,!0)+"</h3>"},H4:{name:"H4",headline:!0,headlineLevel:4,toText:t=>"<h4>"+$(t.items,!0)+"</h4>"},H5:{name:"H5",headline:!0,headlineLevel:5,toText:t=>"<h5>"+$(t.items,!0)+"</h5>"},H6:{name:"H6",headline:!0,headlineLevel:6,toText:t=>"<h6>"+$(t.items,!0)+"</h6>"},TOC:{name:"TOC",mergeToBlock:!0,toText:t=>$(t.items,!0)},FOOTNOTES:{name:"FOOTNOTES",mergeToBlock:!0,mergeFollowingNonTypedItems:!0,toText:t=>"<p>"+$(t.items,!1)+"</p>"},CODE:{name:"CODE",mergeToBlock:!0,toText:t=>"<code>"+$(t.items,!0)+"</code>"},LIST:{name:"LIST",mergeToBlock:!1,mergeFollowingNonTypedItemsWithSmallDistance:!0,toText:t=>"<ul>\n"+$(t.items,!1)+"</ul>"},PARAGRAPH:{name:"PARAGRAPH",toText:t=>"<p>"+$(t.items,!1)+"</p>"}};function $(t,e){var s,n="";const o=()=>{n+=s.endSymbol,s=null};return t.forEach((r,a)=>{var i;r&&(r.words.forEach((t,r)=>{const a=t.type,i=t.format;var h;!s||i&&i===s||o(),r>0&&(!a||!a.attachWithoutWhitespace)&&(1!==(h=t.string).length||"."!==h[0]&&"!"!==h[0]&&"?"!==h[0])&&(n+=" "),!i||s||e||(n+=(s=i).startSymbol),!a||e&&!a.plainTextFormat?n+=t.string:n+=a.toText(t.string)}),!s||a!==t.length-1&&(0===(i=t[a+1]).words.length?null:i.words[0].format)===s||o(),n+="\n")}),n}P.enumValueOf=function(t){return P[t]},P.isHeadline=function(t){return t&&2===t.name.length&&"H"===t.name[0]},P.blockToText=function(t){return t.type?t.type.toText(t):$(t.items,!1)},P.headlineByLevel=function(t){return 1===t?P.H1:2===t?P.H2:3===t?P.H3:4===t?P.H4:5===t?P.H5:(t>6&&console.warn("Unsupported headline level: "+t+" (supported are 1-6), defaulting to level 6"),P.H6)};var j=class extends A{constructor(){super("Compact To Lines")}transform(t){const{mostUsedDistance:e,fontToFormats:s}=t.globals,n=[],o=[];var r=0,a=0;const i=new W({mostUsedDistance:e}),h=new H(s);return t.pages.forEach(t=>{if(t.items.length>0){const e=[];i.group(t.items).forEach(s=>{const i=h.compact(s);if(s.length>1&&(i.annotation=x,s.forEach(t=>{t.annotation=E,e.push(new S({...t}))})),0===i.words.length&&(i.annotation=E),e.push(i),i.parsedElements.formattedWords&&(a+=i.parsedElements.formattedWords),i.parsedElements.containLinks&&r++,i.parsedElements.footnoteLinks.length>0){const e=i.parsedElements.footnoteLinks.map(e=>({footnoteLink:e,page:t.index+1}));o.push.apply(o,e)}if(i.parsedElements.footnotes.length>0){i.type=P.FOOTNOTES;const e=i.parsedElements.footnotes.map(e=>({footnote:e,page:t.index+1}));n.push.apply(n,e)}}),t.items=e}}),new I({...t,messages:["Detected "+a+" formatted words","Found "+r+" links","Detected "+o.length+" footnotes links","Detected "+n.length+" footnotes"]})}};function q(t){var e=0;if(0===t.trim().length)return e;for(var n=0;n<t.length;n++){const o=t.charCodeAt(n);s(o)||32===o||160===o||(e=(e<<5)-e+o,e|=0)}return e}var Y=class extends A{constructor(){super("Remove Repetitive Elements")}transform(t){const e=[],s={},n={};t.pages.forEach(t=>{const o=t.items.reduce((t,e)=>(e.y<t.minY?(t.minElements=[e],t.minY=e.y):e.y===t.minY&&t.minElements.push(e),e.y>t.maxY?(t.maxElements=[e],t.maxY=e.y):e.y===t.maxY&&t.maxElements.push(e),t),{minY:999,maxY:0,minElements:[],maxElements:[]}),r=q(o.minElements.reduce((t,e)=>t+e.text().toUpperCase(),"")),a=q(o.maxElements.reduce((t,e)=>t+e.text().toUpperCase(),""));e.push({minElements:o.minElements,maxElements:o.maxElements,minLineHash:r,maxLineHash:a}),s[r]=s[r]?s[r]+1:1,n[a]=n[a]?n[a]+1:1});var o=0,r=0;return t.pages.forEach((a,i)=>{s[e[i].minLineHash]>=Math.max(3,2*t.pages.length/3)&&(e[i].minElements.forEach(t=>{t.annotation=E}),r++),n[e[i].maxLineHash]>=Math.max(3,2*t.pages.length/3)&&(e[i].maxElements.forEach(t=>{t.annotation=E}),o++)}),new I({...t,messages:["Removed Header: "+o,"Removed Footers: "+r]})}},R=class extends A{constructor(){super("Vertical to Horizontal Text")}transform(t){var e=0;return t.pages.forEach(t=>{const s=new V;s.consumeAll(t.items),t.items=s.complete(),e+=s.foundVerticals}),new I({...t,messages:["Converted "+e+" verticals"]})}},V=class extends k{foundVerticals;constructor(){super(),this.foundVerticals=0}shouldStash(t){return 1===t.words.length&&1===t.words[0].string.length}doMatchesStash(t,e){return t.y-e.y>5&&t.words[0].type===e.words[0].type}doFlushStash(t,e){if(t.length>5){var s=[],n=999,o=0,r=0,a=0;t.forEach(t=>{t.annotation=E,e.push(t),s.push(t.words[0]),n=Math.min(n,t.x),o=Math.max(o,t.y),r+=t.width,a=Math.max(a,t.height)}),e.push(new S({...t[0],x:n,y:o,width:r,height:a,words:s,annotation:x})),this.foundVerticals++}else e.push(...t)}},K=class{headlineCharCodes;stackedLineItems;stackedChars;constructor(t){this.headlineCharCodes=r(t.headline),this.stackedLineItems=[],this.stackedChars=0}consume(t){const e=r(t.text());if(this.matchAll(e)){if(this.stackedLineItems.push(t),this.stackedChars+=e.length,this.stackedChars===this.headlineCharCodes.length)return this.stackedLineItems}else this.stackedChars>0&&(this.stackedChars=0,this.stackedLineItems=[],this.consume(t));return null}matchAll(t){for(var e=0;e<t.length;e++){const s=this.headlineCharCodes[this.stackedChars+e];if(t[e]!==s)return!1}return!0}},J=class extends A{constructor(){super("Detect TOC")}transform(t){const e=[],r=Math.min(20,t.pages.length),a=new z;var i,l,c=[];t.pages.slice(0,r).forEach(t=>{var r=0;const h=new Set,m=[];var u,f;if(t.items.filter(t=>t.words.includes(t=>o(t.string,"."))).forEach(t=>{var e=t.words.filter(t=>!o(t.string,"."));const a=[];for(;e.length>0&&n(e[e.length-1].string);){const t=e.pop();a.unshift(t.string)}if(0===a.length&&e.length>0){const t=e[e.length-1];for(;s(t.string.charCodeAt(t.string.length-1));)a.unshift(t.string.charAt(t.string.length-1)),t.string=t.string.substring(0,t.string.length-1)}var i=a.length>0;i?(i=!0,u&&(e.push(...u),u=null),m.push(new G({pageNumber:parseInt(a.join("")),lineItem:new S({...t,words:e})})),r++):l?(u&&h.add(f),u=e,f=t):l=t}),100*r/t.items.length>75){e.push(t.index+1),i=t,a.levelPageItems(m),c.push(...m);const s=[];t.items.forEach(t=>{h.has(t)||(t.annotation=E),s.push(t),t===l&&s.push(new S({...t,type:P.H2,annotation:x}))}),t.items=s}else l=null});var m=c.length;const u=[],f=[],p={};if(e.length>0){c.forEach(t=>{i.items.push(new S({words:[new O({string:" ".repeat(3*t.level)+"-"})].concat(t.lineItem.words),type:P.TOC,annotation:x}))});const e=function(t,e){for(var s of e){const e=Q(t,s.lineItem.text());if(e)return e.index-s.pageNumber}return null}(t.pages.filter(t=>t.index>i.index),c);c.forEach(s=>{var n,o=t.pages[s.pageNumber+e];o&&((n=_(o,s.lineItem.text()))||(o=t.pages[s.pageNumber+e+1])&&(n=_(o,s.lineItem.text()))),n?function(t,e,s,n){s.headlineItems.forEach(t=>t.annotation=E);const o=P.headlineByLevel(e.level+2),r=s.headlineItems.reduce((t,e)=>Math.max(t,e.height),0);t.items.splice(s.lineIndex+1,0,new S({...s.headlineItems[0],words:e.lineItem.words,height:r,type:o,annotation:x}));var a=n[o.name];a?(a.min=Math.min(a.min,r),a.max=Math.max(a.max,r)):(a={min:r,max:r},n[o.name]=a)}(o,s,n,p):u.push(s)});var d=i.index+2,g=[];const s=e=>{g.length>0&&(g.forEach(s=>{const n=P.headlineByLevel(s.level+2),o=p[n.name];if(o){const[r,a]=function(t,e,s,n,o){const r=e.lineItem.text().toUpperCase();for(var a=n;a<=o;a++){const e=t[a-1];if(e){const t=e.items.findIndex(t=>!t.type&&!t.annotation&&t.height>=s.min&&t.height<=s.max&&h(r,t.text())>=.5);if(t>-1)return[a-1,t]}}return[-1,-1]}(t.pages,s,o,d,e);if(a>-1){const e=t.pages[r];e.items[a].annotation=E,e.items.splice(a+1,0,new S({...s.lineItem,type:n,annotation:x})),f.push(s)}}}),g=[])};u.length>0&&(c.forEach(t=>{u.includes(t)?g.push(t):(s(t.pageNumber),d=t.pageNumber)}),g.length>0&&s(t.pages.length))}const y=[];return y.push("Detected "+e.length+" table of content pages"),e.length>0&&(y.push("TOC headline heights: "+JSON.stringify(p)),y.push("Found TOC headlines: "+(m-u.length+f.length)+"/"+m)),u.length>0&&(y.push("Found TOC headlines (by size): "+f.map(t=>t.lineItem.text())),y.push("Missing TOC headlines: "+u.filter(t=>!f.includes(t)).map(t=>t.lineItem.text()+"=>"+t.pageNumber))),new I({...t,globals:{...t.globals,tocPages:e,headlineTypeToHeightRange:p},messages:y})}};function Q(t,e){for(var s of t)if(_(s,e))return s;return null}function _(t,e){const s=new K({headline:e});var n=0;for(var o of t.items){const t=s.consume(o);if(t)return{lineIndex:n,headlineItems:t};n++}return null}var z=class{levelByMethod;uniqueFonts;constructor(){this.levelByMethod=null,this.uniqueFonts=[]}levelPageItems(t){if(!this.levelByMethod)if(this.calculateUniqueX(t).length>1)this.levelByMethod=this.levelByXDiff;else{const e=this.calculateUniqueFonts(t);e.length>1?(this.uniqueFonts=e,this.levelByMethod=this.levelByFont):this.levelByMethod=this.levelToZero}this.levelByMethod(t)}levelByXDiff(t){const e=this.calculateUniqueX(t);t.forEach(t=>{t.level=e.indexOf(t.lineItem.x)})}levelByFont(t){t.forEach(t=>{t.level=this.uniqueFonts.indexOf(t.lineItem.font)})}levelToZero(t){t.forEach(t=>{t.level=0})}calculateUniqueX(t){var e=t.reduce(function(t,e){return t.indexOf(e.lineItem.x)<0&&t.push(e.lineItem.x),t},[]);return e.sort((t,e)=>t-e),e}calculateUniqueFonts(t){return t.reduce(function(t,e){return t.indexOf(e.lineItem.font)<0&&t.push(e.lineItem.font),t},[])}},G=class{lineItem;pageNumber;level;constructor(t){this.lineItem=t.lineItem,this.pageNumber=t.pageNumber,this.level=0}},X=class extends A{constructor(){super("Detect List Items")}transform(t){var e=0,s=0;return t.pages.forEach(t=>{const n=[];t.items.forEach(t=>{if(n.push(t),!t.type){var o=t.text();if(a(t.words[0].string))if(e++,"-"===t.words[0].string)t.annotation=v,t.type=P.LIST;else{t.annotation=E;const e=t.words.map(t=>new O({...t}));e[0].string="-",n.push(new S({...t,words:e,annotation:x,type:P.LIST}))}else/^[\s]*[\d]*[.][\s].*$/g.test(o)&&(s++,t.annotation=v,t.type=P.LIST)}}),t.items=n}),new I({...t,messages:["Detected "+e+" plain list items.","Detected "+s+" numbered list items."]})}},Z=class extends A{constructor(){super("Detect Headers")}transform(t){const{tocPages:e,headlineTypeToHeightRange:s,mostUsedHeight:n,mostUsedDistance:o,mostUsedFont:r,maxHeight:a}=t.globals,h=e.length>0;var l=0;const c=function(t,e){const s=new Set;return t.forEach(t=>{t.items.forEach(n=>{n.type||n.height!==e||s.add(t)})}),s}(t.pages,a),m=n+(a-n)/4;if(c.forEach(t=>{t.items.forEach(t=>{const e=t.height;!t.type&&e>m&&(t.type=e===a?P.H1:P.H2,t.annotation=v,l++)})}),h)Object.keys(s).forEach(e=>{var o=s[e];o.max>n&&t.pages.forEach(t=>{t.items.forEach(t=>{t.type||t.height!==o.max||(t.annotation=v,t.type=P.enumValueOf(e),l++)})})});else{const e=[];t.pages.forEach(t=>{t.items.forEach(t=>{!t.type&&t.height>n&&!i(t.text())&&(e.includes(t.height)||e.push(t.height))})}),e.sort((t,e)=>e-t),e.forEach((e,s)=>{if(s+2<=6){const n=P.headlineByLevel(2+s);t.pages.forEach(t=>{t.items.forEach(t=>{t.type||t.height!==e||i(t.text())||(l++,t.annotation=v,t.type=n)})})}})}var u=1;if(t.pages.forEach(t=>{t.items.forEach(t=>{t.type&&t.type.headline&&(u=Math.max(u,t.type.headlineLevel))})}),u<6){const e=P.headlineByLevel(u+1);t.pages.forEach(t=>{var s;t.items.forEach(t=>{!t.type&&t.height===n&&t.font!==r&&(!s||s.y<t.y||s.type&&s.type.headline||s.y-t.y>2*o)&&t.text()===t.text().toUpperCase()&&(l++,t.annotation=v,t.type=e),s=t})})}return new I({...t,messages:["Detected "+l+" headlines."]})}};var tt=class extends p{items;constructor(t){super(t),this.items=[],t.items&&t.items.forEach(t=>this.addItem(t))}addItem(t){if(this.type&&t.type&&this.type!==t.type)throw new Error(`Adding item of type ${t.type.name} to block of type ${this.type.name}`);this.type||(this.type=t.type),t.parsedElements&&(this.parsedElements?this.parsedElements.add(t.parsedElements):this.parsedElements=t.parsedElements);const e=new S({...t});e.type=null,this.items.push(e)}},et=class t extends y{constructor(e){if(super(e,tt.name),this.constructor===t)throw new TypeError("Can not construct abstract class.")}completeTransform(t){return t.messages=[],t.pages.forEach(t=>{t.items=t.items.filter(t=>!t.annotation||t.annotation!==E),t.items.forEach(t=>t.annotation=null)}),t}},st=class extends et{constructor(){super("Gather Blocks")}transform(t){const{mostUsedDistance:e}=t.globals;var s=0,n=0;return t.pages.map(t=>{n+=t.items.length;const o=[];var r=new tt({});const a=()=>{r.items.length>1&&(r.annotation=v),o.push(r),r=new tt({}),s++};var i=function(t){var e=999;return t.forEach(t=>{e=Math.min(e,t.x)}),999===e?null:e}(t.items);t.items.forEach(t=>{r.items.length>0&&function(t,e,s,n){if(t.type&&t.type.mergeFollowingNonTypedItems&&!e.type)return!1;const o=t.items[t.items.length-1],r=function(t,e,s,n){const o=t.y-e.y;if(o<0-n/2)return!0;var r=n+1;t.x>s&&e.x>s&&(r=n+n/2);return o>r}(o,e,s,n);if(t.type&&t.type.mergeFollowingNonTypedItemsWithSmallDistance&&!e.type&&!r)return!1;if(e.type!==t.type)return!0;return e.type?!e.type.mergeToBlock:r}(r,t,i,e)&&a(),r.addItem(t)}),r.items.length>0&&a(),t.items=o}),new I({...t,messages:["Gathered "+s+" blocks out of "+n+" line items"]})}};var nt=class extends et{constructor(){super("$1")}transform(t){const e=t.globals.mostUsedHeight??0;var s=0;return t.pages.forEach(t=>{var n=function(t){var e=999;return t.forEach(t=>{t.items.forEach(t=>{e=Math.min(e,t.x)})}),999===e?null:e}(t.items);t.items.forEach(t=>{!t.type&&function(t,e,s){if(0===e.length)return!1;if(1===e.length)return e[0].x>t&&e[0].height<=s+1;for(var n of e)if(n.x===t)return!1;return!0}(n,t.items,e)&&(t.annotation=v,t.type=P.CODE,s++)})}),new I({...t,messages:["Detected "+s+" code/quote items."]})}};var ot=class extends et{constructor(){super("Level Lists")}transform(t){var e=0,s=0;return t.pages.forEach(t=>{t.items.filter(t=>t.type===P.LIST).forEach(t=>{var n,o=0;const r={};var a=!1;t.items.forEach(t=>{n?t.x>n?(o++,r[t.x]=o):t.x<n&&(o=r[t.x]):r[t.x]=0,o>0&&(t.words=[new O({string:" ".repeat(3*o)})].concat(t.words),a=!0),n=t.x}),e++,a?(s++,t.annotation=b):t.annotation=T})}),new I({...t,messages:["Modified "+s+" / "+e+" list blocks."]})}},rt=class extends y{constructor(){super("To Text Blocks","TextBlock")}transform(t){return t.pages.forEach(t=>{const e=[];t.items.forEach(t=>{const s=t.type?t.type.name:"Unknown";e.push({category:s,text:P.blockToText(t)})}),t.items=e}),new I({...t})}},at=class extends y{constructor(){super("To HTML","String")}transform(t){return t.pages.forEach(t=>{var e="";t.items.forEach(t=>{let s;s="TOC"===t.category?t.text:t.text.replace(/(\r\n|\n|\r)/gm,"\n"),"LIST"!==t.category&&(s=s.split("- ").join("")),"CODE"===t.category&&(s=s.split("<code>").join("").split("</code>").join("")),e+=`<p>${s}</p>\n\n`}),t.items=[e]}),new I({...t})}};exports.convertPDFToHTML=async function(t,e={}){var{addPageNumbers:s=!1,addCitation:n=!0}=e,o="string"==typeof t?await async function(t,e={}){const s=e.timeout?1e3*e.timeout:1e4,n=new AbortController,o=setTimeout(()=>n.abort(),s);try{const s=await fetch(t,{signal:n.signal});if(clearTimeout(o),!s.ok)throw new Error(`HTTP ${s.status}`);return"arraybuffer"===e.responseType?await s.arrayBuffer():await s.text()}catch(r){throw clearTimeout(o),r}}(t,{responseType:"arraybuffer",timeout:10}):t;let r;try{let{resolvePDFJS:t}=await import("https://cdn.jsdelivr.net/npm/pdfjs-serverless@1.1.0/+esm");const{getDocument:e}=await t();r=await e({data:new Uint8Array(o),useSystemFonts:!0,verbosity:0}).promise}catch(E){return{error:E.message}}const a=[...Array(r.numPages).keys()].map(t=>new g({index:t}));let i,h={};for(let f=1;f<=r.numPages;f++){const t=await r.getPage(f),e=await t.getTextContent();if(!(Object.keys(h).length<10)){i=u(h);break}h=m(h,t.pageNumber-1,e.items)}let l=i?i.pageNum:0;for(let m=1;m<=r.numPages;m++){const t=await r.getPage(m);await t.getOperatorList();const e=t.getViewport({scale:1});let s=await t.getTextContent();i&&t.pageIndex>=i.pageIndex&&(s=f(s,l),l++);const n=s.items.map(t=>{const s=[1,0,0,1,0,0];for(let r=0;r<6;r++)s[r]+=t.transform[r]*e.transform[r%2?3:0],r%2&&(s[r+1]+=t.transform[r]*e.transform[1]);const n=Math.sqrt(s[2]*s[2]+s[3]*s[3]),o=t.height/n;return new d({x:Math.round(t.transform[4]),y:Math.round(t.transform[5]),width:Math.round(t.width),height:Math.round(o<=1?t.height:o),text:t.str,font:t.fontName})});a[t.pageNumber-1].items=n}var c=new I({pages:a});let p;[new B,new j,new Y,new R,new J,new Z,new X,new st,new nt,new ot,new rt,new at].forEach(t=>{p&&(c=p.completeTransform(c)),c=t.transform(c),p=t});var y=c.pages.reduce((t,e,n)=>t+`<p id="page-${n+1}">${s?` [${n+1}] `:""}${e.items.join('</p><p id="page-'+n+'">')}</p>`,"");if(n){var{Author:w,Title:x}=(await r.getMetadata()).info;x=y.slice(0,400).match(/<h[0-9]>(.*?)<\/h[0-9]>/)?.[1]||x}return{author:w,title:x,html:y,format:"pdf"}};
|
package/dist/pdf-to-html.d.ts
CHANGED
|
@@ -1,41 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
format: string;
|
|
38
|
-
error?: undefined;
|
|
39
|
-
}>;
|
|
40
|
-
|
|
41
|
-
export { }
|
|
1
|
+
/**
|
|
2
|
+
* Extracts formatted text from PDF with parsing of linebreaks ,
|
|
3
|
+
* page headers, footnotes, and section headings. Supports fonts, links, bold,
|
|
4
|
+
* italics, lists, headings, headers, footnotes, and Table of Contents,
|
|
5
|
+
* Quotes, and Code Blocks, . Removes repeated headers, links footnote anchors to the footnote,
|
|
6
|
+
* and preserves number of the PDF page with invisible I element.
|
|
7
|
+
*
|
|
8
|
+
* This function uses [pdfjs-serverless](https://github.com/johannschopplich/pdfjs-serverless)
|
|
9
|
+
* to work in more environments than PDF.js-based tools:
|
|
10
|
+
* Cloudflare workers, serverless, node.js, and front-end only.
|
|
11
|
+
* @param {string} pdfURLOrBuffer - URL to a PDF file or buffer from fs.readFile
|
|
12
|
+
* @param {Object} [options]
|
|
13
|
+
* @param {boolean} options.addPageNumbers default=false - Adds # to end of each page
|
|
14
|
+
* @param {boolean} options.removePageHeaders default=true - Removes repeated headers found on each page
|
|
15
|
+
* @returns {string|Object} HTML formatted text
|
|
16
|
+
* @category Extract
|
|
17
|
+
* @author [vtempest (2025)](https://github.com/vtempest),
|
|
18
|
+
* [pdf-to-markdown (2017)](https://github.com/jzillmann/pdf-to-markdown/tree/master),
|
|
19
|
+
* [pdf.js (2012-)](https://github.com/mozilla/pdf.js/releases),
|
|
20
|
+
*/
|
|
21
|
+
export declare function convertPDFToHTML(pdfURLOrBuffer: any, options?: {
|
|
22
|
+
addPageNumbers?: boolean;
|
|
23
|
+
addCitation?: boolean;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
error: any;
|
|
26
|
+
author?: undefined;
|
|
27
|
+
title?: undefined;
|
|
28
|
+
html?: undefined;
|
|
29
|
+
format?: undefined;
|
|
30
|
+
} | {
|
|
31
|
+
author: any;
|
|
32
|
+
title: any;
|
|
33
|
+
html: string;
|
|
34
|
+
format: string;
|
|
35
|
+
error?: undefined;
|
|
36
|
+
}>;
|