@barocss/math-editor 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/ADAPTERS.md +25 -14
  2. package/API-JAVASCRIPT.md +1 -1
  3. package/API-REACT.md +5 -1
  4. package/API-SESSION.md +178 -6
  5. package/API-SOLID.md +1 -1
  6. package/API-SVELTE.md +1 -1
  7. package/API-VUE.md +1 -1
  8. package/API-WEB-COMPONENT.md +3 -1
  9. package/CHANGELOG.md +106 -0
  10. package/EDITING-SCENARIOS.md +174 -0
  11. package/EMBEDDING.md +4 -0
  12. package/IMPLEMENTATION.md +122 -2
  13. package/JSON-MODEL.md +1 -0
  14. package/LATEX-GUIDE.md +30 -1
  15. package/LATEX-MODEL.md +12 -0
  16. package/LATEX-SCOPE.md +12 -3
  17. package/LICENSE +1 -3
  18. package/README.md +201 -39
  19. package/RELEASING.md +81 -10
  20. package/RENDERING-TESTS.md +79 -0
  21. package/ROADMAP.md +220 -11
  22. package/STYLING.md +157 -0
  23. package/SUPPORT.md +24 -12
  24. package/VALIDATION.md +444 -4
  25. package/dist/context-tools.d.ts +21 -0
  26. package/dist/context-tools.js +37 -0
  27. package/dist/core.d.ts +5 -1
  28. package/dist/core.js +5 -1
  29. package/dist/document-codec.d.ts +0 -1
  30. package/dist/document-codec.js +0 -1
  31. package/dist/dom/caret-geometry.d.ts +3 -0
  32. package/dist/dom/caret-geometry.js +41 -0
  33. package/dist/dom/context-keyboard.d.ts +2 -0
  34. package/dist/dom/context-keyboard.js +22 -0
  35. package/dist/dom/elements.d.ts +1 -2
  36. package/dist/dom/elements.js +2 -3
  37. package/dist/dom/latex-panel.d.ts +10 -0
  38. package/dist/dom/latex-panel.js +59 -0
  39. package/dist/dom/matrix-selection.d.ts +11 -0
  40. package/dist/dom/matrix-selection.js +30 -0
  41. package/dist/dom/menu-position.d.ts +17 -2
  42. package/dist/dom/menu-position.js +129 -16
  43. package/dist/dom/presentation-controls.d.ts +4 -0
  44. package/dist/dom/presentation-controls.js +56 -0
  45. package/dist/dom/quick-panel.d.ts +12 -0
  46. package/dist/dom/quick-panel.js +102 -0
  47. package/dist/dom/selection.d.ts +29 -0
  48. package/dist/dom/selection.js +53 -0
  49. package/dist/dom/symbol-panel.d.ts +9 -0
  50. package/dist/dom/symbol-panel.js +146 -0
  51. package/dist/dom/theme.d.ts +6 -0
  52. package/dist/dom/theme.js +68 -0
  53. package/dist/dom/toolbar-catalog.d.ts +10 -0
  54. package/dist/dom/toolbar-catalog.js +35 -0
  55. package/dist/dom/toolbar.d.ts +13 -2
  56. package/dist/dom/toolbar.js +295 -28
  57. package/dist/dom.d.ts +9 -1
  58. package/dist/dom.js +682 -151
  59. package/dist/editor-labels.d.ts +0 -1
  60. package/dist/editor-labels.js +0 -1
  61. package/dist/enter-policy.d.ts +0 -1
  62. package/dist/enter-policy.js +0 -1
  63. package/dist/fences.d.ts +0 -1
  64. package/dist/fences.js +0 -1
  65. package/dist/i18n.d.ts +0 -1
  66. package/dist/i18n.js +0 -1
  67. package/dist/index.d.ts +4 -1
  68. package/dist/index.js +4 -1
  69. package/dist/latex-insertion.d.ts +12 -0
  70. package/dist/latex-insertion.js +26 -0
  71. package/dist/latex-paste-panel.d.ts +3 -0
  72. package/dist/latex-paste-panel.js +18 -0
  73. package/dist/latex.d.ts +0 -1
  74. package/dist/latex.js +11 -1
  75. package/dist/lines.d.ts +0 -1
  76. package/dist/lines.js +0 -1
  77. package/dist/locales/en.js +39 -1
  78. package/dist/locales/en.json +39 -1
  79. package/dist/locales/ko.js +39 -1
  80. package/dist/locales/ko.json +39 -1
  81. package/dist/math-editor-toolbar.d.ts +4 -2
  82. package/dist/math-editor-toolbar.js +3 -4
  83. package/dist/math-editor.d.ts +7 -2
  84. package/dist/math-editor.js +661 -308
  85. package/dist/math-layout.d.ts +12 -0
  86. package/dist/math-layout.js +59 -0
  87. package/dist/math-spacing.d.ts +13 -0
  88. package/dist/math-spacing.js +87 -0
  89. package/dist/matrix-range.d.ts +40 -0
  90. package/dist/matrix-range.js +242 -0
  91. package/dist/matrix.d.ts +0 -1
  92. package/dist/matrix.js +0 -1
  93. package/dist/model.d.ts +4 -1
  94. package/dist/model.js +49 -5
  95. package/dist/outputs.d.ts +0 -1
  96. package/dist/outputs.js +0 -1
  97. package/dist/preferences.d.ts +24 -0
  98. package/dist/preferences.js +66 -0
  99. package/dist/presentation-controls.d.ts +10 -0
  100. package/dist/presentation-controls.js +10 -0
  101. package/dist/presentation.d.ts +13 -0
  102. package/dist/presentation.js +86 -0
  103. package/dist/quick-panel.d.ts +2 -0
  104. package/dist/quick-panel.js +17 -0
  105. package/dist/range.d.ts +2 -1
  106. package/dist/range.js +11 -1
  107. package/dist/react.d.ts +0 -1
  108. package/dist/react.js +0 -1
  109. package/dist/root-transform.d.ts +19 -0
  110. package/dist/root-transform.js +69 -0
  111. package/dist/session.d.ts +31 -2
  112. package/dist/session.js +108 -7
  113. package/dist/solid.d.ts +0 -1
  114. package/dist/solid.js +0 -1
  115. package/dist/suggestions.d.ts +13 -1
  116. package/dist/suggestions.js +153 -12
  117. package/dist/svelte.d.ts +0 -1
  118. package/dist/svelte.js +0 -1
  119. package/dist/symbol-browser.d.ts +0 -1
  120. package/dist/symbol-browser.js +0 -1
  121. package/dist/symbols.d.ts +0 -1
  122. package/dist/symbols.js +0 -1
  123. package/dist/templates.d.ts +0 -1
  124. package/dist/templates.js +0 -1
  125. package/dist/tokens.d.ts +0 -1
  126. package/dist/tokens.js +0 -1
  127. package/dist/vertical-navigation.d.ts +15 -0
  128. package/dist/vertical-navigation.js +145 -0
  129. package/dist/vue.d.ts +0 -1
  130. package/dist/vue.js +0 -1
  131. package/dist/web-component.d.ts +0 -1
  132. package/dist/web-component.js +9 -2
  133. package/package.json +11 -3
  134. package/src/fonts/KaTeX_Math-Italic.woff2 +0 -0
  135. package/src/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  136. package/src/fonts/README.md +18 -0
  137. package/src/shapes/README.md +21 -0
  138. package/src/shapes/parenthesis-bottom.svg +1 -0
  139. package/src/shapes/parenthesis-top.svg +1 -0
  140. package/src/shapes/parenthesis.svg +1 -0
  141. package/src/shapes/radical.svg +1 -0
  142. package/src/style.css +949 -173
  143. package/dist/core.d.ts.map +0 -1
  144. package/dist/core.js.map +0 -1
  145. package/dist/document-codec.d.ts.map +0 -1
  146. package/dist/document-codec.js.map +0 -1
  147. package/dist/dom/elements.d.ts.map +0 -1
  148. package/dist/dom/elements.js.map +0 -1
  149. package/dist/dom/menu-position.d.ts.map +0 -1
  150. package/dist/dom/menu-position.js.map +0 -1
  151. package/dist/dom/toolbar.d.ts.map +0 -1
  152. package/dist/dom/toolbar.js.map +0 -1
  153. package/dist/dom.d.ts.map +0 -1
  154. package/dist/dom.js.map +0 -1
  155. package/dist/editor-labels.d.ts.map +0 -1
  156. package/dist/editor-labels.js.map +0 -1
  157. package/dist/enter-policy.d.ts.map +0 -1
  158. package/dist/enter-policy.js.map +0 -1
  159. package/dist/fences.d.ts.map +0 -1
  160. package/dist/fences.js.map +0 -1
  161. package/dist/i18n.d.ts.map +0 -1
  162. package/dist/i18n.js.map +0 -1
  163. package/dist/index.d.ts.map +0 -1
  164. package/dist/index.js.map +0 -1
  165. package/dist/latex.d.ts.map +0 -1
  166. package/dist/latex.js.map +0 -1
  167. package/dist/lines.d.ts.map +0 -1
  168. package/dist/lines.js.map +0 -1
  169. package/dist/math-editor-toolbar.d.ts.map +0 -1
  170. package/dist/math-editor-toolbar.js.map +0 -1
  171. package/dist/math-editor.d.ts.map +0 -1
  172. package/dist/math-editor.js.map +0 -1
  173. package/dist/matrix.d.ts.map +0 -1
  174. package/dist/matrix.js.map +0 -1
  175. package/dist/model.d.ts.map +0 -1
  176. package/dist/model.js.map +0 -1
  177. package/dist/outputs.d.ts.map +0 -1
  178. package/dist/outputs.js.map +0 -1
  179. package/dist/range.d.ts.map +0 -1
  180. package/dist/range.js.map +0 -1
  181. package/dist/react.d.ts.map +0 -1
  182. package/dist/react.js.map +0 -1
  183. package/dist/session.d.ts.map +0 -1
  184. package/dist/session.js.map +0 -1
  185. package/dist/solid.d.ts.map +0 -1
  186. package/dist/solid.js.map +0 -1
  187. package/dist/suggestions.d.ts.map +0 -1
  188. package/dist/suggestions.js.map +0 -1
  189. package/dist/svelte.d.ts.map +0 -1
  190. package/dist/svelte.js.map +0 -1
  191. package/dist/symbol-browser.d.ts.map +0 -1
  192. package/dist/symbol-browser.js.map +0 -1
  193. package/dist/symbols.d.ts.map +0 -1
  194. package/dist/symbols.js.map +0 -1
  195. package/dist/templates.d.ts.map +0 -1
  196. package/dist/templates.js.map +0 -1
  197. package/dist/tokens.d.ts.map +0 -1
  198. package/dist/tokens.js.map +0 -1
  199. package/dist/vue.d.ts.map +0 -1
  200. package/dist/vue.js.map +0 -1
  201. package/dist/web-component.d.ts.map +0 -1
  202. package/dist/web-component.js.map +0 -1
