Pollen Footnotes

300 views
Skip to first unread message

Joel Dueck

unread,
Jan 22, 2018, 7:38:30 PM1/22/18
to Pollen
We’re getting heavy snow all day today here in Minneapolis, so I took the day off. I spent some of the down time writing up my approach for implementing footnotes in an upcoming project: https://thenotepad.org/posts/pollen-footnotes-approach.html

Hopefully it can be of use to someone! If there’s anything I can correct or clear up, let me know.

Matthew Butterick

unread,
Jan 24, 2018, 3:04:41 PM1/24/18
to Joel Dueck, Pollen

On Jan 22, 2018, at 4:38 PM, Joel Dueck <dueck...@gmail.com> wrote:

We’re getting heavy snow all day today here in Minneapolis, so I took the day off. I spent some of the down time writing up my approach for implementing footnotes in an upcoming project: https://thenotepad.org/posts/pollen-footnotes-approach.html

Thanks a lot for this Joel. I was drawn to your comment in the article:

"The output for footnotes (given my requirements) can’t very well be handled within individual tag functions; it demands a top-down approach."

I was curious why this was necessarily so. I tried refactoring your footnote mechanism into tag functions. (I left out the MD5 fingerprinting and the blank-footnote requirement.) Result below. It seems to work with your samples. Where does this approach break down?


;;;;;;;;;;;;;;;;;

#lang racket/base
(provide (all-defined-out))

(define (fn-id x) (string-append x "_fn"))
(define (fnref-id x) (string-append x "_fnref"))

