My extension: typst orange book added
This commit is contained in:
21
_extensions/MHellmund/julia-color/LICENSE_quarto_bookext.txt
Normal file
21
_extensions/MHellmund/julia-color/LICENSE_quarto_bookext.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Posit Software, PBC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,7 +1,7 @@
|
||||
title: Julia-color
|
||||
author: Meik Hellmund
|
||||
version: 1.0.0
|
||||
quarto-required: ">=99.9.0"
|
||||
quarto-required: ">=1.9.0"
|
||||
contributes:
|
||||
formats:
|
||||
common:
|
||||
@@ -14,8 +14,18 @@ contributes:
|
||||
- "resources/css/juliamono.css"
|
||||
pdf:
|
||||
include-in-header:
|
||||
- "resources/tex/juliainc.tex"
|
||||
- "resources/tex/juliainc.tex"
|
||||
|
||||
typst:
|
||||
include-in-header:
|
||||
- "resources/typst/juliainc.typ"
|
||||
- "resources/typst/juliainc.typ"
|
||||
# the following is from
|
||||
# quarto-cli/src/resources/extension-subtrees/orange-book/_extensions/orange-book
|
||||
# comment out for an article
|
||||
template-partials:
|
||||
- numbering.typ
|
||||
- page.typ
|
||||
- typst-show.typ
|
||||
filters:
|
||||
- path: orange-book.lua
|
||||
at: post-quarto
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
-- based on
|
||||
-- https://github.com/jupyter/nbconvert/blob/main/nbconvert/filters/ansi.py
|
||||
|
||||
-- debug: pandoc -t native file.{typst,html,..}.md
|
||||
|
||||
local ANSI_COLORS = {
|
||||
"ansi-black",
|
||||
"ansi-red",
|
||||
@@ -224,18 +226,47 @@ local function HTMLconverter(fg, bg, bold, light, italic, underline, inverse)
|
||||
return starttag..">","</span>"
|
||||
end
|
||||
|
||||
|
||||
local function escapeTypstString(s)
|
||||
s = s:gsub("\\", "\\\\")
|
||||
s = s:gsub("%$", "\\$")
|
||||
s = s:gsub("*", "\\*")
|
||||
s = s:gsub([["]],[[\"]])
|
||||
return s
|
||||
end
|
||||
|
||||
local function escapeTypstString2(s)
|
||||
s = s:gsub("@", "\\@")
|
||||
s = s:gsub("<", "\\<")
|
||||
s = s:gsub("#", "\\#")
|
||||
s = s:gsub(">", "\\>")
|
||||
--s = s:gsub("]","\\]")
|
||||
s = s:gsub("\n","\\\n")
|
||||
s = s:gsub("+","\\+")
|
||||
s = s:gsub("/","\\/")
|
||||
s = s:gsub("=","\\=")
|
||||
return s
|
||||
end
|
||||
|
||||
|
||||
local function noescapeString(s)
|
||||
return s
|
||||
end
|
||||
|
||||
|
||||
local function codeBlockTrans(e)
|
||||
local converter, fmt
|
||||
local converter, fmt, escapeString
|
||||
if quarto.doc.isFormat('latex') then
|
||||
converter = LaTeXconverter
|
||||
fmt = 'latex'
|
||||
escapeString = noescapeString
|
||||
elseif quarto.doc.isFormat('html') then
|
||||
converter = HTMLconverter
|
||||
fmt = 'html'
|
||||
escapeString = noescapeString
|
||||
elseif quarto.doc.isFormat('typst') then
|
||||
converter = Typstconverter
|
||||
fmt = 'typst'
|
||||
escapeString = escapeTypstString
|
||||
else
|
||||
return
|
||||
end
|
||||
@@ -264,7 +295,7 @@ local function codeBlockTrans(e)
|
||||
local out=""
|
||||
local text = e.text
|
||||
|
||||
-- we remove links (eg in julia ParseErrors. THey link to local files, so they are useless anyway)
|
||||
-- we remove links (eg in julia ParseErrors. They link to local files, so they are useless anyway)
|
||||
text = text:gsub("\x1b%]8;.-\x1b\\", "")
|
||||
|
||||
if string.find(text, "\x1b%[") then
|
||||
@@ -305,7 +336,7 @@ local function codeBlockTrans(e)
|
||||
else
|
||||
starttag, endtag = converter(fg, bg, bold, light, italic, underline, inverse)
|
||||
end
|
||||
out = out .. starttag .. chunk .. endtag
|
||||
out = out .. starttag .. escapeString(chunk) .. endtag
|
||||
end
|
||||
|
||||
while next(numbers) ~= nil do
|
||||
@@ -359,7 +390,7 @@ local function codeBlockTrans(e)
|
||||
end
|
||||
end
|
||||
else
|
||||
out = text
|
||||
out = escapeString(text)
|
||||
end
|
||||
if fmt == 'html' then
|
||||
return pandoc.RawBlock(fmt,
|
||||
@@ -369,7 +400,11 @@ local function codeBlockTrans(e)
|
||||
return pandoc.RawBlock(fmt, [[\begin{]]..texenv.."}\n"..out.."\n"..[[\end{]].. texenv .. "}")
|
||||
end
|
||||
if fmt == 'typst' then
|
||||
return pandoc.RawBlock(fmt, "#"..texenv.."[\n"..out.."\n]")
|
||||
if texenv == "OutputCell" then
|
||||
return pandoc.RawBlock(fmt, "#"..texenv.."[\n" .. escapeTypstString2(out) .. "\n]")
|
||||
else
|
||||
return pandoc.RawBlock(fmt, "#"..texenv.."[\n" .. out .. "\n]")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -398,12 +433,52 @@ local function divCodeBlockNoHeader1(e)
|
||||
if el.t == 'Header' then
|
||||
el.level = 6
|
||||
end
|
||||
-- if el.t == 'CodeBlock' then -- attempt for typst, but we use an extra quarto cell
|
||||
-- for i,v in ipairs(el.classes) do
|
||||
-- if v=="julia" or v=="jldoctest" then -- example code and before
|
||||
-- el.classes:remove(i)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
return e
|
||||
end
|
||||
|
||||
|
||||
-- test if two divs should be merged
|
||||
local function testmerge(d1, d2)
|
||||
return d1 and d1.t == "Div" and d1.classes:includes("cell-output") and #d1.content == 1
|
||||
and d2 and d2.t == "Div" and d2.classes:includes("cell-output") and #d2.content == 1
|
||||
and d1.content[1].t == "CodeBlock" and not d1.classes:includes("cell-output-stderr")
|
||||
and d2.content[1].t == "CodeBlock" and not d2.classes:includes("cell-output-stderr")
|
||||
end
|
||||
|
||||
-- merge (div (codecell (text1)), div (codecell(text2))) to div(codecell(text1+text2))
|
||||
local function blockMerge(es)
|
||||
local nl = ""
|
||||
for i = #es-1, 1, -1 do
|
||||
if testmerge(es[i], es[i+1]) then
|
||||
str1 = es[i].content[1].text
|
||||
str2 = es[i+1].content[1].text
|
||||
nl = "\n"
|
||||
if es[i].classes:includes("cell-output-stdout") and es[i+1].classes:includes("cell-output-stdout") then
|
||||
if str1:sub(-1) == "\n" then
|
||||
nl = ""
|
||||
end
|
||||
if str2:sub(1, 1) == "\n" then
|
||||
nl = ""
|
||||
end
|
||||
end
|
||||
es[i].content[1].text = str1 .. nl .. str2
|
||||
es:remove(i+1)
|
||||
end
|
||||
end
|
||||
return es
|
||||
end
|
||||
|
||||
return {
|
||||
{Div = divStderr},
|
||||
{Div = divCodeBlockNoHeader1},
|
||||
{Blocks = blockMerge},
|
||||
{CodeBlock = codeBlockTrans},
|
||||
}
|
||||
|
||||
38
_extensions/MHellmund/julia-color/numbering.typ
Normal file
38
_extensions/MHellmund/julia-color/numbering.typ
Normal file
@@ -0,0 +1,38 @@
|
||||
// Chapter-based numbering for books with appendix support
|
||||
#let equation-numbering = it => {
|
||||
let pattern = if state("appendix-state", none).get() != none { "(A.1)" } else { "(1.1)" }
|
||||
numbering(pattern, counter(heading).get().first(), it)
|
||||
}
|
||||
#let callout-numbering = it => {
|
||||
let pattern = if state("appendix-state", none).get() != none { "A.1" } else { "1.1" }
|
||||
numbering(pattern, counter(heading).get().first(), it)
|
||||
}
|
||||
#let subfloat-numbering(n-super, subfloat-idx) = {
|
||||
let chapter = counter(heading).get().first()
|
||||
let pattern = if state("appendix-state", none).get() != none { "A.1a" } else { "1.1a" }
|
||||
numbering(pattern, chapter, n-super, subfloat-idx)
|
||||
}
|
||||
// Theorem configuration for theorion
|
||||
// Chapter-based numbering (H1 = chapters)
|
||||
#let theorem-inherited-levels = 1
|
||||
|
||||
// Appendix-aware theorem numbering
|
||||
#let theorem-numbering(loc) = {
|
||||
if state("appendix-state", none).at(loc) != none { "A.1" } else { "1.1" }
|
||||
}
|
||||
|
||||
// Theorem render function
|
||||
// Note: brand-color is not available at this point in template processing
|
||||
#let theorem-render(prefix: none, title: "", full-title: auto, body) = {
|
||||
block(
|
||||
width: 100%,
|
||||
inset: (left: 1em),
|
||||
stroke: (left: 2pt + black),
|
||||
)[
|
||||
#if full-title != "" and full-title != auto and full-title != none {
|
||||
strong[#full-title]
|
||||
linebreak()
|
||||
}
|
||||
#body
|
||||
]
|
||||
}
|
||||
69
_extensions/MHellmund/julia-color/orange-book.lua
Normal file
69
_extensions/MHellmund/julia-color/orange-book.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
-- orange-book.lua
|
||||
-- Orange-book specific part and appendix handling for Typst books
|
||||
|
||||
local function is_typst_book()
|
||||
local file_state = quarto.doc.file_metadata()
|
||||
return quarto.doc.is_format("typst") and
|
||||
file_state ~= nil and
|
||||
file_state.file ~= nil
|
||||
end
|
||||
|
||||
local header_filter = {
|
||||
Header = function(el)
|
||||
local file_state = quarto.doc.file_metadata()
|
||||
|
||||
if not is_typst_book() then
|
||||
return nil
|
||||
end
|
||||
|
||||
if file_state == nil or file_state.file == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local file = file_state.file
|
||||
local bookItemType = file.bookItemType
|
||||
|
||||
if el.level ~= 1 or bookItemType == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Handle parts
|
||||
if bookItemType == "part" then
|
||||
return pandoc.RawBlock('typst', '#part[' .. pandoc.utils.stringify(el.content) .. ']')
|
||||
end
|
||||
|
||||
-- Handle appendices
|
||||
if bookItemType == "appendix" then
|
||||
-- First appendix triggers the show rule with localized "Appendices" title
|
||||
if file.bookItemNumber == 1 or file.bookItemNumber == nil then
|
||||
-- Get localized title from language settings
|
||||
local language = quarto.doc.language
|
||||
local appendicesTitle = (language and language["section-title-appendices"]) or "Appendices"
|
||||
|
||||
-- Use hide-parent: true to work around orange-book bug where unnumbered headings
|
||||
-- (like Bibliography) trigger duplicate "Appendices" TOC entries.
|
||||
local appendixStart = pandoc.RawBlock('typst',
|
||||
'#show: appendices.with("' .. appendicesTitle .. '", hide-parent: true)')
|
||||
|
||||
-- If this is the synthetic "Appendices" divider heading (has .unnumbered class),
|
||||
-- emit our own Appendices heading for TOC display
|
||||
if el.classes:includes("unnumbered") then
|
||||
local appendicesHeading = pandoc.RawBlock('typst',
|
||||
'#heading(level: 1, numbering: none)[' .. appendicesTitle .. ']')
|
||||
return {appendixStart, appendicesHeading}
|
||||
end
|
||||
|
||||
return {appendixStart, el}
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
}
|
||||
|
||||
-- Combine with file_metadata_filter so book metadata markers are parsed
|
||||
-- during this filter's document traversal (needed for bookItemType, etc.)
|
||||
return quarto.utils.combineFilters({
|
||||
quarto.utils.file_metadata_filter(),
|
||||
header_filter
|
||||
})
|
||||
15
_extensions/MHellmund/julia-color/page.typ
Normal file
15
_extensions/MHellmund/julia-color/page.typ
Normal file
@@ -0,0 +1,15 @@
|
||||
#set page(
|
||||
paper: $if(papersize)$"$papersize$"$else$"us-letter"$endif$,
|
||||
$if(margin-geometry)$
|
||||
// Margins handled by marginalia.setup in typst-show.typ AFTER book.with()
|
||||
$elseif(margin)$
|
||||
margin: ($for(margin/pairs)$$margin.key$: $margin.value$,$endfor$),
|
||||
$else$
|
||||
margin: (x: 1.25in, y: 1.25in),
|
||||
$endif$
|
||||
numbering: $if(page-numbering)$"$page-numbering$"$else$none$endif$,
|
||||
columns: $if(columns)$$columns$$else$1$endif$,
|
||||
)
|
||||
// Logo is handled by orange-book's cover page, not as a page background
|
||||
// NOTE: marginalia.setup is called in typst-show.typ AFTER book.with()
|
||||
// to ensure marginalia's margins override the book format's default margins
|
||||
@@ -2,10 +2,45 @@
|
||||
#show raw: set text(font: "JuliaMono")
|
||||
|
||||
// define cell layout
|
||||
#let OutputCell = block.with(width:100%, inset: 5pt)
|
||||
#let AnsiOutputCell = block.with(width: 100%, inset: 5pt)
|
||||
|
||||
//#let EndLine() = raw("\n")
|
||||
//#let OutputCell(lines) = {
|
||||
// let blocks = []
|
||||
// for ln in lines {
|
||||
// blocks = blocks + ln + EndLine()
|
||||
// }
|
||||
// block(width:100%, inset:(x:5pt, y:0pt), stroke:( left: 2pt + gray), blocks)
|
||||
//}
|
||||
|
||||
#let OutputCell(content) = block(
|
||||
width: 100%,
|
||||
inset: (x: 5pt, y: 5pt),
|
||||
above: 0pt,
|
||||
below: 8pt,
|
||||
stroke: (left: 2pt + gray, bottom: .5pt + gray)
|
||||
)[
|
||||
#set text(font: "JuliaMono", size:8pt)
|
||||
#content
|
||||
]
|
||||
|
||||
|
||||
#let AnsiOutputCell(content) = block(
|
||||
width: 100%,
|
||||
inset: (x: 5pt, y: 5pt),
|
||||
above: 0pt,
|
||||
below: 8pt,
|
||||
stroke: (left: 2pt + gray, bottom: .5pt + gray)
|
||||
)[
|
||||
#set text(font: "JuliaMono", size:9pt)
|
||||
#content
|
||||
]
|
||||
|
||||
|
||||
|
||||
// does not exist with julia engine
|
||||
#let StderrOutputCell = block.with(width: 100%, stroke: 1pt + red, inset: 5pt)
|
||||
|
||||
|
||||
//#set highlight(top-edge: "ascender", bottom-edge: "descender")
|
||||
#let invertbox(color, c) = box(outset: (x: 0.05em, y: 0.25em), fill: color, c)
|
||||
|
||||
|
||||
61
_extensions/MHellmund/julia-color/typst-show.typ
Normal file
61
_extensions/MHellmund/julia-color/typst-show.typ
Normal file
@@ -0,0 +1,61 @@
|
||||
#import "@preview/orange-book:0.7.1": book, part, chapter, appendices
|
||||
|
||||
#show: book.with(
|
||||
$if(title)$
|
||||
title: [$title$],
|
||||
$endif$
|
||||
$if(subtitle)$
|
||||
subtitle: [$subtitle$],
|
||||
$endif$
|
||||
$if(by-author)$
|
||||
author: "$for(by-author)$$it.name.literal$$sep$, $endfor$",
|
||||
$endif$
|
||||
$if(date)$
|
||||
date: "$date$",
|
||||
$endif$
|
||||
$if(lang)$
|
||||
lang: "$lang$",
|
||||
$endif$
|
||||
main-color: brand-color.at("primary", default: blue),
|
||||
logo: {
|
||||
let logo-info = brand-logo.at("medium", default: none)
|
||||
if logo-info != none { image(logo-info.path, alt: logo-info.at("alt", default: none)) }
|
||||
},
|
||||
$if(toc-depth)$
|
||||
outline-depth: $toc-depth$,
|
||||
$endif$
|
||||
$if(lof)$
|
||||
list-of-figure-title: "$if(crossref.lof-title)$$crossref.lof-title$$else$$crossref-lof-title$$endif$",
|
||||
$endif$
|
||||
$if(lot)$
|
||||
list-of-table-title: "$if(crossref.lot-title)$$crossref.lot-title$$else$$crossref-lot-title$$endif$",
|
||||
$endif$
|
||||
$if(margin-geometry)$
|
||||
padded-heading-number: false,
|
||||
$endif$
|
||||
)
|
||||
|
||||
$if(margin-geometry)$
|
||||
// Configure marginalia page geometry for book context
|
||||
// Geometry computed by Quarto's meta.lua filter (typstGeometryFromPaperWidth)
|
||||
// IMPORTANT: This must come AFTER book.with() to override the book format's margin settings
|
||||
#import "@preview/marginalia:0.3.1" as marginalia
|
||||
|
||||
#show: marginalia.setup.with(
|
||||
inner: (
|
||||
far: $margin-geometry.inner.far$,
|
||||
width: $margin-geometry.inner.width$,
|
||||
sep: $margin-geometry.inner.separation$,
|
||||
),
|
||||
outer: (
|
||||
far: $margin-geometry.outer.far$,
|
||||
width: $margin-geometry.outer.width$,
|
||||
sep: $margin-geometry.outer.separation$,
|
||||
),
|
||||
top: $if(margin.top)$$margin.top$$else$1.25in$endif$,
|
||||
bottom: $if(margin.bottom)$$margin.bottom$$else$1.25in$endif$,
|
||||
// CRITICAL: Enable book mode for recto/verso awareness
|
||||
book: true,
|
||||
clearance: $margin-geometry.clearance$,
|
||||
)
|
||||
$endif$
|
||||
Reference in New Issue
Block a user