@@ -1,8 +1,12 @@
1
+ import { transformRoot } from './root-transform.js';
2
+ import { structureEditingContext, changeContextFence } from './context-tools.js';
3
+ import { fencePairs } from './fences.js';
1
4
  import { mathLocaleAliases, translate, } from './i18n.js';
2
5
  import { insertTemplate, mathTemplates } from './templates.js';
3
- import { insertIdentityMatrix, insertMatrix } from './matrix.js';
6
+ import { activeMatrix, insertIdentityMatrix, insertMatrix, MATRIX_MAX_SIZE } from './matrix.js';
4
7
  import { insertStructure, namedFunctions, moveCaret, setText, textNodes, } from './model.js';
5
8
  import { mathSymbols } from './symbols.js';
9
+ import { wrappingKinds } from './range.js';
6
10
  const structure = (kind, glyph, label, aliases, triggers, detail) => ({
7
11
  id: kind,
8
12
  kind,
@@ -129,14 +133,58 @@ const catalog = [
129
133
  })),
130
134
  ];
131
135
  const triggers = [...new Set(catalog.flatMap((c) => c.triggers))].sort((a, b) => b.length - a.length);
136
+ /** Generate only the requested size, with identity matrices reserved for square shapes. */
137
+ function matrixDimensionSuggestions(rows, columns) {
138
+ const sizeId = rows === columns ? String(rows) : `${rows}x${columns}`;
139
+ const dimensions = { rows, columns };
140
+ const empty = {
141
+ ...mathStructures[0],
142
+ id: `matrix-${sizeId}`,
143
+ label: 'matrix.size',
144
+ labelParameters: dimensions,
145
+ detail: 'grid.dimensions',
146
+ detailParameters: dimensions,
147
+ matrixSize: rows,
148
+ matrixColumns: columns,
149
+ };
150
+ const candidates = [empty];
151
+ if (rows === columns) {
152
+ candidates.push({
153
+ ...empty,
154
+ id: `identity-${sizeId}`,
155
+ glyph: 'I',
156
+ label: 'matrix.identity',
157
+ detail: 'matrix.identityDetail',
158
+ identity: true,
159
+ });
160
+ }
161
+ candidates.push({
162
+ ...empty,
163
+ id: `template-zero-${sizeId}`,
164
+ glyph: '0',
165
+ label: 'matrix.zero',
166
+ zero: true,
167
+ });
168
+ return candidates;
169
+ }
132
170
  export function findSuggestions(text, caret, locale = 'ko') {
133
171
  const before = text.slice(0, caret);
172
+ // Size shortcuts are complete tokens, not the tail of a number or identifier.
173
+ // They remain ordinary text until the user accepts a matrix candidate.
174
+ const dimensionMatch = /^[\p{L}\p{M}\p{N}_.]/u.test(text.slice(caret))
175
+ ? null
176
+ : before.match(/(?<![\p{L}\p{M}\p{N}_.\\])([1-9]\d?)[xX×]([1-9]\d?)$/u);
177
+ const dimension = dimensionMatch &&
178
+ Number(dimensionMatch[1]) <= MATRIX_MAX_SIZE &&
179
+ Number(dimensionMatch[2]) <= MATRIX_MAX_SIZE
180
+ ? dimensionMatch
181
+ : null;
134
182
  // Recognize the longest symbolic spelling first: E/ must never become a fraction query.
135
183
  const trigger = triggers.find((t) => before.endsWith(t) &&
136
184
  (t.length > 1 || !/[A-Za-z]/.test(t) || !/[\p{L}\p{N}]/u.test(before.at(-2) ?? '')));
137
185
  const word = before.match(/(?:\\)?[\p{L}\p{M}]+$/u)?.[0];
138
186
  const operand = before.match(/(?:\d+(?:\.\d+)?|[\p{L}\p{M}]+)$/u)?.[0];
139
- const query = trigger ?? word ?? operand ?? '';
187
+ const query = dimension?.[0] ?? trigger ?? word ?? operand ?? '';
140
188
  if (!query)
141
189
  return { query: '', candidates: [] };
142
190
  const name = query.replace(/^\\/, '');
@@ -160,12 +208,16 @@ export function findSuggestions(text, caret, locale = 'ko') {
160
208
  return 3;
161
209
  return 4;
162
210
  };
163
- const candidates = trigger
164
- ? catalog.filter((item) => item.triggers.includes(trigger))
165
- : /^\d+(?:\.\d+)?$/.test(name)
166
- ? []
167
- : catalog.filter((item) => names(item).some((value) => normalize(value).includes(needle)));
211
+ const candidates = dimension
212
+ ? matrixDimensionSuggestions(Number(dimension[1]), Number(dimension[2]))
213
+ : trigger
214
+ ? catalog.filter((item) => item.triggers.includes(trigger))
215
+ : /^\d+(?:\.\d+)?$/.test(name)
216
+ ? []
217
+ : catalog.filter((item) => names(item).some((value) => normalize(value).includes(needle)));
168
218
  candidates.sort((a, b) => {
219
+ if (dimension)
220
+ return 0;
169
221
  if (trigger === '-' || trigger === '->') {
170
222
  const preferred = trigger === '-' ? ['−', '→'] : ['→'];
171
223
  const order = (item) => {
@@ -216,6 +268,16 @@ export function findSuggestions(text, caret, locale = 'ko') {
216
268
  });
217
269
  }
218
270
  }
271
+ // A partial name after a coefficient is an operand (4ab), not an "absolute"
272
+ // command. Keep explicit commands such as 4abs and \\abs working as before.
273
+ if (!trigger && /^\p{L}+$/u.test(query) && /\d$/.test(before.slice(0, -query.length))) {
274
+ for (let index = 0; index < candidates.length; index++) {
275
+ const item = candidates[index];
276
+ if (item.kind && wrappingKinds.some((kind) => kind === item.kind) && rank(item) >= 3) {
277
+ candidates[index] = { ...item, wrapOperand: true, detail: 'suggestion.wrapOperand' };
278
+ }
279
+ }
280
+ }
219
281
  return {
220
282
  query,
221
283
  candidates: candidates.map((item) => ({
@@ -225,11 +287,68 @@ export function findSuggestions(text, caret, locale = 'ko') {
225
287
  })),
226
288
  };
227
289
  }
290
+ /** Combine text search with changes available for the current formula structure. */
291
+ export function findStateSuggestions(state, locale = 'ko', caret = state.caret.start) {
292
+ const text = textNodes(state.document).find((node) => node.id === state.caret.id)?.text ?? '';
293
+ const result = findSuggestions(text, caret, locale);
294
+ const context = structureEditingContext(state);
295
+ const root = context?.root;
296
+ const kind = root?.canTransform ? root.target : undefined;
297
+ if (root && kind) {
298
+ const item = mathStructures.find((entry) => entry.kind === kind);
299
+ result.candidates.push({
300
+ ...item,
301
+ id: `transform-${kind}`,
302
+ transformRootId: root.id,
303
+ label: translate(locale, 'suggestion.transform', {
304
+ kind: translate(locale, `context.${kind}`),
305
+ }),
306
+ detail: translate(locale, 'suggestion.transformRootDetail'),
307
+ });
308
+ }
309
+ if (context?.fence) {
310
+ const fence = context.fence;
311
+ const changes = Object.keys(fencePairs)
312
+ .filter((kind) => kind !== fence.type)
313
+ .map((kind) => {
314
+ const item = mathStructures.find((entry) => entry.kind === kind);
315
+ return {
316
+ ...item,
317
+ id: `transform-fence-${kind}`,
318
+ transformFenceId: fence.id,
319
+ label: translate(locale, 'suggestion.transformFence', {
320
+ kind: translate(locale, item.label),
321
+ }),
322
+ detail: translate(locale, 'suggestion.transformFenceDetail'),
323
+ };
324
+ });
325
+ // Explicit symbol/command matches stay first. Changes precede new wrapping actions.
326
+ const wrapping = result.candidates.findIndex((item) => item.wrapOperand);
327
+ result.candidates.splice(wrapping < 0 ? result.candidates.length : wrapping, 0, ...changes);
328
+ }
329
+ return result;
330
+ }
228
331
  export function acceptSuggestion(state, query, candidate) {
332
+ if (candidate.transformFenceId)
333
+ return candidate.kind
334
+ ? changeContextFence(state, candidate.transformFenceId, candidate.kind)
335
+ : state;
336
+ if (candidate.transformRootId) {
337
+ return candidate.kind === 'root' || candidate.kind === 'indexedRoot'
338
+ ? transformRoot(state, candidate.transformRootId, candidate.kind)
339
+ : state;
340
+ }
229
341
  const node = textNodes(state.document).find((n) => n.id === state.caret.id);
230
342
  const start = state.caret.start - query.length;
231
343
  if (!node || start < 0 || node.text.slice(start, state.caret.start) !== query)
232
344
  return state;
345
+ if (candidate.matrixSize !== undefined) {
346
+ const rows = candidate.matrixSize;
347
+ const columns = candidate.matrixColumns ?? rows;
348
+ if (![rows, columns].every((size) => Number.isInteger(size) && size >= 1 && size <= MATRIX_MAX_SIZE) ||
349
+ (candidate.identity && rows !== columns))
350
+ return state;
351
+ }
233
352
  if (candidate.wrapOperand && candidate.kind) {
234
353
  const selected = { ...state, caret: { ...state.caret, start, end: state.caret.start } };
235
354
  const wrapped = insertStructure(selected, candidate.kind);
@@ -253,12 +372,34 @@ export function acceptSuggestion(state, query, candidate) {
253
372
  const inserted = insertStructure(next, 'operatorName');
254
373
  return moveCaret(setText(inserted, candidate.operatorName, candidate.operatorName.length), 1);
255
374
  }
256
- if (candidate.templateId)
257
- return insertTemplate(next, candidate.templateId);
258
- if (candidate.matrixSize)
259
- return (candidate.identity ? insertIdentityMatrix : insertMatrix)(next, candidate.matrixSize);
375
+ if (candidate.templateId) {
376
+ const inserted = insertTemplate(next, candidate.templateId);
377
+ if (!candidate.templateId.startsWith('zero-') || inserted === next)
378
+ return inserted;
379
+ // Matrix suggestions start cell editing, including populated zero templates.
380
+ // Pasting a template otherwise leaves the caret after its last structure.
381
+ const matrix = activeMatrix(moveCaret(inserted, -1))?.matrix;
382
+ const first = matrix?.slots[0].children[0];
383
+ return first?.type === 'text'
384
+ ? { ...inserted, caret: { id: first.id, start: 0, end: 0 } }
385
+ : inserted;
386
+ }
387
+ if (candidate.matrixSize) {
388
+ const inserted = candidate.identity
389
+ ? insertIdentityMatrix(next, candidate.matrixSize)
390
+ : insertMatrix(next, candidate.matrixSize, candidate.matrixColumns);
391
+ if (inserted === next)
392
+ return state;
393
+ if (candidate.zero) {
394
+ activeMatrix(inserted)?.matrix.slots.forEach((cell) => {
395
+ const first = cell.children[0];
396
+ if (first.type === 'text')
397
+ first.text = '0';
398
+ });
399
+ }
400
+ return inserted;
401
+ }
260
402
  return candidate.kind
261
403
  ? insertStructure(next, candidate.kind, ['fraction', 'binomial', 'superscript', 'subscript'].includes(candidate.kind), candidate.mathStyle)
262
404
  : next;
263
405
  }
264
- //# sourceMappingURL=suggestions.js.map
package/dist/svelte.d.ts CHANGED
@@ -2,4 +2,3 @@ import type { Action } from 'svelte/action';
2
2
  import { type DOMMathEditorOptions } from './dom.js';
3
3
  /** Svelte action: <div use:mathEditor={options} />. */
4
4
  export declare const mathEditor: Action<HTMLElement, DOMMathEditorOptions | undefined>;
5
- //# sourceMappingURL=svelte.d.ts.map
package/dist/svelte.js CHANGED
@@ -11,4 +11,3 @@ export const mathEditor = (host, options) => {
11
11
  },
12
12
  };
13
13
  };
14
- //# sourceMappingURL=svelte.js.map
@@ -5,4 +5,3 @@ export declare function SymbolBrowser({ locale, onInsert, onClose, }: {
5
5
  onInsert: (glyph: string) => void;
6
6
  onClose: () => void;
7
7
  }): import("react/jsx-runtime").JSX.Element;
8
- //# sourceMappingURL=symbol-browser.d.ts.map
@@ -29,4 +29,3 @@ export function SymbolBrowser({ locale, onInsert, onClose, }) {
29
29
  }
30
30
  }, children: [_jsxs("div", { className: "me-symbol-browser-heading", children: [_jsxs("strong", { children: [t('symbols.title'), ' ', _jsxs("small", { children: [matches.length, " / ", mathSymbols.length] })] }), _jsx("button", { type: "button", onClick: onClose, children: t('common.close') })] }), _jsxs("label", { className: "me-symbol-search", children: [t('symbols.search'), _jsx("input", { ref: search, type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t('symbols.searchPlaceholder') })] }), _jsx("p", { className: "me-symbol-help", children: t('symbols.help') }), _jsx("div", { className: "me-symbol-grid", children: matches.map(([glyph, label]) => (_jsxs("button", { type: "button", "aria-label": `${glyph} — ${t(label)}`, title: `${translate('en', label)} / ${translate('ko', label)}`, onClick: () => onInsert(glyph), children: [_jsx("span", { className: "me-symbol-glyph", "aria-hidden": "true", children: glyph }), _jsx("span", { children: t(label) })] }, glyph))) }), !matches.length && _jsx("p", { role: "status", children: t('symbols.noMatches') })] }));
