Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F6051652
makeSvgsAndCss.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
makeSvgsAndCss.js
View Options
#!/usr/bin/env node
'use strict'
;
/* eslint-disable no-use-before-define */
function
generateCSS
(
symbolsFile
,
cssFile
,
inputType
)
{
let
cssRules
=
[];
// Whole CSS rules
const
rerenderAll
=
process
.
argv
.
slice
(
2
).
includes
(
'--all'
),
unmodifiedClasses
=
{},
cssClasses
=
{},
// Unique part of class name and whether baseline is shifted
generatedRules
=
[],
currentRule
=
[],
symbolList
=
[],
// Symbols whose CSS rules need to be added or adjusted
cssPrefix
=
'.ve-ui-mwLatexSymbol-'
,
fs
=
require
(
'fs'
),
http
=
require
(
'http'
),
querystring
=
require
(
'querystring'
),
mathoidMaxConnections
=
20
,
// If symbol.alignBaseline is true, a background-position property will be added to the
// CSS rule to shift the baseline of the SVG to be a certain proportion of the way up the
// button.
singleButtonHeight
=
1.8
,
// Height of the single-height math dialog buttons in em
baseline
=
0.65
;
// Proportion of the way down the button the baseline should be
// eslint-disable-next-line security/detect-non-literal-fs-filename
const
symbolsData
=
fs
.
readFileSync
(
symbolsFile
).
toString
();
let
cssData
;
try
{
// eslint-disable-next-line security/detect-non-literal-fs-filename
cssData
=
fs
.
readFileSync
(
cssFile
).
toString
();
}
catch
(
e
)
{}
function
encodeURIComponentForCSS
(
str
)
{
return
encodeURIComponent
(
str
)
.
replace
(
/[!'*()]/g
,
(
chr
)
=>
'%'
+
chr
.
charCodeAt
(
0
).
toString
(
16
)
);
}
/**
* Make the className, replacing any non-alphanumerics with their character code
*
* The reverse of function would look like this, although we have no use for it yet:
*
* return className.replace( /_([0-9]+)_/g, ( all, one ) => String.fromCharCode( +one ) } );
*
* @param {string} tex TeX input
* @return {string} Class name
*/
function
texToClass
(
tex
)
{
return
tex
.
replace
(
/[^\w]/g
,
(
c
)
=>
'_'
+
c
.
charCodeAt
(
0
)
+
'_'
);
}
function
makeRequest
(
symbol
)
{
const
tex
=
symbol
.
tex
||
symbol
.
insert
,
data
=
querystring
.
stringify
(
{
q
:
inputType
===
'chem'
?
'\\ce{'
+
tex
+
'}'
:
tex
,
type
:
inputType
}
),
// API call to mathoid
options
=
{
host
:
'localhost'
,
port
:
'10044'
,
path
:
'/'
,
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/x-www-form-urlencoded'
,
'Content-Length'
:
Buffer
.
byteLength
(
data
)
}
};
// Populate and make the API call
const
request
=
http
.
request
(
options
,
(
res
)
=>
{
let
body
=
''
;
res
.
setEncoding
(
'utf8'
);
res
.
on
(
'data'
,
(
innerData
)
=>
{
body
+=
innerData
;
}
);
res
.
on
(
'end'
,
()
=>
{
const
className
=
texToClass
(
tex
),
bodyData
=
JSON
.
parse
(
body
),
svg
=
bodyData
.
svg
;
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
generatedRules
,
className
)
)
{
console
.
log
(
className
+
' already generated'
);
onEnd
();
return
;
}
generatedRules
[
className
]
=
true
;
if
(
!
svg
)
{
console
.
log
(
tex
+
' FAILED: '
+
body
);
onEnd
();
return
;
}
let
cssRule
=
cssPrefix
+
className
+
' {\n'
+
'\tbackground-image: url( data:image/svg+xml,'
+
encodeURIComponentForCSS
(
svg
)
+
' );\n'
;
if
(
symbol
.
alignBaseline
)
{
// Convert buttonHeight from em to ex, because SVG height is given in ex. (This is an
// approximation, since the em:ex ratio differs from font to font.)
const
buttonHeight
=
symbol
.
largeLayout
?
singleButtonHeight
*
4
:
singleButtonHeight
*
1.9931
;
// height and verticalAlign rely on the format of the SVG parameters
// HACK: Adjust these by a factor of 0.8 to match VE's default font size of 0.8em
const
height
=
parseFloat
(
bodyData
.
mathoidStyle
.
match
(
/height:\s*([\d.]+)ex/
)[
1
]
)
*
0.8
;
const
verticalAlign
=
-
parseFloat
(
bodyData
.
mathoidStyle
.
match
(
/vertical-align:\s*([-\d.]+)ex/
)[
1
]
)
*
0.8
;
// CSS percentage positioning is based on the difference between the image and container sizes
const
heightDifference
=
buttonHeight
-
height
;
const
offset
=
100
*
(
verticalAlign
-
height
+
(
baseline
*
buttonHeight
)
)
/
heightDifference
;
cssRule
+=
'\tbackground-position: 50% '
+
offset
+
'%;\n'
+
'}'
;
cssRules
.
push
(
cssRule
);
console
.
log
(
tex
+
' -> '
+
className
);
}
else
{
cssRule
+=
'}'
;
cssRules
.
push
(
cssRule
);
console
.
log
(
tex
+
' -> '
+
className
);
}
onEnd
();
}
);
}
);
request
.
setTimeout
(
10000
);
request
.
write
(
data
);
request
.
end
();
runNext
();
}
function
onEnd
()
{
count
--
;
runNext
();
}
function
runNext
()
{
if
(
count
<
mathoidMaxConnections
&&
symbolList
.
length
)
{
count
++
;
makeRequest
(
symbolList
.
shift
()
);
}
if
(
!
symbolList
.
length
&&
!
count
)
{
cssRules
.
sort
();
// eslint-disable-next-line security/detect-non-literal-fs-filename
fs
.
writeFileSync
(
cssFile
,
'/*!\n'
+
' * This file is GENERATED by tools/makeSvgsAndCss.js\n'
+
' * DO NOT EDIT\n'
+
' */\n'
+
'/* stylelint-disable plugin/no-unsupported-browser-features */'
+
'\n'
+
cssRules
.
join
(
'\n\n'
)
+
'\n'
);
}
}
if
(
cssData
)
{
let
currentClassName
;
const
cssLines
=
cssData
.
split
(
'\n'
);
for
(
let
i
=
0
;
i
<
cssLines
.
length
;
i
++
)
{
if
(
cssLines
[
i
].
indexOf
(
cssPrefix
)
===
0
)
{
currentClassName
=
cssLines
[
i
].
slice
(
cssPrefix
.
length
,
-
2
);
currentRule
.
push
(
cssLines
[
i
]
);
cssClasses
[
currentClassName
]
=
false
;
// Default to false
}
else
if
(
currentRule
.
length
)
{
currentRule
.
push
(
cssLines
[
i
]
);
if
(
cssLines
[
i
].
indexOf
(
'\tbackground-position'
)
===
0
)
{
cssClasses
[
currentClassName
]
=
true
;
}
if
(
cssLines
[
i
].
indexOf
(
'}'
)
===
0
)
{
cssRules
.
push
(
currentRule
.
join
(
'\n'
)
);
currentRule
.
splice
(
0
,
currentRule
.
length
);
}
}
}
}
const
symbolObject
=
JSON
.
parse
(
symbolsData
);
for
(
const
group
in
symbolObject
)
{
const
symbols
=
symbolObject
[
group
];
for
(
let
i
=
0
;
i
<
symbols
.
length
;
i
++
)
{
const
symbol
=
symbols
[
i
];
if
(
symbol
.
duplicate
||
symbol
.
notWorking
)
{
continue
;
}
const
currentClassName
=
texToClass
(
symbol
.
tex
||
symbol
.
insert
);
const
alignBaseline
=
!
symbol
.
alignBaseline
;
// If symbol is not in the old CSS file, or its alignBaseline status has changed,
// add it to symbolList. Check to make sure it hasn't already been added.
if
(
rerenderAll
||
cssClasses
[
currentClassName
]
===
undefined
||
(
unmodifiedClasses
[
currentClassName
]
!==
true
&&
cssClasses
[
currentClassName
]
===
alignBaseline
)
)
{
symbolList
.
push
(
symbol
);
}
else
{
// At the end of this loop, any CSS class names that aren't in unmodifiedClasses
// will be deleted from cssRules. cssRules will then only contain rules that will
// stay unmodified.
unmodifiedClasses
[
currentClassName
]
=
true
;
}
}
}
console
.
log
(
'----'
);
console
.
log
(
'Comparing '
+
cssFile
+
' and '
+
symbolsFile
);
console
.
log
(
Object
.
keys
(
cssClasses
).
length
+
' images found in '
+
cssFile
);
console
.
log
(
symbolList
.
length
+
' symbols need rendering'
);
if
(
!
rerenderAll
)
{
console
.
log
(
Object
.
keys
(
unmodifiedClasses
).
length
+
' symbols already rendered'
);
console
.
log
(
'To re-render all symbols, use --all'
);
}
// Keep only classes that will stay the same. Remove classes that are being adjusted and
// classes of symbols that have been deleted from the JSON.
cssRules
=
cssRules
.
filter
(
(
rule
)
=>
{
const
currentClassName
=
rule
.
split
(
'\n'
)[
0
].
slice
(
cssPrefix
.
length
,
-
2
);
if
(
unmodifiedClasses
[
currentClassName
]
)
{
return
true
;
}
console
.
log
(
'Removing or adjusting: '
+
currentClassName
);
return
false
;
}
);
let
count
=
0
;
runNext
();
}
generateCSS
(
'../mathSymbols.json'
,
'../ve.ui.MWMathSymbols.css'
,
'tex'
);
generateCSS
(
'../chemSymbols.json'
,
'../ve.ui.MWChemSymbols.css'
,
'chem'
);
File Metadata
Details
Attached
Mime Type
application/javascript
Expires
Oct 4 2026, 23:31 (5 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ce/13/cdfa5375cacccb0d6209d0b60223
Default Alt Text
makeSvgsAndCss.js (7 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment