├── .github └── workflows │ └── build.yml ├── .gitignore ├── .nocomputo ├── .quartoignore ├── DESCRIPTION ├── LICENSE ├── README.md ├── _extensions └── computo │ ├── .gitignore │ ├── _extension.yml │ ├── _extensions │ ├── leovan │ │ └── pseudocode │ │ │ ├── _extension.yml │ │ │ ├── pseudocode.lua │ │ │ ├── pseudocode.min.css │ │ │ └── pseudocode.min.js │ └── pandoc-ext │ │ ├── abstract-section │ │ ├── _extension.yaml │ │ └── abstract-section.lua │ │ ├── diagram │ │ ├── _extension.yaml │ │ └── diagram.lua │ │ └── list-table │ │ ├── _extension.yaml │ │ └── list-table.lua │ ├── color-text.lua │ ├── custom.scss │ ├── logo_text_white.pdf │ ├── partials │ ├── html │ │ ├── title-block.html │ │ └── title-metadata.html │ └── pdf │ │ ├── before-body.tex │ │ └── include-in-header.tex │ └── shortcodes.lua ├── _quarto.yml ├── computo-quarto-extension.qmd ├── environment.yml ├── figures └── sfds.png ├── references.bib ├── renv.lock ├── renv └── .gitignore ├── setup-env-ci.sh └── setup-render-ci.sh /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Main Workflows 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | repository_dispatch: 7 | types: [custom-event] 8 | workflow_dispatch: 9 | pull_request: 10 | branches: ["main"] 11 | schedule: 12 | - cron: '0 4 * * 0' 13 | 14 | jobs: 15 | call_env_workflow: 16 | uses: computorg/workflows/.github/workflows/global-env.yml@main 17 | call_quartopublish_workflow: 18 | permissions: 19 | id-token: write 20 | contents: write 21 | pages: write 22 | uses: computorg/workflows/.github/workflows/publish-render.yml@main 23 | needs: call_env_workflow 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Rhistory 3 | /.luarc.json 4 | /.quarto/ 5 | /*_files 6 | /*_cache 7 | /_freeze 8 | .Rprofile 9 | computo-quarto-extension.html 10 | computo-quarto-extension.pdf 11 | _environment 12 | /.micromamba/ 13 | -------------------------------------------------------------------------------- /.nocomputo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/computorg/computo-quarto-extension/d82f4fdea3376e12dff441b1f7d767e4cb972ff4/.nocomputo -------------------------------------------------------------------------------- /.quartoignore: -------------------------------------------------------------------------------- 1 | # Add files / folder that are useful to your repo 2 | # but should not be in the downloaded bundle after `quarto use template` 3 | style-guide 4 | .github -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Depends: reticulate, webshot2, ellipsis 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 quarto-journals 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use of the Computo format template 2 | 3 | This is a [Quarto-based](https://quarto.org) template that assists you in creating a manuscript for Computo. 4 | 5 | ## Creating a new article 6 | 7 | You can use this as a template to create an article for Computo. To do this, use the following command: 8 | 9 | ```bash 10 | quarto use template computorg/computo-quarto-extension 11 | ``` 12 | 13 | This will install the extension and create an example qmd file and bibiography that you can use as a starting place for your article. 14 | 15 | ## Installation for existing document 16 | 17 | You may also use this format with an existing Quarto project or document. From the quarto project or document directory, run the following command to install this format: 18 | 19 | ```bash 20 | quarto add computorg/computo-quarto-extension 21 | ``` 22 | 23 | ## Usage 24 | 25 | To use the format, you can use the format names `computo-html`. For example: 26 | 27 | ```bash 28 | quarto render article.qmd --to computo-html 29 | ``` 30 | 31 | or in your document yaml 32 | 33 | ```yaml 34 | format: 35 | computo-html: default 36 | ``` 37 | 38 | You can view a preview of the rendered HTML and PDF template at (for PDF, see "other formats" bnext to the TOC). 39 | 40 | 41 | -------------------------------------------------------------------------------- /_extensions/computo/.gitignore: -------------------------------------------------------------------------------- 1 | /.luarc.json 2 | -------------------------------------------------------------------------------- /_extensions/computo/_extension.yml: -------------------------------------------------------------------------------- 1 | title: Computo Format Template 2 | author: Computorg Team 3 | version: 0.2.6 4 | contributes: 5 | formats: 6 | common: 7 | # define below YAML configuration common to all formats 8 | filters: 9 | # You can include here Lua filters from your extension format 10 | - abstract-section 11 | - diagram 12 | - pseudocode 13 | - list-table 14 | - color-text.lua 15 | shortcodes: 16 | # You can include here Lua filters defining shortcodes from your extension 17 | - shortcodes.lua 18 | knitr: 19 | opts_chunk: 20 | screenshot.opts: 21 | cliprect: viewport 22 | vwidth: 640 23 | vheight: 400 24 | number-sections: true 25 | toc: true 26 | toc-title: Contents 27 | code-copy: true 28 | code-block-background: true 29 | pdf: 30 | echo: false 31 | pdf-engine: lualatex 32 | papersize: a4 33 | geometry: 34 | - a4paper 35 | - textheight=24cm 36 | - textwidth=15.5cm 37 | fontfamily: libertinus 38 | fontsize: 11pt 39 | highlight-style: github 40 | monofont: Latin Modern Mono 41 | monofontoptions: 42 | - Scale=0.92 43 | include-in-header: 44 | - "partials/pdf/include-in-header.tex" 45 | template-partials: 46 | - "partials/pdf/before-body.tex" 47 | format-resources: 48 | - logo_text_white.pdf 49 | html: 50 | theme: [custom.scss] 51 | title-block-banner: "#034E79" 52 | title-block-banner-color: "#FFFFFF" 53 | license: "CC BY" 54 | reference-section-title: Bibliography 55 | date-format: long 56 | hover-citations: true 57 | hover-footnotes: true 58 | fig-format: svg 59 | highlight-style: github 60 | code-block-background: true 61 | code-tools: 62 | source: true 63 | toggle: false 64 | caption: "source" 65 | code-copy: true 66 | code-summary: "Hide/Show the code" 67 | code-fold: hide 68 | anchor-sections: true 69 | link-external-icon: false 70 | link-external-newwindow: true 71 | crossref: 72 | sec-prefix: Section 73 | eq-prefix: Equation 74 | fig-prefix: Figure 75 | tbl-prefix: Table 76 | html-math-method: katex 77 | template-partials: 78 | - "partials/html/title-block.html" 79 | - "partials/html/title-metadata.html" 80 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/leovan/pseudocode/_extension.yml: -------------------------------------------------------------------------------- 1 | title: Pseudocode 2 | author: 范叶亮 | Leo Van 3 | version: 1.1.0 4 | quarto-required: ">=1.4.0" 5 | contributes: 6 | filters: 7 | - pseudocode.lua 8 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/leovan/pseudocode/pseudocode.lua: -------------------------------------------------------------------------------- 1 | local function ensure_html_deps() 2 | quarto.doc.add_html_dependency({ 3 | name = "pseudocode", 4 | version = "2.4.1", 5 | scripts = { "pseudocode.min.js" }, 6 | stylesheets = { "pseudocode.min.css" } 7 | }) 8 | quarto.doc.include_text("after-body", [[ 9 | 40 | ]]) 41 | end 42 | 43 | local function ensure_latex_deps() 44 | quarto.doc.use_latex_package("algorithm") 45 | quarto.doc.use_latex_package("algpseudocode") 46 | quarto.doc.use_latex_package("caption") 47 | end 48 | 49 | local function extract_source_code_options(source_code, render_type) 50 | local options = {} 51 | local source_codes = {} 52 | local found_source_code = false 53 | 54 | for str in string.gmatch(source_code, "([^\n]*)\n?") do 55 | if (string.match(str, "^%s*#|.*") or string.gsub(str, "%s", "") == "") and not found_source_code then 56 | if string.match(str, "^%s*#|%s+[" .. render_type .. "|label].*") then 57 | str = string.gsub(str, "^%s*#|%s+", "") 58 | local idx_start, idx_end = string.find(str, ":%s*") 59 | 60 | if idx_start and idx_end and idx_end + 1 < #str then 61 | k = string.sub(str, 1, idx_start - 1) 62 | v = string.sub(str, idx_end + 1) 63 | v = string.gsub(v, "^\"%s*", "") 64 | v = string.gsub(v, "%s*\"$", "") 65 | 66 | options[k] = v 67 | else 68 | quarto.log.warning("Invalid pseducode option: " .. str) 69 | end 70 | end 71 | else 72 | found_source_code = true 73 | table.insert(source_codes, str) 74 | end 75 | end 76 | 77 | return options, table.concat(source_codes, "\n") 78 | end 79 | 80 | local function render_pseudocode_block_html(global_options) 81 | ensure_html_deps() 82 | 83 | local filter = { 84 | CodeBlock = function(el) 85 | if not el.attr.classes:includes("pseudocode") then 86 | return el 87 | end 88 | 89 | local options, source_code = extract_source_code_options(el.text, "html") 90 | 91 | source_code = string.gsub(source_code, "%s*\\begin{algorithm}[^\n]+", "\\begin{algorithm}") 92 | source_code = string.gsub(source_code, "%s*\\begin{algorithmic}[^\n]+", "\\begin{algorithmic}") 93 | 94 | local alg_id = options["label"] 95 | options["label"] = nil 96 | options["html-caption-prefix"] = global_options.caption_prefix 97 | 98 | if global_options.number_with_in_chapter and global_options.html_chapter_level then 99 | options["html-chapter-level"] = global_options.html_chapter_level 100 | end 101 | 102 | if global_options.caption_number then 103 | options["html-pseudocode-number"] = global_options.html_current_number 104 | end 105 | 106 | local data_options = {} 107 | for k, v in pairs(options) do 108 | if string.match(k, "^html-") then 109 | data_k = string.gsub(k, "^html", "data") 110 | data_options[data_k] = v 111 | end 112 | end 113 | 114 | local inner_el = pandoc.Div(source_code) 115 | inner_el.attr.classes = pandoc.List() 116 | inner_el.attr.classes:insert("pseudocode") 117 | 118 | local outer_el = pandoc.Div(inner_el) 119 | outer_el.attr.classes = pandoc.List() 120 | outer_el.attr.classes:insert("pseudocode-container") 121 | outer_el.attr.classes:insert("quarto-float") 122 | outer_el.attr.attributes = data_options 123 | 124 | if alg_id then 125 | outer_el.attr.identifier = alg_id 126 | global_options.html_identifier_number_mapping[alg_id] = global_options.html_current_number 127 | global_options.html_current_number = global_options.html_current_number + 1 128 | end 129 | 130 | return outer_el 131 | end 132 | } 133 | 134 | return filter 135 | end 136 | 137 | local function render_pseudocode_block_latex(global_options) 138 | ensure_latex_deps() 139 | 140 | if global_options.caption_number then 141 | quarto.doc.include_text("before-body", "\\floatname{algorithm}{" .. global_options.caption_prefix .. "}") 142 | else 143 | quarto.doc.include_text("in-header", "\\DeclareCaptionLabelFormat{algnonumber}{" .. global_options.caption_prefix .. "}") 144 | quarto.doc.include_text("before-body", "\\captionsetup[algorithm]{labelformat=algnonumber}") 145 | end 146 | 147 | if global_options.number_with_in_chapter then 148 | quarto.doc.include_text("before-body", "\\numberwithin{algorithm}{chapter}") 149 | end 150 | 151 | local filter = { 152 | CodeBlock = function(el) 153 | if not el.attr.classes:includes("pseudocode") then 154 | return el 155 | end 156 | 157 | local options, source_code = extract_source_code_options(el.text, "pdf") 158 | 159 | local pdf_placement = "H" 160 | if options["pdf-placement"] then 161 | pdf_placement = options["pdf-placement"] 162 | end 163 | source_code = string.gsub(source_code, "\\begin{algorithm}%s*\n", "\\begin{algorithm}[" .. pdf_placement .. "]\n") 164 | 165 | if not options["pdf-line-number"] or options["pdf-line-number"] == "true" then 166 | source_code = string.gsub(source_code, "\\begin{algorithmic}%s*\n", "\\begin{algorithmic}[1]\n") 167 | end 168 | 169 | if options["label"] then 170 | source_code = string.gsub(source_code, "\\begin{algorithmic}", "\\label{" .. options["label"] .. "}\n\\begin{algorithmic}") 171 | end 172 | 173 | return pandoc.RawInline("latex", source_code) 174 | end 175 | } 176 | 177 | return filter 178 | end 179 | 180 | local function render_pseudocode_block(global_options) 181 | local filter = { 182 | CodeBlock = function(el) 183 | return el 184 | end 185 | } 186 | 187 | if quarto.doc.is_format("html") then 188 | filter = render_pseudocode_block_html(global_options) 189 | elseif quarto.doc.is_format("latex") then 190 | filter = render_pseudocode_block_latex(global_options) 191 | end 192 | 193 | return filter 194 | end 195 | 196 | local function render_pseudocode_ref_html(global_options) 197 | local filter = { 198 | Cite = function(el) 199 | local cite_text = pandoc.utils.stringify(el.content) 200 | 201 | for k, v in pairs(global_options.html_identifier_number_mapping) do 202 | if cite_text == "@" .. k then 203 | local link_src = "#" .. k 204 | local alg_id = v 205 | 206 | if global_options.html_chapter_level then 207 | alg_id = global_options.html_chapter_level .. "." .. alg_id 208 | end 209 | 210 | local link_text = global_options.reference_prefix .. " " .. alg_id 211 | local link = pandoc.Link(link_text, link_src) 212 | link.attr.classes = pandoc.List() 213 | link.attr.classes:insert("quarto-xref") 214 | 215 | return link 216 | end 217 | end 218 | end 219 | } 220 | 221 | return filter 222 | end 223 | 224 | local function render_pseudocode_ref_latex(global_options) 225 | local filter = { 226 | Cite = function(el) 227 | local cite_text = pandoc.utils.stringify(el.content) 228 | 229 | if string.match(cite_text, "^@alg-") then 230 | return pandoc.RawInline("latex", " " .. global_options.reference_prefix .. "~\\ref{" .. string.gsub(cite_text, "^@", "") .. "} " ) 231 | end 232 | end 233 | } 234 | 235 | return filter 236 | end 237 | 238 | local function render_pseudocode_ref(global_options) 239 | local filter = { 240 | Cite = function(el) 241 | return el 242 | end 243 | } 244 | 245 | if quarto.doc.is_format("html") then 246 | filter = render_pseudocode_ref_html(global_options) 247 | elseif quarto.doc.is_format("latex") then 248 | filter = render_pseudocode_ref_latex(global_options) 249 | end 250 | 251 | return filter 252 | end 253 | 254 | function Pandoc(doc) 255 | local global_options = { 256 | caption_prefix = "Algorithm", 257 | reference_prefix = "Algorithm", 258 | caption_number = true, 259 | number_with_in_chapter = false, 260 | html_chapter_level = nil, 261 | html_current_number = 1, 262 | html_identifier_number_mapping = {} 263 | } 264 | 265 | if doc.meta["pseudocode"] then 266 | global_options.caption_prefix = pandoc.utils.stringify(doc.meta["pseudocode"]["caption-prefix"]) or global_options.caption_prefix 267 | global_options.reference_prefix = pandoc.utils.stringify(doc.meta["pseudocode"]["reference-prefix"]) or global_options.reference_prefix 268 | global_options.caption_number = doc.meta["pseudocode"]["caption-number"] or global_options.caption_number 269 | end 270 | 271 | if doc.meta["book"] then 272 | global_options.number_with_in_chapter = true 273 | 274 | if quarto.doc.is_format("html") then 275 | local _, input_qmd_filename = string.match(quarto.doc["input_file"], "^(.-)([^\\/]-%.([^\\/%.]-))$") 276 | local renders = doc.meta["book"]["render"] 277 | 278 | for _, render in pairs(renders) do 279 | if render["file"] and render["number"] and pandoc.utils.stringify(render["file"]) == input_qmd_filename then 280 | global_options.html_chapter_level = pandoc.utils.stringify(render["number"]) 281 | end 282 | end 283 | end 284 | end 285 | 286 | doc = doc:walk(render_pseudocode_block(global_options)) 287 | 288 | return doc:walk(render_pseudocode_ref(global_options)) 289 | end 290 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/leovan/pseudocode/pseudocode.min.css: -------------------------------------------------------------------------------- 1 | @import url(https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css);.ps-root{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-size:1em;font-weight:100;-webkit-font-smoothing:antialiased!important}.ps-root .ps-algorithm{margin:.8em 0;border-top:3px solid #000;border-bottom:2px solid #000}.ps-root .ps-algorithm.with-caption>.ps-line:first-child{border-bottom:2px solid #000}.ps-root .katex{text-indent:0;font-size:1em}.ps-root .MathJax,.ps-root .MathJax_CHTML{text-indent:0;font-size:1em!important}.ps-root .ps-line{margin:0;padding:0;line-height:1.2}.ps-root .ps-funcname{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:400;font-variant:small-caps;font-style:normal;text-transform:none}.ps-root .ps-keyword{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:700;font-variant:normal;font-style:normal;text-transform:none}.ps-root .ps-comment{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:400;font-variant:normal;font-style:normal;text-transform:none}.ps-root .ps-linenum{font-size:.8em;line-height:1em;width:1.6em;text-align:right;display:inline-block;position:relative;padding-right:.3em}.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code{text-indent:-1.6em}.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code>span{text-indent:0}.ps-root .ps-algorithmic.with-scopelines div.ps-block{border-left-style:solid;border-left-width:.1em;padding-left:.6em}.ps-root .ps-algorithmic.with-scopelines>div.ps-block{border-left:none} -------------------------------------------------------------------------------- /_extensions/computo/_extensions/leovan/pseudocode/pseudocode.min.js: -------------------------------------------------------------------------------- 1 | (function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.pseudocode=e()}})(function(){var e,t,n;return function(){function p(o,s,a){function l(n,e){if(!s[n]){if(!o[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(h)return h(n,!0);var i=new Error("Cannot find module '"+n+"'");throw i.code="MODULE_NOT_FOUND",i}var r=s[n]={exports:{}};o[n][0].call(r.exports,function(e){var t=o[n][1][e];return l(t||e)},r,r.exports,p,o,s,a)}return s[n].exports}for(var h="function"==typeof require&&require,e=0;ethis.renderElement(e,t))}}},{"./src/Lexer":2,"./src/ParseError":3,"./src/Parser":4,"./src/Renderer":5}],2:[function(e,t,n){var i=e("./utils");var u=e("./ParseError");var r=function(e){this._input=e;this._remain=e;this._pos=0;this._nextAtom=this._currentAtom=null;this._next()};r.prototype.accept=function(e,t){if(this._nextAtom.type===e&&this._matchText(t)){this._next();return this._currentAtom.text}return null};r.prototype.expect=function(e,t){var n=this._nextAtom;if(n.type!==e){throw new u(`Expected an atom of ${e} but received ${n.type}`,this._pos,this._input)}if(!this._matchText(t)){throw new u(`Expected \`${t}\` but received \`${n.text}\``,this._pos,this._input)}this._next();return this._currentAtom.text};r.prototype.get=function(){return this._currentAtom};var o={exec:function(e){var t=[{start:"$",end:"$"},{start:"\\(",end:"\\)"}];var n=e.length;for(var i=0;i0&&a[l-1]==="\\"){var h=l+o.length;a=a.slice(h);s+=h;continue}var p=[e.slice(0,s+l+o.length),e.slice(r.length,s+l)];return p}}return null}};var p={special:/^(\\\\|\\{|\\}|\\\$|\\&|\\#|\\%|\\_)/,math:o,func:/^\\([a-zA-Z]+)/,open:/^\{/,close:/^\}/,quote:/^(`|``|'|'')/,ordinary:/^[^\\{}$&#%_\s]+/};var c=/^%.*/;var f=/^\s+/;r.prototype._skip=function(e){this._pos+=e;this._remain=this._remain.slice(e)};r.prototype._next=function(){var e=false;while(1){var t=f.exec(this._remain);if(t){e=true;var n=t[0].length;this._skip(n)}var i=c.exec(this._remain);if(!i)break;var r=i[0].length;this._skip(r)}this._currentAtom=this._nextAtom;if(this._remain===""){this._nextAtom={type:"EOF",text:null,whitespace:false};return false}for(var o in p){var s=p[o];var a=s.exec(this._remain);if(!a)continue;var l=a[0];var h=a[1]?a[1]:l;this._nextAtom={type:o,text:h,whitespace:e};this._pos+=l.length;this._remain=this._remain.slice(a[0].length);return true}throw new u("Unrecoganizable atom",this._pos,this._input)};r.prototype._matchText=function(e){if(e===null||e===undefined)return true;if(i.isString(e))return e.toLowerCase()===this._nextAtom.text.toLowerCase();else return e.some(e=>e.toLowerCase()===this._nextAtom.text.toLowerCase())};t.exports=r},{"./ParseError":3,"./utils":6}],3:[function(e,t,n){function i(e,t,n){var i=`Error: ${e}`;if(t!==undefined&&n!==undefined){i+=` at position ${t}: \``;n=`${n.slice(0,t)}\u21B1${n.slice(t)}`;var r=Math.max(0,t-15);var o=t+15;i+=`${n.slice(r,o)}\``}this.message=i}i.prototype=Object.create(Error.prototype);i.prototype.constructor=i;t.exports=i},{}],4:[function(e,t,n){var s=e("./utils");var r=e("./ParseError");var a=function(e,t){this.type=e;this.value=t;this.children=[]};a.prototype.toString=function(e){if(!e)e=0;var t="";for(var n=0;n`;if(this.value)i+=` (${s.toString(this.value)})`;i+="\n";if(this.children){for(var r=0;r0){e.addChild(t);continue}break}return e};i.prototype._parseCaption=function(){var e=this._lexer;if(!e.accept("func","caption"))return null;var t=new a("caption");e.expect("open");t.addChild(this._parseCloseText());e.expect("close");return t};i.prototype._parseBlock=function(){var e=new a("block");while(true){var t=this._parseControl();if(t){e.addChild(t);continue}var n=this._parseFunction();if(n){e.addChild(n);continue}var i=this._parseStatement(h);if(i){e.addChild(i);continue}var r=this._parseCommand(p);if(r){e.addChild(r);continue}var o=this._parseComment();if(o){e.addChild(o);continue}break}return e};i.prototype._parseControl=function(){var e;if(e=this._parseIf())return e;if(e=this._parseLoop())return e;if(e=this._parseRepeat())return e;if(e=this._parseUpon())return e};i.prototype._parseFunction=function(){var e=this._lexer;if(!e.accept("func",["function","procedure"]))return null;var t=this._lexer.get().text;e.expect("open");var n=e.expect("ordinary");e.expect("close");e.expect("open");var i=this._parseCloseText();e.expect("close");var r=this._parseBlock();e.expect("func",`end${t}`);var o=new a("function",{type:t,name:n});o.addChild(i);o.addChild(r);return o};i.prototype._parseIf=function(){if(!this._lexer.accept("func","if"))return null;var e=new a("if");this._lexer.expect("open");e.addChild(this._parseCond());this._lexer.expect("close");e.addChild(this._parseBlock());var t=0;while(this._lexer.accept("func",["elif","elsif","elseif"])){this._lexer.expect("open");e.addChild(this._parseCond());this._lexer.expect("close");e.addChild(this._parseBlock());t++}var n=false;if(this._lexer.accept("func","else")){n=true;e.addChild(this._parseBlock())}this._lexer.expect("func","endif");e.value={numElif:t,hasElse:n};return e};i.prototype._parseLoop=function(){if(!this._lexer.accept("func",["FOR","FORALL","WHILE"]))return null;var e=this._lexer.get().text.toLowerCase();var t=new a("loop",e);this._lexer.expect("open");t.addChild(this._parseCond());this._lexer.expect("close");t.addChild(this._parseBlock());var n=e!=="forall"?`end${e}`:"endfor";this._lexer.expect("func",n);return t};i.prototype._parseRepeat=function(){if(!this._lexer.accept("func",["REPEAT"]))return null;var e=this._lexer.get().text.toLowerCase();var t=new a("repeat",e);t.addChild(this._parseBlock());this._lexer.expect("func","until");this._lexer.expect("open");t.addChild(this._parseCond());this._lexer.expect("close");return t};i.prototype._parseUpon=function(){if(!this._lexer.accept("func","upon"))return null;var e=new a("upon");this._lexer.expect("open");e.addChild(this._parseCond());this._lexer.expect("close");e.addChild(this._parseBlock());this._lexer.expect("func","endupon");return e};var l=["ensure","require","input","output"];var h=["state","print","return"];i.prototype._parseStatement=function(e){if(!this._lexer.accept("func",e))return null;var t=this._lexer.get().text.toLowerCase();var n=new a("statement",t);n.addChild(this._parseOpenText());return n};var p=["break","continue"];i.prototype._parseCommand=function(e){if(!this._lexer.accept("func",e))return null;var t=this._lexer.get().text.toLowerCase();var n=new a("command",t);return n};i.prototype._parseComment=function(){if(!this._lexer.accept("func","comment"))return null;var e=new a("comment");this._lexer.expect("open");e.addChild(this._parseCloseText());this._lexer.expect("close");return e};i.prototype._parseCall=function(){var e=this._lexer;if(!e.accept("func","call"))return null;var t=e.get().whitespace;e.expect("open");var n=e.expect("ordinary");e.expect("close");var i=new a("call");i.whitespace=t;i.value=n;e.expect("open");var r=this._parseCloseText();i.addChild(r);e.expect("close");return i};i.prototype._parseCond=i.prototype._parseCloseText=function(){return this._parseText("close")};i.prototype._parseOpenText=function(){return this._parseText("open")};i.prototype._parseText=function(e){var t=new a(`${e}-text`);var n=false;var i;while(true){i=this._parseAtom()||this._parseCall();if(i){if(n)i.whitespace|=n;t.addChild(i);continue}if(this._lexer.accept("open")){i=this._parseCloseText();n=this._lexer.get().whitespace;i.whitespace=n;t.addChild(i);this._lexer.expect("close");n=this._lexer.get().whitespace;continue}break}return t};var u={ordinary:{tokenType:"ordinary"},math:{tokenType:"math"},special:{tokenType:"special"},"cond-symbol":{tokenType:"func",tokenValues:["and","or","not","true","false","to","downto"]},"quote-symbol":{tokenType:"quote"},"sizing-dclr":{tokenType:"func",tokenValues:["tiny","scriptsize","footnotesize","small","normalsize","large","Large","LARGE","huge","Huge"]},"font-dclr":{tokenType:"func",tokenValues:["normalfont","rmfamily","sffamily","ttfamily","upshape","itshape","slshape","scshape","bfseries","mdseries","lfseries"]},"font-cmd":{tokenType:"func",tokenValues:["textnormal","textrm","textsf","texttt","textup","textit","textsl","textsc","uppercase","lowercase","textbf","textmd","textlf"]},"text-symbol":{tokenType:"func",tokenValues:["textbackslash"]}};i.prototype._parseAtom=function(){for(var e in u){var t=u[e];var n=this._lexer.accept(t.tokenType,t.tokenValues);if(n===null)continue;var i=this._lexer.get().whitespace;if(e!=="ordinary"&&e!=="math")n=n.toLowerCase();return new o(e,n,i)}return null};t.exports=i},{"./ParseError":3,"./utils":6}],5:[function(n,e,t){var a=n("./utils");function A(e){this._css={};this._fontSize=this._outerFontSize=e!==undefined?e:1}A.prototype.outerFontSize=function(e){if(e!==undefined)this._outerFontSize=e;return this._outerFontSize};A.prototype.fontSize=function(){return this._fontSize};A.prototype._fontCommandTable={normalfont:{"font-family":"KaTeX_Main"},rmfamily:{"font-family":"KaTeX_Main"},sffamily:{"font-family":"KaTeX_SansSerif"},ttfamily:{"font-family":"KaTeX_Typewriter"},bfseries:{"font-weight":"bold"},mdseries:{"font-weight":"medium"},lfseries:{"font-weight":"lighter"},upshape:{"font-style":"normal","font-variant":"normal"},itshape:{"font-style":"italic","font-variant":"normal"},scshape:{"font-style":"normal","font-variant":"small-caps"},slshape:{"font-style":"oblique","font-variant":"normal"},textnormal:{"font-family":"KaTeX_Main"},textrm:{"font-family":"KaTeX_Main"},textsf:{"font-family":"KaTeX_SansSerif"},texttt:{"font-family":"KaTeX_Typewriter"},textbf:{"font-weight":"bold"},textmd:{"font-weight":"medium"},textlf:{"font-weight":"lighter"},textup:{"font-style":"normal","font-variant":"normal"},textit:{"font-style":"italic","font-variant":"normal"},textsc:{"font-style":"normal","font-variant":"small-caps"},textsl:{"font-style":"oblique","font-variant":"normal"},uppercase:{"text-transform":"uppercase"},lowercase:{"text-transform":"lowercase"}};A.prototype._sizingScalesTable={tiny:.68,scriptsize:.8,footnotesize:.85,small:.92,normalsize:1,large:1.17,Large:1.41,LARGE:1.58,huge:1.9,Huge:2.28};A.prototype.updateByCommand=function(e){var t=this._fontCommandTable[e];if(t!==undefined){for(var n in t)this._css[n]=t[n];return}var i=this._sizingScalesTable[e];if(i!==undefined){this._outerFontSize=this._fontSize;this._fontSize=i;return}throw new ParserError("Unrecognized `text-style` command")};A.prototype.toCSS=function(){var e="";for(var t in this._css){var n=this._css[t];if(n===undefined)continue;e+=`${t}:${n};`}if(this._fontSize!==this._outerFontSize)e+=`font-size:${this._fontSize/this._outerFontSize}em;`;return e};function B(e,t){this._nodes=e;this._textStyle=t}B.prototype._renderCloseText=function(e,t){var n=new A(this._textStyle.fontSize());var i=new B(e.children,n);if(e.whitespace)this._html.putText(" ");this._html.putHTML(i.renderToHTML(t))};B.prototype.renderToHTML=function(e){this._html=new _;var t;while((t=this._nodes.shift())!==undefined){var n=t.type;var i=t.value;if(t.whitespace)this._html.putText(" ");switch(n){case"ordinary":this._html.putText(i);break;case"math":if(typeof e==="undefined"){throw EvalError("No math backend found. Please setup KaTeX or MathJax.")}else if(e.name==="katex"){this._html.putHTML(e.driver.renderToString(i))}else if(e.name==="mathjax"){if(e.version<3){this._html.putText(`$${i}$`)}else{this._html.putHTML(e.driver.tex2chtml(i,{display:false}).outerHTML)}}else{throw new EvalError(`Unknown math backend ${e}`)}break;case"cond-symbol":this._html.beginSpan("ps-keyword").putText(i.toLowerCase()).endSpan();break;case"special":if(i==="\\\\"){this._html.putHTML("
");break}var r={"\\{":"{","\\}":"}","\\$":"$","\\&":"&","\\#":"#","\\%":"%","\\_":"_"};var o=r[i];this._html.putText(o);break;case"text-symbol":var s={textbackslash:"\\"};var a=s[i];this._html.putText(a);break;case"quote-symbol":var l={"`":"\u2018","``":"\u201c","'":"\u2019","''":"\u201d"};var h=l[i];this._html.putText(h);break;case"call":this._html.beginSpan("ps-funcname").putText(i).endSpan();this._html.write("(");var p=t.children[0];this._renderCloseText(p,e);this._html.write(")");break;case"close-text":this._renderCloseText(t,e);break;case"font-dclr":case"sizing-dclr":this._textStyle.updateByCommand(i);this._html.beginSpan(null,this._textStyle.toCSS());var u=new B(this._nodes,this._textStyle);this._html.putHTML(u.renderToHTML(e));this._html.endSpan();break;case"font-cmd":var c=this._nodes[0];if(c.type!=="close-text")continue;var f=new A(this._textStyle.fontSize());f.updateByCommand(i);this._html.beginSpan(null,f.toCSS());var d=new B(c.children,f);this._html.putHTML(d.renderToHTML(e));this._html.endSpan();break;default:throw new ParseError(`Unexpected ParseNode of type ${t.type}`)}}return this._html.toMarkup()};function _(){this._body=[];this._textBuf=[]}_.prototype.beginDiv=function(e,t,n){this._beginTag("div",e,t,n);this._body.push("\n");return this};_.prototype.endDiv=function(){this._endTag("div");this._body.push("\n");return this};_.prototype.beginP=function(e,t,n){this._beginTag("p",e,t,n);this._body.push("\n");return this};_.prototype.endP=function(){this._flushText();this._endTag("p");this._body.push("\n");return this};_.prototype.beginSpan=function(e,t,n){this._flushText();return this._beginTag("span",e,t,n)};_.prototype.endSpan=function(){this._flushText();return this._endTag("span")};_.prototype.putHTML=function(e){this._flushText();this._body.push(e);return this};_.prototype.putText=function(e){this._textBuf.push(e);return this};_.prototype.write=function(e){this._body.push(e)};_.prototype.toMarkup=function(){this._flushText();var e=this._body.join("");return e.trim()};_.prototype.toDOM=function(){var e=this.toMarkup();var t=document.createElement("div");t.innerHTML=e;return t.firstChild};_.prototype._flushText=function(){if(this._textBuf.length===0)return;var e=this._textBuf.join("");this._body.push(this._escapeHtml(e));this._textBuf=[]};_.prototype._beginTag=function(e,t,n,i){var r=`<${e}`;if(t)r+=` class="${t}"`;if(n){var o;if(a.isString(n)){o=n}else{o="";for(var s in n){attrVal=n[s];o+=`${s}:${attrVal};`}}if(i)o+=i;r+=` style="${o}"`}r+=">";this._body.push(r);return this};_.prototype._endTag=function(e){this._body.push(``);return this};var i={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};_.prototype._escapeHtml=function(e){return String(e).replace(/[&<>"'/]/g,e=>i[e])};function r(e){e=e||{};this.indentSize=e.indentSize?this._parseEmVal(e.indentSize):1.2;this.commentDelimiter=e.commentDelimiter!==undefined?e.commentDelimiter:" // ";this.lineNumberPunc=e.lineNumberPunc!==undefined?e.lineNumberPunc:":";this.lineNumber=e.lineNumber!==undefined?e.lineNumber:false;this.noEnd=e.noEnd!==undefined?e.noEnd:false;this.scopeLines=e.scopeLines!==undefined?e.scopeLines:false;if(e.captionCount!==undefined)F.captionCount=e.captionCount;this.titlePrefix=e.titlePrefix!==undefined?e.titlePrefix:"Algorithm"}r.prototype._parseEmVal=function(e){e=e.trim();if(e.indexOf("em")!==e.length-2)throw new TypeError("Unit error; expected `em` suffix");return Number(e.substring(0,e.length-2))};function F(e,t){this._root=e.parse();this._options=new r(t);this._openLine=false;this._blockLevel=0;this._textLevel=-1;this._globalTextStyle=new A;this.backend=undefined;try{if(typeof katex==="undefined")katex=n("katex")}catch(e){}try{if(typeof MathJax==="undefined")MathJax=n("mathjax")}catch(e){}if(typeof katex!=="undefined"){this.backend={name:"katex",driver:katex}}else if(typeof MathJax!=="undefined"){this.backend={name:"mathjax",version:parseInt(MathJax.version.split(".")[0]),driver:MathJax}}}F.captionCount=0;F.prototype.toMarkup=function(){var e=this._html=new _;this._buildTree(this._root);delete this._html;return e.toMarkup()};F.prototype.toDOM=function(){var e=this.toMarkup();var t=document.createElement("div");t.innerHTML=e;return t.firstChild};F.prototype._beginGroup=function(e,t,n){this._closeLineIfAny();this._html.beginDiv(`ps-${e}${t?` ${t}`:""}`,n)};F.prototype._endGroup=function(e){this._closeLineIfAny();this._html.endDiv()};F.prototype._beginBlock=function(){var e=this._options.lineNumber&&this._blockLevel===0?.6:0;var t=this._options.indentSize+e;if(this._options.scopeLines)t/=2;this._beginGroup("block",null,{"margin-left":`${t}em`});this._blockLevel++};F.prototype._endBlock=function(){this._closeLineIfAny();this._endGroup();this._blockLevel--};F.prototype._newLine=function(){this._closeLineIfAny();this._openLine=true;this._globalTextStyle.outerFontSize(1);var e=this._options.indentSize;if(this._blockLevel>0){this._numLOC++;this._html.beginP("ps-line ps-code",this._globalTextStyle.toCSS());var t=this._options.lineNumber?e*1.25:0;t+=this._options.scopeLines?e*.1:0;if(this._options.lineNumber){this._html.beginSpan("ps-linenum",{left:`${-((this._blockLevel-1)*t)}em`}).putText(this._numLOC+this._options.lineNumberPunc).endSpan()}}else{this._html.beginP("ps-line",{"text-indent":`${-e}em`,"padding-left":`${e}em`},this._globalTextStyle.toCSS())}};F.prototype._closeLineIfAny=function(){if(!this._openLine)return;this._html.endP();this._openLine=false};F.prototype._typeKeyword=function(e){this._html.beginSpan("ps-keyword").putText(e).endSpan()};F.prototype._typeFuncName=function(e){this._html.beginSpan("ps-funcname").putText(e).endSpan()};F.prototype._typeText=function(e){this._html.write(e)};F.prototype._buildTreeForAllChildren=function(e){var t=e.children;for(var n=0;n0&&t[0].type==="comment"){var n=t.shift();this._buildTree(n)}};F.prototype._buildTree=function(e){var t;var n;var i;switch(e.type){case"root":this._beginGroup("root");this._buildTreeForAllChildren(e);this._endGroup();break;case"algorithm":var r;for(t=0;t 0 then 54 | section_identifiers = {} 55 | for i, ident in ipairs(section_identifiers_list) do 56 | section_identifiers[stringify(ident)] = true 57 | end 58 | end 59 | -- unset config in meta 60 | doc.meta['abstract-section'] = nil 61 | 62 | local blocks = {} 63 | if PANDOC_VERSION >= {2,17} then 64 | -- Walk all block lists by default 65 | blocks = doc.blocks:walk{Blocks = abstract_from_blocklist} 66 | elseif PANDOC_VERSION >= {2,9,2} then 67 | -- Do the same with pandoc versions that don't have walk methods but the 68 | -- `walk_block` function. 69 | blocks = pandoc.utils.walk_block( 70 | pandoc.Div(doc.blocks), 71 | {Blocks = abstract_from_blocklist} 72 | ).content 73 | else 74 | -- otherwise, just check the top-level block-list 75 | blocks = abstract_from_blocklist(doc.blocks) 76 | end 77 | for metakey in pairs(section_identifiers) do 78 | metakey = stringify(metakey) 79 | local abstract = collected[metakey] 80 | if not meta[metakey] and abstract and #abstract > 0 then 81 | meta[metakey] = pandoc.MetaBlocks(abstract) 82 | end 83 | end 84 | return pandoc.Pandoc(blocks, meta) 85 | end 86 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/pandoc-ext/diagram/_extension.yaml: -------------------------------------------------------------------------------- 1 | title: diagram 2 | author: Albert Krewinkel 3 | version: 1.2.0 4 | quarto-required: ">=1.3" 5 | contributes: 6 | filters: 7 | - diagram.lua 8 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/pandoc-ext/diagram/diagram.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | diagram – create images and figures from code blocks. 3 | 4 | See copyright notice in file LICENSE. 5 | ]] 6 | -- The filter uses the Figure AST element, which was added in pandoc 3. 7 | PANDOC_VERSION:must_be_at_least '3.0' 8 | 9 | local version = pandoc.types.Version '1.2.0' 10 | 11 | -- Report Lua warnings to stderr if the `warn` function is not plugged into 12 | -- pandoc's logging system. 13 | if not warn then 14 | -- fallback 15 | warn = function(...) io.stderr:write(table.concat({ ... })) end 16 | elseif PANDOC_VERSION < '3.1.4' then 17 | -- starting with pandoc 3.1.4, warnings are reported to pandoc's logging 18 | -- system, so no need to print warnings to stderr. 19 | warn '@on' 20 | end 21 | 22 | local io = require 'io' 23 | local pandoc = require 'pandoc' 24 | local system = require 'pandoc.system' 25 | local utils = require 'pandoc.utils' 26 | local List = require 'pandoc.List' 27 | local stringify = utils.stringify 28 | local with_temporary_directory = system.with_temporary_directory 29 | local with_working_directory = system.with_working_directory 30 | 31 | --- Returns a filter-specific directory in which cache files can be 32 | --- stored, or nil if no such directory is available. 33 | local function cachedir () 34 | local cache_home = os.getenv 'XDG_CACHE_HOME' 35 | if not cache_home or cache_home == '' then 36 | local user_home = system.os == 'windows' 37 | and os.getenv 'USERPROFILE' 38 | or os.getenv 'HOME' 39 | 40 | if not user_home or user_home == '' then 41 | return nil 42 | end 43 | cache_home = pandoc.path.join{user_home, '.cache'} or nil 44 | end 45 | 46 | -- Create filter cache directory 47 | return pandoc.path.join{cache_home, 'pandoc-diagram-filter'} 48 | end 49 | 50 | --- Path holding the image cache, or `nil` if the cache is not used. 51 | local image_cache = nil 52 | 53 | local mimetype_for_extension = { 54 | jpeg = 'image/jpeg', 55 | jpg = 'image/jpeg', 56 | pdf = 'application/pdf', 57 | png = 'image/png', 58 | svg = 'image/svg+xml', 59 | } 60 | 61 | local extension_for_mimetype = { 62 | ['application/pdf'] = 'pdf', 63 | ['image/jpeg'] = 'jpg', 64 | ['image/png'] = 'png', 65 | ['image/svg+xml'] = 'svg', 66 | } 67 | 68 | --- Converts a list of format specifiers to a set of MIME types. 69 | local function mime_types_set (tbl) 70 | local set = {} 71 | local mime_type 72 | for _, image_format_spec in ipairs(tbl) do 73 | mime_type = mimetype_for_extension[image_format_spec] or image_format_spec 74 | set[mime_type] = true 75 | end 76 | return set 77 | end 78 | 79 | --- Reads the contents of a file. 80 | local function read_file (filepath) 81 | local fh = io.open(filepath, 'rb') 82 | local contents = fh:read('a') 83 | fh:close() 84 | return contents 85 | end 86 | 87 | --- Writes the contents into a file at the given path. 88 | local function write_file (filepath, content) 89 | local fh = io.open(filepath, 'wb') 90 | fh:write(content) 91 | fh:close() 92 | end 93 | 94 | --- Like `pandoc.pipe`, but allows "multi word" paths: 95 | -- Supplying a list as the first argument will use the first element as 96 | -- the executable path and prepend the remaining elements to the list of 97 | -- arguments. 98 | local function pipe (command, args, input) 99 | local cmd 100 | if pandoc.utils.type(command) == 'List' then 101 | command = command:map(stringify) 102 | cmd = command:remove(1) 103 | args = command .. args 104 | else 105 | cmd = stringify(command) 106 | end 107 | return pandoc.pipe(cmd, args, input) 108 | end 109 | 110 | 111 | -- 112 | -- Diagram Engines 113 | -- 114 | 115 | -- PlantUML engine; assumes that there's a `plantuml` binary. 116 | local plantuml = { 117 | line_comment_start = [[']], 118 | mime_types = mime_types_set{'pdf', 'png', 'svg'}, 119 | compile = function (self, puml) 120 | local mime_type = self.mime_type or 'image/svg+xml' 121 | -- PlantUML format identifiers correspond to common file extensions. 122 | local format = extension_for_mimetype[mime_type] 123 | if not format then 124 | format, mime_type = 'svg', 'image/svg+xml' 125 | end 126 | local args = {'-t' .. format, "-pipe", "-charset", "UTF8"} 127 | return pipe(self.execpath or 'plantuml', args, puml), mime_type 128 | end, 129 | } 130 | 131 | --- GraphViz engine for the dot language 132 | local graphviz = { 133 | line_comment_start = '//', 134 | mime_types = mime_types_set{'jpg', 'pdf', 'png', 'svg'}, 135 | mime_type = 'image/svg+xml', 136 | compile = function (self, code) 137 | local mime_type = self.mime_type 138 | -- GraphViz format identifiers correspond to common file extensions. 139 | local format = extension_for_mimetype[mime_type] 140 | if not format then 141 | format, mime_type = 'svg', 'image/svg+xml' 142 | end 143 | return pipe(self.execpath or 'dot', {"-T"..format}, code), mime_type 144 | end, 145 | } 146 | 147 | --- Mermaid engine 148 | local mermaid = { 149 | line_comment_start = '%%', 150 | mime_types = mime_types_set{'pdf', 'png', 'svg'}, 151 | compile = function (self, code) 152 | local mime_type = self.mime_type or 'image/svg+xml' 153 | local file_extension = extension_for_mimetype[mime_type] 154 | return with_temporary_directory("diagram", function (tmpdir) 155 | return with_working_directory(tmpdir, function () 156 | local infile = 'diagram.mmd' 157 | local outfile = 'diagram.' .. file_extension 158 | write_file(infile, code) 159 | pipe( 160 | self.execpath or 'mmdc', 161 | {"--pdfFit", "--input", infile, "--output", outfile}, 162 | '' 163 | ) 164 | return read_file(outfile), mime_type 165 | end) 166 | end) 167 | end, 168 | } 169 | 170 | --- TikZ 171 | -- 172 | 173 | --- LaTeX template used to compile TikZ images. 174 | local tikz_template = pandoc.template.compile [[ 175 | \documentclass{standalone} 176 | \usepackage{tikz} 177 | $for(header-includes)$ 178 | $it$ 179 | $endfor$ 180 | $additional-packages$ 181 | \begin{document} 182 | $body$ 183 | \end{document} 184 | ]] 185 | 186 | --- The TikZ engine uses pdflatex to compile TikZ code to an image 187 | local tikz = { 188 | line_comment_start = '%%', 189 | 190 | mime_types = { 191 | ['application/pdf'] = true, 192 | }, 193 | 194 | --- Compile LaTeX with TikZ code to an image 195 | compile = function (self, src, user_opts) 196 | return with_temporary_directory("tikz", function (tmpdir) 197 | return with_working_directory(tmpdir, function () 198 | -- Define file names: 199 | local file_template = "%s/tikz-image.%s" 200 | local tikz_file = file_template:format(tmpdir, "tex") 201 | local pdf_file = file_template:format(tmpdir, "pdf") 202 | 203 | -- Treat string values as raw LaTeX 204 | local meta = { 205 | ['header-includes'] = user_opts['header-includes'], 206 | ['additional-packages'] = {pandoc.RawInline( 207 | 'latex', 208 | stringify(user_opts['additional-packages'] or '') 209 | )}, 210 | } 211 | local tex_code = pandoc.write( 212 | pandoc.Pandoc({pandoc.RawBlock('latex', src)}, meta), 213 | 'latex', 214 | {template = tikz_template} 215 | ) 216 | write_file(tikz_file, tex_code) 217 | 218 | -- Execute the LaTeX compiler: 219 | local success, result = pcall( 220 | pipe, 221 | self.execpath or 'pdflatex', 222 | { '-interaction=nonstopmode', '-output-directory', tmpdir, tikz_file }, 223 | '' 224 | ) 225 | if not success then 226 | warn(string.format( 227 | "The call\n%s\nfailed with error code %s. Output:\n%s", 228 | result.command, 229 | result.error_code, 230 | result.output 231 | )) 232 | end 233 | return read_file(pdf_file), 'application/pdf' 234 | end) 235 | end) 236 | end 237 | } 238 | 239 | --- Asymptote diagram engine 240 | local asymptote = { 241 | line_comment_start = '%%', 242 | mime_types = { 243 | ['application/pdf'] = true, 244 | }, 245 | compile = function (self, code) 246 | return with_temporary_directory("asymptote", function(tmpdir) 247 | return with_working_directory(tmpdir, function () 248 | local pdf_file = "pandoc_diagram.pdf" 249 | local args = {'-tex', 'pdflatex', "-o", "pandoc_diagram", '-'} 250 | pipe(self.execpath or 'asy', args, code) 251 | return read_file(pdf_file), 'application/pdf' 252 | end) 253 | end) 254 | end, 255 | } 256 | 257 | --- Cetz diagram engine 258 | local cetz = { 259 | line_comment_start = '%%', 260 | mime_types = mime_types_set{'jpg', 'pdf', 'png', 'svg'}, 261 | mime_type = 'image/svg+xml', 262 | compile = function (self, code) 263 | local mime_type = self.mime_type 264 | local format = extension_for_mimetype[mime_type] 265 | if not format then 266 | format, mime_type = 'svg', 'image/svg+xml' 267 | end 268 | local preamble = [[ 269 | #import "@preview/cetz:0.2.2" 270 | #set page(width: auto, height: auto, margin: .5cm) 271 | ]] 272 | 273 | local typst_code = preamble .. code 274 | 275 | return with_temporary_directory("diagram", function (tmpdir) 276 | return with_working_directory(tmpdir, function () 277 | local outfile = 'diagram.' .. format 278 | local execpath = self.execpath 279 | if not execpath and quarto and quarto.version >= '1.4' then 280 | -- fall back to the Typst exec shipped with Quarto. 281 | execpath = List{'quarto', 'typst'} 282 | end 283 | pipe( 284 | execpath or 'typst', 285 | {"compile", "-f", format, "-", outfile}, 286 | typst_code 287 | ) 288 | return read_file(outfile), mime_type 289 | end) 290 | end) 291 | end, 292 | } 293 | 294 | local default_engines = { 295 | asymptote = asymptote, 296 | dot = graphviz, 297 | mermaid = mermaid, 298 | plantuml = plantuml, 299 | tikz = tikz, 300 | cetz = cetz, 301 | } 302 | 303 | -- 304 | -- Configuration 305 | -- 306 | 307 | --- Options for the output format of the given name. 308 | local function format_options (name) 309 | local pdf2svg = name ~= 'latex' and name ~= 'context' 310 | local is_office_format = name == 'docx' or name == 'odt' 311 | -- Office formats seem to work better with PNG than with SVG. 312 | local preferred_mime_types = is_office_format 313 | and pandoc.List{'image/png', 'application/pdf'} 314 | or pandoc.List{'application/pdf', 'image/png'} 315 | -- Prefer SVG for non-PDF output formats, except for Office formats 316 | if is_office_format then 317 | preferred_mime_types:insert('image/svg+xml') 318 | elseif pdf2svg then 319 | preferred_mime_types:insert(1, 'image/svg+xml') 320 | end 321 | return { 322 | name = name, 323 | pdf2svg = pdf2svg, 324 | preferred_mime_types = preferred_mime_types, 325 | best_mime_type = function (self, supported_mime_types, requested) 326 | return self.preferred_mime_types:find_if(function (preferred) 327 | return supported_mime_types[preferred] and 328 | (not requested or 329 | (pandoc.utils.type(requested) == 'List' and 330 | requested:includes(preferred)) or 331 | (pandoc.utils.type(requested) == 'table' and 332 | requested[preferred]) or 333 | 334 | -- Assume string, Inlines, and Blocks values specify the only 335 | -- acceptable MIME type. 336 | stringify(requested) == preferred) 337 | end) 338 | end 339 | } 340 | end 341 | 342 | --- Returns a configured diagram engine. 343 | local function get_engine (name, engopts, format) 344 | local engine = default_engines[name] or 345 | select(2, pcall(require, stringify(engopts.package))) 346 | 347 | -- Sanity check 348 | if not engine then 349 | warn(PANDOC_SCRIPT_FILE, ": No such engine '", name, "'.") 350 | return nil 351 | elseif engopts == false then 352 | -- engine is disabled 353 | return nil 354 | elseif engopts == true then 355 | -- use default options 356 | return engine 357 | end 358 | 359 | local execpath = engopts.execpath or os.getenv(name:upper() .. '_BIN') 360 | 361 | local mime_type = format:best_mime_type( 362 | engine.mime_types, 363 | engopts['mime-type'] or engopts['mime-types'] 364 | ) 365 | if not mime_type then 366 | warn(PANDOC_SCRIPT_FILE, ": Cannot use ", name, " with ", format.name) 367 | return nil 368 | end 369 | 370 | return { 371 | execpath = execpath, 372 | compile = engine.compile, 373 | line_comment_start = engine.line_comment_start, 374 | mime_type = mime_type, 375 | opt = engopts or {}, 376 | } 377 | end 378 | 379 | --- Returns the diagram engine configs. 380 | local function configure (meta, format_name) 381 | local conf = meta.diagram or {} 382 | local format = format_options(format_name) 383 | meta.diagram = nil 384 | 385 | -- cache for image files 386 | if conf.cache then 387 | image_cache = conf['cache-dir'] 388 | and stringify(conf['cache-dir']) 389 | or cachedir() 390 | pandoc.system.make_directory(image_cache, true) 391 | end 392 | 393 | -- engine configs 394 | local engine = {} 395 | for name, engopts in pairs(conf.engine or default_engines) do 396 | engine[name] = get_engine(name, engopts, format) 397 | end 398 | 399 | return { 400 | engine = engine, 401 | format = format, 402 | cache = image_cache and true, 403 | image_cache = image_cache, 404 | } 405 | end 406 | 407 | -- 408 | -- Format conversion 409 | -- 410 | 411 | --- Converts a PDF to SVG. 412 | local pdf2svg = function (imgdata) 413 | -- Using `os.tmpname()` instead of a hash would be slightly cleaner, but the 414 | -- function causes problems on Windows (and wasm). See, e.g., 415 | -- https://github.com/pandoc-ext/diagram/issues/49 416 | local pdf_file = 'diagram-' .. pandoc.utils.sha1(imgdata) .. '.pdf' 417 | write_file(pdf_file, imgdata) 418 | local args = { 419 | '--export-type=svg', 420 | '--export-plain-svg', 421 | '--export-filename=-', 422 | pdf_file 423 | } 424 | return pandoc.pipe('inkscape', args, ''), os.remove(pdf_file) 425 | end 426 | 427 | local function properties_from_code (code, comment_start) 428 | local props = {} 429 | local pattern = comment_start:gsub('%p', '%%%1') .. '| ' .. 430 | '([-_%w]+): ([^\n]*)\n' 431 | for key, value in code:gmatch(pattern) do 432 | if key == 'fig-cap' then 433 | props['caption'] = value 434 | else 435 | props[key] = value 436 | end 437 | end 438 | return props 439 | end 440 | 441 | local function diagram_options (cb, comment_start) 442 | local attribs = comment_start 443 | and properties_from_code(cb.text, comment_start) 444 | or {} 445 | for key, value in pairs(cb.attributes) do 446 | attribs[key] = value 447 | end 448 | 449 | local alt 450 | local caption 451 | local fig_attr = {id = cb.identifier} 452 | local filename 453 | local image_attr = {} 454 | local user_opt = {} 455 | 456 | for attr_name, value in pairs(attribs) do 457 | if attr_name == 'alt' then 458 | alt = value 459 | elseif attr_name == 'caption' then 460 | -- Read caption attribute as Markdown 461 | caption = attribs.caption 462 | and pandoc.read(attribs.caption).blocks 463 | or nil 464 | elseif attr_name == 'filename' then 465 | filename = value 466 | elseif attr_name == 'label' then 467 | fig_attr.id = value 468 | elseif attr_name == 'name' then 469 | fig_attr.name = value 470 | else 471 | -- Check for prefixed attributes 472 | local prefix, key = attr_name:match '^(%a+)%-(%a[-%w]*)$' 473 | if prefix == 'fig' then 474 | fig_attr[key] = value 475 | elseif prefix == 'image' or prefix == 'img' then 476 | image_attr[key] = value 477 | elseif prefix == 'opt' then 478 | user_opt[key] = value 479 | else 480 | -- Use as image attribute 481 | image_attr[attr_name] = value 482 | end 483 | end 484 | end 485 | 486 | return { 487 | ['alt'] = alt or 488 | (caption and pandoc.utils.blocks_to_inlines(caption)) or 489 | {}, 490 | ['caption'] = caption, 491 | ['fig-attr'] = fig_attr, 492 | ['filename'] = filename, 493 | ['image-attr'] = image_attr, 494 | ['opt'] = user_opt, 495 | } 496 | end 497 | 498 | local function get_cached_image (hash, mime_type) 499 | if not image_cache then 500 | return nil 501 | end 502 | local filename = hash .. '.' .. extension_for_mimetype[mime_type] 503 | local imgpath = pandoc.path.join{image_cache, filename} 504 | local success, imgdata = pcall(read_file, imgpath) 505 | if success then 506 | return imgdata, mime_type 507 | end 508 | return nil 509 | end 510 | 511 | local function cache_image (codeblock, imgdata, mimetype) 512 | -- do nothing if caching is disabled or not possible. 513 | if not image_cache then 514 | return 515 | end 516 | local ext = extension_for_mimetype[mimetype] 517 | local filename = pandoc.sha1(codeblock.text) .. '.' .. ext 518 | local imgpath = pandoc.path.join{image_cache, filename} 519 | write_file(imgpath, imgdata) 520 | end 521 | 522 | -- Executes each document's code block to find matching code blocks: 523 | local function code_to_figure (conf) 524 | return function (block) 525 | -- Check if a converter exists for this block. If not, return the block 526 | -- unchanged. 527 | local diagram_type = block.classes[1] 528 | if not diagram_type then 529 | return nil 530 | end 531 | 532 | local engine = conf.engine[diagram_type] 533 | if not engine then 534 | return nil 535 | end 536 | 537 | -- Unified properties. 538 | local dgr_opt = diagram_options(block, engine.line_comment_start) 539 | for optname, value in pairs(engine.opt or {}) do 540 | dgr_opt.opt[optname] = dgr_opt.opt[optname] or value 541 | end 542 | 543 | local run_pdf2svg = engine.mime_type == 'application/pdf' 544 | and conf.format.pdf2svg 545 | 546 | -- Try to retrieve the image data from the cache. 547 | local imgdata, imgtype 548 | if conf.cache then 549 | imgdata, imgtype = get_cached_image( 550 | pandoc.sha1(block.text), 551 | run_pdf2svg and 'image/svg+xml' or engine.mime_type 552 | ) 553 | end 554 | 555 | if not imgdata or not imgtype then 556 | -- No cached image; call the converter 557 | local success 558 | success, imgdata, imgtype = 559 | pcall(engine.compile, engine, block.text, dgr_opt.opt) 560 | 561 | -- Bail if an error occurred; imgdata contains the error message 562 | -- when that happens. 563 | if not success then 564 | warn(PANDOC_SCRIPT_FILE, ': ', tostring(imgdata)) 565 | return nil 566 | elseif not imgdata then 567 | warn(PANDOC_SCRIPT_FILE, ': Diagram engine returned no image data.') 568 | return nil 569 | elseif not imgtype then 570 | warn(PANDOC_SCRIPT_FILE, ': Diagram engine did not return a MIME type.') 571 | return nil 572 | end 573 | 574 | -- Convert SVG if necessary. 575 | if imgtype == 'application/pdf' and conf.format.pdf2svg then 576 | imgdata, imgtype = pdf2svg(imgdata), 'image/svg+xml' 577 | end 578 | 579 | -- If we got here, then the transformation went ok and `img` contains 580 | -- the image data. 581 | cache_image(block, imgdata, imgtype) 582 | end 583 | 584 | -- Use the block's filename attribute or create a new name by hashing the 585 | -- image content. 586 | local basename, _extension = pandoc.path.split_extension( 587 | dgr_opt.filename or pandoc.sha1(imgdata) 588 | ) 589 | local fname = basename .. '.' .. extension_for_mimetype[imgtype] 590 | 591 | -- Store the data in the media bag: 592 | pandoc.mediabag.insert(fname, imgtype, imgdata) 593 | 594 | -- Create the image object. 595 | local image = pandoc.Image(dgr_opt.alt, fname, "", dgr_opt['image-attr']) 596 | 597 | -- Create a figure if the diagram has a caption; otherwise return 598 | -- just the image. 599 | return dgr_opt.caption and 600 | pandoc.Figure( 601 | pandoc.Plain{image}, 602 | dgr_opt.caption, 603 | dgr_opt['fig-attr'] 604 | ) or 605 | pandoc.Plain{image} 606 | end 607 | end 608 | 609 | return setmetatable( 610 | {{ 611 | Pandoc = function (doc) 612 | local conf = configure(doc.meta, FORMAT) 613 | return doc:walk { 614 | CodeBlock = code_to_figure(conf), 615 | } 616 | end 617 | }}, 618 | { 619 | version = version, 620 | } 621 | ) 622 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/pandoc-ext/list-table/_extension.yaml: -------------------------------------------------------------------------------- 1 | title: list-table 2 | author: Martin Fischer, pandoc extensions contributors 3 | version: 1.0.0 4 | quarto-required: ">=1.3" 5 | contributes: 6 | filters: 7 | - list-table.lua 8 | -------------------------------------------------------------------------------- /_extensions/computo/_extensions/pandoc-ext/list-table/list-table.lua: -------------------------------------------------------------------------------- 1 | -- lua filter for RST-like list-tables in Markdown. 2 | -- Copyright (C) 2021 Martin Fischer, released under MIT license 3 | 4 | if PANDOC_VERSION and PANDOC_VERSION.must_be_at_least then 5 | PANDOC_VERSION:must_be_at_least("2.11") 6 | else 7 | error("pandoc version >=2.11 is required") 8 | end 9 | 10 | -- Get the list of cells in a row. 11 | local row_cells = function (row) return row.cells or {} end 12 | 13 | -- "Polyfill" for older pandoc versions. 14 | if PANDOC_VERSION <= '2.16.2' then 15 | -- previous pandoc versions used simple Attr/list pairs 16 | pandoc.Row = function (cells) return {{}, cells} end 17 | pandoc.TableHead = function (rows) return {{}, rows or {}} end 18 | pandoc.TableFoot = function (rows) return {{}, rows or {}} end 19 | pandoc.Cell = function (contents, align, rowspan, colspan, attr) 20 | return { 21 | attr = attr or pandoc.Attr(), 22 | alignment = align or pandoc.AlignDefault, 23 | contents = contents or {}, 24 | col_span = colspan or 1, 25 | row_span = rowspan or 1 26 | } 27 | end 28 | row_cells = function (row) return row[2] end 29 | end 30 | 31 | local alignments = { 32 | d = 'AlignDefault', 33 | l = 'AlignLeft', 34 | r = 'AlignRight', 35 | c = 'AlignCenter' 36 | } 37 | 38 | 39 | -- This is like assert() but it can take a Block or Blocks 'where' argument 40 | -- and will output the corresponding markdown (truncated at 1024 characters). 41 | local function assert_(assertion, message, where) 42 | message = message or 'assertion failed!' 43 | if not assertion then 44 | local extra = '' 45 | if where then 46 | local blocks = pandoc.Blocks(where) 47 | local markdown = pandoc.write(pandoc.Pandoc(blocks), 'markdown') 48 | extra = ' at\n' .. markdown:sub(1, 1024) .. 49 | (#markdown > 1024 and '...' or '') 50 | end 51 | error(message .. extra, 2) 52 | end 53 | end 54 | 55 | -- Skip data-pos Divs inserted by the sourcepos extension 56 | local function block_skip_data_pos(block) 57 | if (block.t == "Div" and block.attr.attributes["data-pos"]) then 58 | block = block.content[1] 59 | end 60 | return block 61 | end 62 | 63 | local function blocks_skip_data_pos(blocks) 64 | local new_blocks = {} 65 | for _, block in ipairs(blocks) do 66 | table.insert(new_blocks, block_skip_data_pos(block)) 67 | end 68 | return new_blocks 69 | end 70 | 71 | local function get_colspecs(div_attributes, column_count) 72 | -- list of (align, width) pairs 73 | local colspecs = {} 74 | 75 | for i = 1, column_count do 76 | table.insert(colspecs, {pandoc.AlignDefault, nil}) 77 | end 78 | 79 | if div_attributes.aligns then 80 | local i = 1 81 | for a in div_attributes.aligns:gmatch('[^,]+') do 82 | assert_(alignments[a] ~= nil, 83 | "unknown column alignment " .. tostring(a)) 84 | colspecs[i][1] = alignments[a] 85 | i = i + 1 86 | end 87 | div_attributes.aligns = nil 88 | end 89 | 90 | if div_attributes.widths then 91 | local total = 0 92 | local widths = {} 93 | for w in div_attributes.widths:gmatch('[^,]+') do 94 | table.insert(widths, tonumber(w)) 95 | total = total + tonumber(w) 96 | end 97 | for i = 1, column_count do 98 | colspecs[i][2] = widths[i] / total 99 | end 100 | div_attributes.widths = nil 101 | end 102 | 103 | return colspecs 104 | end 105 | 106 | local function new_table_body(rows, attr, header_col_count) 107 | attr = attr or {} 108 | return { 109 | attr = attr, 110 | body = rows, 111 | head = {}, 112 | row_head_columns = header_col_count 113 | } 114 | end 115 | 116 | local function new_cell(contents) 117 | local attr = {} 118 | local colspan = 1 119 | local rowspan = 1 120 | local align = pandoc.AlignDefault 121 | 122 | contents = blocks_skip_data_pos(contents) 123 | 124 | -- At the time of writing this Pandoc does not support attributes 125 | -- on list items, so we use empty spans as a workaround. 126 | if contents[1] and contents[1].content then 127 | if contents[1].content[1] and contents[1].content[1].t == "Span" then 128 | if #contents[1].content[1].content == 0 then 129 | attr = contents[1].content[1].attr 130 | table.remove(contents[1].content, 1) 131 | colspan = attr.attributes.colspan or 1 132 | attr.attributes.colspan = nil 133 | rowspan = attr.attributes.rowspan or 1 134 | attr.attributes.rowspan = nil 135 | align = alignments[attr.attributes.align] or pandoc.AlignDefault 136 | attr.attributes.align = nil 137 | end 138 | end 139 | end 140 | 141 | return pandoc.Cell(contents, align, rowspan, colspan, attr) 142 | end 143 | 144 | local function process(div) 145 | if (div.attr.classes[1] ~= "list-table" and 146 | div.attr.classes[1] ~= "list-table-body") then return nil end 147 | local class = div.attr.classes[1] 148 | table.remove(div.attr.classes, 1) 149 | 150 | if #div.content == 0 then return nil end 151 | 152 | local content = blocks_skip_data_pos(div.content) 153 | 154 | local caption = {} 155 | if content[1].t == "Para" then 156 | local para = table.remove(content, 1) 157 | caption = {pandoc.Plain(para.content)} 158 | end 159 | 160 | if #content == 0 then return nil end 161 | 162 | assert_(content[1].t == "BulletList", 163 | "expected bullet list, found " .. content[1].t, content[1]) 164 | local list = content[1] 165 | 166 | -- rows points to the current body's rows 167 | local bodies = {attr=nil, {rows={}}} 168 | local rows = bodies[#bodies].rows 169 | 170 | for i = 1, #list.content do 171 | local attr = nil 172 | local items = list.content[i] 173 | if (#items > 1) then 174 | local item = block_skip_data_pos(items[1]) 175 | assert_(item.content, "expected list item to have row attrs", 176 | item) 177 | assert_(#item.content == 1, "expected row attrs to contain " .. 178 | "only one inline", item.content) 179 | assert_(item.content[1].t == "Span", "expected row attrs to " .. 180 | "contain a span", item.content[1]) 181 | assert_(#item.content[1].content == 0, "expected row attrs " .. 182 | "span to be empty", item.content[1]) 183 | attr = item.content[1].attr 184 | table.remove(items, 1) 185 | end 186 | 187 | assert_(#items == 1, "expected item to contain only one block", items) 188 | 189 | local item = block_skip_data_pos(items[1]) 190 | if (item.t ~= 'Table') then 191 | assert_(item.t == "BulletList", "expected bullet list, found " .. 192 | item.t, item) 193 | local cells = {} 194 | for _, cell_content in pairs(item.content) do 195 | table.insert(cells, new_cell(cell_content)) 196 | end 197 | local row = pandoc.Row(cells, attr) 198 | table.insert(rows, row) 199 | 200 | else 201 | local tab = item 202 | -- XXX is there a better way to check that there's no caption? 203 | assert_((not tab.caption.long or #tab.caption.long == 0) and 204 | (not tab.caption.short or #tab.caption.short == 0), 205 | "table bodies can't have captions (they'd be " .. 206 | "ignored)", tab) 207 | -- XXX would have to check against default colspecs to know whether 208 | -- any have been defined? 209 | -- assert_(#tab.colspecs == 0, "table bodies can't (yet) have " .. 210 | -- "column specs", tab) 211 | -- XXX should allow empty headers; this can happen with pipe tables 212 | -- assert_(not tab.head or #tab.head.rows == 0, 213 | -- "table bodies can't (yet) have headers", tab) 214 | assert_(#tab.bodies == 1, "table bodies can't contain other " .. 215 | "table bodies", tab) 216 | 217 | if #rows > 0 then 218 | table.insert(bodies, {attr=nil, rows={}}) 219 | rows = bodies[#bodies].rows 220 | end 221 | 222 | bodies[#bodies].attr = tab.attr 223 | for _, row in ipairs(tab.bodies[1].body) do 224 | table.insert(rows, row) 225 | end 226 | end 227 | end 228 | 229 | -- switch back to the first body 230 | rows = bodies[1].rows 231 | 232 | local header_row_count = tonumber(div.attr.attributes['header-rows']) or 233 | (class == 'list-table' and 1 or 0) 234 | div.attr.attributes['header-rows'] = nil 235 | 236 | local header_col_count = tonumber(div.attr.attributes['header-cols']) or 0 237 | div.attr.attributes['header-cols'] = nil 238 | 239 | local column_count = 0 240 | for i = 1, #row_cells(rows[1] or {}) do 241 | column_count = column_count + row_cells(rows[1])[i].col_span 242 | end 243 | 244 | local colspecs = get_colspecs(div.attr.attributes, column_count) 245 | local thead_rows = {} 246 | for i = 1, header_row_count do 247 | table.insert(thead_rows, table.remove(rows, 1)) 248 | end 249 | 250 | local new_bodies = {} 251 | for _, body in ipairs(bodies) do 252 | if #body.rows > 0 then 253 | table.insert(new_bodies, new_table_body(body.rows, body.attr, 254 | header_col_count)) 255 | end 256 | -- XXX this should be a body property 257 | header_col_count = 0 258 | end 259 | 260 | return pandoc.Table( 261 | {long = caption, short = {}}, 262 | colspecs, 263 | pandoc.TableHead(thead_rows), 264 | new_bodies, 265 | pandoc.TableFoot(), 266 | div.attr 267 | ) 268 | end 269 | 270 | return {{Div = process}} 271 | -------------------------------------------------------------------------------- /_extensions/computo/color-text.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | If you extension format needs some special processing, include a Lua filter to be used in addition of Quarto built-in ones. 3 | Quarto exports utils function that can be used in all filters. See 4 | https://github.com/quarto-dev/quarto-cli/blob/main/src/resources/pandoc/datadir/init.lua#L1522-L1576 5 | ]]-- 6 | 7 | -- Example: allow to color words by using a span attribute. 8 | -- This filter will use the correct syntax depending on the format 9 | color_span = function(el) 10 | color = el.attributes['color'] 11 | -- if no color attribute, return unchange 12 | if color == nil then return el end 13 | 14 | -- transform to 15 | if quarto.doc.isFormat("html") then 16 | -- remove color attributes 17 | el.attributes['color'] = nil 18 | -- use style attribute instead 19 | el.classes:insert('color-' .. color ) 20 | -- return full span element 21 | return el 22 | elseif quarto.doc.isFormat("pdf") then 23 | -- remove color attributes 24 | el.attributes['color'] = nil 25 | -- encapsulate in latex code 26 | table.insert( 27 | el.content, 1, 28 | pandoc.RawInline('latex', '\\textcolor{'..color..'}{') 29 | ) 30 | table.insert( 31 | el.content, 32 | pandoc.RawInline('latex', '}') 33 | ) 34 | -- returns only span content 35 | return el.content 36 | else 37 | -- for other format return unchanged 38 | return el 39 | end 40 | end 41 | 42 | return { 43 | { 44 | Span = color_span 45 | } 46 | } -------------------------------------------------------------------------------- /_extensions/computo/custom.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | @import url('https://fonts.googleapis.com/css2?family=Oswald'); 3 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans'); 4 | 5 | /******************************************************************************* 6 | * Variables used throughout the theme. 7 | * To adjust anything, simply edit the variables below and rebuild the theme. 8 | ******************************************************************************/ 9 | 10 | // Colors 11 | $grey-color: #828282 !default; 12 | $grey-color-light: lighten($grey-color, 40%); 13 | $grey-color-dark: darken($grey-color, 25%); 14 | $white-color: #ffffff !default; 15 | $black-color: #000000 !default; 16 | $computo-color: #034E79 !default; 17 | $computo-color-dark: darken($computo-color, 10%); 18 | $computo-color-light: lighten($computo-color, 60%); 19 | $computo-color-lighter: rgba($computo-color, 0.04); 20 | 21 | $computo-color-yellow: #797903 !default; 22 | $computo-color-red: #790303 !default; 23 | 24 | 25 | // Theme colors 26 | $body-color: $grey-color-dark; 27 | $link-color: $computo-color; 28 | 29 | // Font 30 | $headings-font-weight: 500 !default; 31 | $h2-font-size: 1.6rem !default; 32 | $font-family-sans-serif: 'Open Sans', sans-serif; 33 | $ff-title: 'Oswald', sans-serif; 34 | $ff-main: 'Open Sans', sans-serif; 35 | 36 | // Callout 37 | $callout-border-color: $computo-color-light !important; 38 | $callout-color-note: $computo-color-dark; 39 | $callout-header-color: $computo-color-dark; 40 | $callout-border-width: 5px ; 41 | 42 | .callout-header { 43 | background-color: $computo-color-lighter !important; 44 | font-size: 1.1rem; 45 | font-weight: 600; 46 | color: $computo-color-dark ; 47 | } 48 | 49 | .theorem, .proposition, .lemma, .corrolary { 50 | background-color: $computo-color-lighter ; 51 | border-radius: 4px; 52 | border: 1px solid $computo-color-light; 53 | border-left-width: 5px ; 54 | border-left-color: $computo-color-dark ; 55 | padding: 2px 4px; 56 | margin: 1em 0 ; 57 | } 58 | 59 | .theorem-title { 60 | font-family: $ff-title ; 61 | color: $computo-color-dark !important; 62 | } 63 | 64 | .pseudocode { 65 | /* avoid parser error in quarto */ 66 | } 67 | 68 | // Bad inclusion of TOC if color specified in CSS for title-block-banner 69 | // Works when using .yml 70 | // $title-block-banner: $computo-color ; 71 | // $title-block-banner-color: $white-color ; 72 | 73 | $navbar-bg: $computo-color; 74 | $navbar-fg: $white-color; 75 | $navbar-text-color: $white-color; 76 | $navbar-hover-color: #{$computo-color-light}; 77 | 78 | /*-- scss:rules --*/ 79 | h1, h2, h3, h4, h5, h6 { 80 | font-family: $ff-title ; 81 | font-weight: 500; 82 | color: var(--computo-color-dark) !important; 83 | } 84 | 85 | :root { 86 | font-size:16px; 87 | } 88 | 89 | .content { 90 | text-align: justify; 91 | } 92 | 93 | body { 94 | font-size: 1rem; 95 | font-weight: 300; 96 | } 97 | -------------------------------------------------------------------------------- /_extensions/computo/logo_text_white.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/computorg/computo-quarto-extension/d82f4fdea3376e12dff441b1f7d767e4cb972ff4/_extensions/computo/logo_text_white.pdf -------------------------------------------------------------------------------- /_extensions/computo/partials/html/title-block.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

6 | 7 |   $title$

8 | $if(subtitle)$ 9 |

$subtitle$

10 | $endif$ 11 |

Creative Commons BY License 12 | ISSN 2824-7795

13 | $if(description)$ 14 |
15 |
16 | $description$ 17 |
18 |
19 | $endif$ 20 | $if(categories)$ 21 | $if(quarto-template-params.title-block-categories)$ 22 |
23 | $for(categories)$ 24 |
$categories$
25 | $endfor$ 26 |
27 | $endif$ 28 | $endif$ 29 |
30 |
31 | 32 | $title-metadata.html()$ 33 | 34 |
-------------------------------------------------------------------------------- /_extensions/computo/partials/html/title-metadata.html: -------------------------------------------------------------------------------- 1 | $if(by-affiliation/first)$ 2 |
3 |
$labels.authors$
4 |
$labels.affiliations$
5 | 6 | $for(by-author)$ 7 |
8 | $if(by-author.url)$$endif$$if(by-author.name.literal)$$by-author.name.literal$$endif$$if(by-author.url)$$endif$ $if(by-author.orcid)$ $endif$ 9 |
10 | 11 | $if(by-author.affiliations)$ 12 |
13 | $for(by-author.affiliations)$ 14 |

15 | $if(it.url)$ 16 | 17 | $endif$ 18 | $it.name$ 19 | $if(it.url)$ 20 | 21 | $endif$ 22 |

23 | $endfor$ 24 |
25 | $endif$ 26 | $endfor$ 27 |
28 | $endif$ 29 | 30 |
31 | $if(by-affiliation)$ 32 | $elseif(by-author)$ 33 |
34 |
$labels.authors$
35 |
36 | $for(by-author)$ 37 |

$by-author.name.literal$ $if(by-author.orcid)$ $endif$

38 | $endfor$ 39 |
40 |
41 | $endif$ 42 | 43 | $if(date)$ 44 |
45 |
$labels.published$
46 |
47 |

$date$

48 |
49 |
50 | $endif$ 51 | 52 | $if(date-modified)$ 53 |
54 |
$labels.modified$
55 |
56 |

$date-modified$

57 |
58 |
59 | $endif$ 60 | 61 | $if(doi)$ 62 |
63 |
$labels.doi$
64 |
65 |

66 | $doi$ 67 |

68 |
69 |
70 | $endif$ 71 | 72 | $if(keywords)$ 73 |
74 |
Keywords
75 |
76 |

$for(keywords)$$keywords$$sep$, $endfor$

77 |
78 |
79 | $endif$ 80 | 81 |
82 |
Status
83 |
84 | $if(draft)$ 85 |

draft

86 | $else$ 87 | build status 88 | $endif$ 89 | $if(published)$ 90 |

91 | reviews 92 | $endif$ 93 |
94 |
95 | 96 |
97 | 98 | $if(abstract)$ 99 |
100 |
101 |
$labels.abstract$
102 | $abstract$ 103 |
104 |
105 | $endif$ 106 | -------------------------------------------------------------------------------- /_extensions/computo/partials/pdf/before-body.tex: -------------------------------------------------------------------------------- 1 | \definecolor{computo-blue}{HTML}{034E79} 2 | 3 | \begin{tikzpicture}[remember picture,overlay] 4 | \fill[computo-blue] 5 | (current page.north west) -- (current page.north east) -- 6 | ([yshift=-5cm]current page.north east|-current page.north east) -- 7 | ([yshift=-5cm]current page.north west|-current page.north west) -- cycle; 8 | \node[anchor=north west, xshift=.75cm, 9 | yshift=-.75cm] at (current page.north west) {\includegraphics[height=3cm]{logo_text_white}}; 10 | \node[font=\sffamily\bfseries\color{white},anchor=north west, xshift=.75cm, 11 | yshift=-4.25cm] at (current page.north 12 | west) {\fontsize{10}{12}\selectfont ISSN 2824-7795}; 13 | \node[font=\sffamily\bfseries\color{white},anchor=west, 14 | xshift=4.25cm,yshift=-2.75cm] at (current page.north west) 15 | {\begin{minipage}{15cm} 16 | \fontsize{25}{30}\selectfont 17 | $title$ 18 | \vspace{.5cm} 19 | \\ 20 | \fontsize{15}{18}\selectfont 21 | $subtitle$ 22 | \end{minipage}}; 23 | \end{tikzpicture} 24 | 25 | \vspace*{2.5cm} 26 | \begin{center} 27 | $for(by-author)$ 28 | $if(by-author.name.literal)$$by-author.name.literal$$endif$$if(it.orcid)$~\orcidlink{$it.orcid$}$endif$$if(it.attributes.corresponding)$\footnote{Corresponding author: $if(it.email)$\href{mailto:$it.email$}{$it.email$}$endif$}$endif$\quad 29 | $for(by-author.affiliations)$ 30 | $if(it.department)$$it.department$$if(it.name)$, $endif$$endif$$if(it.name)$$it.name$$endif$$if(it.department)$\\$elseif(it.name)$\\$endif$ 31 | $endfor$ 32 | $endfor$ 33 | 34 | \bigskip 35 | 36 | Date published: $date$ \quad Last modified: $date-modified$ 37 | \end{center} 38 | 39 | \bigskip 40 | \begin{abstract} 41 | $abstract$ 42 | \end{abstract} 43 | 44 | $if(keywords)$ 45 | \noindent% 46 | {\it Keywords:} $for(keywords)$$it$$sep$, $endfor$ 47 | \vfill 48 | $endif$ 49 | 50 | $if(draft)$ 51 | \linenumbers 52 | \DraftwatermarkOptions{stamp=true, colorspec=0.9, text={\rm submitted}} 53 | $else$ 54 | \DraftwatermarkOptions{stamp=false} 55 | $endif$ 56 | 57 | -------------------------------------------------------------------------------- /_extensions/computo/partials/pdf/include-in-header.tex: -------------------------------------------------------------------------------- 1 | \usepackage{tikz} 2 | \usepackage{xcolor} 3 | \usepackage{tabularx} 4 | \usepackage{orcidlink} 5 | \usepackage{lineno} 6 | \usepackage{libertinus} 7 | \usepackage{draftwatermark} 8 | 9 | -------------------------------------------------------------------------------- /_extensions/computo/shortcodes.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This file defines the shortcodes that your extension will make available 3 | https://quarto.org/docs/authoring/shortcodes.html#custom-shortcodes 4 | Quarto exports utils function that can be used in all filters. See 5 | https://github.com/quarto-dev/quarto-cli/blob/main/src/resources/pandoc/datadir/init.lua#L1522-L1576 6 | ]]-- 7 | 8 | -- Example shortcode that provides a nicely formatted 'LaTeX' string 9 | function latex() 10 | if quarto.doc.isFormat("pdf") then 11 | return pandoc.RawBlock('tex', '{\\LaTeX}') 12 | elseif quarto.doc.isFormat("html") then 13 | return pandoc.Math('InlineMath', "\\LaTeX") 14 | else 15 | return pandoc.Span('LaTeX') 16 | end 17 | end -------------------------------------------------------------------------------- /_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | title: "computo-quarto-extension" 3 | render: 4 | - computo-quarto-extension.qmd 5 | resources: 6 | - environment.yml 7 | 8 | title: "Computo Journal Format" 9 | subtitle: "To be used as template for contribution to Computo" 10 | date: 01/02/2023 11 | date-modified: last-modified 12 | author: 13 | - name: The Computo Team 14 | corresponding: true 15 | email: computo@sfds.asso.fr 16 | url: https://computo.sfds.asso.fr 17 | orcid: 0000-0000-0000-0000 18 | affiliations: 19 | - name: Société Française de Statistique 20 | department: Statistique 21 | address: IHP 22 | city: Paris 23 | country: France 24 | postal-code: 75005 25 | - name: a friend 26 | affiliations: 27 | - Another Affiliation 28 | description: | 29 | This document provides a template based on the quarto system for contributions to Computo. The github repository in itself provides a specific quarto extension useful for authors (and editors!). 30 | keywords: [template, quarto, R, Python, reproductibility] 31 | doi: 10.xxxx/xxx-xxx 32 | citation: 33 | type: article-journal 34 | container-title: "Computo" 35 | doi: "10.xxxx/xxx-xxx" 36 | url: "https://github.com/computorg/computo-quarto-extension" 37 | issn: "2824-7795" 38 | abstract: | 39 | This is the abstract - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vestibulum facilisis. Aenean pretium orci augue, quis lobortis libero accumsan eu. Nam mollis lorem sit amet pellentesque ullamcorper. Curabitur lobortis libero eget malesuada vestibulum. Nam nec nibh massa. Pellentesque porttitor cursus tellus. Mauris urna erat, rhoncus sed faucibus sit amet, venenatis eu ipsum. 40 | bibliography: references.bib 41 | google-scholar: true 42 | github-user: computorg 43 | repo: "computo-quarto-extension" 44 | draft: true # set to false once the build is running 45 | published: false # will be set to true once accepted 46 | format: 47 | computo-html: default 48 | computo-pdf: default 49 | 50 | diagram: 51 | engine: 52 | tikz: 53 | header-includes: 54 | - '\usetikzlibrary{arrows}' 55 | 56 | -------------------------------------------------------------------------------- /computo-quarto-extension.qmd: -------------------------------------------------------------------------------- 1 | # About this document 2 | 3 | This document provides a template based on the [quarto system](https://quarto.org/) for contributions to **Computo** [@computo]. We show how `Python` [@perez2011python] or `R` [@R-base] code can be included. 4 | 5 | # Formatting 6 | 7 | This section covers basic formatting guidelines. [Quarto](https://quarto.org/) is a versatile formatting system for authoring HTML based on markdown, integrating {{< latex >}} and various code block interpreted either via Jupyter or Knitr (and thus deal with Python, R and many other langages). It relies on the [Pandoc Markdown](https://rmarkdown.rstudio.com/authoring_pandoc_markdown.html) markup language. 8 | 9 | :::{.callout-note} 10 | We will only give some formatting elements. Authors can refer to the [Quarto web page](https://quarto.org/) for a complete view of the formatting possibilities. 11 | ::: 12 | 13 | :::{.callout-note} 14 | Quarto itself is a work-in-progress and a lot of bugs are constantly fixed or features added. As such, we recommend: 15 | 16 | - searching any encountered issue with renders in [the upstream quarto dev repo](https://github.com/quarto-dev/quarto-cli/issues) 17 | - using a [version of quarto > 1.2](https://github.com/quarto-dev/quarto-cli/releases) 18 | ::: 19 | 20 | To render/compile a document, run `quarto render`. A document will be generated that includes both content as well as the output of any embedded code chunks within the document: 21 | 22 | ``` .bash 23 | quarto render content.qmd # will render to html 24 | ``` 25 | 26 | ## Basic markdown formatting 27 | 28 | **Bold text** or _italic_ 29 | 30 | - This is a list 31 | - With more elements 32 | - It isn't numbered. 33 | 34 | But we can also do a numbered list 35 | 36 | 1. This is my first item 37 | 2. This is my second item 38 | 3. This is my third item 39 | 40 | ## Mathematics 41 | 42 | ### Mathematical formulae 43 | 44 | [{{< latex >}}](https://www.latex-project.org/) code is natively supported[^katex], which makes it possible to use mathematical formulae: 45 | 46 | [^katex]: We use [katex](https://katex.org/) for this purpose. 47 | 48 | will render 49 | 50 | $$ 51 | f(x_1, \dots, x_n; \mu, \sigma^2) = 52 | \frac{1}{\sigma \sqrt{2\pi}} \exp{\left(- \frac{1}{2\sigma^2}\sum_{i=1}^n(x_i - \mu)^2\right)} 53 | $$ 54 | 55 | It is also possible to cross-reference an equation, see @eq-mylabel: 56 | 57 | $$ 58 | \begin{aligned} 59 | D_{x_N} & = \frac12 60 | \left[\begin{array}{cc} 61 | x_L^\top & x_N^\top \end{array}\right] \, 62 | \left[\begin{array}{cc} L_L & B \\ B^\top & L_N \end{array}\right] \, 63 | \left[\begin{array}{c} 64 | x_L \\ x_N \end{array}\right] \\ 65 | & = \frac12 (x_L^\top L_L x_L + 2 x_N^\top B^\top x_L + x_N^\top L_N x_N), 66 | \end{aligned} 67 | $$ {#eq-mylabel} 68 | 69 | ### Theorems and other amsthem-like environments 70 | 71 | Quarto includes a nice support for theorems, with predefined prefix labels for theorems, lemmas, proposition, etc. see [this page](https://quarto.org/docs/authoring/cross-references.html#theorems-and-proofs). Here is a simple example: 72 | 73 | ::: {#thm-slln} 74 | ### Strong law of large numbers 75 | 76 | The sample average converges almost surely to the expected value: 77 | 78 | $$\overline{X}_n\ \xrightarrow{\text{a.s.}}\ \mu \qquad\textrm{when}\ n \to \infty.$$ 79 | ::: 80 | 81 | See @thm-slln. 82 | 83 | ## Code 84 | 85 | Quarto uses either Jupyter or knitr to render code chunks. This can be triggered in the yaml header, e.g., for Jupyter (should be installed on your computer) use 86 | 87 | ``` yaml 88 | --- 89 | title: "My Document" 90 | author "Jane Doe" 91 | jupyter: python3 92 | --- 93 | ``` 94 | 95 | For knitr (R + knitr must be installed on your computer) 96 | 97 | ``` yaml 98 | --- 99 | title: "My Document" 100 | author "Jane Doe" 101 | --- 102 | ``` 103 | 104 | You can use Jupyter for Python code and more. And R + KnitR for if you want to mix R with Python (via the package reticulate @R-reticulate). 105 | 106 | ### R 107 | 108 | `R` code [@R-base] chunks may be embedded as follows: 109 | 110 | ```{r r-code, echo=TRUE} 111 | x <- rnorm(10) 112 | ``` 113 | 114 | 115 | ### Python 116 | 117 | ```{python} 118 | #| label: fig-plotly 119 | #| fig-cap: "A simple python plotly example" 120 | import plotly.express as px 121 | df = px.data.tips() 122 | fig = px.histogram(df, x="total_bill", y="tip", color="sex", 123 | marginal="box", # or violin, rug 124 | hover_data=df.columns) 125 | fig 126 | ``` 127 | 128 | ## Figures 129 | 130 | Plots can be generated as follows and referenced. See plot @fig-gg: 131 | 132 | ```{r} 133 | #| label: fig-gg 134 | #| fig-cap: "A simple ggplot example" 135 | #| message: false 136 | library("ggplot2") 137 | p <- ggplot(mpg, aes(displ, hwy)) + 138 | geom_point() + 139 | geom_smooth() + theme_bw() 140 | p 141 | ``` 142 | 143 | Interactive plots may also be produced in the HTML output of the document^[The pdf output is just a screenshot of the interactive plot from the html output]: 144 | 145 | ```{r} 146 | #| label: fig-ggplotly 147 | #| fig-cap: "A simple ggplotly interactive example" 148 | #| message: false 149 | #| warning: false 150 | library("plotly") 151 | ggplotly(p) 152 | ``` 153 | 154 | It is also possible to create figures from static images: 155 | 156 | :::{#fig-logo} 157 | 158 | ![](figures/sfds.png) 159 | 160 | SFdS logo (c.a. 2021) 161 | 162 | ::: 163 | 164 | ## Tables 165 | 166 | ### Markdown syntax 167 | 168 | Tables (with label: `@tbl-mylabel` renders @tbl-mylabel) can be generated with markdown as follows 169 | 170 | ```markdown 171 | | Tables | Are | Cool | 172 | |----------|:-------------:|------:| 173 | | col 1 is | left-aligned | $1600 | 174 | | col 2 is | centered | $12 | 175 | | col 3 is | right-aligned | $1 | 176 | : my table caption {#tbl-mylabel} 177 | ``` 178 | 179 | | Tables | Are | Cool | 180 | |----------|:-------------:|------:| 181 | | col 1 is | left-aligned | $1600 | 182 | | col 2 is | centered | $12 | 183 | | col 3 is | right-aligned | $1 | 184 | : my table caption {#tbl-mylabel} 185 | 186 | ### List-table filter 187 | 188 | We also integrate the [list tables](https://github.com/pandoc-ext/list-table) filter from Pandoc, so that you may alternatively use this format , easier to write and maintain: 189 | 190 | ```markdown 191 | :::list-table 192 | * - row 1, column 1 193 | - row 1, column 2 194 | - row 1, column 3 195 | 196 | * - row 2, column 1 197 | - 198 | - row 2, column 3 199 | 200 | * - row 3, column 1 201 | - row 3, column 2 202 | ::: 203 | ``` 204 | 205 | :::list-table 206 | * - row 1, column 1 207 | - row 1, column 2 208 | - row 1, column 3 209 | 210 | * - row 2, column 1 211 | - 212 | - row 2, column 3 213 | 214 | * - row 3, column 1 215 | - row 3, column 2 216 | ::: 217 | 218 | ### Table generated from code 219 | 220 | Table can also be generated by some code, for instance with ```knitr``` here: 221 | 222 | ```{r cars} 223 | knitr::kable(summary(cars), caption = "Table caption.") 224 | ``` 225 | 226 | ## Algorithms 227 | 228 | A solution to typeset pseudocode just like you would do with {{< latex >}}, yet with HTML output is to rely on the JavaScript [pseudocode.js](https://github.com/SaswatPadhi/pseudocode.js). Your pseudocode is written inside a [Code Block](https://quarto.org/docs/authoring/markdown-basics.html#source-code) with the `pseudocode` class. Do not forget the class tag, that will trigger the rendering process of your pseudo-code. The result is as follows^[For proper pdf rendering, use [Camel cased](https://en.wikipedia.org/wiki/Camel_case) names for all `algorithmic` keywords, not upper case ones, like the examples in `pseudocode.js`’s documentation, which are not compatible with LaTeX.]: 229 | 230 | ````markdown 231 | ```pseudocode 232 | #| label: alg-quicksort 233 | #| html-indent-size: "1.2em" 234 | #| html-comment-delimiter: "//" 235 | #| html-line-number: true 236 | #| html-line-number-punc: ":" 237 | #| html-no-end: false 238 | #| pdf-placement: "htb!" 239 | #| pdf-line-number: true 240 | 241 | \begin{algorithm} 242 | \caption{Quicksort} 243 | \begin{algorithmic} 244 | \Procedure{Quicksort}{$A, p, r$} 245 | \If{$p < r$} 246 | \State $q = $ \Call{Partition}{$A, p, r$} 247 | \State \Call{Quicksort}{$A, p, q - 1$} 248 | \State \Call{Quicksort}{$A, q + 1, r$} 249 | \EndIf 250 | \EndProcedure 251 | \Procedure{Partition}{$A, p, r$} 252 | \State $x = A[r]$ 253 | \State $i = p - 1$ 254 | \For{$j = p, \dots, r - 1$} 255 | \If{$A[j] < x$} 256 | \State $i = i + 1$ 257 | \State exchange 258 | $A[i]$ with $A[j]$ 259 | \EndIf 260 | \State exchange $A[i]$ with $A[r]$ 261 | \EndFor 262 | \EndProcedure 263 | \end{algorithmic} 264 | \end{algorithm} 265 | ``` 266 | ```` 267 | 268 | ```pseudocode 269 | #| label: alg-quicksort 270 | #| html-indent-size: "1.2em" 271 | #| html-comment-delimiter: "//" 272 | #| html-line-number: true 273 | #| html-line-number-punc: ":" 274 | #| html-no-end: false 275 | #| pdf-placement: "htb!" 276 | #| pdf-line-number: true 277 | 278 | \begin{algorithm} 279 | \caption{Quicksort} 280 | \begin{algorithmic} 281 | \Procedure{Quicksort}{$A, p, r$} 282 | \If{$p < r$} 283 | \State $q = $ \Call{Partition}{$A, p, r$} 284 | \State \Call{Quicksort}{$A, p, q - 1$} 285 | \State \Call{Quicksort}{$A, q + 1, r$} 286 | \EndIf 287 | \EndProcedure 288 | \Procedure{Partition}{$A, p, r$} 289 | \State $x = A[r]$ 290 | \State $i = p - 1$ 291 | \For{$j = p, \dots, r - 1$} 292 | \If{$A[j] < x$} 293 | \State $i = i + 1$ 294 | \State exchange 295 | $A[i]$ with $A[j]$ 296 | \EndIf 297 | \State exchange $A[i]$ with $A[r]$ 298 | \EndFor 299 | \EndProcedure 300 | \end{algorithmic} 301 | \end{algorithm} 302 | ``` 303 | 304 | @alg-quicksort is extracted from Chapter 7, Introduction to Algorithms (3rd edition). 305 | 306 | ## Diagrams 307 | 308 | In addition of [quarto supported diagrams](https://quarto.org/docs/authoring/diagrams.html), we also support [tikz](https://www.overleaf.com/learn/latex/TikZ_package) diagrams. The following example^[This is the new syntax for cross-references since quarto 1.4, see [Crossreferenceable elements](https://quarto.org/docs/prerelease/1.4/crossref.html)] is rendered as follows. 309 | 310 | ````markdown 311 | :::{#fig-tikz} 312 | 313 | ``` tikz 314 | %%| filename: ../figure-tikz/fig-tikz 315 | \begin{tikzpicture}[node distance=2cm, auto, thick, scale=2, every node/.style={transform shape}] 316 | \node (P) {$P$}; 317 | \node (B) [right of=P] {$B$}; 318 | \node (A) [below of=P] {$A$}; 319 | \node (C) [below of=B] {$C$}; 320 | \node (P1) [node distance=1.4cm, left of=P, above of=P] {$\hat{P}$}; 321 | \draw[->] (P) to node {$f$} (B); 322 | \draw[->] (P) to node [swap] {$g$} (A); 323 | \draw[->] (A) to node [swap] {$f$} (C); 324 | \draw[->] (B) to node {$g$} (C); 325 | \draw[->, bend right] (P1) to node [swap] {$\hat{g}$} (A); 326 | \draw[->, bend left] (P1) to node {$\hat{f}$} (B); 327 | \draw[->, dashed] (P1) to node {$k$} (P); 328 | \end{tikzpicture} 329 | ``` 330 | 331 | A simple example of a commutative diagram with $\texttt{tikz}$. 332 | 333 | ::: 334 | ```` 335 | 336 | :::{#fig-tikz} 337 | 338 | ``` tikz 339 | %%| filename: ../figure-tikz/fig-tikz 340 | \begin{tikzpicture}[node distance=2cm, auto, thick, scale=2, every node/.style={transform shape}] 341 | \node (P) {$P$}; 342 | \node (B) [right of=P] {$B$}; 343 | \node (A) [below of=P] {$A$}; 344 | \node (C) [below of=B] {$C$}; 345 | \node (P1) [node distance=1.4cm, left of=P, above of=P] {$\hat{P}$}; 346 | \draw[->] (P) to node {$f$} (B); 347 | \draw[->] (P) to node [swap] {$g$} (A); 348 | \draw[->] (A) to node [swap] {$f$} (C); 349 | \draw[->] (B) to node {$g$} (C); 350 | \draw[->, bend right] (P1) to node [swap] {$\hat{g}$} (A); 351 | \draw[->, bend left] (P1) to node {$\hat{f}$} (B); 352 | \draw[->, dashed] (P1) to node {$k$} (P); 353 | \end{tikzpicture} 354 | ``` 355 | 356 | A simple example of a commutative diagram with $\texttt{tikz}$. 357 | 358 | ::: 359 | 360 | You may refer to it as @fig-tikz. 361 | 362 | ## Handling references {#sec-references} 363 | 364 | ### Bibliographic references 365 | 366 | References are displayed as footnotes using [BibTeX](http://www.bibtex.org/), e.g. `[@computo]` will be displayed 367 | as [@computo], where `computo` is the bibtex key for this specific entry. The bibliographic information is automatically retrieved from 368 | the `.bib` file specified in the header of this document (here:`references.bib`). 369 | 370 | ### Other cross-references 371 | 372 | As already (partially) seen, Quarto includes a mecanism similar to the bibliographic references for sections, equations, theorems, figures, 373 | lists, etc. Have a look at [this page](https://quarto.org/docs/authoring/cross-references.html). 374 | 375 | ## To go further 376 | 377 | :::{.callout-note} 378 | ### One last note 379 | To go into more involved details, you can also simply check the source code of this document (button at the top), or have a look at the source of our [t-sne remake example](https://computo.sfds.asso.fr/published-paper-tsne/). 380 | ::: 381 | 382 | 383 | ## Bibliography {.unnumbered} 384 | 385 | ::: {#refs} 386 | ::: -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: computo-quarto-extension 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python=3.12 6 | - jupyter 7 | - plotly 8 | - numpy 9 | - pandas 10 | - python-kaleido 11 | 12 | -------------------------------------------------------------------------------- /figures/sfds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/computorg/computo-quarto-extension/d82f4fdea3376e12dff441b1f7d767e4cb972ff4/figures/sfds.png -------------------------------------------------------------------------------- /references.bib: -------------------------------------------------------------------------------- 1 | @article{computo, 2 | title = {Computo: reproducible computational/algorithmic contributions in statistics and machine learning}, 3 | author = {{Computo Team}}, 4 | year = {2021}, 5 | journal = {computo} 6 | } 7 | 8 | @Manual{R-base, 9 | title = {R: A Language and Environment for Statistical Computing}, 10 | author = {{R Core Team}}, 11 | organization = {R Foundation for Statistical Computing}, 12 | address = {Vienna, Austria}, 13 | year = {2020}, 14 | url = {https://www.R-project.org/}, 15 | } 16 | 17 | @Manual{R-reticulate, 18 | title = {reticulate: Interface to Python}, 19 | author = {Kevin Ushey and JJ Allaire and Yuan Tang}, 20 | year = {2020}, 21 | note = {R package version 1.18}, 22 | url = {https://github.com/rstudio/reticulate}, 23 | } 24 | 25 | @article{perez2011python, 26 | title = {Python: an ecosystem for scientific computing}, 27 | author = {Perez, Fernando and Granger, Brian E and Hunter, John D}, 28 | journal = {Computing in Science \\& Engineering}, 29 | volume = {13}, 30 | number = {2}, 31 | pages = {13--21}, 32 | year = {2011}, 33 | publisher = {AIP Publishing} 34 | } 35 | 36 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | local/ 3 | cellar/ 4 | lock/ 5 | python/ 6 | sandbox/ 7 | staging/ 8 | activate.R 9 | settings.json -------------------------------------------------------------------------------- /setup-env-ci.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get install -y libcurl4-openssl-dev libpng-dev 2 | -------------------------------------------------------------------------------- /setup-render-ci.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get install -y inkscape 2 | quarto install chromium 3 | mkdir -p ~/.local/bin 4 | ~/.TinyTeX/bin/x86_64-linux/tlmgr option sys_bin ~/.local/bin 5 | ~/.TinyTeX/bin/x86_64-linux/tlmgr path add 6 | ~/.TinyTeX/bin/x86_64-linux/tlmgr update --self --------------------------------------------------------------------------------