diff --git a/package.json b/package.json index b5ff41a..9dc074a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tex2typst", - "version": "0.2.2", + "version": "0.2.6", "description": "JavaScript library for converting TeX code to Typst", "type": "module", "main": "dist/index.js", diff --git a/src/index.ts b/src/index.ts index 3d24e00..d601e3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { parseTex } from "./parser"; import { Tex2TypstOptions } from "./types"; import { TypstWriter } from "./writer"; +import { symbolMap } from "./map"; export function tex2typst(tex: string, options?: Tex2TypstOptions): string { @@ -26,4 +27,4 @@ export function tex2typst(tex: string, options?: Tex2TypstOptions): string { return writer.finalize(); } -export { Tex2TypstOptions }; +export { symbolMap, Tex2TypstOptions }; diff --git a/src/map.ts b/src/map.ts index bb99d3c..ba266a8 100644 --- a/src/map.ts +++ b/src/map.ts @@ -1,5 +1,4 @@ export const symbolMap = new Map([ - ['gets', 'arrow.l'], ['nonumber', ''], ['vec', 'arrow'], ['neq', 'eq.not'], @@ -25,14 +24,15 @@ export const symbolMap = new Map([ ['tfrac', 'frac'], ['boldsymbol', 'bold'], - ['mathbf', 'bold'], ['mathbb', 'bb'], + ['mathbf', 'bold'], ['mathcal', 'cal'], + ['mathit', 'italic'], ['mathfrak', 'frak'], + ['mathrm', 'upright'], ['mathsf', 'sans'], ['mathtt', 'mono'], - ['mathrm', 'upright'], ['rm', 'upright'], // TODO: \pmb need special logic to handle but it is not implemented now. See the commented test case. @@ -58,18 +58,6 @@ export const symbolMap = new Map([ ['equiv', 'equiv'], ['propto', 'prop'], - /* arrows used in proofs */ - // tex: \implies \iff \leftrightarrow \longleftrightarrow \rightrightarrows - // typst: arrow.r.double.long arrow.l.r.double.long arrow.l.r arrow.l.r.long arrows.rr - ['implies', 'arrow.r.double.long'], - ['Longrightarrow', 'arrow.r.double.long'], // Note: This macro is not supported by KaTeX - ['iff', 'arrow.l.r.double.long'], - ['Longleftrightarrow', 'arrow.l.r.double.long'], // Note: This macro is not supported by KaTeX - ['leftrightarrow', 'arrow.l.r'], - ['longleftrightarrow', 'arrow.l.r.long'], - ['rightrightarrows', 'arrows.rr'], - - /* left and right floor,ceil */ // tex: \lfloor \rfloor \lceil \rceil // typst: ⌊ ⌋ ⌈ ⌉ @@ -80,6 +68,39 @@ export const symbolMap = new Map([ ['lceil', '⌈'], ['rceil', '⌉'], + /* arrows */ + ['gets', 'arrow.l'], + ['hookleftarrow', 'arrow.l.hook'], + ['leftharpoonup', 'harpoon.lt'], + ['leftharpoondown', 'harpoon.lb'], + ['rightleftharpoons', 'harpoons.rtlb'], + ['longleftarrow', 'arrow.l.long'], + ['longrightarrow', 'arrow.r.long'], + ['longleftrightarrow', 'arrow.l.r.long'], + ['Longleftarrow', 'arrow.l.double.long'], + ['Longrightarrow', 'arrow.r.double.long'], + ['Longleftrightarrow', 'arrow.l.r.double.long'], + ['longmapsto', 'arrow.r.bar'], + ['hookrightarrow', 'arrow.r.hook'], + ['rightharpoonup', 'harpoon.rt'], + ['rightharpoondown', 'harpoon.rb'], + ['iff', 'arrow.l.r.double.long'], + ['implies', 'arrow.r.double.long'], + ['uparrow', 'arrow.t'], + ['downarrow', 'arrow.b'], + ['updownarrow', 'arrow.t.b'], + ['Uparrow', 'arrow.t.double'], + ['Downarrow', 'arrow.b.double'], + ['Updownarrow', 'arrow.t.b.double'], + ['nearrow', 'arrow.tr'], + ['searrow', 'arrow.br'], + ['swarrow', 'arrow.bl'], + ['nwarrow', 'arrow.tl'], + ['leadsto', 'arrow.squiggly'], + + ['leftleftarrows', 'arrows.ll'], + ['rightrightarrows', 'arrows.rr'], + ['Cap', 'sect.double'], ['Cup', 'union.double'], @@ -87,12 +108,16 @@ export const symbolMap = new Map([ ['Gamma', 'Gamma'], ['Join', 'join'], ['Lambda', 'Lambda'], + ['Leftarrow', 'arrow.l.double'], + ['Leftrightarrow', 'arrow.l.r.double'], ['Longrightarrow', 'arrow.r.double.long'], ['Omega', 'Omega'], + ['P', 'pilcrow'], ['Phi', 'Phi'], ['Pi', 'Pi'], ['Psi', 'Psi'], - ['Rightarrow', 'arrow.double'], + ['Rightarrow', 'arrow.r.double'], + ['S', 'section'], ['Sigma', 'Sigma'], ['Theta', 'Theta'], ['aleph', 'alef'], @@ -126,6 +151,7 @@ export const symbolMap = new Map([ ['colon', 'colon'], ['cong', 'tilde.equiv'], ['coprod', 'product.co'], + ['copyright', 'copyright'], ['cup', 'union'], ['curlyvee', 'or.curly'], ['curlywedge', 'and.curly'], @@ -179,7 +205,7 @@ export const symbolMap = new Map([ ['leqslant', 'lt.eq.slant'], ['lhd', 'triangle.l'], ['ll', 'lt.double'], - ['longmapsto', 'arrow.long.bar'], + ['longmapsto', 'arrow.bar.long'], ['longrightarrow', 'arrow.long'], ['lor', 'or'], ['ltimes', 'times.l'], diff --git a/src/parser.ts b/src/parser.ts index 8c430b5..71e4edf 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -5,36 +5,25 @@ const UNARY_COMMANDS = [ 'sqrt', 'text', - 'arccos', - 'arcsin', - 'arctan', - 'arg', 'bar', 'bold', 'boldsymbol', 'ddot', - 'det', - 'dim', 'dot', - 'exp', - 'gcd', 'hat', - 'ker', 'mathbb', 'mathbf', 'mathcal', + 'mathfrak', + 'mathit', + 'mathrm', 'mathscr', 'mathsf', 'mathtt', - 'mathrm', - 'max', - 'min', - 'mod', 'operatorname', 'overbrace', 'overline', 'pmb', - 'sup', 'rm', 'tilde', 'underbrace', @@ -295,17 +284,15 @@ function tokenize(latex: string): Token[] { throw new LatexParserError('Expecting command name after \\'); } const firstTwoChars = latex.slice(pos, pos + 2); - if (firstTwoChars === '\\\\') { - token = { type: 'control', value: '\\\\' }; - pos += 2; + if (['\\\\', '\\,'].includes(firstTwoChars)) { + token = { type: 'control', value: firstTwoChars }; } else if (['\\{','\\}', '\\%', '\\$', '\\&', '\\#', '\\_'].includes(firstTwoChars)) { token = { type: 'element', value: firstTwoChars }; - pos += 2; } else { const command = eat_command_name(latex, pos + 1); token = { type: 'command', value: '\\' + command}; - pos += 1 + command.length; } + pos += token.value.length; break; } default: { @@ -502,6 +489,8 @@ export class LatexParser { throw new LatexParserError("Unmatched '}'"); case '\\\\': return [{ type: 'control', content: '\\\\' }, start + 1]; + case '\\,': + return [{ type: 'control', content: '\\,' }, start + 1]; case '_': { let [sub, pos] = this.parseNextExpr(tokens, start + 1); let sup: TexNode | undefined = undefined; diff --git a/src/writer.ts b/src/writer.ts index 711bf3b..fb7afc4 100644 --- a/src/writer.ts +++ b/src/writer.ts @@ -283,6 +283,8 @@ export class TypstWriter { } else if (node.type === 'control') { if (node.content === '\\\\') { this.queue.push({ type: 'symbol', content: node.content }); + } else if (node.content === '\\,') { + this.queue.push({ type: 'symbol', content: 'thin' }); } else { throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node); } diff --git a/test/cheat-sheet.test.ts b/test/cheat-sheet.test.ts index fa5638f..113c92a 100644 --- a/test/cheat-sheet.test.ts +++ b/test/cheat-sheet.test.ts @@ -2,7 +2,7 @@ import path from 'path'; import toml from 'toml'; import fs from 'node:fs'; import { describe, it, test, expect } from 'vitest'; -import { tex2typst } from '../src'; +import { tex2typst, symbolMap } from '../src'; interface CheatSheet { math_commands: { [key: string]: string }; @@ -38,6 +38,7 @@ describe('cheat sheet', () => { const expected = value; const result = tex2typst(input); expect(result).toBe(expected); + expect(symbolMap.get(key)).toBe(expected); } }); }); diff --git a/test/cheat-sheet.toml b/test/cheat-sheet.toml index b72e3b5..b09c093 100644 --- a/test/cheat-sheet.toml +++ b/test/cheat-sheet.toml @@ -36,7 +36,9 @@ ln = "ln" log = "log" mathbb = "bb" mathcal = "cal" +mathit = "italic" mathsf = "sans" +mathrm = "upright" mathtt = "mono" max = "max" min = "min" @@ -61,18 +63,41 @@ vec = "arrow" widehat = "hat" [math_symbols] +gets = "arrow.l" +hookleftarrow = "arrow.l.hook" +leftharpoonup = "harpoon.lt" +leftharpoondown = "harpoon.lb" +rightleftharpoons = "harpoons.rtlb" +longleftarrow = "arrow.l.long" +hookrightarrow = "arrow.r.hook" +rightharpoonup = "harpoon.rt" +rightharpoondown = "harpoon.rb" +iff = "arrow.l.r.double.long" +Uparrow = "arrow.t.double" +Downarrow = "arrow.b.double" +Updownarrow = "arrow.t.b.double" +nearrow = "arrow.tr" +searrow = "arrow.br" + + Cap = "sect.double" Cup = "union.double" Delta = "Delta" Gamma = "Gamma" Join = "join" Lambda = "Lambda" +Leftarrow = "arrow.l.double" +Leftrightarrow = "arrow.l.r.double" +Longleftarrow = "arrow.l.double.long" +Longleftrightarrow = "arrow.l.r.double.long" Longrightarrow = "arrow.r.double.long" Omega = "Omega" +P = "pilcrow" Phi = "Phi" Pi = "Pi" Psi = "Psi" -Rightarrow = "arrow.double" +Rightarrow = "arrow.r.double" +S = "section" Sigma = "Sigma" Theta = "Theta" aleph = "alef" @@ -106,6 +131,7 @@ circ = "circle.small" # 'circle.small' or 'compose' colon = "colon" cong = "tilde.equiv" coprod = "product.co" +copyright = "copyright" cup = "union" curlyvee = "or.curly" curlywedge = "and.curly" @@ -153,13 +179,14 @@ ldots = "dots.l" le = "lt.eq" leadsto = "arrow.squiggly" leftarrow = "arrow.l" +leftleftarrows = "arrows.ll" leftthreetimes = "times.three.l" leftrightarrow = "arrow.l.r" leq = "lt.eq" leqslant = "lt.eq.slant" lhd = "triangle.l" ll = "lt.double" -longmapsto = "arrow.long.bar" +longmapsto = "arrow.bar.long" longrightarrow = "arrow.long" ltimes = "times.l" mapsto = "arrow.bar" diff --git a/test/math.yml b/test/math.yml index 57c23e0..c070fa1 100644 --- a/test/math.yml +++ b/test/math.yml @@ -293,4 +293,10 @@ cases: a + b // co c + d e + f // co - g + h \ No newline at end of file + g + h + - title: mod + tex: a^{p-1} \equiv 1 \mod{p} + typst: a^(p - 1) equiv 1 mod p + - title: thin space + tex: a \, b + typst: a thin b \ No newline at end of file