บทความ/จัดพิมพ์ โดย: นาย เจษฏา กานต์ประชา
นอกจากการทำให้ใช้ภาษาไทยได้ผ่าน option renderActions เราสามารถแก้ไขผ่าน option FindTeX ได้ด้วย ซึ่งจะทำให้เราสามารถจัดการกับ LaTeX ที่แสดงผลได้มากขึ้น เพราะปกติแล้ว MathJaX จะไม่ได้ค้นหา LaTeX ที่ต้องการแสดงผลจาก inline/display delimiter เท่านั้น แต่จะดูเนื้อหาข้างในด้วยว่า มีวงเล็บ {} ครบคู่หรือไม่ หากมีไม่ครบคู่ก็จะข้าม ไม่ยอมประมวลผล และไม่แจ้งเตือนอะไรทั้งสิ้นว่าเกิดจากปัญหาอะไร เราสามารถแก้ปัญหาตรงนี้ได้ด้วยการตั้งค่า FindTeX Option
สิ่งที่ต้องใส่ใน FindTeX Option จะเป็น Object instance ที่มี method findMath โดย MathJaX จะเรียกใช้ method นี้ผ่าน method findMath ของ Class input/TeX
TeX.prototype.findMath = function (strings) { return this.findTeX.findMath(strings); };
สิ่งที่ส่งเข้ามาใน method คือ strings เป็นอาเรย์ของข้อความที่ต้องการให้ตรวจสอบว่า มีข้อความไหนที่มี LaTeX หรือไม่ ยกตัวอย่างเช่น
[ "Hello", "Test \\[\\frac{abc}{def}\\] English", "World", "Go \\(\\sqrt{b^2 - 4ac}\\) Message \\(\\pm \\sqrt{2}\\) Hello" ]
ผลลัพธ์ที่ส่งกลับมาจาก method เป็นอาเรย์ของ Object แจ้งรายละเอียดของ LaTeX ที่เจอในข้อความทั้งหมด ยกตัวอย่างเช่น
[ { "open": "\\[", "math": "\\frac{abc}{def}", "close": "\\]", "n": 1, "start": { "n": 5 }, "end": { "n": 24 }, "display": true }, { "open": "\\(", "math": "\\sqrt{b^2 - 4ac}", "close": "\\)", "n": 3, "start": { "n": 3 }, "end": { "n": 23 }, "display": false }, { "open": "\\(", "math": "\\pm \\sqrt{2}", "close": "\\)", "n": 3, "start": { "n": 32 }, "end": { "n": 48 }, "display": false } ]
แต่ละฟิลด์มีความหมายดังนี้
Template เริ่มต้นสำหรับใช้ตั้งค่า FindTeX Option
const MyFindTeX = (function () { function MyFindTeX(options) { this.options = options; } MyFindTeX.prototype.findMath = function (strings) { // Todo: Define your findMath here }; return MyFindTeX; }()); const findTeXOptions = { // Todo: Define your option here }; const myFindTeX = new MyFindTeX(findTeXOptions); window.MathJax = { tex: { FindTeX: myFindTeX } };
ตัวอย่างการแก้ไข Object instance ให้รองรับภาษาไทย และไม่ดูเนื้อหาข้างในว่า มีวงเล็บ {} ครบคู่หรือไม่
let MyFindTeX = (function () { function MyFindTeX(options) { this.options = options; this.init(); } MyFindTeX.prototype.init = function () { const opens = []; this.closeInfo = {}; for (const delims of this.options['inlineMath']) { this.addPattern(opens, delims, false) } for (const delims of this.options['displayMath']) { this.addPattern(opens, delims, true) } this.openPattern = new RegExp(opens.sort(this.sortLength).join('|'), 'g'); this.regexThaiCharacter = /(\\text(?:rm)?\{[^}]*)?([\u0E00-\u0E7F]+)/g; } MyFindTeX.prototype.sortLength = function (a, b) { return a.length !== b.length ? b.length - a.length : a === b ? 0 : a < b ? -1 : 1; }; MyFindTeX.prototype.addPattern = function (opens, delims, display) { const [open, close] = delims; opens.push(this.quotePattern(open)); const closePattern = new RegExp(this.quotePattern(close), 'g'); this.closeInfo[open] = [close, display, closePattern]; }; MyFindTeX.prototype.quotePattern = function (text) { return text.replace(/([\^$(){}+*?\-|\[\]\:\\])/g, '\\$1'); }; MyFindTeX.prototype.findMath = function (strings) { const maths = []; for (const [n, text] of strings.entries()) { let matchOpen, matchClose; this.openPattern.lastIndex = 0; while ((matchOpen = this.openPattern.exec(text))) { const [close, display, closePattern] = this.closeInfo[matchOpen[0]]; const startMathIdx = closePattern.lastIndex = matchOpen.index + matchOpen[0].length; matchClose = closePattern.exec(text); if (matchClose[0] === close) { const endIdx = matchClose.index + matchClose[0].length; const math = text.substr(startMathIdx, matchClose.index - startMathIdx); const thaiMath = math.replace(this.regexThaiCharacter, function (match, has_textcmd_before, thai_content) { return has_textcmd_before ? match : "\\text{" + thai_content + "}"; }); maths.push({ open: matchOpen[0], math: thaiMath, close: close, n: n, start: { n: matchOpen.index }, end: { n: endIdx }, display: display }); this.openPattern.lastIndex = endIdx; } } } return maths; }; return MyFindTeX; }()); let findTeXOptions = { inlineMath: [ ['\\(', '\\)'], ['$', '$'] ], displayMath: [ ['\\[', '\\]'], ['$$', '$$'] ] }; let myFindTeX = new MyFindTeX(findTeXOptions); window.MathJax = { tex: { FindTeX: myFindTeX, macros: { bmatrix: ['\\begin{bmatrix}#1\\end{bmatrix}', 1], Bmatrix: ['\\begin{Bmatrix}#1\\end{Bmatrix}', 1], vmatrix: ['\\begin{vmatrix}#1\\end{vmatrix}', 1], Vmatrix: ['\\begin{Vmatrix}#1\\end{Vmatrix}', 1], cases: ['\\begin{cases}#1\\end{cases}', 1], euler: '\\atopwithdelims<>', sech: '\\text{sech}\\,' }, }, svg: { mtextInheritFont: true, scale: 1, fontCache: 'global' } };
หากเราการต้องการแก้ไข Object instance จากตัวเริ่มต้นที่ MathJaX ให้มา ก็จำเป็นต้องคัดลอกสิ่งที่ MathJaX เตรียมมาให้ แล้วแก้ไขในส่วนที่ต้องการ ตัวอย่างโค้ดการใช้งาน FindTeX Option ที่แก้ไขให้รองรับภาษาไทย และไม่ดูเนื้อหาข้างในว่า มีวงเล็บ {} ครบคู่หรือไม่
const MyFindTeX = (function () { function MyFindTeX(options) { this.options = options; this.getPattern(); this.regexThaiCharacter = /(\\text(?:rm)?\{[^}]*)?([\u0E00-\u0E7F]+)/g; } MyFindTeX.prototype.getPattern = function () { let options = this.options; let starts = [], parts = [], subparts = []; this.end = {}; this.env = this.sub = 0; let i = 1; options['inlineMath'].forEach((delims) => this.addPattern(starts, delims, false)); options['displayMath'].forEach((delims) => this.addPattern(starts, delims, true)); if (starts.length) { parts.push(starts.sort(this.sortLength).join('|')); } if (options['processEnvironments']) { parts.push('\\\\begin\\s*\\{([^}]*)\\}'); this.env = i; i++; } if (options['processEscapes']) { subparts.push('\\\\([\\\\$])'); } if (options['processRefs']) { subparts.push('(\\\\(?:eq)?ref\\s*\\{[^}]*\\})'); } if (subparts.length) { parts.push('(' + subparts.join('|') + ')'); this.sub = i; } this.start = new RegExp(parts.join('|'), 'g'); this.hasPatterns = (parts.length > 0); }; MyFindTeX.prototype.sortLength = function (a, b) { return a.length !== b.length ? b.length - a.length : a === b ? 0 : a < b ? -1 : 1; }; MyFindTeX.prototype.addPattern = function (starts, delims, display) { let [open, close] = delims; starts.push(this.quotePattern(open)); this.end[open] = [close, display, this.endPattern(close)]; }; MyFindTeX.prototype.quotePattern = function (text) { return text.replace(/([\^$(){}+*?\-|\[\]\:\\])/g, '\\$1'); }; MyFindTeX.prototype.endPattern = function (end, endp) { return new RegExp((endp || this.quotePattern(end)) + '|\\\\(?:[a-zA-Z]|.)|[{}]', 'g'); }; MyFindTeX.prototype.findEnd = function (text, n, start, end) { let [close, display, pattern] = end; let i = pattern.lastIndex = start.index + start[0].length; let match; while ((match = pattern.exec(text))) { if ((match[1] || match[0]) === close) { return this.protoItem(start[0], text.substr(i, match.index - i), match[0], n, start.index, match.index + match[0].length, display); } } return null; }; MyFindTeX.prototype.findMath = function (strings) { let math = []; for (let i = 0, m = strings.length; i < m; i++) { this.findMathInString(math, i, strings[i]); } return math; }; MyFindTeX.prototype.protoItem = function (open, math, close, n, start, end, display = null) { let thaiMath = math.replace(this.regexThaiCharacter, function (match, has_textcmd_before, thai_content) { return has_textcmd_before ? match : "\\text{" + thai_content + "}"; }); return { open: open, math: thaiMath, close: close, n: n, start: { n: start }, end: { n: end }, display: display }; }; MyFindTeX.prototype.findMathInString = function (math, n, text) { let start, match; this.start.lastIndex = 0; while ((start = this.start.exec(text))) { if (start[this.env] !== undefined && this.env) { let end = '\\\\end\\s*(\\{' + this.quotePattern(start[this.env]) + '\\})'; match = this.findEnd(text, n, start, ['{' + start[this.env] + '}', true, this.endPattern(null, end)]); if (match) { match.math = match.open + match.math + match.close; match.open = match.close = ''; } } else if (start[this.sub] !== undefined && this.sub) { let math = start[this.sub]; let end = start.index + start[this.sub].length; if (math.length === 2) { match = this.protoItem('', math.substr(1), '', n, start.index, end); } else { match = this.protoItem('', math, '', n, start.index, end, false); } } else { match = this.findEnd(text, n, start, this.end[start[0]]); } if (match) { math.push(match); this.start.lastIndex = match.end.n; } } }; return MyFindTeX; }()); const findTeXOptions = { inlineMath: [ ['\\(', '\\)'], ['$', '$'], ], displayMath: [ ['\\[', '\\]'], ['$$', '$$'] ], processEscapes: true, processEnvironments: false, processRefs: false, }; const myFindTeX = new MyFindTeX(findTeXOptions); window.MathJax = { tex: { FindTeX: myFindTeX, macros: { bmatrix: ['\\begin{bmatrix}#1\\end{bmatrix}', 1], Bmatrix: ['\\begin{Bmatrix}#1\\end{Bmatrix}', 1], vmatrix: ['\\begin{vmatrix}#1\\end{vmatrix}', 1], Vmatrix: ['\\begin{Vmatrix}#1\\end{Vmatrix}', 1], cases: ['\\begin{cases}#1\\end{cases}', 1], euler: '\\atopwithdelims<>', sech: '\\text{sech}\\,' } }, svg: { mtextInheritFont: true, scale: 1, fontCache: 'global' } };
จะสังเกตว่า Object instance ตัวเริ่มต้นของ MathJaX ใช้ค่า Option ต่อไปนี้เท่านั้น ค่า Option อื่นของ TeX นอกเหนือจากนี้ให้กำหนดนอก FindTeX