31
31
  }
32
- //# sourceMappingURL=symbol-browser.js.map
package/dist/symbols.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  /** One catalog powers discovery and LaTeX export. */
2
2
  export declare const mathSymbols: readonly [readonly ["↑", "symbol.upArrow", "uparrow arrow 화살표", "- ->", "\\uparrow"], readonly ["↕", "symbol.upDownArrow", "updownarrow arrow 화살표", "- ->", "\\updownarrow"], readonly ["⇐", "symbol.doubleLeftArrow", "Leftarrow arrow 화살표", "- ->", "\\Leftarrow"], readonly ["⇑", "symbol.doubleUpArrow", "Uparrow arrow 화살표", "- ->", "\\Uparrow"], readonly ["⇓", "symbol.doubleDownArrow", "Downarrow arrow 화살표", "- ->", "\\Downarrow"], readonly ["⇕", "symbol.doubleUpDownArrow", "Updownarrow arrow 화살표", "- ->", "\\Updownarrow"], readonly ["↗", "symbol.northEastArrow", "nearrow arrow 화살표", "- ->", "\\nearrow"], readonly ["↘", "symbol.southEastArrow", "searrow arrow 화살표", "- ->", "\\searrow"], readonly ["↙", "symbol.southWestArrow", "swarrow arrow 화살표", "- ->", "\\swarrow"], readonly ["↖", "symbol.northWestArrow", "nwarrow arrow 화살표", "- ->", "\\nwarrow"], readonly ["⟶", "symbol.longRightArrow", "longrightarrow arrow 화살표", "- ->", "\\longrightarrow"], readonly ["⟵", "symbol.longLeftArrow", "longleftarrow arrow 화살표", "- ->", "\\longleftarrow"], readonly ["⟷", "symbol.longLeftRightArrow", "longleftrightarrow arrow 화살표", "-", "\\longleftrightarrow"], readonly ["⟹", "symbol.longDoubleRightArrow", "Longrightarrow arrow 화살표", "- ->", "\\Longrightarrow"], readonly ["⟸", "symbol.longDoubleLeftArrow", "Longleftarrow arrow 화살표", "- ->", "\\Longleftarrow"], readonly ["⟺", "symbol.longDoubleLeftRightArrow", "Longleftrightarrow arrow 화살표", "", "\\Longleftrightarrow"], readonly ["↪", "symbol.hookRightArrow", "hookrightarrow arrow 화살표", "- ->", "\\hookrightarrow"], readonly ["↩", "symbol.hookLeftArrow", "hookleftarrow arrow 화살표", "- ->", "\\hookleftarrow"], readonly ["⇀", "symbol.rightHarpoonUp", "rightharpoonup arrow 화살표", "- ->", "\\rightharpoonup"], readonly ["↼", "symbol.leftHarpoonUp", "leftharpoonup arrow 화살표", "- ->", "\\leftharpoonup"], readonly ["⇁", "symbol.rightHarpoonDown", "rightharpoondown arrow 화살표", "- ->", "\\rightharpoondown"], readonly ["↽", "symbol.leftHarpoonDown", "leftharpoondown arrow 화살표", "- ->", "\\leftharpoondown"], readonly ["⇌", "symbol.rightLeftHarpoons", "rightleftharpoons arrow 화살표", "- ->", "\\rightleftharpoons"], readonly ["⇋", "symbol.leftRightHarpoons", "leftrightharpoons arrow 화살표", "- ->", "\\leftrightharpoons"], readonly ["↓", "symbol.downArrow", "downarrow down 아래화살표 아래쪽화살표", "- ->", "\\downarrow"], readonly ["⩾", "symbol.greaterOrEqualSlanted", "geqslant", "", "\\geqslant"], readonly ["⩽", "symbol.lessOrEqualSlanted", "leqslant", "", "\\leqslant"], readonly ["∋", "symbol.contains", "ni contains", "", "\\ni"], readonly ["⊃", "symbol.superset", "supset superset", "", "\\supset"], readonly ["⊇", "symbol.supersetOrEqual", "supseteq", "", "\\supseteq"], readonly ["⊈", "symbol.notSubsetOrEqual", "nsubseteq", "", "\\nsubseteq"], readonly ["∖", "symbol.setDifference", "setminus difference", "", "\\setminus"], readonly ["ℕ", "symbol.naturals", "naturals natural", "", "\\mathbb{N}"], readonly ["ℤ", "symbol.integers", "integers integer", "", "\\mathbb{Z}"], readonly ["ℚ", "symbol.rationals", "rationals rational", "", "\\mathbb{Q}"], readonly ["ℝ", "symbol.reals", "reals real", "", "\\mathbb{R}"], readonly ["ℂ", "symbol.complexes", "complexes complex", "", "\\mathbb{C}"], readonly ["⊥", "symbol.perpendicular", "perp perpendicular", "", "\\perp"], readonly ["∥", "symbol.parallel", "parallel", "", "\\parallel"], readonly ["≅", "symbol.congruent", "cong congruent", "", "\\cong"], readonly ["∼", "symbol.similar", "sim similar", "", "\\sim"], readonly ["↦", "symbol.mapsTo", "mapsto maps arrow 화살표", "|-> - ->", "\\mapsto"], readonly ["…", "symbol.ellipsis", "ldots ellipsis", "", "\\ldots"], readonly ["⋯", "symbol.centeredDots", "cdots", "", "\\cdots"], readonly ["⋮", "symbol.verticalDots", "vdots", "", "\\vdots"], readonly ["⋱", "symbol.diagonalDots", "ddots", "", "\\ddots"], readonly ["°", "symbol.degree", "degree degrees 각도", "", "{}^{\\circ}"], readonly ["∠", "symbol.angle", "angle", "", "\\angle"], readonly ["+", "symbol.plus", "plus sum", "+", "+"], readonly ["−", "symbol.minus", "minus", "-", "-"], readonly ["±", "symbol.plusMinus", "plusminus sum", "+- +", "\\pm"], readonly ["∓", "symbol.minusPlus", "minusplus", "-+ -", "\\mp"], readonly ["×", "symbol.times", "times multiply", "*", "\\times"], readonly ["·", "symbol.dot", "dot multiply", "*", "\\cdot"], readonly ["÷", "symbol.divide", "divide division", "/", "\\div"], readonly ["=", "symbol.equal", "equal", "=", "="], readonly ["≠", "symbol.notEqual", "neq unequal", "!= /= =", "\\ne"], readonly ["≈", "symbol.approximatelyEqual", "approx", "~ ~~ =", "\\approx"], readonly ["≡", "symbol.equivalent", "equiv", "=== =", "\\equiv"], readonly ["<", "symbol.lessThan", "less", "<", "<"], readonly [">", "symbol.greaterThan", "greater", ">", ">"], readonly ["≤", "symbol.lessOrEqual", "leq less", "<= <- <", "\\le"], readonly ["≥", "symbol.greaterOrEqual", "geq greater", ">= >- >", "\\ge"], readonly ["∝", "symbol.proportional", "proportional", "~", "\\propto"], readonly ["→", "symbol.rightArrow", "rightarrow arrow", "-> -", "\\to"], readonly ["←", "symbol.leftArrow", "leftarrow arrow", "<- < - ->", "\\leftarrow"], readonly ["↔", "symbol.leftRightArrow", "leftrightarrow arrow", "<-> - ->", "\\leftrightarrow"], readonly ["⇒", "symbol.implies", "implies arrow 화살표", "=> = - ->", "\\Rightarrow"], readonly ["⇔", "symbol.iff", "iff arrow 화살표", "<=> - ->", "\\Leftrightarrow"], readonly ["∈", "symbol.elementOf", "in element", "E", "\\in"], readonly ["∉", "symbol.notElementOf", "notin element", "E/", "\\notin"], readonly ["∀", "symbol.forAll", "forall", "A", "\\forall"], readonly ["∃", "symbol.exists", "exists", "E", "\\exists"], readonly ["∪", "symbol.union", "union", "U", "\\cup"], readonly ["∩", "symbol.intersection", "intersection", "", "\\cap"], readonly ["⊂", "symbol.subset", "subset", "<", "\\subset"], readonly ["⊆", "symbol.subsetOrEqual", "subseteq", "<=", "\\subseteq"], readonly ["∅", "symbol.emptySet", "emptyset", "", "\\emptyset"], readonly ["∞", "symbol.infinity", "infinity infty", "", "\\infty"], readonly ["∂", "symbol.partialDerivative", "partial derivative", "", "\\partial"], readonly ["∇", "symbol.nabla", "nabla gradient", "", "\\nabla"], readonly ["∧", "symbol.logicalAnd", "and wedge", "^", "\\wedge"], readonly ["∨", "symbol.logicalOr", "or vee", "", "\\vee"], readonly ["¬", "symbol.logicalNot", "not neg", "!", "\\neg"], readonly ["α", "symbol.alpha", "alpha", "", "\\alpha"], readonly ["β", "symbol.beta", "beta", "", "\\beta"], readonly ["γ", "symbol.gamma", "gamma", "", "\\gamma"], readonly ["δ", "symbol.delta", "delta", "", "\\delta"], readonly ["ε", "symbol.epsilon", "epsilon", "", "\\epsilon"], readonly ["ζ", "symbol.zeta", "zeta", "", "\\zeta"], readonly ["η", "symbol.eta", "eta", "", "\\eta"], readonly ["θ", "symbol.theta", "theta", "", "\\theta"], readonly ["ι", "symbol.iota", "iota", "", "\\iota"], readonly ["κ", "symbol.kappa", "kappa", "", "\\kappa"], readonly ["λ", "symbol.lambda", "lambda", "", "\\lambda"], readonly ["μ", "symbol.mu", "mu", "", "\\mu"], readonly ["ν", "symbol.nu", "nu", "", "\\nu"], readonly ["ξ", "symbol.xi", "xi", "", "\\xi"], readonly ["π", "symbol.pi", "pi", "", "\\pi"], readonly ["ρ", "symbol.rho", "rho", "", "\\rho"], readonly ["σ", "symbol.sigma", "sigma", "", "\\sigma"], readonly ["τ", "symbol.tau", "tau", "", "\\tau"], readonly ["υ", "symbol.upsilon", "upsilon", "", "\\upsilon"], readonly ["φ", "symbol.phi", "phi", "", "\\phi"], readonly ["χ", "symbol.chi", "chi", "", "\\chi"], readonly ["ψ", "symbol.psi", "psi", "", "\\psi"], readonly ["ω", "symbol.omega", "omega", "", "\\omega"], readonly ["Γ", "symbol.upperGamma", "Gamma", "", "\\Gamma"], readonly ["Δ", "symbol.upperDelta", "Delta", "", "\\Delta"], readonly ["Θ", "symbol.upperTheta", "Theta", "", "\\Theta"], readonly ["Λ", "symbol.upperLambda", "Lambda", "", "\\Lambda"], readonly ["Ξ", "symbol.upperXi", "Xi", "", "\\Xi"], readonly ["Π", "symbol.upperPi", "Pi", "", "\\Pi"], readonly ["Σ", "symbol.upperSigma", "Sigma", "", "\\Sigma"], readonly ["Φ", "symbol.upperPhi", "Phi", "", "\\Phi"], readonly ["Ψ", "symbol.upperPsi", "Psi", "", "\\Psi"], readonly ["Ω", "symbol.upperOmega", "Omega", "", "\\Omega"]];
