diff options
| author | John MacFarlane <jgm@berkeley.edu> | 2023-03-26 13:37:08 -0700 |
|---|---|---|
| committer | John MacFarlane <jgm@berkeley.edu> | 2023-03-26 19:02:05 -0700 |
| commit | cd6b3bde622c93eea763e981692b4bc53dd5e5df (patch) | |
| tree | 78659dda4e5c40ef4ab62e8ad3949165da442c9d | |
| parent | 657ebffd8f4efe334e27e7398700c5b92e453363 (diff) | |
Typst writer improvements.
+ Fix non-decimal enumerated lists.
+ Fix endnotes ending with code blocks.
+ Improve default template to use a typst template.
+ Factor out definitions and typst template into partials.
+ Properly escape backslash and quote inside double quotes.
+ Update tests.
| -rw-r--r-- | MANUAL.txt | 2 | ||||
| -rw-r--r-- | data/templates/default.typst | 90 | ||||
| -rw-r--r-- | data/templates/definitions.typst | 18 | ||||
| -rw-r--r-- | data/templates/template.typst | 73 | ||||
| -rw-r--r-- | pandoc.cabal | 2 | ||||
| -rw-r--r-- | src/Text/Pandoc/Writers/Typst.hs | 54 | ||||
| -rw-r--r-- | test/writer.typst | 241 |
7 files changed, 347 insertions, 133 deletions
diff --git a/MANUAL.txt b/MANUAL.txt index 309204a6a..13c72c9f3 100644 --- a/MANUAL.txt +++ b/MANUAL.txt @@ -481,7 +481,7 @@ header when requesting a document from a URL: [OPML]: http://dev.opml.org/spec2.html [OpenDocument]: http://opendocument.xml.org [ODT]: https://en.wikipedia.org/wiki/OpenDocument -[Textile]: https://www.promptworks.com/textile +[Textile]: https://textile-lang.com [MediaWiki markup]: https://www.mediawiki.org/wiki/Help:Formatting [DokuWiki markup]: https://www.dokuwiki.org/dokuwiki [ZimWiki markup]: https://zim-wiki.org/manual/Help/Wiki_Syntax.html diff --git a/data/templates/default.typst b/data/templates/default.typst index c6066dd24..11e7b1211 100644 --- a/data/templates/default.typst +++ b/data/templates/default.typst @@ -1,59 +1,55 @@ -#set page( +$definitions.typst()$ + +$if(template)$ +#import "$template$": conf +$else$ +$template.typst()$ +$endif$ + +#show: doc => conf( +$if(title)$ + title: [$title$], +$endif$ +$if(author)$ + authors: ( +$for(author)$ +$if(author.name)$ + ( name: [$author.name$], + affiliation: [$author.affiliation$], + email: [$author.email$] ), +$else$ + ( name: [$author$], + affiliation: [], + email: [] ), +$endif$ +$endfor$ + ), +$endif$ +$if(date)$ + date: [$date$], +$endif$ +$if(abstract)$ + abstract: [$abstract$], +$endif$ +$if(margin)$ + margin: ($for(margin/pairs)$$margin.key$: $margin.value$,$endfor$), +$endif$ $if(papersize)$ paper: "$papersize$", $endif$ - numbering: "1" -) -#set par(justify: true) -#set text( -$if(lang)$ - lang: "$lang$", -$endif$ $if(mainfont)$ - font: "$mainfont$", + font: ("$mainfont$",), $endif$ $if(fontsize)$ - size: $fontsize$, + fontsize: $fontsize$, $endif$ -) -#set heading( -$if(numbering)$ - numbering: "$numbering$" +$if(section-numbering)$ + sectionnumbering: "$section-numbering$", $endif$ + cols: $if(columns)$$columns$$else$1$endif$, + doc, ) -#align(center)[#block(inset: 2em)[ - #text(weight: "bold", size: 18pt)[$title$] \ -$for(author)$ - $author$ \ -$endfor$ -$if(date)$ - $date$ -$endif$ -]] - -#let definition(term, ..defs) = [ - #strong(term) \ - #(defs.pos().join("\n")) -] - -#let blockquote(body) = [ - #set text( size: 0.92em ) - #block(inset: (left: 1.5em, top: 0.2em, bottom: 0.2em))[#body] -] - -#let horizontalrule = [ - #line(start: (25%,0%), end: (75%,0%)) -] - -#let endnote(num, contents) = [ - #stack(dir: ltr, spacing: 3pt, super[#num], contents) -] - -$if(columns)$ -#show: doc => columns($columns$, doc) -$endif$ - $for(header-includes)$ $header-includes$ @@ -71,6 +67,7 @@ $endif$ $body$ +$if(notes)$ #v(1em) #block[ #horizontalrule @@ -79,6 +76,7 @@ $body$ $notes$ ] +$endif$ $if(bibliographystyle)$ #set bibliography(style: "$bibliographystyle$") diff --git a/data/templates/definitions.typst b/data/templates/definitions.typst new file mode 100644 index 000000000..02fe80f38 --- /dev/null +++ b/data/templates/definitions.typst @@ -0,0 +1,18 @@ +// Some definitions presupposed by pandoc's typst output. +#let definition(term, ..defs) = [ + #strong(term) \ + #(defs.pos().join("\n")) +] + +#let blockquote(body) = [ + #set text( size: 0.92em ) + #block(inset: (left: 1.5em, top: 0.2em, bottom: 0.2em))[#body] +] + +#let horizontalrule = [ + #line(start: (25%,0%), end: (75%,0%)) +] + +#let endnote(num, contents) = [ + #stack(dir: ltr, spacing: 3pt, super[#num], contents) +] diff --git a/data/templates/template.typst b/data/templates/template.typst new file mode 100644 index 000000000..29edca969 --- /dev/null +++ b/data/templates/template.typst @@ -0,0 +1,73 @@ +#let conf( + title: none, + authors: none, + date: none, + abstract: none, + cols: 1, + margin: (x: 1.25in, y: 1.25in), + paper: "us-letter", + lang: none, + font: none, + fontsize: 11pt, + sectionnumbering: none, + doc, +) = { + set page( + paper: paper, + margin: margin, + numbering: "1", + ) + set par(justify: true) + if lang != none { + set text(lang: lang) + } + if font != none { + set text(font: font) + } + if fontsize != none { + set text(size: fontsize) + } + if sectionnumbering != none { + set heading(numbering: sectionnumbering) + } + + if title != none { + align(center)[#block(inset: 2em)[ + #text(weight: "bold", size: 1.5em)[#title] + ]] + } + + if authors != none { + let count = authors.len() + let ncols = calc.min(count, 3) + grid( + columns: (1fr,) * ncols, + row-gutter: 1.5em, + ..authors.map(author => + align(center)[ + #author.name \ + #author.affiliation \ + #author.email + ] + ) + ) + } + + if date != none { + align(center)[#block(inset: 1em)[ + #date + ]] + } + + if abstract != none { + block(inset: 2em)[ + #text(weight: "semibold")[Abstract] #h(1em) #abstract + ] + } + + if cols == 1 { + doc + } else { + columns(cols, doc) + } +} diff --git a/pandoc.cabal b/pandoc.cabal index 6061eec13..044164838 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -96,6 +96,8 @@ data-files: data/templates/affiliations.jats data/templates/default.markua data/templates/default.typst + data/templates/definitions.typst + data/templates/template.typst -- translations data/translations/*.yaml -- entities diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs index bcecda035..12364ce37 100644 --- a/src/Text/Pandoc/Writers/Typst.hs +++ b/src/Text/Pandoc/Writers/Typst.hs @@ -28,14 +28,14 @@ import Data.Text (Text) import Data.List (intercalate, intersperse) import qualified Data.Text as T import Control.Monad.State ( StateT, evalStateT, gets, modify ) -import Text.Pandoc.Writers.Shared ( metaToContext, defField, toLegacyTable ) +import Text.Pandoc.Writers.Shared ( metaToContext, defField, setField, + toLegacyTable, lookupMetaString ) import Text.Pandoc.Shared (isTightList, orderedListMarkers) import Text.Pandoc.Writers.Math (convertMath) import qualified Text.TeXMath as TM import Text.DocLayout import Text.DocTemplates (renderTemplate) import Text.Pandoc.Extensions (Extension(..)) -import Control.Monad (zipWithM) -- | Convert Pandoc to Typst. writeTypst :: PandocMonad m => WriterOptions -> Pandoc -> m Text @@ -66,11 +66,14 @@ pandocToTypst options (Pandoc meta blocks) = do let notes = vsep $ zipWith (\(num :: Int) cont -> "#endnote" <> parens (brackets (text (show num)) - <> ", " <> brackets (chomp cont))) + <> ", " <> brackets (chomp cont <> cr))) [1..] noteContents let context = defField "body" main $ defField "notes" notes $ defField "toc" (writerTableOfContents options) + $ (case lookupMetaString "lang" meta of + "" -> id + lang -> setField "lang" $ T.takeWhile (/='-') lang) $ (if writerNumberSections options then defField "numbering" ("1.1.1.1.1" :: Text) else id) @@ -116,12 +119,27 @@ blockToTypst block = HorizontalRule -> return $ blankline <> "#horizontalrule" <> blankline OrderedList attribs items -> do - items' <- zipWithM (\marker item -> - chomp <$> listItemToTypst 3 (literal marker) item) - (orderedListMarkers attribs) items - return $ (if isTightList items + let addBlock = case attribs of + (1, DefaultStyle, DefaultDelim) -> id + (1, Decimal, Period) -> id + (start, sty, delim) -> \x -> + "#block[" $$ + ("#set enum" <> + parens ( + "numbering: " <> + doubleQuoted + (head (orderedListMarkers + (1, sty, delim))) <> + ", start: " <> + text (show start) )) $$ + x $$ + "]" + items' <- mapM (fmap chomp . listItemToTypst 2 ("+ ")) items + return $ addBlock + (if isTightList items then vcat items' - else vsep items') $$ blankline + else vsep items') + $$ blankline BulletList items -> do items' <- mapM (fmap chomp . listItemToTypst 2 "- ") items return $ (if isTightList items @@ -208,9 +226,9 @@ inlineToTypst inline = DisplayMath -> return $ "$ " <> literal r <> " $" Code (_,cls,_) code -> return $ case cls of - (lang:_) -> "#raw(lang=" <> doubleQuotes (literal lang) <> - ", " <> doubleQuotes (literal code) <> ")" - _ | T.any (=='`') code -> "#raw(" <> doubleQuotes (literal code) <> ")" + (lang:_) -> "#raw(lang=" <> doubleQuoted lang <> + ", " <> doubleQuoted code <> ")" + _ | T.any (=='`') code -> "#raw(" <> doubleQuoted code <> ")" | otherwise -> "`" <> literal code <> "`" RawInline fmt str -> case fmt of @@ -238,11 +256,11 @@ inlineToTypst inline = then return $ -- Note: this loses locators, prefix, suffix "#cite" <> parens (mconcat $ intersperse ", " $ - map (doubleQuotes . literal . citationId) citations) + map (doubleQuoted . citationId) citations) else inlinesToTypst inlines Link _attrs inlines (src,_tit) -> do contents <- inlinesToTypst inlines - return $ "#link" <> parens (doubleQuotes (literal src)) <> + return $ "#link" <> parens (doubleQuoted src) <> if render Nothing contents == src then mempty else nowrap $ brackets contents @@ -250,7 +268,7 @@ inlineToTypst inline = let width' = maybe mempty ((", width: " <>) . literal) $ lookup "width" kvs let height' = maybe mempty ((", height: " <>) . literal) $ lookup "height" kvs - return $ "#image(" <> doubleQuotes (literal src) <> width' <> height' <> ")" + return $ "#image(" <> doubleQuoted src <> width' <> height' <> ")" Note blocks -> do -- currently typst has no footnotes! -- TODO create endnotes with manual typesetting contents <- blocksToTypst blocks @@ -291,3 +309,11 @@ toLabel ident = if T.null ident then mempty else "#label" <> parens (doubleQuotes (literal ident)) + +doubleQuoted :: Text -> Doc Text +doubleQuoted = doubleQuotes . literal . escape + where + escape = T.concatMap escapeChar + escapeChar '\\' = "\\\\" + escapeChar '"' = "\\\"" + escapeChar c = T.singleton c diff --git a/test/writer.typst b/test/writer.typst index aa14a59a6..130c53f59 100644 --- a/test/writer.typst +++ b/test/writer.typst @@ -1,19 +1,4 @@ -#set page( - numbering: "1" -) -#set par(justify: true) -#set text( -) -#set heading( -) - -#align(center)[#block(inset: 2em)[ - #text(weight: "bold", size: 18pt)[Pandoc Test Suite] \ - John MacFarlane \ - Anonymous \ - July 17, 2006 -]] - +// Some definitions presupposed by pandoc's typst output. #let definition(term, ..defs) = [ #strong(term) \ #(defs.pos().join("\n")) @@ -31,7 +16,93 @@ #let endnote(num, contents) = [ #stack(dir: ltr, spacing: 3pt, super[#num], contents) ] - +#let conf( + title: none, + authors: none, + date: none, + abstract: none, + cols: 1, + margin: (x: 1.25in, y: 1.25in), + paper: "us-letter", + lang: none, + font: none, + fontsize: 11pt, + sectionnumbering: none, + doc, +) = { + set page( + paper: paper, + margin: margin, + numbering: "1", + ) + set par(justify: true) + if lang != none { + set text(lang: lang) + } + if font != none { + set text(font: font) + } + if fontsize != none { + set text(size: fontsize) + } + if sectionnumbering != none { + set heading(numbering: sectionnumbering) + } + + if title != none { + align(center)[#block(inset: 2em)[ + #text(weight: "bold", size: 1.5em)[#title] + ]] + } + + if authors != none { + let count = authors.len() + let ncols = calc.min(count, 3) + grid( + columns: (1fr,) * ncols, + row-gutter: 1.5em, + ..authors.map(author => + align(center)[ + #author.name \ + #author.affiliation \ + #author.email + ] + ) + ) + } + + if date != none { + align(center)[#block(inset: 1em)[ + #date + ]] + } + + if abstract != none { + block(inset: 2em)[ + #text(weight: "semibold")[Abstract] #h(1em) #abstract + ] + } + + if cols == 1 { + doc + } else { + columns(cols, doc) + } +} +#show: doc => conf( + title: [Pandoc Test Suite], + authors: ( + ( name: [John MacFarlane], + affiliation: [], + email: [] ), + ( name: [Anonymous], + affiliation: [], + email: [] ), + ), + date: [July 17, 2006], + cols: 1, + doc, +) This is a set of tests for pandoc. Most of them are adapted from John Gruber’s @@ -97,8 +168,8 @@ sub status { A list: -1. item one -2. item two ++ item one ++ item two Nested block quotes: @@ -191,41 +262,41 @@ Minuses loose: #label("ordered") Tight: -1. First -2. Second -3. Third ++ First ++ Second ++ Third and: -1. One -2. Two -3. Three ++ One ++ Two ++ Three Loose using tabs: -1. First ++ First -2. Second ++ Second -3. Third ++ Third and using spaces: -1. One ++ One -2. Two ++ Two -3. Three ++ Three Multiple paragraphs: -1. Item 1, graf one. ++ Item 1, graf one. - Item 1. graf two. The quick brown fox jumped over the lazy dog’s back. + Item 1. graf two. The quick brown fox jumped over the lazy dog’s back. -2. Item 2. ++ Item 2. -3. Item 3. ++ Item 3. == Nested #label("nested") @@ -235,24 +306,24 @@ Multiple paragraphs: Here’s another: -1. First -2. Second: - - Fee - - Fie - - Foe -3. Third ++ First ++ Second: + - Fee + - Fie + - Foe ++ Third Same thing but with paragraphs: -1. First ++ First -2. Second: ++ Second: - - Fee - - Fie - - Foe + - Fee + - Fie + - Foe -3. Third ++ Third == Tabs and spaces #label("tabs-and-spaces") @@ -266,29 +337,50 @@ Same thing but with paragraphs: == Fancy list markers #label("fancy-list-markers") -(2) begins with 2 - -(3) and now 3 - - with a continuation - - iv. sublist with roman numerals, starting with 4 - v. more items - (A) a subsublist - (B) a subsublist +#block[ +#set enum(numbering: "(1)", start: 2) ++ begins with 2 + ++ and now 3 + + with a continuation + + #block[ + #set enum(numbering: "i.", start: 4) + + sublist with roman numerals, starting with 4 + + more items + #block[ + #set enum(numbering: "(A)", start: 1) + + a subsublist + + a subsublist + ] + ] +] Nesting: -A. Upper Alpha - I. Upper Roman. - (6) Decimal start with 6 - c) Lower alpha with paren +#block[ +#set enum(numbering: "A.", start: 1) ++ Upper Alpha + #block[ + #set enum(numbering: "I.", start: 1) + + Upper Roman. + #block[ + #set enum(numbering: "(1)", start: 6) + + Decimal start with 6 + #block[ + #set enum(numbering: "a)", start: 3) + + Lower alpha with paren + ] + ] + ] +] Autonumbering: -1. Autonumber. -2. More. - 1. Nested. ++ Autonumber. ++ More. + + Nested. Should not be a list item: @@ -380,8 +472,8 @@ Blank line after term, indented marker, alternate markers: #definition[orange][orange fruit -1. sublist -2. sublist ++ sublist ++ sublist ] @@ -674,7 +766,7 @@ Here is an inline note.#super[3] Notes can go in quotes.#super[4] ] -1. And in list items.#super[5] ++ And in list items.#super[5] This paragraph should not be part of the note, as it is not indented. @@ -685,7 +777,8 @@ This paragraph should not be part of the note, as it is not indented. #v(3pt) // otherwise first note marker is swallowed, bug? #endnote([1], [Here is the footnote. It can go anywhere after the footnote -reference. It need not be placed at the end of the document.]) +reference. It need not be placed at the end of the document. +]) #endnote([2], [Here’s the long note. This one contains multiple blocks. @@ -697,13 +790,17 @@ list items). ``` If you want, you can indent every line, but you can also be lazy and just indent -the first line of each block.]) +the first line of each block. +]) #endnote([3], [This is #emph[easier] to type. Inline notes may contain #link("http://google.com")[links] and `]` verbatim characters, as well as -\[bracketed text\].]) +\[bracketed text\]. +]) -#endnote([4], [In quote.]) +#endnote([4], [In quote. +]) -#endnote([5], [In list.]) +#endnote([5], [In list. +]) ] |
