@@ -41,6 +41,12 @@ export type PdfLineSeparator = '' | ' ' | '\n'
4141export interface JoinLinesOptions {
4242 /** Lowercase `a-b` compounds seen intact in the document; a line break on their hyphen keeps it. */
4343 compounds ?: ReadonlySet < string >
44+ /**
45+ * Lowercase words seen in the document. A line-end hyphen is dropped only when
46+ * the joined word occurs elsewhere, so `high-` / `quality` keeps its hyphen
47+ * while `Infra-` / `structure` rejoins when `Infrastructure` appears intact.
48+ */
49+ words ?: ReadonlySet < string >
4450 /** Dominant body-text height for the document; enables heading markers and heading pitch scaling. */
4551 bodyHeight ?: number
4652 /** Prefixes short, oversized lines with `## ` so Markdown-aware chunkers split on them. */
@@ -81,6 +87,7 @@ const SOFT_HYPHEN = '\u00AD'
8187const TRAILING_HYPHEN = / ( \p{ L} + ) - $ / u
8288const LEADING_LOWERCASE_WORD = / ^ ( \p{ Ll} \p{ L} * ) / u
8389const INTACT_COMPOUND = / \p{ L} + - \p{ L} + / gu
90+ const WORD_TOKEN = / \p{ L} { 3 , } / gu
8491
8592/**
8693 * Reads an item's placement, or undefined when the item is rotated, vertical,
@@ -194,6 +201,15 @@ export function collectCompounds(lines: Iterable<PdfLine>): Set<string> {
194201 return compounds
195202}
196203
204+ /** Lowercase words of three or more letters seen anywhere in the document. */
205+ export function collectWords ( lines : Iterable < PdfLine > ) : Set < string > {
206+ const words = new Set < string > ( )
207+ for ( const line of lines ) {
208+ for ( const match of line . text . matchAll ( WORD_TOKEN ) ) words . add ( match [ 0 ] . toLowerCase ( ) )
209+ }
210+ return words
211+ }
212+
197213/** Character-weighted modal line height across the document; 0 when unknown. */
198214export function dominantLineHeight ( lines : Iterable < PdfLine > ) : number {
199215 const weights = new Map < number , number > ( )
@@ -224,6 +240,7 @@ export function joinLines(lines: readonly PdfLine[], options: JoinLinesOptions =
224240 const bodyHeight = options . bodyHeight ?? 0
225241 const headingMarkers = options . headingMarkers ?? PDF_HEADING_MARKERS_ENABLED
226242 const compounds = options . compounds
243+ const words = options . words
227244
228245 let out = decorate ( lines [ 0 ] , bodyHeight , headingMarkers )
229246 for ( let i = 1 ; i < lines . length ; i ++ ) {
@@ -234,7 +251,7 @@ export function joinLines(lines: readonly PdfLine[], options: JoinLinesOptions =
234251 continue
235252 }
236253 if ( separator === '\n' ) {
237- const joined = dehyphenate ( out , line . text , compounds )
254+ const joined = dehyphenate ( out , line . text , compounds , words )
238255 if ( joined !== undefined ) {
239256 out = joined
240257 continue
@@ -260,20 +277,25 @@ function decorate(line: PdfLine, bodyHeight: number, headingMarkers: boolean): s
260277
261278/**
262279 * Joins `next` onto `out` across a hyphen that ended the line, or undefined
263- * when the break is not a hyphenation. A compound seen intact elsewhere in the
264- * document keeps its hyphen.
280+ * when the break is not a hyphenation. The hyphen is removed only when the
281+ * document itself shows the joined word; a compound seen intact keeps it, and
282+ * an unknown pair keeps it too, because `high-quality` split at a line end is
283+ * far more common in real documents than a word the document never repeats.
265284 */
266285function dehyphenate (
267286 out : string ,
268287 next : string ,
269- compounds : ReadonlySet < string > | undefined
288+ compounds : ReadonlySet < string > | undefined ,
289+ words : ReadonlySet < string > | undefined
270290) : string | undefined {
271291 const head = TRAILING_HYPHEN . exec ( out )
272292 const tail = LEADING_LOWERCASE_WORD . exec ( next )
273293 if ( ! head || ! tail ) return undefined
274294 const compound = `${ head [ 1 ] } -${ tail [ 1 ] } ` . toLowerCase ( )
275295 if ( compounds ?. has ( compound ) ) return out + next
276- return out . slice ( 0 , - 1 ) + next
296+ const joined = `${ head [ 1 ] } ${ tail [ 1 ] } ` . toLowerCase ( )
297+ if ( words ?. has ( joined ) ) return out . slice ( 0 , - 1 ) + next
298+ return out + next
277299}
278300
279301function separatorBetween (
0 commit comments