Skip to content

Instantly share code, notes, and snippets.

@zr-tex8r
Created December 25, 2025 11:36
Show Gist options
  • Select an option

  • Save zr-tex8r/8672faa38b8f531f2f1eb6054ad4b41b to your computer and use it in GitHub Desktop.

Select an option

Save zr-tex8r/8672faa38b8f531f2f1eb6054ad4b41b to your computer and use it in GitHub Desktop.
LaTeX: DVIファイルのドライバ指定状態を検査する

chkdvidriver

LaTeX: DVIファイルのドライバ指定状態を検査する

使用法

This is chkdvidriver v0.2.0 <2025-12-25> by 'ZR'
Usage: chkdvidriver[.lua] [OPTION...] EXPECTED IN_PATH
       chkdvidriver[.lua] {-s | -c} [OPTION...] IN_PATH
       chkdvidriver[.lua] -l [OPTION...] EXPECTED IN_PATH OUT_PATH TEX_PATH
Options:
  -s/--show         Show mode (show dvi driver)
  -c/--count        Count mode (show page count)
     --check        Check mode (default)
  -l/--latexmk      Latexmk filter mode
  -w/--workshop     LaTeX Workshop mode (implies -l)
  -W/--no-workshop  Negation of -w
  -a/--alarm        Show loud alarm in failure
  -A/--no-alarm     Negation of -a
  -v/--verbose      Show more messages
  -q/--quiet        Show fewer messages
  -h/--help         Show help and exit
  -V/--version      Show version and exit

更新履歴

  • Version 0.2.0 <2025-12-25>
    • 最初の公開版。

Takayuki YATO (aka. "ZR")
https://github.com/zr-tex8r

#!/usr/bin/env texlua
--
-- This is file 'chkdvidriver.lua'.
--
-- Copyright (c) 2025 Takayuki YATO (aka. "ZR")
-- GitHub: https://github.com/zr-tex8r
-- Twitter: @zr_tex8r
--
-- This package is distributed under the MIT License.
--
program = 'chkdvidriver'
version = '0.2.0'
mod_date = '2025-12-25'
---------------------------------------- preparations
verbose = 0
mode, alarm, workshop = 'check', false, false
expected, in_path, out_path, tex_path = nil
local pack, unpack = string.pack, string.unpack
local insert, concat = table.insert, table.concat
---------------------------------------- logging
do
local function log(...)
local t = {program, ...}
for i = 1, #t do t[i] = tostring(t[i]) end
io.stderr:write(concat(t, ": ").."\n")
end
function info(...)
if verbose >= 1 then log(...) end
end
function alert(...)
if verbose >= 0 then log(...) end
end
function abort(...)
log('ERROR', ...)
os.exit(1)
end
function sure(val, ...)
if val then return val, ... end
abort(...)
end
end
---------------------------------------- parse DVI
do
-- Opcode table
-- An integer means the skip length of that opcode.
local op_table = {} -- 0-127 is set_char_N, 171-234 is fnt_num_N
for c = 0, 255 do op_table[c] = 0 end
for i, op in ipairs({ -- 128-170
1, 2, 3, 4, 8, 1, 2, 3, 4, 8, 0, 44, 0, 0, 0, -- 128
1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, -- 143
1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, -- 157
}) do op_table[i + 127] = op end
for i, op in ipairs({-- 235-255
1, 1, 1, 1, {'sp',1}, {'sp',2}, {'sp',3}, {'sp',4}, -- 235
{'fd',1}, {'fd',2}, {'fd',3}, {'fd',4}, -- 243
{'pre'}, {'post'}, {'pp'}, {}, {}, {}, {}, {}, 1, -- 247
}) do op_table[i + 234] = op end
local function make_reader(source)
return function(fmt, ...)
local v = { unpack(fmt, source, ...) }
return v[#v], v
end
end
-- Parse DVI data and return the DVI-info.
function parse_dvi(source)
local sr, pos, v, npos = make_reader(source), 1
npos, v = sr('B')
sure(v[1] == 247, "unexpected format")
local di = {
source = source, specials = {}, text = {}, extras = nil,
last_bop = nil, post = nil, postpost = nil, -- offsets
pages = nil, width = nil, height = nil,
}
while true do
npos, v = sr('B', pos)
local e = op_table[v[1]]
if type(e) == 'number' then -- e = skip length
-- info("op", v[1], e)
pos = npos + e
-- if v[1] < 128 then insert(di.text, v[1]) end
else -- e is table
local op, ver = e[1], e[2]
sure(op, "invalid opcode", v[1])
-- info("op", op, ver)
if op == 'pre' then
sure(pos == 1, "unexpected pre")
npos, v = sr('>BLLLs1', npos)
sure(v[1] == 2 or v[1] == 3, "unknown pre-ID", v[1])
sure(v[2] == 25400000 or v[3] == 473628672, -- TeX setting
"unknown unit", v[2], v[3])
sure(1 <= v[4] and v[4] <= 32767, -- valid in TeX
"unexpected mag value", v[4])
elseif op == 'post' then
di.post = pos - 1
npos, v = sr('>Lc12LLHH', npos)
di.last_bop, di.pages = v[1], v[6]
di.height, di.width = v[3], v[4]
elseif op == 'pp' then -- post_post
di.postpost = pos - 1
npos, v = sr('>LB', npos)
sure(di.post == v[1], "post offset inconsistent")
local trail = source:sub(npos)
sure(#trail >= 4 and trail == ('\223'):rep(#trail),
"bad trail bytes")
break
elseif op == 'sp' then -- xxx (special)
npos, v = sr('>s1', npos)
insert(di.specials, v[1])
elseif op == 'fd' then -- fnt_def
npos, v = sr('BB', npos + ver + 12)
npos = npos + v[1] + v[2]
else abort("???") -- unreachable
end
pos = npos
end
end
sure(di.last_bop, "missing postamble")
sure(di.post, "missing post-postamble")
return di
end
end
---------------------------------------- check DVI driver
do
function actual_dvi_driver(dinfo)
for _, text in ipairs(dinfo.specials) do
if text == 'header=l3backend-dvips.pro' then
return 'dvips'
end
end
return 'dvipdfmx'
end
local alarm_message = [[
|!!!!!!!!!!!!!!!!!!!!! F A I L U R E !!!!!!!!!!!!!!!!!!!!!
| This DVI file is built for dvips, NOT dvipdfmx.
| You must specify the global option 'dvipdfmx'.
| \documentclass[dvipdfmx, ...]{...}
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
]]
local lws_error_dvipdfmx = [[
Global driver option is MISSING! Add 'dvipdfmx' to \documentclass option list.]]
local lws_error_dvips = [[
BAD global driver option! Remove 'dvipdfmx' from \documentclass option list.]]
function process_check(expected, actual)
info("expected dvi driver setting", expected)
if expected ~= actual then
alert("FAIL", "actual dvi file setting is", actual)
if alarm and expected == 'dvipdfmx' then
io.stderr:write(alarm_message, "\n")
end
os.exit(2)
end
end
local function find_documentclass_line()
local tex, current, found = io.open(tex_path), 0
if not tex then return end
for line in tex:lines() do
current = current + 1
if line:match('\\documentclass%f[^a-zA-Z]') then
found = current
break
end
end
tex:close()
return found
end
function process_latexmk(dinfo, expected, actual)
if expected ~= actual then
info("wrong dvi driver setting")
if workshop then
local line = find_documentclass_line() or 1
local message = (expected == 'dvipdfmx') and lws_error_dvipdfmx or
lws_error_dvips
local s = (dinfo.pages > 1) and 's' or ''
io.stdout:write(([[
****Message for LaTeX Workshop
Latexmk: applying rule 'latex'...
%s:%s: %s
Output written on main.dvi (%s page%s, %s bytes).
Latexmk: applying rule 'dvifilter'
****END
]]):format(
tex_path, line, message, dinfo.pages, s, #dinfo.source
))
end
alert("FAIL", "wrong dvi file setting",
"expected="..expected, "actual="..actual)
if alarm and expected == 'dvipdfmx' then
io.stderr:write(alarm_message, "\n")
end
os.exit(2)
end
info("output file path", out_path)
local outfile = sure(io.open(out_path, 'wb'),
"cannot open file for write", out_path)
assert(outfile:write(dinfo.source))
outfile:close()
end
end
---------------------------------------- main procedure
do
local function show_usage()
io.stdout:write(([[
This is %s v%s <%s> by 'ZR'
Usage: %s[.lua] [OPTION...] EXPECTED IN_PATH
%s[.lua] {-s | -c} [OPTION...] IN_PATH
%s[.lua] -l [OPTION...] EXPECTED IN_PATH OUT_PATH TEX_PATH
Options:
-s/--show Show mode (show dvi driver)
-c/--count Count mode (show page count)
--check Check mode (default)
-l/--latexmk Latexmk filter mode
-w/--workshop LaTeX Workshop mode (implies -l)
-W/--no-workshop Negation of -w
-a/--alarm Show loud alarm in failure
-A/--no-alarm Negation of -a
-v/--verbose Show more messages
-q/--quiet Show fewer messages
-h/--help Show help and exit
-V/--version Show version and exit
]]):format(program, version, mod_date, program, program, program))
os.exit(0)
end
local function read_option()
if #arg == 0 then show_usage() end
local idx, overwrite = 1, false
while idx <= #arg do
local opt = arg[idx]
if opt:sub(1, 1) ~= '-' then break end
if opt == '-h' or opt == '--help' then
show_usage()
elseif opt == '-v' or opt == '--verbose' then
verbose = 1
elseif opt == '-q' or opt == '--quiet' then
verbose = -1
elseif opt == '-s' or opt == '--show' then
mode = 'show'
elseif opt == '-c' or opt == '--count' then
mode = 'count'
elseif opt == '-l' or opt == '--latexmk' then
mode = 'latexmk'
elseif opt == '--check' then
mode = 'check'
elseif opt == '-w' or opt == '--workshop' then
workshop, mode = true, 'latexmk'
elseif opt == '-W' or opt == '--no-workshop' then
workshop = false
elseif opt == '-a' or opt == '--alarm' then
alarm = true
elseif opt == '-A' or opt == '--no-alarm' then
alarm = false
else abort("invalid option", opt)
end
idx = idx + 1
end
local ac = (mode == 'show' or mode == 'count') and 1 or
(mode == 'latexmk') and 4 or 2
sure(#arg == idx + ac - 1,
"wrong number of arguments")
if ac == 1 then
in_path = arg[idx]
else
expected, in_path, out_path, tex_path =
arg[idx], arg[idx + 1], arg[idx + 2], arg[idx + 3]
sure(not expected or expected == 'dvips' or expected == 'dvipdfmx',
"bad expected value", expected)
end
end
function main()
read_option()
info("input file path", in_path)
local infile = sure(io.open(in_path, 'rb'),
"cannot open file for read", in_path)
local insrc = assert(infile:read('*a'))
infile:close()
local dinfo = parse_dvi(insrc)
info("input page count", dinfo.pages)
local actual = actual_dvi_driver(dinfo)
info("actual dvi driver setting", actual)
if mode == 'check' then
process_check(expected, actual)
elseif mode == 'count' then
io.write(dinfo.pages, "\n")
elseif mode == 'show' then
io.write(actual, "\n")
elseif mode == 'latexmk' then
process_latexmk(dinfo, expected, actual)
end
info("done")
end
end
---------------------------------------- go to main
sure(string.pack, "Lua version is old")
main()
-- EOF
@zr-tex8r
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment