478 lines
20 KiB
Odin
478 lines
20 KiB
Odin
package pdf
|
|||
|
|
|
||
|
|
import "core:mem/virtual"
|
||
|
|
|
||
|
|
// Annotations and their appearance streams (ISO 32000-2, 12.5).
|
||
|
|
//
|
||
|
|
// An annotation is a dictionary in a page's /Annots array: a link, a form
|
||
|
|
// field, a stamp, a signature, a highlight, a sticky note. None of them say
|
||
|
|
// what they look like. What is drawn is an APPEARANCE STREAM -- a form XObject
|
||
|
|
// under /AP -- mapped onto the annotation's /Rect. /Annots is in 53% of the
|
||
|
|
// files in the corpus, so a renderer that stops at the page's content stream
|
||
|
|
// draws the page perfectly and loses every form field and every stamp on it.
|
||
|
|
//
|
||
|
|
// THIS LIBRARY NEVER SYNTHESISES AN APPEARANCE
|
||
|
|
//
|
||
|
|
// An annotation with no /AP draws nothing at all: no border for a /Square, no
|
||
|
|
// leader line for a /Line, no laid-out text for a /FreeText or for a widget's
|
||
|
|
// /DA string. That is a deliberate scope decision, not an oversight.
|
||
|
|
// Generating appearances is the whole of 12.5.6's per-subtype geometry plus
|
||
|
|
// 12.7.4.3's variable-text layout, which is what a form-*filling* library
|
||
|
|
// owes; a reader owes what the file says the page looks like. Every producer
|
||
|
|
// that expects its annotations to be seen writes an /AP, and the ones that do
|
||
|
|
// not set /NeedAppearances, which announces that the consumer is expected to
|
||
|
|
// be an editor.
|
||
|
|
//
|
||
|
|
// PLACEMENT IS THE WHOLE JOB
|
||
|
|
//
|
||
|
|
// `annot_matrix` is the algorithm of 12.5.5, step by named step. Getting it
|
||
|
|
// wrong does not fail loudly -- it silently misplaces or wrongly scales every
|
||
|
|
// stamp and form field on the page -- so each step is named in the code and
|
||
|
|
// pinned by tests against hand-computed matrices.
|
||
|
|
//
|
||
|
|
// Once placed, the appearance is drawn by the ordinary content interpreter,
|
||
|
|
// through two synthesised tokens: `/Ap Do`. Invoking a form XObject is exactly
|
||
|
|
// what `Do` is for, and going through the interpreter rather than around it is
|
||
|
|
// what gets the /BBox clip, a transparency /Group, the form's own /Matrix, the
|
||
|
|
// /OC test and the self-reference guard right -- all of it already written and
|
||
|
|
// tested in content.odin.
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Limits
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// Entries read from one page's /Annots. A table of contents can carry a link
|
||
|
|
// per line and a form can carry a field per cell, so this is generous; past it
|
||
|
|
// the array is not a page's annotations.
|
||
|
|
@(private)
|
||
|
|
MAX_ANNOTS :: 4096
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Flags
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// Annot_Flag is one bit of /F (12.5.3, table 167).
|
||
|
|
//
|
||
|
|
// The format numbers its flag bits from 1; these values are the same bits
|
||
|
|
// numbered from 0, which is what a bit_set indexes by. `.Hidden` is therefore
|
||
|
|
// the format's "bit position 2".
|
||
|
|
Annot_Flag :: enum u32 {
|
||
|
|
Invisible = 0,
|
||
|
|
Hidden = 1,
|
||
|
|
Print = 2,
|
||
|
|
No_Zoom = 3,
|
||
|
|
No_Rotate = 4,
|
||
|
|
No_View = 5,
|
||
|
|
Read_Only = 6,
|
||
|
|
Locked = 7,
|
||
|
|
Toggle_No_View = 8,
|
||
|
|
Locked_Contents = 9,
|
||
|
|
}
|
||
|
|
|
||
|
|
Annot_Flags :: bit_set[Annot_Flag; u32]
|
||
|
|
|
||
|
|
// annot_flags_read turns /F into a set. Bits above the ten the format defines
|
||
|
|
// are dropped rather than kept: they are reserved, and a set that holds
|
||
|
|
// members no enumerator names prints as garbage.
|
||
|
|
@(private)
|
||
|
|
annot_flags_read :: proc(raw: i64) -> (f: Annot_Flags) {
|
||
|
|
for bit in 0 ..< len(Annot_Flag) {
|
||
|
|
if raw & (i64(1) << uint(bit)) != 0 {
|
||
|
|
f += {Annot_Flag(bit)}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Annot
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// Annot is one annotation of a page with its appearance already chosen.
|
||
|
|
//
|
||
|
|
// It borrows from the Document: `dict`, `ap`, `subtype` and `state` all point
|
||
|
|
// into the Document's arena or the file bytes and become invalid when `close`
|
||
|
|
// is called. The struct is a value and holds no state of its own.
|
||
|
|
Annot :: struct {
|
||
|
|
// The annotation dictionary itself, for the many per-subtype entries this
|
||
|
|
// library does not interpret (/Contents, /T, /Dest, /FT, /V ...).
|
||
|
|
dict: ^Dict,
|
||
|
|
// Object number, or {0,0} when /Annots held the dictionary directly.
|
||
|
|
ref: Ref,
|
||
|
|
// /Subtype verbatim: "Link", "Widget", "Stamp", "Popup", ... Empty when
|
||
|
|
// the file omitted it, which is malformed and does happen.
|
||
|
|
subtype: string,
|
||
|
|
// /Rect, with its corners sorted: the format allows either order and
|
||
|
|
// producers use both.
|
||
|
|
rect: Rect,
|
||
|
|
flags: Annot_Flags,
|
||
|
|
|
||
|
|
// The appearance stream to draw, after /AS has chosen among the states of
|
||
|
|
// an /AP /N sub-dictionary. nil when the annotation has no appearance, and
|
||
|
|
// then nothing is drawn -- see the note at the top of this file.
|
||
|
|
ap: ^Stream,
|
||
|
|
// The appearance state /AS asked for, or the sole state of a /N
|
||
|
|
// sub-dictionary that had only one. Empty when /N was a stream outright.
|
||
|
|
// It is set even when the state named nothing and `ap` is therefore nil,
|
||
|
|
// which is the ordinary way an unticked checkbox is written.
|
||
|
|
state: string,
|
||
|
|
|
||
|
|
// False when this annotation is not part of the page's visible content:
|
||
|
|
// /F hides it, or it is a popup. It says nothing about /OC, which is
|
||
|
|
// resolved when the appearance runs -- a text-extraction pass deliberately
|
||
|
|
// draws layers a renderer would leave switched off, and that choice is
|
||
|
|
// `Content_Options.ignore_optional_content`, made per run and not here.
|
||
|
|
visible: bool,
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Loading
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// annot_load reads a page's /Annots.
|
||
|
|
//
|
||
|
|
// Every entry that resolves to a dictionary comes back, including the ones
|
||
|
|
// that will not be drawn: a viewer needs a /Link's rectangle to make it
|
||
|
|
// clickable and a /Widget's dictionary to fill it in, whether or not either
|
||
|
|
// has an appearance. `visible` and `ap` say what `annot_run` will do with it.
|
||
|
|
//
|
||
|
|
// The slice is the caller's, allocated with `allocator`, and is released with
|
||
|
|
// one `delete`; everything the Annots point at is Document memory and must not
|
||
|
|
// be freed. An error comes back only for a nil argument -- a page with no
|
||
|
|
// /Annots, or with an /Annots that is not an array, has no annotations, which
|
||
|
|
// is not a failure.
|
||
|
|
annot_load :: proc(doc: ^Document, page: ^Page, allocator := context.allocator) -> ([]Annot, Error) {
|
||
|
|
if doc == nil || page == nil || page.dict == nil {
|
||
|
|
return nil, .Invalid_Argument
|
||
|
|
}
|
||
|
|
arr := dict_array(doc, page.dict, "Annots")
|
||
|
|
n := min(array_len(arr), MAX_ANNOTS)
|
||
|
|
if n == 0 {
|
||
|
|
return nil, .None
|
||
|
|
}
|
||
|
|
|
||
|
|
list := make([dynamic]Annot, 0, min(n, 64), allocator)
|
||
|
|
defer delete(list)
|
||
|
|
|
||
|
|
for i in 0 ..< n {
|
||
|
|
raw := array_get_raw(arr, i)
|
||
|
|
d, ok := to_dict(resolve(doc, raw))
|
||
|
|
if !ok {
|
||
|
|
// A null entry, or a reference to an object that is gone, is how
|
||
|
|
// a producer deletes an annotation without rewriting the array.
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
a := annot_read(doc, d)
|
||
|
|
a.ref, _ = to_ref(raw)
|
||
|
|
append(&list, a)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handed back at its exact length: a caller frees this with `delete`, and
|
||
|
|
// freeing a slice that was sized as a dynamic array's capacity is a
|
||
|
|
// mismatch for every allocator that checks.
|
||
|
|
out := make([]Annot, len(list), allocator)
|
||
|
|
copy(out, list[:])
|
||
|
|
return out, .None
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
annot_read :: proc(doc: ^Document, d: ^Dict) -> (a: Annot) {
|
||
|
|
a.dict = d
|
||
|
|
a.subtype = dict_name(doc, d, "Subtype")
|
||
|
|
// object_to_rect sorts the corners and rejects a non-finite one, so
|
||
|
|
// everything downstream can subtract without checking.
|
||
|
|
a.rect = dict_rect(doc, d, "Rect")
|
||
|
|
a.flags = annot_flags_read(dict_int(doc, d, "F", 0))
|
||
|
|
a.ap, a.state = annot_appearance(doc, d)
|
||
|
|
|
||
|
|
// 12.5.3: bit 2 Hidden means do not render it anywhere. Bit 6 NoView means
|
||
|
|
// do not render it on a screen -- and this library renders for viewing, so
|
||
|
|
// it is honoured the same way; a consumer producing a print stream is the
|
||
|
|
// one that would want the opposite and would have to say so.
|
||
|
|
//
|
||
|
|
// Bit 1 Invisible does NOT suppress an appearance stream. It applies only
|
||
|
|
// to an annotation whose subtype the reader has no handler for, and it
|
||
|
|
// says "then draw nothing rather than a placeholder"; an annotation with
|
||
|
|
// an /AP is handled by definition (12.5.3, table 167).
|
||
|
|
a.visible = !(.Hidden in a.flags) && !(.No_View in a.flags)
|
||
|
|
|
||
|
|
// A popup is the window a markup annotation opens when its parent is
|
||
|
|
// clicked. It is never part of the page's own appearance, even though it
|
||
|
|
// sits in /Annots and carries a /Rect (12.5.6.14).
|
||
|
|
if a.subtype == "Popup" {
|
||
|
|
a.visible = false
|
||
|
|
}
|
||
|
|
|
||
|
|
// /NoZoom and /NoRotate ask for an appearance that ignores the viewer's
|
||
|
|
// zoom and the page's /Rotate. Both are properties of an interactive
|
||
|
|
// view, not of the page: honouring them needs the zoom factor teased back
|
||
|
|
// out of the CTM, and a static render has no zoom to be independent of.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// annot_appearance picks the stream to draw out of /AP.
|
||
|
|
//
|
||
|
|
// Only /N is page content. /R (rollover) and /D (down) are responses to a
|
||
|
|
// pointer that is not there and never appear in a rendering of the page
|
||
|
|
// (12.5.5).
|
||
|
|
@(private)
|
||
|
|
annot_appearance :: proc(doc: ^Document, d: ^Dict) -> (^Stream, string) {
|
||
|
|
n := dict_get(doc, dict_dict(doc, d, "AP"), "N")
|
||
|
|
if stm, ok := to_stream(n); ok {
|
||
|
|
return stm, ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// A sub-dictionary instead of a stream is keyed by appearance state: it is
|
||
|
|
// how a checkbox carries both its /Off and its /Yes look in one place and
|
||
|
|
// /AS says which of them it is showing.
|
||
|
|
sub, is_dict := to_dict(n)
|
||
|
|
if !is_dict {
|
||
|
|
return nil, ""
|
||
|
|
}
|
||
|
|
|
||
|
|
if state := dict_name(doc, d, "AS"); state != "" {
|
||
|
|
// A state that names nothing draws nothing. A checkbox whose /N holds
|
||
|
|
// only /Yes and whose /AS is /Off is unchecked, and the absent entry
|
||
|
|
// IS its appearance; falling back to the only stream present would
|
||
|
|
// tick every empty box on the page.
|
||
|
|
stm, ok := to_stream(dict_get(doc, sub, state))
|
||
|
|
return ok ? stm : nil, state
|
||
|
|
}
|
||
|
|
|
||
|
|
// /AS is required as soon as /N is a sub-dictionary and is nonetheless
|
||
|
|
// left out. With exactly one state there is nothing to disambiguate and
|
||
|
|
// every viewer draws it; with more there is no way to choose, and drawing
|
||
|
|
// the wrong state is worse than drawing none.
|
||
|
|
if len(sub^) == 1 {
|
||
|
|
for k, v in sub^ {
|
||
|
|
if stm, ok := to_stream(resolve(doc, v)); ok {
|
||
|
|
return stm, k
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if len(sub^) > 1 {
|
||
|
|
add_repair(doc, .Bad_Resource, 0)
|
||
|
|
}
|
||
|
|
return nil, ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Placement -- ISO 32000-2, 12.5.5, "Algorithm: appearance streams"
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// annot_matrix maps an appearance stream onto its annotation's rectangle.
|
||
|
|
//
|
||
|
|
// The algorithm, exactly as the clause gives it:
|
||
|
|
//
|
||
|
|
// 1. Map the appearance's /BBox through its /Matrix -- transform its four
|
||
|
|
// corners.
|
||
|
|
// 2. Take the bounding box of those four transformed points. For a /Matrix
|
||
|
|
// that rotates or skews this is bigger than the shape inside it, and
|
||
|
|
// that is the whole reason the step exists.
|
||
|
|
// 3. Compute A: the matrix that maps that box onto the annotation's /Rect.
|
||
|
|
// 4. The appearance is drawn under /Matrix * A * the page's CTM.
|
||
|
|
//
|
||
|
|
// `form` is that final matrix -- the one the appearance's own coordinates are
|
||
|
|
// drawn under, and what a caller asking where an annotation landed wants.
|
||
|
|
// `invoke` is the same thing without the form's own /Matrix (A * page_ctm),
|
||
|
|
// because the interpreter applies /Matrix itself when it runs the stream and
|
||
|
|
// applying it here as well would apply it twice.
|
||
|
|
//
|
||
|
|
// `ok` is false when there is nothing to place: an annotation with no area, or
|
||
|
|
// a transformed appearance box with no width or no height. The scale factor of
|
||
|
|
// step 3 would be a division by zero, and a zero-area appearance has no
|
||
|
|
// picture in it to stretch.
|
||
|
|
@(private)
|
||
|
|
annot_matrix :: proc(doc: ^Document, a: ^Annot, page_ctm: Matrix) -> (form, invoke: Matrix, ok: bool) {
|
||
|
|
if doc == nil || a == nil || a.ap == nil {
|
||
|
|
return MATRIX_IDENTITY, MATRIX_IDENTITY, false
|
||
|
|
}
|
||
|
|
if rect_is_empty(a.rect) {
|
||
|
|
// A missing /Rect reads as the empty rectangle, and an annotation with
|
||
|
|
// no area shows nothing however its appearance is scaled.
|
||
|
|
return MATRIX_IDENTITY, MATRIX_IDENTITY, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// A /Matrix that is short, non-numeric or infinite arrives as the identity:
|
||
|
|
// object_to_matrix is the finiteness boundary for a matrix out of a file,
|
||
|
|
// and the identity is both the format's default and the only safe guess.
|
||
|
|
mtx := dict_matrix(doc, a.ap.dict, "Matrix")
|
||
|
|
|
||
|
|
place := MATRIX_IDENTITY
|
||
|
|
if bbox, has_bbox := object_to_rect(doc, dict_get(doc, a.ap.dict, "BBox")); has_bbox {
|
||
|
|
// Steps 1 and 2. transform_rect maps the four corners and returns
|
||
|
|
// their bounding box, which is precisely what the two steps ask for.
|
||
|
|
tb := transform_rect(mtx, bbox)
|
||
|
|
tw := rect_width(tb)
|
||
|
|
th := rect_height(tb)
|
||
|
|
if !(tw > 0) || !(th > 0) {
|
||
|
|
return MATRIX_IDENTITY, MATRIX_IDENTITY, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// Step 3. A maps tb onto /Rect: scale each axis by the ratio of the
|
||
|
|
// sides, then translate tb's low corner onto /Rect's.
|
||
|
|
sx := rect_width(a.rect) / tw
|
||
|
|
sy := rect_height(a.rect) / th
|
||
|
|
place = Matrix{sx, 0, 0, sy, a.rect.x0 - tb.x0 * sx, a.rect.y0 - tb.y0 * sy}
|
||
|
|
} else {
|
||
|
|
// /BBox is required of every form XObject and there is nothing to map
|
||
|
|
// onto the /Rect without it. Drawing the appearance where its own
|
||
|
|
// coordinates put it is what content.odin does with the same damage,
|
||
|
|
// and it shows something that is at least in the right units.
|
||
|
|
add_repair(doc, .Bad_Resource, 0)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Step 4.
|
||
|
|
invoke = matrix_mul(place, page_ctm)
|
||
|
|
form = matrix_mul(mtx, invoke)
|
||
|
|
if !matrix_is_finite(form) || !matrix_is_finite(invoke) {
|
||
|
|
return MATRIX_IDENTITY, MATRIX_IDENTITY, false
|
||
|
|
}
|
||
|
|
return form, invoke, true
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Drawing
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// The synthesised content stream that invokes the appearance, and the names it
|
||
|
|
// uses. The names are looked up only in the dictionary built below, so they
|
||
|
|
// cannot collide with anything in the file.
|
||
|
|
@(private = "file")
|
||
|
|
ANNOT_INVOKE :: "/Ap Do"
|
||
|
|
@(private = "file")
|
||
|
|
ANNOT_INVOKE_OC :: "/OC /Oc BDC /Ap Do EMC"
|
||
|
|
@(private = "file")
|
||
|
|
ANNOT_AP_NAME :: "Ap"
|
||
|
|
@(private = "file")
|
||
|
|
ANNOT_OC_NAME :: "Oc"
|
||
|
|
|
||
|
|
// annot_run draws one annotation's appearance stream.
|
||
|
|
//
|
||
|
|
// `page_ctm` is the same matrix the page's own content stream was run with:
|
||
|
|
// annotations live in default user space, so the page flip, /Rotate and zoom
|
||
|
|
// apply to them unchanged.
|
||
|
|
//
|
||
|
|
// An annotation with no appearance, or one /F or its subtype hides, draws
|
||
|
|
// nothing and is not an error -- most pages are mostly those. `opts` is passed
|
||
|
|
// through to the interpreter, which is how a caller shares one scratch arena
|
||
|
|
// across a page and how a text-extraction pass asks for switched-off layers.
|
||
|
|
annot_run :: proc(doc: ^Document, a: ^Annot, dev: ^Device, page_ctm: Matrix,
|
||
|
|
opts := Content_Options{}) -> Error {
|
||
|
|
if doc == nil || a == nil {
|
||
|
|
return .Invalid_Argument
|
||
|
|
}
|
||
|
|
if !matrix_is_finite(page_ctm) {
|
||
|
|
return .Invalid_Argument
|
||
|
|
}
|
||
|
|
if a.ap == nil || !a.visible {
|
||
|
|
return .None
|
||
|
|
}
|
||
|
|
|
||
|
|
// `form` is the complete appearance matrix; the interpreter arrives at it
|
||
|
|
// itself by multiplying the form's /Matrix onto the CTM it is invoked
|
||
|
|
// with, which is why it is `invoke` that is handed over.
|
||
|
|
_, invoke, ok := annot_matrix(doc, a, page_ctm)
|
||
|
|
if !ok {
|
||
|
|
return .None
|
||
|
|
}
|
||
|
|
|
||
|
|
o := opts
|
||
|
|
arena: virtual.Arena
|
||
|
|
if o.allocator.procedure == nil {
|
||
|
|
// Same contract as run_content: the run frees nothing individually, so
|
||
|
|
// its allocator has to be one that reclaims in bulk.
|
||
|
|
if arena_err := virtual.arena_init_growing(&arena); arena_err != nil {
|
||
|
|
return .Out_Of_Memory
|
||
|
|
}
|
||
|
|
o.allocator = virtual.arena_allocator(&arena)
|
||
|
|
}
|
||
|
|
defer if opts.allocator.procedure == nil {
|
||
|
|
virtual.arena_destroy(&arena)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The appearance is a form XObject, so it is placed the way every form is
|
||
|
|
// placed: by invoking it with `Do` from a stream whose CTM is the
|
||
|
|
// placement matrix.
|
||
|
|
//
|
||
|
|
// The dictionary this is looked up in holds nothing but the appearance
|
||
|
|
// itself. An appearance stream that has no /Resources of its own therefore
|
||
|
|
// resolves no names at all, and that is right twice over: an annotation is
|
||
|
|
// not page content and inherits nothing from the page (12.5.5), and a
|
||
|
|
// dictionary that dies with this call must not be reachable from anything
|
||
|
|
// that outlives it -- a tiling pattern found through the invoking
|
||
|
|
// resources keeps them on the Document (pattern.odin), which would leave a
|
||
|
|
// dangling pointer behind every annotation.
|
||
|
|
res := new_dict(o.allocator, 2)
|
||
|
|
xobj := new_dict(o.allocator, 1)
|
||
|
|
dict_set(xobj, ANNOT_AP_NAME, a.ap)
|
||
|
|
dict_set(res, "XObject", xobj)
|
||
|
|
|
||
|
|
// An annotation may carry its own optional-content membership (12.5.2).
|
||
|
|
// Wrapping the invocation in a marked-content section hands it to the code
|
||
|
|
// that already evaluates a page's layers, OCMD policies and all, instead
|
||
|
|
// of growing a second copy of it here.
|
||
|
|
src := ANNOT_INVOKE
|
||
|
|
if oc := dict_get_raw(a.dict, "OC"); oc != nil {
|
||
|
|
props := new_dict(o.allocator, 1)
|
||
|
|
dict_set(props, ANNOT_OC_NAME, oc)
|
||
|
|
dict_set(res, "Properties", props)
|
||
|
|
src = ANNOT_INVOKE_OC
|
||
|
|
}
|
||
|
|
|
||
|
|
return run_content(doc, dev, transmute([]byte)src, res, invoke, o)
|
||
|
|
}
|
||
|
|
|
||
|
|
// annot_run_page draws every drawable annotation of a page, in /Annots order,
|
||
|
|
// which is the order they paint over one another in.
|
||
|
|
//
|
||
|
|
// This is the whole of what a renderer has to call: `render_page` runs the
|
||
|
|
// page's content stream and then this, with the same CTM.
|
||
|
|
annot_run_page :: proc(doc: ^Document, page: ^Page, dev: ^Device, page_ctm: Matrix,
|
||
|
|
opts := Content_Options{}) -> Error {
|
||
|
|
if doc == nil || page == nil {
|
||
|
|
return .Invalid_Argument
|
||
|
|
}
|
||
|
|
if !matrix_is_finite(page_ctm) {
|
||
|
|
return .Invalid_Argument
|
||
|
|
}
|
||
|
|
|
||
|
|
// The annotation list is scratch. When the caller supplied an allocator it
|
||
|
|
// is theirs -- a per-page arena reclaimed in one call -- and freeing out of
|
||
|
|
// it here would be at best a no-op and at worst a mismatch.
|
||
|
|
alloc := opts.allocator.procedure != nil ? opts.allocator : context.allocator
|
||
|
|
annots, err := annot_load(doc, page, alloc)
|
||
|
|
if err != .None {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer if opts.allocator.procedure == nil {
|
||
|
|
delete(annots, alloc)
|
||
|
|
}
|
||
|
|
|
||
|
|
n := 0
|
||
|
|
for &a in annots {
|
||
|
|
if a.visible && a.ap != nil {
|
||
|
|
n += 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if n == 0 {
|
||
|
|
return .None
|
||
|
|
}
|
||
|
|
|
||
|
|
// The page's annotations share the token budget the page itself gets. A
|
||
|
|
// file with five hundred annotations all naming one enormous appearance
|
||
|
|
// stream is otherwise five hundred times the work of the page it is on,
|
||
|
|
// and it costs three lines to write.
|
||
|
|
o := opts
|
||
|
|
budget := opts.max_ops > 0 ? opts.max_ops : DEFAULT_CONTENT_FUEL
|
||
|
|
o.max_ops = max(budget / n, 1)
|
||
|
|
|
||
|
|
for &a in annots {
|
||
|
|
// One annotation cannot fail the page. The only error `annot_run` can
|
||
|
|
// return from here is a scratch arena it could not reserve, and the
|
||
|
|
// next annotation may well get one.
|
||
|
|
annot_run(doc, &a, dev, page_ctm, o)
|
||
|
|
}
|
||
|
|
return .None
|
||
|
|
}
|