3
- //# sourceMappingURL=symbols.d.ts.map
package/dist/symbols.js CHANGED
@@ -142,4 +142,3 @@ export const mathSymbols = [
142
142
  ['Ψ', 'symbol.upperPsi', 'Psi', '', '\\Psi'],
143
143
  ['Ω', 'symbol.upperOmega', 'Omega', '', '\\Omega'],
144
144
  ];
145
- //# sourceMappingURL=symbols.js.map
@@ -13,4 +13,3 @@ export declare const mathTemplates: MathTemplate[];
13
13
  /** Each call creates independent, editable nodes; templates are not opaque LaTeX. */
14
14
  export declare function createTemplate(id: string): MathFragment | undefined;
15
15
  export declare function insertTemplate(state: MathState, id: string, range?: MathRange): MathState;
16
- //# sourceMappingURL=templates.d.ts.map
package/dist/templates.js CHANGED
@@ -83,4 +83,3 @@ export function insertTemplate(state, id, range) {
83
83
  const fragment = createTemplate(id);
84
84
  return fragment ? pasteFragment(state, fragment, range) : state;
85
85
  }
86
- //# sourceMappingURL=templates.js.map
package/dist/tokens.d.ts CHANGED
@@ -8,4 +8,3 @@ export interface MathToken {
8
8
  /** Lexical input units, not inferred mathematical definitions (e and c remain variables). */
9
9
  export declare function tokenizeMathText(text: string): MathToken[];
10
10
  export declare function tokenIndexAt(tokens: MathToken[], offset: number, affinity?: 'forward' | 'backward'): number;
11
- //# sourceMappingURL=tokens.d.ts.map
package/dist/tokens.js CHANGED
@@ -21,4 +21,3 @@ export function tokenIndexAt(tokens, offset, affinity = 'backward') {
21
21
  : offset > token.start && offset <= token.end);
22
22
  return index >= 0 ? index : offset <= 0 ? 0 : tokens.length - 1;
23
23
  }
