diff --git a/configuration b/configuration index e70824da128..9c200155b20 100644 --- a/configuration +++ b/configuration @@ -13,12 +13,12 @@ export DENO=v2.7.14 # TODO figure out where 0.1.41 apple silicon libs are available export DENO_DOM=v0.1.41-alpha-artifacts -export PANDOC=3.8.3 -export DARTSASS=1.87.0 -export ESBUILD=0.25.10 -export TYPST=0.14.2 +export PANDOC=3.10 +export DARTSASS=1.101.0 +export ESBUILD=0.28.1 +export TYPST=0.15.0 export TYPST_GATHER=0.2.3 -export VERAPDF=1.28.2 +export VERAPDF=1.30.2 # NB: we can't put comments in the same line as export statements because it diff --git a/src/command/check/check.ts b/src/command/check/check.ts index dce5c9c8bbb..2dad5455644 100644 --- a/src/command/check/check.ts +++ b/src/command/check/check.ts @@ -244,10 +244,10 @@ async function checkVersions(conf: CheckConfiguration) { // file is in an awkward format and it is not packaged // with our installers const versionConstraints: [string | undefined, string, string][] = [ - [pandocVersion, "3.8.3", "Pandoc"], - [sassVersion, "1.87.0", "Dart Sass"], + [pandocVersion, "3.10", "Pandoc"], + [sassVersion, "1.101.0", "Dart Sass"], [denoVersion, "2.7.14", "Deno"], - [typstVersion, "0.14.2", "Typst"], + [typstVersion, "0.15.0", "Typst"], ]; const checkData: [string | undefined, string, string][] = versionConstraints .map(([version, ver, name]) => [ diff --git a/src/resources/filters/modules/jog.lua b/src/resources/filters/modules/jog.lua index f3ab5c07302..a3547570c40 100644 --- a/src/resources/filters/modules/jog.lua +++ b/src/resources/filters/modules/jog.lua @@ -118,6 +118,9 @@ local function recurse (element, tp, jogger) elseif tp == 'pandoc TableHead' or tp == 'pandoc TableFoot' or tp == 'TableHead' or tp == 'TableFoot' then element.rows = jogger(element.rows) + elseif tp == 'pandoc TableBody' or tp == 'TableBody' then + element.head = jogger(element.head) + element.body = jogger(element.body) elseif tp == 'Blocks' or tp == 'Inlines' then local expected_itemtype = tp == 'Inlines' and 'Inline' or 'Block' local pos = 0 diff --git a/src/resources/filters/modules/mediabag.lua b/src/resources/filters/modules/mediabag.lua index 658b613b156..5662ebeac73 100644 --- a/src/resources/filters/modules/mediabag.lua +++ b/src/resources/filters/modules/mediabag.lua @@ -82,7 +82,10 @@ local function write_mediabag_entry(src) if contents == nil then return nil end local mediabagDir = param("mediabag-dir", nil) - local mediaFile = pandoc.path.join{mediabagDir, src} + -- Always use forward slashes: this path is assigned to el.src and can flow + -- into writers (e.g. Typst 0.15+) that reject backslash path separators, + -- while forward slashes work fine for the actual file write on Windows too. + local mediaFile = pandoc.path.join{mediabagDir, src}:gsub('\\', '/') local file = _quarto.file.write(mediaFile, contents) if not file then diff --git a/src/resources/formats/beamer/pandoc/beamer.template b/src/resources/formats/beamer/pandoc/beamer.template index 6e2c0ed80cf..bb88d82662f 100644 --- a/src/resources/formats/beamer/pandoc/beamer.template +++ b/src/resources/formats/beamer/pandoc/beamer.template @@ -125,7 +125,7 @@ $for(titlegraphic)$ $endfor$} $endif$ $if(logo)$ -\logo{\includegraphics{$logo$}} +\logo{\includegraphics$if(logooptions)$[$for(logooptions)$$logooptions$$sep$, $endfor$]$endif${$logo$}} $endif$ \begin{document} diff --git a/src/resources/formats/beamer/pandoc/hypersetup.latex b/src/resources/formats/beamer/pandoc/hypersetup.latex index ff67655576e..610ba2c8fd8 100644 --- a/src/resources/formats/beamer/pandoc/hypersetup.latex +++ b/src/resources/formats/beamer/pandoc/hypersetup.latex @@ -1,3 +1,7 @@ +% fallback for those not using the hyperref driver hyperxmp: +\makeatletter +\@ifundefined{xmpquote}{\newcommand{\xmpquote}[1]{#1}}{} +\makeatother \hypersetup{ $if(title-meta)$ pdftitle={$title-meta$}, @@ -12,7 +16,7 @@ $if(subject)$ pdfsubject={$subject$}, $endif$ $if(keywords)$ - pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, + pdfkeywords={$for(keywords)$\xmpquote{$keywords$}$sep$, $endfor$}, $endif$ $if(colorlinks)$ colorlinks=true, diff --git a/src/resources/formats/beamer/pandoc/title.tex b/src/resources/formats/beamer/pandoc/title.tex index a3163a911c2..ccbe5198f1f 100644 --- a/src/resources/formats/beamer/pandoc/title.tex +++ b/src/resources/formats/beamer/pandoc/title.tex @@ -18,5 +18,5 @@ $endfor$} $endif$ $if(logo)$ -\logo{\includegraphics{$logo$}} +\logo{\includegraphics$if(logooptions)$[$for(logooptions)$$logooptions$$sep$, $endfor$]$endif${$logo$}} $endif$ \ No newline at end of file diff --git a/src/resources/formats/html/pandoc/html.styles b/src/resources/formats/html/pandoc/html.styles index 800f63c7a6a..cac6172c5ed 100644 --- a/src/resources/formats/html/pandoc/html.styles +++ b/src/resources/formats/html/pandoc/html.styles @@ -106,6 +106,7 @@ div.abstract-title { } $endif$ code { + white-space: pre-wrap; font-family: $if(monofont)$$monofont$$else$Menlo, Monaco, Consolas, 'Lucida Console', monospace$endif$; $if(monobackgroundcolor)$ background-color: $monobackgroundcolor$; @@ -183,10 +184,13 @@ header { text-decoration: none; } $endif$ -code{white-space: pre-wrap;} span.smallcaps{font-variant: small-caps;} -div.columns{display: flex; gap: min(4vw, 1.5em);} -div.column{flex: auto; overflow-x: auto;} +div.columns{display: flex; gap: 1.5em;} +div.column{flex: auto;} +@media screen { +div.columns{gap: min(4vw, 1.5em);} +div.column{overflow-x: auto;} +} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} /* The extra [class] is a hack that increases specificity enough to override a similar rule in reveal.js */ diff --git a/src/resources/formats/html/pandoc/html.template b/src/resources/formats/html/pandoc/html.template index 26740d1b0ec..2f39bc95ec6 100644 --- a/src/resources/formats/html/pandoc/html.template +++ b/src/resources/formats/html/pandoc/html.template @@ -1,8 +1,8 @@ - + - + $for(author-meta)$ diff --git a/src/resources/formats/html/pandoc/styles.html b/src/resources/formats/html/pandoc/styles.html index ebcc014be1f..795fd311307 100644 --- a/src/resources/formats/html/pandoc/styles.html +++ b/src/resources/formats/html/pandoc/styles.html @@ -118,6 +118,7 @@ } $endif$ code { + white-space: pre-wrap; font-family: $if(monofont)$$monofont$$else$Menlo, Monaco, Consolas, 'Lucida Console', monospace$endif$; $if(monobackgroundcolor)$ background-color: $monobackgroundcolor$; @@ -190,10 +191,13 @@ text-decoration: none; } $endif$ -code{white-space: pre-wrap;} span.smallcaps{font-variant: small-caps;} -div.columns{display: flex; gap: min(4vw, 1.5em);} -div.column{flex: auto; overflow-x: auto;} +div.columns{display: flex; gap: 1.5em;} +div.column{flex: auto;} +@media screen { +div.columns{gap: min(4vw, 1.5em);} +div.column{overflow-x: auto;} +} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} ul.task-list{list-style: none;} ul.task-list li input[type="checkbox"] { diff --git a/src/resources/formats/html/pandoc/template.html b/src/resources/formats/html/pandoc/template.html index 3a7b1b9675f..5ae339e9211 100644 --- a/src/resources/formats/html/pandoc/template.html +++ b/src/resources/formats/html/pandoc/template.html @@ -1,5 +1,5 @@ - + diff --git a/src/resources/formats/pdf/pandoc/hypersetup.latex b/src/resources/formats/pdf/pandoc/hypersetup.latex index ff67655576e..610ba2c8fd8 100644 --- a/src/resources/formats/pdf/pandoc/hypersetup.latex +++ b/src/resources/formats/pdf/pandoc/hypersetup.latex @@ -1,3 +1,7 @@ +% fallback for those not using the hyperref driver hyperxmp: +\makeatletter +\@ifundefined{xmpquote}{\newcommand{\xmpquote}[1]{#1}}{} +\makeatother \hypersetup{ $if(title-meta)$ pdftitle={$title-meta$}, @@ -12,7 +16,7 @@ $if(subject)$ pdfsubject={$subject$}, $endif$ $if(keywords)$ - pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, + pdfkeywords={$for(keywords)$\xmpquote{$keywords$}$sep$, $endfor$}, $endif$ $if(colorlinks)$ colorlinks=true, diff --git a/src/resources/formats/pdf/pandoc/latex.template b/src/resources/formats/pdf/pandoc/latex.template index 6c70459554f..e993af1d77d 100644 --- a/src/resources/formats/pdf/pandoc/latex.template +++ b/src/resources/formats/pdf/pandoc/latex.template @@ -1,3 +1,4 @@ +$document-metadata.latex()$ $passoptions.latex()$ \documentclass[ $for(babel-otherlangs)$ diff --git a/src/resources/formats/revealjs/pandoc/revealjs.template b/src/resources/formats/revealjs/pandoc/revealjs.template index 5278dd14571..0fc4464f170 100644 --- a/src/resources/formats/revealjs/pandoc/revealjs.template +++ b/src/resources/formats/revealjs/pandoc/revealjs.template @@ -29,6 +29,9 @@ $if(theme)$ $else$ $endif$ +$if(highlight-js)$ + +$endif$ $for(css)$ $endfor$ @@ -87,6 +90,9 @@ $body$ $if(mathjax)$ $endif$ +$if(highlight-js)$ + +$endif$ $if(mathjax)$ +$endif$ +$if(highlight-js)$ + $endif$ @@ -313,6 +319,9 @@ plugins: [ $if(mathjax)$ RevealMath, +$endif$ +$if(highlight-js)$ + RevealHighlight, $endif$ RevealNotes, RevealSearch, diff --git a/src/resources/formats/typst/pandoc/template.typst b/src/resources/formats/typst/pandoc/template.typst index 648476620f5..1d90b1d9769 100644 --- a/src/resources/formats/typst/pandoc/template.typst +++ b/src/resources/formats/typst/pandoc/template.typst @@ -73,47 +73,48 @@ } } - block(below: 1em, width: 100%)[ - #if title != none { - align(center, block[ - #text(weight: "bold", size: 1.5em)[#title #if thanks != none { - footnote(thanks, numbering: "*") - counter(footnote).update(n => n - 1) - }] - #( - if subtitle != none { - parbreak() - text(weight: "bold", size: 1.25em)[#subtitle] - } - )]) - } - - #if authors != none and authors != [] { - let count = authors.len() - let ncols = calc.min(count, 3) - grid( - columns: (1fr,) * ncols, - row-gutter: 1.5em, - ..authors.map(author => align(center)[ - #author.name \ - #author.affiliation \ - #author.email - ]) - ) - } + if title != none { + place(top, float: true, scope: "parent", clearance: 4mm, block(below: 1em, width: 100%)[ + #if title != none { + align(center, block[ + #text(weight: "bold", size: 1.5em, hyphenate: false)[#title #if thanks != none { + footnote(thanks, numbering: "*") + counter(footnote).update(n => n - 1) + }] + #( + if subtitle != none { + parbreak() + text(weight: "bold", size: 1.25em, hyphenate: false)[#subtitle] + } + )]) + } - #if date != none { - align(center)[#block(inset: 1em)[ - #date - ]] - } + #if authors != none and authors != [] { + let count = authors.len() + let ncols = calc.min(count, 3) + grid( + columns: (1fr,) * ncols, + row-gutter: 1.5em, + ..authors.map(author => align(center)[ + #author.name \ + #author.affiliation \ + #author.email + ]) + ) + } - #if abstract != none { - block(inset: 2em)[ - #text(weight: "semibold")[#abstract-title] #h(1em) #abstract - ] - } - ] + #if date != none { + align(center)[#block(inset: 1em)[ + #date + ]] + } + #if abstract != none { + block(inset: 2em)[ + #text(weight: "semibold")[#abstract-title] #h(1em) #abstract + ] + } + ]) + } doc } diff --git a/src/resources/formats/typst/pandoc/typst.template b/src/resources/formats/typst/pandoc/typst.template index a499cd8db9f..9b161a4c873 100644 --- a/src/resources/formats/typst/pandoc/typst.template +++ b/src/resources/formats/typst/pandoc/typst.template @@ -1,13 +1,9 @@ #let horizontalrule = line(start: (25%,0%), end: (75%,0%)) -#show terms: it => { - it.children - .map(child => [ - #strong[#child.term] - #block(inset: (left: 1.5em, top: -0.4em))[#child.description] - ]) - .join() -} +#show terms.item: it => block(breakable: false)[ + #text(weight: "bold")[#it.term] + #block(inset: (left: 1.5em, top: -0.4em))[#it.description] +] #set table( inset: 6pt, diff --git a/src/resources/lua-types/pandoc/components.lua b/src/resources/lua-types/pandoc/components.lua index 7c838708590..06b1e65a777 100644 --- a/src/resources/lua-types/pandoc/components.lua +++ b/src/resources/lua-types/pandoc/components.lua @@ -198,6 +198,16 @@ number of row header columns. pandoc.TableBody = {} +--[[ +Creates a table body +]] +---@param body? pandoc.List List of `Row` +---@param head? pandoc.List Intermediate head (list of `Row`) +---@param row_head_columns? integer Number of columns taken up by the row head of each row of a `TableBody` +---@param attr? pandoc.Attr Table body attributes +---@return pandoc.TableBody +function pandoc.TableBody(body, head, row_head_columns, attr) end + --[[ Make a clone ]] diff --git a/src/resources/lua-types/pandoc/pandoc.lua b/src/resources/lua-types/pandoc/pandoc.lua index 0c33cb5e453..cea7c64e344 100644 --- a/src/resources/lua-types/pandoc/pandoc.lua +++ b/src/resources/lua-types/pandoc/pandoc.lua @@ -3,10 +3,10 @@ ---@module 'pandoc' pandoc = {} ----@type table +---@type table pandoc.readers = {} ----@type table +---@type table pandoc.writers = {} --[[ diff --git a/src/resources/lua-types/pandoc/version.lua b/src/resources/lua-types/pandoc/version.lua index ecf1c5394a8..89c8869159f 100644 --- a/src/resources/lua-types/pandoc/version.lua +++ b/src/resources/lua-types/pandoc/version.lua @@ -27,6 +27,30 @@ pandoc.Version = {} function pandoc.types.Version(version_specifier) end +--[[ +A named source of text, e.g. a file name paired with its contents. +Used as an item in a `Sources` list. +]] +---@class pandoc.types.Source +---@field name string Name/path the text originated from +---@field text string The source text + +--[[ +A list of `Source` items, pairing input text with information on +where it originated (e.g. a file name). Used by Pandoc's text readers. +]] +---@class pandoc.types.Sources: pandoc.List + +---@alias sources_specifier string|table|pandoc.types.Source[]|pandoc.types.Sources + +--[[ +Creates a new Sources element, i.e., a list of `Source` items. +]] +---@param srcs sources_specifier +---@return pandoc.types.Sources +function pandoc.types.Sources(srcs) end + + --[[ Raise an error message if the version is older than the expected version; does nothing if actual is equal to or newer than the expected version. diff --git a/tests/smoke/lua-unit/lua-unit.test.ts b/tests/smoke/lua-unit/lua-unit.test.ts index 1553052f60f..5cc15696e34 100644 --- a/tests/smoke/lua-unit/lua-unit.test.ts +++ b/tests/smoke/lua-unit/lua-unit.test.ts @@ -25,6 +25,7 @@ import { unitTest } from "../../test.ts"; // Explicit list, relative to tests/unit-lua/. Keep alphabetized. const LUA_TESTS: string[] = [ + "mediabag.test.lua", "typst-css.test.lua", ]; diff --git a/tests/unit-lua/mediabag.test.lua b/tests/unit-lua/mediabag.test.lua new file mode 100644 index 00000000000..8a00d546c22 --- /dev/null +++ b/tests/unit-lua/mediabag.test.lua @@ -0,0 +1,57 @@ +-- Unit tests for src/resources/filters/modules/mediabag.lua +-- +-- Run via the Deno smoke test in tests/smoke/lua-unit/lua-unit.test.ts +-- (which sets LUA_PATH and invokes `quarto run`), or directly with: +-- LUA_PATH="tests/unit-lua/?.lua;src/resources/filters/modules/?.lua;;" \ +-- package/dist/bin/quarto run tests/unit-lua/mediabag.test.lua + +-- Mocks for filter-runtime globals ------------------------------------------- + +local param_values = {} +function param(name, default) + if param_values[name] ~= nil then return param_values[name] end + return default +end + +function warn(_msg) end + +_quarto = { + file = { + write = function(_path, _contents) return true end, + }, +} + +local lu = require('luaunit') +local mediabag = require('mediabag') + +-- Tests ---------------------------------------------------------------------- + +-- Regression test for Typst 0.15.0 hard-rejecting backslash path separators +-- in image() calls (quarto-cli-fpil). pandoc.path.join uses the host OS's +-- native separator, which is a backslash on Windows. Any path handed to +-- el.src must use forward slashes only, since it flows straight into +-- writers (e.g. Typst's image()) that may not accept native separators. +TestWriteMediabagEntry = {} + +function TestWriteMediabagEntry:setUp() + param_values = { ["mediabag-dir"] = "imgtest_files/mediabag" } + pandoc.mediabag.insert("png-base64,abc123.png", "image/png", "fake-bytes") +end + +function TestWriteMediabagEntry:tearDown() + pandoc.mediabag.empty() +end + +function TestWriteMediabagEntry:testResultHasNoBackslash() + local path = mediabag.write_mediabag_entry("png-base64,abc123.png") + lu.assertNotNil(path) + lu.assertNil(path:find('\\', 1, true), + 'write_mediabag_entry returned a path with a backslash: ' .. path) +end + +function TestWriteMediabagEntry:testResultJoinsDirAndFilename() + local path = mediabag.write_mediabag_entry("png-base64,abc123.png") + lu.assertEquals(path, "imgtest_files/mediabag/png-base64,abc123.png") +end + +os.exit(lu.LuaUnit.run())