-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Typst property output #9648
Typst property output #9648
Conversation
src/Text/Pandoc/Writers/Typst.hs
Outdated
|
||
formatTypstProps :: [(Text, Text)] -> [Text] | ||
formatTypstProps = | ||
map (\(k,v) -> last (T.splitOn (T.pack ":") k) <> ": " <> v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid last
, as it's a partial function.
Also you don't need T.pack ":"
; ":"
will work by itself because we have OverloadedStrings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points. I had to improve validation to drop keys like typst:foo:bar
, so I took the opportunity to pull the last element at input time and not worry about invalid input during output.
test/Tests/Writers/Typst.hs
Outdated
, " )" | ||
] | ||
, "table text" =: | ||
let headers = [] | ||
rows = [] | ||
in tableWith ("", [], [("typst:text:fill", "orange")]) | ||
emptyCaption | ||
[] | ||
(TableHead nullAttr $ toHeaderRow headers) | ||
[TableBody nullAttr 0 [] $ map toRow rows] | ||
(TableFoot nullAttr []) | ||
=?> unlines [ | ||
"#figure(" | ||
, " align(center)[#text(fill: orange)[#table(" | ||
, " columns: 0," | ||
, " align: ()," | ||
, " )]]" | ||
, " , kind: table" | ||
, " )" | ||
] | ||
, "table cell" =: | ||
let headers = [] | ||
rows = [Row nullAttr [ | ||
cellWith ("", [], [("typst:fill", "blue")]) AlignDefault (RowSpan 1) (ColSpan 1) $ para "Foo" | ||
, simpleCell $ para "Bar" | ||
]] | ||
in table | ||
emptyCaption | ||
[(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)] | ||
(TableHead nullAttr $ toHeaderRow headers) | ||
[TableBody nullAttr 0 [] rows] | ||
(TableFoot nullAttr []) | ||
=?> unlines [ | ||
"#figure(" | ||
, " align(center)[#table(" | ||
, " columns: 2," | ||
, " align: (auto,auto,)," | ||
, " table.cell(fill: blue)[Foo" | ||
, "" | ||
, " ], [Bar" | ||
, "" | ||
, " ]," | ||
, " )]" | ||
, " , kind: table" | ||
, " )" | ||
] | ||
, "table cell text no attr" =: | ||
let headers = [] | ||
rows = [Row nullAttr [ | ||
cellWith ("", [], [("typst:text:fill", "orange")]) AlignDefault (RowSpan 1) (ColSpan 1) $ para "Foo" | ||
, simpleCell $ para "Bar" | ||
]] | ||
in table | ||
emptyCaption | ||
[(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)] | ||
(TableHead nullAttr $ toHeaderRow headers) | ||
[TableBody nullAttr 0 [] rows] | ||
(TableFoot nullAttr []) | ||
=?> unlines [ | ||
"#figure(" | ||
, " align(center)[#table(" | ||
, " columns: 2," | ||
, " align: (auto,auto,)," | ||
, " [#set text(fill: orange); Foo" | ||
, "" | ||
, " ], [Bar" | ||
, "" | ||
, " ]," | ||
, " )]" | ||
, " , kind: table" | ||
, " )" | ||
] | ||
, "table cell text w attr" =: | ||
let headers = [] | ||
rows = [Row nullAttr [ | ||
cellWith ("", [], [("typst:text:fill", "orange")]) AlignCenter (RowSpan 1) (ColSpan 1) $ para "Foo" | ||
, simpleCell $ para "Bar" | ||
]] | ||
in table | ||
emptyCaption | ||
[(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)] | ||
(TableHead nullAttr $ toHeaderRow headers) | ||
[TableBody nullAttr 0 [] rows] | ||
(TableFoot nullAttr []) | ||
=?> unlines [ | ||
"#figure(" | ||
, " align(center)[#table(" | ||
, " columns: 2," | ||
, " align: (auto,auto,)," | ||
, " table.cell(align: center)[#set text(fill: orange); Foo" | ||
, "" | ||
, " ], [Bar" | ||
, "" | ||
, " ]," | ||
, " )]" | ||
, " , kind: table" | ||
, " )" | ||
] | ||
] | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest just using a "command test" (see test/commands/*.md
).
They are easier to create and much easier to maintain (since we can just do make test TESTARGS=--accept
to update the expected output instead of doing it manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, much simpler to write tests that way!
I threw in negative cases: elements that don't have typst:*
attributes should not affect the output. This was covered by previous tests but worth it to be somewhat complete.
src/Text/Pandoc/Writers/Typst.hs
Outdated
-- there is probably a more idiomatic way to do this | ||
ifNotEmpty :: ([a2] -> Doc Text) -> [a2] -> Doc Text | ||
ifNotEmpty f list = | ||
case list of | ||
[] -> "" | ||
_ -> f list | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be more elegant to use a fold. E.g. foldl' addTypstProperty mempty props
.
If the list props
is empty, you get mempty
, so you wouldn't need this ugly function.
It should be possible to make this code simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little bit more complicated, because we have this conditional text we want to add around the lists, like we want to emit either #set text(prop:value);
or the empty string at the top of a block where we are applying typst:text:*
attributes. We don't want to emit a useless #set text();
(if that's even valid).
And likewise everywhere else: if a block has no attributes, we want to emit #block[content]
and not #block()[content]
, etc.
@cscheid and I looked at it together and agreed that most clear thing to do is not to try to abstract it, but just write functions that both check for the empty list and generate text. This section is about half as many lines of code now.
38d0a75
to
c6a21ca
Compare
Okay, I think I have addressed your concerns, as well as adding solid validation of Glad to make any other changes! Should I add documentation somewhere? I think I'd prefer to wait to implement output of properties from other elements until we get more experience, unless you have any requests.
I can do another PR later with other elements as our users inevitably test the boundaries of this, or we find more uses for Typst properties. Thanks for all your helpful comments so far. |
As for documentation, one possibility would be to have a separate document along the lines of |
src/Text/Pandoc/Writers/Typst.hs
Outdated
if length(spl) < 2 || length(spl) > 3 | ||
|| head spl /= "typst" | ||
|| (length(spl) == 3 && spl !! 1 /= "text") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We like to avoid using partial functions like head
. I think it's cleaner to use pattern matching for this kind of thing. Could you do something like
case T.splitOn ":" k of
"typst":"text":x -> Right (x,v)
"typst":x -> Left (x, v)
_ -> Nothing
Second thought: instead of using partitionEithers . catMaybes . map
, why not just use a fold that directly constructs the tuple? This way you also avoid needing to use Maybe or Either.
pickTypstAttrs = foldr go ([],[])
where
go (k,v) (eltProps, textProps) =
case T.splitOn ":" k of
"typst":"text":x -> (eltProps, (x,v):textProps)
"typst":x -> ((x,v):eltProps, textProps)
_ -> (eltProps, textProps)
Or even more compactly with Data.Bifunctor.(first,second)
pickTypstAttrs = foldr go ([],[])
where
go (k,v)
case T.splitOn ":" k of
"typst":"text":x -> second ((x,v):)
"typst":x -> first ((x,v):)
_ -> id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very nice!
All five of my logical conditions covered by three patterns, and the meaning is very clear with the bifunctor approach.
src/Text/Pandoc/Writers/Typst.hs
Outdated
formatTypstProps :: [(Text, Text)] -> [Text] | ||
formatTypstProps = | ||
map (\(k,v) -> k <> ": " <> v) | ||
|
||
toTypstPropsListSep :: [(Text, Text)] -> Doc Text | ||
toTypstPropsListSep typstAttrs = | ||
literal (T.intercalate ", " $ formatTypstProps typstAttrs) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this approach the attribute lists will be unbreakable.
That's probably not what we want. Better:
formatTypstProps = map (\(k,v) -> literal (k <> ": " <> v))
toTypstPropsListSep = hsep . intersperse "," . formatTypstProps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, knew I was missing something about line breaks. Thanks!
src/Text/Pandoc/Writers/Typst.hs
Outdated
|
||
toTypstTextElement :: [(Text, Text)] -> Doc Text -> Doc Text | ||
toTypstTextElement [] content = content | ||
toTypstTextElement typstTextAttrs content = "#text" <> parens (toTypstPropsListSep typstTextAttrs) <> brackets content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could simplify by using toTypstPropsListParens
here instead of the spelled out version
Excellent feedback - I think it is very clean and simple now. I've pushed a first draft of the docs. The wording is a little rough but I think they are complete. Will take another pass over the weekend and clean up. |
Amazingly useful contribution @gordonwoodhull!!! 🥳 |
doc/typst-property-output.md
Outdated
the command | ||
|
||
```sh | ||
quarto pandoc -f html -t typst --lua-filter ./typst-property-example.lua |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is pandoc's documentation, not quarto, shouldn't this just be pandoc
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!
This implements output of Typst properties from attributes of the forms * `typst:prop` - the value should be interpreted as raw Typst code to set a parameter on the corresponding Typst element * `typst:text:prop` - the value should be interpreted as raw Typst code to set a parameter on a Typst `#set text()` rule, or text element as appropriate. This commit covers the following elements: * span text * div and div text * table and table text * td and td text It would be possible to support more elements in the future.
cfb080c
to
62b7cec
Compare
I fixed the example, did a few more copy edits, and squashed everything into one commit. I think this is ready! Please let me know if you want any further changes. |
``` | ||
|
||
``` | ||
% pandoc -f html -t typst |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a difference between this test and the one at line 76?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to have a test of an attribute on table
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I guess I added negative tests for both table and cell without somehow realizing they are the same. Removed the redundant test.
this is the annoying case where we have to concatenate align and i forgot to update it when cleaning up attr validation
Because it's already inside figure & align, so these settings won't pollute anything
I found that I had failed to update the ugly special case for Also I noticed that we can use a set-text rule instead of text element for table, since the table is inside the figure & align scopes and the set-text rule won't leak out. |
I think it's good to go! Thanks. |
Thank you! |
This is working great and we'd like to include "Typst CSS" in Quarto 1.5. When you're around and have a minute, please release 3.1.14! |
Here is my work in progress on Typst property output. Supercedes #9623.
https://github.com/gordonwoodhull/pandoc/tree/typst-property-output
It handles 8 cases of attribute-bearing AST nodes:
text()
is the closest element to span)Now that I have tests I would be comfortable implementing the rest, but I haven't needed them yet. Thought I'd check in to ask if I'm on track here. All suggestions are welcome!
I think the code is more idiomatic Haskell (but not perfect) and the usage is terse (except for the align case, see below). Some details of whitespace/line breaks probably need more work.
There aren't row elements in Typst; tr attributes would have to be underlaid on cell properties.
align is messy
There is one case that is ugly - the Typst reader extracts (horizontal)
align
, but this needs to be coalesced withvertical-align
for the Typst cellalign
. An alternative design would be to not remove the style in the reader, and allow Lua to override the whole property. Technically I should also parsetypst:align
as addition and then dedupe, because Typst will error if there are duplicate terms in the align expression, but Lua only seesvertical-align
currently, so it works out.