Skip to content

Commit 04660f0

Browse files
committed
footnote: add margin-note and backlink support
Support a `#:margin` mode for `define-footnote` as an alternative to putting footnotes in a section created by a `footnote-part` identifier that is bound by `define-footnote`. The margin format is defined by default CSS, and different CSS magic could make cause a "margin note" to actually render differently. At the footnote, hyperlink its number back to the reference to the footnote, as as typical for many web sites with footnotes. Use `scriblib/render-cond` to specialie Latex output and avoid the double inclusion of content previously warned against in the documentation. Meanwhile, warn against limitations of creating footnotes inside footnotes. For testing, use a strategy that has worked well for Rhombus Scribble (and that probably should be made more reusable).
1 parent 6b6007d commit 04660f0

12 files changed

Lines changed: 519 additions & 61 deletions

File tree

scribble-doc/scriblib/scribblings/footnote.scrbl

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,39 @@
1010

1111
@defproc[(note [pre-content pre-content?] ...) element?]{
1212

13-
Creates a margin note for HTML and a footnote for Latex/PDF output.}
13+
Creates a margin note for HTML and a footnote for Latex/PDF output.
1414

15-
@defform[(define-footnote footnote-id footnote-part-id)]{
15+
To produce a numbered note for HTML (which can useful if a CSS
16+
specialization changes how ``margin notes'' are rendered), use
17+
@racket[define-footnote] with @racket[#:margin], instead.
1618

17-
Binds @racket[footnote-id] to a form like @racket[note] that registers a
18-
footnote.
19-
Binds @racket[footnote-part-id] to a function that generates a section to
20-
display the registered footnotes.
21-
(The section generated by @racket[footnote-part-id] will not show a title or
22-
appear in a table of contents; it will look like a footnote area.)
19+
The element generated by @racket[note] uses the style
20+
@racket["NoteBox"] wrapped around an element using the style
21+
@racket["NoteContent"]. CSS and Latex macros configure rendering for
22+
HTML and Latex/PDF to create a margin note or footnote, but those CSS
23+
classes or Latex macros can be redefined or overridden to produce a
24+
different result.
2325

24-
Beware that any content passed to @racket[footnote-id] will occur
25-
twice in at least an intermediate form of the document, and perhaps
26-
also in the rendered form of the document. Consequently, the content
27-
passed to @racket[footnote-id] should not bind link targets or include
28-
other one-time declarations.}
26+
}
27+
28+
@defform*[((define-footnote footnote-id footnote-part-id)
29+
(define-footnote footnote-id #:margin))]{
30+
31+
Binds @racket[footnote-id] to a form like @racket[note] that registers
32+
a footnote. For Latex/PDF output, this is the same result as using
33+
@racket[note].
34+
35+
If @racket[footnote-part-id] is provided, it is bound to a function
36+
that generates a section to display the registered footnotes in HTML.
37+
The section generated by @racket[footnote-part-id] will not show a title or
38+
appear in a table of contents; it will look like a footnote area.
39+
If @racket[#:margin] is supplied instead, then each footnote is
40+
rendered as an immediate margin note, and no separate footnote section
41+
is needed.
42+
43+
Using @racket[footnote-id] within the argument to a
44+
@racket[footnote-id] will not always work, but it can work in
45+
@racket[#:margin] mode for HTML output.
2946

3047
Example:
3148
@codeblock|{
@@ -48,3 +65,40 @@ Example:
4865
@section{March}
4966
March has 30 days.
5067
}|
68+
69+
70+
The elements and parts generated by @racket[footnote-id] and
71+
@racket[footnote-part-id] use the following style names, which can be
72+
redefined as CSS classes or Latex macros to adjust the result:
73+
74+
@itemlist[
75+
76+
@item{@racket["Footnote"]: Style for the result of
77+
@racket[footnote-id], which is mapped to @tt{\footnote} for Latex.}
78+
79+
@item{@racket["FootnoteRef"]: Wrapped around the reference to a
80+
footnote that is rendered by @racket[footnote-part-id] or as a margin
81+
note. For Latex, this name is mapped to a macro that returns nothing,
82+
leaving the reference managment to @tt{\footnote}.}
83+
84+
@item{@racket["FootnoteTarget"]: Wrapped around the footnote that is
85+
rendered by @racket[footnote-part-id] or as a margin note. For Latex,
86+
this name is mapped to a macro that returns nothing, leaving the
87+
reference managment to @tt{\footnote}.}
88+
89+
@item{@racket["FootnoteContent"]: For Latex, wrapped around the
90+
content of a footnote as rendered by @racket[footnote-part-id].}
91+
92+
@item{@racket["FootnoteMarginContent"]: Wrapped around the content of
93+
a footnote as rendered as a margin note. By default, CSS styling for
94+
this name floats the note into the right margin.}
95+
96+
@item{@racket["FootnoteBlock"]: Wrapped around the output of
97+
@racket[footnote-part-id].}
98+
99+
@item{@racket["FootnoteBlockContent"]: Also wrapped around the output of
100+
@racket[footnote-part-id], inside the @racket["FootnoteBlock"] wrapper.}
101+
102+
]
103+
104+
}

scribble-lib/info.rkt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
(define pkg-authors '(mflatt eli))
2323

24-
(define version "1.61")
24+
(define version "1.62")
2525

2626
(define license
2727
'((Apache-2.0 OR MIT)

scribble-lib/scribble/core.rkt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,8 @@
637637
(define (tag-key tg ri)
638638
(if (generated-tag? (cadr tg))
639639
(list (car tg)
640-
(hash-ref (collect-info-tags (resolve-info-ci ri)) (cadr tg)))
640+
(hash-ref (collect-info-tags (resolve-info-ci ri)) (cadr tg)
641+
'unknown))
641642
tg))
642643

643644
(define current-tag-prefixes (make-parameter null))

scribble-lib/scriblib/footnote.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
.NoteBox {
2+
.NoteBox, .FootnoteMarginContent {
33
position: relative;
44
float: right;
55
left: 2em;

scribble-lib/scriblib/footnote.rkt

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#lang racket/base
22

3-
(require scribble/core
3+
(require (for-syntax racket/base)
4+
scribble/core
45
scribble/decode
56
scribble/html-properties
67
scribble/latex-properties
78
racket/promise
89
setup/main-collects
10+
scriblib/render-cond
911
"private/counter.rkt")
1012

1113
(provide note
@@ -32,31 +34,56 @@
3234
(define footnote-style (make-style "Footnote" footnote-style-extras))
3335
(define footnote-ref-style (make-style "FootnoteRef" footnote-style-extras))
3436
(define footnote-content-style (make-style "FootnoteContent" footnote-style-extras))
37+
(define footnote-margin-content-style (make-style "FootnoteMarginContent" footnote-style-extras))
3538
(define footnote-target-style (make-style "FootnoteTarget" footnote-style-extras))
3639
(define footnote-block-style (make-style "FootnoteBlock" footnote-style-extras))
3740
(define footnote-block-content-style (make-style "FootnoteBlockContent" footnote-style-extras))
3841

39-
(define-syntax-rule (define-footnote footnote footnote-part)
40-
(begin
41-
(define footnotes (new-counter "footnote"))
42-
(define id (gensym))
43-
(define (footnote . text) (do-footnote footnotes id text))
44-
(define (footnote-part . text) (do-footnote-part footnotes id))))
42+
(define-syntax (define-footnote stx)
43+
(define (check-identifier id)
44+
(unless (identifier? id)
45+
(raise-syntax-error #f "expected an identifier" stx id)))
46+
(define (generate-footnote footnote-id margin?)
47+
#`(begin
48+
(define footnotes (new-counter "footnote"))
49+
(define id (gensym))
50+
(define (#,footnote-id . text) (do-footnote footnotes id text #,margin?))))
51+
(syntax-case stx ()
52+
[(_ footnote #:margin)
53+
(begin
54+
(check-identifier #'footnote)
55+
(generate-footnote #'footnote #t))]
56+
[(_ footnote footnote-part)
57+
(begin
58+
(check-identifier #'footnote)
59+
(check-identifier #'footnote-part)
60+
#`(begin
61+
#,(generate-footnote #'footnote #f)
62+
(define (footnote-part . text) (do-footnote-part footnotes id))))]))
4563

46-
(define (do-footnote footnotes id text)
64+
(define (do-footnote footnotes id text margin?)
4765
(define tag (generated-tag))
4866
(define content (decode-content text))
67+
(define target (cons (make-element footnote-target-style
68+
(make-element 'superscript
69+
(counter-target footnotes tag #f
70+
#:use-ref? #t)))
71+
content))
4972
(make-traverse-element
5073
(lambda (get set)
51-
(set id
52-
(cons (cons (make-element footnote-target-style
53-
(make-element 'superscript (counter-target footnotes tag #f)))
54-
content)
55-
(get id null)))
74+
(unless margin?
75+
(set id (cons target (get id null))))
5676
(make-element footnote-style
5777
(list (make-element footnote-ref-style
58-
(make-element 'superscript (counter-ref footnotes tag #f)))
59-
(make-element footnote-content-style content))))))
78+
(make-element 'superscript (counter-ref footnotes tag #f
79+
#:use-target? #t)))
80+
(if margin?
81+
(make-element footnote-margin-content-style target)
82+
(cond-element
83+
[latex
84+
(make-element footnote-content-style content)]
85+
[else
86+
null])))))))
6087

6188
(define (do-footnote-part footnotes id)
6289
(make-part
@@ -71,5 +98,8 @@
7198
(make-compound-paragraph
7299
footnote-block-style
73100
(for/list ([content (in-list (reverse (get id null)))])
74-
(make-paragraph footnote-block-content-style content))))))
101+
(make-paragraph footnote-block-content-style
102+
(cond-element
103+
[latex null]
104+
[else content])))))))
75105
null))

scribble-lib/scriblib/footnote.tex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
\newcommand{\FootnoteRef}[1]{}
77
\newcommand{\FootnoteTarget}[1]{}
88
\newcommand{\FootnoteContent}[1]{#1}
9+
\newcommand{\FootnoteMarginContent}[1]{#1}
910

1011
% Redefine \noindent to avoid generating any output at all:
1112
\newenvironment{FootnoteBlock}{\renewcommand{\noindent}{}}{}

scribble-lib/scriblib/private/counter.rkt

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@
2525
#:label-style [label-style #f]
2626
#:label-suffix [label-suffix '()]
2727
#:continue? [continue? #f]
28+
#:use-ref? [use-ref? #f]
2829
. content)
30+
(define (wrap-ref e)
31+
(if use-ref?
32+
(make-link-element #f e (tag->counter-tag counter tag "use"))
33+
e))
2934
(let ([content (decode-content content)])
3035
(define c
3136
(make-target-element
3237
target-style
33-
(list
38+
(wrap-ref
3439
(make-collect-element
3540
#f
3641
(list
@@ -79,35 +84,41 @@
7984
[else (format "x~x" (char->integer c))]))))
8085

8186
(define (counter-ref counter tag label
87+
#:use-target? [use-target? #f]
8288
#:link-render-style [link-style #f])
83-
(make-delayed-element
84-
(lambda (renderer part ri)
85-
(define n (resolve-get part ri (tag->counter-tag counter tag "value")))
86-
(let ([n (if (counter-ref-wrap counter)
87-
((counter-ref-wrap counter)
88-
(format "~a" n)
89-
;; Don't use this argument:
90-
(format "t:~a" (t-encode (list 'counter (list (counter-name counter) tag)))))
91-
(list (format "~a" n)))]
92-
[link-number-only?
93-
(eq? (link-render-style-mode (or link-style (current-link-render-style))) 'number)])
94-
(cond
95-
[(and label link-number-only?)
96-
(make-element
97-
#f
98-
(list label 'nbsp (make-link-element #f (list n) (tag->counter-tag counter tag))))]
99-
[else
100-
(make-link-element #f
101-
(if label
102-
(list label 'nbsp n)
103-
n)
104-
(tag->counter-tag counter tag))])))
105-
(lambda () (if label
106-
(list label 'nbsp "N")
107-
(list "N")))
108-
(lambda () (if label
109-
(list label 'nbsp "N")
110-
(list "N")))))
89+
(define (wrap-target e)
90+
(if use-target?
91+
(make-target-element #f e (tag->counter-tag counter tag "use"))
92+
e))
93+
(wrap-target
94+
(make-delayed-element
95+
(lambda (renderer part ri)
96+
(define n (resolve-get part ri (tag->counter-tag counter tag "value")))
97+
(let ([n (if (counter-ref-wrap counter)
98+
((counter-ref-wrap counter)
99+
(format "~a" n)
100+
;; Don't use this argument:
101+
(format "t:~a" (t-encode (list 'counter (list (counter-name counter) tag)))))
102+
(list (format "~a" n)))]
103+
[link-number-only?
104+
(eq? (link-render-style-mode (or link-style (current-link-render-style))) 'number)])
105+
(cond
106+
[(and label link-number-only?)
107+
(make-element
108+
#f
109+
(list label 'nbsp (make-link-element #f (list n) (tag->counter-tag counter tag))))]
110+
[else
111+
(make-link-element #f
112+
(if label
113+
(list label 'nbsp n)
114+
n)
115+
(tag->counter-tag counter tag))])))
116+
(lambda () (if label
117+
(list label 'nbsp "N")
118+
(list "N")))
119+
(lambda () (if label
120+
(list label 'nbsp "N")
121+
(list "N"))))))
111122

112123
(define (counter-collect-value counter)
113124
(counter-n counter))

0 commit comments

Comments
 (0)