24
- //# sourceMappingURL=tokens.js.map
@@ -0,0 +1,15 @@
1
+ import { type MathCaret, type MathState } from './model.js';
2
+ import type { MathPoint } from './range.js';
3
+ type VerticalPoint = MathPoint & Pick<MathCaret, 'affinity'>;
4
+ /** Viewport coordinates of a rendered insertion point, including either side of a token boundary. */
5
+ export type MathCaretGeometry = (point: VerticalPoint) => {
6
+ x: number;
7
+ y: number;
8
+ } | undefined;
9
+ /**
10
+ * Move through the nearest vertical structure, then enclosing grids and equation lines.
11
+ * Geometry is optional so the same operation works in headless sessions and either renderer.
12
+ * Native text selections and literal text retain their own arrow-key behavior.
13
+ */
14
+ export declare function moveVertical(state: MathState, direction: -1 | 1, geometry?: MathCaretGeometry): MathState;
15
+ export {};
@@ -0,0 +1,145 @@
1
+ import { documentRows, isGrid, isLiteralText, textNodes, } from './model.js';
2
+ /** Candidate slots in semantic order; paired scripts may also move along their shared column. */
3
+ function verticalSlots(node, slot, direction) {
4
+ if (isGrid(node)) {
5
+ const target = slot + direction * node.columns;
6
+ return target >= 0 && target < node.slots.length ? [target] : [];
7
+ }
8
+ switch (node.type) {
9
+ case 'fraction':
10
+ case 'binomial':
11
+ case 'indexedRoot':
12
+ case 'overset':
13
+ case 'overbrace':
14
+ case 'xrightarrow':
15
+ case 'xleftarrow':
16
+ case 'subscript':
17
+ return slot + direction >= 0 && slot + direction < 2 ? [slot + direction] : [];
18
+ case 'underset':
19
+ case 'underbrace':
20
+ return slot - direction >= 0 && slot - direction < 2 ? [slot - direction] : [];
21
+ case 'superscript':
22
+ return slot - direction >= 0 && slot - direction < 2 ? [slot - direction] : [];
23
+ case 'scripts':
24
+ if (slot === 0)
25
+ return [direction === -1 ? 2 : 1];
26
+ if (slot === 1 && direction === -1)
27
+ return [0, 2];
28
+ if (slot === 2 && direction === 1)
29
+ return [0, 1];
30
+ return [];
31
+ case 'limit':
32
+ case 'limsup':
33
+ case 'liminf':
34
+ return slot - direction >= 0 && slot - direction < 2 ? [slot - direction] : [];
35
+ case 'sum':
36
+ case 'product':
37
+ case 'integral':
38
+ case 'doubleIntegral':
39
+ case 'tripleIntegral':
40
+ case 'contourIntegral':
41
+ if (slot === 2)
42
+ return [direction === -1 ? 1 : 0];
43
+ if (slot === 0 && direction === -1)
44
+ return [2, 1];
45
+ if (slot === 1 && direction === 1)
46
+ return [2, 0];
47
+ return [];
48
+ default:
49
+ return [];
50
+ }
51
+ }
52
+ function caretOffsets(text) {
53
+ const offsets = [0];
54
+ for (const character of text)
55
+ offsets.push(offsets.at(-1) + character.length);
56
+ return offsets;
57
+ }
58
+ function focusRows(state, targets, direction, geometry) {
59
+ const nodes = targets.flatMap((root) => textNodes({ version: 1, root }));
60
+ if (!nodes.length)
61
+ return state;
62
+ const origin = geometry?.({
63
+ id: state.caret.id,
64
+ offset: state.caret.start,
65
+ affinity: state.caret.affinity,
66
+ });
67
+ let nearest;
68
+ let horizontal = Infinity;
69
+ let vertical = Infinity;
70
+ if (origin && Number.isFinite(origin.x) && Number.isFinite(origin.y)) {
71
+ for (const node of nodes) {
72
+ for (const offset of caretOffsets(node.text)) {
73
+ // React renders adjacent lexical tokens with distinct physical caret positions.
74
+ for (const affinity of ['backward', 'forward']) {
75
+ const point = { id: node.id, offset, affinity };
76
+ const position = geometry?.(point);
77
+ if (!position || !Number.isFinite(position.x) || !Number.isFinite(position.y))
78
+ continue;
79
+ const dy = (position.y - origin.y) * direction;
80
+ if (dy <= 0)
81
+ continue;
82
+ const dx = Math.abs(position.x - origin.x);
83
+ if (dx < horizontal - 0.5 || (Math.abs(dx - horizontal) <= 0.5 && dy < vertical)) {
84
+ nearest = point;
85
+ horizontal = dx;
86
+ vertical = dy;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // Without layout, preserve the existing offset/clamping behavior in the first target slot.
93
+ const fallback = nodes[0];
94
+ const offset = nearest?.offset ??
95
+ caretOffsets(fallback.text)
96
+ .filter((at) => at <= state.caret.start)
97
+ .at(-1);
98
+ return {
99
+ ...state,
100
+ caret: {
101
+ id: nearest?.id ?? fallback.id,
102
+ start: offset,
103
+ end: offset,
104
+ ...(nearest ? { affinity: nearest.affinity } : {}),
105
+ },
106
+ };
107
+ }
108
+ /**
109
+ * Move through the nearest vertical structure, then enclosing grids and equation lines.
110
+ * Geometry is optional so the same operation works in headless sessions and either renderer.
111
+ * Native text selections and literal text retain their own arrow-key behavior.
112
+ */
113
+ export function moveVertical(state, direction, geometry) {
114
+ if (state.caret.start !== state.caret.end || isLiteralText(state.document, state.caret.id))
115
+ return state;
116
+ const path = [];
117
+ const find = (row) => {
118
+ for (const node of row.children) {
119
+ if (node.type === 'text') {
120
+ if (node.id === state.caret.id)
121
+ return true;
122
+ }
123
+ else {
124
+ for (let slot = 0; slot < node.slots.length; slot++) {
125
+ path.push({ structure: node, slot });
126
+ if (find(node.slots[slot]))
127
+ return true;
128
+ path.pop();
129
+ }
130
+ }
131
+ }
132
+ return false;
133
+ };
134
+ const lines = documentRows(state.document);
135
+ const line = lines.findIndex(find);
136
+ if (line < 0)
137
+ return state;
138
+ for (const { structure, slot } of path.reverse()) {
139
+ const targets = verticalSlots(structure, slot, direction).map((index) => structure.slots[index]);
140
+ if (targets.length)
141
+ return focusRows(state, targets, direction, geometry);
142
+ }
143
+ const target = lines[line + direction];
144
+ return target ? focusRows(state, [target], direction, geometry) : state;
145
+ }
package/dist/vue.d.ts CHANGED
@@ -2,4 +2,3 @@ import type { ObjectDirective } from 'vue';
2
2
  import { type DOMMathEditorOptions } from './dom.js';
3
3
  /** Use as v-math-editor. session/defaultValue are mount-only. */
4
4
  export declare const vMathEditor: ObjectDirective<HTMLElement, DOMMathEditorOptions>;
5
- //# sourceMappingURL=vue.d.ts.map
package/dist/vue.js CHANGED
@@ -13,4 +13,3 @@ export const vMathEditor = {
13
13
  instances.delete(host);
14
14
  },
15
15
  };
16
- //# sourceMappingURL=vue.js.map
@@ -7,4 +7,3 @@ export interface MathEditorElement extends HTMLElement {
7
7
  }
8
8
  /** Explicit registration; importing this module is safe without a browser. Uses light DOM. */
9
9
  export declare function defineMathEditor(tagName?: string): CustomElementConstructor;
10
- //# sourceMappingURL=web-component.d.ts.map
@@ -6,7 +6,14 @@ export function defineMathEditor(tagName = 'barocss-math-editor') {
6
6
  if (existing)
7
7
  return existing;
8
8
  class EditorElement extends HTMLElement {
9
- static observedAttributes = ['locale', 'mode', 'toolbar', 'line-numbers', 'enter-behavior'];
9
+ static observedAttributes = [
10
+ 'locale',
11
+ 'mode',
12
+ 'toolbar',
13
+ 'line-numbers',
14
+ 'enter-behavior',
15
+ 'context-tools',
16
+ ];
10
17
  session = createMathSession();
11
18
  editor;
12
19
  get value() {
@@ -26,6 +33,7 @@ export function defineMathEditor(tagName = 'barocss-math-editor') {
26
33
  onCancel: () => this.dispatchEvent(new CustomEvent('math-cancel', { bubbles: true, composed: true })),
27
34
  onCommit: (snapshot) => this.dispatchEvent(new CustomEvent('math-commit', { detail: snapshot, bubbles: true, composed: true })),
28
35
  showLineNumbers: this.getAttribute('line-numbers') !== 'false',
36
+ contextTools: this.getAttribute('context-tools') !== 'false',
29
37
  onChange: (document, latex) => this.dispatchEvent(new CustomEvent('math-change', {
30
38
  detail: { document, latex },
31
39
  bubbles: true,
@@ -64,4 +72,3 @@ export function defineMathEditor(tagName = 'barocss-math-editor') {
64
72
  customElements.define(tagName, EditorElement);
65
73
  return EditorElement;
66
74
  }
67
- //# sourceMappingURL=web-component.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barocss/math-editor",
3
- "version": "0.2.1",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -46,7 +46,8 @@
46
46
  "src/style.css",
47
47
  "*.md",
48
48
  "LICENSE",
49
- "src/fonts"
49
+ "src/fonts",
50
+ "src/shapes"
50
51
  ],
51
52
  "peerDependencies": {
52
53
  "react": ">=18",
@@ -120,6 +121,13 @@
120
121
  "test": "vitest run",
121
122
  "test:run": "vitest run",
122
123
  "format": "prettier --write src test \"examples/*.ts\" scripts",
123
- "format:check": "prettier --check src test \"examples/*.ts\" scripts"
124
+ "format:check": "prettier --check src test \"examples/*.ts\" scripts",
125
+ "test:rendering": "node test/rendering/run.mjs",
126
+ "test:rendering:syntax": "node test/rendering/run.mjs --suite=syntax",
127
+ "test:rendering:combinations": "node test/rendering/run.mjs --suite=combinations",
128
+ "test:rendering:inventory": "vitest run test/rendering-coverage.test.ts",
129
+ "test:rendering:update": "node test/rendering/update.mjs",
130
+ "test:rendering:report": "node test/rendering/coverage-report.mjs",
131
+ "test:editing": "node test/editing/run.mjs"
124
132
  }
125
133
  }
@@ -0,0 +1,18 @@
1
+ # Bundled mathematical glyphs
2
+
3
+ The WOFF2 files in this directory come from KaTeX 0.16.28. Their MIT notice is
4
+ preserved in [LICENSE-KaTeX.txt](LICENSE-KaTeX.txt).
5
+
6
+ The editor uses Main Regular for upright mathematics, Math Italic for variables,
7
+ Size1 Regular for inline operators, and Size2 Regular for display operators. Bold, calligraphic and blackboard faces
8
+ support the corresponding alphabet structures. Unsupported characters retain
9
+ system-font fallbacks.
10
+
11
+ The over/underbrace masks in `../style.css` also derive from the `leftbrace`,
12
+ `midbrace` and `rightbrace` paths in KaTeX 0.16.28 `src/svgGeometry.js`, under the
13
+ same MIT license. Three clipped slices preserve the ends and center when the
14
+ brace stretches. These assets do not add a KaTeX JavaScript runtime dependency.
15
+
16
+ Root and parenthesis masks also use KaTeX contours. See
17
+ [../shapes/README.md](../shapes/README.md) for the source glyphs, bounds and
18
+ conversion details.
@@ -0,0 +1,21 @@
1
+ # Mathematical outlines
2
+
3
+ These static SVG masks are derived from KaTeX 0.16.28, under its MIT license.
4
+ The copyright and license notice is preserved in
5
+ [../fonts/LICENSE-KaTeX.txt](../fonts/LICENSE-KaTeX.txt).
6
+
7
+ | Asset | Source | Conversion |
8
+ | ------------------------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | `radical.svg` | `src/svgGeometry.js`, `sqrtMain` | Set `extraVinculum` and top padding to zero; crop to the 850 × 1000 radical region. The editable radicand slot draws the horizontal rule. |
10
+ | `parenthesis.svg` | Main Regular, U+0028 | Extract the glyph outline, invert the font Y axis and crop to its ink bounds: (94, −250)–(333, 750). |
11
+ | `parenthesis-top.svg` | Size4 Regular, U+239B | Same conversion, bounds (291, −655)–(843, 1154). |
12
+ | `parenthesis-bottom.svg` | Size4 Regular, U+239D | Same conversion, bounds (291, −644)–(843, 1165). |
13
+
14
+ Font contours were extracted from the distributed TTF files with fontTools
15
+ `SVGPathPen` and `TransformPen`. The masks retain the existing editor layout
16
+ boxes; they do not reproduce KaTeX's full delimiter-size selection algorithm.
17
+ Tall parentheses use bounded end caps and a straight extender. Right parentheses
18
+ mirror the corresponding left outline.
19
+
20
+ These files are packaged with `style.css`. They do not require KaTeX JavaScript,
21
+ fontTools, network requests to a CDN, or code generation in a consuming app.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 552 1809" preserveAspectRatio="none"><path d="M5 1Q7 0 75 0Q118 0 120 1Q124 2 125.0 8.0Q126 14 126 46Q126 290 143 500Q174 908 272.0 1223.0Q370 1538 542 1782Q552 1796 552 1800Q552 1803 546 1809H529Q510 1809 509 1808Q501 1800 494 1791Q130 1349 36 629Q4 384 0 72Q0 21 0.5 12.0Q1 3 5 1Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 552 1809" preserveAspectRatio="none"><path d="M509 1Q510 0 528 0H546Q552 6 552 9Q552 13 542 27Q339 316 241.5 692.0Q144 1068 129 1574Q126 1730 126 1763Q126 1795 125.0 1801.0Q124 1807 120 1808Q118 1809 75 1809Q8 1809 6 1808Q1 1806 0.5 1797.0Q0 1788 0 1737Q3 1498 24 1283Q104 491 494 18Q501 9 509 1Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 239 1000" preserveAspectRatio="none"><path d="M239 991Q239 1000 221 1000H208Q201 994 180 976Q0 812 0 500Q0 378 29 277Q75 120 180 24Q187 18 196.0 10.5Q205 3 208 0H221Q233 0 236.0 3.0Q239 6 239 9Q239 13 228 24Q73 188 73.0 500.0Q73 812 228 976Q239 987 239 991Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 850 1000" preserveAspectRatio="none"><path d="M95,622 c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14 c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54 c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10 s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429 c69,-144,104.5,-217.7,106.5,-221 l0.0 -0 c5.3,-9.3,12,-14,20,-14 H400000v40H845.2724 s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7 c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z M834 0h400000v40h-400000z"/></svg>