-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathluatodonotes.lua
2258 lines (1992 loc) · 85.3 KB
/
luatodonotes.lua
1
2
3
4
5
6
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
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
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--
-- Copyright (C) 2014-2020 by Fabian Lipp <fabian.lipp@gmx.de>
-- ------------------------------------------------------------
--
-- This file may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either version 1.2
-- of this license or (at your option) any later version.
-- The latest version of this license is in:
--
-- http://www.latex-project.org/lppl.txt
--
-- and version 1.2 or later is part of all distributions of LaTeX
-- version 1999/12/01 or later.
--
require("lualibs")
--require("debugger")()
local inspect = require('inspect')
local point = require'path_point'
local pathLine = require'path_line'
--local bezier3 = require'path_bezier3'
luatodonotes = {}
-- strings used to switch to standard catcodes for LaTeX packages
local catcodeStart = "\\makeatletter"
local catcodeEnd = "\\makeatother"
local currentPage = 1
local const1In = string.todimen("1in") -- needed for calculations of page borders
-- (used as a constant in TeX)
-- constants set in sty-file
-- + noteInnerSep (inner sep used for tikz nodes)
-- + noteInterSpace (vertical space between notes)
-- + routingAreaWidth (width of the track routing area for opo-leaders)
-- + minNoteWidth (width that must be available for labels to consider the left or
-- right border of the page for placing labels)
-- + distanceNotesPageBorder (distance from the page borders to the outmost point
-- of the labels)
-- + distanceNotesText (horizontal distance between the labels and the text area)
-- + rasterHeight (height of raster for po leader algorithm)
-- + todonotesDebug (activate debug outputs when true)
--
-- values are filled into local variables in function initTodonotes (from
-- corresponding fields luatodonotes.*)
local noteInnerSep = nil
local noteInterSpace = nil
local routingAreaWidth = nil
local minNoteWidth = nil
local distanceNotesPageBorder = nil
local distanceNotesText = nil
local rasterHeight = nil
local todonotesDebug = nil
-- stores information about available algorithms
local positioningAlgos = {}
local splittingAlgos = {}
local leaderTypes = {}
local positioning = nil
local splitting = nil
local leaderType = nil
function luatodonotes.setPositioningAlgo(algo)
if positioningAlgos[algo] ~= nil then
positioning = positioningAlgos[algo]
else
positioning = positioningAlgos["inputOrderStacks"]
tex.print("\\PackageWarningNoLine{luatodonotes}{Invalid value for parameter positioning: " .. algo .. "}")
end
end
function luatodonotes.setSplittingAlgo(algo)
if splittingAlgos[algo] ~= nil then
splitting = splittingAlgos[algo]
else
splitting = splittingAlgos["none"]
tex.print("\\PackageWarningNoLine{luatodonotes}{Invalid value for parameter split: " .. algo .. "}")
end
end
function luatodonotes.setLeaderType(typ)
if leaderTypes[typ] ~= nil then
leaderType = leaderTypes[typ]
else
leaderType = leaderTypes["opo"]
tex.print("\\PackageWarningNoLine{luatodonotes}{Invalid value for parameter leadertype: " .. typ .. "}")
end
end
-- stores the notes for the current page
luatodonotes.notesForPage = {}
local notesForPage = luatodonotes.notesForPage
luatodonotes.notesForNextPage = {}
local notesForNextPage = luatodonotes.notesForNextPage
-- Fields for each note:
-- index: numbers notes in whole document
-- indexOnPage: index of the note in the notesForPage array
-- textbox: links to a hbox that contains the text, which is displayed inside
-- the note
-- origInputX, origInputY: position in which the todo-command was issued
-- inputX, inputY: position to which the leader should be attached (can have a
-- certain offset to origInputX/Y)
-- heightLeft, heightRight: height of the contained text when placed on
-- left/right side
-- pageNr: absolute number of page on which site for note is placed
-- rightSide: true means the note should be placed on the right side;
-- otherwise left side is meant
-- fontsize: fontsize used for paragraph in that the note was defined
-- baselineskip: \baselineskip in the paragraph in that the note was defined
-- normalbaselineskip: \normalbaselineskip in the paragraph in that the note was defined
-- outputX, outputY: position on which the north west anchor of the note should
-- be placed
-- lineColor: color of line connecting note to text
-- backgroundColor: color of background of note
-- borderColor: color of border of note
-- leaderWidth: width of leader (used as argument for tikz line width)
-- sizeCommand: fontsize command given as parameter for this note
--
-- Additional fields for text area:
-- noteType: constant string "area"
-- origInputEndX, origInputEndY: position at which the todo area ends
-- pageNrEnd: absolute number of page on which todo area ends
-- lineCountInArea: highest line index in area
-- linesInArea: positions of lines in area (to detect page/column break)
-- stores the areas for the labels on the current page
-- (calculated in function calcLabelAreaDimensions())
local labelArea = {}
-- stores the positions of the text lines on every page
local linePositions = {}
-- Fields for every position:
-- 1: position of baseline
-- 2: upper bound of line (baseline + height)
-- 3: lower bound of line (baseline - depth)
-- variables used for construction of linePositions list when reading the file
local linePositionsCurPage = {}
local linePositionsPageNr = 0
-- *** metatable for note objects ***
local noteMt = {}
-- getHeight(): yield heightLeft or heightRight depending on rightSide (implemented by metatable)
function noteMt:getHeight()
if self.rightSide then
return self.heightRight
else
return self.heightLeft
end
end
function noteMt:getLabelAnchorY()
local leaderAnchor = positioning.leaderAnchor
local y
if leaderAnchor == "north east" then
y = self.outputY
elseif leaderAnchor == "east" then
y = self.outputY - noteInnerSep - self:getHeight() / 2
else
error("Invalid anchor for algorithm")
end
if positioning.leaderShift then
y = y + self.leaderShiftY
end
return y
end
function noteMt:getInTextAnchorTikz()
return "(" .. self.inputX .. "sp," .. self.inputY .. "sp)"
end
function noteMt:getLabelAnchorTikz()
local leaderAnchor = positioning.leaderAnchor
if leaderAnchor == "north east" and self.rightSide then
leaderAnchor = "north west"
elseif leaderAnchor == "east" and self.rightSide then
leaderAnchor = "west"
end
local shiftStr = ""
if positioning.leaderShift then
shiftStr = "[shift={(" .. self.leaderShiftX .. "sp," .. self.leaderShiftY .. "sp)}]"
end
return "(" .. shiftStr .. " @todonotes@" .. self.index .. " note." .. leaderAnchor .. ")"
end
function noteMt:boxForNoteText(rightSide)
local area = labelArea:getArea(rightSide)
local noteWidth
if area == nil then
noteWidth = minNoteWidth
else
noteWidth = area.noteWidth - 2*noteInnerSep
end
local retval = "\\directlua{tex.box[\"@todonotes@notetextbox\"] = " ..
"node.copy_list(luatodonotes.notesForPage[" .. self.indexOnPage .. "].textbox)}"
retval = retval .. "\\parbox{" .. noteWidth .. "sp}" ..
"{\\raggedright\\unhbox\\@todonotes@notetextbox}"
return retval
end
function noteMt:getClipPathForTodoArea()
-- detext which lines are in same column/page as start of area
local lineCount = self.lineCountInArea
local maxLine = 1
local lines = self.linesInArea
local lastY = lines[1]
while maxLine < lineCount do
if lines[maxLine + 1] < lastY then
maxLine = maxLine + 1
lastY = lines[maxLine]
end
end
local function nodename(i, corner)
return "(@todonotes@" .. self.index .. "@" .. i .. " area" .. corner .. ")"
end
local path = nodename(1, "NW")
local pathLeft = ""
if maxLine == 1 then
-- only one line
path = path .. " -- " .. nodename(1, "NE") ..
" -- " .. nodename(1, "SE")
else
path = path .. " -- " .. nodename(1, "NE") ..
" decorate[@todonotes@todoarea] { -- " .. nodename(1, "SE") .. "}"
end
for i = 2, maxLine do
if i == lineCount then
-- area does not use the whole line
path = path .. " -| " .. nodename(i, "NE") ..
" -- " .. nodename(i, "SE")
else
-- area uses whole line
path = path .. " -- " .. nodename(i, "NE") ..
" decorate[@todonotes@todoarea] { -- " .. nodename(i, "SE") .. "}"
end
pathLeft = " -- " .. nodename(i, "SW") ..
" decorate[@todonotes@todoarea] { -- " .. nodename(i, "NW") .. "}" ..
pathLeft
end
path = path .. pathLeft .. " -| " .. nodename(1, "SW") .. "-- cycle"
return path
end
-- *** label areas ***
-- stores areas for placing labels on current page
function labelArea:getArea(rightSide)
if rightSide then
return self.right
else
return self.left
end
end
-- yields the x-coordinate of the boundary of the label that is pointing
-- towards the text
function labelArea:getXTextSide(rightSide)
if rightSide then
return self.right.left
else
return self.left.right
end
end
function labelArea:isOneSided()
if self.right == nil or self.left == nil then
return true
else
return false
end
end
-- divides notes in two lists (for left and right side)
-- side must be stored in note.rightSide for every note before using this function
local function segmentNotes(notes)
local availableNotesLeft = {}
local availableNotesRight = {}
for k, v in pairs(notes) do
if v.rightSide == true then
table.insert(availableNotesRight, k)
else
table.insert(availableNotesLeft, k)
end
end
return availableNotesLeft, availableNotesRight
end
-- is called by the sty-file when all settings (algorithms etc.) are made
function luatodonotes.initTodonotes()
-- fill local variables (defined at begin of file) with package options
noteInnerSep = luatodonotes.noteInnerSep
noteInterSpace = luatodonotes.noteInterSpace
routingAreaWidth = luatodonotes.routingAreaWidth
minNoteWidth = luatodonotes.minNoteWidth
distanceNotesPageBorder = luatodonotes.distanceNotesPageBorder
distanceNotesText = luatodonotes.distanceNotesText
rasterHeight = luatodonotes.rasterHeight
todonotesDebug = luatodonotes.todonotesDebug
if positioning.needLinePositions then
luatexbase.add_to_callback("post_linebreak_filter", luatodonotes.callbackOutputLinePositions, "outputLinePositions")
tex.print("\\@starttoc{lpo}")
tex.print("\\directlua{lpoFileStream = \\the\\tf@lpo}")
end
end
-- valid values for noteType: nil/"" (for point in text), "area"
function luatodonotes.addNoteToList(index, drawLeader, noteType)
if next(notesForPage) ~= nil
and index <= notesForPage[#notesForPage].index then
-- Index is the same as for one of the previous note.
-- This can happen when commands are read multiple times
-- => don't add anything to list in this case
return
end
local newNote = {}
newNote.index = index
newNote.textbox = node.copy_list(tex.box["@todonotes@notetextbox"])
newNote.baselineskip = tex.dimen["@todonotes@baselineskip"]
newNote.normalbaselineskip = tex.dimen["@todonotes@normalbaselineskip"]
newNote.fontsize = tex.dimen["@todonotes@fontsize"]
newNote.lineColor = tex.toks["@todonotes@toks@currentlinecolor"]
newNote.backgroundColor = tex.toks["@todonotes@toks@currentbackgroundcolor"]
newNote.borderColor = tex.toks["@todonotes@toks@currentbordercolor"]
newNote.leaderWidth = tex.toks["@todonotes@toks@currentleaderwidth"]
newNote.sizeCommand = tex.toks["@todonotes@toks@sizecommand"]
newNote.drawLeader = drawLeader
if noteType == "area" then
newNote.noteType = "area"
newNote.lineCountInArea = 0
-- else: newNote.noteType = nil (default value)
end
setmetatable(newNote, {__index = noteMt})
newNote.indexOnPage = #notesForPage + 1
notesForPage[newNote.indexOnPage] = newNote
end
function luatodonotes.clearNotes()
-- delete the texts for the notes on this page from memory
-- (garbage collection does not work for nodes)
for _, v in pairs(notesForPage) do
node.free(v.textbox)
end
luatodonotes.notesForPage = notesForNextPage
notesForPage = luatodonotes.notesForPage
-- update indexOnPage for the new notes
for k, v in pairs(notesForPage) do
v.indexOnPage = k
end
currentPage = currentPage + 1
end
function luatodonotes.processLastLineInTodoArea()
-- LaTeX counter is accessed as TeX count by prefixing c@
ind = tex.count["c@@todonotes@numberoftodonotes"]
val = tex.count["c@@todonotes@numberofLinesInArea"]
notesForPage[#notesForPage].lineCountInArea = val
end
-- *** constructing the linePositions list ***
function luatodonotes.linePositionsNextPage()
linePositionsPageNr = linePositionsPageNr + 1
linePositionsCurPage = {}
linePositions[linePositionsPageNr] = linePositionsCurPage
end
function luatodonotes.linePositionsAddLine(ycoord, lineheight, linedepth)
linePositionsCurPage[#linePositionsCurPage + 1] = {ycoord, lineheight, linedepth}
end
function luatodonotes.getInputCoordinatesForNotes()
tex.sprint(catcodeStart)
for k, v in ipairs(notesForPage) do
local nodename = "@todonotes@" .. v.index .. " inText"
tex.sprint("\\pgfextractx{\\@todonotes@extractx}{\\pgfpointanchor{" ..
nodename .. "}{center}}")
tex.sprint("\\pgfextracty{\\@todonotes@extracty}{\\pgfpointanchor{" ..
nodename .. "}{center}}")
tex.print("\\directlua{luatodonotes.notesForPage[" .. k .. "].origInputX = " ..
"tex.dimen[\"@todonotes@extractx\"]}")
tex.print("\\directlua{luatodonotes.notesForPage[" .. k .. "].origInputY = " ..
"tex.dimen[\"@todonotes@extracty\"]}")
if v.noteType == "area" then
nodename = nodename .. "End"
tex.sprint("\\pgfextractx{\\@todonotes@extractx}{\\pgfpointanchor{" ..
nodename .. "}{center}}")
tex.sprint("\\pgfextracty{\\@todonotes@extracty}{\\pgfpointanchor{" ..
nodename .. "}{center}}")
tex.print("\\directlua{luatodonotes.notesForPage[" .. k .. "].origInputEndX = " ..
"tex.dimen[\"@todonotes@extractx\"]}")
tex.print("\\directlua{luatodonotes.notesForPage[" .. k .. "].origInputEndY = " ..
"tex.dimen[\"@todonotes@extracty\"]}")
notesForPage[k].linesInArea = {}
for i = 1, v.lineCountInArea do
nodename = "@todonotes@" .. v.index .. "@" .. i .. " areaSW"
tex.sprint("\\pgfextracty{\\@todonotes@extracty}{\\pgfpointanchor{" ..
nodename .. "}{center}}")
tex.print("\\directlua{luatodonotes.notesForPage[" .. k .. "].linesInArea[" ..
i .. "] = " .. "tex.dimen[\"@todonotes@extracty\"]}")
end
end
end
tex.sprint(catcodeEnd)
end
function luatodonotes.calcLabelAreaDimensions()
local routingAreaSpace = 0
if leaderType.needRoutingArea then
routingAreaSpace = routingAreaWidth
end
local top = tex.voffset + tex.dimen.topmargin + const1In
local bottom = top + tex.dimen.headheight + tex.dimen.headsep + tex.dimen.textheight + tex.dimen.footskip
local currentsidemargin = tex.hoffset + tex.dimen["@todonotes@currentsidemargin"] + const1In
local left = {}
left.top = -top
left.bottom = -bottom
left.left = distanceNotesPageBorder
left.right = currentsidemargin - distanceNotesText - routingAreaSpace
if left.right - left.left < minNoteWidth then
-- not enough space left of text
left = nil
else
left.noteWidth = left.right - left.left
end
local right = {}
right.top = -top
right.bottom = -bottom
right.left = currentsidemargin + tex.dimen.textwidth + distanceNotesText + routingAreaSpace
right.right = tex.pagewidth - distanceNotesPageBorder
if right.right - right.left < minNoteWidth then
-- not enough space right of text
right = nil
else
right.noteWidth = right.right - right.left
end
local text = {}
text.left = currentsidemargin
text.right = currentsidemargin + tex.dimen.textwidth
labelArea.left = left
labelArea.right = right
labelArea.text = text
end
function luatodonotes.calcHeightsForNotes()
-- function has to be called outside of a tikzpicture-environment
tex.sprint(catcodeStart)
for k, v in ipairs(notesForPage) do
-- store height for note
-- (is determined by creating a box with the text and reading its size)
-- left side
tex.sprint("\\savebox{\\@todonotes@heightcalcbox}" ..
"{" .. v.sizeCommand .. v:boxForNoteText(false) .. "}")
tex.sprint("\\@todonotes@heightcalcboxdepth=\\dp\\@todonotes@heightcalcbox")
tex.sprint("\\@todonotes@heightcalcboxheight=\\ht\\@todonotes@heightcalcbox")
tex.sprint("\\directlua{luatodonotes.notesForPage[" .. k .. "].heightLeft = " ..
"tex.dimen[\"@todonotes@heightcalcboxheight\"]" ..
" + tex.dimen[\"@todonotes@heightcalcboxdepth\"]}")
-- right side
tex.sprint("\\savebox{\\@todonotes@heightcalcbox}" ..
"{" .. v.sizeCommand .. v:boxForNoteText(true) .. "}")
tex.sprint("\\@todonotes@heightcalcboxdepth=\\dp\\@todonotes@heightcalcbox")
tex.sprint("\\@todonotes@heightcalcboxheight=\\ht\\@todonotes@heightcalcbox")
tex.sprint("\\directlua{luatodonotes.notesForPage[" .. k .. "].heightRight = " ..
"tex.dimen[\"@todonotes@heightcalcboxheight\"]" ..
" + tex.dimen[\"@todonotes@heightcalcboxdepth\"]}")
-- store pageNr for note
-- (is determined as reference to a label)
tex.sprint("\\directlua{luatodonotes.notesForPage[" .. k .. "].pageNr = " ..
"\\zref@extract{@todonotes@" .. v.index .. "}{abspage}}")
if v.noteType == "area" then
tex.sprint("\\directlua{luatodonotes.notesForPage[" .. k .. "].pageNrEnd = " ..
"\\zref@extract{@todonotes@" .. v.index .. "@end}{abspage}}")
end
end
tex.sprint(catcodeEnd)
end
local inputShiftX = string.todimen("-0.05cm") -- sensible value depends on shape of mark
function luatodonotes.printNotes()
print("Drawing notes for page " .. currentPage)
-- seperate notes that should be placed on another page
-- This can occur when note is in a paragraph which doesn't fit on the
-- current page and is thus moved to the next one. But the \todo-command is
-- still read before the shipout of the current page is done
luatodonotes.notesForNextPage = {}
notesForNextPage = luatodonotes.notesForNextPage
local k=1
while k <= #notesForPage do
local v = notesForPage[k]
if v.pageNr == 0 then
-- Notes without a page number occur when the zref label is not
-- defined correctly. This happens with notes in a
-- \caption-command, e.g.
-- In this case two version of the note are stored and we drop the
-- note that does not have a valid page number (the other note
-- seems to have one).
table.remove(notesForPage, k)
if todonotesDebug then
print("deleting note: " .. k .. " (" .. v.index .. ")")
end
elseif v.pageNr ~= currentPage then
table.insert(notesForNextPage, v)
table.remove(notesForPage, k)
if todonotesDebug then
print("moving note to next page: " .. k .. " (" .. v.index .. ")")
end
else
-- update index here (needed if a note was deleted before)
v.indexOnPage = k
k = k + 1
end
end
-- add offset to input coordinates
for _, v in pairs(notesForPage) do
if v.noteType ~= "area" then
v.inputX = v.origInputX + inputShiftX
local bls = v.baselineskip
if v.baselineskip == 0 then
bls = v.normalbaselineskip
end
v.inputY = v.origInputY - 1.3 * (bls - v.fontsize)
else
v.inputX = v.origInputX
v.inputY = v.origInputY
end
end
splitting.algo()
if positioning.twoSided then
local notesLeft, notesRight = segmentNotes(notesForPage)
if #notesLeft > 0 then
positioning.algo(notesLeft, false)
end
if #notesRight > 0 then
positioning.algo(notesRight, true)
end
else
positioning.algo()
end
for k, v in ipairs(notesForPage) do
if todonotesDebug then
local function outputWithPoints(val)
if val ~= nil then
return val .. " (" .. number.topoints(val, "%s%s") .. ")"
else
return ""
end
end
print("-----------------")
print(k .. ": ")
print("index: " .. v.index)
print("origInputX: " .. v.origInputX)
print("origInputY: " .. v.origInputY)
if (v.noteType ~= nil) then
print("noteType: " .. v.noteType)
print("origInputEndX:" .. v.origInputEndX)
print("origInputEndY:" .. v.origInputEndY)
print("lineCountInArea:" .. v.lineCountInArea)
print("linesInArea :" .. inspect(v.linesInArea))
else
print("noteType: nil")
end
print("inputX: " .. v.inputX)
print("inputY: " .. v.inputY)
print("outputX: " .. v.outputX)
print("outputY: " .. v.outputY)
if (v.rasterSlots ~= nil) then
print("rasterSlots: " .. v.rasterSlots)
end
print("baselineskip: " .. outputWithPoints(v.baselineskip))
print("nbaselineskip:" .. outputWithPoints(v.normalbaselineskip))
print("fontsize: " .. outputWithPoints(v.fontsize))
print("textbox: " .. inspect(v.textbox))
print("height: " .. outputWithPoints(v:getHeight()))
print("heightLeft: " .. outputWithPoints(v.heightLeft))
print("heightRight: " .. outputWithPoints(v.heightRight))
print("rightSide: " .. tostring(v.rightSide))
if v.pageNr ~= nil then
print("pageNr: " .. v.pageNr)
end
print("lineColor: " .. v.lineColor)
print("backgroundColor:" .. v.backgroundColor)
print("borderColor: " .. v.borderColor)
print("leaderWidth: " .. v.leaderWidth)
print("sizeCommand: " .. v.sizeCommand)
print("drawLeader: " .. tostring(v.drawLeader))
end
-- print note
tex.print(catcodeStart)
tex.print("\\node[@todonotes@notestyle,anchor=north west," ..
"fill=" .. v.backgroundColor .. ",draw=" .. v.borderColor .. "," ..
"font=" .. v.sizeCommand .. "] " ..
"(@todonotes@" .. v.index ..
" note) at (" .. v.outputX .. "sp," .. v.outputY .. "sp) {" ..
v:boxForNoteText(v.rightSide) .. "};")
tex.print(catcodeEnd)
-- output debugging hints on page
if todonotesDebug then
tex.print("\\node[anchor=north west,text=blue,fill=white,rectangle] at (@todonotes@" .. v.index .. " inText) {" .. v.index .. "};")
tex.print("\\draw[green,fill] (@todonotes@" .. v.index .. " inText) circle(2pt);")
tex.print("\\draw[black,fill] (@todonotes@" .. v.index .. " inText) circle(0.2pt);")
if v.noteType == "area" then
tex.print("\\draw[red,fill] (@todonotes@" .. v.index .. " inTextEnd) circle(2pt);")
end
if (v.noteType ~= nil) then
print(v:getClipPathForTodoArea())
tex.print("\\draw[blue] " .. v:getClipPathForTodoArea() .. ";")
--for i=1, v.lineCountInArea do
--tex.print(" (@todonotes@" .. v.index .. "@" .. i .. " areaSW) -- ")
--end
--tex.print("cycle;")
end
end
end
-- draw leader
leaderType.algo()
-- draw mark in text
for _, v in pairs(notesForPage) do
if v.drawLeader ~= false and v.noteType ~= "area" then
local shiftStr = "(" .. v.inputX .. "sp," .. v.inputY .. "sp)"
tex.print("\\draw[@todonotes@textmark," ..
"draw=" .. v.lineColor .. ",fill=" .. v.lineColor .. "," ..
"shift={" .. shiftStr .. "}," ..
"scale around={0.5:(-0,-0)},shift={(-0.5,-0.1)}]" ..
"(1,0) .. controls (0.5,0.2) and (0.65,0.3) .." ..
"(0.5,0.7) .. controls (0.35,0.3) and (0.5,0.2) .." ..
"(0,0) -- cycle;")
end
end
--- draw label areas when requested
if todonotesDebug then
local area = labelArea.left
if area ~= nil then
tex.print("\\draw[blue] (" .. area.left .. "sp," .. area.top .. "sp) rectangle (" ..
area.right .. "sp," .. area.bottom .. "sp);")
end
area = labelArea.right
if area ~= nil then
tex.print("\\draw[blue] (" .. area.left .. "sp," .. area.top .. "sp) rectangle (" ..
area.right .. "sp," .. area.bottom .. "sp);")
end
end
end
-- ********** Helper Functions **********
-- * comparators for table.sort() *
-- (yields true if first parameter should be placed before second parameter in
-- sorted table)
local function compareNoteInputXAsc(note1, note2)
if note1.inputX < note2.inputX then
return true
end
end
local function compareNoteIndInputXAsc(key1, key2)
if notesForPage[key1].inputX < notesForPage[key2].inputX then
return true
end
end
local function compareNoteIndInputXDesc(key1, key2)
if notesForPage[key1].inputX > notesForPage[key2].inputX then
return true
end
end
local function compareNoteIndInputYDesc(key1, key2)
local v1 = notesForPage[key1]
local v2 = notesForPage[key2]
if v1.inputY > v2.inputY then
return true
elseif v1.inputY == v2.inputY then
if v1.inputX < v2.inputX then
return true
end
end
end
-- * callbacks for Luatex *
local function appendStrToTokenlist(tokenlist, str)
str:gsub(".", function(c)
tokenlist[#tokenlist + 1] = {12, c:byte(), 0}
end)
end
-- writes commands into the node tree that print the absolute position on the
-- page to the output file (streamId is taken from lpoFileStream) at the
-- beginning of every line
-- should be called as post_linebreak_filter
local ID_GLYPH_NODE = node.id("glyph")
local ID_HLIST_NODE = node.id("hlist")
function luatodonotes.callbackOutputLinePositions(head)
while head do
if head.id == ID_HLIST_NODE then
-- check if we are in the main text area (hlists in, e.g.,
-- tikz nodes should have other widths)
if head.width == tex.dimen.textwidth then
-- check if there is a glyph in this hlist
-- -> then we consider it a text line
local foundGlyph = false
local glyphTest = head.head
while glyphTest do
if glyphTest.id == ID_GLYPH_NODE then
foundGlyph = true
break
end
glyphTest = glyphTest.next
end
if foundGlyph then
local w = node.new("whatsit", "write")
w.stream = lpoFileStream
local tokenlist = {
{12, 92, 0}, -- \
{12, 64, 0}, -- @
{12, 116, 0}, -- t
{12, 111, 0}, -- o
{12, 100, 0}, -- d
{12, 111, 0}, -- o
{12, 110, 0}, -- n
{12, 111, 0}, -- o
{12, 116, 0}, -- t
{12, 101, 0}, -- e
{12, 115, 0}, -- s
{12, 64, 0}, -- @
{12, 108, 0}, -- l
{12, 105, 0}, -- i
{12, 110, 0}, -- n
{12, 101, 0}, -- e
{12, 112, 0}, -- p
{12, 111, 0}, -- o
{12, 115, 0}, -- s
{12, 105, 0}, -- i
{12, 116, 0}, -- t
{12, 105, 0}, -- i
{12, 111, 0}, -- o
{12, 110, 0}, -- n
{12, 123, 0} -- {
}
local t = token.create("@todonotes@pdflastypos")
-- the token handling changed with newer LuaTeX versions
if tex.luatexversion > 81 then
tokenlist[#tokenlist + 1] = {0, t.tok}
else
tokenlist[#tokenlist + 1] = t
end
tokenlist[#tokenlist + 1] = {12, 125, 0} -- }
tokenlist[#tokenlist + 1] = {12, 123, 0} -- {
appendStrToTokenlist(tokenlist, tostring(head.height))
tokenlist[#tokenlist + 1] = {12, 125, 0} -- }
tokenlist[#tokenlist + 1] = {12, 123, 0} -- {
appendStrToTokenlist(tokenlist, tostring(head.depth))
tokenlist[#tokenlist + 1] = {12, 125, 0} -- }
w.data = tokenlist
head.head = node.insert_before(head.head,head.head,w)
-- the name of the whatsit node changed with newer LuaTeX versions
local whatsitName
if tex.luatexversion > 80 then
whatsitName = "save_pos"
else
whatsitName = "pdf_save_pos"
end
local w = node.new("whatsit", whatsitName)
head.head = node.insert_before(head.head,head.head,w)
end
end
end
head = head.next
end
return true
end
-- ********** Leader Drawing Algorithms **********
local function drawLeaderPath(note, path)
if note.drawLeader == false then
return
end
local clipPath
if note.noteType == "area" then
clipPath = note:getClipPathForTodoArea()
tex.print("\\begin{scope}")
tex.print("\\clip (current page.north west) rectangle (current page.south east) ")
tex.print(clipPath)
tex.print(";")
end
tex.print("\\draw[@todonotes@leader,draw=" .. note.lineColor ..
",line width=" .. note.leaderWidth .. ",name path=leader] " .. path .. ";")
if note.noteType == "area" then
tex.print("\\path[name path=clipping] " .. clipPath .. ";")
tex.print("\\fill[@todonotes@leader,name intersections={of=leader and clipping, by=x,sort by=leader},fill=" .. note.lineColor .. "] (x) circle(3pt);")
tex.print("\\end{scope}")
end
end
-- ** leader drawing: s-leaders
local function drawSLeaders()
for k, v in ipairs(notesForPage) do
drawLeaderPath(v, v:getLabelAnchorTikz() ..
" -- " .. v:getInTextAnchorTikz())
end
end
leaderTypes["s"] = {algo = drawSLeaders}
-- ** leader drawing: opo-leaders
local function drawOpoLeader(v, opoShift, rightSide)
if rightSide then
opoShift = - opoShift
end
drawLeaderPath(v, v:getLabelAnchorTikz() .. " -- +(" .. opoShift .. "sp,0) " ..
"|- " .. v:getInTextAnchorTikz())
end
local function drawOpoGroup(group, directionDown, rightSide)
if directionDown == nil then
for _, v2 in ipairs(group) do
drawOpoLeader(notesForPage[v2], 0, rightSide)
end
else
if #group == 1 then
-- place p-section of leader in center of routing area
local opoShift = distanceNotesText / 2 + routingAreaWidth / 2
drawOpoLeader(notesForPage[group[1]], opoShift, rightSide)
else
local leaderDistance = routingAreaWidth / (#group - 1)
-- initialise shift value
local nextOpoShift, move
if directionDown then
nextOpoShift = distanceNotesText / 2 + routingAreaWidth
move = -leaderDistance
else
nextOpoShift = distanceNotesText / 2
move = leaderDistance
end
-- cycle through group
for _, v2 in ipairs(group) do
drawOpoLeader(notesForPage[v2], nextOpoShift, rightSide)
nextOpoShift = nextOpoShift + move
end
end
end
end
local function drawOpoLeadersSide(notes, rightSide)
table.sort(notes, compareNoteIndInputYDesc)
local lastDirectionDown = nil
local group = {}
local prevNote
for _, ind in ipairs(notes) do
local v = notesForPage[ind]
local leaderAnchorY = v:getLabelAnchorY()
if leaderAnchorY > v.inputY then
newDirectionDown = true
elseif leaderAnchorY < v.inputY then
newDirectionDown = false
else
newDirectionDown = nil
end
if lastDirectionDown == newDirectionDown and
prevNote ~= nil and
-- following conditions check that leaders would really intersect
-- otherwise we can start a new group
((newDirectionDown and leaderAnchorY >= prevNote.inputY) or
(not newDirectionDown and v.inputY >= prevNote:getLabelAnchorY())) then
-- note belongs to group
table.insert(group, ind)
else
-- draw leaders for group
drawOpoGroup(group, lastDirectionDown, rightSide)
-- initialise new group with this note
lastDirectionDown = newDirectionDown
group = {ind}
end
prevNote = v
end
drawOpoGroup(group, lastDirectionDown, rightSide)
end
local function drawOpoLeaders()
local notesLeft, notesRight = segmentNotes(notesForPage)
if #notesLeft > 0 then
drawOpoLeadersSide(notesLeft, false)
end
if #notesRight > 0 then
drawOpoLeadersSide(notesRight, true)
end
end
leaderTypes["opo"] = {algo = drawOpoLeaders,
needRoutingArea = true}
-- ** leader drawing: po-leaders
local function drawPoLeaders()
for _, v in ipairs(notesForPage) do
drawLeaderPath(v, v:getLabelAnchorTikz() .. " -| " .. v:getInTextAnchorTikz())
end
end
leaderTypes["po"] = {algo = drawPoLeaders}
-- ** leader drawing: os-leaders
local function drawOsLeaders()
for _, v in ipairs(notesForPage) do
local cornerX
if v.rightSide then
cornerX = labelArea.right.left - distanceNotesText / 2 - routingAreaWidth
else
cornerX = labelArea.left.right + distanceNotesText / 2 + routingAreaWidth
end
drawLeaderPath(v, v:getInTextAnchorTikz() ..
" -- (" .. cornerX .. "sp,0 |- 0," .. v.inputY .. "sp) -- " ..
v:getLabelAnchorTikz())
end
end
leaderTypes["os"] = {algo = drawOsLeaders,
needRoutingArea = true}
-- ** leader drawing: s-Bezier-leaders
-- additional fields for each note:
-- leaderArmY
-- movableControlPointX
-- optimalPositionX
-- currentForce
-- forceLimitDec
-- forceLimitInc
-- settings for algorithm
local maxIterations = 1000
local factorRepulsiveControlPoint = 1
local factorAttractingControlPoint = 1
local stopCondition = 65536 -- corresponds to 1pt
local function constructCurve(l)
local curve = {}
-- site
curve[1] = {}
curve[1].x = l.inputX
curve[1].y = l.inputY
-- unmovable control point (middle point of site and movable control point)
curve[2] = {}
curve[2].x = (l.inputX + l.movableControlPointX) / 2
curve[2].y = (l.inputY + l.leaderArmY) / 2
-- movable control point
curve[3] = {}
curve[3].x = l.movableControlPointX
curve[3].y = l.leaderArmY
-- port
curve[4] = {}