From 0dc62c76bdb6e10bae38b805df1d2594595c2f43 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 28 Nov 2024 16:11:54 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20More=20typst=20export=20improvem?= =?UTF-8?q?ents=20(#1665)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rowan Cockett --- .changeset/beige-ways-learn.md | 5 +++ .changeset/clean-toys-work.md | 5 +++ .changeset/heavy-melons-count.md | 5 +++ .changeset/soft-shrimps-brake.md | 5 +++ .changeset/tidy-crabs-rhyme.md | 5 +++ .changeset/unlucky-adults-fry.md | 5 +++ packages/myst-to-typst/src/container.ts | 33 ++++++++++++++----- packages/myst-to-typst/src/index.ts | 23 +++++++++---- packages/myst-to-typst/src/table.ts | 10 ++++-- .../myst-to-typst/tests/cross-references.yml | 17 ++++++++++ packages/myst-to-typst/tests/figures.yml | 4 +-- packages/myst-to-typst/tests/tables.yml | 8 ++--- 12 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 .changeset/beige-ways-learn.md create mode 100644 .changeset/clean-toys-work.md create mode 100644 .changeset/heavy-melons-count.md create mode 100644 .changeset/soft-shrimps-brake.md create mode 100644 .changeset/tidy-crabs-rhyme.md create mode 100644 .changeset/unlucky-adults-fry.md diff --git a/.changeset/beige-ways-learn.md b/.changeset/beige-ways-learn.md new file mode 100644 index 000000000..108ac2013 --- /dev/null +++ b/.changeset/beige-ways-learn.md @@ -0,0 +1,5 @@ +--- +'myst-to-typst': patch +--- + +Allow overriding default breakable value for typst figures diff --git a/.changeset/clean-toys-work.md b/.changeset/clean-toys-work.md new file mode 100644 index 000000000..9c905600a --- /dev/null +++ b/.changeset/clean-toys-work.md @@ -0,0 +1,5 @@ +--- +'myst-to-typst': patch +--- + +Add hook for customizing table columns in typst diff --git a/.changeset/heavy-melons-count.md b/.changeset/heavy-melons-count.md new file mode 100644 index 000000000..fc5081bce --- /dev/null +++ b/.changeset/heavy-melons-count.md @@ -0,0 +1,5 @@ +--- +'myst-to-typst': patch +--- + +Render table legends below tables in typst diff --git a/.changeset/soft-shrimps-brake.md b/.changeset/soft-shrimps-brake.md new file mode 100644 index 000000000..5cc6a45d0 --- /dev/null +++ b/.changeset/soft-shrimps-brake.md @@ -0,0 +1,5 @@ +--- +'myst-to-typst': patch +--- + +Support lists that do not start at 1 in typst diff --git a/.changeset/tidy-crabs-rhyme.md b/.changeset/tidy-crabs-rhyme.md new file mode 100644 index 000000000..a8c4b03bd --- /dev/null +++ b/.changeset/tidy-crabs-rhyme.md @@ -0,0 +1,5 @@ +--- +'myst-to-typst': patch +--- + +Render cross-references with correct text in typst diff --git a/.changeset/unlucky-adults-fry.md b/.changeset/unlucky-adults-fry.md new file mode 100644 index 000000000..0afac5b91 --- /dev/null +++ b/.changeset/unlucky-adults-fry.md @@ -0,0 +1,5 @@ +--- +'myst-to-typst': patch +--- + +Support table cell background color in typst diff --git a/packages/myst-to-typst/src/container.ts b/packages/myst-to-typst/src/container.ts index 49d7ed8d4..4634c1766 100644 --- a/packages/myst-to-typst/src/container.ts +++ b/packages/myst-to-typst/src/container.ts @@ -35,6 +35,11 @@ export function determineCaptionKind(node: GenericNode): CaptionKind | null { function renderFigureChild(node: GenericNode, state: ITypstSerializer) { const useBrackets = node.type !== 'image' && node.type !== 'table'; + if (node.type === 'legend') { + state.useMacro('#let legendStyle = (fill: black.lighten(20%), size: 8pt, style: "italic")'); + state.write('text(..legendStyle)'); + node.type = 'paragraph'; + } if (useBrackets) state.write('[\n'); else state.write('\n '); state.renderChildren({ children: [node] }); @@ -60,12 +65,17 @@ export const containerHandler: Handler = (node, state) => { state.data.isInFigure = true; const { identifier, kind } = node; let label: string | undefined = identifier; - const captions = node.children?.filter( - (child: GenericNode) => child.type === 'caption' || child.type === 'legend', - ); - const nonCaptions = node.children?.filter( - (child: GenericNode) => child.type !== 'caption' && child.type !== 'legend', - ); + const captionTypes = node.kind === 'table' ? ['caption'] : ['caption', 'legend']; + const captions: GenericNode[] = node.children?.filter((child: GenericNode) => { + return captionTypes.includes(child.type); + }); + let nonCaptions: GenericNode[] = node.children?.filter((child: GenericNode) => { + return !captionTypes.includes(child.type); + }); + nonCaptions = [ + ...nonCaptions.filter((child) => child.type !== 'legend'), + ...nonCaptions.filter((child) => child.type === 'legend'), + ]; if (!nonCaptions || nonCaptions.length === 0) { fileError(state.file, `Figure with no non-caption content: ${label}`, { node, @@ -99,7 +109,10 @@ export const containerHandler: Handler = (node, state) => { nonCaptions.filter((item: GenericNode) => item.type === 'container').length === nonCaptions.length; state.useMacro('#import "@preview/subpar:0.1.1"'); - state.write(`#show figure: set block(breakable: ${allSubFigs ? 'false' : 'true'})\n`); + state.useMacro('#let breakableDefault = true'); + state.write( + `#show figure: set block(breakable: ${allSubFigs ? 'false' : 'breakableDefault'})\n`, + ); state.write('#subpar.grid('); let columns = nonCaptions.length <= 3 ? nonCaptions.length : 2; // TODO: allow this to be customized nonCaptions.forEach((item: GenericNode) => { @@ -123,12 +136,14 @@ export const containerHandler: Handler = (node, state) => { label = undefined; } } else if (nonCaptions && nonCaptions.length === 1) { - state.write('#show figure: set block(breakable: true)\n'); + state.useMacro('#let breakableDefault = true'); + state.write('#show figure: set block(breakable: breakableDefault)\n'); state.write('#figure('); renderFigureChild(nonCaptions[0], state); state.write(','); } else { - state.write('#show figure: set block(breakable: true)\n'); + state.useMacro('#let breakableDefault = true'); + state.write('#show figure: set block(breakable: breakableDefault)\n'); state.write('#figure([\n '); state.renderChildren(node, 1); state.write('],'); diff --git a/packages/myst-to-typst/src/index.ts b/packages/myst-to-typst/src/index.ts index 4970e0980..2c6817f91 100644 --- a/packages/myst-to-typst/src/index.ts +++ b/packages/myst-to-typst/src/index.ts @@ -189,10 +189,17 @@ const handlers: Record = { state.addNewLine(); }, list(node, state) { + const setStart = node.ordered && node.start && node.start !== 1; + if (setStart) { + state.write(`#set enum(start: ${node.start})`); + } state.data.list ??= { env: [] }; state.data.list.env.push(node.ordered ? '+' : '-'); - state.renderChildren(node, 2); + state.renderChildren(node, setStart ? 1 : 2); state.data.list.env.pop(); + if (setStart) { + state.write('#set enum(start: 1)\n\n'); + } }, listItem(node, state) { const listEnv = state.data.list?.env ?? []; @@ -355,13 +362,15 @@ const handlers: Record = { linkHandler({ ...node, url: url }, state); return; } - // Look up reference and add the text - // const usedTemplate = node.template?.includes('%s') ? node.template : undefined; - // const text = (usedTemplate ?? toText(node))?.replace(/\s/g, '~') || '%s'; const id = node.identifier; - // state.write(text.replace(/%s/g, `@${id}`)); - const next = nextCharacterIsText(parent, node); - state.write(next ? `#[@${id}]` : `@${id}`); + if (node.children && node.children.length > 0) { + state.write(`#link(<${id}>)[`); + state.renderChildren(node); + state.write(']'); + } else { + const next = nextCharacterIsText(parent, node); + state.write(next ? `#[@${id}]` : `@${id}`); + } }, citeGroup(node, state) { state.renderChildren(node, 0, { delim: ' ' }); diff --git a/packages/myst-to-typst/src/table.ts b/packages/myst-to-typst/src/table.ts index 81915eb13..96fa774f6 100644 --- a/packages/myst-to-typst/src/table.ts +++ b/packages/myst-to-typst/src/table.ts @@ -34,9 +34,12 @@ export const tableHandler: Handler = (node, state) => { return; } state.useMacro('#import "@preview/tablex:0.0.9": tablex, cellx, hlinex, vlinex'); + // These two separate style hooks are somewhat redundant, but they allow defining + // article-wide styles and single-table styles separately state.useMacro('#let tableStyle = (:)'); + state.useMacro('#let columnStyle = (:)'); state.write( - `${command}(columns: ${columns}, header-rows: ${countHeaderRows(node)}, repeat-header: true, ..tableStyle,\n`, + `${command}(columns: ${columns}, header-rows: ${countHeaderRows(node)}, repeat-header: true, ..tableStyle, ..columnStyle,\n`, ); state.renderChildren(node, 1); state.write(')\n'); @@ -48,7 +51,7 @@ export const tableRowHandler: Handler = (node, state) => { }; export const tableCellHandler: Handler = (node, state) => { - if (node.rowspan || node.colspan || node.align) { + if (node.rowspan || node.colspan || node.align || node.style?.backgroundColor) { state.write('cellx('); if (node.rowspan) { state.write(`rowspan: ${node.rowspan}, `); @@ -59,6 +62,9 @@ export const tableCellHandler: Handler = (node, state) => { if (node.align) { state.write(`align: ${node.align}, `); } + if (node.style?.backgroundColor) { + state.write(`fill: rgb("${node.style.backgroundColor}"), `); + } state.write(')'); } state.write('[\n'); diff --git a/packages/myst-to-typst/tests/cross-references.yml b/packages/myst-to-typst/tests/cross-references.yml index 5d12bc1b9..0804ae80e 100644 --- a/packages/myst-to-typst/tests/cross-references.yml +++ b/packages/myst-to-typst/tests/cross-references.yml @@ -36,3 +36,20 @@ cases: value: 'a for the first part of the figure!' typst: |- See #[@fig1]a for the first part of the figure! + - title: references followed by text with children + mdast: + type: root + children: + - type: paragraph + children: + - type: text + value: 'See ' + - type: crossReference + identifier: fig1 + children: + - type: text + value: 'Figure 56' + - type: text + value: 'a for the first part of the figure!' + typst: |- + See #link()[Figure 56]a for the first part of the figure! diff --git a/packages/myst-to-typst/tests/figures.yml b/packages/myst-to-typst/tests/figures.yml index df33a93cf..935ef7aeb 100644 --- a/packages/myst-to-typst/tests/figures.yml +++ b/packages/myst-to-typst/tests/figures.yml @@ -21,7 +21,7 @@ cases: url: glacier.jpg width: 500px typst: |- - #show figure: set block(breakable: true) + #show figure: set block(breakable: breakableDefault) #figure( image("glacier.jpg", width: 62.5%), kind: "figure", @@ -45,7 +45,7 @@ cases: - type: text value: A curious figure. typst: |- - #show figure: set block(breakable: true) + #show figure: set block(breakable: breakableDefault) #figure( image("glacier.jpg", width: 62.5%), caption: [ diff --git a/packages/myst-to-typst/tests/tables.yml b/packages/myst-to-typst/tests/tables.yml index 9ec4d60b9..a5a4119af 100644 --- a/packages/myst-to-typst/tests/tables.yml +++ b/packages/myst-to-typst/tests/tables.yml @@ -31,7 +31,7 @@ cases: value: Row 1, Column 2 typst: |- - #tablex(columns: 2, header-rows: 1, repeat-header: true, ..tableStyle, + #tablex(columns: 2, header-rows: 1, repeat-header: true, ..tableStyle, ..columnStyle, [ Head 1, Column 1 ], @@ -78,7 +78,7 @@ cases: value: Row 1, Column 2 typst: |- - #tablex(columns: 2, header-rows: 1, repeat-header: true, ..tableStyle, + #tablex(columns: 2, header-rows: 1, repeat-header: true, ..tableStyle, ..columnStyle, [ Head 1, Column 1 ], @@ -119,7 +119,7 @@ cases: value: Row 1, Column 2 typst: |- - #tablex(columns: 2, header-rows: 0, repeat-header: true, ..tableStyle, + #tablex(columns: 2, header-rows: 0, repeat-header: true, ..tableStyle, ..columnStyle, cellx(rowspan: 2, )[ Head 1, Column 1 ], @@ -156,7 +156,7 @@ cases: value: Row 1, Column 2 typst: |- - #tablex(columns: 2, header-rows: 0, repeat-header: true, ..tableStyle, + #tablex(columns: 2, header-rows: 0, repeat-header: true, ..tableStyle, ..columnStyle, cellx(colspan: 2, )[ Head 1, Column 1 ],