2

The image below shows the result for the second page of the following code:

enter image description here

\fontfam[Myriad]

\margins/2 a4 (2,6,2,2)cm 
\mnotesize=4.5cm 
 
\newcount\notenum 
%\fixmnotes\right 
\def\note#1{\global\advance\notenum by 1 
   \fnmark{\the\notenum}% 
   \mnote{\fnmark{\the\notenum}\kern.3em #1}% 
} 

\def\fnmark#1{\leavevmode\raise.7ex\hbox{\setfontsize{mag.6}\setff{+lnum}\setff{-onum}\currvar #1}}

%and use:
%
%Text\fnmark{69} text.
%
%If you want to use this in footnotes, try to define:

\def\_printfnotemark{\fnmark{\_fnotenum}}


\onum\rm

ASDF1234567890\note{asdf}

ASDF\note{asdf} And this note will be above the next one \mnoteskip -\baselineskip\note{asdf}

\mnoteskip =-\baselineskip
ASDF\note{asdf}


\lipsum[1]\note{asdf}

\vfill \eject


ASDF1234567890\note{This is a very long note to check out if we’re going to need more space below.}

ASDF \mnoteskip-2\baselineskip \note{asdf} And this note will be above the next one. \mnoteskip-3\baselineskip \note{asdf}\mnoteskip-4\baselineskip\note{asdf}

ASDF\mnoteskip-5\baselineskip \note{asdf}


\lipsum[1]\note{asdf}



\bye

The worrisome part is that we have to adjust the vertical space manually with \mnoteskip whenever there is more than one note or a long note. Is there a way to set this space according to the space of the previous note?

2
  • 2
    If a simple solution of your task existed then it would been implemented in OpTeX. IMHO, this isn't trivial task. What the automaton should do if there are more "notes" material than "normal pages" material in the document? The positions of notes can be saved to the ref file and read them in the next OpTeX run and diagnose if there are overlapping notes and then decide, what to do with overlapping notes. But how to decide? We need a reasonable rule.
    – wipet
    Jun 25, 2023 at 4:44
  • I was thinking something like the latex package scrlayer-notecolumn (tex.stackexchange.com/questions/295864/…), but now I see it's very difficult to implement anyway.
    – user574859
    Jun 25, 2023 at 10:48

1 Answer 1

6

If we add the sidenotes at shipout time using Lua, we can make sure that nothing collides. With this mechanism, we only need a single pass, and we don't need to write to any .aux or .ref files.

sidenote-test.tex

\fontfam[lm]

\margins/2 letter (1,3,2,2)in
\parindent=0pt
\parskip=1ex

\addto\_begoutput{%
    \ifodd\pageno%
        \sidenote_side\right%
    \else%
        \sidenote_side\left%
    \fi%
}

\load[sidenote]

\lorem[1.]\sidenote{\lorem[1.]}

\lorem[2.]\sidenote{\lorem[2.]}

\def\_par{\let\_par=\par}
\lorem[3]\sidenote{\lorem[3.]}

\lorem[4.]\sidenote{\lorem[4.]}

\lorem[5.]\sidenote{\lorem[5.]}
\lorem[6.]\sidenote{\lorem[6.]}
\lorem[7.]\sidenote{\lorem[7.]}

\vfill\eject

\sidenote_number=199
\def\sidenote_makemark{%
    \leavevmode%
    \raise 0.7ex%
    \hbox{\typosize[8/8]\romannumeral\sidenote_number}%
}

\def\sidenote_makeleft#1{%
    \hsize=2in\relax%
    \leftskip=0pt plus 1fill\relax%
    \rightskip=1in\relax%
    \it%
    \sidenote_makemark%
    #1%
    \vskip 1cm\relax%
}

\lorem[1.]\sidenote{\lorem[1.]}

\lorem[2.]\sidenote{\lorem[2.]}

\def\_par{\let\_par=\par}
\lorem[3]\sidenote{\lorem[3.]}

\lorem[4.]\sidenote{\lorem[4.]}

