@lofcz/tikz 0.3.0 → 0.3.1

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.
package/src/recover.js ADDED
@@ -0,0 +1,255 @@
1
+ // Conservative repairs of generated TikZ syntax, using the LaTeX lexer before
2
+ // argument attachment. Never invent coordinates, lengths, or missing geometry.
3
+ import { parseMinimal } from '@unified-latex/unified-latex-util-parse';
4
+ import { printRaw } from '@unified-latex/unified-latex-util-print-raw';
5
+ import { parsePgfkeys } from '@unified-latex/unified-latex-util-pgfkeys';
6
+
7
+ const string = content => ({ type: 'string', content });
8
+ const group = content => ({ type: 'group', content });
9
+ const is = (node, text) => node?.type === 'string' && node.content === text;
10
+ const space = node => node?.type === 'whitespace' || node?.type === 'parbreak';
11
+ const skip = (nodes, i) => { while (space(nodes[i])) i++; return i; };
12
+ function walk(nodes, visit) {
13
+ visit(nodes);
14
+ for (const node of nodes) if (Array.isArray(node.content)) walk(node.content, visit);
15
+ }
16
+ function closeAt(nodes, start, open, close) {
17
+ let depth = 0;
18
+ for (let i = start; i < nodes.length; i++) {
19
+ if (is(nodes[i], open)) depth++;
20
+ else if (is(nodes[i], close) && --depth === 0) return i;
21
+ }
22
+ return -1;
23
+ }
24
+
25
+ const DEFINITIONS = new Set(['def', 'gdef', 'edef', 'xdef', 'let', 'newcommand', 'renewcommand', 'providecommand', 'pgfmathsetmacro', 'pgfmathtruncatemacro', 'pgfmathsetlengthmacro']);
26
+ function extendedIdentifiers(root, source) {
27
+ const bindings = new Map();
28
+ const names = new Set();
29
+ walk(root, nodes => { for (const node of nodes) if (node.type === 'macro') names.add(node.content); });
30
+ walk(root, nodes => {
31
+ for (let i = 0; i < nodes.length; i++) {
32
+ if (nodes[i].type !== 'macro' || !DEFINITIONS.has(nodes[i].content)) continue;
33
+ const at = skip(nodes, i + 1);
34
+ const target = nodes[at]?.type === 'group' ? nodes[at].content : nodes.slice(at, at + 3);
35
+ const [macro, tail, end] = target;
36
+ if (macro?.type !== 'macro' || tail?.type !== 'string' || !/^(?:_[A-Za-z0-9_]+|[0-9][A-Za-z0-9_]*)$/.test(tail.content)) continue;
37
+ if (end && end.type !== 'group' && !(nodes[i].content === 'let' && end.type === 'macro')) continue; // keep real delimited/parameter macros
38
+ if (macro.position?.end.offset !== tail.position?.start.offset) continue;
39
+ const key = macro.content + tail.content;
40
+ if (bindings.has(key)) continue;
41
+ let name = 'tikzRecoveredVariable';
42
+ let n = bindings.size;
43
+ do { name += String.fromCharCode(65 + n % 26); n = Math.floor(n / 26); } while (n);
44
+ while (names.has(name)) name += 'X';
45
+ names.add(name);
46
+ bindings.set(key, { base: macro.content, suffix: tail.content, name });
47
+ }
48
+ });
49
+ if (!bindings.size) return;
50
+ walk(root, nodes => {
51
+ for (let i = 0; i + 1 < nodes.length; i++) {
52
+ const node = nodes[i];
53
+ if (node.type !== 'macro') continue;
54
+ for (const binding of bindings.values()) {
55
+ const offset = node.position?.end.offset;
56
+ if (node.content !== binding.base || !source.startsWith(binding.suffix, offset)) continue;
57
+ const end = offset + binding.suffix.length;
58
+ if (/[A-Za-z0-9_]/.test(source[end] ?? '')) continue;
59
+ let j = i + 1;
60
+ while (j < nodes.length && nodes[j].position?.end.offset <= end) j++;
61
+ if (nodes[j - 1]?.position?.end.offset !== end) continue;
62
+ node.content = binding.name; nodes.splice(i + 1, j - i - 1);
63
+ break;
64
+ }
65
+ }
66
+ });
67
+ }
68
+
69
+ // >=Latex[length=3mm,width=2mm] needs braces around the complete arrow value;
70
+ // TeX's optional argument scanner otherwise stops at the inner closing bracket.
71
+ function nestedOptions(nodes) {
72
+ for (let i = 0; i < nodes.length; i++) {
73
+ if (!is(nodes[i], '[')) continue;
74
+ let close = closeAt(nodes, i, '[', ']');
75
+ if (close < 0) continue;
76
+ for (let j = i + 1; j < close; j++) {
77
+ if (!is(nodes[j], '[')) continue;
78
+ const end = closeAt(nodes, j, '[', ']');
79
+ let start = j - 1;
80
+ while (start > i && !is(nodes[start], '=') && !is(nodes[start], ',')) start--;
81
+ if (end < 0 || !is(nodes[start], '=')) continue;
82
+ const value = nodes.slice(start + 1, end + 1);
83
+ nodes.splice(start + 1, end - start, group(value));
84
+ close -= end - start - 1;
85
+ j = start + 1;
86
+ }
87
+ i = close;
88
+ }
89
+ }
90
+
91
+ function coordinateComponent(nodes, alreadyGrouped = false) {
92
+ const significant = nodes.filter(node => !space(node));
93
+ if (significant.length === 1 && significant[0].type === 'group') {
94
+ significant[0].content = coordinateComponent(significant[0].content, true);
95
+ return nodes;
96
+ }
97
+ let unitStart = significant.length - 1;
98
+ let unit = significant[unitStart]?.type === 'string' ? significant[unitStart].content : '';
99
+ if (unit.length === 1 && significant[unitStart - 1]?.type === 'string') { unitStart--; unit = significant[unitStart].content + unit; }
100
+ if (!/^(?:cm|mm|pt|bp|in|em|ex)$/.test(unit)) unit = null;
101
+ if (unit && significant.length > 1 && (significant[0].type === 'group' || significant.some(node => is(node, '(')))) {
102
+ const expr = significant.slice(0, unitStart).flatMap(node => node.type === 'group' ? [string('('), ...node.content, string(')')] : [node]);
103
+ const repaired = [string('('), ...expr, string(')*1' + unit)];
104
+ return alreadyGrouped ? repaired : [group(repaired)];
105
+ }
106
+ if (!alreadyGrouped && nodes.some(node => is(node, '('))) return [group(nodes)];
107
+ return nodes;
108
+ }
109
+
110
+ function coordinates(nodes) {
111
+ for (let i = 0; i < nodes.length; i++) {
112
+ if (nodes[i].type === 'inlinemath') { coordinates(nodes[i].content); continue; }
113
+ if (!is(nodes[i], '(')) continue;
114
+ const end = closeAt(nodes, i, '(', ')');
115
+ if (end < 0) continue;
116
+ const inside = nodes.slice(i + 1, end);
117
+ // Calc expressions have their own coordinate parentheses inside $...$.
118
+ if (inside.some(node => node.type === 'inlinemath')) { for (const node of inside) if (node.type === 'inlinemath') coordinates(node.content); i = end; continue; }
119
+ let depth = 0, start = 0;
120
+ const segments = [], delimiters = [];
121
+ for (let j = 0; j < inside.length; j++) {
122
+ if (is(inside[j], '(')) depth++;
123
+ else if (is(inside[j], ')')) depth--;
124
+ else if (!depth && (is(inside[j], ',') || is(inside[j], ':') || is(inside[j], 'and'))) {
125
+ segments.push(inside.slice(start, j)); delimiters.push(is(inside[j], 'and') ? string(' and ') : inside[j]); start = j + 1;
126
+ }
127
+ }
128
+ segments.push(inside.slice(start));
129
+ // Do not reinterpret named nodes, explicit coordinate systems or projections.
130
+ if (inside.some(node => is(node, '=') || is(node, '|') || is(node, 'cs'))) { i = end; continue; }
131
+ const rebuilt = segments.flatMap((part, j) => [...coordinateComponent(part), ...(delimiters[j] ? [delimiters[j]] : [])]);
132
+ nodes.splice(i + 1, end - i - 1, ...rebuilt);
133
+ i += rebuilt.length + 1;
134
+ }
135
+ }
136
+
137
+ const PATHS = new Set(['draw', 'fill', 'filldraw', 'shade', 'shadedraw', 'path', 'coordinate', 'node', 'clip', 'pic']);
138
+
139
+ function delimited(nodes, at, open, close) {
140
+ at = skip(nodes, at);
141
+ if (!is(nodes[at], open)) return null;
142
+ const end = closeAt(nodes, at, open, close);
143
+ return end < 0 ? null : { start: at, end: end + 1, content: nodes.slice(at + 1, end) };
144
+ }
145
+
146
+ /** Adapt old/shorthand tkz calls to the bundled package's explicit results. */
147
+ function tkzCommands(nodes) {
148
+ for (let i = 0; i < nodes.length; i++) {
149
+ const node = nodes[i];
150
+ if (node.type !== 'macro' || !node.content.startsWith('tkz')) continue;
151
+ const options = delimited(nodes, i + 1, '[', ']');
152
+ const point = delimited(nodes, options?.end ?? i + 1, '(', ')');
153
+ if (!point) continue;
154
+ if (node.content === 'tkzDefLine' && options) {
155
+ const first = skip(options.content, 0);
156
+ const through = skip(options.content, first + 1);
157
+ if (['orthogonal', 'perpendicular', 'parallel'].some(key => is(options.content[first], key)) && is(options.content[through], 'through')) {
158
+ nodes.splice(options.start + 1 + first + 1, 0, string('='));
159
+ }
160
+ }
161
+ if (node.content === 'tkzMidPoint') node.content = 'tkzDefMidPoint';
162
+ if (['tkzDefMidPoint', 'tkzDefPointBy', 'tkzDefPointWith'].includes(node.content)) {
163
+ const out = skip(nodes, point.end);
164
+ if (nodes[out]?.type === 'group') nodes.splice(out, 0, { type: 'macro', content: 'tkzGetPoint' });
165
+ if (node.content === 'tkzDefPointBy' && options) {
166
+ const onto = options.content.findIndex(token => is(token, 'onto'));
167
+ const line = skip(options.content, onto + 1);
168
+ if (onto >= 0 && is(options.content[line], 'line')) {
169
+ options.content.splice(line, 1);
170
+ nodes.splice(options.start + 1, options.end - options.start - 2, ...options.content);
171
+ }
172
+ }
173
+ }
174
+ if (node.content === 'tkzInter' && options && printRaw(options.content).trim() === 'R' && delimited(nodes, point.end, '(', ')')) node.content = 'tkzInterCC';
175
+ if (node.content === 'tkzDrawCircle' && options) {
176
+ const keys = parsePgfkeys(options.content).map(item => (item.itemParts ?? []).map(part => printRaw(part).trim()).join('='));
177
+ const mode = keys.find(key => ['circum', 'in', 'diameter', 'R'].includes(key));
178
+ const count = point.content.filter(token => is(token, ',')).length + 1;
179
+ if (!mode || count !== (['circum', 'in'].includes(mode) ? 3 : 2)) continue;
180
+ const rest = keys.filter(key => key !== mode).join(',');
181
+ if (mode === 'R') {
182
+ const comma = point.content.findIndex(token => is(token, ','));
183
+ nodes.splice(i, point.end - i, string(String.raw`\draw[circle style,${rest}] (${printRaw(point.content.slice(0, comma))}) circle (${printRaw(point.content.slice(comma + 1))});`));
184
+ continue;
185
+ }
186
+ // tkzDefCircle supplies both center and a point on the circle. Drawing
187
+ // from those results preserves the construction rather than guessing a radius.
188
+ const replacement = String.raw`{\tkzDefCircle[${mode}](${printRaw(point.content)})\tkzGetPoints{tikzRecoveredCircleCenter}{tikzRecoveredCirclePoint}\tkzDrawCircle[${rest}](tikzRecoveredCircleCenter,tikzRecoveredCirclePoint)}`;
189
+ nodes.splice(i, point.end - i, string(replacement));
190
+ }
191
+ }
192
+ }
193
+
194
+ /** PGF prints numbers, not expressions; evaluate explicit arithmetic first. */
195
+ function numberExpressions(nodes) {
196
+ for (let i = 0; i < nodes.length; i++) {
197
+ const node = nodes[i];
198
+ if (node.type !== 'macro') continue;
199
+ if (['pgfmathparse', 'pgfmathsetmacro', 'pgfmathtruncatemacro'].includes(node.content)) {
200
+ let at = skip(nodes, i + 1);
201
+ if (node.content !== 'pgfmathparse') at = skip(nodes, at + 1);
202
+ const expr = nodes[at];
203
+ if (expr?.type !== 'group') continue;
204
+ const mod = expr.content.findIndex(token => is(token, 'mod'));
205
+ const atom = tokens => { const value = tokens.filter(token => !space(token)); return value.length === 1 && (value[0].type === 'macro' || value[0].type === 'group' || (value[0].type === 'string' && /^-?\d+(?:\.\d+)?$/.test(value[0].content))); };
206
+ if (mod > 0 && atom(expr.content.slice(0, mod)) && atom(expr.content.slice(mod + 1))) expr.content = [string('mod('), ...expr.content.slice(0, mod), string(','), ...expr.content.slice(mod + 1), string(')')];
207
+ }
208
+ if (node.content !== 'pgfmathprintnumber') continue;
209
+ const options = delimited(nodes, i + 1, '[', ']');
210
+ const at = skip(nodes, options?.end ?? i + 1);
211
+ const expr = nodes[at];
212
+ if (expr?.type !== 'group' || !expr.content.some(token => token.type === 'string' && ['*', '/', '+', '('].includes(token.content))) continue;
213
+ nodes.splice(i, at - i + 1, string(String.raw`{\pgfmathparse{${printRaw(expr.content)}}\pgfmathprintnumber${options ? `[${printRaw(options.content)}]` : ''}{\pgfmathresult}}`));
214
+ }
215
+ }
216
+
217
+ /** A node position is one coordinate, not a path with a trailing ++ step. */
218
+ function relativeNodePosition(nodes) {
219
+ for (let i = 0; i < nodes.length; i++) {
220
+ if (nodes[i].type !== 'macro' || nodes[i].content !== 'node') continue;
221
+ let at = i + 1;
222
+ while (at < nodes.length && !is(nodes[at], 'at') && !is(nodes[at], ';')) at++;
223
+ if (!is(nodes[at], 'at')) continue;
224
+ const base = delimited(nodes, at + 1, '(', ')');
225
+ if (!base) continue;
226
+ let plus = skip(nodes, base.end);
227
+ if (!is(nodes[plus], '+')) continue;
228
+ plus++;
229
+ if (is(nodes[plus], '+')) plus++;
230
+ const offset = delimited(nodes, plus, '(', ')');
231
+ if (!offset) continue;
232
+ nodes.splice(base.start, offset.end - base.start, string(`($(${printRaw(base.content)})+(${printRaw(offset.content)})$)`));
233
+ }
234
+ }
235
+
236
+ export function recoverGeneratedSyntax(source) {
237
+ const root = parseMinimal(source).content;
238
+ extendedIdentifiers(root, source);
239
+ walk(root, nestedOptions);
240
+ walk(root, tkzCommands);
241
+ walk(root, numberExpressions);
242
+ walk(root, relativeNodePosition);
243
+ walk(root, nodes => {
244
+ for (let i = 0; i < nodes.length; i++) {
245
+ if (nodes[i].type !== 'macro' || !PATHS.has(nodes[i].content)) continue;
246
+ let end = i + 1;
247
+ while (end < nodes.length && !is(nodes[end], ';')) end++;
248
+ const tokens = nodes.slice(i + 1, end);
249
+ coordinates(tokens);
250
+ nodes.splice(i + 1, end - i - 1, ...tokens);
251
+ i += tokens.length;
252
+ }
253
+ });
254
+ return printRaw(root);
255
+ }
package/src/source.js CHANGED
@@ -17,6 +17,7 @@ import { printRaw } from '@unified-latex/unified-latex-util-print-raw';
17
17
  import { parsePgfkeys } from '@unified-latex/unified-latex-util-pgfkeys';
