summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/strc-bkm.lua
diff options
context:
space:
mode:
authorContext Git Mirror Bot <phg42.2a@gmail.com>2016-01-12 17:15:07 +0100
committerContext Git Mirror Bot <phg42.2a@gmail.com>2016-01-12 17:15:07 +0100
commit8d8d528d2ad52599f11250cfc567fea4f37f2a8b (patch)
tree94286bc131ef7d994f9432febaf03fe23d10eef8 /tex/context/base/mkiv/strc-bkm.lua
parentf5aed2e51223c36c84c5f25a6cad238b2af59087 (diff)
downloadcontext-8d8d528d2ad52599f11250cfc567fea4f37f2a8b.tar.gz
2016-01-12 16:26:00
Diffstat (limited to 'tex/context/base/mkiv/strc-bkm.lua')
-rw-r--r--tex/context/base/mkiv/strc-bkm.lua516
1 files changed, 516 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/strc-bkm.lua b/tex/context/base/mkiv/strc-bkm.lua
new file mode 100644
index 000000000..a055a97a1
--- /dev/null
+++ b/tex/context/base/mkiv/strc-bkm.lua
@@ -0,0 +1,516 @@
+if not modules then modules = { } end modules ['strc-bkm'] = {
+ version = 0.200,
+ comment = "companion to strc-bkm.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- Future version will support adding arbitrary bookmarks with
+-- associated complex actions (rather trivial to implement).
+
+-- this should become proper separated backend code
+
+-- we should hook the placement into everystoptext ... needs checking
+
+-- todo: make an lpeg for stripped
+
+local next, type = next, type
+local gsub, lower = string.gsub, string.lower
+local concat = table.concat
+local utfvalues = utf.values
+local settings_to_hash = utilities.parsers.settings_to_hash
+
+local trace_bookmarks = false trackers.register("references.bookmarks", function(v) trace_bookmarks = v end)
+local report_bookmarks = logs.reporter("structure","bookmarks")
+
+local structures = structures
+
+structures.bookmarks = structures.bookmarks or { }
+
+local bookmarks = structures.bookmarks
+local sections = structures.sections
+local lists = structures.lists
+local levelmap = sections.levelmap
+local variables = interfaces.variables
+local implement = interfaces.implement
+local codeinjections = backends.codeinjections
+
+bookmarks.method = "internal" -- or "page"
+
+local names = { }
+local opened = { }
+local forced = { }
+local numbered = { }
+
+function bookmarks.register(settings)
+ local force = settings.force == variables.yes
+ local number = settings.number == variables.yes
+ local allopen = settings.opened == variables.all
+ for k, v in next, settings_to_hash(settings.names or "") do
+ names[k] = true
+ if force then
+ forced[k] = true
+ if allopen then
+ opened[k] = true
+ end
+ end
+ if number then
+ numbered[k] = true
+ end
+ end
+ if not allopen then
+ for k, v in next, settings_to_hash(settings.opened or "") do
+ opened[k] = true
+ end
+ end
+end
+
+function bookmarks.overload(name,text)
+ local l, ls = lists.tobesaved, nil
+ if #l == 0 then
+ -- no entries
+ elseif name == "" then
+ ls = l[#l]
+ else
+ for i=#l,0,-1 do
+ local li = l[i]
+ local metadata = li.metadata
+ if metadata and not metadata.nolist and metadata.name == name then
+ ls = li
+ break
+ end
+ end
+ end
+ if ls then
+ local titledata = ls.titledata
+ if titledata then
+ titledata.bookmark = text
+ end
+ end
+ -- last resort
+ -- context.writetolist({name},text,"")
+end
+
+local function stripped(str) -- kind of generic
+ str = gsub(str,"\\([A-Z]+)","%1") -- \LOGO
+ str = gsub(str,"\\ "," ") -- \
+ str = gsub(str,"\\([A-Za-z]+) *{(.-)}","%2") -- \bla{...}
+ str = gsub(str," +"," ") -- spaces
+ return str
+end
+
+-- todo: collect specs and collect later i.e. multiple places
+
+local numberspec = { }
+
+function bookmarks.setup(spec)
+ -- table.merge(numberspec,spec)
+ for k, v in next, spec do
+ numberspec[k] = v
+ end
+end
+
+function bookmarks.place()
+ if next(names) then
+ local levels = { }
+ local noflevels = 0
+ local lastlevel = 1
+ local nofblocks = #lists.sectionblocks -- always >= 1
+ local showblocktitle = toboolean(numberspec.showblocktitle,true)
+ for i=1,nofblocks do
+ local block = lists.sectionblocks[i]
+ local blockdone = nofblocks == 1
+ local list = lists.filter {
+ names = names,
+ criterium = block .. ":all",
+ forced = forced,
+ }
+ for i=1,#list do
+ local li = list[i]
+ local metadata = li.metadata
+ local name = metadata.name
+ if not metadata.nolist or forced[name] then -- and levelmap[name] then
+ local titledata = li.titledata
+ --
+ if not titledata then
+ local userdata = li.userdata
+ if userdata then
+ local first = userdata.first
+ local second = userdata.second
+ if first then
+ if second then
+ titledata = { title = first .. " " .. second }
+ else
+ titledata = { title = first }
+ end
+ elseif second then
+ titledata = { title = second }
+ else
+ -- ignoring (command and so)
+ end
+ end
+ end
+ --
+ if titledata then
+ if not blockdone then
+ if showblocktitle then
+ -- add block entry
+ local blockdata = sections.sectionblockdata[block]
+ noflevels = noflevels + 1
+ local references = li.references
+ levels[noflevels] = {
+ level = 1, -- toplevel
+ title = stripped(blockdata.bookmark ~= "" and blockdata.bookmark or block),
+ reference = references,
+ opened = allopen or opened[name], -- same as first entry
+ realpage = references and references.realpage or 0, -- handy for later
+ usedpage = true,
+ }
+ end
+ blockdone = true
+ end
+ local structural = levelmap[name]
+ lastlevel = structural or lastlevel
+ if nofblocks > 1 then
+ -- we have a block so increase the level
+ lastlevel = lastlevel + 1
+ end
+ local title = titledata.bookmark
+ if not title or title == "" then
+ -- We could typeset the title and then convert it.
+ -- if not structural then
+ -- title = titledata.title or "?")
+ -- else
+ title = titledata.title or "?"
+ -- end
+ end
+ if numbered[name] then
+ local sectiondata = sections.collected[li.references.section]
+ local numberdata = li.numberdata
+ if sectiondata and numberdata then
+ if not numberdata.hidenumber then
+ -- we could typeset the number and convert it
+ local number = sections.typesetnumber(sectiondata,"direct",numberspec,sectiondata)
+ if number and #number > 0 then
+ title = concat(number) .. " " .. title
+ end
+ end
+ end
+ end
+ noflevels = noflevels + 1
+ local references = li.references
+ levels[noflevels] = {
+ level = lastlevel,
+ title = stripped(title), -- can be replaced by converter
+ reference = references, -- has internal and realpage
+ opened = allopen or opened[name],
+ realpage = references and references.realpage or 0, -- handy for later
+ usedpage = true,
+ structural = structural,
+ name = name,
+ }
+ end
+ end
+ end
+ end
+-- inspect(levels)
+ bookmarks.finalize(levels)
+ function bookmarks.place() end -- prevent second run
+ end
+end
+
+function bookmarks.flatten(levels)
+ if not levels then
+ -- a plugin messed up
+ return { }
+ end
+ -- This function promotes leading structurelements with a higher level
+ -- to the next lower level. Such situations are the result of lack of
+ -- structure: a subject preceding a chapter in a sectionblock. So, the
+ -- following code runs over section blocks as well. (bookmarks-007.tex)
+ local noflevels = #levels
+ if noflevels > 1 then
+ local function showthem()
+ for i=1,noflevels do
+ local level = levels[i]
+ -- if level.structural then
+ -- report_bookmarks("%i > %s > %s",level.level,level.reference.block,level.title)
+ -- else
+ report_bookmarks("%i > %s > %s > %s",level.level,level.reference.block,level.name,level.title)
+ -- end
+ end
+ end
+ if trace_bookmarks then
+ report_bookmarks("checking structure")
+ showthem()
+ end
+ local skip = false
+ local done = 0
+ local start = 1
+ local one = levels[1]
+ local first = one.level
+ local block = one.reference.block
+ for i=2,noflevels do
+ local current = levels[i]
+ local new = current.level
+ local reference = current.reference
+ local newblock = type(reference) == "table" and current.reference.block or block
+ if newblock ~= block then
+ first = new
+ block = newblock
+ start = i
+ skip = false
+ elseif skip then
+ -- go on
+ elseif new > first then
+ skip = true
+ elseif new < first then
+ for j=start,i-1 do
+ local previous = levels[j]
+ local old = previous.level
+ previous.level = new
+ if trace_bookmarks then
+ report_bookmarks("promoting entry %a from level %a to %a: %s",j,old,new,previous.title)
+ end
+ done = done + 1
+ end
+ skip = true
+ end
+ end
+ if trace_bookmarks then
+ if done > 0 then
+ report_bookmarks("%a entries promoted")
+ showthem()
+ else
+ report_bookmarks("nothing promoted")
+ end
+ end
+ end
+ return levels
+end
+
+local extras = { }
+local lists = { }
+local names = { }
+
+bookmarks.extras = extras
+
+local function cleanname(name)
+ return lower(file.basename(name))
+end
+
+function extras.register(name,levels)
+ if name and levels then
+ name = cleanname(name)
+ local found = names[name]
+ if found then
+ lists[found].levels = levels
+ else
+ lists[#lists+1] = {
+ name = name,
+ levels = levels,
+ }
+ names[name] = #lists
+ end
+ end
+end
+
+function extras.get(name)
+ if name then
+ local found = names[cleanname(name)]
+ if found then
+ return lists[found].levels
+ end
+ else
+ return lists
+ end
+end
+
+function extras.reset(name)
+ local l, n = { }, { }
+ if name then
+ name = cleanname(name)
+ for i=1,#lists do
+ local li = lists[i]
+ local ln = li.name
+ if name == ln then
+ -- skip
+ else
+ local m = #l + 1
+ l[m] = li
+ n[ln] = m
+ end
+ end
+ end
+ lists, names = l, n
+end
+
+local function checklists()
+ for i=1,#lists do
+ local levels = lists[i].levels
+ for j=1,#levels do
+ local entry = levels[j]
+ local pageindex = entry.pageindex
+ if pageindex then
+ entry.reference = figures.getrealpage(pageindex)
+ entry.pageindex = nil
+ end
+ end
+ end
+end
+
+function extras.tosections(levels)
+ local sections = { }
+ local noflists = #lists
+ for i=1,noflists do
+ local levels = lists[i].levels
+ local data = { }
+ sections[i] = data
+ for j=1,#levels do
+ local entry = levels[j]
+ if entry.usedpage then
+ local section = entry.section
+ local d = data[section]
+ if d then
+ d[#d+1] = entry
+ else
+ data[section] = { entry }
+ end
+ end
+ end
+ end
+ return sections
+end
+
+function extras.mergesections(levels,sections)
+ if not sections or #sections == 0 then
+ return levels
+ elseif not levels then
+ return { }
+ else
+ local merge = { }
+ local noflists = #lists
+ if #levels == 0 then
+ local level = 0
+ local section = 0
+ for i=1,noflists do
+ local entries = sections[i][0]
+ if entries then
+ for i=1,#entries do
+ local entry = entries[i]
+ merge[#merge+1] = entry
+ entry.level = entry.level + level
+ end
+ end
+ end
+ else
+ for j=1,#levels do
+ local entry = levels[j]
+ merge[#merge+1] = entry
+ local section = entry.reference.section
+ local level = entry.level
+ entry.section = section -- for tracing
+ for i=1,noflists do
+ local entries = sections[i][section]
+ if entries then
+ for i=1,#entries do
+ local entry = entries[i]
+ merge[#merge+1] = entry
+ entry.level = entry.level + level
+ end
+ end
+ end
+ end
+ end
+ return merge
+ end
+end
+
+function bookmarks.merge(levels,mode)
+ return extras.mergesections(levels,extras.tosections())
+end
+
+local sequencers = utilities.sequencers
+local appendgroup = sequencers.appendgroup
+local appendaction = sequencers.appendaction
+
+local bookmarkactions = sequencers.new {
+ arguments = "levels,method",
+ returnvalues = "levels",
+ results = "levels",
+}
+
+appendgroup(bookmarkactions,"before") -- user
+appendgroup(bookmarkactions,"system") -- private
+appendgroup(bookmarkactions,"after" ) -- user
+
+appendaction(bookmarkactions,"system",bookmarks.flatten)
+appendaction(bookmarkactions,"system",bookmarks.merge)
+
+function bookmarks.finalize(levels)
+ local method = bookmarks.method or "internal"
+ checklists() -- so that plugins have the adapted page number
+ levels = bookmarkactions.runner(levels,method)
+ if levels and #levels > 0 then
+ -- normally this is not needed
+ local purged = { }
+ for i=1,#levels do
+ local l = levels[i]
+ if l.usedpage ~= false then
+ purged[#purged+1] = l
+ end
+ end
+ --
+ codeinjections.addbookmarks(purged,method)
+ else
+ -- maybe a plugin messed up
+ end
+end
+
+function bookmarks.installhandler(what,where,func)
+ if not func then
+ where, func = "after", where
+ end
+ if where == "before" or where == "after" then
+ sequencers.appendaction(bookmarkactions,where,func)
+ else
+ report_tex("installing bookmark %a handlers in %a is not possible",what,tostring(where))
+ end
+end
+
+-- interface
+
+implement {
+ name = "setupbookmarks",
+ actions = bookmarks.setup,
+ arguments = {
+ {
+ { "separatorset" },
+ { "conversionset" },
+ { "starter" },
+ { "stopper" },
+ { "segments" },
+ { "showblocktitle" },
+ }
+ }
+}
+
+implement {
+ name = "registerbookmark",
+ actions = bookmarks.register,
+ arguments = {
+ {
+ { "names" },
+ { "opened" },
+ { "force" },
+ { "number" },
+ }
+ }
+}
+
+implement {
+ name = "overloadbookmark",
+ actions = bookmarks.overload,
+ arguments = { "string", "string" }
+}