In MathJax-node, there is an option called cjkCharWidth
to control the width of CJK character: https://github.com/mathjax/MathJax-node#typesetoptions-callback
However, in MathJax3 this option is no longer available. I read the documentation and source code but didn't get help. Thus I would like to ask if there is a way to achieve similar functionality in MathJax3? Thank you!
I'm assuming you are working in a node application on the server, as in the browser the width should be measure properly automatically.
Are you using MathJax components, or direct calls to the MathJax modules (see the MathJax node examples for illustrations of each)? The way you would handle this depends on which approach you are using.
Thank you for your reply. Yes, I am working in a node application on the server, and the program is based on this demo:
https://github.com/mathjax/MathJax-demos-node/blob/master/direct/tex2svg-page
OK, here is a modified version of tex2svg
that allows you to specify the CJK width (and normal width as well).
#! /usr/bin/env -S node -r esm
/*************************************************************************
*
* direct/tex2svg
*
* Uses MathJax v3 to convert a TeX string to an SVG string.
*
* ----------------------------------------------------------------------
*
* Copyright (c) 2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Load the packages needed for MathJax
//
const mathjax = require('mathjax-full/js/mathjax.js').mathjax;
const TeX = require('mathjax-full/js/input/tex.js').TeX;
const SVG = require('mathjax-full/js/output/svg.js').SVG;
const LiteAdaptor = require('mathjax-full/js/adaptors/liteAdaptor.js').LiteAdaptor;
const RegisterHTMLHandler = require('mathjax-full/js/handlers/html.js').RegisterHTMLHandler;
const AllPackages = require('mathjax-full/js/input/tex/AllPackages.js').AllPackages;
//
// Subclass the LiteAdaptor to modify how widths are determined
//
class myAdaptor extends LiteAdaptor {
isCJK(c) {
return (
(c >= 0x3400 && c < 0x4DC0) ||
(c >= 0x4E00 && c < 0xA000) ||
(c >= 0xF900 && c < 0xFB00) ||
(c >= 0x20000 && c < 0xE0000)
);
}
nodeSize(node, em = 1, local = null) {
const cjk = this.options.cjkWidth;
const width = this.options.normalWidth;
const text = this.textContent(node);
let w = 0;
for (const c of text.split('')) {
w += (this.isCJK(c.codePointAt(0)) ? cjk : width);
}
return [w / this.options.em, 0];
}
}
myAdaptor.OPTIONS = {
...LiteAdaptor.OPTIONS,
cjkWidth: 13, // the default CJK width
normalWidth: 8.5, // the default normal (monospace) width
em: 16 // the default em size
};
//
// Get the command-line arguments
//
var argv = require('yargs')
.demand(0).strict()
.usage('$0 [options] "math" > file.svg')
.options({
inline: {
boolean: true,
describe: "process as inline math"
},
em: {
default: 16,
describe: 'em-size in pixels'
},
ex: {
default: 8,
describe: 'ex-size in pixels'
},
width: {
default: 80 * 16,
describe: 'width of container in pixels'
},
cjkWidth: {
default: 13,
describe: 'width of CJK characters'
},
normalWidth: {
default: 8.5,
describe: 'width of unknown non-cjk characters'
},
packages: {
default: AllPackages.sort().join(', '),
describe: 'the packages to use, e.g. "base, ams"'
},
css: {
boolean: true,
describe: 'output the required CSS rather than the SVG itself'
},
fontCache: {
boolean: true,
default: true,
describe: 'whether to use a local font cache or not'
}
})
.argv;
//
// Create DOM adaptor and register it for HTML documents
//
const adaptor = new myAdaptor({
cjkWidth: argv.cjkWidth,
normalWidth: argv.normalWidth,
em: argv.em
});
RegisterHTMLHandler(adaptor);
//
// Create input and output jax and a document using them on the content from the HTML file
//
const tex = new TeX({packages: argv.packages.split(/\s*,\s*/)});
const svg = new SVG({fontCache: (argv.fontCache ? 'local' : 'none')});
const html = mathjax.document('', {InputJax: tex, OutputJax: svg});
//
// Typeset the math from the command line
//
const node = html.convert(argv._[0] || '', {
display: !argv.inline,
em: argv.em,
ex: argv.ex,
containerWidth: argv.width
});
//
// If the --css option was specified, output the CSS,
// Otherwise, typeset the math and output the HTML
//
if (argv.css) {
console.log(adaptor.textContent(svg.styleSheet(html)));
} else {
console.log(adaptor.outerHTML(node));
}
The main change is the addition of the myAdaptor
class that implements the new measuring of unknown text. Note in particular the change in capitalization when loading LiteAdaptor
rather than liteAdaptor
in the require()
statements at the top.
I also added command-line options to allow you to control the values without altering the program. These are passed to the new myAdaptor()
call (that replaces liteAdaptor()
) as options.
I think that should do what you want.
Thanks, the calculation of width is perfect now.
In addition, I found a npm package named string-width, which is used to determine the width of Unicode characters, because codePointAt
does not always seem accurate in some languages. Hope this helps more people.
OK, here is a modified version of
tex2svg
that allows you to specify the CJK width (and normal width as well).#! /usr/bin/env -S node -r esm /************************************************************************* * * direct/tex2svg * * Uses MathJax v3 to convert a TeX string to an SVG string. * * ---------------------------------------------------------------------- * * Copyright (c) 2018 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // Load the packages needed for MathJax // const mathjax = require('mathjax-full/js/mathjax.js').mathjax; const TeX = require('mathjax-full/js/input/tex.js').TeX; const SVG = require('mathjax-full/js/output/svg.js').SVG; const LiteAdaptor = require('mathjax-full/js/adaptors/liteAdaptor.js').LiteAdaptor; const RegisterHTMLHandler = require('mathjax-full/js/handlers/html.js').RegisterHTMLHandler; const AllPackages = require('mathjax-full/js/input/tex/AllPackages.js').AllPackages; // // Subclass the LiteAdaptor to modify how widths are determined // class myAdaptor extends LiteAdaptor { nodeSize(node, em = 1, local = null) { const cjk = this.options.cjkWidth; const width = this.options.normalWidth; const text = this.textContent(node); let w = 0; for (const c of text.split('')) { w += (c.codePointAt(0) ? cjk : width); } return [w, 0]; } } myAdaptor.OPTIONS = { ...LiteAdaptor.OPTIONS, cjkWidth: 13, // the default CJK width normalWidth: 8.5 // the default normal (monospace) width }; // // Get the command-line arguments // var argv = require('yargs') .demand(0).strict() .usage('$0 [options] "math" > file.svg') .options({ inline: { boolean: true, describe: "process as inline math" }, em: { default: 16, describe: 'em-size in pixels' }, ex: { default: 8, describe: 'ex-size in pixels' }, width: { default: 80 * 16, describe: 'width of container in pixels' }, cjkWidth: { default: 13, describe: 'width of CJK characters' }, normalWidth: { default: 8.5, describe: 'width of unknown non-cjk characters' }, packages: { default: AllPackages.sort().join(', '), describe: 'the packages to use, e.g. "base, ams"' }, css: { boolean: true, describe: 'output the required CSS rather than the SVG itself' }, fontCache: { boolean: true, default: true, describe: 'whether to use a local font cache or not' } }) .argv; // // Create DOM adaptor and register it for HTML documents // const adaptor = new myAdaptor({ cjkWidth: argv.cjkWidth, normalWidth: argv.normalWidth }); RegisterHTMLHandler(adaptor); // // Create input and output jax and a document using them on the content from the HTML file // const tex = new TeX({packages: argv.packages.split(/\s*,\s*/)}); const svg = new SVG({fontCache: (argv.fontCache ? 'local' : 'none')}); const html = mathjax.document('', {InputJax: tex, OutputJax: svg}); // // Typeset the math from the command line // const node = html.convert(argv._[0] || '', { display: !argv.inline, em: argv.em, ex: argv.ex, containerWidth: argv.width }); // // If the --css option was specified, output the CSS, // Otherwise, typeset the math and output the HTML // if (argv.css) { console.log(adaptor.textContent(svg.styleSheet(html))); } else { console.log(adaptor.outerHTML(node)); }
The main change is the addition of the
myAdaptor
class that implements the new measuring of unknown text. Note in particular the change in capitalization when loadingLiteAdaptor
rather thanliteAdaptor
in therequire()
statements at the top.I also added command-line options to allow you to control the values without altering the program. These are passed to the
new myAdaptor()
call (that replacesliteAdaptor()
) as options.I think that should do what you want.
I'm working on mathjax-node v3, and also get stuck in this issue.
but i'm using this template of tex2svg-page
https://github.com/mathjax/MathJax-demos-node/blob/master/component/tex2svg-page, really has no clue on how to modified it after reading your modification of another version. Would you pls help me on modifying this template, thanks.
@mathedu4all, The example that corresponds to the one I modified above is the direct tex2svg-page one. You should be able to make the corresponding changes there. Is there a reason that you need to use the component-based version rather than the direct one?
The changes you have made are basically just setting the options, and that can also be done in the direct version, but they just aren't all in one place. For example, the TeX input options are in the line
https://github.com/mathjax/MathJax-demos-node/blob/567dd623affac2830f6b3a8556c7ffc8e9d34fed/direct/tex2svg-page#L77 https://github.com/mathjax/MathJax-demos-node/blob/567dd623affac2830f6b3a8556c7ffc8e9d34fed/direct/tex2svg-page#L77
See if you are able to make that work for you.
thanks for your kind reply and contribution to the mathjax. It helps me a lot.
This works for me, but I'm curious what should be returned in nodeSize
. The implementation of LiteAdaptor
returns 0.6 for each character, but in the code above you use 8.5 for normal width and 13 for CJK width. This is a little confusing to me.
@perqin, you are right the code I posted is broken. The result should be the size in ems not in pixels, which is what my code returned. I have modified my example above (so that anyone copying will get the corrected version).
There was also a problem that the CJK width was being applied to ALL characters (that aren't in the MathJax fonts), not just the CJK ones. I've added a function to decide when to apply the CJK width. I'm not really sure which Unicode ranges should be included, but I think this should handle most situations. Modify the function as needed.
Sorry about the errors in the original. Hope this resolves any issues.
There is now a PR for this.
Most helpful comment
There is now a PR for this.