Module: RubyRich::Markdown::TerminalConverter::LatexConverter
- Defined in:
- lib/ruby_rich/markdown.rb
Overview
—- LaTeX to Unicode converter —- Translates common LaTeX math commands to Unicode characters for terminal display. Handles Greek letters, big operators, frac, sqrt, super/subscript, cases, and ~150 common symbols.
Constant Summary collapse
- SYMBOLS =
— big lookup table ———————————— Format: “\command” => “unicode_char”
{ # Greek lowercase 'alpha' => 'α', 'beta' => 'β', 'gamma' => 'γ', 'delta' => 'δ', 'epsilon' => 'ε', 'varepsilon' => 'ɛ', 'zeta' => 'ζ', 'eta' => 'η', 'theta' => 'θ', 'vartheta' => 'ϑ', 'iota' => 'ι', 'kappa' => 'κ', 'lambda' => 'λ', 'mu' => 'μ', 'nu' => 'ν', 'xi' => 'ξ', 'pi' => 'π', 'varpi' => 'ϖ', 'rho' => 'ρ', 'varrho' => 'ϱ', 'sigma' => 'σ', 'varsigma' => 'ς', 'tau' => 'τ', 'upsilon' => 'υ', 'phi' => 'φ', 'varphi' => 'ϕ', 'chi' => 'χ', 'psi' => 'ψ', 'omega' => 'ω', # Greek uppercase 'Gamma' => 'Γ', 'Delta' => 'Δ', 'Theta' => 'Θ', 'Lambda' => 'Λ', 'Xi' => 'Ξ', 'Pi' => 'Π', 'Sigma' => 'Σ', 'Upsilon' => 'Υ', 'Phi' => 'Φ', 'Psi' => 'Ψ', 'Omega' => 'Ω', # Relations 'leq' => '≤', 'geq' => '≥', 'neq' => '≠', 'equiv' => '≡', 'approx' => '≈', 'sim' => '∼', 'simeq' => '≃', 'propto' => '∝', 'll' => '≪', 'gg' => '≫', 'doteq' => '≐', 'prec' => '≺', 'succ' => '≻', 'preceq' => '≼', 'succeq' => '≽', 'subset' => '⊂', 'supset' => '⊃', 'subseteq' => '⊆', 'supseteq' => '⊇', 'in' => '∈', 'ni' => '∋', 'notin' => '∉', 'perp' => '⊥', 'parallel' => '∥', # Binary operators 'times' => '×', 'div' => '÷', 'cdot' => '·', 'pm' => '±', 'mp' => '∓', 'oplus' => '⊕', 'ominus' => '⊖', 'otimes' => '⊗', 'oslash' => '⊘', 'odot' => '⊙', 'circ' => '∘', 'bullet' => '∙', 'cap' => '∩', 'cup' => '∪', 'setminus' => '∖', 'land' => '∧', 'lor' => '∨', 'wedge' => '∧', 'vee' => '∨', 'star' => '⋆', # Arrows 'to' => '→', 'rightarrow' => '→', 'Rightarrow' => '⇒', 'leftarrow' => '←', 'Leftarrow' => '⇐', 'leftrightarrow' => '↔', 'Leftrightarrow' => '⇔', 'mapsto' => '↦', 'longmapsto' => '⟼', 'uparrow' => '↑', 'downarrow' => '↓', 'longrightarrow' => '⟶', 'Longrightarrow' => '⟹', # Big operators 'sum' => '∑', 'prod' => '∏', 'coprod' => '∐', 'int' => '∫', 'iint' => '∬', 'iiint' => '∭', 'oint' => '∮', 'bigcup' => '⋃', 'bigcap' => '⋂', 'bigvee' => '⋁', 'bigwedge' => '⋀', # Misc symbols 'infty' => '∞', 'partial' => '∂', 'nabla' => '∇', 'forall' => '∀', 'exists' => '∃', 'nexists' => '∄', 'emptyset' => '∅', 'varnothing' => '∅', 'Re' => 'ℜ', 'Im' => 'ℑ', 'aleph' => 'ℵ', 'ell' => 'ℓ', 'hbar' => 'ℏ', 'wp' => '℘', 'angle' => '∠', 'triangle' => '△', 'triangledown' => '▽', 'square' => '□', 'Box' => '□', 'diamond' => '◇', 'clubsuit' => '♣', 'diamondsuit' => '♢', 'heartsuit' => '♡', 'spadesuit' => '♠', 'ldots' => '…', 'cdots' => '⋯', 'vdots' => '⋮', 'ddots' => '⋱', 'dots' => '…', 'cong' => '≅', 'models' => '⊨', 'mid' => '∣', 'nmid' => '∤', 'therefore' => '∴', 'because' => '∵', 'neg' => '¬', 'lnot' => '¬', 'top' => '⊤', 'bot' => '⊥', 'degree' => '°', 'prime' => '′', 'dag' => '†', 'ddag' => '‡', 'S' => '§', 'P' => '¶', 'pound' => '£', 'euro' => '€', 'yen' => '¥', 'copyright' => '©', 'circledR' => '®', # Delimiters – strip LaTeX wrapper 'left' => '', 'right' => '', 'bigl' => '', 'bigr' => '', 'Bigl' => '', 'Bigr' => '', 'biggl' => '', 'biggr' => '', # Arrows special 'gets' => '←', # Text sub/sup scripts 'text' => '', }.freeze
- TEXT_LIKE =
Commands whose argument should be preserved verbatim (e.g. textabc)
%w[text textrm textsf texttt textbf textit].freeze
- SUPERSCRIPTS =
{ '0' => '⁰', '1' => '¹', '2' => '²', '3' => '³', '4' => '⁴', '5' => '⁵', '6' => '⁶', '7' => '⁷', '8' => '⁸', '9' => '⁹', '+' => '⁺', '-' => '⁻', '=' => '⁼', '(' => '⁽', ')' => '⁾', 'i' => 'ⁱ', 'n' => 'ⁿ', }.freeze
- SUBSCRIPTS =
{ '0' => '₀', '1' => '₁', '2' => '₂', '3' => '₃', '4' => '₄', '5' => '₅', '6' => '₆', '7' => '₇', '8' => '₈', '9' => '₉', '+' => '₊', '-' => '₋', '=' => '₌', '(' => '₍', ')' => '₎', 'a' => 'ₐ', 'e' => 'ₑ', 'i' => 'ᵢ', 'j' => 'ⱼ', 'n' => 'ₙ', 'x' => 'ₓ', }.freeze
Class Method Summary collapse
- .convert(formula) ⇒ Object
-
.process_cases(text) ⇒ Object
begincases …
-
.process_frac(text) ⇒ Object
fracnumden → (num)/(den) or num/den when single-char.
-
.process_scripts(text) ⇒ Object
^x / _x → Unicode super/subscript.
-
.process_sqrt(text) ⇒ Object
sqrtx → √(x) sqrtx → ⁿ√(x).
-
.replace_symbols(text) ⇒ Object
Replace command tokens with Unicode equivalents.
- .script_chars(str, map) ⇒ Object
-
.strip_delim_spacing(text) ⇒ Object
Remove stray spaces inserted by left / right.
Class Method Details
.convert(formula) ⇒ Object
736 737 738 739 740 741 742 743 744 745 746 747 |
# File 'lib/ruby_rich/markdown.rb', line 736 def self.convert(formula) return formula if formula.nil? || formula.strip.empty? result = formula.dup result = process_frac(result) result = process_sqrt(result) result = process_cases(result) result = process_scripts(result) result = replace_symbols(result) result = strip_delim_spacing(result) result end |
.process_cases(text) ⇒ Object
begincases … endcases → ⎧ … ⎨ … ⎩ …
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 |
# File 'lib/ruby_rich/markdown.rb', line 771 def self.process_cases(text) text.gsub(/\\begin\{cases\}(.*?)\\end\{cases\}/m) do body = Regexp.last_match(1).strip lines = body.split('\\\\').map(&:strip).reject(&:empty?) return '{}' if lines.empty? out = +"" lines.each_with_index do |line, i| leader = case i when 0 then '⎧' when lines.length - 1 then '⎩' else '⎨' end out << "#{leader} #{line.gsub('&', '')}\n" end out.strip end end |
.process_frac(text) ⇒ Object
fracnumden → (num)/(den) or num/den when single-char
750 751 752 753 754 755 756 757 758 |
# File 'lib/ruby_rich/markdown.rb', line 750 def self.process_frac(text) text.gsub(/\\frac\s*\{([^{}]*(?:\{[^}]*\}[^{}]*)*)\}\s*\{([^{}]*(?:\{[^}]*\}[^{}]*)*)\}/) do num = Regexp.last_match(1) den = Regexp.last_match(2) num_wrap = num.length > 1 ? "(#{num})" : num den_wrap = den.length > 1 ? "(#{den})" : den "#{num_wrap}/#{den_wrap}" end end |
.process_scripts(text) ⇒ Object
^x / _x → Unicode super/subscript
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 |
# File 'lib/ruby_rich/markdown.rb', line 790 def self.process_scripts(text) # ^{...} text = text.gsub(/\^\{([^}]+)\}/) { inner = Regexp.last_match(1) inner.include?('\\') ? "^\{#{inner}\}" : script_chars(inner, SUPERSCRIPTS) } # _{...} text = text.gsub(/_\{([^}]+)\}/) { inner = Regexp.last_match(1) inner.include?('\\') ? "_\{#{inner}\}" : script_chars(inner, SUBSCRIPTS) } # ^x (single non-whitespace char, not \ or {) text = text.gsub(/\^([^\s\\{])/) { SUPERSCRIPTS[Regexp.last_match(1)] || "^#{Regexp.last_match(1)}" } # _x (single non-whitespace char, not \ or {) text = text.gsub(/_([^\s\\{])/) { SUBSCRIPTS[Regexp.last_match(1)] || "_#{Regexp.last_match(1)}" } text end |
.process_sqrt(text) ⇒ Object
sqrtx → √(x) sqrtx → ⁿ√(x)
761 762 763 764 765 766 767 768 |
# File 'lib/ruby_rich/markdown.rb', line 761 def self.process_sqrt(text) text.gsub(/\\sqrt(?:\[([^\]]*)\])?\s*\{([^{}]*(?:\{[^}]*\}[^{}]*)*)\}/) do degree = Regexp.last_match(1) radicand = Regexp.last_match(2) prefix = degree ? script_chars(degree, SUPERSCRIPTS) : '' "#{prefix}√(#{radicand})" end end |
.replace_symbols(text) ⇒ Object
Replace command tokens with Unicode equivalents.
813 814 815 816 817 818 819 820 |
# File 'lib/ruby_rich/markdown.rb', line 813 def self.replace_symbols(text) # Handle \text{…} first – keep content, remove wrapper text = text.gsub(/\\(text\w*)\s*\{(.*?)\}/) { Regexp.last_match(2) } # Replace all other \commands text.gsub(/\\([a-zA-Z]+)/) { |m| SYMBOLS[Regexp.last_match(1)] || m } end |
.script_chars(str, map) ⇒ Object
808 809 810 |
# File 'lib/ruby_rich/markdown.rb', line 808 def self.script_chars(str, map) str.each_char.map { |c| map[c] || c }.join end |
.strip_delim_spacing(text) ⇒ Object
Remove stray spaces inserted by left / right.
823 824 825 826 827 828 |
# File 'lib/ruby_rich/markdown.rb', line 823 def self.strip_delim_spacing(text) text.gsub(/\(\s+/, '(').gsub(/\s+\)/, ')') .gsub(/\[\s+/, '[').gsub(/\s+\]/, ']') .gsub(/\{\s+/, '{').gsub(/\s+\}/, '}') .gsub(/\\s+/, ' ') end |