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
125
126
127
128local type, rawset, rawget = type, rawset, rawget
129local concat = table.concat
130local formatters = string.formatters
131local replacesuffix, suffixonly, nameonly, collapsepath = file.replacesuffix, file.suffix, file.nameonly, file.collapsepath
132local openfile, renamefile, removefile = io.open, os.rename, os.remove
133
134local report_system = logs.reporter("system")
135
136local tex = tex
137local texget = tex.get
138
139local nuts = nodes.nuts
140
141local getid = nuts.getid
142local getlist = nuts.getlist
143local setlist = nuts.setlist
144local getnext = nuts.getnext
145local getwhd = nuts.getwhd
146local getsubtype = nuts.getsubtype
147
148local nodecodes = nodes.nodecodes
149local kerncodes = nodes.kerncodes
150
151local nextnode = nuts.traversers.node
152
153local glyph_code = nodecodes.glyph
154local disc_code = nodecodes.disc
155local glue_code = nodecodes.glue
156local penalty_code = nodecodes.penalty
157local kern_code = nodecodes.kern
158local hlist_code = nodecodes.hlist
159local vlist_code = nodecodes.vlist
160local fontkern_code = kerncodes.fontkern
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_outline_rule = nodepool.outlinerule
169local new_kern = nodepool.kern
170
171local getdimensions = nuts.dimensions
172local getrangedimensions = nuts.rangedimensions
173
174local getinputfields = nuts.getinputfields
175local forceinputstatefile = tex.forceinputstatefile
176local forceinputstateline = tex.forceinputstateline
177local getinputstateline = tex.getinputstateline
178local setinputstatemode = tex.setinputstatemode
179
180local foundintree = resolvers.foundintree
181
182local getpagedimensions = layouts.getpagedimensions
183
184local eol <const> = "\010"
185
186
187
188
189
190
191local z_hlist <const> = "[0,0:0,0:0,0,0\010"
192local z_vlist <const> = "(0,0:0,0:0,0,0\010"
193
194local s_hlist <const> = "]\010"
195local s_vlist <const> = ")\010"
196
197local f_hlist_1 = formatters["h%i,%i:%i,%i:%i,%i,%i\010"]
198local f_hlist_2 = formatters["h%i,%i:%i,%s:%i,%i,%i\010"]
199local f_vlist_1 = formatters["v%i,%i:%i,%i:%i,%i,%i\010"]
200local f_vlist_2 = formatters["v%i,%i:%i,%s:%i,%i,%i\010"]
201
202local synctex = luatex.synctex or { }
203luatex.synctex = synctex
204
205local getpos ; getpos = function() getpos = job.positions.getpos return getpos() end
206
207
208
209local enabled = false
210local paused = 0
211local used = false
212local never = false
213
214
215
216local noftags = 0
217local stnums = { }
218local nofblocked = 0
219local blockedfilenames = { }
220local blockedsuffixes = {
221 mkii = true,
222 mkiv = true,
223 mkvi = true,
224 mkxl = true,
225 mklx = true,
226 mkix = true,
227 mkxi = true,
228
229}
230
231local sttags = table.setmetatableindex(function(t,fullname)
232 local name = collapsepath(fullname)
233 if blockedsuffixes[suffixonly(name)] then
234
235 nofblocked = nofblocked + 1
236 return 0
237 elseif blockedfilenames[nameonly(name)] then
238
239 nofblocked = nofblocked + 1
240 return 0
241 elseif foundintree(name) then
242
243 nofblocked = nofblocked + 1
244 return 0
245 else
246 noftags = noftags + 1
247 t[name] = noftags
248 if name ~= fullname then
249 t[fullname] = noftags
250 end
251 stnums[noftags] = name
252 return noftags
253 end
254end)
255
256function synctex.blockfilename(name)
257 blockedfilenames[nameonly(name)] = name
258end
259
260function synctex.setfilename(name,line)
261 if paused == 0 and name then
262 forceinputstatefile(sttags[name])
263 if line then
264 forceinputstateline(line)
265 end
266 end
267end
268
269function synctex.resetfilename()
270 if paused == 0 then
271 forceinputstatefile(0)
272 forceinputstateline(0)
273 end
274end
275
276do
277
278 local nesting = 0
279 local ignored = false
280
281 function synctex.pushline()
282 nesting = nesting + 1
283 if nesting == 1 then
284 local l = getinputstateline()
285 ignored = l and l > 0
286 if not ignored then
287 forceinputstateline(texget("inputlineno"))
288 end
289 end
290 end
291
292 function synctex.popline()
293 if nesting == 1 then
294 if not ignored then
295 forceinputstateline()
296 ignored = false
297 end
298 end
299 nesting = nesting - 1
300 end
301
302end
303
304
305
306local filehandle = nil
307local nofsheets = 0
308local nofobjects = 0
309local last = 0
310local filesdone = 0
311local tmpfile = false
312local logfile = false
313
314local function writeanchor()
315 local size = filehandle:seek("end")
316 filehandle:write("!",size-last,eol)
317 last = size
318end
319
320local function writefiles()
321 local total = #stnums
322 if filesdone < total then
323 for i=filesdone+1,total do
324 filehandle:write("Input:",i,":",stnums[i],eol)
325 end
326 filesdone = total
327 end
328end
329
330local function makenames()
331 logfile = replacesuffix(tex.jobname,"synctex")
332 tmpfile = replacesuffix(logfile,"syncctx")
333end
334
335local function flushpreamble()
336 makenames()
337 filehandle = openfile(tmpfile,"wb")
338 if filehandle then
339 filehandle:setvbuf("full",64*1024)
340 filehandle:write("SyncTeX Version:1",eol)
341 writefiles()
342 filehandle:write("Output:pdf",eol)
343 filehandle:write("Magnification:1000",eol)
344 filehandle:write("Unit:1",eol)
345 filehandle:write("X Offset:0",eol)
346 filehandle:write("Y Offset:0",eol)
347 filehandle:write("Content:",eol)
348 flushpreamble = function()
349 writefiles()
350 return filehandle
351 end
352 else
353 enabled = false
354 end
355 return filehandle
356end
357
358function synctex.wrapup()
359 if tmpfile then
360 renamefile(tmpfile,logfile)
361 tmpfile = nil
362 end
363end
364
365local function flushpostamble()
366 if not filehandle then
367 return
368 end
369 writeanchor()
370 filehandle:write("Postamble:",eol)
371 filehandle:write("Count:",nofobjects,eol)
372 writeanchor()
373 filehandle:write("Post scriptum:",eol)
374 filehandle:close()
375 enabled = false
376end
377
378
379
380
381
382
383
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
416local x_hlist do
417
418 local function doaction_1(data)
419 local pagewidth, pageheight = getpagedimensions()
420 local x, y = getpos()
421 filehandle:write(f_hlist_1(data[1],data[2],x,pageheight-y,data[3],data[4],data[5]))
422 nofobjects = nofobjects + 1
423 end
424
425 local lasty = false
426
427 local function doaction_2(data)
428 local pagewidth, pageheight = getpagedimensions()
429 local x, y = getpos()
430 y = pageheight - y
431 filehandle:write(f_hlist_2(t[1],data[2],x,y == lasty and "=" or y,data[3],data[3],data[5]))
432 lasty = y
433 nofobjects = nofobjects + 1
434 end
435
436 local doaction = doaction_1
437
438 x_hlist = function(head,current,t,l,w,h,d)
439 if filehandle then
440 return insertbefore(head,current,new_latelua { action = doaction, t, l, w, h, d })
441 else
442 return head
443 end
444 end
445
446 directives.register("system.synctex.compression", function(v)
447 doaction = tonumber(v) == 2 and doaction_2 or doaction_1
448 end)
449
450end
451
452
453
454local collect = nil
455local fulltrace = false
456local trace = false
457local height = 10 * 65536
458local depth = 5 * 65536
459local traceheight = 32768
460local tracedepth = 32768
461local outlinewidth = 65536 / 4
462
463trackers.register("system.synctex.visualize", function(v)
464 trace = v
465 fulltrace = v == "real"
466end)
467
468local function inject(head,first,last,tag,line)
469 local w, h, d = getdimensions(first,getnext(last))
470 if h < height then
471 h = height
472 end
473 if d < depth then
474 d = depth
475 end
476 if trace then
477 if fulltrace then
478 head = insertbefore(head,first,new_outline_rule(w,h,d,outlinewidth))
479 else
480 head = insertbefore(head,first,new_rule(w,traceheight,tracedepth))
481 end
482 head = insertbefore(head,first,new_kern(-w))
483 end
484 head = x_hlist(head,first,tag,line,w,h,d)
485 return head
486end
487
488local function collect_min(head)
489 local current = head
490 while current do
491 local id = getid(current)
492 if id == glyph_code then
493 local first = current
494 local last = current
495 local tag = 0
496 local line = 0
497 while true do
498 if id == glyph_code then
499 local tc, lc = getinputfields(current)
500 if tc and tc > 0 then
501 tag = tc
502 line = lc
503 end
504 last = current
505 elseif id == disc_code or (id == kern_code and getsubtype(current) == fontkern_code) then
506 last = current
507 else
508 if tag > 0 then
509 head = inject(head,first,last,tag,line)
510 end
511 break
512 end
513 current = getnext(current)
514 if current then
515 id = getid(current)
516 else
517 if tag > 0 then
518 head = inject(head,first,last,tag,line)
519 end
520 return head
521 end
522 end
523 end
524
525 if id == hlist_code or id == vlist_code then
526 local list = getlist(current)
527 if list then
528 local l = collect(list)
529 if l ~= list then
530 setlist(current,l)
531 end
532 end
533 end
534 current = getnext(current)
535 end
536 return head
537end
538
539local function inject(parent,head,first,last,tag,line)
540 local w, h, d = getrangedimensions(parent,first,getnext(last))
541 if h < height then
542 h = height
543 end
544 if d < depth then
545 d = depth
546 end
547 if trace then
548 if fulltrace then
549 head = insertbefore(head,first,new_outline_rule(w,h,d,outlinewidth))
550 else
551 head = insertbefore(head,first,new_rule(w,traceheight,tracedepth))
552 end
553 head = insertbefore(head,first,new_kern(-w))
554 end
555 head = x_hlist(head,first,tag,line,w,h,d)
556 return head
557end
558
559local function collect_max(head,parent)
560 local current = head
561 while current do
562 local id = getid(current)
563 if id == glyph_code then
564 local first = current
565 local last = current
566 local tag = 0
567 local line = 0
568 while true do
569 if id == glyph_code then
570 local tc, lc = getinputfields(current)
571 if tc and tc > 0 then
572 if tag > 0 and (tag ~= tc or line ~= lc) then
573 head = inject(parent,head,first,last,tag,line)
574 first = current
575 end
576 tag = tc
577 line = lc
578 last = current
579 else
580 if tag > 0 then
581 head = inject(parent,head,first,last,tag,line)
582 tag = 0
583 end
584 first = nil
585 last = nil
586 end
587 elseif id == disc_code then
588 if not first then
589 first = current
590 end
591 last = current
592 elseif id == kern_code and getsubtype(current) == fontkern_code then
593 if first then
594 last = current
595 end
596 elseif id == glue_code then
597 elseif id == penalty_code then
598
599 else
600 if tag > 0 then
601 head = inject(parent,head,first,last,tag,line)
602 tag = 0
603 end
604 break
605 end
606 current = getnext(current)
607 if current then
608 id = getid(current)
609 else
610 if tag > 0 then
611 head = inject(parent,head,first,last,tag,line)
612 end
613 return head
614 end
615 end
616 end
617
618 if id == hlist_code or id == vlist_code then
619 local list = getlist(current)
620 if list then
621 local l = collect(list,current)
622 if l and l ~= list then
623 setlist(current,l)
624 end
625 end
626 end
627 current = getnext(current)
628 end
629 return head
630end
631
632collect = collect_max
633
634function synctex.collect(head,where)
635 if enabled and where ~= "object" then
636 return collect(head,head)
637 else
638 return head
639 end
640end
641
642
643
644function synctex.start()
645 if enabled then
646 nofsheets = nofsheets + 1
647 if flushpreamble() then
648 writeanchor()
649 filehandle:write("{",nofsheets,eol)
650
651 local pagewidth, pageheight = getpagedimensions()
652
653 filehandle:write(z_hlist)
654 filehandle:write(f_vlist_1(0,0,0,pageheight,pagewidth,pageheight,0))
655 end
656 end
657end
658
659function synctex.stop()
660 if enabled then
661
662 filehandle:write(s_hlist)
663 writeanchor()
664 filehandle:write("}",nofsheets,eol)
665 nofobjects = nofobjects + 2
666 end
667end
668
669local enablers = { }
670local disablers = { }
671
672function synctex.registerenabler(f)
673 enablers[#enablers+1] = f
674end
675
676function synctex.registerdisabler(f)
677 disablers[#disablers+1] = f
678end
679
680function synctex.enable()
681 if not never and not enabled then
682 enabled = true
683 setinputstatemode(3)
684 if not used then
685 nodes.tasks.enableaction("shipouts","luatex.synctex.collect")
686 report_system("synctex functionality is enabled, expect 5-10 pct runtime overhead!")
687 used = true
688 end
689 for i=1,#enablers do
690 enablers[i](true)
691 end
692
693 flushpreamble()
694 end
695end
696
697function synctex.disable()
698 if enabled then
699 setinputstatemode(0)
700 report_system("synctex functionality is disabled!")
701 enabled = false
702 for i=1,#disablers do
703 disablers[i](false)
704 end
705 end
706end
707
708function synctex.finish()
709 if enabled then
710 flushpostamble()
711 else
712 makenames()
713 removefile(logfile)
714 removefile(tmpfile)
715 end
716end
717
718local filename = nil
719
720function synctex.pause()
721 paused = paused + 1
722 if enabled and paused == 1 then
723 setinputstatemode(0)
724 end
725end
726
727function synctex.resume()
728 if enabled and paused == 1 then
729 setinputstatemode(3)
730 end
731 paused = paused - 1
732end
733
734
735
736luatex.registerstopactions(synctex.finish)
737
738statistics.register("synctex tracing",function()
739 if used then
740 return string.format("%i referenced files, %i files ignored, %i objects flushed, logfile: %s",
741 noftags,nofblocked,nofobjects,logfile)
742 end
743end)
744
745local implement = interfaces.implement
746local variables = interfaces.variables
747
748function synctex.setup(t)
749 if t.state == variables.never then
750 synctex.disable()
751 never = true
752 return
753 end
754 if t.method == variables.max then
755 collect = collect_max
756 else
757 collect = collect_min
758 end
759 if t.state == variables.start then
760 synctex.enable()
761 else
762 synctex.disable()
763 end
764end
765
766implement {
767 name = "synctexblockfilename",
768 arguments = "string",
769 protected = true,
770 public = true,
771 actions = synctex.blockfilename,
772}
773
774implement {
775 name = "synctexsetfilename",
776 arguments = "string",
777 protected = true,
778 public = true,
779 actions = synctex.setfilename,
780}
781
782implement {
783 name = "synctexresetfilename",
784 protected = true,
785 public = true,
786 actions = synctex.resetfilename,
787}
788
789implement {
790 name = "setupsynctex",
791 actions = synctex.setup,
792 arguments = {
793 {
794 { "state" },
795 { "method" },
796 },
797 },
798}
799
800implement {
801 name = "synctexpause",
802 actions = synctex.pause,
803}
804
805implement {
806 name = "synctexresume",
807 actions = synctex.resume,
808}
809
810implement {
811 name = "synctexpushline",
812 actions = synctex.pushline,
813}
814
815implement {
816 name = "synctexpopline",
817 actions = synctex.popline,
818}
819
820implement {
821 name = "synctexdisable",
822 actions = synctex.disable,
823}
824 |