(define fn-names null)
(define (fn name-in)
  (define name (format "~a" name-in))
  (set! fn-names (if (member name fn-names) fn-names (cons name fn-names)))
  `(sup (a ((href ,(string-append "#" (fnref-id name)))
            (id ,(fn-id name)))
           ,(format "(~a)" (length fn-names)))))

(define fndefs (make-hash))
(define (fndef name . xs) (hash-set! fndefs (format "~a" name) xs))

(define (footnote-block)
  (define note-items
    (for/list ([fn-name (in-list (reverse fn-names))])
              `(li ((id ,(fnref-id fn-name)))
                   ,@(append
                      (hash-ref fndefs fn-name)
                      (list `(a ((href ,(string-append "#" (fn-id fn-name)))) "↩"))))))
  `(section ((class "footnotes")) (ol ,@note-items)))

(define (root . elements)
  `(root ,@elements ,(footnote-block)))






Matthew Butterick

unread,
Jan 24, 2018, 3:23:40 PM1/24/18
to Joel Dueck, Pollen
Correction: `(length fn-names)` should be `(length (member name fn-names))` to handle the possibility of multiple references to the same footnote.

;;;;;;;;;;;;;;;;;;;;;;;;;;

#lang racket/base
(require racket/list)
(provide (all-defined-out))

(define (fn-id x) (string-append x "_fn"))
(define (fnref-id x) (string-append x "_fnref"))

(define fn-names null)
(define (fn name-in)
(define name (format "~a" name-in))
(set! fn-names (if (member name fn-names) fn-names (cons name fn-names)))
`(sup (a ((href ,(string-append "#" (fnref-id name)))
(id ,(fn-id name)))
,(format "(~a)" (length (member name fn-names))))))

Joel Dueck

unread,
Jan 24, 2018, 10:38:01 PM1/24/18
to Pollen
Wow! I suspected there was a more elegant/functional/concise approach that what I’d come up with, but yowsers. I have a lot to learn. (Don’t get me wrong, this is helpful and much appreciated.) 

> I was drawn to your comment in the article:
> "The output for footnotes (given my requirements) can’t very well be handled within individual tag functions; it demands a top-down approach."
> I was curious why this was necessarily so. 

Well, since you ask, I’ve had to think about it. In hindsight, this was a notion I started with and never fully examined. 

I started with it for two (similarly unexamined) reasons. The main one was, it was never clear to me that Pollen gave me any guarantees about the order in which tag functions would be called. Normally, when every tag function is its own cocoon growing its own butterfly, it doesn’t matter, but in a case like this when I needed to things to happen in a certain order I assumed top-down was the natural approach. By the way, I also had the same basic question about decode, i.e., in what order it would apply #:txexpr-proc. Was it innermost-out, left-to-right, etc? So I did some tests to be sure. I found that, conveniently, the #:txexpr-proc will match the ◊fn tags in the same order a left-to-right reader would encounter them in the text, regardless of nesting. If I had given it more thought, I might have figured out that Pollen does the same with the entire doc.

The second reason was, I assumed my requirement to apply some deterministic+unique prefix (the MD5 hash) to the relative URLs dictated that footnote processing needed to start from some place with enough visibility over the doc to determine that prefix from the outset. But now I see that I could just as well have let the tag functions do most of the work and just put the prefixes in afterwards.

Joel Dueck

unread,
Jan 24, 2018, 10:52:57 PM1/24/18
to Pollen
> Correction: `(length fn-names)` should be `(length (member name fn-names))` to handle the possibility of multiple references to the same footnote. 

I made a few more changes to fully handle the multiple references to the same footnote case. The missing footnote case is also easily handled at the point of the call to hash-ref.

(define fn-names null)
(define (fn name-in)
  (define name (format "~a" name-in))
  (set! fn-names (cons name fn-names))
  `(sup (a ((href ,(string-append "#" (fnref-id name)))
            (id ,(string-append (fn-id name) (number->string (count (curry string=? name) fn-names)))))
           ,(format "(~a)" (+ 1 (index-of (remove-duplicates (reverse fn-names)) name))))))

(define fndefs (make-hash))
(define (fndef name . xs) (hash-set! fndefs (format "~a" name) xs))

(define (footnote-block)
  (define note-items
    (for/list ([fn-name (in-list (remove-duplicates (reverse fn-names)))])
              `(li ((id ,(fnref-id fn-name)))
                   ,@(append
                      (or (hash-ref fndefs fn-name #f) '((i "Missing.")))
                      (for/list ([fref-num (in-range (count (curry string=? fn-name) fn-names))])
                                `(a ((href ,(string-append "#" (fn-id fn-name) (format "~a" (+ 1 fref-num))))) "↩"))))))

Matthew Butterick

unread,
Jan 24, 2018, 11:30:28 PM1/24/18
to Joel Dueck, Pollen
On Jan 24, 2018, at 7:38 PM, Joel Dueck <dueck...@gmail.com> wrote:

I found that, conveniently, the #:txexpr-proc will match the ◊fn tags in the same order a left-to-right reader would encounter them in the text, regardless of nesting. If I had given it more thought, I might have figured out that Pollen does the same with the entire doc.

Right. Racket guarantees left-to-right evaluation (though I can't find the docs link for this right now). As you say, the way to think about it is to imagine how it looks as an X-expression, and the evaluation will go in the same order (pre-order depth-first traversal, I believe is the fancy term)


(+ 1 (index-of (remove-duplicates (reverse fn-names)) name))

FWIW this is the same as `(length (member name fn-names))` if you avoid putting duplicates in `fn-names` to begin with. `member` returns the tail of the list beginning with the matching item. So in this case, you'd get a list of the footnote refs from the target name to the beginning (because the list is being accumulated in reverse)


I apologize if I exploded part of your blog post. Occasional unfortunate side effect of being a curious character. May we be united in the quest for knowledge ;)

Matthew Butterick

unread,
Jan 25, 2018, 12:56:19 AM1/25/18
to Joel Dueck, Pollen

On Jan 24, 2018, at 8:30 PM, Matthew Butterick <m...@mbtype.com> wrote:

FWIW this is the same as `(length (member name fn-names))` if you avoid putting duplicates in `fn-names` to begin with. `member` returns the tail of the list beginning with the matching item. So in this case, you'd get a list of the footnote refs from the target name to the beginning (because the list is being accumulated in reverse)


PS That last sentence was poorly phrased.

So in this case, the tail would comprise a list of the footnote names from the target name to the first name collected (because the list is being accumulated in reverse). 

Joel Dueck

unread,
Jan 25, 2018, 9:52:06 AM1/25/18
to Pollen
> (+ 1 (index-of (remove-duplicates (reverse fn-names)) name))

FWIW this is the same as `(length (member name fn-names))` if you avoid putting duplicates in `fn-names` to begin with. `member` returns the tail of the list beginning with the matching item. So in this case, [the tail would comprise a list of the footnote names from the target name to the first name collected (because the list is being accumulated in reverse). ]


OK I understand what you were doing there now. I was a bit confused at the behavior of `member`, although now I see how it could come in handy.

However, I did have to include duplicates (I removed the `if` from `set! fn-names`) because that’s what allows you to include the right number of back-links at the end of each footnote. And the list has to be reversed anyways before duplicates are removed otherwise it will number them according to the last occurence instead of the first.


I apologize if I exploded part of your blog post. Occasional unfortunate side effect of being a curious character. May we be united in the quest for knowledge ;)

Hear hear! No, one of my original reasons for starting the notepad was to have a place for “techie fish-wrap.” If I ever publish a “Pollen Cookbook” [1] hopefully the stuff I put in there will be a little more explosion-proof for having been examined like this.

[1]: https://www.abebooks.com/first-edition/Basic-Cookbook-Tracton-Ken-RADIO-SHACK/4257303723/bd

Jens Axel Søgaard

unread,
Jan 25, 2018, 4:46:50 PM1/25/18
to Joel Dueck, Pollen
Thanks for writing your thoughts on foot/side notes! Great inspiration.

Is there a way to make the notes look pretty even when you print the page?

I tried use "Print..." in Chrome, and it is hard to tell where the notes end.

/Jens Axel


2018-01-23 1:38 GMT+01:00 Joel Dueck <dueck...@gmail.com>:
We’re getting heavy snow all day today here in Minneapolis, so I took the day off. I spent some of the down time writing up my approach for implementing footnotes in an upcoming project: https://thenotepad.org/posts/pollen-footnotes-approach.html

Hopefully it can be of use to someone! If there’s anything I can correct or clear up, let me know.

--
You received this message because you are subscribed to the Google Groups "Pollen" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pollenpub+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
--
Jens Axel Søgaard

Joel Dueck

unread,
Jan 25, 2018, 6:16:48 PM1/25/18
to Pollen
Jens —
  I assume you mean that when you go to https://thenotepad.org/posts/pollen-footnotes-approach.html and print that page in your browser, the footnotes run in with the text, correct?

Two things to note:
1.  The technique I discuss in that article is one that I'll be using on a different site—it is not the technique I actually use on that page. (I wondered/worried if that would be confusing as I was writing it.)
  1a. The footnotes on the page (the sidenote-style ones) don't print well via the browser, and that's actually another point against them that I didn't mention.
  1b. The footnotes made with the technique discussed in the page should print just fine.

2.  If you wish to print out that page, I advise you to use the PDF link (shown just under the title).

Hopefully that makes sense!


On Thursday, January 25, 2018 at 3:46:50 PM UTC-6, Jens Axel Søgaard wrote:
Thanks for writing your thoughts on foot/side notes! Great inspiration.

Is there a way to make the notes look pretty even when you print the page?

I tried use "Print..." in Chrome, and it is hard to tell where the notes end.

/Jens Axel

2018-01-23 1:38 GMT+01:00 Joel Dueck <dueck...@gmail.com>:
We’re getting heavy snow all day today here in Minneapolis, so I took the day off. I spent some of the down time writing up my approach for implementing footnotes in an upcoming project: https://thenotepad.org/posts/pollen-footnotes-approach.html

Hopefully it can be of use to someone! If there’s anything I can correct or clear up, let me know.

--
You received this message because you are subscribed to the Google Groups "Pollen" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pollenpub+...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Jens Axel Søgaard

unread,
Jan 26, 2018, 6:12:54 AM1/26/18
to Joel Dueck, Pollen
2018-01-26 0:16 GMT+01:00 Joel Dueck <dueck...@gmail.com>:
Jens —
  I assume you mean that when you go to https://thenotepad.org/posts/pollen-footnotes-approach.html and 
  print that page in your browser, the footnotes run in with the text, correct?

Yes.
 
Two things to note:
1.  The technique I discuss in that article is one that I'll be using on a different site—it is not the technique I actually use on that page. 
(I wondered/worried if that would be confusing as I was writing it.)

I jumped to conclusions :-)
 
  1a. The footnotes on the page (the sidenote-style ones) don't print well via the browser, and that's actually another point against them that I didn't mention.
  1b. The footnotes made with the technique discussed in the page should print just fine.

My problem is that I know how to generate HTML, but I am inexperienced in writing CSS.
I have problems getting my side notes to show correctly when printed - so I was sort of hoping 
you had some CSS I could steal :-)

Right now pages with side notes become too wide when printed. The text does get the correct
width. The wide page has the side effect of tricking Chrome to scale SVGs *way* too large
though - so I'll need to fix the problem somehow. (I want my side notes to appear as side notes
also in the printed version).

Your discussion on what features you want from footnotes / side notes is very nice,
and I think I'll change this-and-that in how I handle side notes.

/Jens Axel


san...@gmail.com

unread,
Feb 17, 2018, 3:19:41 PM2/17/18
to Pollen
Thanks for sharing! It's nice to see how you tackled this.

I've got something similar working at http://sanchom.github.io/interveners.html (with pollen.rkt at https://github.com/sanchom/sanchom.github.io/blob/master-source/pollen.rkt).

I wrote CSS that turns sidenotes into footnotes in print layouts, and pollen.rkt exposes a function that lets you make notes be footnotes instead of sidenotes in the web view as well.

Sancho

san...@gmail.com

unread,
Feb 17, 2018, 8:10:29 PM2/17/18
to Pollen
Reply all
Reply to author
Forward
0 new messages