1if not modules then modules = { } end modules ['node-syn'] = {
2 version = 1.001,
3 comment = "companion to node-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125local type, rawset = type, rawset
126local concat = table.concat
127local formatters = string.formatters
128local replacesuffix, suffixonly, nameonly, collapsepath = file.replacesuffix, file.suffix, file.nameonly, file.collapsepath
129local openfile, renamefile, removefile = io.open, os.rename, os.remove
130
131local report_system = logs.reporter("system")
132
133local tex = tex
134local texget = tex.get
135
136local nuts = nodes.nuts
137
138local getid = nuts.getid
139local getlist = nuts.getlist
140local setlist = nuts.setlist
141local getnext = nuts.getnext
142local getwhd = nuts.getwhd
143local getwidth = nuts.getwidth
144local getsubtype = nuts.getsubtype
145
146local nodecodes = nodes.nodecodes
147local kerncodes = nodes.kerncodes
148
149local glyph_code = nodecodes.glyph
150local disc_code = nodecodes.disc
151local glue_code = nodecodes.glue
152local penalty_code = nodecodes.penalty
153local kern_code = nodecodes.kern
154
155local hlist_code = nodecodes.hlist
156local vlist_code = nodecodes.vlist
157local dir_code = nodecodes.dir
158local fontkern_code = kerncodes.fontkern
159
160local cancel_code = nodes.dircodes.cancel
161
162local insertbefore = nuts.insertbefore
163local insertafter = nuts.insertafter
164
165local nodepool = nuts.pool
166local new_latelua = nodepool.latelua
167local new_rule = nodepool.rule
168local new_kern = nodepool.kern
169
170local getdimensions = nuts.dimensions
171local getrangedimensions = nuts.rangedimensions
172
173local getsynctexfields = nuts.getsynctexfields
174local forcesynctextag = tex.forcesynctextag or tex.force_synctex_tag
175local forcesynctexline = tex.forcesynctexline or tex.force_synctex_line
176local getsynctexline = tex.getsynctexline or tex.get_synctex_line
177local setsynctexmode = tex.setsynctexmode or tex.set_synctex_mode
178
179local foundintree = resolvers.foundintree
180
181local eol = "\010"
182
183
184
185
186
187
188local z_hlist = "[0,0:0,0:0,0,0\010"
189local z_vlist = "(0,0:0,0:0,0,0\010"
190
191local s_hlist = "]\010"
192local s_vlist = ")\010"
193
194local f_hlist_1 = formatters["h%i,%i:%i,%i:%i,%i,%i\010"]
195local f_hlist_2 = formatters["h%i,%i:%i,%s:%i,%i,%i\010"]
196local f_vlist_1 = formatters["v%i,%i:%i,%i:%i,%i,%i\010"]
197local f_vlist_2 = formatters["v%i,%i:%i,%s:%i,%i,%i\010"]
198
199local synctex = luatex.synctex or { }
200luatex.synctex = synctex
201
202local getpos ; getpos = function() getpos = job.positions.getpos return getpos() end
203
204
205
206local enabled = false
207local paused = 0
208local used = false
209local never = false
210
211
212
213if tex.set_synctex_no_files then
214 tex.set_synctex_no_files(1)
215end
216
217
218
219local noftags = 0
220local stnums = { }
221local nofblocked = 0
222local blockedfilenames = { }
223local blockedsuffixes = {
224 mkii = true,
225 mkiv = true,
226 mkvi = true,
227 mkxl = true,
228 mklx = true,
229 mkix = true,
230 mkxi = true,
231
232}
233
234local sttags = table.setmetatableindex(function(t,fullname)
235 local name = collapsepath(fullname)
236 if blockedsuffixes[suffixonly(name)] then
237
238 nofblocked = nofblocked + 1
239 return 0
240 elseif blockedfilenames[nameonly(name)] then
241
242 nofblocked = nofblocked + 1
243 return 0
244 elseif foundintree(name) then
245
246 nofblocked = nofblocked + 1
247 return 0
248 else
249 noftags = noftags + 1
250 t[name] = noftags
251 if name ~= fullname then
252 t[fullname] = noftags
253 end
254 stnums[noftags] = name
255 return noftags
256 end
257end)
258
259function synctex.blockfilename(name)
260 blockedfilenames[nameonly(name)] = name
261end
262
263function synctex.setfilename(name,line)
264 if paused == 0 and name then
265 forcesynctextag(sttags[name])
266 if line then
267 forcesynctexline(line)
268 end
269 end
270end
271
272function synctex.resetfilename()
273 if paused == 0 then
274 forcesynctextag(0)
275 forcesynctexline(0)
276 end
277end
278
279do
280
281 local nesting = 0
282 local ignored = false
283
284 function synctex.pushline()
285 nesting = nesting + 1
286 if nesting == 1 then
287 local l = getsynctexline()
288 ignored = l and l > 0
289 if not ignored then
290 forcesynctexline(texget("inputlineno"))
291 end
292 end
293 end
294
295 function synctex.popline()
296 if nesting == 1 then
297 if not ignored then
298 forcesynctexline()
299 ignored = false
300 end
301 end
302 nesting = nesting - 1
303 end
304
305end
306
307
308
309local filehandle = nil
310local nofsheets = 0
311local nofobjects = 0
312local last = 0
313local filesdone = 0
314local tmpfile = false
315local logfile = false
316
317local function writeanchor()
318 local size = filehandle:seek("end")
319 filehandle:write("!",size-last,eol)
320 last = size
321end
322
323local function writefiles()
324 local total = #stnums
325 if filesdone < total then
326 for i=filesdone+1,total do
327 filehandle:write("Input:",i,":",stnums[i],eol)
328 end
329 filesdone = total
330 end
331end
332
333local function makenames()
334 logfile = replacesuffix(tex.jobname,"synctex")
335 tmpfile = replacesuffix(logfile,"syncctx")
336end
337
338local function flushpreamble()
339 makenames()
340 filehandle = openfile(tmpfile,"wb")
341 if filehandle then
342 filehandle:write("SyncTeX Version:1",eol)
343 writefiles()
344 filehandle:write("Output:pdf",eol)
345 filehandle:write("Magnification:1000",eol)
346 filehandle:write("Unit:1",eol)
347 filehandle:write("X Offset:0",eol)
348 filehandle:write("Y Offset:0",eol)
349 filehandle:write("Content:",eol)
350 flushpreamble = function()
351 writefiles()
352 return filehandle
353 end
354 else
355 enabled = false
356 end
357 return filehandle
358end
359
360function synctex.wrapup()
361 if tmpfile then
362 renamefile(tmpfile,logfile)
363 tmpfile = nil
364 end
365end
366
367local function flushpostamble()
368 if not filehandle then
369 return
370 end
371 writeanchor()
372 filehandle:write("Postamble:",eol)
373 filehandle:write("Count:",nofobjects,eol)
374 writeanchor()
375 filehandle:write("Post scriptum:",eol)
376 filehandle:close()
377 enabled = false
378end
379
380local getpagedimensions getpagedimensions = function()
381 getpagedimensions = backends.codeinjections.getpagedimensions
382 return getpagedimensions()
383end
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430local x_hlist do
431
432 local function doaction_1(t,l,w,h,d)
433 local pagewidth, pageheight = getpagedimensions()
434 local x, y = getpos()
435 filehandle:write(f_hlist_1(t,l,x,pageheight-y,w,h,d))
436 nofobjects = nofobjects + 1
437 end
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458 local lasty = false
459
460 local function doaction_2(t,l,w,h,d)
461 local pagewidth, pageheight = getpagedimensions()
462 local x, y = getpos()
463 y = pageheight - y
464 filehandle:write(f_hlist_2(t,l,x,y == lasty and "=" or y,w,h,d))
465 lasty = y
466 nofobjects = nofobjects + 1
467 end
468
469 local doaction = doaction_1
470
471 x_hlist = function(head,current,t,l,w,h,d)
472 if filehandle then
473 return insertbefore(head,current,new_latelua(function() doaction(t,l,w,h,d) end))
474 else
475 return head
476 end
477 end
478
479 directives.register("system.synctex.compression", function(v)
480 doaction = tonumber(v) == 2 and doaction_2 or doaction_1
481 end)
482
483end
484
485
486
487local collect = nil
488local fulltrace = false
489local trace = false
490local height = 10 * 65536
491local depth = 5 * 65536
492local traceheight = 32768
493local tracedepth = 32768
494
495trackers.register("system.synctex.visualize", function(v)
496 trace = v
497 fulltrace = v == "real"
498end)
499
500local function inject(head,first,last,tag,line)
501 local w, h, d = getdimensions(first,getnext(last))
502 if h < height then
503 h = height
504 end
505 if d < depth then
506 d = depth
507 end
508 if trace then
509 head = insertbefore(head,first,new_rule(w,fulltrace and h or traceheight,fulltrace and d or tracedepth))
510 head = insertbefore(head,first,new_kern(-w))
511 end
512 head = x_hlist(head,first,tag,line,w,h,d)
513 return head
514end
515
516local function collect_min(head)
517 local current = head
518 while current do
519 local id = getid(current)
520 if id == glyph_code then
521 local first = current
522 local last = current
523 local tag = 0
524 local line = 0
525 while true do
526 if id == glyph_code then
527 local tc, lc = getsynctexfields(current)
528 if tc and tc > 0 then
529 tag = tc
530 line = lc
531 end
532 last = current
533 elseif id == disc_code or (id == kern_code and getsubtype(current) == fontkern_code) then
534 last = current
535 else
536 if tag > 0 then
537 head = inject(head,first,last,tag,line)
538 end
539 break
540 end
541 current = getnext(current)
542 if current then
543 id = getid(current)
544 else
545 if tag > 0 then
546 head = inject(head,first,last,tag,line)
547 end
548 return head
549 end
550 end
551 end
552
553 if id == hlist_code or id == vlist_code then
554 local list = getlist(current)
555 if list then
556 local l = collect(list)
557 if l ~= list then
558 setlist(current,l)
559 end
560 end
561 end
562 current = getnext(current)
563 end
564 return head
565end
566
567local function inject(parent,head,first,last,tag,line)
568 local w, h, d = getrangedimensions(parent,first,getnext(last))
569 if h < height then
570 h = height
571 end
572 if d < depth then
573 d = depth
574 end
575 if trace then
576 head = insertbefore(head,first,new_rule(w,fulltrace and h or traceheight,fulltrace and d or tracedepth))
577 head = insertbefore(head,first,new_kern(-w))
578 end
579 head = x_hlist(head,first,tag,line,w,h,d)
580 return head
581end
582
583local function collect_max(head,parent)
584 local current = head
585 while current do
586 local id = getid(current)
587 if id == glyph_code then
588 local first = current
589 local last = current
590 local tag = 0
591 local line = 0
592 while true do
593 if id == glyph_code then
594 local tc, lc = getsynctexfields(current)
595 if tc and tc > 0 then
596 if tag > 0 and (tag ~= tc or line ~= lc) then
597 head = inject(parent,head,first,last,tag,line)
598 first = current
599 end
600 tag = tc
601 line = lc
602 last = current
603 else
604 if tag > 0 then
605 head = inject(parent,head,first,last,tag,line)
606 tag = 0
607 end
608 first = nil
609 last = nil
610 end
611 elseif id == disc_code then
612 if not first then
613 first = current
614 end
615 last = current
616 elseif id == kern_code and getsubtype(current) == fontkern_code then
617 if first then
618 last = current
619 end
620 elseif id == glue_code then
621 if tag > 0 then
622 local tc, lc = getsynctexfields(current)
623 if tc and tc > 0 then
624 if tag ~= tc or line ~= lc then
625 head = inject(parent,head,first,last,tag,line)
626 tag = 0
627 break
628 end
629 else
630 head = inject(parent,head,first,last,tag,line)
631 tag = 0
632 break
633 end
634 else
635 tag = 0
636 break
637 end
638 id = nil
639 elseif id == penalty_code then
640
641 else
642 if tag > 0 then
643 head = inject(parent,head,first,last,tag,line)
644 tag = 0
645 end
646 break
647 end
648 current = getnext(current)
649 if current then
650 id = getid(current)
651
652
653
654
655
656
657
658
659
660
661
662
663
664 else
665 if tag > 0 then
666 head = inject(parent,head,first,last,tag,line)
667 end
668 return head
669 end
670 end
671 end
672
673 if id == hlist_code or id == vlist_code then
674 local list = getlist(current)
675 if list then
676 local l = collect(list,current)
677 if l and l ~= list then
678 setlist(current,l)
679 end
680 end
681 end
682 current = getnext(current)
683 end
684 return head
685end
686
687collect = collect_max
688
689function synctex.collect(head,where)
690 if enabled and where ~= "object" then
691 return collect(head,head)
692 else
693 return head
694 end
695end
696
697
698
699function synctex.start()
700 if enabled then
701 nofsheets = nofsheets + 1
702 if flushpreamble() then
703 writeanchor()
704 filehandle:write("{",nofsheets,eol)
705
706 local pagewidth, pageheight = getpagedimensions()
707 filehandle:write(z_hlist)
708 filehandle:write(f_vlist_1(0,0,0,pageheight,pagewidth,pageheight,0))
709 end
710 end
711end
712
713function synctex.stop()
714 if enabled then
715
716 filehandle:write(s_hlist)
717 writeanchor()
718 filehandle:write("}",nofsheets,eol)
719 nofobjects = nofobjects + 2
720 end
721end
722
723local enablers = { }
724local disablers = { }
725
726function synctex.registerenabler(f)
727 enablers[#enablers+1] = f
728end
729
730function synctex.registerdisabler(f)
731 disablers[#disablers+1] = f
732end
733
734function synctex.enable()
735 if not never and not enabled then
736 enabled = true
737 setsynctexmode(3)
738 if not used then
739 nodes.tasks.enableaction("shipouts","luatex.synctex.collect")
740 report_system("synctex functionality is enabled, expect 5-10 pct runtime overhead!")
741 used = true
742 end
743 for i=1,#enablers do
744 enablers[i](true)
745 end
746 end
747end
748
749function synctex.disable()
750 if enabled then
751 setsynctexmode(0)
752 report_system("synctex functionality is disabled!")
753 enabled = false
754 for i=1,#disablers do
755 disablers[i](false)
756 end
757 end
758end
759
760function synctex.finish()
761 if enabled then
762 flushpostamble()
763 else
764 makenames()
765 removefile(logfile)
766 removefile(tmpfile)
767 end
768end
769
770local filename = nil
771
772function synctex.pause()
773 paused = paused + 1
774 if enabled and paused == 1 then
775 setsynctexmode(0)
776 end
777end
778
779function synctex.resume()
780 if enabled and paused == 1 then
781 setsynctexmode(3)
782 end
783 paused = paused - 1
784end
785
786
787
788luatex.registerstopactions(synctex.finish)
789
790statistics.register("synctex tracing",function()
791 if used then
792 return string.format("%i referenced files, %i files ignored, %i objects flushed, logfile: %s",
793 noftags,nofblocked,nofobjects,logfile)
794 end
795end)
796
797local implement = interfaces.implement
798local variables = interfaces.variables
799
800function synctex.setup(t)
801 if t.state == variables.never then
802 synctex.disable()
803 never = true
804 return
805 end
806 if t.method == variables.max then
807 collect = collect_max
808 else
809 collect = collect_min
810 end
811 if t.state == variables.start then
812 synctex.enable()
813 else
814 synctex.disable()
815 end
816end
817
818implement {
819 name = "synctexblockfilename",
820 arguments = "string",
821 actions = synctex.blockfilename,
822}
823
824implement {
825 name = "synctexsetfilename",
826 arguments = "string",
827 actions = synctex.setfilename,
828}
829
830implement {
831 name = "synctexresetfilename",
832 actions = synctex.resetfilename,
833}
834
835implement {
836 name = "setupsynctex",
837 actions = synctex.setup,
838 arguments = {
839 {
840 { "state" },
841 { "method" },
842 },
843 },
844}
845
846implement {
847 name = "synctexpause",
848 actions = synctex.pause,
849}
850
851implement {
852 name = "synctexresume",
853 actions = synctex.resume,
854}
855
856implement {
857 name = "synctexpushline",
858 actions = synctex.pushline,
859}
860
861implement {
862 name = "synctexpopline",
863 actions = synctex.popline,
864}
865
866implement {
867 name = "synctexdisable",
868 actions = synctex.disable,
869}
870 |