\lorem[5.]\sidenote{\lorem[5.]}
\lorem[6.]\sidenote{\lorem[6.]}
\lorem[7.]\sidenote{\lorem[7.]}

\bye

sidenote.opm

%% Initialize namespace
\_namespace{sidenote}

%% Initialize some variables
\_newcount\sidenote_number
\_newbox\.left
\_newbox\.right


%% We just want a unique attribute number, but to get at the number
%% itself, we need this hack.
\_newattribute\.attr__
\_chardef\.attr=\_attributealloc

%% Load the Lua module
\_directlua{require "sidenote"}


%% Add some redefinable styling commands

%% Make the sidenote mark in the text.
%%     out --> Something to be typeset in the current paragraph
%%             (will be placed in an \hbox)
%% (redefine this if you want a different style)
\_newpublic\_def\sidenote_makemark{%
    \_unskip$^{\_the\sidenote_number}$%
}


%% Make the sidenote variant for the left margin.
%%     #1  --> The text of the sidenote
%%     out --> The contents of the sidenote (will be placed in a \vbox)
%% (redefine this if you want a different style)
\_newpublic\_def\sidenote_makeleft#1{%
    \_hsize=2in\_relax%%  Width of the sidenote
    \_leftskip=0pt plus 1fill\_relax%%  Ragged left
    \_rightskip=1em\_relax%%  1em separation from the paragraphs
    \sidenote_makemark%%  Print the sidenote number
    #1%%  Sidenote text
    \_vskip 1ex\_relax%%  Make sure succesive sidenotes are 1ex apart
}


%% Make the sidenote variant for the left margin.
%%     #1  --> The text of the sidenote
%%     out --> The contents of the sidenote (will be placed in a \vbox)
%% (redefine this if you want a different style)
\_newpublic\_def\sidenote_makeright#1{%
    \_hsize=2in\_relax%%  Width of the sidenote
    \_rightskip=0pt plus 1fill\_relax%%  Ragged right
    \_leftskip=1em\_relax%%  1em separation from the paragraphs
    \sidenote_makemark%%  Print the sidenote number
    #1%%  Sidenote text
    \_vskip 1ex\_relax%%  Make sure succesive sidenotes are 1ex apart
}


%% Define the sidenote command
%%     #1  --> The text of the sidenote
%%     out --> Typesets the sidenote number with \sidenote_makemark,
%%             and records the sidenote text to be included at shipout.
\_newpublic\_def\sidenote#1{%
    \_incr\sidenote_number%%  Increment the sidenote number
    \_setbox\.left=\_vbox{%%  Save the left sidenote contents
        \sidenote_makeleft{#1}%
    }%
    \_setbox\.right=\_vbox{%%  Save the right sidenote contents
        \sidenote_makeright{#1}%
    }%
    %
    %% Make the sidenote mark and mark it with an attribute so we can
    %% find it from Lua
    \_hbox attr \.attr=\sidenote_number {\sidenote_makemark}%
    %
    \.savenote%% Run the Lua function
}


%% Set the page side
\_newcount\.side


%% Sets the side to typeset the sidenotes on
%%     #1  --> ``\left'' or ``\right''
%%     out --> (nothing)
\_newpublic\_def\sidenote_side#1{%
    \ifx#1\_left%
        \_global\.side=0%
    \else%
        \_global\.side=1%
    \fi%
}

%% Set the default sidenote side
\sidenote_side\left


%% Switch sides automatically for each page
% \addto\_begoutput{%
%     \ifodd\pageno%
%         \sidenote_side\right%
%     \else%
%         \sidenote_side\left%
%     \fi%
% }

\_endnamespace

sidenote.lua

-- Initialize some constants
local glue_id = node.id("glue")
local hlist_id = node.id("hlist")
local vlist_id = node.id("vlist")
local attribute = token.create("_sidenote_attr").mode
local LEFT = 0


-- Make an \hss now so that we can copy and reuse it later
local hss = node.new("glue")
hss.stretch = 1
hss.stretch_order = 1
hss.shrink = 1
hss.shrink_order = 1


