T wo T r:commands.lua
From LuaTeXWiki
--- Copyright (c) 2021 by Toadstone Enterprises.
--- ISC-type license, see License.txt for details.
-----------------------------------------------------------------
local util = require("utils")
local read_group = util.read_group
local is_command_terminator = util.is_command_terminator
local is_whitespace = util.is_whitespace
local node_type = util.node_type
local reader = require("reader")
local push_reader = reader.push_reader
local pop_reader = reader.pop_reader
local read_value = reader.read_value
local unread_value = reader.unread_value
local replace_text = reader.replace_text
local pos = reader.pos
local format = require("format")
local update_state = format.update_state
local update_locals = format.update_locals
local push_tbl = format.push_tbl
local pop_tbl = format.pop_tbl
local top_tbl = format.top_tbl
local push_do_command_stop = format.push_do_command_stop
local footnote_tbl = format.footnote_tbl
local emph_text_font = format.emph_text_font
local bold_text_font = format.bold_text_font
local title_font = format.title_font
local superscript_font = format.superscript_font
local pages = require("pages")
local process_par = pages.process_par
local build_par = pages.build_par
local build_pages = pages.build_pages
local main = main or require("main")
local main_loop = main.main_loop
-----------------------------------------------------------------
--- We will keep our various commands in a table, so that we
--- can retrieve them by name. We use a metatable to throw
--- a consistent error if a non-existant command is looked
--- for. The idea was that this would throw the error at that
--- time, instead of later on when we tried to use it.
local command_table = {}
local mt = {__index = function (_, command)
error("Bad Command:" .. command)
end
}
setmetatable(command_table, mt)
local register_command = function(name, func)
command_table[name] = func
end
-----------------------------------------------------------------
local get_command = function()
-- A command in initiated by a "\", and terminated by a
-- "[", "{", or whitespace. (But the initiating "\" has
-- already been read past.) Return the command as a string.
-- We also 'eat' any extra following whitespace, and then
-- back up by one character, so the command can see the
-- terminator; although we may lose the distinction of
-- which whitespace terminated the command.
-- This backing up will also allow the main_loop to see
-- the "{", if there was one, and so doing the do_command_start.
local command = ""
local value = read_value()
if is_command_terminator(value) then
-- A one character command, consisting solely of a command terminator.
command = unicode.utf8.char(value)
else
while (value and (not is_command_terminator(value))) do
command = command .. unicode.utf8.char(value)
value = read_value()
end
end
while is_whitespace(value) do
-- eat the white
value = read_value()
end
-- so command can see the 'terminator'
-- Note that if the command was terminated by whitespace,
-- and then followed by more whitespace, we will not be
-- distinguishing between the various types of whitespace.
-- Does this matter?
unread_value()
return command
end
--- WARNING: An Emph in a Footnote will result in a change of font size!
--- There should be some notion of a font family where given the current
--- font, an appropriate Emph font can be chosen. At least consider size.
--- Emph displays most of the standard ways to do a command.
--- We define a function do_emph, which will then be registered.
local do_emph = function()
-- We start off with some initialization. In this case, are we
-- emphasizing the following group, or are we (presumably) already
-- within a group?
local value = read_value()
unread_value{}
if value == unicode.utf8.byte("{") then
-- We emphasize the following group
-- We now finish the initialization in this branch of the
-- if statement.
-- We push the current table, to that we can restore it
-- at the end, namely in the do_command_stop.
push_tbl(copy_table(top_tbl()))
-- We have finished the initialization, so we define a
-- do_command_start function to be executed upon encountering
-- the "{"
local do_command_start = function()
-- Switch over to the Emph font.
local tbl = top_tbl()
tbl.font = emph_text_font
end
-- We then put this function into the proper place in State.
State.do_command_start = do_command_start
-- We also define a do_command_stop function, to be executed
-- upon encountering the terminating "}".
local do_command_stop = function()
-- Pop the table, to return to the
-- previous situation.
pop_tbl()
end
-- We then put this function into the proper place in State.
push_do_command_stop(do_command_stop)
else
-- Emphasize all the following text (presumably within a group)
-- This change of font will get reset when the group is exited,
-- since State.tbl has been copied and pushed upon entrance into
-- the group.
local tbl = top_tbl()
tbl.font = emph_text_font
end
end
register_command("Emph", do_emph)
local do_bold = function()
local value = read_value()
unread_value{}
if value == unicode.utf8.byte("{") then
push_tbl(copy_table(top_tbl()))
local do_command_start = function()
local tbl = top_tbl()
tbl.font = bold_text_font
end
State.do_command_start = do_command_start
local do_command_stop = function()
pop_tbl()
end
push_do_command_stop(do_command_stop)
else
local tbl = top_tbl()
tbl.font = bold_text_font
end
end
register_command("Bold", do_bold)
local do_input = function()
-- Input a file.
local filename
local do_command_start = function()
-- so that the following read_group will see the opening "{"
unread_value()
-- Note that with the argument true to read_group
-- Input{filename}
-- Input { filename }
-- Input{file name}
-- wil all read "filename"
-- Do we want this? The second seems useful, the
-- third does not.
filename = read_group(true)
-- So that main_loop will see the closing "}"
-- and call do_command_stop.
unread_value()
end
State.do_command_start = do_command_start
local do_command_stop = function()
-- We could have done all this above.
-- Now read in the file
push_reader("file:" .. filename)
-- Recursively call main_loop to typeset the
-- material we have read in. At the end of the
-- files contents, main_loop will terminate,
-- and execution will resume with the call to
-- pop_reader below.
-- Why do I need to qualify this?
main.main_loop()
-- Back to the original reader.
pop_reader()
-- And we now build the pages. We could probably
-- just leave the material there, but I am
-- envisioning each of these input files to
-- represent a chapter, to be typeset as their
-- own set of pages.
build_pages()
end
push_do_command_stop(do_command_stop)
end
register_command("Input", do_input)
local do_fix = function()
-- If one is transcribing a document, one may wish to correct
-- typos. Three args - {<old>}{<new>}{<reason>}
-- Perhaps not too useful in general, but a nice demonstration
-- piece.
-- Read the three groups, and insert the new text.
local start = pos()
local old = read_group(false)
local new = read_group(false)
local reason = read_group(false)
local stop = pos()
replace_text(new, start, stop-1)
end
register_command("Fix", do_fix)
local do_nb_space = function()
-- Non breaking space. Note that we may have eaten any
-- extra following white space, so the char at pos() may not
-- be the original space.
local cur_pos = pos()
replace_text(unicode.utf8.char(0x00A0), cur_pos -2, cur_pos -1)
end
register_command(" ", do_nb_space)
--- QUESTION: Why, unlike in Footnote, do we not need to save the
--- current head and tail? Why do we set them to nil when we return
--- in do_command_stop?
local do_title = function()
local value = read_value()
if value == unicode.utf8.byte("[") then
-- We use the contents of this optional argument to set the
-- text of the header.
unread_value()
local args = read_group(false)
-- Set the text of the header.
State.header_text = args
else
-- So that the opening "{" will be seen.
unread_value()
end
local old_baselineskip = tex.baselineskip
push_tbl(copy_table(top_tbl()))
local do_command_start = function()
-- Set the new font and baselineskip
tex.baselineskip = make_glue_spec(tex.sp("25pt"))
-- Add a title_tbl in format.lua?
local tbl = top_tbl()
tbl.font = title_font
tbl.parindent = tex.sp("0pt")
tbl.leftskip = filll()
tbl.rightskip = filll()
end
State.do_command_start = do_command_start
local do_command_stop = function()
-- We have now read the title, so force
-- building a paragraph
local head, tail, _, _ = update_locals()
local par = build_par(head, tail)
process_par(par)
-- Insert the (vertical) space after the title.
local title_sep = make_glue("userskip", tex.sp("0.25in"))
process_par(title_sep)
-- Reset various variables
update_state(nil, nil, false, false)
tex.baselineskip = old_baselineskip
pop_tbl()
-- New chapter started
State.first_paragraph_of_chapter = true
State.first_page_of_chapter = true
State.notes = {}
State.max_notes = 0
end
push_do_command_stop(do_command_stop)
end
register_command("Title", do_title)
local do_footnote = function()
-- See the QUESTION: before do_title.
local old_head, old_tail, _, _ = update_locals()
local old_baselineskip = tex.baselineskip
State.max_notes = State.max_notes + 1
local do_command_start = function()
-- We want to mark the first preceding glyph
-- node with the Footnote marker.
local to_be_marked = old_tail
-- Search backward
while (node_type(to_be_marked) ~= "glyph") do
to_be_marked = to_be_marked.prev
end
-- Naked glyphs cannot go into a vlist:
local marker = node.hpack(make_glyph(unicode.utf8.byte(tostring(State.max_notes)),
superscript_font,
1, 3, 3))
-- Some glue to raise the mark
local marker_glue = make_glue(0, tex.sp("5pt"))
link_nodes(marker, marker_glue)
-- vpack, so that the glue is on the bottom,
-- not the left.
local packed = node.vpack(marker)
-- Link things back up.
if (to_be_marked == old_tail) then
link_nodes(to_be_marked, packed)
old_tail = packed
else
link_nodes(to_be_marked, packed, to_be_marked.next)
end
-- Add attribute to to_be_marked, so that we can
-- see it while building the page, and get the
-- note.
node.set_attribute(to_be_marked, 444, State.max_notes)
-- Now, set up the first glyph of the footnote
-- to also receive the footnote mark. Here we need
-- to search forward using the reader.
local pos = pos()
local value = read_value()
-- We make the (possibly false) assumption that
-- the footnote starts with a glyph. There should
-- be some way to identify glyphs. What if the
-- footnote started with an Emph?
replace_text("\\Mark{" .. unicode.utf8.char(value) .. "}", pos, pos)
-- Switch to footnote mode, and call main_loop
-- to typeset the footnote.
State.mode = "footnote"
update_state(nil, nil, false, false)
push_tbl(footnote_tbl)
main.main_loop()
end
State.do_command_start = do_command_start
local do_command_stop = function()
-- We now build the (final) paragraph of the
-- footnote and process it (put it in
-- State.notes[max_notes]). Note that if the
-- footnote contains multiple paragraphs, the
-- earlier ones have already been processed.
-- Note also that, unlike with Input, where
-- main_loop will terminate on eol, and so
-- build_par, process_par, and build_pages,
-- we have to do some of this now.
local head, tail, _, _ = update_locals()
tex.baselineskip = make_glue_spec(tex.sp("12pt"))
local par = build_par(head, tail)
process_par(par)
tex.baselineskip = old_baselineskip
-- and restore the previous mode, head, and tail.
pop_tbl()
State.mode = "main_text"
update_state(old_head, old_tail, false, false)
end
push_do_command_stop(do_command_stop)
end
register_command("Footnote", do_footnote)
local do_mark = function()
-- Even though we do nothing in do_command_start, we
-- must define it, so that upon seeing the "{" main_loop
-- will know that we are starting a command, and not
-- entering a new group.
local do_command_start = function()
end
State.do_command_start = do_command_start
local do_command_stop = function()
local head, tail, _, _ = update_locals()
-- We should have just read the single chararcter
-- at the start of the footnote. Therefore head
-- and tail should be the same. Check this.
assert((head == tail), "Head and tail are not the same.")
-- add the mark as in Footnote.
local marker = node.hpack(make_glyph(unicode.utf8.byte(tostring(State.max_notes)),
superscript_font,
1, 3, 3))
local marker_glue = make_glue(0, tex.sp("5pt"))
link_nodes(marker, marker_glue)
local packed = node.vpack(marker)
link_nodes(packed, head)
update_state(packed, tail, false, false)
end
push_do_command_stop(do_command_stop)
end
register_command("Mark", do_mark)
local initialize_command = function(command)
return command_table[command]()
end
local commands = {get_command = get_command,
initialize_command = initialize_command,
}
return commands