diff options
| author | John MacFarlane <jgm@berkeley.edu> | 2024-03-20 14:25:08 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-20 14:25:08 -0700 |
| commit | 604f541b0ff2e521ba207991baf43c9550189736 (patch) | |
| tree | bbeece4e30227df45e206008be89072beae9dc94 | |
| parent | ebcae86cf47e3037ccc3c7146cf610e5a7af3b03 (diff) | |
Typst writer: support Typst 0.11 table features. (#9593)
* Typst writer: support Typst 0.11 table features.
- colspans
- rowspans
- cell alignment overrides
- relative column widths
- header and footer
- multiple table bodies with intermediate headers
Row heads are not yet supported.
The default typst template has been modified so that tables
don't have lines by default. As is standard with pandoc, we only
add a line under a header or over a footer. However, a different
default stroke pattern can easily be added in a template.
Closes #9588.
| -rw-r--r-- | data/templates/default.typst | 3 | ||||
| -rw-r--r-- | pandoc.cabal | 1 | ||||
| -rw-r--r-- | src/Text/Pandoc/Writers/Typst.hs | 76 | ||||
| -rw-r--r-- | test/Tests/Old.hs | 2 | ||||
| -rw-r--r-- | test/tables.typst | 130 | ||||
| -rw-r--r-- | test/tables/nordics.typst | 23 | ||||
| -rw-r--r-- | test/tables/planets.typst | 33 | ||||
| -rw-r--r-- | test/tables/students.typst | 26 | ||||
| -rw-r--r-- | test/writer.typst | 3 |
9 files changed, 193 insertions, 104 deletions
diff --git a/data/templates/default.typst b/data/templates/default.typst index 25f105db2..ee4b8212f 100644 --- a/data/templates/default.typst +++ b/data/templates/default.typst @@ -10,7 +10,8 @@ $definitions.typst()$ } #set table( - inset: 6pt + inset: 6pt, + stroke: none ) $if(template)$ diff --git a/pandoc.cabal b/pandoc.cabal index a08cd20ca..27bb87e9f 100644 --- a/pandoc.cabal +++ b/pandoc.cabal @@ -326,6 +326,7 @@ extra-source-files: test/tables/*.html4 test/tables/*.html5 test/tables/*.latex + test/tables/*.typst test/tables/*.native test/tables/*.mediawiki test/tables/*.jats_archiving diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs index 7c804ccd8..245d0ec87 100644 --- a/src/Text/Pandoc/Writers/Typst.hs +++ b/src/Text/Pandoc/Writers/Typst.hs @@ -20,12 +20,12 @@ import Text.Pandoc.Definition import Text.Pandoc.Class ( PandocMonad) import Text.Pandoc.Options ( WriterOptions(..), WrapOption(..), isEnabled ) import Data.Text (Text) -import Data.List (intercalate) +import Data.List (intercalate, intersperse) import Network.URI (unEscapeString) import qualified Data.Text as T import Control.Monad.State ( StateT, evalStateT, gets, modify ) import Text.Pandoc.Writers.Shared ( metaToContext, defField, resetField, - toLegacyTable, lookupMetaString, + lookupMetaString, isOrderedListMarker ) import Text.Pandoc.Shared (isTightList, orderedListMarkers, tshow) import Text.Pandoc.Writers.Math (convertMath) @@ -34,6 +34,7 @@ import Text.DocLayout import Text.DocTemplates (renderTemplate) import Text.Pandoc.Extensions (Extension(..)) import Text.Collate.Lang (Lang(..), parseLang) +import Text.Printf (printf) import Data.Char (isAlphaNum) -- | Convert Pandoc to Typst. @@ -162,33 +163,80 @@ blockToTypst block = else vsep items') $$ blankline DefinitionList items -> ($$ blankline) . vsep <$> mapM defListItemToTypst items - Table (ident,_,_) blkCapt colspecs thead tbodies tfoot -> do - let (caption, aligns, _, headers, rows) = - toLegacyTable blkCapt colspecs thead tbodies tfoot - let numcols = length aligns - headers' <- mapM blocksToTypst headers - rows' <- mapM (mapM blocksToTypst) rows + Table (ident,_,_) (Caption _ caption) colspecs thead tbodies tfoot -> do + let lab = toLabel FreestandingLabel ident capt' <- if null caption then return mempty else do - captcontents <- inlinesToTypst caption + captcontents <- blocksToTypst caption return $ ", caption: " <> brackets captcontents - let lab = toLabel FreestandingLabel ident + let numcols = length colspecs + let (aligns, widths) = unzip colspecs + let commaSep = hcat . intersperse ", " + let toPercentage (ColWidth w) = + literal $ (T.dropWhileEnd (== '.') . T.dropWhileEnd (== '0')) + (T.pack (printf "%0.2f" (w * 100))) <> "%" + toPercentage ColWidthDefault = literal "auto" + let columns = if all (== ColWidthDefault) widths + then literal $ tshow numcols + else parens (commaSep (map toPercentage widths)) let formatalign AlignLeft = "left," formatalign AlignRight = "right," formatalign AlignCenter = "center," formatalign AlignDefault = "auto," let alignarray = parens $ mconcat $ map formatalign aligns + let fromCell (Cell _attr alignment rowspan colspan bs) = do + let cellattrs = + (case alignment of + AlignDefault -> [] + AlignLeft -> [ "align: left" ] + AlignRight -> [ "align: right" ] + AlignCenter -> [ "align: center" ]) ++ + (case rowspan of + RowSpan 1 -> [] + RowSpan n -> [ "rowspan: " <> tshow n ]) ++ + (case colspan of + ColSpan 1 -> [] + ColSpan n -> [ "colspan: " <> tshow n ]) + cellContents <- blocksToTypst bs + pure $ if null cellattrs + then brackets cellContents + else "table.cell" <> + parens + (literal (T.intercalate ", " cellattrs)) <> + brackets cellContents + let fromRow (Row _ cs) = + (<> ",") . commaSep <$> mapM fromCell cs + let fromHead (TableHead _attr headRows) = + if null headRows + then pure mempty + else (($$ "table.hline(),") . + (<> ",") . ("table.header" <>) . parens . nest 2 . vcat) + <$> mapM fromRow headRows + let fromFoot (TableFoot _attr footRows) = + if null footRows + then pure mempty + else (("table.hline()," $$) . + (<> ",") . ("table.footer" <>) . parens . nest 2 . vcat) + <$> mapM fromRow footRows + let fromTableBody (TableBody _attr _rowHeadCols headRows bodyRows) = do + hrows <- mapM fromRow headRows + brows <- mapM fromRow bodyRows + pure $ vcat (hrows ++ ["table.hline()," | not (null hrows)] ++ brows) + header <- fromHead thead + footer <- fromFoot tfoot + body <- vcat <$> mapM fromTableBody tbodies return $ "#figure(" $$ nest 2 ("align(center)[#table(" $$ nest 2 - ( "columns: " <> text (show numcols) <> "," -- auto - $$ "align: (col, row) => " <> alignarray <> ".at(col)," - $$ hsep (map ((<>",") . brackets) headers') - $$ vcat (map (\x -> brackets x <> ",") (concat rows')) + ( "columns: " <> columns <> "," + $$ "align: " <> alignarray <> "," + $$ header + $$ body + $$ footer ) $$ ")]" $$ capt' diff --git a/test/Tests/Old.hs b/test/Tests/Old.hs index 7a07558ea..ca9988275 100644 --- a/test/Tests/Old.hs +++ b/test/Tests/Old.hs @@ -199,7 +199,7 @@ tests pandocPath = [ testGroup "writer" $ writerTests' "ms" ] , testGroup "typst" - [ testGroup "writer" $ writerTests' "typst" + [ testGroup "writer" $ writerTests' "typst" ++ extWriterTests' "typst" , testGroup "reader" [ test' "typst-reader" ["-r", "typst", "-w", "native", "-s"] "typst-reader.typ" "typst-reader.native" diff --git a/test/tables.typst b/test/tables.typst index 0eaa77600..3736a7193 100644 --- a/test/tables.typst +++ b/test/tables.typst @@ -3,20 +3,12 @@ Simple table with caption: #figure( align(center)[#table( columns: 4, - align: (col, row) => (right,left,center,auto,).at(col), - [Right], [Left], [Center], [Default], - [12], - [12], - [12], - [12], - [123], - [123], - [123], - [123], - [1], - [1], - [1], - [1], + align: (right,left,center,auto,), + table.header([Right], [Left], [Center], [Default],), + table.hline(), + [12], [12], [12], [12], + [123], [123], [123], [123], + [1], [1], [1], [1], )] , caption: [Demonstration of simple table syntax.] , kind: table @@ -27,20 +19,12 @@ Simple table without caption: #figure( align(center)[#table( columns: 4, - align: (col, row) => (right,left,center,auto,).at(col), - [Right], [Left], [Center], [Default], - [12], - [12], - [12], - [12], - [123], - [123], - [123], - [123], - [1], - [1], - [1], - [1], + align: (right,left,center,auto,), + table.header([Right], [Left], [Center], [Default],), + table.hline(), + [12], [12], [12], [12], + [123], [123], [123], [123], + [1], [1], [1], [1], )] , kind: table ) @@ -50,20 +34,12 @@ Simple table indented two spaces: #figure( align(center)[#table( columns: 4, - align: (col, row) => (right,left,center,auto,).at(col), - [Right], [Left], [Center], [Default], - [12], - [12], - [12], - [12], - [123], - [123], - [123], - [123], - [1], - [1], - [1], - [1], + align: (right,left,center,auto,), + table.header([Right], [Left], [Center], [Default],), + table.hline(), + [12], [12], [12], [12], + [123], [123], [123], [123], + [1], [1], [1], [1], )] , caption: [Demonstration of simple table syntax.] , kind: table @@ -73,17 +49,14 @@ Multiline table with caption: #figure( align(center)[#table( - columns: 4, - align: (col, row) => (center,left,right,left,).at(col), - [Centered Header], [Left Aligned], [Right Aligned], [Default aligned], - [First], - [row], - [12.0], - [Example of a row that spans multiple lines.], - [Second], - [row], - [5.0], - [Here’s another one. Note the blank line between rows.], + columns: (15%, 13.75%, 16.25%, 35%), + align: (center,left,right,left,), + table.header([Centered Header], [Left Aligned], [Right Aligned], [Default + aligned],), + table.hline(), + [First], [row], [12.0], [Example of a row that spans multiple lines.], + [Second], [row], [5.0], [Here’s another one. Note the blank line between + rows.], )] , caption: [Here’s the caption. It may span multiple lines.] , kind: table @@ -93,17 +66,14 @@ Multiline table without caption: #figure( align(center)[#table( - columns: 4, - align: (col, row) => (center,left,right,left,).at(col), - [Centered Header], [Left Aligned], [Right Aligned], [Default aligned], - [First], - [row], - [12.0], - [Example of a row that spans multiple lines.], - [Second], - [row], - [5.0], - [Here’s another one. Note the blank line between rows.], + columns: (15%, 13.75%, 16.25%, 35%), + align: (center,left,right,left,), + table.header([Centered Header], [Left Aligned], [Right Aligned], [Default + aligned],), + table.hline(), + [First], [row], [12.0], [Example of a row that spans multiple lines.], + [Second], [row], [5.0], [Here’s another one. Note the blank line between + rows.], )] , kind: table ) @@ -113,19 +83,10 @@ Table without column headers: #figure( align(center)[#table( columns: 4, - align: (col, row) => (right,left,center,right,).at(col), - [12], - [12], - [12], - [12], - [123], - [123], - [123], - [123], - [1], - [1], - [1], - [1], + align: (right,left,center,right,), + [12], [12], [12], [12], + [123], [123], [123], [123], + [1], [1], [1], [1], )] , kind: table ) @@ -134,16 +95,11 @@ Multiline table without column headers: #figure( align(center)[#table( - columns: 4, - align: (col, row) => (center,left,right,auto,).at(col), - [First], - [row], - [12.0], - [Example of a row that spans multiple lines.], - [Second], - [row], - [5.0], - [Here’s another one. Note the blank line between rows.], + columns: (15%, 13.75%, 16.25%, 35%), + align: (center,left,right,auto,), + [First], [row], [12.0], [Example of a row that spans multiple lines.], + [Second], [row], [5.0], [Here’s another one. Note the blank line between + rows.], )] , kind: table ) diff --git a/test/tables/nordics.typst b/test/tables/nordics.typst new file mode 100644 index 000000000..322d19845 --- /dev/null +++ b/test/tables/nordics.typst @@ -0,0 +1,23 @@ +#figure( + align(center)[#table( + columns: (30%, 30%, 20%, 20%), + align: (center,left,left,left,), + table.header(table.cell(align: center)[Name], table.cell(align: center)[Capital], table.cell(align: center)[Population + \ + (in 2018)], table.cell(align: center)[Area \ + (in km#super[2];)],), + table.hline(), + [Denmark], [Copenhagen], [5,809,502], [43,094], + [Finland], [Helsinki], [5,537,364], [338,145], + [Iceland], [Reykjavik], [343,518], [103,000], + [Norway], [Oslo], [5,372,191], [323,802], + [Sweden], [Stockholm], [10,313,447], [450,295], + table.hline(), + table.footer([Total], [], [27,376,022], [1,258,336],), + )] + , caption: [States belonging to the #emph[Nordics.] + + ] + , kind: table + ) +<nordics> diff --git a/test/tables/planets.typst b/test/tables/planets.typst new file mode 100644 index 000000000..1d80ae88b --- /dev/null +++ b/test/tables/planets.typst @@ -0,0 +1,33 @@ +#figure( + align(center)[#table( + columns: 12, + align: (center,center,auto,right,right,right,right,right,right,right,right,auto,), + table.header(table.cell(colspan: 2)[], [Name], [Mass (10^24kg)], [Diameter + (km)], [Density (kg/m^3)], [Gravity (m/s^2)], [Length of day + (hours)], [Distance from Sun (10^6km)], [Mean temperature (C)], [Number of + moons], [Notes],), + table.hline(), + table.cell(rowspan: 4, colspan: 2)[Terrestrial + planets], [Mercury], [0.330], [4,879], [5427], [3.7], [4222.6], [57.9], [167], [0], [Closest + to the Sun], + [Venus], [4.87], [12,104], [5243], [8.9], [2802.0], [108.2], [464], [0], [], + [Earth], [5.97], [12,756], [5514], [9.8], [24.0], [149.6], [15], [1], [Our + world], + [Mars], [0.642], [6,792], [3933], [3.7], [24.7], [227.9], [-65], [2], [The + red planet], + table.cell(rowspan: 4)[Jovian planets], table.cell(rowspan: 2)[Gas + giants], [Jupiter], [1898], [142,984], [1326], [23.1], [9.9], [778.6], [-110], [67], [The + largest planet], + [Saturn], [568], [120,536], [687], [9.0], [10.7], [1433.5], [-140], [62], [], + table.cell(rowspan: 2)[Ice + giants], [Uranus], [86.8], [51,118], [1271], [8.7], [17.2], [2872.5], [-195], [27], [], + [Neptune], [102], [49,528], [1638], [11.0], [16.1], [4495.1], [-200], [14], [], + table.cell(colspan: 2)[Dwarf + planets], [Pluto], [0.0146], [2,370], [2095], [0.7], [153.3], [5906.4], [-225], [5], [Declassified + as a planet in 2006.], + )] + , caption: [Data about the planets of our solar system. + + ] + , kind: table + ) diff --git a/test/tables/students.typst b/test/tables/students.typst new file mode 100644 index 000000000..223732b06 --- /dev/null +++ b/test/tables/students.typst @@ -0,0 +1,26 @@ +#figure( + align(center)[#table( + columns: (50%, 50%), + align: (left,left,), + table.header(table.cell(align: center)[Student + ID], table.cell(align: center)[Name],), + table.hline(), + table.cell(colspan: 2)[Computer Science], + table.hline(), + [3741255], [Jones, Martha], + [4077830], [Pierce, Benjamin], + [5151701], [Kirk, James], + table.cell(colspan: 2)[Russian Literature], + table.hline(), + [3971244], [Nim, Victor], + table.cell(colspan: 2)[Astrophysics], + table.hline(), + [4100332], [Petrov, Alexandra], + [4100332], [Toyota, Hiroko], + )] + , caption: [List of Students + + ] + , kind: table + ) +<students> diff --git a/test/writer.typst b/test/writer.typst index e24de9a8a..9c5a8f921 100644 --- a/test/writer.typst +++ b/test/writer.typst @@ -16,7 +16,8 @@ } #set table( - inset: 6pt + inset: 6pt, + stroke: none ) #let conf( |