18
18
  import { BUNDLED_PACKAGES, BUNDLED_PGFPLOTS_LIBRARIES, BUNDLED_TIKZ_LIBRARIES } from './bundled.js';
19
19
  import * as T from './tables.js';
20
+ import { recoverGeneratedSyntax } from './recover.js';
20
21
 
21
22
  export const DEFAULT_LIBRARIES = ['calc', 'intersections', 'angles', 'quotes', 'arrows.meta', 'positioning'];
22
23
 
@@ -492,11 +493,33 @@ function processKeyList(nodes, context) {
492
493
  for (const item of items) {
493
494
  const parts = item.itemParts ?? [];
494
495
  if (!parts.length) continue;
495
- const key = printRaw(parts[0]).trim();
496
+ let key = printRaw(parts[0]).trim();
496
497
  const value = parts[1];
498
+ // In TikZ, a named collection of keys needs /.style. PGFPlots has real
499
+ // setters named "tick style" etc., so leave its options alone.
500
+ if (context.tikz && !key.includes('/') && key.endsWith(' style') && value?.some(isGroup)) {
501
+ key += '/.style';
502
+ parts[0] = [str(key)];
503
+ }
497
504
  if (value && TEXT_KEYS.has(key)) repairTextNodes(value, context);
498
505
  if (value && /^(?:xtick|ytick|ztick)labels$|^legend entries$/.test(key)) for (const node of value) if (isGroup(node)) repairTextNodes(node.content, context);
499
506
  const styleName = /^(.+?)\/\.(?:style|append style)$/.exec(key)?.[1];
507
+ // A user style called name/label must not replace TikZ's node naming or
508
+ // labeling machinery. Keep explicit property assignments on the native key.
509
+ if (styleName && ['name', 'label'].includes(styleName)) {
510
+ const alias = `tikz recovered ${styleName} style`;
511
+ context.styleAliases?.set(styleName, alias);
512
+ parts[0] = [str(key.replace(styleName, alias))];
513
+ } else if (!value && context.styleAliases?.has(key)) parts[0] = [str(context.styleAliases.get(key))];
514
+ if (value && /^(above|below|left|right)( left| right)?$/.test(key)) {
515
+ const target = value.findIndex(node => isString(node, 'of'));
516
+ const point = target >= 0 ? skipSpace(value, target + 1) : -1;
517
+ if (point >= 0 && value[point]?.type === 'inlinemath') {
518
+ // positioning adds the outer parentheses itself; only protect commas
519
+ // from the PGF key parser, which does not recognize math delimiters.
520
+ parts[1] = [{ type: 'group', content: value }];
521
+ }
522
+ }
500
523
  if (styleName && value) for (const node of value) if (isGroup(node)) unloopStyle(node.content, styleName);
501
524
  if (key === 'font' && value) {
502
525
  const color = pullColors(value);
@@ -535,6 +558,16 @@ function mergeParenthesisedValues(items) {
535
558
  for (let i = 0; i < items.length; i++) {
536
559
  const item = items[i];
537
560
  const parts = item.itemParts ?? [];
561
+ if (parts.length === 2 && /^(above|below|left|right)( left| right)?$/.test(printRaw(parts[0]).trim())) {
562
+ const of = parts[1].findIndex(node => isString(node, 'of'));
563
+ const number = tokens => /^-?\d+(?:\.\d+)?(?:cm|mm|pt)?$/.test(printRaw(tokens).trim());
564
+ const next = items[i + 1]?.itemParts;
565
+ if (of >= 0 && number(parts[1].slice(of + 1)) && next?.length === 1 && number(next[0])) {
566
+ out.push({ ...item, itemParts: [parts[0], [{ type: 'group', content: [...parts[1], str(','), ...next[0]] }]] });
567
+ i++;
568
+ continue;
569
+ }
570
+ }
538
571
  if (parts.length !== 2 || balance(parts[1]) <= 0) { out.push(item); continue; }
539
572
  const value = [...parts[1]];
540
573
  let depth = balance(value);
@@ -620,7 +653,7 @@ function processKeyLists(root, context) {
620
653
  continue;
621
654
  }
622
655
  if (node?.type === 'environment' && Array.isArray(node.args)) {
623
- for (const argument of node.args) if (argument.openMark === '[') processKeyList(argument.content, context);
656
+ for (const argument of node.args) if (argument.openMark === '[') processKeyList(argument.content, { ...context, tikz: ['tikzpicture', 'scope', 'circuitikz'].includes(node.env) });
624
657
  }
625
658
  if (isMacro(node) && KEY_LIST_MACROS.has(node.content) && hasArgs(node)) {
626
659
  for (const argument of node.args) if (argument.openMark === '{') processKeyList(argument.content, context);
@@ -882,6 +915,20 @@ function repairForeachLists(root) {
882
915
  }
883
916
  });
884
917
  }
918
+
919
+ // \boldmath expands unsafely while TikZ parses quoted labels. Move its effect
920
+ // into the actual math expression, keeping group scope and the displayed value.
921
+ function repairBoldMath(root) {
922
+ walkArrays(root, array => {
923
+ if (!array.some(node => node.type === 'inlinemath')) return;
924
+ let bold = false;
925
+ for (let i = 0; i < array.length; i++) {
926
+ if (isMacro(array[i], 'boldmath')) { bold = true; array.splice(i--, 1); continue; }
927
+ if (isMacro(array[i], 'unboldmath')) { bold = false; array.splice(i--, 1); continue; }
928
+ if (bold && array[i].type === 'inlinemath') array[i].content = [macro('boldsymbol', [{ type: 'argument', openMark: '{', closeMark: '}', content: array[i].content }])];
929
+ }
930
+ });
931
+ }
885
932
  function mathOnly(nodes) {
886
933
  if (nodes.some(node => node.type === 'inlinemath' || node.type === 'displaymath')) return false;
887
934
  return needsMath(nodes.filter(node => !isSpace(node)));
@@ -925,7 +972,7 @@ function formatNumbers(root) {
925
972
  * @returns {{ source: string, unavailable: string[], notes: string[] }}
926
973
  */
927
974
  export function prepareTikzDocument(code) {
928
- const cleaned = cleanInput(code);
975
+ const cleaned = recoverGeneratedSyntax(cleanInput(code));
929
976
  if (!cleaned) throw new TikzSourceError('TikZ source is empty.');
930
977
  const state = {
931
978
  packages: new Map(), libraries: new Set(DEFAULT_LIBRARIES), pgfplotsLibraries: new Set(), gdLibraries: new Set(),
@@ -944,15 +991,17 @@ export function prepareTikzDocument(code) {
944
991
  const unicodeSeen = new Set();
945
992
  const colorsDefined = new Set();
946
993
  const colorsSeen = new Set();
947
- for (const root of [body, statementsRoot]) {
994
+ const keyContext = { styleAliases: new Map() };
995
+ for (const root of [statementsRoot, body]) {
948
996
  rewriteHexColors(root);
949
997
  replaceUnavailableMacros(root, state);
950
- processKeyLists(root, {});
998
+ processKeyLists(root, keyContext);
951
999
  }
952
1000
  stripOverlayKeys(body);
953
1001
  protectAndDelimiter(body);
954
1002
  repairForeachLists(body);
955
1003
  replaceTypos(body);
1004
+ repairBoldMath(body);
956
1005
  repairNodeTexts(body);
957
1006
  // siunitx parses quantities itself; preserve its input grammar.
958
1007
  unicodeSupport(body, unicodeSeen, unicode);
@@ -988,6 +1037,12 @@ export function prepareTikzDocument(code) {
988
1037
  if (state.gdLibraries.size) lines.push(`\\usegdlibrary{${[...state.gdLibraries].join(',')}}`);
989
1038
  if (state.packages.has('pgfplots') && !state.compat) lines.push('\\pgfplotsset{compat=1.18}');
990
1039
  lines.push(...unicode, ...colors, T.COMPAT_SHIM);
1040
+ if (!excluded.has('pgfmathdistance') && /\\pgfmathdistance\b/.test(everything)) lines.push(String.raw`\makeatletter
1041
+ \providecommand{\pgfmathdistance}[2]{\pgfpointdiff{#1}{#2}\pgfmathveclen{\the\pgf@x}{\the\pgf@y}}
1042
+ \makeatother`);
1043
+ if (/\bparallelogram\b/.test(everything)) lines.push(String.raw`\pgfkeysifdefined{/tikz/parallelogram/.@cmd}{}{\tikzset{parallelogram/.style={shape=trapezium,trapezium left angle=70,trapezium right angle=110}}}`);
1044
+ lines.push(String.raw`\pgfkeysifdefined{/tikz/rounded cap/.@cmd}{}{\tikzset{rounded cap/.style={line cap=round}}}
1045
+ \pgfkeysifdefined{/tikz/final/.@cmd}{}{\tikzset{final/.style={accepting}}}`);
991
1046
  if (state.shims.has('siunitx')) lines.push(T.siunitxShim());
992
1047
  if (state.shims.has('physics')) lines.push(T.PHYSICS_SHIM);
993
1048
  if (state.shims.has('upgreek')) lines.push(...T.GREEK_NAMES.map(name => `\\providecommand{\\up${name}}{\\ensuremath{\\${name}}}`));
package/src/tables.js CHANGED
@@ -90,7 +90,7 @@ export const AUTO_PACKAGES = [
90
90
  ];
91
91
  export const AUTO_LIBRARIES = [
92
92
  [/\bmatrix of (?:math )?nodes\b|\\matrix\b/, 'matrix'],
93
- [/\b(?:regular polygon|trapezium|semicircle|isosceles triangle|kite|dart|circular sector|cylinder|diamond|star|ellipse)\s*[,\]=]/, 'shapes.geometric'],
93
+ [/\b(?:regular polygon|trapezium|parallelogram|semicircle|isosceles triangle|kite|dart|circular sector|cylinder|diamond|star|ellipse)\s*[,\]=]/, 'shapes.geometric'],
94
94
  [/\b(?:cross out|strike out|rounded rectangle|chamfered rectangle)\b/, 'shapes.misc'],
95
95
  [/\b(?:cloud|forbidden sign|magnifying glass|starburst|signal|tape|magnetic tape|correct forbidden sign)\s*[,\]=]/, 'shapes.symbols'],
96
96
  [/\b(?:rectangle split|circle split|ellipse split)\b/, 'shapes.multipart'],