import { ColorSpaceInfo, GsPaint } from './shading.js'; import { TextMarkup } from './annot-draw.js'; import { ShapeGradient } from '../core/vector.js'; import { PdfDict, PdfStream } from '../pdf/objects.js'; /** * A page font as the interpreter needs it (built from the font dictionaries in * EP2b): how wide each code is, and how a run of codes decodes to Unicode. An * unmapped font falls back to Latin-1 with a half-em advance so text still * surfaces. */ export interface ContentFont { /** Bytes per character code: simple fonts read 1 byte/code, Type0 reads 2. */ readonly bytesPerCode: 1 | 2; /** * §9.7.6.2 — how a shown string breaks into codes, where the CMap is not * fixed-width. A named CMap like `90ms-RKSJ-H` mixes one-byte and two-byte * codes, and split down the middle a Japanese line came apart into nonsense. * Absent means the fixed width above. */ readonly splitCodes?: (bytes: Uint8Array) => Array; /** Decode a sequence of character codes to a Unicode string. */ decode: (codes: ReadonlyArray) => string; /** Glyph advance for one code, in 1000-unit text space. */ width: (code: number) => number; /** * §9.4.4 / §9.7.4.3 — the face sets its text DOWN the page, not across, and * the pen advances by the vertical displacement `w1` rather than by `w0`. * A `…-V` CMap asks for this; `/DW2`'s default `[880 -1000]` is one em down. * The number here is that displacement in 1000-unit text space, and it is * negative because the page's y runs up. */ readonly verticalAdvance?: (code: number) => number; /** §9.6.2 — the face's own `/BaseFont` name, for a document that embeds it. */ readonly name?: string; /** §9.8.1 — the face is a bold one (weight, the ForceBold flag, or its name). */ readonly bold?: boolean; /** §9.8.1 — the face is slanted (`/ItalicAngle`, the Italic flag, or its name). */ readonly italic?: boolean; /** * §9.6.5 — a Type 3 face, whose glyphs are content streams rather than * outlines. What such a font draws is not type at all: it is whatever the * procedure paints, in the resources the font states. */ readonly type3?: Type3Face; /** * §9.6.6 — the OUTLINE a code draws, for a code that stands for no * character. A face that can say what its characters are never has one: * this is the last resort before a blank page. */ readonly outline?: GlyphOutline; } /** §9.6.6 — the outlines of a face read by glyph index, and their glyph space. */ export interface GlyphOutline { /** Glyph space to text space, as a Type 3 font's `/FontMatrix` is. */ readonly matrix: Matrix; /** The contours one code draws, or `undefined` where it draws none. */ readonly path: (code: number) => Array | undefined; } /** §9.6.5 — the parts of a Type 3 font a caller needs to run its glyphs. */ export interface Type3Face { /** `/FontMatrix` — glyph space to text space. */ readonly matrix: Matrix; /** `/Encoding` + `/CharProcs` — the content stream one code draws. */ readonly proc: (code: number) => PdfStream | undefined; /** `/Resources` the procedures draw with, when the font states its own. */ readonly resources: PdfDict | undefined; } /** * One positioned text run emitted by a show operator: its decoded text, the * glyph origin in page space and the effective font size — the raw material a * later stage groups into lines and paragraphs. */ export interface TextRun { readonly text: string; /** Glyph origin x in page space (points). */ readonly x: number; /** Glyph origin y in page space (points). */ readonly y: number; /** * Where the pen stood after the last glyph, in page space (§9.4.4). The * interpreter advances the text matrix by the font's own widths, so this is * a measurement, not the half-em-per-character guess the reader used to make * — and the difference between a word space and a table column is exactly * the kind of thing a guess gets wrong. */ readonly endX: number; /** The pen's y after the last glyph — with {@link endX}, the whole advance. */ readonly endY: number; /** * The baseline's direction in page space, degrees counter-clockwise from * left-to-right (§9.4.2 — the text matrix may turn as well as move). Absent * for ordinary upright text, which is nearly all of it. */ readonly angleDeg?: number; readonly fontSizePt: number; readonly fontKey: string; /** * §9.6.2 `/BaseFont` — the face's own name, subset prefix dropped and * lowercased, or absent when the font states none. This is what a rebuilt run * asks for, so a page whose faces the file EMBEDS is re-set in them rather * than in a substitute (see `./embedded-fonts`). */ readonly fontName?: string; /** * §9.4.4 — the advance of this face's SPACE at the size the run was shown * at, which is what says whether a gap between two runs was a word space. * Absent where the face states no width for it. */ readonly spaceWidthPt?: number; /** §9.8.1 — the face the glyphs were shown in is a bold one. */ readonly bold?: boolean; /** * §12.5.6.10 — a text-markup annotation marks these glyphs: highlighted, * underlined, struck through. Applied after extraction, from the page's * `/Annots` (see `./text`), because it is stated about the words rather than * painted among them. */ readonly markup?: TextMarkup; /** * §8.6.6.2 — the glyphs are filled with a tiling PATTERN, named here for the * caller to resolve: a pattern is a content stream, not a colour, and the * fill colour still standing from before is not what the page shows. */ readonly fillPatternName?: string; /** * §8.7.4.5 — the glyphs are filled with a SHADING pattern: a sweep from one * colour to another, of which the run carries the middle. The colour is not * lost, the shape of it is. */ readonly gradientFill?: boolean; /** * §9.3.6 — the page painted these glyphs NOWHERE: mode 3 shows nothing and * mode 7 only adds to the clip. A scanned page carries its recognised words * that way, under the picture of the page — so the run is kept, because it * is the only text such a document has, and a reader reproducing the page * leaves it to the picture. */ readonly invisible?: boolean; /** * §9.3.6 — the colour the glyphs are STROKED in, when the rendering mode * asks for a stroke, and how wide the pen is. */ readonly outlineHex?: string; readonly outlineWidthPt?: number; /** §9.8.1 — the face the glyphs were shown in is a slanted one. */ readonly italic?: boolean; /** * §9.6.5 — the face is a Type 3 one, so what the page SHOWS here is the * glyph procedures, not type. The run is kept for its words; a reader that * reproduces the page draws the procedures instead of re-setting it. */ readonly type3?: boolean; /** §8.6.8 — the non-stroking colour the glyphs were painted in (6-hex). */ readonly colorHex: string; /** * The marked-content id of the enclosing `BDC` sequence (§14.6), if any — the * link from this text to the structure element that owns it (E-PDF EP3). */ readonly mcid?: number; /** * A `/Link` annotation whose `/Rect` covers this run's origin attaches its URI * here (E-PDF EP8), so the reconstructed run carries the hyperlink. */ readonly href?: string; } /** * A painted XObject (`/Name Do`, §8.8) — an image or form. The CTM maps the unit * square to page space, so it carries both the placement and the size; the * `mcid` links the paint to its structure element (a `/Figure`, E-PDF EP6). */ /** §8.9.7 — an image written into the content stream rather than named. */ export interface InlineImage { /** Its dictionary, keys as written — the abbreviated ones (`/W`, `/CS`, `/F`). */ readonly dict: PdfDict; /** Its bytes, still filtered as the dictionary says. */ readonly data: Uint8Array; } export interface ImagePlacement { /** Where the `Do` fell in the stream's painting order — see {@link VectorPlacement.order}. */ readonly order: number; /** XObject resource name (no leading slash), empty for an inline image. */ readonly name: string; /** §8.9.7 — the image itself, where it was written into the stream. */ readonly inline?: InlineImage; readonly ctm: Matrix; /** §8.5.4 — the clip in force when it was painted, when there was one. */ readonly clip?: ClipRegion; /** * §8.9.6.2 — the non-stroking colour in force. A stencil `/ImageMask` carries * no colour of its own: it says only WHERE to paint, and this is what. */ readonly fillHex: string; readonly mcid?: number; /** §11.6.4.4 `/ca` — how opaque the picture is drawn, when the page asked for less. */ readonly alpha?: number; /** §11.3.5 `/BM` — a blend the page asked for that nothing here performs. */ readonly blend?: string; /** §11.6.5 `/SMask` — the paint faded from place to place; nothing here does. */ readonly masked?: boolean; } /** * One segment of a painted path (E-PDF EP10/EP11), in page space (y-up): a * `move`/`line`/`cubic` Bézier point or a subpath `close`. */ export type PathSeg = { readonly op: 'move'; readonly x: number; readonly y: number; } | { readonly op: 'line'; readonly x: number; readonly y: number; } | { readonly op: 'cubic'; readonly x1: number; readonly y1: number; readonly x2: number; readonly y2: number; readonly x: number; readonly y: number; } | { readonly op: 'close'; }; /** * §8.5.4 — the clipping region in force when a path was painted: the path that * `W`/`W*` installed, plus its page-space bounding box. */ export interface ClipRegion { readonly segs: ReadonlyArray; readonly minX: number; readonly minY: number; readonly maxX: number; readonly maxY: number; } export interface VectorPlacement { /** * Where this fell in the stream's painting order (§8.5.3): later covers * earlier, and a `Do` of a form is numbered here too, so a caller walking * into that form knows exactly where its marks belong among these. */ readonly order: number; readonly segs: ReadonlyArray; /** §8.5.4 — the clip in force when it was painted, when there was one. */ readonly clip?: ClipRegion; /** Fill colour (6-hex), present iff the path is filled (`f` / `F` / `f*` / `B` / `b`). */ readonly fillHex?: string; /** Shading pattern, present iff filled with one (EP16c). */ readonly gradient?: ShapeGradient; /** §11.6.4.4 `/ca` — how opaque the fill is, when the page asked for less. */ readonly alpha?: number; /** * §11.3.5 `/BM` — the fill only DARKENS what it covers (`Multiply`, * `Darken`), so the marks under it read through. A highlighter is this and * nothing else. */ readonly darkens?: boolean; /** §11.3.5 `/BM` — a blend the page asked for that nothing here performs. */ readonly blend?: string; /** §11.6.5 `/SMask` — the paint faded from place to place; nothing here does. */ readonly masked?: boolean; /** * §9.6.6 — this path is a GLYPH, traced from the face because its code stands * for no character. It is type, not artwork, so the de-cluttering a page's * paths go through does not apply: a full stop is a mark one point across and * every filter there would throw it out. */ readonly glyph?: boolean; /** * §8.7.3 — the TILING pattern resource name the path is filled with. Its * content is a stream of its own, so what the fill actually shows is only * known by walking into it; the `fillHex` beside this is not the fill. */ readonly patternName?: string; /** Stroke colour (6-hex), present iff the path is stroked (`S` / `s` / `B` / `b`) — EP11. */ readonly strokeHex?: string; /** Stroke width in page-space points — EP11. */ readonly lineWidth?: number; readonly mcid?: number; } /** Everything {@link interpretContent} extracts from one page's content stream. */ export interface InterpretResult { readonly texts: Array; readonly images: Array; readonly vectors: Array; /** §9.6.5 — every Type 3 glyph the stream showed, with where to run it. */ readonly glyphs: Array; /** * §8.7.4.3 — every region the stream painted with a bare `sh`, which fills * the CLIP rather than a path. What a caller can do with one depends on the * shading's own type, so the placement is carried rather than counted. */ readonly shadings: Array; /** * §9.6.6 — every glyph the stream showed that stands for NO character, as * the path it draws in page space. There is nothing to write for one, and * there is something to draw. */ readonly outlines: Array; } /** * §9.6.5 — one showing of a Type 3 glyph: which procedure, and the matrix that * puts glyph space on the page. */ /** §8.7.4.3 — one `sh`: which shading, where the CTM put it, what bounded it. */ export interface ShadingPaint { /** The `/Shading` resource name (no leading slash). */ readonly name: string; /** The CTM in force, which maps the shading's own space onto the page. */ readonly ctm: Matrix; /** §8.5.4 — the clip the paint was bounded by, which is its whole extent. */ readonly clip?: ClipRegion; /** * §11.6.5 `/SMask` — the paint was faded from place to place. For a `sh` that * is not a detail: the mask is the SHAPE, and the clip is only its outer * bound. */ readonly masked?: boolean; /** §11.6.4.4 `/ca` — how opaque the paint is, when the page asked for less. */ readonly alpha?: number; /** §11.3.5 `/BM` — the paint only DARKENS what it covers. */ readonly darkens?: boolean; /** Where it fell in the painting order (§8.5.3). */ readonly order: number; } export interface Type3Call { readonly stream: PdfStream; readonly resources: PdfDict | undefined; readonly ctm: Matrix; /** §8.5.3 — its place in the stream's painting order, as a form call has. */ readonly order: number; /** * §9.6.5/§9.10 — the font SAYS what character this glyph stands for. A * flowing reading re-sets that character in a face of its own, so drawing the * procedure as well puts two copies of the letter on the page. */ readonly readable: boolean; } /** * §9.10 — whether the font says what character a glyph stands for: text that is * neither empty nor the replacement character a reader writes where nothing is * stated. * * @param text What the font's decode gave for the code. * @returns Whether a flowing reading can write it as a letter. */ export declare function statesACharacter(text: string): boolean; /** * 2D affine matrix `[a b c d e f]`, row-vector convention (`[x y 1] · M`): * ``` * ⎡a b 0⎤ * ⎢c d 0⎥ * ⎣e f 1⎦ * ``` */ export type Matrix = readonly [number, number, number, number, number, number]; /** The identity {@link Matrix} (no transform). */ export declare const IDENTITY: Matrix; /** Compose two {@link Matrix matrices}: `a` applied first, then `b`. */ export declare function multiply(a: Matrix, b: Matrix): Matrix; export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap, initialCtm?: Matrix, shadings?: ReadonlyMap, alphas?: ReadonlyMap, spaces?: ReadonlyMap, hiddenOc?: ReadonlySet): InterpretResult; /** * Whether a string is wholly right-to-left: at least one letter of an RTL * script and nothing of any other, spaces and joiners aside. * * Anything mixed — a number inside an Arabic sentence runs left to right — * needs the full bidi algorithm, and guessing at it would be worse than * leaving it alone. * * @param text The string to judge. * @returns Whether it is one run of right-to-left script. */ export declare function isRightToLeft(text: string): boolean;