pub struct Context {Show 75 fields
pub o_stack: Stack,
pub e_stack: Stack,
pub d_stack: Vec<EntityId>,
pub strings: DualStringStore,
pub arrays: DualArrayStore,
pub dicts: DualDictStore,
pub names: NameTable,
pub files: FileStore,
pub loops: Vec<LoopState>,
pub operators: Vec<OpEntry>,
pub systemdict: EntityId,
pub globaldict: EntityId,
pub userdict: EntityId,
pub errordict: EntityId,
pub dollar_error: EntityId,
pub rand_state: u64,
pub rand_seed: i64,
pub current_source_line: u32,
pub packing_mode: bool,
pub echo: bool,
pub name_cache: NameCache,
pub stdout: Box<dyn Write>,
pub save_stack: SaveStack,
pub job_start_save_depth: usize,
pub vm_alloc_mode: bool,
pub object_format: i32,
pub current_operator: Option<NameId>,
pub null_device_used: bool,
pub in_error_handler: bool,
pub initializing: bool,
pub allow_ps_resolution: bool,
pub exit_code: Option<i32>,
pub gstate: GraphicsState,
pub gstate_stack: Vec<GstateEntry>,
pub gstate_store: Vec<GraphicsState>,
pub device: Option<Box<dyn OutputDevice>>,
pub display_list: DisplayList,
pub group_stack: Vec<GroupFrame>,
pub save_group_depths: FxHashMap<u32, usize>,
pub ocg_registry: FxHashMap<NameId, OcgRecord>,
pub next_ocg_id: u32,
pub doc_structure: DocumentStructure,
pub capture_display_lists: Option<Vec<(DisplayList, f64)>>,
pub display_list_sender: Option<Sender<(DisplayList, f64, u32, u32, Option<Arc<Vec<u8>>>, bool)>>,
pub page_width: u32,
pub page_height: u32,
pub output_path: Option<String>,
pub page_filter: Option<HashSet<i32>>,
pub device_factory: Option<Box<dyn Fn(u32, u32) -> Box<dyn OutputDevice>>>,
pub font_directory: EntityId,
pub font_resource_path: Option<String>,
pub next_fid: i32,
pub global_resources: EntityId,
pub local_resources: EntityId,
pub category_registry: EntityId,
pub resource_base_path: Option<String>,
pub user_params: EntityId,
pub system_params: EntityId,
pub internaldict: EntityId,
pub icc_cache: IccCache,
pub exec_sync_fn: Option<ExecSyncFn>,
pub char_width: Option<(f64, f64)>,
pub char_width_mode1: Option<((f64, f64), (f64, f64))>,
pub glyph_caches: FxHashMap<EntityId, GlyphCache>,
pub char_cache_mode: Option<Type3CacheMode>,
pub cshow_pending_cid: Option<i32>,
pub charpath_capture: Option<PsPath>,
pub pattern_store: Vec<PatternData>,
pub form_cache: FxHashMap<EntityId, DisplayList>,
pub cie_decode_cache: FxHashMap<CieDecodeKey, Vec<f64>>,
pub start_time: Option<Instant>,
pub dict_version: u64,
pub name_resolve_cache: Vec<(u64, PsObject)>,
pub interrupt_flag: Option<Arc<AtomicBool>>,
pub yield_after_showpage: bool,
}Fields§
§o_stack: Stack§e_stack: Stack§d_stack: Vec<EntityId>§strings: DualStringStore§arrays: DualArrayStore§dicts: DualDictStore§names: NameTable§files: FileStore§loops: Vec<LoopState>§operators: Vec<OpEntry>§systemdict: EntityId§globaldict: EntityId§userdict: EntityId§errordict: EntityId§dollar_error: EntityId§rand_state: u64§rand_seed: i64Last value handed to srand, returned by rrand. Widened with
PsValue::Int: rrand must return exactly what srand was given.
current_source_line: u32Current source line number (1-based), updated during scanning.
packing_mode: boolPacking mode for array/procedure creation (setpacking/currentpacking).
echo: boolEcho mode for %lineedit/%statementedit (PLRM echo operator).
name_cache: NameCache§stdout: Box<dyn Write>§save_stack: SaveStack§job_start_save_depth: usizeSave stack depth when the current job started (for startjob condition 3).
vm_alloc_mode: bool§object_format: i32Binary object format (0-4). Default 0.
current_operator: Option<NameId>§null_device_used: boolWhether nulldevice was installed at any point during this job.
Sticky for the job, and deliberately not cleared by a grestore that
puts a real device back: it records intent, not current state. A
program that asked for the null device said it wants no output, so
marks it leaves unemitted at end of job are expected rather than a
dropped page worth reporting. The PS test suite is the motivating
case — its files paint into gsave nulldevice ... grestore to
exercise operators, never call showpage, and must not be nagged
about it.
in_error_handler: bool§initializing: boolTrue during init script execution — relaxes access checks.
allow_ps_resolution: boolWhen true, PS programs can change HWResolution via setpagedevice. Set by WASM frontend; CLI leaves false to keep DPI under user control.
exit_code: Option<i32>Process exit code requested by the running PS program via the
.quitwithcode operator. None means “use the default” (0 on
success). The CLI reads this on PsError::Quit and propagates
to std::process::exit.
gstate: GraphicsState§gstate_stack: Vec<GstateEntry>§gstate_store: Vec<GraphicsState>Storage for gstate objects (PsValue::Gstate indexes into this).
device: Option<Box<dyn OutputDevice>>§display_list: DisplayList§group_stack: Vec<GroupFrame>Stack of active transparency-group capture frames. While non-empty,
paint operators emit into the topmost frame’s display list instead
of display_list. endtransparencygroup pops the top frame and
emits a stet_graphics::display_list::DisplayElement::Group into
the next-innermost target. See op_begintransparencygroup /
op_endtransparencygroup in stet-ops::transparency_ops.
save_group_depths: FxHashMap<u32, usize>group_stack.len() recorded at each save. restore consults
this to refuse a revert that would unwind across an unbalanced
begintransparencygroup / endtransparencygroup pair.
ocg_registry: FxHashMap<NameId, OcgRecord>Registry of OCGs (PDF Optional Content Groups) declared via the
defineocg operator. Keyed by the interned NameId of the
human-readable layer name from the OCG dict’s /Name entry.
beginoptionalcontent looks up an OCG by name to obtain the
ocg_id and default_visible it embeds into the emitted
stet_graphics::display_list::OcgVisibility::Single.
next_ocg_id: u32Monotonic counter feeding OcgRecord::ocg_id. Each call to
defineocg increments this; ids never recycle.
doc_structure: DocumentStructureDocument-level structural data — outline, annotations, metadata,
page boxes, etc. — parallel IR to display_list.
PostScript pdfmark operators populate this; the PDF output device
drains it at end-of-job; non-PDF devices ignore it. Document-global:
save / restore do not roll this back. See
stet_graphics::document_structure.
capture_display_lists: Option<Vec<(DisplayList, f64)>>When Some, each showpage clones the display list here before consuming it.
Used by the WASM frontend to retain display lists for viewport re-rendering.
Each entry is (DisplayList, dpi) where dpi is from the pagedevice HWResolution.
display_list_sender: Option<Sender<(DisplayList, f64, u32, u32, Option<Arc<Vec<u8>>>, bool)>>When Some, each showpage sends a clone of the display list through this channel.
Used by the CLI viewer for incremental display list delivery.
Tuple: (DisplayList, dpi, page_width, page_height, effective_cmyk_bytes, cmyk_proofing). PostScript pages always pass
None/false (no PDF/X concept); PDF pages may set these from the
document’s OutputIntent context.
page_width: u32§page_height: u32§output_path: Option<String>§page_filter: Option<HashSet<i32>>Page filter: if set, only render pages in this set (1-based).
device_factory: Option<Box<dyn Fn(u32, u32) -> Box<dyn OutputDevice>>>Factory closure for creating raster devices (registered by CLI).
font_directory: EntityId§font_resource_path: Option<String>§next_fid: i32§global_resources: EntityId§local_resources: EntityId§category_registry: EntityId§resource_base_path: Option<String>§user_params: EntityId§system_params: EntityId§internaldict: EntityIdBacking dict for the internaldict operator.
Created during bootstrap rather than on first use. Context holds the
EntityId for the whole life of the interpreter, so the dict has to
outlive every restore; creating it lazily inside a save bracket would
leave this handle pointing at storage that restore reclaims. Entries
written into it after a save are still reverted normally, by the
dict’s own copy-on-write.
icc_cache: IccCache§exec_sync_fn: Option<ExecSyncFn>§char_width: Option<(f64, f64)>§char_width_mode1: Option<((f64, f64), (f64, f64))>§glyph_caches: FxHashMap<EntityId, GlyphCache>§char_cache_mode: Option<Type3CacheMode>§cshow_pending_cid: Option<i32>§charpath_capture: Option<PsPath>Set while a Type 3 glyph procedure runs under charpath.
PLRM: charpath “obtains the path for the glyph outlines that would
result if string were shown”; for a Type 3 font that means running the
glyph procedure without painting. While this is Some, fill,
eofill and stroke contribute the path they were given here instead
of marking the page — in particular stroke contributes the path as
constructed, since charpath’s own boolean operand, not the glyph
procedure, decides whether the result gets stroked.
pattern_store: Vec<PatternData>Storage for pattern instances created by makepattern.
form_cache: FxHashMap<EntityId, DisplayList>Cache of form display lists keyed by dict EntityId.
cie_decode_cache: FxHashMap<CieDecodeKey, Vec<f64>>Memo of sampled CIE decode tables, keyed by a structural fingerprint of the decode procedure together with the sampled range.
Sampling one table runs the procedure 256 times, and a CIE colour
space installs up to six of them, so a file that re-installs the same
space per page (what pdftops emits for every ICCBased space) pays
thousands of exec_sync calls per page. Worse, decode procedures
routinely contain inline array literals, and every evaluation
allocates a fresh array in the non-reclaiming array arena — turning
the repeated sampling into unbounded memory growth.
Memoising is sound because the PLRM already requires a CIE decode
procedure to be a pure function of its single input: sampling it at
256 points and interpolating (which this code has always done) is the
same assumption. See stet_ops::color_ops::eval_decode_table_range.
start_time: Option<Instant>§dict_version: u64§name_resolve_cache: Vec<(u64, PsObject)>Name resolution cache indexed by NameId. Each entry is (dict_version, resolved_object). Public for inline cache checks in the eval loop’s hot path.
interrupt_flag: Option<Arc<AtomicBool>>When set, the eval loop aborts with PsError::Quit on the next iteration.
Used by the interactive viewer to cancel an in-flight parse when the
user drops a new file.
yield_after_showpage: boolWhen true, each successful showpage / copypage sets interrupt_flag
after capturing the display list, so the eval loop yields back to the
caller one page at a time. The caller clears the flag and re-enters
eval to drive the next page. Used by the WASM viewer to stream
multi-page PostScript documents: page 1 renders while pages 2..N are
still pending interpretation. Requires interrupt_flag to be set.
Implementations§
Source§impl Context
impl Context
Sourcepub fn exec_sync(&mut self, proc_obj: PsObject) -> Result<(), PsError>
pub fn exec_sync(&mut self, proc_obj: PsObject) -> Result<(), PsError>
Execute a PostScript procedure synchronously and return.
Sourcepub fn pump_proc_sources(&mut self, entity: EntityId) -> Result<(), PsError>
pub fn pump_proc_sources(&mut self, entity: EntityId) -> Result<(), PsError>
Run any not-yet-executed procedure data source underneath entity,
replacing it with the bytes it produces.
A filter’s data source may be a procedure (PLRM 3.8.4), which the
filter is supposed to call for more data as the consumer reads. Running
it when filter is called instead is observably wrong: the procedure
runs against whatever is on the operand stack at that moment. pdftops
builds an inline image as
<< /ImageType 1 ... /DataSource { pdfImStr } /LZWDecode filter >> imagemaskso filter is reached while the enclosing << ... >> is still on the
stack, and pdfImStr — which reads its array index state off the
stack — would pick up the half-built dictionary instead.
So the procedure is run here, from the read path, when the consumer’s
operands are the ones in place. Every entry point that reads from a
file must call this first; see [FileHandle::PendingProc].
The drain is one-shot rather than incremental: the procedure is run to completion and the result installed as a plain byte source. That keeps the filters themselves unchanged — none of them has to cope with a source that is temporarily dry — while still running the procedure at the right moment, which is what the bug above is about.
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new context with empty stacks and stores.
Call build_system_dict afterward to populate operators.
Sourcepub fn new_with_output(output: Box<dyn Write>) -> Self
pub fn new_with_output(output: Box<dyn Write>) -> Self
Create a context that captures stdout to a buffer (for testing).
Sourcepub fn dict_load(&mut self, key: &DictKey) -> Option<PsObject>
pub fn dict_load(&mut self, key: &DictKey) -> Option<PsObject>
Look up a name in the dictionary stack (top to bottom).
Sourcepub fn invalidate_name_cache(&mut self)
pub fn invalidate_name_cache(&mut self)
Invalidate the name resolution cache (call on begin/end/def).
Sourcepub fn dict_where(&self, key: &DictKey) -> Option<(EntityId, PsObject)>
pub fn dict_where(&self, key: &DictKey) -> Option<(EntityId, PsObject)>
Look up and return (dict_entity, value) pair.
Sourcepub fn dict_def(&mut self, key: DictKey, value: PsObject) -> Result<(), PsError>
pub fn dict_def(&mut self, key: DictKey, value: PsObject) -> Result<(), PsError>
Store in current dict (top of d_stack).
Sourcepub fn dict_store(
&mut self,
key: DictKey,
value: PsObject,
) -> Result<(), PsError>
pub fn dict_store( &mut self, key: DictKey, value: PsObject, ) -> Result<(), PsError>
Store in first dict that contains key, or current dict if not found.
Sourcepub fn make_dict_key(&mut self, obj: &PsObject) -> Result<DictKey, PsError>
pub fn make_dict_key(&mut self, obj: &PsObject) -> Result<DictKey, PsError>
Convert a PsObject to a DictKey.
Sourcepub fn alloc_loop(&mut self, state: LoopState) -> EntityId
pub fn alloc_loop(&mut self, state: LoopState) -> EntityId
Allocate a new loop state, returning its EntityId.
Sourcepub fn get_loop_mut(&mut self, entity: EntityId) -> &mut LoopState
pub fn get_loop_mut(&mut self, entity: EntityId) -> &mut LoopState
Get a mutable loop state by EntityId.
Sourcepub fn current_display_list_mut(&mut self) -> &mut DisplayList
pub fn current_display_list_mut(&mut self) -> &mut DisplayList
Return the display list paint operators should currently append to.
While a transparency group is active (group_stack non-empty),
the topmost frame’s display list is returned. Otherwise the
page-level display_list is returned. Every paint-emitting
operator must route through this helper to keep group capture
correct.
Sourcepub fn current_display_list(&self) -> &DisplayList
pub fn current_display_list(&self) -> &DisplayList
Read-only counterpart to Self::current_display_list_mut.
Sourcepub fn take_display_list(&mut self) -> DisplayList
pub fn take_display_list(&mut self) -> DisplayList
Take the display list, optionally capturing a clone for viewport re-rendering.
This replaces std::mem::take(&mut ctx.display_list) at showpage/copypage
call sites. When capture_display_lists is active, a clone is saved
along with the current page DPI from the pagedevice HWResolution.
Sourcepub fn current_page_dpi(&self) -> f64
pub fn current_page_dpi(&self) -> f64
Read the current page DPI from the pagedevice HWResolution, defaulting to 72.
pub fn vm_save(&mut self) -> PsObject
Sourcepub fn vm_restore(&mut self, save_id: u32) -> Result<(), PsError>
pub fn vm_restore(&mut self, save_id: u32) -> Result<(), PsError>
Perform a restore: revert VM to the given save state.
Sourcepub fn cow_check_string(&mut self, entity: EntityId)
pub fn cow_check_string(&mut self, entity: EntityId)
Check if a string entity needs COW before mutation. If yes, creates a backup copy and records it.
Sourcepub fn cow_check_array(&mut self, entity: EntityId)
pub fn cow_check_array(&mut self, entity: EntityId)
Check if an array entity needs COW before mutation.
Sourcepub fn dict_put_cow(&mut self, entity: EntityId, key: DictKey, value: PsObject)
pub fn dict_put_cow(&mut self, entity: EntityId, key: DictKey, value: PsObject)
Check if a dict entity needs COW before mutation. Store into a dictionary, copy-on-writing it first.
Prefer this over a bare ctx.dicts.put for any write into a dictionary
the current operation did not itself allocate — FontDirectory, the
resource dictionaries, userdict, a caller-supplied dict. Writing
straight through bypasses save/restore: if the dictionary predates the
current save, no backup is taken and restore will not revert the
entry. That leaves the dictionary holding a value the restore released.
cow_check_dict is cheap and idempotent — it
returns immediately at save level 0, for global entities, and for
dictionaries already copied at this level — so there is no reason to
skip it when in doubt.
pub fn cow_check_dict(&mut self, entity: EntityId)
Sourcepub fn token_to_object(&mut self, token: Token) -> Result<PsObject, PsError>
pub fn token_to_object(&mut self, token: Token) -> Result<PsObject, PsError>
Convert a tokenizer token into a PsObject.
Sourcepub fn reset_local_vm(&mut self)
pub fn reset_local_vm(&mut self)
Reset local VM stores (for job boundary cleanup). Full implementation deferred until job server loop is built.