-- Store the sidenote contents
local lefts = {}
local rights = {}


-- Save the sidenote contents in Lua so we can access them later
define_lua_command(
    "_sidenote_savenote",
    function()
        local number = tex.count.sidenote_number
        lefts[number] = node.copy_list(tex.box._sidenote_left)
        rights[number] = node.copy_list(tex.box._sidenote_right)
    end
)


-- Typeset the sidenote contents
callback.add_to_callback("pre_shipout_filter", function(head)
    -- Should we place the sidenotes on the left or on the right?
    local side = tex.count._sidenote_side

    -- The minimum height to place the sidenote at
    local min_height = 0
    local max_height = (tex.voffset or 0) + tex.vsize

    -- Recursively traverse the page
    local function recurse(n, height, parent)
        for m in node.traverse(n) do
            -- Set the height of the current node
            local self_height = 0
            if m.id == glue_id and parent.id == vlist_id then
                -- Glue is only vertical if it's in a \vbox
                self_height = node.effective_glue(m, parent)
            elseif (m.height or m.depth) and parent.id == vlist_id then
                self_height = (m.height or 0) + (m.depth or 0)
            end

            -- A node's "shift" attribute is vertical only if the parent is
            -- an \hbox. This corresponds to the primitive \raise.
            local vshift = 0
            if m.shift and
               (parent.id == hlist_id or
                not node.is_node(parent))
            then
                vshift = m.shift
            end

            -- Set the current height
            height = height + self_height

            -- The number of this node's corresponding sidenote
            local attr = node.get_attribute(m, attribute)

            -- We've found a sidenote node
            if attr then
                -- Get the sidenote contents
                local note
                if side == LEFT then
                    note = lefts[attr]
                else
                    note = rights[attr]
                end

                local note_height = note.height + note.depth

                if min_height + note_height > max_height then
                    texio.write_nl(
                        "Warning: sidenote " .. attr .. " is too tall!!!\n"
                    )
                end

                -- Zero out the note's height
                note.height = 0
                note.depth = 0

                -- Add any necessary vertical space to prevent collisions
                local offset = math.max(min_height - height, 0)
                local vskip = node.new("glue")
                vskip.width = offset
                note.list = node.insert_before(note.list, note.list, vskip)

                local hss = node.copy(hss)

                -- Create a zero-width \hbox to hold the sidenote
                local box
                if side == LEFT then
                    hss.next = note
                    box = node.hpack(hss, 0, "exactly")

                    lefts[attr] = nil
                    node.free(rights[attr])
                    rights[attr] = nil
                else
                    local offset = node.new("glue")
                    offset.width = parent.width

                    offset.next = note
                    note.next = hss
                    box = node.hpack(offset, 0, "exactly")

                    rights[attr] = nil
                    node.free(lefts[attr])
                    lefts[attr] = nil
                end

                -- Inject the \hbox into the page
                parent.prev.next = box
                box.next = parent
                node.slide(parent.prev)

                -- Update the minimum height
                min_height = height + note_height + offset

            -- Not a sidenote, so we need to recurse
            elseif m.list then
                recurse(
                    m.list,
                    height - self_height + vshift,
                    m
                )
            end
        end
    end

    -- Start at the root of the page
    recurse(head.list, tex.voffset or 0, {})

    return true
end, "sidenote")

sample output page 1

sample output page 2

20
  • 3
    This is great. Lua is powerful tool.
    – wipet
    Jun 27, 2023 at 4:49
  • This is really brilliant. Thank you so much (again), Max!
    – user574859
    Jun 27, 2023 at 14:06
  • I noticed that the note number is the same style as the footnote: it uses the math mode font. I tried to find out in the files where I could change that (using the fnmark defined in my question, but it eludes me. Any hints?
    – user574859
    Jun 27, 2023 at 15:02
  • For the same reason, I can't restart the side note number every chapter...
    – user574859
    Jun 27, 2023 at 15:16
  • 2
    Well, this works perfectly now in Overleaf due this available TeXLive 2023.
    – juanuni
    Sep 10, 2023 at 8:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .