2222 */
2323
2424import { MathDocumentConstructor , ContainerList } from '../../core/MathDocument.js' ;
25- import { MathItem , STATE } from '../../core/MathItem.js' ;
25+ import { MathItem , STATE , newState } from '../../core/MathItem.js' ;
2626import { HTMLMathItem } from '../../handlers/html/HTMLMathItem.js' ;
2727import { HTMLDocument } from '../../handlers/html/HTMLDocument.js' ;
2828import { HTMLHandler } from '../../handlers/html/HTMLHandler.js' ;
2929import { handleRetriesFor } from '../../util/Retries.js' ;
30+ import { OptionList } from '../../util/Options.js' ;
3031
3132/**
3233 * Add the needed function to the window object.
3334 */
3435declare const window : {
3536 requestIdleCallback : ( callback : ( ) => void ) => void ;
37+ addEventListener : ( ( type : string , handler : ( event : Event ) => void ) => void ) ;
38+ matchMedia : ( type : string ) => {
39+ addListener : ( handler : ( event : Event ) => void ) => void ;
40+ } ;
3641} ;
3742
3843/**
@@ -97,6 +102,8 @@ export class LazyList<N, T, D> {
97102
98103/*==========================================================================*/
99104
105+ newState ( 'LAZYALWAYS' , STATE . FINDMATH + 3 ) ;
106+
100107/**
101108 * The attribute to use for the ID on the marker node
102109 */
@@ -244,16 +251,6 @@ export function LazyMathItemMixin<N, T, D, B extends Constructor<HTMLMathItem<N,
244251 }
245252 }
246253
247- /**
248- * @override
249- */
250- public state ( state : number = undefined , restore : boolean = false ) {
251- //
252- // don't set the state if we are lazy processing
253- //
254- return ( restore === null ? this . _state : super . state ( state , restore ) ) ;
255- }
256-
257254 } ;
258255
259256}
@@ -279,6 +276,21 @@ export interface LazyMathDocument<N, T, D> extends HTMLDocument<N, T, D> {
279276 */
280277 lazyList : LazyList < N , T , D > ;
281278
279+ /**
280+ * The containers whose contents should always be typeset
281+ */
282+ lazyAlwaysContainers : N [ ] ;
283+
284+ /**
285+ * A function that will typeset all the remaining expressions (e.g., for printing)
286+ */
287+ lazyTypesetAll ( ) : Promise < void > ;
288+
289+ /**
290+ * Mark the math items that are to be always typeset
291+ */
292+ lazyAlways ( ) : void ;
293+
282294}
283295
284296/**
@@ -299,6 +311,19 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
299311
300312 return class BaseClass extends BaseDocument {
301313
314+ /**
315+ * @override
316+ */
317+ public static OPTIONS : OptionList = {
318+ ...BaseDocument . OPTIONS ,
319+ lazyMargin : '200px' ,
320+ lazyAlwaysTypeset : null ,
321+ renderActions : {
322+ ...BaseDocument . OPTIONS . renderActions ,
323+ lazyAlways : [ STATE . LAZYALWAYS , 'lazyAlways' , '' , false ]
324+ }
325+ } ;
326+
302327 /**
303328 * The Intersection Observer used to track the appearance of the expression markers
304329 */
@@ -309,6 +334,16 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
309334 */
310335 public lazyList : LazyList < N , T , D > ;
311336
337+ /**
338+ * The containers whose contents should always be typeset
339+ */
340+ public lazyAlwaysContainers : N [ ] = null ;
341+
342+ /**
343+ * Index of last container where math was found in lazyAlwaysContainers
344+ */
345+ public lazyAlwaysIndex : number = 0 ;
346+
312347 /**
313348 * A promise to make sure our compiling/typesetting is sequential
314349 */
@@ -334,21 +369,155 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
334369 * Augment the MathItem class used for this MathDocument,
335370 * then create the intersection observer and lazy list,
336371 * and bind the lazyProcessSet function to this instance
337- * so it can be used as a callback more easily.
372+ * so it can be used as a callback more easily. Add the
373+ * event listeners to typeset everything before printing.
338374 *
339375 * @override
340376 * @constructor
341377 */
342378 constructor ( ...args : any [ ] ) {
343379 super ( ...args ) ;
380+ //
381+ // Use the LazyMathItem for math items
382+ //
344383 this . options . MathItem =
345384 LazyMathItemMixin < N , T , D , Constructor < HTMLMathItem < N , T , D > > > ( this . options . MathItem ) ;
346- this . lazyObserver = new IntersectionObserver ( this . lazyObserve . bind ( this ) ) ;
385+ //
386+ // Allocate a process bit for lazyAlways
387+ //
388+ const ProcessBits = ( this . constructor as typeof HTMLDocument ) . ProcessBits ;
389+ ! ProcessBits . has ( 'lazyAlways' ) && ProcessBits . allocate ( 'lazyAlways' ) ;
390+ //
391+ // Set up the lazy observer and other needed data
392+ //
393+ this . lazyObserver = new IntersectionObserver ( this . lazyObserve . bind ( this ) , { rootMargin : this . options . lazyMargin } ) ;
347394 this . lazyList = new LazyList < N , T , D > ( ) ;
348395 const callback = this . lazyHandleSet . bind ( this ) ;
349- this . lazyProcessSet = ( typeof window !== 'undefined' && window . requestIdleCallback ?
350- ( ) => window . requestIdleCallback ( callback ) :
351- ( ) => setTimeout ( callback , 10 ) ) ;
396+ this . lazyProcessSet = ( window && window . requestIdleCallback ?
397+ ( ) => window . requestIdleCallback ( callback ) :
398+ ( ) => setTimeout ( callback , 10 ) ) ;
399+ //
400+ // Install print listeners to typeset the rest of the document before printing
401+ //
402+ if ( window ) {
403+ let done = false ;
404+ const handler = ( ) => {
405+ ! done && this . lazyTypesetAll ( ) ;
406+ done = true ;
407+ } ;
408+ window . matchMedia ( 'print' ) . addListener ( handler ) ; // for Safari
409+ window . addEventListener ( 'beforeprint' , handler ) ; // for everyone else
410+ }
411+ }
412+
413+ /**
414+ * Check all math items for those that should always be typeset
415+ */
416+ public lazyAlways ( ) {
417+ if ( ! this . lazyAlwaysContainers || this . processed . isSet ( 'lazyAlways' ) ) return ;
418+ for ( const item of this . math ) {
419+ const math = item as LazyMathItem < N , T , D > ;
420+ if ( math . lazyTypeset && this . lazyIsAlways ( math ) ) {
421+ math . lazyCompile = math . lazyTypeset = false ;
422+ }
423+ }
424+ this . processed . set ( 'lazyAlways' ) ;
425+ }
426+
427+ /**
428+ * Check if the MathItem is in one of the containers to always typeset.
429+ * (start looking using the last container where math was found,
430+ * in case the next math is in the same container).
431+ *
432+ * @param {LazyMathItem<N,T,D> } math The MathItem to test
433+ * @return {boolean } True if one of the document's containers holds the MathItem
434+ */
435+ protected lazyIsAlways ( math : LazyMathItem < N , T , D > ) : boolean {
436+ if ( math . state ( ) < STATE . LAZYALWAYS ) {
437+ math . state ( STATE . LAZYALWAYS ) ;
438+ const node = math . start . node ;
439+ const adaptor = this . adaptor ;
440+ const start = this . lazyAlwaysIndex ;
441+ const end = this . lazyAlwaysContainers . length ;
442+ do {
443+ const container = this . lazyAlwaysContainers [ this . lazyAlwaysIndex ] ;
444+ if ( adaptor . contains ( container , node ) ) return true ;
445+ if ( ++ this . lazyAlwaysIndex >= end ) {
446+ this . lazyAlwaysIndex = 0 ;
447+ }
448+ } while ( this . lazyAlwaysIndex !== start ) ;
449+ }
450+ return false ;
451+ }
452+
453+ /**
454+ * @override
455+ */
456+ public state ( state : number , restore : boolean = false ) {
457+ super . state ( state , restore ) ;
458+ if ( state < STATE . LAZYALWAYS ) {
459+ this . processed . clear ( 'lazyAlways' ) ;
460+ }
461+ return this ;
462+ }
463+
464+ /**
465+ * Function to typeset all remaining expressions (for printing, etc.)
466+ *
467+ * @return {Promise } Promise that is resolved after the typesetting completes.
468+ */
469+ public async lazyTypesetAll ( ) : Promise < void > {
470+ //
471+ // The state we need to go back to (COMPILED or TYPESET).
472+ //
473+ let state = STATE . LAST ;
474+ //
475+ // Loop through all the math...
476+ //
477+ for ( const item of this . math ) {
478+ const math = item as LazyMathItem < N , T , D > ;
479+ //
480+ // If it is not lazy compile or typeset, skip it.
481+ //
482+ if ( ! math . lazyCompile && ! math . lazyTypeset ) continue ;
483+ //
484+ // Mark the state that we need to start at.
485+ //
486+ if ( math . lazyCompile ) {
487+ math . state ( STATE . COMPILED - 1 ) ;
488+ state = STATE . COMPILED ;
489+ } else {
490+ math . state ( STATE . TYPESET - 1 ) ;
491+ if ( STATE . TYPESET < state ) state = STATE . TYPESET ;
492+ }
493+ //
494+ // Mark it as not lazy and remove it from the observer.
495+ //
496+ math . lazyCompile = math . lazyTypeset = false ;
497+ math . lazyMarker && this . lazyObserver . unobserve ( math . lazyMarker as any as Element ) ;
498+ }
499+ //
500+ // If something needs updating
501+ //
502+ if ( state === STATE . LAST ) return Promise . resolve ( ) ;
503+ //
504+ // Reset the document state to the starting state that we need.
505+ //
506+ this . state ( state - 1 , null ) ;
507+ //
508+ // Save the SVG font cache and set it to "none" temporarily
509+ // (needed by Firefox, which doesn't seem to process the
510+ // xlinks otherwise).
511+ //
512+ const fontCache = this . outputJax . options . fontCache ;
513+ if ( fontCache ) this . outputJax . options . fontCache = 'none' ;
514+ //
515+ // Typeset the math and put back the font cache when done.
516+ //
517+ this . reset ( ) ;
518+ return handleRetriesFor ( ( ) => this . render ( ) ) . then ( ( ) => {
519+ if ( fontCache ) this . outputJax . options . fontCache = fontCache ;
520+ } ) ;
352521 }
353522
354523 /**
@@ -495,6 +664,21 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
495664 return items ;
496665 }
497666
667+ /**
668+ * @override
669+ */
670+ public render ( ) {
671+ //
672+ // Get the containers whose content should always be typeset
673+ //
674+ const always = this . options . lazyAlwaysTypeset ;
675+ this . lazyAlwaysContainers = ! always ? null :
676+ this . adaptor . getElements ( Array . isArray ( always ) ? always : [ always ] , this . document ) ;
677+ this . lazyAlwaysIndex = 0 ;
678+ super . render ( ) ;
679+ return this ;
680+ }
681+
498682 } ;
499683
500684}
0 commit comments