|
|
|
import defineFunction from "../defineFunction"; |
|
import type {Measurement} from "../units"; |
|
import {calculateSize, validUnit, makeEm} from "../units"; |
|
import ParseError from "../ParseError"; |
|
import {Img} from "../domTree"; |
|
import mathMLTree from "../mathMLTree"; |
|
import {assertNodeType} from "../parseNode"; |
|
import type {CssStyle} from "../domTree"; |
|
|
|
const sizeData = function(str: string): Measurement { |
|
if (/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(str)) { |
|
|
|
|
|
return {number: +str, unit: "bp"}; |
|
} else { |
|
const match = (/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/).exec(str); |
|
if (!match) { |
|
throw new ParseError("Invalid size: '" + str |
|
+ "' in \\includegraphics"); |
|
} |
|
const data = { |
|
number: +(match[1] + match[2]), |
|
unit: match[3], |
|
}; |
|
if (!validUnit(data)) { |
|
throw new ParseError("Invalid unit: '" + data.unit |
|
+ "' in \\includegraphics."); |
|
} |
|
return data; |
|
} |
|
}; |
|
|
|
defineFunction({ |
|
type: "includegraphics", |
|
names: ["\\includegraphics"], |
|
props: { |
|
numArgs: 1, |
|
numOptionalArgs: 1, |
|
argTypes: ["raw", "url"], |
|
allowedInText: false, |
|
}, |
|
handler: ({parser}, args, optArgs) => { |
|
let width = {number: 0, unit: "em"}; |
|
let height = {number: 0.9, unit: "em"}; |
|
let totalheight = {number: 0, unit: "em"}; |
|
let alt = ""; |
|
|
|
if (optArgs[0]) { |
|
const attributeStr = assertNodeType(optArgs[0], "raw").string; |
|
|
|
|
|
const attributes = attributeStr.split(","); |
|
for (let i = 0; i < attributes.length; i++) { |
|
const keyVal = attributes[i].split("="); |
|
if (keyVal.length === 2) { |
|
const str = keyVal[1].trim(); |
|
switch (keyVal[0].trim()) { |
|
case "alt": |
|
alt = str; |
|
break; |
|
case "width": |
|
width = sizeData(str); |
|
break; |
|
case "height": |
|
height = sizeData(str); |
|
break; |
|
case "totalheight": |
|
totalheight = sizeData(str); |
|
break; |
|
default: |
|
throw new ParseError("Invalid key: '" + keyVal[0] + |
|
"' in \\includegraphics."); |
|
} |
|
} |
|
} |
|
} |
|
|
|
const src = assertNodeType(args[0], "url").url; |
|
|
|
if (alt === "") { |
|
|
|
alt = src; |
|
alt = alt.replace(/^.*[\\/]/, ''); |
|
alt = alt.substring(0, alt.lastIndexOf('.')); |
|
} |
|
|
|
if (!parser.settings.isTrusted({ |
|
command: "\\includegraphics", |
|
url: src, |
|
})) { |
|
return parser.formatUnsupportedCmd("\\includegraphics"); |
|
} |
|
|
|
return { |
|
type: "includegraphics", |
|
mode: parser.mode, |
|
alt: alt, |
|
width: width, |
|
height: height, |
|
totalheight: totalheight, |
|
src: src, |
|
}; |
|
}, |
|
htmlBuilder: (group, options) => { |
|
const height = calculateSize(group.height, options); |
|
let depth = 0; |
|
|
|
if (group.totalheight.number > 0) { |
|
depth = calculateSize(group.totalheight, options) - height; |
|
} |
|
|
|
let width = 0; |
|
if (group.width.number > 0) { |
|
width = calculateSize(group.width, options); |
|
} |
|
|
|
const style: CssStyle = {height: makeEm(height + depth)}; |
|
if (width > 0) { |
|
style.width = makeEm(width); |
|
} |
|
if (depth > 0) { |
|
style.verticalAlign = makeEm(-depth); |
|
} |
|
|
|
const node = new Img(group.src, group.alt, style); |
|
node.height = height; |
|
node.depth = depth; |
|
|
|
return node; |
|
}, |
|
mathmlBuilder: (group, options) => { |
|
const node = new mathMLTree.MathNode("mglyph", []); |
|
node.setAttribute("alt", group.alt); |
|
|
|
const height = calculateSize(group.height, options); |
|
let depth = 0; |
|
if (group.totalheight.number > 0) { |
|
depth = calculateSize(group.totalheight, options) - height; |
|
node.setAttribute("valign", makeEm(-depth)); |
|
} |
|
node.setAttribute("height", makeEm(height + depth)); |
|
|
|
if (group.width.number > 0) { |
|
const width = calculateSize(group.width, options); |
|
node.setAttribute("width", makeEm(width)); |
|
} |
|
node.setAttribute("src", group.src); |
|
return node; |
|
}, |
|
}); |
|
|