texlive[73916] trunk: extractbb, now also ebb
commits+karl at tug.org
commits+karl at tug.org
Wed Feb 12 15:58:30 CET 2025
Revision: 73916
https://tug.org/svn/texlive?view=revision&revision=73916
Author: karl
Date: 2025-02-12 15:58:30 +0100 (Wed, 12 Feb 2025)
Log Message:
-----------
extractbb, now also ebb
Modified Paths:
--------------
trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
trunk/Build/source/texk/texlive/linked_scripts/extractbb/extractbb.lua
trunk/Master/texmf-dist/doc/man/man1/extractbb.1
trunk/Master/texmf-dist/doc/man/man1/extractbb.man1.pdf
trunk/Master/texmf-dist/doc/support/extractbb/README.md
trunk/Master/texmf-dist/doc/support/extractbb/extractbb.man1.pdf
trunk/Master/texmf-dist/scripts/extractbb/extractbb.lua
trunk/Master/tlpkg/libexec/ctan2tds
Added Paths:
-----------
trunk/Master/bin/aarch64-linux/ebb
trunk/Master/bin/amd64-freebsd/ebb
trunk/Master/bin/amd64-netbsd/ebb
trunk/Master/bin/armhf-linux/ebb
trunk/Master/bin/i386-freebsd/ebb
trunk/Master/bin/i386-linux/ebb
trunk/Master/bin/i386-netbsd/ebb
trunk/Master/bin/i386-solaris/ebb
trunk/Master/bin/universal-darwin/ebb
trunk/Master/bin/x86_64-cygwin/ebb
trunk/Master/bin/x86_64-darwinlegacy/ebb
trunk/Master/bin/x86_64-linux/ebb
trunk/Master/bin/x86_64-linuxmusl/ebb
trunk/Master/bin/x86_64-solaris/ebb
Removed Paths:
-------------
trunk/Master/bin/aarch64-linux/ebb
trunk/Master/bin/amd64-freebsd/ebb
trunk/Master/bin/amd64-netbsd/ebb
trunk/Master/bin/armhf-linux/ebb
trunk/Master/bin/i386-freebsd/ebb
trunk/Master/bin/i386-linux/ebb
trunk/Master/bin/i386-netbsd/ebb
trunk/Master/bin/i386-solaris/ebb
trunk/Master/bin/universal-darwin/ebb
trunk/Master/bin/windows/ebb.exe
trunk/Master/bin/x86_64-cygwin/ebb
trunk/Master/bin/x86_64-darwinlegacy/ebb
trunk/Master/bin/x86_64-linux/ebb
trunk/Master/bin/x86_64-linuxmusl/ebb
trunk/Master/bin/x86_64-solaris/ebb
trunk/Master/texmf-dist/scripts/extractbb/extractbb-scratch.lua
trunk/Master/texmf-dist/scripts/extractbb/extractbb-wrapper.lua
Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.am 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.am 2025-02-12 14:58:30 UTC (rev 73916)
@@ -284,6 +284,7 @@
cluttex:clxelatex \
cluttex:cllualatex \
epstopdf:repstopdf \
+ extractbb:ebb \
fmtutil:mktexfmt \
kpsetool:kpsexpand \
kpsetool:kpsepath \
Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.in 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.in 2025-02-12 14:58:30 UTC (rev 73916)
@@ -503,6 +503,7 @@
cluttex:clxelatex \
cluttex:cllualatex \
epstopdf:repstopdf \
+ extractbb:ebb \
fmtutil:mktexfmt \
kpsetool:kpsexpand \
kpsetool:kpsepath \
Modified: trunk/Build/source/texk/texlive/linked_scripts/extractbb/extractbb.lua
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/extractbb/extractbb.lua 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Build/source/texk/texlive/linked_scripts/extractbb/extractbb.lua 2025-02-12 14:58:30 UTC (rev 73916)
@@ -2,86 +2,664 @@
-- extractbb-lua
-- https://github.com/gucci-on-fleek/extractbb
-- SPDX-License-Identifier: MPL-2.0+
--- SPDX-FileCopyrightText: 2024 Max Chernoff
+-- SPDX-FileCopyrightText: 2024--2025 Max Chernoff
--
--- A wrapper script to allow you to choose which implementation of extractbb to
--- use. Should hopefully be replaced with the ``scratch'' file in TeX Live 2025.
+-- Inclusion Methods
+-- =================
--
--- v1.0.6 (2024-11-21) %%version %%dashdate
+-- This script can use two different methods to extract bounding boxes from
+-- images: the "img" module and the "pdfe" module. The "img" module will be
+-- automatically selected in most cases and supports all image types that are
+-- supported by the original "extractbb" program. If and only if the "img"
+-- module fails to load, the "pdfe" module will be used as a fallback. However,
+-- the "pdfe" module only supports PDF files. Both modules are built in to the
+-- LuaTeX binaries, however due to some technical issues, the "img" module may
+-- fail to load on some more exotic platforms.
+--
+--
+-- Compatibility
+-- =============
+--
+-- Based off of my testing, this Lua script is 100% compatible with the original
+-- C-based "extractbb" program, with the following exceptions:
+--
+-- * When running in "img" mode, the PDF version is always reported as "1.5".
+--
+-- * When running in "img" mode, if the requested bounding box is not found,
+-- the script will fallback to the Crop box or the Media box, instead of
+-- following the original fallback order. (In practice, almost all PDFs set
+-- all their bounding boxes equal to each other, and even if the boxes are
+-- set to different values, the script will still return the requested box,
+-- provided that it is set in the PDF.)
+--
+-- * When running in "pdfe" mode, only PDF files are supported.
+--
+-- All of these issues are very unlikely to affect any real-world documents.
+--
+--
+-- Security
+-- ========
+--
+-- This script is designed to be safely ran from restricted shell escape. A few
+-- security features:
+--
+-- * The majority of this script runs inside a sandboxed Lua environment,
+-- which only exposes a very restricted set of functions.
+--
+-- * All file-related functions available inside the sandbox first check with
+-- kpathsea to ensure that the file is allowed to be opened.
+--
+-- * In the event of any errors, the script immediately exits.
+--
+-- * This script does not run (fork/exec) any external programs.
+--
+-- * This script is written entirely in Lua, so overflow/use-after-free
+-- vulnerabilities are not possible.
+--
+-- Some potential security concerns:
+--
+-- * This script has not been audited or reviewed by anyone other than myself.
+--
+-- * The underlying LuaTeX modules may themselves have security
+-- vulnerabilities, which would be inherited by this script.
----------------------
---- Configuration ---
----------------------
--- Choose which implementation of extractbb to use.
-local DEFAULT = "wrapper"
+----------------------
+--- Initialization ---
+----------------------
------------------
---- Execution ---
------------------
+-- Pre-sandbox variables/constants
+local show_errors = true
+local SOURCE_DATE_EPOCH = tonumber(os.getenv("SOURCE_DATE_EPOCH"))
+local version = "extractbb.lua v1.1.0 (2025-02-11)" --%%version %%dashdate
--- Send the error messages to stderr.
-local function error(...)
- -- Header
- io.stderr:write("! extractbb ERROR: ")
+-- Required for any kpathsea calls to work.
+kpse.set_program_name("texlua", "extractbb")
- -- Message
- for i = 1, select("#", ...) do
- io.stderr:write(tostring(select(i, ...)), " ")
+-- Required to use the "img" module from texlua, but only works for LuaTeX
+-- versions >= 1.21.0.
+if not (status.development_id >= 7661) then
+ error("LuaTeX version is too old, cannot proceed.")
+end
+texconfig.texlua_img = true
+
+-- We need to set \outputmode to PDF to be able to use most of the "img" module
+-- functions, but to set \outputmode, we need to initialize the TeX interpreter.
+tex.initialize()
+_G.tex = package.loaded.tex
+tex.enableprimitives("", tex.extraprimitives())
+tex.outputmode = 1
+tex.interactionmode = 0
+
+-- "pdf" module
+_G.pdf = package.loaded.pdf
+pdf.setignoreunknownimages(1)
+pdf.setmajorversion(2)
+pdf.setminorversion(0)
+
+
+------------------
+--- Sandboxing ---
+------------------
+
+-- Prepare the sandbox for the rest of the script.
+local env = {
+ arg = arg,
+ io = { stdout = io.stdout, },
+ ipairs = ipairs,
+ math = math,
+ os = { date = os.date, exit = os.exit, },
+ pairs = pairs,
+ pdfe = pdfe,
+ print = print,
+ select = select,
+ table = table,
+ tonumber = tonumber,
+ type = type,
+}
+
+do
+ -- Saved global functions
+ local debug_traceback = debug.traceback
+ local find_file = kpse.find_file
+ local img_scan = img.scan
+ local io_open = io.open
+ local io_stderr = io.stderr
+ local kpse_in_name_ok = kpse.in_name_ok
+ local kpse_out_name_ok = kpse.out_name_ok
+ local kpse_var_value = kpse.var_value
+ local lfs_attributes = lfs.attributes
+ local os_exit = os.exit
+ local os_setenv = os.setenv
+ local pdfe_open = pdfe.open
+ local select = select
+ local tostring = tostring
+
+ -- Error messages
+ local function error(...)
+ if show_errors then
+ -- Header
+ io_stderr:write("! extractbb ERROR: ")
+
+ -- Message
+ for i = 1, select("#", ...) do
+ io_stderr:write(tostring(select(i, ...)), " ")
+ end
+
+ -- Traceback
+ io_stderr:write("\n", "\n")
+ io_stderr:write(debug_traceback(nil, 2), "\n")
+ end
+
+ -- Flush and exit
+ io_stderr:flush()
+ os_exit(1)
end
- -- Flush and exit
- io.stderr:write("\n")
- io.stderr:flush()
- os.exit(1)
+ env.error = error
+
+ -- Make sure that "openin_any" is at least "restricted", and that
+ -- "openout_any" is at least "paranoid".
+ local initial_openin = kpse_var_value("openin_any")
+ local initial_openout = kpse_var_value("openout_any")
+
+ if (initial_openin ~= "r") or (initial_openout ~= "p") then
+ os_setenv("openin_any", "r")
+ end
+
+ if (initial_openout ~= "p") then
+ os_setenv("openout_any", "p")
+ end
+
+ -- Check the input paths.
+ local function resolve_input_name(file_name)
+ local file_path = find_file(file_name, "graphic/figure", true)
+ if not file_path then
+ error("Cannot find input file:", file_name)
+ end
+
+ local allowed = kpse_in_name_ok(file_path)
+ if not allowed then
+ error("Input file is not allowed:", file_path)
+ end
+
+ local mode = lfs_attributes(file_path, "mode")
+ if mode ~= "file" then
+ error("Input file is not a regular file:", file_path)
+ end
+
+ return file_path
+ end
+
+ -- Check the output paths.
+ local function resolve_output_name(file_name)
+ local allowed = kpse_out_name_ok(file_name)
+ if not allowed then
+ error("Output file is not allowed:", file_name)
+ end
+
+ local name, extension = file_name:match("(.+)%.([^.]-)$")
+
+ if (not name) or (not extension) or
+ (name == "") or (extension == "")
+ then
+ error("Output file has no extension:", file_name)
+ end
+
+ if (extension ~= "xbb") and (extension ~= "bb") then
+ error("Output file has an invalid extension:", file_name)
+ end
+
+ -- We shouldn't allow files with weird characters in their names.
+ if name:match("[%c%%\t\r\n><*|]") then
+ error("Output file has an invalid name:", file_name)
+ end
+
+ return file_name
+ end
+
+ -- Opens a file.
+ function env.open_file(file_name, read_write, binary_text)
+ local file_path, mode
+ if read_write == "read" then
+ file_path = resolve_input_name(file_name)
+ mode = "r"
+ elseif read_write == "write" then
+ file_path = resolve_output_name(file_name)
+ mode = "w"
+ else
+ error("Invalid read/write mode:", read_write)
+ end
+
+ if binary_text == "binary" then
+ mode = mode .. "b"
+ elseif binary_text == "text" then
+ mode = mode .. ""
+ else
+ error("Invalid binary/text mode:", binary_text)
+ end
+
+ local file, message = io_open(file_path, mode)
+
+ if not file then
+ error("Cannot open file:", file_path, message)
+ end
+
+ return file
+ end
+
+ -- Open an PDF file.
+ function env.pdfe.open(file_name)
+ local file_path = resolve_input_name(file_name)
+ return pdfe_open(file_path)
+ end
+
+ -- Open an image file.
+ function env.open_image(file_name, page, box)
+ local file_path = resolve_input_name(file_name)
+ return img_scan {
+ filename = file_path,
+ filepath = file_path,
+ page = page,
+ pagebox = box,
+ }
+ end
+
+ if not img_scan then
+ env.open_image = false
+ end
end
--- Get the value of the environment variable that decides which version to run.
-local env_choice = os.env["TEXLIVE_EXTRACTBB"]
+-- Prevent trying to change the environment.
+local function bad_index(...)
+ env.error("Attempt to access an undefined index:", select(2, ...))
+end
--- If the environment variable is set to a file path, run that directly.
-local env_mode = lfs.attributes(env_choice or "", "mode")
-if (env_mode == "file") or (env_mode == "link") then
- arg[0] = env_choice
- table.insert(arg, 1, env_choice)
- arg[-1] = nil
- return os.exec(arg)
+setmetatable(env, {
+ __index = bad_index,
+ __metatable = false,
+ __newindex = bad_index,
+})
+
+-- Set the environment.
+_ENV = env
+
+
+-----------------------------------
+--- Post-Sandbox Initialization ---
+-----------------------------------
+
+-- Constants
+local BP_TO_SP = 65781.76
+local IN_TO_BP = 72
+local DATE_FORMAT = "%a %b %d %H:%M:%S %Y" -- "%c"
+
+-- Save often-used globals for a slight speed boost.
+local floor = math.floor
+local insert = table.insert
+local remove = table.remove
+local script_arguments = arg
+local unpack = table.unpack
+
+-- General-purpose functions
+local function round(number)
+ return floor(number +0.5)
end
--- Find the subscripts
-kpse.set_program_name("texlua", "extractbb")
-local function find_script(name)
- -- Find the script, searching **only** in the scripts directories.
- local path = kpse.lookup(
- name,
- { path = kpse.var_value("TEXMFSCRIPTS"), format = "lua" }
- )
+-------------------------
+--- Argument Handling ---
+-------------------------
- -- Make sure that the script is not writable.
- if kpse.out_name_ok_silent_extended(path) then
- if os.env["TEXLIVE_EXTRACTBB_UNSAFE"] == "unsafe" then
- -- If we're running in development mode, then we can allow this.
- else
- error("Refusing to run a writable script.")
+-- Define the argument handling functions.
+local process_arguments = {}
+
+-- > Specify a PDF pagebox for bounding box
+-- > pagebox=cropbox, mediabox, artbox, trimbox, bleedbox
+local bbox_option = "auto"
+function process_arguments.B(script_arguments)
+ bbox_option = remove(script_arguments, 1)
+end
+
+-- > Show this help message and exit
+function process_arguments.h(script_arguments)
+ print [[
+Usage: extractbb [-B pagebox] [-p page] [-q|-v] [-O] [-m|-x] FILE...
+ extractbb --help|--version
+Extract bounding box from PDF, PNG, JPEG, JP2, or BMP file; default output below.
+
+Options:
+ -B pagebox Specify a PDF pagebox for bounding box
+ pagebox=cropbox, mediabox, artbox, trimbox, bleedbox
+ -h | --help Show this help message and exit
+ --version Output version information and exit
+ -p page Specify a PDF page to extract bounding box
+ -q Be quiet
+ -v Be verbose
+ -O Write output to stdout
+ -m Output .bb file used in DVIPDFM (default)
+ -x Output .xbb file used in DVIPDFMx
+]]
+ os.exit(0)
+end
+
+process_arguments["-help"] = process_arguments.h
+
+-- > Output version information and exit
+function process_arguments.V(script_arguments)
+ print(version)
+ os.exit(0)
+end
+
+process_arguments["-version"] = process_arguments.V
+
+-- > Specify a PDF page to extract bounding box
+local page_number = 1
+function process_arguments.p(script_arguments)
+ page_number = tonumber(remove(script_arguments, 1))
+end
+
+-- > Be quiet
+function process_arguments.q(script_arguments)
+ show_errors = false
+end
+
+-- > Be verbose
+function process_arguments.v(script_arguments)
+ show_errors = true
+end
+
+-- > Write output to stdout
+local output_file
+function process_arguments.O(script_arguments)
+ output_file = io.stdout
+end
+
+-- Output format
+local output_format = "xbb"
+
+if script_arguments[0]:match("ebb") then
+ output_format = "bb"
+end
+
+-- > Output .bb file used in DVIPDFM (default)
+function process_arguments.m(script_arguments)
+ output_format = "bb"
+end
+
+-- > Output .xbb file used in DVIPDFMx
+function process_arguments.x(script_arguments)
+ output_format = "xbb"
+end
+
+-- Get the input file name.
+local input_name
+function process_arguments.i(script_arguments)
+ input_name = remove(script_arguments, 1)
+end
+
+process_arguments["-input-name"] = process_arguments.i
+
+-- Clear the interpreter and script names.
+script_arguments[-1] = nil
+script_arguments[0] = nil
+
+-- Process the arguments.
+while script_arguments[1] do
+ -- Get the next argument.
+ local arg = remove(script_arguments, 1)
+ local cmd = arg:match("^%-(.*)$")
+
+ -- Default to "--input-name" if no command is given.
+ if not cmd then
+ insert(script_arguments, 1, arg)
+ cmd = "-input-name"
+ end
+
+ -- Handle multi-character arguments.
+ if (cmd:len() >= 2) and (not cmd:match("^%-")) then
+ local i = 0
+ for char in cmd:gmatch(".") do
+ i = i + 1
+ insert(script_arguments, i, "-" .. char)
end
+
+ goto continue
end
- return path
+ -- Get the function to process the argument and run it.
+ local func = process_arguments[cmd]
+
+ if not func then
+ error("Invalid argument:", arg)
+ end
+
+ func(script_arguments)
+
+ ::continue::
end
--- Map the choice names to file names.
-local choice_mapping = {
- wrapper = find_script("extractbb-wrapper.lua"),
- scratch = find_script("extractbb-scratch.lua"),
+-- Validate the arguments.
+if not type(page_number) == "number" then
+ error("Invalid page number:", page_number)
+end
+
+if not input_name then
+ error("No input file specified.")
+end
+
+-- Validate the bounding box type. We need this rather crazy fallback scheme
+-- to match the behaviour of "extractbb".
+local bbox_orders = {}
+bbox_orders.mediabox = {
+ { img = "media", pdfe = "MediaBox" },
}
+bbox_orders.cropbox = {
+ { img = "crop", pdfe = "CropBox" }, unpack(bbox_orders.mediabox)
+}
+bbox_orders.artbox = {
+ { img = "art", pdfe = "ArtBox" }, unpack(bbox_orders.cropbox)
+}
+bbox_orders.trimbox = {
+ { img = "trim", pdfe = "TrimBox" }, unpack(bbox_orders.artbox)
+}
+bbox_orders.bleedbox = {
+ { img = "bleed", pdfe = "BleedBox" }, unpack(bbox_orders.trimbox)
+}
+bbox_orders.auto = {
+ bbox_orders.cropbox[1], bbox_orders.artbox[1], bbox_orders.trimbox[1],
+ bbox_orders.bleedbox[1], bbox_orders.mediabox[1],
+}
--- Choose the implementation to run.
-local choice = choice_mapping[env_choice] or choice_mapping[DEFAULT]
+local bbox_order = bbox_orders[bbox_option]
-if not choice then
- error("No implementation of extractbb found.")
+if not bbox_order then
+ error("Invalid PDF box type:", bbox_option)
end
--- And run it.
-dofile(choice)
+-- Set the default pixel resolution.
+local default_dpi
+if output_format == "xbb" then
+ default_dpi = 72
+elseif output_format == "bb" then
+ default_dpi = 100
+else
+ error("Invalid output format:", output_format)
+end
+
+-- Open the output file.
+if not output_file then
+ local base_name = input_name:match("(.+)%.([^.]-)$") or input_name
+ local output_name = base_name .. "." .. output_format
+ output_file = open_file(output_name, "write", "text")
+end
+
+
+------------------------
+--- Image Processing ---
+------------------------
+
+local x_min, y_min, x_max, y_max
+local num_pages, image_type
+local pdf_major_version, pdf_minor_version
+
+if open_image then
+ -- Check the number of pages.
+ local image = open_image(input_name)
+ num_pages = image.pages
+
+ if page_number > num_pages then
+ error("Invalid page number:", page_number)
+ end
+
+ -- Open the image to the specified page and bounding box. If the requested
+ -- bounding box is not available, LuaTeX will fall back to the crop box
+ -- or the media box.
+ image = open_image(input_name, page_number, bbox_order[1].img)
+
+ if not image then
+ error("Cannot open image:", input_name)
+ end
+
+ -- Get the image metadata.
+ image_type = image.imagetype
+ local bounding_box = image.bbox
+
+ if not bounding_box then
+ error("Cannot get bounding box:", page_number)
+ end
+
+ local x_resolution = image.xres
+ local y_resolution = image.yres
+
+ if (x_resolution or 0) == 0 then
+ x_resolution = default_dpi
+ end
+
+ if (y_resolution or 0) == 0 then
+ y_resolution = default_dpi
+ end
+
+ -- Convert the bounding box to PostScript points.
+ for i, dimen in ipairs(bounding_box) do
+ if image_type == "pdf" then
+ dimen = dimen / BP_TO_SP
+ else
+ if i % 2 == 1 then
+ dimen = dimen / x_resolution * IN_TO_BP
+ else
+ dimen = dimen / y_resolution * IN_TO_BP
+ end
+ end
+
+ bounding_box[i] = dimen
+ end
+
+ -- Save the bounding box.
+ x_min, y_min, x_max, y_max = unpack(bounding_box)
+
+ -- We can't get the PDF version with the "img" library, so we'll just
+ -- pretend that it's v1.5 (which supports most features).
+ pdf_major_version = 1
+ pdf_minor_version = 5
+else
+ -- Fallback to PDFs only.
+ image_type = "pdf"
+ local document = pdfe.open(input_name)
+
+ if pdfe.getstatus(document) ~= 0 then
+ error("Cannot open PDF file:", input_name)
+ end
+
+ -- Check the number of pages.
+ num_pages = pdfe.getnofpages(document)
+
+ if type(num_pages) ~= "number" then
+ error("Invalid number of pages:", num_pages)
+ end
+
+ if page_number > num_pages then
+ error("Invalid page number:", page_number)
+ end
+
+ -- Get the page.
+ local page = pdfe.getpage(document, page_number)
+
+ if not page then
+ error("Cannot get page:", page_number)
+ end
+
+ -- Get the bounding box. Here, we check the boxes in the exact same order
+ -- that "extractbb" does.
+ local bounding_box
+ for _, bbox in ipairs(bbox_order) do
+ bounding_box = pdfe.getbox(page, bbox.pdfe)
+
+ if bounding_box then
+ break
+ end
+ end
+
+ if not bounding_box then
+ error("Cannot get bounding box:", page_number)
+ end
+
+ -- Save the bounding box.
+ x_min, y_min, x_max, y_max = unpack(bounding_box)
+
+ -- Get the PDF version.
+ pdf_major_version, pdf_minor_version = pdfe.getversion(document)
+end
+
+-- Validate the bounding box.
+for _, dimen in ipairs { x_min, y_min, x_max, y_max } do
+ if type(dimen) ~= "number" then
+ error("Invalid bounding box:", x_min, y_min, x_max, y_max)
+ end
+end
+
+
+--------------
+--- Output ---
+--------------
+
+-- Get the output fields and values.
+local lines = {}
+
+insert(lines, ("Title: %s"):format(input_name))
+insert(lines, ("Creator: %s"):format(version))
+insert(lines,
+ ("BoundingBox: %d %d %d %d")
+ :format(round(x_min), round(y_min), round(x_max), round(y_max)))
+
+if output_format == "xbb" then
+ insert(lines,
+ ("HiResBoundingBox: %0.6f %0.6f %0.6f %0.6f")
+ :format(x_min, y_min, x_max, y_max))
+
+ if image_type == "pdf" then
+ insert(lines,
+ ("PDFVersion: %d.%d")
+ :format(pdf_major_version, pdf_minor_version))
+
+ insert(lines, ("Pages: %d"):format(num_pages))
+ end
+
+end
+
+insert(lines, ("CreationDate: %s"):format(os.date(DATE_FORMAT, SOURCE_DATE_EPOCH)))
+
+-- Create the output text.
+local begin_line = "%%"
+local end_line = "\n"
+
+local text = begin_line ..
+ table.concat(lines, end_line .. begin_line) ..
+ end_line .. end_line
+
+-- Write the output text.
+output_file:write(text)
+output_file:close()
+
+-- Everything is done, so now we can exit.
+os.exit(0)
Deleted: trunk/Master/bin/aarch64-linux/ebb
===================================================================
--- trunk/Master/bin/aarch64-linux/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/aarch64-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/aarch64-linux/ebb
===================================================================
--- trunk/Master/bin/aarch64-linux/ebb (rev 0)
+++ trunk/Master/bin/aarch64-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/aarch64-linux/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/amd64-freebsd/ebb
===================================================================
--- trunk/Master/bin/amd64-freebsd/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/amd64-freebsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/amd64-freebsd/ebb
===================================================================
--- trunk/Master/bin/amd64-freebsd/ebb (rev 0)
+++ trunk/Master/bin/amd64-freebsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/amd64-freebsd/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/amd64-netbsd/ebb
===================================================================
--- trunk/Master/bin/amd64-netbsd/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/amd64-netbsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/amd64-netbsd/ebb
===================================================================
--- trunk/Master/bin/amd64-netbsd/ebb (rev 0)
+++ trunk/Master/bin/amd64-netbsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/amd64-netbsd/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/armhf-linux/ebb
===================================================================
--- trunk/Master/bin/armhf-linux/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/armhf-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/armhf-linux/ebb
===================================================================
--- trunk/Master/bin/armhf-linux/ebb (rev 0)
+++ trunk/Master/bin/armhf-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/armhf-linux/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/i386-freebsd/ebb
===================================================================
--- trunk/Master/bin/i386-freebsd/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/i386-freebsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/i386-freebsd/ebb
===================================================================
--- trunk/Master/bin/i386-freebsd/ebb (rev 0)
+++ trunk/Master/bin/i386-freebsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/i386-freebsd/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/i386-linux/ebb
===================================================================
--- trunk/Master/bin/i386-linux/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/i386-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/i386-linux/ebb
===================================================================
--- trunk/Master/bin/i386-linux/ebb (rev 0)
+++ trunk/Master/bin/i386-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/i386-linux/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/i386-netbsd/ebb
===================================================================
--- trunk/Master/bin/i386-netbsd/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/i386-netbsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/i386-netbsd/ebb
===================================================================
--- trunk/Master/bin/i386-netbsd/ebb (rev 0)
+++ trunk/Master/bin/i386-netbsd/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/i386-netbsd/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/i386-solaris/ebb
===================================================================
--- trunk/Master/bin/i386-solaris/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/i386-solaris/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/i386-solaris/ebb
===================================================================
--- trunk/Master/bin/i386-solaris/ebb (rev 0)
+++ trunk/Master/bin/i386-solaris/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/i386-solaris/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/universal-darwin/ebb
===================================================================
--- trunk/Master/bin/universal-darwin/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/universal-darwin/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/universal-darwin/ebb
===================================================================
--- trunk/Master/bin/universal-darwin/ebb (rev 0)
+++ trunk/Master/bin/universal-darwin/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/universal-darwin/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/windows/ebb.exe
===================================================================
(Binary files differ)
Deleted: trunk/Master/bin/x86_64-cygwin/ebb
===================================================================
--- trunk/Master/bin/x86_64-cygwin/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/x86_64-cygwin/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx.exe
\ No newline at end of file
Added: trunk/Master/bin/x86_64-cygwin/ebb
===================================================================
--- trunk/Master/bin/x86_64-cygwin/ebb (rev 0)
+++ trunk/Master/bin/x86_64-cygwin/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/x86_64-cygwin/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/x86_64-darwinlegacy/ebb
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/x86_64-darwinlegacy/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/x86_64-darwinlegacy/ebb
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/ebb (rev 0)
+++ trunk/Master/bin/x86_64-darwinlegacy/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/x86_64-darwinlegacy/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/x86_64-linux/ebb
===================================================================
--- trunk/Master/bin/x86_64-linux/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/x86_64-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/x86_64-linux/ebb
===================================================================
--- trunk/Master/bin/x86_64-linux/ebb (rev 0)
+++ trunk/Master/bin/x86_64-linux/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/x86_64-linux/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/x86_64-linuxmusl/ebb
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/x86_64-linuxmusl/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/x86_64-linuxmusl/ebb
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/ebb (rev 0)
+++ trunk/Master/bin/x86_64-linuxmusl/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/x86_64-linuxmusl/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: trunk/Master/bin/x86_64-solaris/ebb
===================================================================
--- trunk/Master/bin/x86_64-solaris/ebb 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/bin/x86_64-solaris/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1 +0,0 @@
-link xdvipdfmx
\ No newline at end of file
Added: trunk/Master/bin/x86_64-solaris/ebb
===================================================================
--- trunk/Master/bin/x86_64-solaris/ebb (rev 0)
+++ trunk/Master/bin/x86_64-solaris/ebb 2025-02-12 14:58:30 UTC (rev 73916)
@@ -0,0 +1 @@
+link extractbb
\ No newline at end of file
Property changes on: trunk/Master/bin/x86_64-solaris/ebb
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Master/texmf-dist/doc/man/man1/extractbb.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/extractbb.1 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/texmf-dist/doc/man/man1/extractbb.1 2025-02-12 14:58:30 UTC (rev 73916)
@@ -39,7 +39,7 @@
as used by
.BR dvipdfm .
.B Xbb
-may be defined as a synomym for
+may be defined as a synonym for
.B extractbb
on your system.
.PP
Modified: trunk/Master/texmf-dist/doc/man/man1/extractbb.man1.pdf
===================================================================
(Binary files differ)
Modified: trunk/Master/texmf-dist/doc/support/extractbb/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/extractbb/README.md 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/texmf-dist/doc/support/extractbb/README.md 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1,7 +1,7 @@
<!-- extractbb-lua
https://github.com/gucci-on-fleek/extractbb
SPDX-License-Identifier: MPL-2.0+ OR CC-BY-SA-4.0+
- SPDX-FileCopyrightText: 2024 Max Chernoff
+ SPDX-FileCopyrightText: 2024--2025 Max Chernoff
-->
`extractbb-lua`
@@ -11,36 +11,6 @@
[`extractbb`](https://texdoc.org/serve/extractbb/0), written in Lua.
-Variants
---------
-
-There are two variants of `extractbb-lua`:
-
-- **`wrapper`**: A wrapper script around the original `xdvipdmfx`-based
- `extractbb` that is used to fix a security vulernability in
- `xdvipdfmx`.
-
-- **`scratch`**: A standalone implementation of `extractbb`, written in
- Lua from scratch, with no dependencies on `xdvipdfmx`.
-
-Currently, the script `extractbb` defaults to the `wrapper` variant, but
-you can manually select any specific variant by setting the
-`TEXLIVE_EXTRACTBB` environment variable to either `wrapper` or
-`scratch`.
-
-> [!WARNING]
-> The `scratch` variant is still in development and may be buggy or
-> insecure.
-
-
-### Secret Developer Options
-
-If you set `TEXLIVE_EXTRACTBB` to the full path of an executable, it
-will run that directly. And if you set
-`TEXLIVE_EXTRACTBB_UNSAFE=unsafe`, then it will ignore some of the
-security checks.
-
-
Support
-------
@@ -78,4 +48,4 @@
licence file.)
---
-_v1.0.6 (2024-11-21)_ <!--%%version %%dashdate-->
+_v1.1.0 (2025-02-11)_ <!--%%version %%dashdate-->
Modified: trunk/Master/texmf-dist/doc/support/extractbb/extractbb.man1.pdf
===================================================================
(Binary files differ)
Deleted: trunk/Master/texmf-dist/scripts/extractbb/extractbb-scratch.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/extractbb/extractbb-scratch.lua 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/texmf-dist/scripts/extractbb/extractbb-scratch.lua 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1,706 +0,0 @@
-#!/usr/bin/env texlua
--- extractbb-lua
--- https://github.com/gucci-on-fleek/extractbb
--- SPDX-License-Identifier: MPL-2.0+
--- SPDX-FileCopyrightText: 2024 Max Chernoff
---
--- Inclusion Methods
--- =================
---
--- This script can use two different methods to extract bounding boxes from
--- images: the "img" module and the "pdfe" module. The "img" module will be
--- automatically selected in most cases and supports all image types that are
--- supported by the original "extractbb" program. If and only if the "img"
--- module fails to load, the "pdfe" module will be used as a fallback. However,
--- the "pdfe" module only supports PDF files. Both modules are built in to the
--- LuaTeX binaries, however due to some technical issues, the "img" module may
--- fail to load on some more exotic platforms.
---
---
--- Compatibility
--- =============
---
--- Based off of my testing, this Lua script is 100% compatible with the original
--- C-based "extractbb" program, with the following exceptions:
---
--- * When running in "img" mode, the PDF version is always reported as "1.5".
---
--- * When running in "img" mode, if the requested bounding box is not found,
--- the script will fallback to the Crop box or the Media box, instead of
--- following the original fallback order. (In practice, almost all PDFs set
--- all their bounding boxes equal to each other, and even if the boxes are
--- set to different values, the script will still return the requested box,
--- provided that it is set in the PDF.)
---
--- * When running in "pdfe" mode, only PDF files are supported.
---
--- All of these issues are very unlikely to affect any real-world documents.
---
---
--- Security
--- ========
---
--- This script is designed to be safely ran from restricted shell escape. A few
--- security features:
---
--- * The majority of this script runs inside a sandboxed Lua environment,
--- which only exposes a very restricted set of functions.
---
--- * All file-related functions available inside the sandbox first check with
--- kpathsea to ensure that the file is allowed to be opened.
---
--- * In the event of any errors, the script immediately exits.
---
--- * This script does not run (fork/exec) any external programs.
---
--- * This script is written entirely in Lua, so overflow/use-after-free
--- vulnerabilities are not possible.
---
--- Some potential security concerns:
---
--- * This script has not been audited or reviewed by anyone other than myself.
---
--- * Using the "ffi" module to load the "img" library is technically undefined
--- behaviour, and as such may potentially lead to unforeseen security
--- issues.
---
--- * The underlying LuaTeX modules may themselves have security
--- vulnerabilities, which would be inherited by this script.
-
-
-----------------------
---- Initialization ---
-----------------------
-
--- Pre-sandbox variables/constants
-local show_errors = true
-local SOURCE_DATE_EPOCH = tonumber(os.getenv("SOURCE_DATE_EPOCH"))
-local version = "extractbb.lua v1.0.6 (2024-11-21)" --%%version %%dashdate
-
--- Required for any kpathsea calls to work.
-kpse.set_program_name("texlua", "extractbb")
-
-
---------------------------
---- Questionable Hacks ---
---------------------------
-
--- LuaTeX doesn't load the "img" library in "texlua" mode, so we need this
--- questionable hack to load it manually. We do it inside of "pcall" since there
--- are some exotic platforms where the "ffi" module is unsupported.
-pcall(function()
- local ffi
-
- if img then
- -- Ok, we're running under a recent LuaTeX that enables the "img"
- -- library in "texlua" mode, so we can skip the FFI hack.
- ffi = false
- else
- ffi = package.loaded.ffi
-
- ffi.cdef[[
- typedef struct lua_State lua_State;
- typedef int (*lua_CFunction) (lua_State *L);
-
- lua_State *Luas;
- void luaL_requiref(lua_State *L, const char *modname,
- lua_CFunction openf, int glb);
- int luaopen_img(lua_State * L);
-
- int lua_only;
- ]]
-
- -- Basic initialization
- ffi.C.lua_only = 0
- end
- tex.initialize()
-
- -- "tex" module
- _G.tex = package.loaded.tex
- tex.enableprimitives("", tex.extraprimitives())
- tex.outputmode = 1
- tex.interactionmode = 0
-
- -- "pdf" module
- _G.pdf = package.loaded.pdf
- pdf.setignoreunknownimages(1)
- pdf.setmajorversion(2)
- pdf.setminorversion(0)
-
- -- "img" module
- if ffi then
- ffi.C.luaL_requiref(ffi.C.Luas, "img", ffi.C.luaopen_img, 1)
- end
-end)
-
--- In case of failure, define an empty "img" table.
-if not img then
- _G.img = {}
-end
-
-
-------------------
---- Sandboxing ---
-------------------
-
--- Prepare the sandbox for the rest of the script.
-local env = {
- arg = arg,
- io = { stdout = io.stdout, },
- ipairs = ipairs,
- math = math,
- os = { date = os.date, exit = os.exit, },
- pairs = pairs,
- pdfe = pdfe,
- print = print,
- select = select,
- table = table,
- tonumber = tonumber,
- type = type,
-}
-
-do
- -- Saved global functions
- local debug_traceback = debug.traceback
- local find_file = kpse.find_file
- local img_scan = img.scan
- local io_open = io.open
- local io_stderr = io.stderr
- local kpse_in_name_ok = kpse.in_name_ok
- local kpse_out_name_ok = kpse.out_name_ok
- local kpse_var_value = kpse.var_value
- local lfs_attributes = lfs.attributes
- local os_exit = os.exit
- local os_setenv = os.setenv
- local pdfe_open = pdfe.open
- local select = select
- local tostring = tostring
-
- -- Error messages
- local function error(...)
- if show_errors then
- -- Header
- io_stderr:write("! extractbb ERROR: ")
-
- -- Message
- for i = 1, select("#", ...) do
- io_stderr:write(tostring(select(i, ...)), " ")
- end
-
- -- Traceback
- io_stderr:write("\n", "\n")
- io_stderr:write(debug_traceback(nil, 2), "\n")
- end
-
- -- Flush and exit
- io_stderr:flush()
- os_exit(1)
- end
-
- env.error = error
-
- -- Make sure that "openin_any" is at least "restricted", and that
- -- "openout_any" is at least "paranoid".
- local initial_openin = kpse_var_value("openin_any")
- local initial_openout = kpse_var_value("openout_any")
-
- if (initial_openin ~= "r") or (initial_openout ~= "p") then
- os_setenv("openin_any", "r")
- end
-
- if (initial_openout ~= "p") then
- os_setenv("openout_any", "p")
- end
-
- -- Check the input paths.
- local function resolve_input_name(file_name)
- local file_path = find_file(file_name, "graphic/figure", true)
- if not file_path then
- error("Cannot find input file:", file_name)
- end
-
- local allowed = kpse_in_name_ok(file_path)
- if not allowed then
- error("Input file is not allowed:", file_path)
- end
-
- local mode = lfs_attributes(file_path, "mode")
- if mode ~= "file" then
- error("Input file is not a regular file:", file_path)
- end
-
- return file_path
- end
-
- -- Check the output paths.
- local function resolve_output_name(file_name)
- local allowed = kpse_out_name_ok(file_name)
- if not allowed then
- error("Output file is not allowed:", file_name)
- end
-
- local name, extension = file_name:match("(.+)%.([^.]-)$")
-
- if (not name) or (not extension) or
- (name == "") or (extension == "")
- then
- error("Output file has no extension:", file_name)
- end
-
- if (extension ~= "xbb") and (extension ~= "bb") then
- error("Output file has an invalid extension:", file_name)
- end
-
- -- We shouldn't allow files with weird characters in their names.
- if name:match("[%c%%\t\r\n><*|]") then
- error("Output file has an invalid name:", file_name)
- end
-
- return file_name
- end
-
- -- Opens a file.
- function env.open_file(file_name, read_write, binary_text)
- local file_path, mode
- if read_write == "read" then
- file_path = resolve_input_name(file_name)
- mode = "r"
- elseif read_write == "write" then
- file_path = resolve_output_name(file_name)
- mode = "w"
- else
- error("Invalid read/write mode:", read_write)
- end
-
- if binary_text == "binary" then
- mode = mode .. "b"
- elseif binary_text == "text" then
- mode = mode .. ""
- else
- error("Invalid binary/text mode:", binary_text)
- end
-
- local file, message = io_open(file_path, mode)
-
- if not file then
- error("Cannot open file:", file_path, message)
- end
-
- return file
- end
-
- -- Open an PDF file.
- function env.pdfe.open(file_name)
- local file_path = resolve_input_name(file_name)
- return pdfe_open(file_path)
- end
-
- -- Open an image file.
- function env.open_image(file_name, page, box)
- local file_path = resolve_input_name(file_name)
- return img_scan {
- filename = file_path,
- filepath = file_path,
- page = page,
- pagebox = box,
- }
- end
-
- if not img_scan then
- env.open_image = false
- end
-end
-
--- Prevent trying to change the environment.
-local function bad_index(...)
- env.error("Attempt to access an undefined index:", select(2, ...))
-end
-
-setmetatable(env, {
- __index = bad_index,
- __metatable = false,
- __newindex = bad_index,
-})
-
--- Set the environment.
-_ENV = env
-
-
------------------------------------
---- Post-Sandbox Initialization ---
------------------------------------
-
--- Constants
-local BP_TO_SP = 65781.76
-local IN_TO_BP = 72
-local DATE_FORMAT = "%a %b %d %H:%M:%S %Y" -- "%c"
-
--- Save often-used globals for a slight speed boost.
-local floor = math.floor
-local insert = table.insert
-local remove = table.remove
-local script_arguments = arg
-local unpack = table.unpack
-
--- General-purpose functions
-local function round(number)
- return floor(number +0.5)
-end
-
-
--------------------------
---- Argument Handling ---
--------------------------
-
--- Define the argument handling functions.
-local process_arguments = {}
-
--- > Specify a PDF pagebox for bounding box
--- > pagebox=cropbox, mediabox, artbox, trimbox, bleedbox
-local bbox_option = "auto"
-function process_arguments.B(script_arguments)
- bbox_option = remove(script_arguments, 1)
-end
-
--- > Show this help message and exit
-function process_arguments.h(script_arguments)
- print [[
-Usage: extractbb [-B pagebox] [-p page] [-q|-v] [-O] [-m|-x] FILE...
- extractbb --help|--version
-Extract bounding box from PDF, PNG, JPEG, JP2, or BMP file; default output below.
-
-Options:
- -B pagebox Specify a PDF pagebox for bounding box
- pagebox=cropbox, mediabox, artbox, trimbox, bleedbox
- -h | --help Show this help message and exit
- --version Output version information and exit
- -p page Specify a PDF page to extract bounding box
- -q Be quiet
- -v Be verbose
- -O Write output to stdout
- -m Output .bb file used in DVIPDFM (default)
- -x Output .xbb file used in DVIPDFMx
-]]
- os.exit(0)
-end
-
-process_arguments["-help"] = process_arguments.h
-
--- > Output version information and exit
-function process_arguments.V(script_arguments)
- print(version)
- os.exit(0)
-end
-
-process_arguments["-version"] = process_arguments.V
-
--- > Specify a PDF page to extract bounding box
-local page_number = 1
-function process_arguments.p(script_arguments)
- page_number = tonumber(remove(script_arguments, 1))
-end
-
--- > Be quiet
-function process_arguments.q(script_arguments)
- show_errors = false
-end
-
--- > Be verbose
-function process_arguments.v(script_arguments)
- show_errors = true
-end
-
--- > Write output to stdout
-local output_file
-function process_arguments.O(script_arguments)
- output_file = io.stdout
-end
-
--- Output format
-local output_format = "xbb"
-
-if script_arguments[0]:match("ebb") then
- output_format = "bb"
-end
-
--- > Output .bb file used in DVIPDFM (default)
-function process_arguments.m(script_arguments)
- output_format = "bb"
-end
-
--- > Output .xbb file used in DVIPDFMx
-function process_arguments.x(script_arguments)
- output_format = "xbb"
-end
-
--- Get the input file name.
-local input_name
-function process_arguments.i(script_arguments)
- input_name = remove(script_arguments, 1)
-end
-
-process_arguments["-input-name"] = process_arguments.i
-
--- Clear the interpreter and script names.
-script_arguments[-1] = nil
-script_arguments[0] = nil
-
--- Process the arguments.
-while script_arguments[1] do
- -- Get the next argument.
- local arg = remove(script_arguments, 1)
- local cmd = arg:match("^%-(.*)$")
-
- -- Default to "--input-name" if no command is given.
- if not cmd then
- insert(script_arguments, 1, arg)
- cmd = "-input-name"
- end
-
- -- Handle multi-character arguments.
- if (cmd:len() >= 2) and (not cmd:match("^%-")) then
- local i = 0
- for char in cmd:gmatch(".") do
- i = i + 1
- insert(script_arguments, i, "-" .. char)
- end
-
- goto continue
- end
-
- -- Get the function to process the argument and run it.
- local func = process_arguments[cmd]
-
- if not func then
- error("Invalid argument:", arg)
- end
-
- func(script_arguments)
-
- ::continue::
-end
-
--- Validate the arguments.
-if not type(page_number) == "number" then
- error("Invalid page number:", page_number)
-end
-
-if not input_name then
- error("No input file specified.")
-end
-
--- Validate the bounding box type. We need this rather crazy fallback scheme
--- to match the behaviour of "extractbb".
-local bbox_orders = {}
-bbox_orders.mediabox = {
- { img = "media", pdfe = "MediaBox" },
-}
-bbox_orders.cropbox = {
- { img = "crop", pdfe = "CropBox" }, unpack(bbox_orders.mediabox)
-}
-bbox_orders.artbox = {
- { img = "art", pdfe = "ArtBox" }, unpack(bbox_orders.cropbox)
-}
-bbox_orders.trimbox = {
- { img = "trim", pdfe = "TrimBox" }, unpack(bbox_orders.artbox)
-}
-bbox_orders.bleedbox = {
- { img = "bleed", pdfe = "BleedBox" }, unpack(bbox_orders.trimbox)
-}
-bbox_orders.auto = {
- bbox_orders.cropbox[1], bbox_orders.artbox[1], bbox_orders.trimbox[1],
- bbox_orders.bleedbox[1], bbox_orders.mediabox[1],
-}
-
-local bbox_order = bbox_orders[bbox_option]
-
-if not bbox_order then
- error("Invalid PDF box type:", bbox_option)
-end
-
--- Set the default pixel resolution.
-local default_dpi
-if output_format == "xbb" then
- default_dpi = 72
-elseif output_format == "bb" then
- default_dpi = 100
-else
- error("Invalid output format:", output_format)
-end
-
--- Open the output file.
-if not output_file then
- local base_name = input_name:match("(.+)%.([^.]-)$") or input_name
- local output_name = base_name .. "." .. output_format
- output_file = open_file(output_name, "write", "text")
-end
-
-
-------------------------
---- Image Processing ---
-------------------------
-
-local x_min, y_min, x_max, y_max
-local num_pages, image_type
-local pdf_major_version, pdf_minor_version
-
-if open_image then
- -- Check the number of pages.
- local image = open_image(input_name)
- num_pages = image.pages
-
- if page_number > num_pages then
- error("Invalid page number:", page_number)
- end
-
- -- Open the image to the specified page and bounding box. If the requested
- -- bounding box is not available, LuaTeX will fall back to the crop box
- -- or the media box.
- image = open_image(input_name, page_number, bbox_order[1].img)
-
- if not image then
- error("Cannot open image:", input_name)
- end
-
- -- Get the image metadata.
- image_type = image.imagetype
- local bounding_box = image.bbox
-
- if not bounding_box then
- error("Cannot get bounding box:", page_number)
- end
-
- local x_resolution = image.xres
- local y_resolution = image.yres
-
- if (x_resolution or 0) == 0 then
- x_resolution = default_dpi
- end
-
- if (y_resolution or 0) == 0 then
- y_resolution = default_dpi
- end
-
- -- Convert the bounding box to PostScript points.
- for i, dimen in ipairs(bounding_box) do
- if image_type == "pdf" then
- dimen = dimen / BP_TO_SP
- else
- if i % 2 == 1 then
- dimen = dimen / x_resolution * IN_TO_BP
- else
- dimen = dimen / y_resolution * IN_TO_BP
- end
- end
-
- bounding_box[i] = dimen
- end
-
- -- Save the bounding box.
- x_min, y_min, x_max, y_max = unpack(bounding_box)
-
- -- We can't get the PDF version with the "img" library, so we'll just
- -- pretend that it's v1.5 (which supports most features).
- pdf_major_version = 1
- pdf_minor_version = 5
-else
- -- Fallback to PDFs only.
- image_type = "pdf"
- local document = pdfe.open(input_name)
-
- if pdfe.getstatus(document) ~= 0 then
- error("Cannot open PDF file:", input_name)
- end
-
- -- Check the number of pages.
- num_pages = pdfe.getnofpages(document)
-
- if type(num_pages) ~= "number" then
- error("Invalid number of pages:", num_pages)
- end
-
- if page_number > num_pages then
- error("Invalid page number:", page_number)
- end
-
- -- Get the page.
- local page = pdfe.getpage(document, page_number)
-
- if not page then
- error("Cannot get page:", page_number)
- end
-
- -- Get the bounding box. Here, we check the boxes in the exact same order
- -- that "extractbb" does.
- local bounding_box
- for _, bbox in ipairs(bbox_order) do
- bounding_box = pdfe.getbox(page, bbox.pdfe)
-
- if bounding_box then
- break
- end
- end
-
- if not bounding_box then
- error("Cannot get bounding box:", page_number)
- end
-
- -- Save the bounding box.
- x_min, y_min, x_max, y_max = unpack(bounding_box)
-
- -- Get the PDF version.
- pdf_major_version, pdf_minor_version = pdfe.getversion(document)
-end
-
--- Validate the bounding box.
-for _, dimen in ipairs { x_min, y_min, x_max, y_max } do
- if type(dimen) ~= "number" then
- error("Invalid bounding box:", x_min, y_min, x_max, y_max)
- end
-end
-
-
---------------
---- Output ---
---------------
-
--- Get the output fields and values.
-local lines = {}
-
-insert(lines, ("Title: %s"):format(input_name))
-insert(lines, ("Creator: %s"):format(version))
-insert(lines,
- ("BoundingBox: %d %d %d %d")
- :format(round(x_min), round(y_min), round(x_max), round(y_max)))
-
-if output_format == "xbb" then
- insert(lines,
- ("HiResBoundingBox: %0.6f %0.6f %0.6f %0.6f")
- :format(x_min, y_min, x_max, y_max))
-
- if image_type == "pdf" then
- insert(lines,
- ("PDFVersion: %d.%d")
- :format(pdf_major_version, pdf_minor_version))
-
- insert(lines, ("Pages: %d"):format(num_pages))
- end
-
-end
-
-insert(lines, ("CreationDate: %s"):format(os.date(DATE_FORMAT, SOURCE_DATE_EPOCH)))
-
--- Create the output text.
-local begin_line = "%%"
-local end_line = "\n"
-
-local text = begin_line ..
- table.concat(lines, end_line .. begin_line) ..
- end_line .. end_line
-
--- Write the output text.
-output_file:write(text)
-output_file:close()
-
--- Everything is done, so now we can exit.
-os.exit(0)
Deleted: trunk/Master/texmf-dist/scripts/extractbb/extractbb-wrapper.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/extractbb/extractbb-wrapper.lua 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/texmf-dist/scripts/extractbb/extractbb-wrapper.lua 2025-02-12 14:58:30 UTC (rev 73916)
@@ -1,270 +0,0 @@
-#!/usr/bin/env texlua
--- extractbb-lua
--- https://github.com/gucci-on-fleek/extractbb
--- SPDX-License-Identifier: MPL-2.0+
--- SPDX-FileCopyrightText: 2024 Max Chernoff
---
--- A generic wrapper to make commands safe to run with restricted shell escape.
---
--- Originally created for extractbb, which is listed in shell_escape_commands,
--- but can be run as dvipdfm(x), which in turn can run arbitrary commands
--- using its -D option.
---
--- The idea is to exec "ebb --ebb <other args>", since only argv[1] is
--- used by dvipdfmx to determine its behavior.
---
--- Note: This script can only adjust the paths and arguments of the target
--- executable; it *CANNOT* make an arbitrary program safe to run with
--- restricted shell escape.
-
--- A shorter, less paranoid version.
--- (Prepend a hyphen to the line below to enable).
---[=[
-arg[0] = arg[0]:gsub("extractbb", "ebb")
-table.insert(arg, 1, "ebb")
-table.insert(arg, 2, "--extractbb")
-os.exec(arg)
-os.exit(1)
---]=]
-
----------------------
---- Configuration ---
----------------------
-
--- The base name of this script. (Example: ``extractbb'')
-local SCRIPT_NAME = "extractbb"
-
--- The base name of the path to the target program. (Example: ``xdvipdfmx'')
-local TARGET_PATH_NAME = "xdvipdfmx"
-
--- The name to use when calling the target program. Equivalent to ``argv[0]''
--- in C. (Example: ``extractbb'')
-local TARGET_EXEC_NAME = "ebb"
-
--- Any extra arguments to be prepended to the target program, before any
--- user-supplied arguments. Equivalent to ``argv[1], ...'' in C.
--- (Example: ``--extractbb'')
-local TARGET_PREPEND_ARGS = { "--extractbb" }
-
--- Any extra arguments to be appended to the target program, after any
--- user-supplied arguments. Equivalent to ``..., argv[argc]'' in C.
-local TARGET_APPEND_ARGS = {}
-
--- Sets the value of ``openin_any'' to this value. If ``nil'', then the value
--- will be left unchanged. (Example: ``r'')
-local READ_PERMS = "r"
-
--- Sets the value of ``openout_any'' to this value. If ``nil'', then the value
--- will be left unchanged. (Example: ``p'')
-local WRITE_PERMS = "p"
-
--- The name of the Lua interpreter. (Example: ``texlua'')
-local INTERPRETER_NAME = "texlua"
-
--- The extension of the interpreter. Extensionless-names are also permitted.
--- (Example: ``exe'')
-local INTERPRETER_EXT = "exe"
-
-
-----------------------
---- Initialization ---
-----------------------
-
--- Save often-used globals for a slight speed boost.
-local insert = table.insert
-
--- Set the kpathsea program name
-kpse.set_program_name(INTERPRETER_NAME, SCRIPT_NAME)
-
--- Rename the input arguments so we don't get confused
-local script_args = arg
-
-
-----------------------------
---- Function Definitions ---
-----------------------------
-
--- Error messages
-local function error(title, details)
- -- Header
- io.stderr:write("! extractbb ERROR: ")
- io.stderr:write(title)
- io.stderr:write(".\n\nTechnical Details:\n")
-
- -- Messages
- for key, value in pairs(details) do
- io.stderr:write(tostring(key), ": ")
- io.stderr:write("(", type(value), ") ")
- io.stderr:write(tostring(value), "\n")
- end
-
- -- Traceback
- io.stderr:write("\n")
- io.stderr:write(debug.traceback(nil, 2), "\n")
-
- -- Flush and exit
- io.stderr:flush()
- os.exit(1)
-end
-
--- Get the directory, name, and extension from a full path. We'll split on
--- either a forward or backward slash---Windows can use either, and we don't
--- need to support Unix systems with TL installed to a directory with
--- backslashes in its name.
-local split_dir_pattern = "^(.*)[/\\]([^/\\]-)$"
-local split_ext_pattern = "(.*)%.([^.]-)$"
-
-local function split_path(path)
- -- Make sure that we were given a string
- if type(path) ~= "string" then
- return nil, nil, nil
- end
-
- -- Split the (directory) from the (name and extension)
- local dir, name_ext = path:match(split_dir_pattern)
-
- -- No directory
- if not dir then
- dir = nil
- name_ext = path
-
- -- A bare directory (with a trailing slash)
- elseif name_ext == "" then
- return dir, nil, nil
- end
-
- -- Split the (name) from the (extension)
- local name, ext = name_ext:match(split_ext_pattern)
-
- -- No extension (or a dotfile)
- if (not name) or (name == "") then
- name = name_ext
- ext = nil
- end
-
- return dir, name, ext
-end
-
--- See if a file exists
-local function file_exists(path)
- local mode = lfs.attributes(path, "mode")
- return (mode == "file") or (mode == "link")
-end
-
-
----------------------
---- Safety Checks ---
----------------------
-
--- Make sure that we're running unrestricted.
-if status.shell_escape ~= 1 then
- error("Shell escape has been disabled", {
- shell_escape = status.shell_escape,
- })
-end
-
-if status.safer_option ~= 0 then
- error("The ``safer'' option has been enabled", {
- safer_option = status.safer_option,
- })
-end
-
--- Set the file permissions.
-if READ_PERMS then
- os.setenv("openin_any", READ_PERMS)
-end
-
-if WRITE_PERMS then
- os.setenv("openout_any", WRITE_PERMS)
-end
-
--- Get the location of the interpreter
-local interpreter_dir = os.selfdir or kpse.var_value("SELFAUTOLOC")
-local _, interpreter_name, interpreter_ext = split_path(script_args[-1])
-
-if os.type == "windows" then
- interpreter_ext = INTERPRETER_EXT
-end
-
--- Error details
-local error_details = {
- interpreter_dir = interpreter_dir or "<nil>",
- interpreter_name = interpreter_name or "<nil>",
- interpreter_ext = interpreter_ext or "<nil>",
- os_type = os.type or "<nil>",
- os_name = os.name or "<nil>",
-}
-
--- Get the path to the target program
-local target_ext = interpreter_ext and ("." .. interpreter_ext) or ""
-local target_path = interpreter_dir .. "/" .. TARGET_PATH_NAME .. target_ext
-
-error_details.target_path = target_path or "<nil>"
-error_details.target_ext = target_ext or "<nil>"
-
--- Make sure that the target program exists
-if not file_exists(target_path) then
- error("The target program does not exist", error_details)
-end
-
-
-----------------------
---- Run the target ---
-----------------------
-
--- Generate the target arguments
-local target_args = {
- [0] = target_path, -- Path to the executable
- [1] = TARGET_EXEC_NAME, -- argv[0]
-}
-
--- argv[2] through argv[n]
-for _, arg in ipairs(TARGET_PREPEND_ARGS) do
- insert(target_args, arg)
-end
-
-for i = 1, #script_args do
- -- We use a numeric iterator here to avoid ``arg[-1]'' and ``arg[0]''.
- local this_arg = script_args[i]
- insert(target_args, this_arg)
-
- -- Show version information
- if this_arg:match("%-version") then
- print("[Wrapped by extractbb.lua v1.0.6 (2024-11-21)]") --%%version %%dashdate
- end
-end
-
-for _, arg in ipairs(TARGET_APPEND_ARGS) do
- insert(target_args, arg)
-end
-
--- For Unixish OSs, argv is passed as an array into a spawned process, so no
--- further tokenization is done by the C runtime, therefore arguments containing
--- special characters won't cause any issues. For Windows, argv is concatenated
--- into a single string when spawning a new process, then retokenized by the C
--- runtime in the new program, so we need to be careful with special characters
--- here since they might be interpreted as token separators.
-if os.type == "windows" then
- for i, arg in ipairs(target_args) do
- -- Hmm, which characters do we need to protect against?
- if arg:match('"') then
- -- Already contains a quote, so let's hope that this means that
- -- someone already quoted it correctly.
- elseif arg:match("[^-_%l%u%d]") then
- -- Contains a special character, so let's quote it.
- target_args[i] = '"' .. arg .. '"'
- end
- -- Any other cases and things should be fine. Probably.
- end
-end
-
--- Run the target program, replacing the current process
-local _, err = os.exec(target_args)
-
--- Unreachable except in the case of a failed exec
-for key, value in ipairs(target_args) do
- error_details["target_args[" .. key .. "]"] = value
-end
-
-error_details.exec_message = err or "<nil>"
-error("The target program failed to run", error_details)
Modified: trunk/Master/texmf-dist/scripts/extractbb/extractbb.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/extractbb/extractbb.lua 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/texmf-dist/scripts/extractbb/extractbb.lua 2025-02-12 14:58:30 UTC (rev 73916)
@@ -2,86 +2,664 @@
-- extractbb-lua
-- https://github.com/gucci-on-fleek/extractbb
-- SPDX-License-Identifier: MPL-2.0+
--- SPDX-FileCopyrightText: 2024 Max Chernoff
+-- SPDX-FileCopyrightText: 2024--2025 Max Chernoff
--
--- A wrapper script to allow you to choose which implementation of extractbb to
--- use. Should hopefully be replaced with the ``scratch'' file in TeX Live 2025.
+-- Inclusion Methods
+-- =================
--
--- v1.0.6 (2024-11-21) %%version %%dashdate
+-- This script can use two different methods to extract bounding boxes from
+-- images: the "img" module and the "pdfe" module. The "img" module will be
+-- automatically selected in most cases and supports all image types that are
+-- supported by the original "extractbb" program. If and only if the "img"
+-- module fails to load, the "pdfe" module will be used as a fallback. However,
+-- the "pdfe" module only supports PDF files. Both modules are built in to the
+-- LuaTeX binaries, however due to some technical issues, the "img" module may
+-- fail to load on some more exotic platforms.
+--
+--
+-- Compatibility
+-- =============
+--
+-- Based off of my testing, this Lua script is 100% compatible with the original
+-- C-based "extractbb" program, with the following exceptions:
+--
+-- * When running in "img" mode, the PDF version is always reported as "1.5".
+--
+-- * When running in "img" mode, if the requested bounding box is not found,
+-- the script will fallback to the Crop box or the Media box, instead of
+-- following the original fallback order. (In practice, almost all PDFs set
+-- all their bounding boxes equal to each other, and even if the boxes are
+-- set to different values, the script will still return the requested box,
+-- provided that it is set in the PDF.)
+--
+-- * When running in "pdfe" mode, only PDF files are supported.
+--
+-- All of these issues are very unlikely to affect any real-world documents.
+--
+--
+-- Security
+-- ========
+--
+-- This script is designed to be safely ran from restricted shell escape. A few
+-- security features:
+--
+-- * The majority of this script runs inside a sandboxed Lua environment,
+-- which only exposes a very restricted set of functions.
+--
+-- * All file-related functions available inside the sandbox first check with
+-- kpathsea to ensure that the file is allowed to be opened.
+--
+-- * In the event of any errors, the script immediately exits.
+--
+-- * This script does not run (fork/exec) any external programs.
+--
+-- * This script is written entirely in Lua, so overflow/use-after-free
+-- vulnerabilities are not possible.
+--
+-- Some potential security concerns:
+--
+-- * This script has not been audited or reviewed by anyone other than myself.
+--
+-- * The underlying LuaTeX modules may themselves have security
+-- vulnerabilities, which would be inherited by this script.
----------------------
---- Configuration ---
----------------------
--- Choose which implementation of extractbb to use.
-local DEFAULT = "wrapper"
+----------------------
+--- Initialization ---
+----------------------
------------------
---- Execution ---
------------------
+-- Pre-sandbox variables/constants
+local show_errors = true
+local SOURCE_DATE_EPOCH = tonumber(os.getenv("SOURCE_DATE_EPOCH"))
+local version = "extractbb.lua v1.1.0 (2025-02-11)" --%%version %%dashdate
--- Send the error messages to stderr.
-local function error(...)
- -- Header
- io.stderr:write("! extractbb ERROR: ")
+-- Required for any kpathsea calls to work.
+kpse.set_program_name("texlua", "extractbb")
- -- Message
- for i = 1, select("#", ...) do
- io.stderr:write(tostring(select(i, ...)), " ")
+-- Required to use the "img" module from texlua, but only works for LuaTeX
+-- versions >= 1.21.0.
+if not (status.development_id >= 7661) then
+ error("LuaTeX version is too old, cannot proceed.")
+end
+texconfig.texlua_img = true
+
+-- We need to set \outputmode to PDF to be able to use most of the "img" module
+-- functions, but to set \outputmode, we need to initialize the TeX interpreter.
+tex.initialize()
+_G.tex = package.loaded.tex
+tex.enableprimitives("", tex.extraprimitives())
+tex.outputmode = 1
+tex.interactionmode = 0
+
+-- "pdf" module
+_G.pdf = package.loaded.pdf
+pdf.setignoreunknownimages(1)
+pdf.setmajorversion(2)
+pdf.setminorversion(0)
+
+
+------------------
+--- Sandboxing ---
+------------------
+
+-- Prepare the sandbox for the rest of the script.
+local env = {
+ arg = arg,
+ io = { stdout = io.stdout, },
+ ipairs = ipairs,
+ math = math,
+ os = { date = os.date, exit = os.exit, },
+ pairs = pairs,
+ pdfe = pdfe,
+ print = print,
+ select = select,
+ table = table,
+ tonumber = tonumber,
+ type = type,
+}
+
+do
+ -- Saved global functions
+ local debug_traceback = debug.traceback
+ local find_file = kpse.find_file
+ local img_scan = img.scan
+ local io_open = io.open
+ local io_stderr = io.stderr
+ local kpse_in_name_ok = kpse.in_name_ok
+ local kpse_out_name_ok = kpse.out_name_ok
+ local kpse_var_value = kpse.var_value
+ local lfs_attributes = lfs.attributes
+ local os_exit = os.exit
+ local os_setenv = os.setenv
+ local pdfe_open = pdfe.open
+ local select = select
+ local tostring = tostring
+
+ -- Error messages
+ local function error(...)
+ if show_errors then
+ -- Header
+ io_stderr:write("! extractbb ERROR: ")
+
+ -- Message
+ for i = 1, select("#", ...) do
+ io_stderr:write(tostring(select(i, ...)), " ")
+ end
+
+ -- Traceback
+ io_stderr:write("\n", "\n")
+ io_stderr:write(debug_traceback(nil, 2), "\n")
+ end
+
+ -- Flush and exit
+ io_stderr:flush()
+ os_exit(1)
end
- -- Flush and exit
- io.stderr:write("\n")
- io.stderr:flush()
- os.exit(1)
+ env.error = error
+
+ -- Make sure that "openin_any" is at least "restricted", and that
+ -- "openout_any" is at least "paranoid".
+ local initial_openin = kpse_var_value("openin_any")
+ local initial_openout = kpse_var_value("openout_any")
+
+ if (initial_openin ~= "r") or (initial_openout ~= "p") then
+ os_setenv("openin_any", "r")
+ end
+
+ if (initial_openout ~= "p") then
+ os_setenv("openout_any", "p")
+ end
+
+ -- Check the input paths.
+ local function resolve_input_name(file_name)
+ local file_path = find_file(file_name, "graphic/figure", true)
+ if not file_path then
+ error("Cannot find input file:", file_name)
+ end
+
+ local allowed = kpse_in_name_ok(file_path)
+ if not allowed then
+ error("Input file is not allowed:", file_path)
+ end
+
+ local mode = lfs_attributes(file_path, "mode")
+ if mode ~= "file" then
+ error("Input file is not a regular file:", file_path)
+ end
+
+ return file_path
+ end
+
+ -- Check the output paths.
+ local function resolve_output_name(file_name)
+ local allowed = kpse_out_name_ok(file_name)
+ if not allowed then
+ error("Output file is not allowed:", file_name)
+ end
+
+ local name, extension = file_name:match("(.+)%.([^.]-)$")
+
+ if (not name) or (not extension) or
+ (name == "") or (extension == "")
+ then
+ error("Output file has no extension:", file_name)
+ end
+
+ if (extension ~= "xbb") and (extension ~= "bb") then
+ error("Output file has an invalid extension:", file_name)
+ end
+
+ -- We shouldn't allow files with weird characters in their names.
+ if name:match("[%c%%\t\r\n><*|]") then
+ error("Output file has an invalid name:", file_name)
+ end
+
+ return file_name
+ end
+
+ -- Opens a file.
+ function env.open_file(file_name, read_write, binary_text)
+ local file_path, mode
+ if read_write == "read" then
+ file_path = resolve_input_name(file_name)
+ mode = "r"
+ elseif read_write == "write" then
+ file_path = resolve_output_name(file_name)
+ mode = "w"
+ else
+ error("Invalid read/write mode:", read_write)
+ end
+
+ if binary_text == "binary" then
+ mode = mode .. "b"
+ elseif binary_text == "text" then
+ mode = mode .. ""
+ else
+ error("Invalid binary/text mode:", binary_text)
+ end
+
+ local file, message = io_open(file_path, mode)
+
+ if not file then
+ error("Cannot open file:", file_path, message)
+ end
+
+ return file
+ end
+
+ -- Open an PDF file.
+ function env.pdfe.open(file_name)
+ local file_path = resolve_input_name(file_name)
+ return pdfe_open(file_path)
+ end
+
+ -- Open an image file.
+ function env.open_image(file_name, page, box)
+ local file_path = resolve_input_name(file_name)
+ return img_scan {
+ filename = file_path,
+ filepath = file_path,
+ page = page,
+ pagebox = box,
+ }
+ end
+
+ if not img_scan then
+ env.open_image = false
+ end
end
--- Get the value of the environment variable that decides which version to run.
-local env_choice = os.env["TEXLIVE_EXTRACTBB"]
+-- Prevent trying to change the environment.
+local function bad_index(...)
+ env.error("Attempt to access an undefined index:", select(2, ...))
+end
--- If the environment variable is set to a file path, run that directly.
-local env_mode = lfs.attributes(env_choice or "", "mode")
-if (env_mode == "file") or (env_mode == "link") then
- arg[0] = env_choice
- table.insert(arg, 1, env_choice)
- arg[-1] = nil
- return os.exec(arg)
+setmetatable(env, {
+ __index = bad_index,
+ __metatable = false,
+ __newindex = bad_index,
+})
+
+-- Set the environment.
+_ENV = env
+
+
+-----------------------------------
+--- Post-Sandbox Initialization ---
+-----------------------------------
+
+-- Constants
+local BP_TO_SP = 65781.76
+local IN_TO_BP = 72
+local DATE_FORMAT = "%a %b %d %H:%M:%S %Y" -- "%c"
+
+-- Save often-used globals for a slight speed boost.
+local floor = math.floor
+local insert = table.insert
+local remove = table.remove
+local script_arguments = arg
+local unpack = table.unpack
+
+-- General-purpose functions
+local function round(number)
+ return floor(number +0.5)
end
--- Find the subscripts
-kpse.set_program_name("texlua", "extractbb")
-local function find_script(name)
- -- Find the script, searching **only** in the scripts directories.
- local path = kpse.lookup(
- name,
- { path = kpse.var_value("TEXMFSCRIPTS"), format = "lua" }
- )
+-------------------------
+--- Argument Handling ---
+-------------------------
- -- Make sure that the script is not writable.
- if kpse.out_name_ok_silent_extended(path) then
- if os.env["TEXLIVE_EXTRACTBB_UNSAFE"] == "unsafe" then
- -- If we're running in development mode, then we can allow this.
- else
- error("Refusing to run a writable script.")
+-- Define the argument handling functions.
+local process_arguments = {}
+
+-- > Specify a PDF pagebox for bounding box
+-- > pagebox=cropbox, mediabox, artbox, trimbox, bleedbox
+local bbox_option = "auto"
+function process_arguments.B(script_arguments)
+ bbox_option = remove(script_arguments, 1)
+end
+
+-- > Show this help message and exit
+function process_arguments.h(script_arguments)
+ print [[
+Usage: extractbb [-B pagebox] [-p page] [-q|-v] [-O] [-m|-x] FILE...
+ extractbb --help|--version
+Extract bounding box from PDF, PNG, JPEG, JP2, or BMP file; default output below.
+
+Options:
+ -B pagebox Specify a PDF pagebox for bounding box
+ pagebox=cropbox, mediabox, artbox, trimbox, bleedbox
+ -h | --help Show this help message and exit
+ --version Output version information and exit
+ -p page Specify a PDF page to extract bounding box
+ -q Be quiet
+ -v Be verbose
+ -O Write output to stdout
+ -m Output .bb file used in DVIPDFM (default)
+ -x Output .xbb file used in DVIPDFMx
+]]
+ os.exit(0)
+end
+
+process_arguments["-help"] = process_arguments.h
+
+-- > Output version information and exit
+function process_arguments.V(script_arguments)
+ print(version)
+ os.exit(0)
+end
+
+process_arguments["-version"] = process_arguments.V
+
+-- > Specify a PDF page to extract bounding box
+local page_number = 1
+function process_arguments.p(script_arguments)
+ page_number = tonumber(remove(script_arguments, 1))
+end
+
+-- > Be quiet
+function process_arguments.q(script_arguments)
+ show_errors = false
+end
+
+-- > Be verbose
+function process_arguments.v(script_arguments)
+ show_errors = true
+end
+
+-- > Write output to stdout
+local output_file
+function process_arguments.O(script_arguments)
+ output_file = io.stdout
+end
+
+-- Output format
+local output_format = "xbb"
+
+if script_arguments[0]:match("ebb") then
+ output_format = "bb"
+end
+
+-- > Output .bb file used in DVIPDFM (default)
+function process_arguments.m(script_arguments)
+ output_format = "bb"
+end
+
+-- > Output .xbb file used in DVIPDFMx
+function process_arguments.x(script_arguments)
+ output_format = "xbb"
+end
+
+-- Get the input file name.
+local input_name
+function process_arguments.i(script_arguments)
+ input_name = remove(script_arguments, 1)
+end
+
+process_arguments["-input-name"] = process_arguments.i
+
+-- Clear the interpreter and script names.
+script_arguments[-1] = nil
+script_arguments[0] = nil
+
+-- Process the arguments.
+while script_arguments[1] do
+ -- Get the next argument.
+ local arg = remove(script_arguments, 1)
+ local cmd = arg:match("^%-(.*)$")
+
+ -- Default to "--input-name" if no command is given.
+ if not cmd then
+ insert(script_arguments, 1, arg)
+ cmd = "-input-name"
+ end
+
+ -- Handle multi-character arguments.
+ if (cmd:len() >= 2) and (not cmd:match("^%-")) then
+ local i = 0
+ for char in cmd:gmatch(".") do
+ i = i + 1
+ insert(script_arguments, i, "-" .. char)
end
+
+ goto continue
end
- return path
+ -- Get the function to process the argument and run it.
+ local func = process_arguments[cmd]
+
+ if not func then
+ error("Invalid argument:", arg)
+ end
+
+ func(script_arguments)
+
+ ::continue::
end
--- Map the choice names to file names.
-local choice_mapping = {
- wrapper = find_script("extractbb-wrapper.lua"),
- scratch = find_script("extractbb-scratch.lua"),
+-- Validate the arguments.
+if not type(page_number) == "number" then
+ error("Invalid page number:", page_number)
+end
+
+if not input_name then
+ error("No input file specified.")
+end
+
+-- Validate the bounding box type. We need this rather crazy fallback scheme
+-- to match the behaviour of "extractbb".
+local bbox_orders = {}
+bbox_orders.mediabox = {
+ { img = "media", pdfe = "MediaBox" },
}
+bbox_orders.cropbox = {
+ { img = "crop", pdfe = "CropBox" }, unpack(bbox_orders.mediabox)
+}
+bbox_orders.artbox = {
+ { img = "art", pdfe = "ArtBox" }, unpack(bbox_orders.cropbox)
+}
+bbox_orders.trimbox = {
+ { img = "trim", pdfe = "TrimBox" }, unpack(bbox_orders.artbox)
+}
+bbox_orders.bleedbox = {
+ { img = "bleed", pdfe = "BleedBox" }, unpack(bbox_orders.trimbox)
+}
+bbox_orders.auto = {
+ bbox_orders.cropbox[1], bbox_orders.artbox[1], bbox_orders.trimbox[1],
+ bbox_orders.bleedbox[1], bbox_orders.mediabox[1],
+}
--- Choose the implementation to run.
-local choice = choice_mapping[env_choice] or choice_mapping[DEFAULT]
+local bbox_order = bbox_orders[bbox_option]
-if not choice then
- error("No implementation of extractbb found.")
+if not bbox_order then
+ error("Invalid PDF box type:", bbox_option)
end
--- And run it.
-dofile(choice)
+-- Set the default pixel resolution.
+local default_dpi
+if output_format == "xbb" then
+ default_dpi = 72
+elseif output_format == "bb" then
+ default_dpi = 100
+else
+ error("Invalid output format:", output_format)
+end
+
+-- Open the output file.
+if not output_file then
+ local base_name = input_name:match("(.+)%.([^.]-)$") or input_name
+ local output_name = base_name .. "." .. output_format
+ output_file = open_file(output_name, "write", "text")
+end
+
+
+------------------------
+--- Image Processing ---
+------------------------
+
+local x_min, y_min, x_max, y_max
+local num_pages, image_type
+local pdf_major_version, pdf_minor_version
+
+if open_image then
+ -- Check the number of pages.
+ local image = open_image(input_name)
+ num_pages = image.pages
+
+ if page_number > num_pages then
+ error("Invalid page number:", page_number)
+ end
+
+ -- Open the image to the specified page and bounding box. If the requested
+ -- bounding box is not available, LuaTeX will fall back to the crop box
+ -- or the media box.
+ image = open_image(input_name, page_number, bbox_order[1].img)
+
+ if not image then
+ error("Cannot open image:", input_name)
+ end
+
+ -- Get the image metadata.
+ image_type = image.imagetype
+ local bounding_box = image.bbox
+
+ if not bounding_box then
+ error("Cannot get bounding box:", page_number)
+ end
+
+ local x_resolution = image.xres
+ local y_resolution = image.yres
+
+ if (x_resolution or 0) == 0 then
+ x_resolution = default_dpi
+ end
+
+ if (y_resolution or 0) == 0 then
+ y_resolution = default_dpi
+ end
+
+ -- Convert the bounding box to PostScript points.
+ for i, dimen in ipairs(bounding_box) do
+ if image_type == "pdf" then
+ dimen = dimen / BP_TO_SP
+ else
+ if i % 2 == 1 then
+ dimen = dimen / x_resolution * IN_TO_BP
+ else
+ dimen = dimen / y_resolution * IN_TO_BP
+ end
+ end
+
+ bounding_box[i] = dimen
+ end
+
+ -- Save the bounding box.
+ x_min, y_min, x_max, y_max = unpack(bounding_box)
+
+ -- We can't get the PDF version with the "img" library, so we'll just
+ -- pretend that it's v1.5 (which supports most features).
+ pdf_major_version = 1
+ pdf_minor_version = 5
+else
+ -- Fallback to PDFs only.
+ image_type = "pdf"
+ local document = pdfe.open(input_name)
+
+ if pdfe.getstatus(document) ~= 0 then
+ error("Cannot open PDF file:", input_name)
+ end
+
+ -- Check the number of pages.
+ num_pages = pdfe.getnofpages(document)
+
+ if type(num_pages) ~= "number" then
+ error("Invalid number of pages:", num_pages)
+ end
+
+ if page_number > num_pages then
+ error("Invalid page number:", page_number)
+ end
+
+ -- Get the page.
+ local page = pdfe.getpage(document, page_number)
+
+ if not page then
+ error("Cannot get page:", page_number)
+ end
+
+ -- Get the bounding box. Here, we check the boxes in the exact same order
+ -- that "extractbb" does.
+ local bounding_box
+ for _, bbox in ipairs(bbox_order) do
+ bounding_box = pdfe.getbox(page, bbox.pdfe)
+
+ if bounding_box then
+ break
+ end
+ end
+
+ if not bounding_box then
+ error("Cannot get bounding box:", page_number)
+ end
+
+ -- Save the bounding box.
+ x_min, y_min, x_max, y_max = unpack(bounding_box)
+
+ -- Get the PDF version.
+ pdf_major_version, pdf_minor_version = pdfe.getversion(document)
+end
+
+-- Validate the bounding box.
+for _, dimen in ipairs { x_min, y_min, x_max, y_max } do
+ if type(dimen) ~= "number" then
+ error("Invalid bounding box:", x_min, y_min, x_max, y_max)
+ end
+end
+
+
+--------------
+--- Output ---
+--------------
+
+-- Get the output fields and values.
+local lines = {}
+
+insert(lines, ("Title: %s"):format(input_name))
+insert(lines, ("Creator: %s"):format(version))
+insert(lines,
+ ("BoundingBox: %d %d %d %d")
+ :format(round(x_min), round(y_min), round(x_max), round(y_max)))
+
+if output_format == "xbb" then
+ insert(lines,
+ ("HiResBoundingBox: %0.6f %0.6f %0.6f %0.6f")
+ :format(x_min, y_min, x_max, y_max))
+
+ if image_type == "pdf" then
+ insert(lines,
+ ("PDFVersion: %d.%d")
+ :format(pdf_major_version, pdf_minor_version))
+
+ insert(lines, ("Pages: %d"):format(num_pages))
+ end
+
+end
+
+insert(lines, ("CreationDate: %s"):format(os.date(DATE_FORMAT, SOURCE_DATE_EPOCH)))
+
+-- Create the output text.
+local begin_line = "%%"
+local end_line = "\n"
+
+local text = begin_line ..
+ table.concat(lines, end_line .. begin_line) ..
+ end_line .. end_line
+
+-- Write the output text.
+output_file:write(text)
+output_file:close()
+
+-- Everything is done, so now we can exit.
+os.exit(0)
Modified: trunk/Master/tlpkg/libexec/ctan2tds
===================================================================
--- trunk/Master/tlpkg/libexec/ctan2tds 2025-02-12 14:09:34 UTC (rev 73915)
+++ trunk/Master/tlpkg/libexec/ctan2tds 2025-02-12 14:58:30 UTC (rev 73916)
@@ -4887,6 +4887,9 @@
&SYSTEM ("ln -s $linkname $platdir/clxelatex")
if $linkname eq "cluttex"; # cluttex->clxelatex
#
+ &SYSTEM ("ln -s $linkname $platdir/ebb")
+ if $linkname eq "extractbb"; # ebb->extractbb
+ #
&SYSTEM ("ln -s $linkname $platdir/r$linkname")
if $linkname =~ /^(pdfcrop|epstopdf)$/; # rpdfcrop ->pdfcrop, ...
#
More information about the tex-live-commits
mailing list.