Skip to content

Commit 7a1a6d0

Browse files
committed
Add skylighting-format-typst.
Export its functions from Skylighting [API change]. Closes #201.
1 parent b5105ed commit 7a1a6d0

File tree

6 files changed

+100
-115
lines changed

6 files changed

+100
-115
lines changed

skylighting-format-typst/src/Skylighting/Format/Typst.hs

+74-114
Original file line numberDiff line numberDiff line change
@@ -8,145 +8,105 @@ module Skylighting.Format.Typst (
88
) where
99

1010
import Control.Monad (mplus)
11-
import Data.Char (isSpace)
1211
import Data.List (sort)
1312
import qualified Data.Map as Map
1413
import Data.Text (Text)
1514
import qualified Data.Text as Text
1615
import Skylighting.Types
17-
import Text.Printf
16+
import qualified Data.Map as M
17+
import Data.Maybe (fromMaybe)
1818
#if !MIN_VERSION_base(4,11,0)
1919
import Data.Semigroup
2020
#endif
2121

22-
formatTypst :: Bool -> [SourceLine] -> Text
23-
formatTypst inline = Text.intercalate (Text.singleton '\n')
24-
. map (sourceLineToTypst inline)
25-
2622
-- | Formats tokens as Typst using custom commands inside
2723
-- @|@ characters. Assumes that @|@ is defined as a short verbatim
2824
-- command by the macros produced by 'styleToTypst'.
2925
-- A @KeywordTok@ is rendered using @\\KeywordTok{..}@, and so on.
3026
formatTypstInline :: FormatOptions -> [SourceLine] -> Text
31-
formatTypstInline _opts ls = "\\VERB|" <> formatTypst True ls <> "|"
27+
formatTypstInline _opts = Text.intercalate newline . map sourceLineToTypst
28+
29+
newline :: Text
30+
newline = "#EndLine()\n"
3231

33-
sourceLineToTypst :: Bool -> SourceLine -> Text
34-
sourceLineToTypst inline = mconcat . map (tokenToTypst inline)
32+
sourceLineToTypst :: SourceLine -> Text
33+
sourceLineToTypst = mconcat . map tokenToTypst
3534

36-
tokenToTypst :: Bool -> Token -> Text
37-
tokenToTypst inline (NormalTok, txt)
38-
| Text.all isSpace txt = escapeTypst inline txt
39-
tokenToTypst inline (toktype, txt) = Text.cons '\\'
40-
(Text.pack (show toktype) <> "{" <> escapeTypst inline txt <> "}")
35+
tokenToTypst :: Token -> Text
36+
tokenToTypst (toktype, txt) =
37+
"#" <> Text.pack (show toktype) <> "(" <> doubleQuoted txt <> ");"
4138

42-
escapeTypst :: Bool -> Text -> Text
43-
escapeTypst inline = Text.concatMap escapeTypstChar
44-
where escapeTypstChar c =
45-
case c of
46-
'\\' -> "\\textbackslash{}"
47-
'{' -> "\\{"
48-
'}' -> "\\}"
49-
'|' | inline -> "\\VerbBar{}" -- used in inline verbatim
50-
'_' -> "\\_"
51-
'&' -> "\\&"
52-
'%' -> "\\%"
53-
'#' -> "\\#"
54-
'`' -> "\\textasciigrave{}"
55-
'\'' -> "\\textquotesingle{}"
56-
'-' -> "{-}" -- prevent ligatures
57-
'~' -> "\\textasciitilde{}"
58-
'^' -> "\\^{}"
59-
'>' -> "\\textgreater{}"
60-
'<' -> "\\textless{}"
61-
_ -> Text.singleton c
39+
doubleQuoted :: Text -> Text
40+
doubleQuoted t = "\"" <> escape t <> "\""
41+
where
42+
escape = Text.concatMap escapeChar
43+
escapeChar '\\' = "\\\\"
44+
escapeChar '"' = "\\\""
45+
escapeChar c = Text.singleton c
6246

6347
-- Typst
6448

6549
-- | Format tokens as a Typst @Highlighting@ environment inside a
66-
-- @Shaded@ environment. @Highlighting@ and @Shaded@ are
67-
-- defined by the macros produced by 'styleToTypst'. @Highlighting@
68-
-- is a verbatim environment using @fancyvrb@; @\\@, @{@, and @}@
69-
-- have their normal meanings inside this environment, so that
70-
-- formatting commands work. @Shaded@ is either nothing
71-
-- (if the style's background color is default) or a @snugshade@
72-
-- environment from @framed@, providing a background color
73-
-- for the whole code block, even if it spans multiple pages.
50+
-- Skylighting block that can be styled. @Skylighting@ is
51+
-- defined by the macros produced by 'styleToTypst'.
7452
formatTypstBlock :: FormatOptions -> [SourceLine] -> Text
75-
formatTypstBlock opts ls = Text.unlines
76-
["\\begin{Shaded}"
77-
,"\\begin{Highlighting}[" <>
78-
(if numberLines opts
79-
then "numbers=left," <>
80-
(if startNumber opts == 1
81-
then ""
82-
else ",firstnumber=" <>
83-
Text.pack (show (startNumber opts))) <> ","
84-
else Text.empty) <> "]"
85-
,formatTypst False ls
86-
,"\\end{Highlighting}"
87-
,"\\end{Shaded}"]
53+
formatTypstBlock opts ls =
54+
"#Skylighting(" <>
55+
(if numberLines opts
56+
then "number: true, start: " <> Text.pack (show (startNumber opts)) <> ", "
57+
else "") <>
58+
"(" <> -- an array
59+
Text.intercalate "\n" (map (\ln -> "[" <> formatTypstInline opts [ln] <> "],") ls)
60+
<> "));"
8861

8962
-- | Converts a 'Style' to a set of Typst macro definitions,
9063
-- which should be placed in the document's preamble.
91-
-- Note: default Typst setup doesn't allow boldface typewriter font.
92-
-- To make boldface work in styles, you need to use a different typewriter
93-
-- font. This will work for computer modern:
94-
--
95-
-- > \DeclareFontShape{OT1}{cmtt}{bx}{n}{<5><6><7><8><9><10><10.95><12><14.4><17.28><20.74><24.88>cmttb10}{}
96-
--
97-
-- Or, with xelatex:
98-
--
99-
-- > \usepackage{fontspec}
100-
-- > \setmainfont[SmallCapsFont={* Caps}]{Latin Modern Roman}
101-
-- > \setsansfont{Latin Modern Sans}
102-
-- > \setmonofont[SmallCapsFont={Latin Modern Mono Caps}]{Latin Modern Mono Light}
103-
--
10464
styleToTypst :: Style -> Text
105-
styleToTypst f = Text.unlines $
106-
[ "\\usepackage{color}"
107-
, "\\usepackage{fancyvrb}"
108-
, "\\newcommand{\\VerbBar}{|}"
109-
, "\\newcommand{\\VERB}{\\Verb[commandchars=\\\\\\{\\}]}"
110-
, "\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\}}"
111-
, "% Add ',fontsize=\\small' for more characters per line"
112-
] ++
113-
(case backgroundColor f of
114-
Nothing -> ["\\newenvironment{Shaded}{}{}"]
115-
Just (RGB r g b) -> ["\\usepackage{framed}"
116-
,Text.pack
117-
(printf "\\definecolor{shadecolor}{RGB}{%d,%d,%d}" r g b)
118-
,"\\newenvironment{Shaded}{\\begin{snugshade}}{\\end{snugshade}}"])
119-
++ sort (map (macrodef (defaultColor f) (Map.toList (tokenStyles f)))
120-
(enumFromTo KeywordTok NormalTok))
65+
styleToTypst f =
66+
Text.unlines $
67+
[ "/* Function definitions for syntax highlighting generated by skylighting: */"
68+
, "#let EndLine() = raw(\"\\n\")"
69+
, "#let Skylighting(fill: none, number: false, start: 1, sourcelines) = {"
70+
, " let blocks = []"
71+
, " let lnum = start - 1"
72+
, " let bgcolor = " <> maybe "none" toTypstColor (backgroundColor f)
73+
, " for ln in sourcelines {"
74+
, " if number {"
75+
, " lnum = lnum + 1"
76+
, " blocks = blocks + box(width: if start + sourcelines.len() > 999 { 30pt } else { 24pt }, text(" <> lineNumberFill <> "[ #lnum ]))"
77+
, " }"
78+
, " blocks = blocks + ln + EndLine()"
79+
, " }"
80+
, " block(fill: bgcolor, blocks)"
81+
, "}"
82+
] <>
83+
sort (map (macrodef (defaultColor f) (Map.toList (tokenStyles f)))
84+
(enumFromTo KeywordTok NormalTok))
85+
where
86+
toTypstColor c = "rgb(" <> Text.pack (show (fromColor c :: String)) <> ")"
87+
lineNumberFill = case lineNumberColor f of
88+
Nothing -> ""
89+
Just c -> "fill: " <> toTypstColor c <> ", "
12190

12291
macrodef :: Maybe Color -> [(TokenType, TokenStyle)] -> TokenType -> Text
123-
macrodef defaultcol tokstyles tokt = "\\newcommand{\\"
124-
<> Text.pack (show tokt)
125-
<> "}[1]{"
126-
<> Text.pack (co . ul . bf . it . bg $ "#1")
127-
<> "}"
128-
where tokf = case lookup tokt tokstyles of
129-
Nothing -> defStyle
130-
Just x -> x
131-
ul x = if tokenUnderline tokf
132-
then "\\underline{" <> x <> "}"
133-
else x
134-
it x = if tokenItalic tokf
135-
then "\\textit{" <> x <> "}"
136-
else x
137-
bf x = if tokenBold tokf
138-
then "\\textbf{" <> x <> "}"
139-
else x
140-
bcol = fromColor `fmap` tokenBackground tokf
141-
:: Maybe (Double, Double, Double)
142-
bg x = case bcol of
143-
Nothing -> x
144-
Just (r, g, b) ->
145-
printf "\\colorbox[rgb]{%0.2f,%0.2f,%0.2f}{%s}" r g b x
146-
col = fromColor `fmap` (tokenColor tokf `mplus` defaultcol)
147-
:: Maybe (Double, Double, Double)
148-
co x = case col of
149-
Nothing -> x
150-
Just (r, g, b) ->
151-
printf "\\textcolor[rgb]{%0.2f,%0.2f,%0.2f}{%s}" r g b x
152-
92+
macrodef defaultcol tokstyles' tokt =
93+
"#let " <> Text.pack (show tokt) <> "(s) = " <> (ul . bg . textstyle) ("raw(s)")
94+
where tokstyles = M.fromList tokstyles'
95+
tokf = fromMaybe defStyle $ M.lookup tokt tokstyles
96+
ul x = if tokenUnderline tokf
97+
then "underline(" <> x <> ")"
98+
else x
99+
bg x = case tokenBackground tokf of
100+
Nothing -> x
101+
Just _c -> x -- TODO?
102+
textstyle x = "text(" <> bf x <> it x <> co x <> x <> ")"
103+
it x = if tokenItalic tokf
104+
then "style: \"italic\","
105+
else ""
106+
bf x = if tokenBold tokf
107+
then "weight: \"bold\","
108+
else ""
109+
co x = case tokenColor tokf `mplus` defaultcol of
110+
Just c -> "fill: rgb(" <>
111+
Text.pack (show (fromColor c :: String)) <> "),"
112+
Nothing -> ""

skylighting/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ This project is divided up into five packages:
2525
rendering skylighting tokens as ConTeXt.
2626
* `skylighting-format-latex`: this provides formatters for
2727
rendering skylighting tokens as LaTeX.
28+
* `skylighting-format-typst`: this provides formatters for
29+
rendering skylighting tokens as Typst.
2830
* `skylighting`: this exposes the `skylighting-core` API and
2931
ANSI, HTML, ConTeXt, and LaTeX formatters, and also
3032
provides bundled Haskell parser modules derived from the XML

skylighting/bin/main.hs

+19-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ data HighlightFormat = FormatANSI
4545
| FormatConTeXt
4646
| FormatHtml
4747
| FormatLaTeX
48+
| FormatTypst
4849
| FormatNative
4950
deriving (Eq, Show)
5051

@@ -61,7 +62,7 @@ options =
6162
,Option ['f']
6263
["format"]
6364
(ReqArg Format "FORMAT")
64-
"output format (ansi|context|html|latex|native)"
65+
"output format (ansi|context|html|latex|typst|native)"
6566
,Option ['r']
6667
["fragment"]
6768
(NoArg Fragment)
@@ -147,6 +148,7 @@ formatOf (Format s : _) = case map toLower s of
147148
"context"-> return FormatConTeXt
148149
"html" -> return FormatHtml
149150
"latex" -> return FormatLaTeX
151+
"typst" -> return FormatTypst
150152
"native" -> return FormatNative
151153
_ -> err $ "Unknown format: " ++ s
152154
formatOf (_ : xs) = formatOf xs
@@ -249,6 +251,7 @@ main = do
249251
FormatConTeXt-> hlConTeXt fragment fname highlightOpts style sourceLines
250252
FormatHtml -> hlHtml fragment fname highlightOpts style sourceLines
251253
FormatLaTeX -> hlLaTeX fragment fname highlightOpts style sourceLines
254+
FormatTypst -> hlTypst fragment fname highlightOpts style sourceLines
252255
FormatNative -> putStrLn $ ppShow sourceLines
253256

254257
hlANSI :: FormatOptions
@@ -298,6 +301,21 @@ hlLaTeX frag fname opts sty sourceLines =
298301
macros = styleToLaTeX sty
299302
pageTitle = "\\title{" <> Text.pack fname <> "}\n"
300303

304+
hlTypst :: Bool -- ^ Fragment
305+
-> FilePath -- ^ Filename
306+
-> FormatOptions
307+
-> Style
308+
-> [SourceLine]
309+
-> IO ()
310+
hlTypst frag fname opts sty sourceLines =
311+
if frag
312+
then Text.putStrLn fragment
313+
else Text.putStrLn $ macros <> "\n" <> pageTitle <> "\n" <> fragment
314+
where fragment = formatTypstBlock opts sourceLines
315+
macros = styleToTypst sty
316+
pageTitle = "= " <> Text.pack fname <> "\n"
317+
318+
301319
hlConTeXt :: Bool -- ^ Fragment
302320
-> FilePath -- ^ Filename
303321
-> FormatOptions

skylighting/skylighting.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ library
4646
, skylighting-format-blaze-html:Skylighting.Format.HTML
4747
, skylighting-format-context:Skylighting.Format.ConTeXt
4848
, skylighting-format-latex:Skylighting.Format.LaTeX
49+
, skylighting-format-typst:Skylighting.Format.Typst
4950
other-modules:
5051
Skylighting.Syntax.Abc
5152
Skylighting.Syntax.Actionscript
@@ -217,6 +218,7 @@ library
217218
skylighting-format-context >= 0.1 && < 0.2,
218219
skylighting-format-latex >= 0.1 && < 0.2,
219220
skylighting-format-blaze-html >= 0.1 && < 0.2,
221+
skylighting-format-typst >= 0.1 && < 0.2,
220222
containers,
221223
binary
222224
hs-source-dirs: src

skylighting/src/Skylighting.hs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module Skylighting
1717
, module Skylighting.Format.ConTeXt
1818
, module Skylighting.Format.HTML
1919
, module Skylighting.Format.LaTeX
20+
, module Skylighting.Format.Typst
2021
, module Skylighting.Loader
2122

2223
)
@@ -27,6 +28,7 @@ import Skylighting.Format.ANSI
2728
import Skylighting.Format.ConTeXt
2829
import Skylighting.Format.HTML
2930
import Skylighting.Format.LaTeX
31+
import Skylighting.Format.Typst
3032
import Skylighting.Parser
3133
import Skylighting.Regex
3234
import Skylighting.Styles

stack.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ packages:
55
- skylighting-format-blaze-html
66
- skylighting-format-context
77
- skylighting-format-latex
8+
- skylighitng-format-typst
89
extra-deps:
910
- xml-conduit-1.9.1.3
1011
resolver: lts-21.0

0 commit comments

Comments
 (0)