{"version":3,"file":"email-exporter.cjs","names":[],"sources":["../src/exporterHelpers/renderMathToImage.ts","../src/email-exporter/index.tsx"],"sourcesContent":["import type { ExportImage } from \"@blocknote/core\";\nimport { liteAdaptor } from \"mathjax-full/js/adaptors/liteAdaptor.js\";\nimport { RegisterHTMLHandler } from \"mathjax-full/js/handlers/html.js\";\nimport { TeX } from \"mathjax-full/js/input/tex.js\";\nimport TexError from \"mathjax-full/js/input/tex/TexError.js\";\nimport { mathjax } from \"mathjax-full/js/mathjax.js\";\n\n// `mathjax-full` ships CommonJS, and depending on the consumer's bundler\n// interop the default import above is either the class itself or a\n// `{ default: class }` namespace object (observed with Vite serving the\n// package from source). Resolve whichever is the constructor - using the\n// namespace directly makes `instanceof` throw \"Right-hand side of\n// 'instanceof' is not callable\" on the first invalid formula.\nfunction isTexError(error: unknown): error is InstanceType<typeof TexError> {\n  const texErrorClass: unknown = (TexError as any).default ?? TexError;\n  return typeof texErrorClass === \"function\" && error instanceof texErrorClass;\n}\nimport { SVG } from \"mathjax-full/js/output/svg.js\";\n\n// Registers the TeX packages listed in `TEX_PACKAGES` below - a curated set\n// with roughly KaTeX's coverage (what the editor itself renders), rather\n// than `AllPackages`, which would pull every package (mhchem, physics,\n// bussproofs, ...) into the bundle.\nimport \"mathjax-full/js/input/tex/ams/AmsConfiguration.js\";\nimport \"mathjax-full/js/input/tex/boldsymbol/BoldsymbolConfiguration.js\";\nimport \"mathjax-full/js/input/tex/braket/BraketConfiguration.js\";\nimport \"mathjax-full/js/input/tex/cancel/CancelConfiguration.js\";\nimport \"mathjax-full/js/input/tex/color/ColorConfiguration.js\";\nimport \"mathjax-full/js/input/tex/mathtools/MathtoolsConfiguration.js\";\nimport \"mathjax-full/js/input/tex/newcommand/NewcommandConfiguration.js\";\nimport \"mathjax-full/js/input/tex/noundefined/NoUndefinedConfiguration.js\";\nimport \"mathjax-full/js/input/tex/textmacros/TextMacrosConfiguration.js\";\nimport \"mathjax-full/js/input/tex/unicode/UnicodeConfiguration.js\";\n\nconst TEX_PACKAGES = [\n  \"base\",\n  \"ams\",\n  \"boldsymbol\",\n  \"braket\",\n  \"cancel\",\n  \"color\",\n  \"mathtools\",\n  \"newcommand\",\n  \"noundefined\",\n  \"textmacros\",\n  \"unicode\",\n];\n\n// MathJax (rather than KaTeX, which the math block itself renders with) is\n// used for image-based export: its SVG output is self-contained paths, so it\n// rasterizes without needing the KaTeX webfonts. `mathjax-full` is an\n// optional peer dependency, needed only for these image-based exports.\nlet mathDocument: ReturnType<typeof mathjax.document> | undefined;\nlet documentAdaptor: ReturnType<typeof liteAdaptor> | undefined;\n\nfunction getMathDocument() {\n  if (!mathDocument || !documentAdaptor) {\n    documentAdaptor = liteAdaptor();\n    RegisterHTMLHandler(documentAdaptor);\n    mathDocument = mathjax.document(\"\", {\n      InputJax: new TeX({\n        packages: TEX_PACKAGES,\n        // MathJax renders TeX errors as error text by default - throw\n        // instead, so the conversion boundary in `latexToMathSVG` can return\n        // them as typed errors.\n        formatError: (_jax: unknown, err: TexError) => {\n          throw err;\n        },\n      }),\n      OutputJax: new SVG({ fontCache: \"none\" }),\n    });\n  }\n  return { mathDocument, documentAdaptor };\n}\n\n/**\n * An {@link ExportImage} known to hold SVG markup - what\n * {@link latexToMathSVG} produces and rasterizers consume, so passing a\n * raster image to a rasterizer is a compile-time error.\n */\nexport type SVGExportImage = ExportImage & { mimeType: \"image/svg+xml\" };\n\n// The ex height (x-height) of MathJax's font, as a fraction of its em size -\n// MathJax sizes its SVG output in `ex` units, and this converts them to the\n// target's units via the surrounding font size.\nconst EX_PER_EM = 0.442;\n\n/**\n * Converts LaTeX to a self-contained {@link SVGExportImage} (via MathJax).\n * Synchronous and environment-independent. Invalid LaTeX is expected (the\n * source is user input), so it's returned as a typed error - with a message\n * safe to show to readers - rather than thrown.\n *\n * The image's `width`/`height` are display dimensions, derived from\n * `fontSize` - the surrounding font's size in the target's units (points,\n * CSS pixels, ...) - and also set as the SVG's intrinsic dimensions, so\n * renderers display it at the right size.\n */\nexport function latexToMathSVG(\n  latex: string,\n  options: { inline: boolean; fontSize: number },\n): { error?: undefined; image: SVGExportImage } | { error: string } {\n  const { mathDocument, documentAdaptor } = getMathDocument();\n\n  let node: unknown;\n  try {\n    node = mathDocument.convert(latex, { display: !options.inline });\n  } catch (error) {\n    // The boundary that converts MathJax's TeX-error throw (see\n    // `formatError` above) into the typed result. Only `TexError`s are\n    // expected (invalid user LaTeX) - anything else is a bug and propagates.\n    if (!isTexError(error)) {\n      throw error;\n    }\n    return { error: error.message };\n  }\n\n  // The conversion returns an `mjx-container` wrapper; only the `svg`\n  // element itself is needed.\n  const svgNode = documentAdaptor.firstChild(node as any) as any;\n  const widthEx = parseFloat(documentAdaptor.getAttribute(svgNode, \"width\"));\n  const heightEx = parseFloat(documentAdaptor.getAttribute(svgNode, \"height\"));\n  if (\n    documentAdaptor.kind(svgNode) !== \"svg\" ||\n    isNaN(widthEx) ||\n    isNaN(heightEx)\n  ) {\n    throw new Error(\"No SVG found in MathJax output\");\n  }\n\n  // MathJax sizes the SVG in `ex` units; replace them with explicit pixel\n  // dimensions, or renderers fall back to a default size.\n  const width = widthEx * options.fontSize * EX_PER_EM;\n  const height = heightEx * options.fontSize * EX_PER_EM;\n  documentAdaptor.setAttribute(svgNode, \"width\", Math.ceil(width));\n  documentAdaptor.setAttribute(svgNode, \"height\", Math.ceil(height));\n\n  return {\n    image: {\n      mimeType: \"image/svg+xml\",\n      data: new TextEncoder().encode(documentAdaptor.outerHTML(svgNode)),\n      width,\n      height,\n    },\n  };\n}\n\n/**\n * Rasterizes an {@link SVGExportImage} (from {@link latexToMathSVG}) to a\n * raster image - rendered above its display size so it stays sharp in the\n * exported document, at a scale of the implementation's choosing; the\n * returned image keeps the display dimensions. The default implementation\n * ({@link rasterizeSVGInBrowser}) needs a browser; exporters running\n * elsewhere plug in their own (e.g. backed by `@resvg/resvg-js`'s `fitTo`\n * zoom or `sharp`'s density).\n */\nexport type RasterizeSVG = (svg: SVGExportImage) => Promise<ExportImage>;\n\nconst DEFAULT_RASTER_SCALE = 2;\n\n/**\n * Rasterizes an {@link SVGExportImage} to a PNG via a canvas, at `scale`\n * times its display size. The screen's device pixel ratio is deliberately\n * not consulted for the scale, since the output goes into documents, not\n * onto the current screen. Browser-only.\n */\nexport async function rasterizeSVGInBrowser(\n  svg: SVGExportImage,\n  scale: number = DEFAULT_RASTER_SCALE,\n): Promise<ExportImage> {\n  const width = Math.max(1, Math.ceil(svg.width * scale));\n  const height = Math.max(1, Math.ceil(svg.height * scale));\n\n  // The scale goes into the SVG's intrinsic dimensions (rather than only\n  // the canvas): browsers rasterize an SVG image at its intrinsic size and\n  // upscale the bitmap when drawn larger, which would blur the output.\n  const svgElement = new DOMParser().parseFromString(\n    new TextDecoder().decode(svg.data),\n    \"image/svg+xml\",\n  ).documentElement;\n  svgElement.setAttribute(\"width\", String(width));\n  svgElement.setAttribute(\"height\", String(height));\n\n  const image = new Image();\n  image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(\n    new XMLSerializer().serializeToString(svgElement),\n  )}`;\n  await image.decode();\n\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = width;\n  canvas.height = height;\n  canvas.getContext(\"2d\")!.drawImage(image, 0, 0, canvas.width, canvas.height);\n\n  const blob = await new Promise<Blob | null>((resolve) =>\n    canvas.toBlob(resolve, \"image/png\"),\n  );\n  if (!blob) {\n    throw new Error(\"Canvas produced no PNG data\");\n  }\n\n  return {\n    mimeType: \"image/png\",\n    data: new Uint8Array(await blob.arrayBuffer()),\n    width: svg.width,\n    height: svg.height,\n  };\n}\n","import type {\n  BlockConfig,\n  BlockFromConfigNoChildren,\n  Exporter,\n} from \"@blocknote/core\";\nimport { plainContentToString } from \"@blocknote/core\";\nimport {\n  dataURLImageDelivery,\n  ReactEmailImageDelivery,\n} from \"@blocknote/xl-email-exporter\";\nimport { Img, Text } from \"@react-email/components\";\n\nimport {\n  latexToMathSVG,\n  RasterizeSVG,\n  rasterizeSVGInBrowser,\n} from \"../exporterHelpers/renderMathToImage.js\";\n\ntype MathBlock = BlockFromConfigNoChildren<\n  BlockConfig<\"mathBlock\", {}, \"plain\">,\n  any,\n  any\n>;\n\ntype InlineMath = { type: \"math\"; content: string };\n\nexport {\n  latexToMathSVG,\n  rasterizeSVGInBrowser,\n} from \"../exporterHelpers/renderMathToImage.js\";\nexport type {\n  RasterizeSVG,\n  SVGExportImage,\n} from \"../exporterHelpers/renderMathToImage.js\";\nimport { getMathExporterDictionary } from \"../i18n/dictionary.js\";\n\ntype MathImageOptions = {\n  /**\n   * Rasterizes the formula SVG to a raster image. Defaults to the built-in\n   * canvas rasterizer in the browser; elsewhere (e.g. server-side email\n   * rendering), the formula is embedded as an SVG instead - pass a\n   * rasterizer (e.g. backed by `@resvg/resvg-js` or `sharp`) to get PNGs\n   * there, which more email clients display.\n   */\n  rasterize?: RasterizeSVG;\n  /**\n   * How generated images get into the email: embedded as data URLs\n   * (default), or e.g. as inline `cid:` attachments via\n   * `createCIDImageDelivery` from `@blocknote/xl-email-exporter` - the most\n   * widely supported option (Gmail and Outlook block data URLs).\n   */\n  imageDelivery?: ReactEmailImageDelivery;\n};\n\n// Emails render body text at 16px.\nconst FONT_SIZE_PIXELS = 16;\n\n// Mirrors the editor, which shows the error state in the preview\n// placeholder, identifying the formula by its source. The parser's message\n// is deliberately NOT rendered: it's authoring detail (and untranslated\n// English) - the editor is where the author sees and fixes it.\nfunction errorText(\n  exporter: Exporter<any, any, any, any, any, any, any>,\n  source: string,\n): string {\n  return getMathExporterDictionary(exporter).invalid_formula(source);\n}\n\n/**\n * Creates an email block mapping for `@blocknote/math-block` that renders\n * math blocks as images, with the LaTeX source as the alt text. Invalid\n * LaTeX renders an error placeholder (mirroring the editor). See\n * {@link MathImageOptions} for how images are generated and delivered:\n *\n * ```ts\n * import { createMathBlockMapping } from \"@blocknote/math-block/email-exporter\";\n *\n * new ReactEmailExporter(schema, {\n *   ...reactEmailDefaultSchemaMappings,\n *   blockMapping: {\n *     ...reactEmailDefaultSchemaMappings.blockMapping,\n *     mathBlock: createMathBlockMapping({ imageDelivery }),\n *   },\n * });\n * ```\n */\nexport function createMathBlockMapping(options?: MathImageOptions) {\n  return async (\n    block: MathBlock,\n    exporter: Exporter<any, any, any, any, any, any, any>,\n  ) => {\n    const source = plainContentToString(block.content);\n    if (!source.trim()) {\n      return <span />;\n    }\n\n    // Rasterized when possible (see `MathImageOptions.rasterize`), embedded\n    // as SVG otherwise. Rasterization and delivery failures are unexpected\n    // and propagate (failing the export) rather than being rendered to\n    // readers.\n    const rasterize =\n      options?.rasterize ??\n      (typeof document !== \"undefined\" ? rasterizeSVGInBrowser : undefined);\n\n    const result = latexToMathSVG(source, {\n      inline: false,\n      fontSize: FONT_SIZE_PIXELS,\n    });\n    if (result.error !== undefined) {\n      return (\n        <Text style={{ color: \"#999999\", textAlign: \"center\" }}>\n          {errorText(exporter, source)}\n        </Text>\n      );\n    }\n\n    const image = rasterize ? await rasterize(result.image) : result.image;\n    const src = (options?.imageDelivery ?? dataURLImageDelivery).deliver({\n      ...image,\n      name: \"math\",\n    });\n\n    return (\n      <Img\n        src={src}\n        alt={source}\n        width={Math.ceil(image.width)}\n        height={Math.ceil(image.height)}\n        style={{ display: \"block\", margin: \"0 auto\" }}\n      />\n    );\n  };\n}\n\n/**\n * Creates an email inline content mapping for `@blocknote/math-block` that\n * renders inline math as images flowing with the text, with the LaTeX\n * source as the alt text. Inline content renders synchronously, so the\n * formula is always embedded as an SVG (never rasterized) - email clients\n * that don't render SVG show the alt text. Invalid LaTeX renders an error\n * placeholder (mirroring the editor):\n *\n * ```ts\n * import { createInlineMathMapping } from \"@blocknote/math-block/email-exporter\";\n *\n * new ReactEmailExporter(schema, {\n *   ...reactEmailDefaultSchemaMappings,\n *   inlineContentMapping: {\n *     ...reactEmailDefaultSchemaMappings.inlineContentMapping,\n *     math: createInlineMathMapping({ imageDelivery }),\n *   },\n * });\n * ```\n */\nexport function createInlineMathMapping(\n  options?: Pick<MathImageOptions, \"imageDelivery\">,\n) {\n  return (\n    inlineContent: InlineMath,\n    exporter: Exporter<any, any, any, any, any, any, any>,\n  ) => {\n    const source = inlineContent.content;\n    if (!source.trim()) {\n      return <span />;\n    }\n\n    const result = latexToMathSVG(source, {\n      inline: true,\n      fontSize: FONT_SIZE_PIXELS,\n    });\n    if (result.error !== undefined) {\n      return (\n        <span style={{ color: \"#999999\" }}>{errorText(exporter, source)}</span>\n      );\n    }\n\n    const src = (options?.imageDelivery ?? dataURLImageDelivery).deliver({\n      ...result.image,\n      name: \"math\",\n    });\n\n    return (\n      <Img\n        src={src}\n        alt={source}\n        width={Math.ceil(result.image.width)}\n        height={Math.ceil(result.image.height)}\n        style={{ display: \"inline\", verticalAlign: \"middle\" }}\n      />\n    );\n  };\n}\n\n/**\n * Email block mapping for `@blocknote/math-block` with the default options -\n * see {@link createMathBlockMapping}.\n */\nexport const mathBlockMapping = createMathBlockMapping();\n\n/**\n * Email inline content mapping for `@blocknote/math-block` with the default\n * options - see {@link createInlineMathMapping}.\n */\nexport const inlineMathMapping = createInlineMathMapping();\n"],"mappings":"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"}