Policy documenting with Typst, ODT and Pandoc
Can I manage document changes between two document formats.
I have been working on a few policy documents in typst and it's been an enjoyable process, [What has been enjoyable]. The initial workflow for creating the documents started off with taking the company's existing wordMicrosoft Word documents and transforming them into typst. It became a pain point when teammates (all non-technical) just wanted a "word" document to edit and share.
Although providing them with a familiar word document was possible, whenever I did convert from typst -> pdf -> word, depending on what layouts and graphics I had incorporatedSometimes a policy would require a table and some extra graphics which I would handroll using typst, and sometimes some occasional svg magic., the resulting document was negatively affected, it also became difficult to apply changes made in the word document to the typst document.
Initially to create the typst document the was workflow was to,
- Take a word document, use an utility I wrote using rust and pandoc_types to render typst for the AST generated from running the
pandoccommand or - Create a typst document from scratch using the
lib.typas a base.
Both worked relatively well, and the main failing points were the lack of easy editable for teammates and applying changes. To improve this process, I started looking at pandoc's lua filters and how I can use this to create this updated workflow.
Current workflow
input
├── data.toml
├── files
│ ├── '*.docx'
output
├── 'policy'
│ ├── '*.json'
│ ├── '*.typ'
│ ├── '*.pdf' // Generated PDF
│ ├── data_table.toml
│ └── media
│ ├── image1.jpeg
│ ├── image2.jpeg
│ └── image3.jpeg
src
├── main.rs
└── policy_typst.rs
The initial workflow I established when I started the project was to take a word document, parse it into an AST using pandoc, this is returned as a JSON file. I serialize the returned JSON using serde and pandoc_types and rendered typst by matching against the tokens in rust, it was a pretty straightforward flow. It is most correct but fails at point when the word document author uses a bold instead of a heading style for instance.
...
342 │ fn render_block(&self, block: &Block) -> String {
343 │ match block {
344 │ Block::Plain(inlines) | Block::Para(inlines) => inlines
345 │ .iter()
346 │ .map(|i| (&self).render_inline(i))
347 │ .collect::>()
348 │ .join(""),
349 │ Block::LineBlock(items) => items
350 │ .iter()
351 │ .map(|item| {
352 │ item.iter()
353 │ .map(|i| (&self).render_inline(i))
354 │ .collect::>()
355 │ .join("")
356 │ })
357 │ .collect::>()
358 │ .join("\n"),
359 │ Block::CodeBlock(attr, code) => {
360 │ let lang = attr.classes.get(0).map_or("", |s| s);
361 │ format!("```{}\n{}\n```", lang, code)
...
But what this meant is the typst documents became the sources of truth, I would then create PDF and share with teammates.
I added ODT
To make sure the styles transferred well when I wanted to share with teammates I ended up creating an ODT reference document with all my styles defined. This worked relatively nicely on first try.