reladraw 0.23.1 → 0.25.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.
package/dist/resolve.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { TEXT_GAP } from './linetexts.js';
2
- import { POINT_KEYS, REGION_KEYS, ALL_ATTR_KEYS, ATTR_KEYS, MARKS, COLOR_KEYS, COLOR_PARTS, CHILD_ALIGNMENTS, GATHER_PLACES, CONTENT_WIDTHS, SIZE_KEY_NAMES, LINE_VALUES, NODE_CORNERS, PADDING_SIDES, describePlacement, nameTarget, partWordHint, } from './ast.js';
3
- import { DEFAULT_FONT_SIZE, DEFAULT_MARGIN, GAPS, ICON_GAP, ICON_LINES, LINE_WIDTH, PAD, THICKNESS, BORDER_THICKNESS, BORDER_WIDTH, fontSizeFor, widestLine, } from './constants.js';
2
+ import { DESCRIPTION_NAME_KEY, LINK_NAMES_KEY, POINT_KEYS, REGION_KEYS, ALL_ATTR_KEYS, ATTR_KEYS, MARKS, COLOR_KEYS, COLOR_PARTS, CHILD_ALIGNMENTS, GATHER_PLACES, CONTENT_WIDTHS, SIZE_KEY_NAMES, LINE_VALUES, NODE_CORNERS, PADDING_SIDES, describePlacement, nameTarget, partWordHint, } from './ast.js';
3
+ import { DEFAULT_FONT_SIZE, DEFAULT_MARGIN, GAPS, ICON_GAP, ICON_LINES, LINE_WIDTH, PAD, THICKNESS, BORDER_THICKNESS, BORDER_WIDTH, fontSizeFor, linesTall, widestLine, } from './constants.js';
4
4
  import { SourceError } from './errors.js';
5
5
  import { bodyFor, iconNamed, pastedIcon } from './icons.js';
6
- import { linksOf } from './links.js';
6
+ import { LINKS_KEY, linksOf } from './links.js';
7
+ import { unsafeLinks } from './markdown.js';
7
8
  import { monospaceMeasurer } from './measure.js';
8
9
  import { isBoxy, middleOf, sizeFor, SUBPROCESS_INSET } from './outline.js';
9
10
  import { markupStyles, parseMarkup, plain, splitRuns, wrapLine } from './text.js';
@@ -20,7 +21,7 @@ function factsFor(node, measurer, fontSize) {
20
21
  const lineHeight = measurer.lineHeight(textSize);
21
22
  const hasText = node.lines.some((line) => plain(line).length > 0);
22
23
  const across = hasText ? widestLine(node.lines, measurer, textSize) : 0;
23
- const tall = hasText ? node.lines.length * lineHeight : 0;
24
+ const tall = hasText ? linesTall(node.lines, measurer, textSize) : 0;
24
25
  // A turned text (design.org 7.2) takes the room of its turned ink: as wide
25
26
  // as its lines are tall, as tall as its widest line.
26
27
  const turned = node.textAttrs['reads'] !== undefined;
@@ -299,6 +300,10 @@ export function resolve(doc, options = {}) {
299
300
  const measurer = options.measurer ?? monospaceMeasurer();
300
301
  const fontSize = options.fontSize ?? DEFAULT_FONT_SIZE;
301
302
  const margin = options.margin ?? DEFAULT_MARGIN;
303
+ // `description: notes` becomes the text it names, and `description for …`
304
+ // waits until the boxes and lines it names exist. Nothing else reads them.
305
+ const described = nameDescriptions(doc.statements);
306
+ doc = { statements: nameLinks(described.statements) };
302
307
  const styles = collectStyles(doc.statements);
303
308
  checkStyleKeys(doc.statements);
304
309
  const defaults = collectDefaults(doc.statements, styles);
@@ -315,7 +320,7 @@ export function resolve(doc, options = {}) {
315
320
  }
316
321
  }
317
322
  }
318
- const { nodes, byName, roots } = buildTree(writeOutCards(doc.statements), styles, defaults, icons);
323
+ const { nodes, byName, roots } = buildTree(writeOutCards(doc.statements), styles, defaults, icons, fontSize);
319
324
  // From here on, `edge each child of a -> b` and `edge a -> b, c` are the lines they stand for.
320
325
  const statements = expandEach(doc.statements, byName);
321
326
  // Edges are resolved to nodes before anything is sized, because a labeled
@@ -323,8 +328,9 @@ export function resolve(doc, options = {}) {
323
328
  // gaps are being worked out. Nothing here reads geometry.
324
329
  const named = collectNamed(doc.statements, byName);
325
330
  const edges = buildEdges(statements, byName, named, styles, defaults, measurer, fontSize);
331
+ attachDescriptions(described.attached, byName, edges);
326
332
  const facts = new Map(nodes.map((node) => [node, factsFor(node, measurer, fontSize)]));
327
- const markTexts = measureMarkTexts(named, measurer, fontSize);
333
+ const markTexts = measureMarkTexts(named, measurer, fontSize, styles);
328
334
  const regionTexts = new Map([...markTexts].flatMap(([name, text]) => (text.region ? [[name, text.region]] : [])));
329
335
  const placer = new Placer(roots, byName, facts, named, regionTexts);
330
336
  placer.run();
@@ -433,18 +439,18 @@ function namesIn(target, named, seen = new Set()) {
433
439
  * an inside text grows its region (design.org 7.4). Set small, as an aside is:
434
440
  * it labels a spot or an area rather than being a thing in the diagram.
435
441
  */
436
- function measureMarkTexts(named, measurer, fontSize) {
442
+ function measureMarkTexts(named, measurer, fontSize, styles) {
437
443
  const measured = new Map();
438
444
  for (const [name, stmt] of named) {
439
445
  if (!stmt.text)
440
446
  continue;
441
447
  const { text, textAttrs } = stmt.text;
442
448
  const subject = `${stmt.kind} "${name}"`;
443
- const lines = linesFor(text, textAttrs, subject, stmt.line);
444
449
  const size = fontSizeFor('none', textAttrs, fontSize, stmt.line);
450
+ const lines = linesFor(text, textAttrs, subject, stmt.line, { styles, fontSize, base: size });
445
451
  const lineHeight = measurer.lineHeight(size);
446
452
  const across = widestLine(lines, measurer, size);
447
- const tall = lines.length * lineHeight;
453
+ const tall = linesTall(lines, measurer, size);
448
454
  const turned = textAttrs['reads'] !== undefined;
449
455
  const width = turned ? tall : across;
450
456
  const height = turned ? across : tall;
@@ -598,6 +604,154 @@ function collectStyles(statements) {
598
604
  return styles;
599
605
  }
600
606
  /** The file's own icons, by name, each pasted in or read from its file. */
607
+ /**
608
+ * Swaps each `description: notes` for the text `description notes` declares,
609
+ * and sets aside the `description for …` statements, which name boxes and
610
+ * lines that don't exist until the tree is built. Every description's links
611
+ * are checked here, inline ones included.
612
+ */
613
+ function nameDescriptions(statements) {
614
+ const declared = new Map();
615
+ const attached = [];
616
+ const checkLinks = (text, line) => {
617
+ const bad = unsafeLinks(text)[0];
618
+ if (bad !== undefined) {
619
+ throw new SourceError(`a description links to "${bad}" — a link goes to a web or mail address (https:, http:, mailto:) or a path`, line);
620
+ }
621
+ };
622
+ for (const stmt of statements) {
623
+ if (stmt.kind !== 'description')
624
+ continue;
625
+ checkLinks(stmt.text, stmt.line);
626
+ if (stmt.targets !== undefined) {
627
+ attached.push(stmt);
628
+ continue;
629
+ }
630
+ const before = declared.get(stmt.name);
631
+ if (before !== undefined) {
632
+ throw new SourceError(`description "${stmt.name}" is declared twice, on lines ${before.line} and ${stmt.line} — keep one`, stmt.line);
633
+ }
634
+ declared.set(stmt.name, stmt);
635
+ }
636
+ const rest = statements.flatMap((stmt) => {
637
+ if (stmt.kind === 'description')
638
+ return [];
639
+ if (!('attrs' in stmt) || stmt.attrs === undefined)
640
+ return [stmt];
641
+ if (stmt.attrs['description'] !== undefined)
642
+ checkLinks(stmt.attrs['description'], stmt.line);
643
+ const name = stmt.attrs[DESCRIPTION_NAME_KEY];
644
+ if (name === undefined)
645
+ return [stmt];
646
+ const found = declared.get(name);
647
+ if (found === undefined) {
648
+ const known = [...declared.keys()];
649
+ throw new SourceError(`there is no description named "${name}" — ` +
650
+ (known.length === 0
651
+ ? 'declare it with `description ' + name + ' """…"""`, or quote the text to write it here'
652
+ : `the file declares ${known.map((one) => `"${one}"`).join(', ')}; quote the text to write it here`), stmt.line);
653
+ }
654
+ const { [DESCRIPTION_NAME_KEY]: _, ...attrs } = stmt.attrs;
655
+ return [{ ...stmt, attrs: { ...attrs, description: found.text } }];
656
+ });
657
+ return { statements: rest, attached };
658
+ }
659
+ /**
660
+ * Takes out the `url` statements and swaps each declared name in a `url:` for
661
+ * the links it declares, a bundle's in order, so nothing after this sees a name.
662
+ */
663
+ function nameLinks(statements) {
664
+ const declared = new Map();
665
+ for (const stmt of statements) {
666
+ if (stmt.kind !== 'url')
667
+ continue;
668
+ const before = declared.get(stmt.name);
669
+ if (before !== undefined) {
670
+ throw new SourceError(`url "${stmt.name}" is declared twice, on lines ${before.line} and ${stmt.line} — keep one`, stmt.line);
671
+ }
672
+ declared.set(stmt.name, stmt);
673
+ }
674
+ const unknown = (name, line) => {
675
+ const known = [...declared.keys()];
676
+ return new SourceError(`there is no link named "${name}" — ` +
677
+ (known.length === 0
678
+ ? 'declare it with `url ' + name + ' "https://…"`, or quote an address to write it here'
679
+ : `the file declares ${known.map((one) => `"${one}"`).join(', ')}; quote an address to write it here`), line);
680
+ };
681
+ const expanded = new Map();
682
+ const expand = (stmt, path) => {
683
+ const done = expanded.get(stmt.name);
684
+ if (done !== undefined)
685
+ return done;
686
+ if (path.includes(stmt.name)) {
687
+ const loop = [...path.slice(path.indexOf(stmt.name)), stmt.name];
688
+ throw new SourceError(`url "${stmt.name}" reaches itself (${loop.join(' → ')}), so it never says where it goes — take one of them out`, stmt.line);
689
+ }
690
+ const links = flatten(stmt.entries, `url "${stmt.name}"`, stmt.line, [...path, stmt.name]);
691
+ expanded.set(stmt.name, links);
692
+ return links;
693
+ };
694
+ const flatten = (entries, subject, line, path) => {
695
+ const links = [];
696
+ for (const entry of entries) {
697
+ let more;
698
+ if ('href' in entry)
699
+ more = [entry];
700
+ else {
701
+ const found = declared.get(entry.ref);
702
+ if (found === undefined)
703
+ throw unknown(entry.ref, line);
704
+ more = expand(found, path);
705
+ }
706
+ for (const link of more) {
707
+ if (links.some((had) => had.href === link.href)) {
708
+ const via = 'ref' in entry ? ` (through "${entry.ref}")` : '';
709
+ throw new SourceError(`${subject}: "${link.href}" is linked twice${via} — keep one`, line);
710
+ }
711
+ links.push(link);
712
+ }
713
+ }
714
+ return links;
715
+ };
716
+ for (const stmt of declared.values())
717
+ expand(stmt, []);
718
+ return statements.flatMap((stmt) => {
719
+ if (stmt.kind === 'url')
720
+ return [];
721
+ if (!('attrs' in stmt) || stmt.attrs === undefined)
722
+ return [stmt];
723
+ const listed = stmt.attrs[LINK_NAMES_KEY];
724
+ if (listed === undefined)
725
+ return [stmt];
726
+ const subject = stmt.kind === 'node' ? `node "${stmt.name}"` : stmt.kind;
727
+ const links = flatten(JSON.parse(listed), subject, stmt.line, []);
728
+ const { [LINK_NAMES_KEY]: _, ...attrs } = stmt.attrs;
729
+ attrs['url'] = links[0].href;
730
+ // As the parser does: the list is kept only when there is more than one bare address.
731
+ if (links.length > 1 || links.some((link) => link.name !== undefined || link.icon !== undefined))
732
+ attrs[LINKS_KEY] = JSON.stringify(links);
733
+ return [{ ...stmt, attrs }];
734
+ });
735
+ }
736
+ /** `description for proxy, cache`: each name a box, or a named line, that has no description yet. */
737
+ function attachDescriptions(attached, byName, edges) {
738
+ for (const stmt of attached) {
739
+ for (const target of stmt.targets) {
740
+ const node = byName.get(target);
741
+ const lines = node ? [] : edges.filter((edge) => edge.name === target);
742
+ const things = node ? [node] : lines;
743
+ if (things.length === 0) {
744
+ throw new SourceError(`description for "${target}": there is no box or named line called "${target}"`, stmt.line);
745
+ }
746
+ for (const thing of things) {
747
+ if (thing.attrs['description'] !== undefined) {
748
+ throw new SourceError(`description for "${target}": "${target}" already has a description — keep one`, stmt.line);
749
+ }
750
+ thing.attrs['description'] = stmt.text;
751
+ }
752
+ }
753
+ }
754
+ }
601
755
  function collectIcons(statements, readIconFile) {
602
756
  const icons = new Map();
603
757
  for (const stmt of statements) {
@@ -678,7 +832,7 @@ function collectDiagram(statements) {
678
832
  }
679
833
  return found ?? {};
680
834
  }
681
- function buildTree(statements, styles, defaults, icons) {
835
+ function buildTree(statements, styles, defaults, icons, fontSize) {
682
836
  const nodes = [];
683
837
  const byName = new Map();
684
838
  const roots = [];
@@ -778,7 +932,11 @@ function buildTree(statements, styles, defaults, icons) {
778
932
  body,
779
933
  corners: 'rounded',
780
934
  text,
781
- lines: linesFor(text, textAttrs, `"${stmt.name}"`, stmt.line),
935
+ lines: linesFor(text, textAttrs, `"${stmt.name}"`, stmt.line, {
936
+ styles,
937
+ fontSize,
938
+ base: fontSizeFor(kind, textAttrs, fontSize, stmt.line),
939
+ }),
782
940
  children: [],
783
941
  x: 0,
784
942
  y: 0,
@@ -1258,6 +1416,10 @@ function checkStyleKeys(statements) {
1258
1416
  if (topKey(key) === 'cards') {
1259
1417
  throw new SourceError(`style "${stmt.name}" has cards:, which are one deck's own children, so it is written on that deck`, stmt.line);
1260
1418
  }
1419
+ if (topKey(key) === 'description') {
1420
+ throw new SourceError(`style "${stmt.name}" has description:, which is about the things it describes rather than how they look — ` +
1421
+ 'to give several one text, write `description for a, b and c """…"""`', stmt.line);
1422
+ }
1261
1423
  if (topKey(key) === 'from' || topKey(key) === 'to') {
1262
1424
  throw new SourceError(`style "${stmt.name}" has ${key}:, which says where one line ends, so it is written on that edge`, stmt.line);
1263
1425
  }
@@ -1445,8 +1607,8 @@ function buildEdges(statements, byName, named, styles, defaults, measurer, fontS
1445
1607
  // page selects a line by the same, so it is accepted.
1446
1608
  const texts = stmt.texts.map(({ text, textAttrs: own, place }) => {
1447
1609
  const textAttrs = { ...bracketOf('text', appearance), ...own };
1448
- const lines = linesFor(text, textAttrs, `edge ${what}`, stmt.line);
1449
1610
  const size = fontSizeFor('edge', textAttrs, fontSize, stmt.line);
1611
+ const lines = linesFor(text, textAttrs, `edge ${what}`, stmt.line, { styles, fontSize, base: size });
1450
1612
  return {
1451
1613
  text,
1452
1614
  lines,
@@ -1454,7 +1616,7 @@ function buildEdges(statements, byName, named, styles, defaults, measurer, fontS
1454
1616
  place,
1455
1617
  size,
1456
1618
  width: widestLine(lines, measurer, size),
1457
- height: lines.length * measurer.lineHeight(size),
1619
+ height: linesTall(lines, measurer, size),
1458
1620
  };
1459
1621
  });
1460
1622
  edges.push({
@@ -1775,13 +1937,15 @@ function bracketOf(key, attrs) {
1775
1937
  return found;
1776
1938
  }
1777
1939
  /**
1778
- * Every style the markup in this file names, resolved to the color it lends.
1940
+ * Every style the markup in this file names that lends a color, resolved to
1941
+ * that color. A style may lend a size instead or as well; the runs carry that
1942
+ * themselves, from `sizeRuns`.
1779
1943
  *
1780
1944
  * A style is the only thing markup may name — never a color — so that a marked
1781
1945
  * word borrows a meaning the file already has instead of restating a value that
1782
1946
  * goes stale the day the thing it means is recolored. Both ways that can fail
1783
- * are refused by name: a style nobody declared, and one that says nothing about
1784
- * text and so would lend nothing.
1947
+ * are refused by name: a style nobody declared, and one that gives text neither
1948
+ * a color nor a size and so would lend nothing.
1785
1949
  */
1786
1950
  function markupColors(nodes, edges, styles, marks = []) {
1787
1951
  const colors = {};
@@ -1797,11 +1961,12 @@ function markupColors(nodes, edges, styles, marks = []) {
1797
1961
  throw new SourceError(`${what}: its text marks [${name}], and there is no style called "${name}"`, line);
1798
1962
  }
1799
1963
  const color = style['text.color'];
1800
- if (color === undefined) {
1801
- throw new SourceError(`${what}: its text marks [${name}], and style "${name}" says nothing about text — ` +
1802
- `write \`style ${name} text: (color: …)\``, line);
1964
+ if (color === undefined && style['text.size'] === undefined) {
1965
+ throw new SourceError(`${what}: its text marks [${name}], and style "${name}" gives text no color or size — ` +
1966
+ `write \`style ${name} text: (color: …)\` or \`text: (size: …)\``, line);
1803
1967
  }
1804
- colors[name] = color;
1968
+ if (color !== undefined)
1969
+ colors[name] = color;
1805
1970
  }
1806
1971
  }
1807
1972
  return colors;
@@ -1820,8 +1985,10 @@ function markupColors(nodes, edges, styles, marks = []) {
1820
1985
  * what makes markup general where the `subtext:` slice it replaced could only
1821
1986
  * ever reach the tail of a text.
1822
1987
  */
1823
- function linesFor(text, textAttrs, subject, line) {
1988
+ function linesFor(text, textAttrs, subject, line, sizes) {
1824
1989
  const lines = splitRuns(parseMarkup(text, subject, line));
1990
+ if (sizes)
1991
+ sizeRuns(lines, sizes, line);
1825
1992
  const stated = textAttrs['wrap'];
1826
1993
  if (stated === undefined)
1827
1994
  return lines;
@@ -1829,7 +1996,25 @@ function linesFor(text, textAttrs, subject, line) {
1829
1996
  if (!Number.isInteger(columns) || columns < 1) {
1830
1997
  throw new SourceError(`wrap must be a whole number of characters, not "${stated}"`, line);
1831
1998
  }
1832
- return lines.flatMap((part) => wrapLine(part, columns));
1999
+ return lines.flatMap((part) => wrapLine(part, columns, sizes?.base));
2000
+ }
2001
+ /**
2002
+ * Give each marked run the size its style states (design.org 7.1). A run whose
2003
+ * style states no size, or the same size as the text it sits in, keeps none,
2004
+ * so a text marked only for color measures exactly as it always did. A style
2005
+ * that doesn't exist is left for `markupColors` to refuse by name.
2006
+ */
2007
+ function sizeRuns(lines, sizes, line) {
2008
+ for (const run of lines.flat()) {
2009
+ if (run.style === undefined)
2010
+ continue;
2011
+ const stated = sizes.styles.get(run.style)?.['text.size'];
2012
+ if (stated === undefined)
2013
+ continue;
2014
+ const size = fontSizeFor('node', { size: stated }, sizes.fontSize, line);
2015
+ if (size !== sizes.base)
2016
+ run.size = size;
2017
+ }
1833
2018
  }
1834
2019
  function bounds(nodes, marks) {
1835
2020
  let minX = Infinity;
package/dist/text.d.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  /** A stretch of text drawn in one style. `style` is a style name from the file. */
2
2
  export interface Run {
3
3
  text: string;
4
- /** The style whose text color this run borrows, or undefined for the node's own. */
4
+ /** The style whose text color and size this run borrows, or undefined for the node's own. */
5
5
  style?: string;
6
+ /**
7
+ * The run's text size in pixels, when its style states one; undefined for
8
+ * the size of the text it sits in. Filled in by the resolver, which knows the
9
+ * styles and the document's size.
10
+ */
11
+ size?: number;
6
12
  }
7
13
  /** One drawn line, as the runs it is made of. */
8
14
  export type Line = Run[];
@@ -40,8 +46,14 @@ export declare function parseMarkup(text: string, subject: string, line: number)
40
46
  * replaced could only reach the tail of a text.
41
47
  */
42
48
  export declare function splitRuns(runs: Run[]): Line[];
43
- /** Fold one line onto several at word boundaries, never exceeding `columns`. */
44
- export declare function wrapLine(line: Line, columns: number): Line[];
49
+ /**
50
+ * Fold one line onto several at word boundaries, never exceeding `columns`.
51
+ *
52
+ * A column is a character of the text's own size (`base`). A marked run of
53
+ * another size counts each of its characters by the ratio of the two, so a
54
+ * small word takes less of the line than a normal one, as it does when drawn.
55
+ */
56
+ export declare function wrapLine(line: Line, columns: number, base?: number): Line[];
45
57
  /** The line as it reads, with no markup — what gets measured. */
46
58
  export declare function plain(line: Line): string;
47
59
  /** Every style name the markup in these lines refers to, without repeats. */
package/dist/text.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * A text and the runs inside it.
3
3
  *
4
4
  * A text is a string, and the one piece of structure it may carry is markup:
5
- * `[dim]synced[/dim]` draws that word in the text color of `style dim`. The
5
+ * `[dim]synced[/dim]` draws that word in the text color and size of `style dim`. The
6
6
  * opener names a *style* and never a color, so the word borrows a meaning the
7
7
  * file already has rather than restating a value that goes stale the day the
8
8
  * thing it means is recolored.
@@ -14,7 +14,7 @@
14
14
  * reaches a word in the middle of a line, which the slice never could.
15
15
  *
16
16
  * Everything downstream therefore works in *runs* rather than strings: a drawn
17
- * line is a list of runs, each with the style it borrows its color from, and
17
+ * line is a list of runs, each with the style it borrows its color and size from, and
18
18
  * line breaking and wrapping carry the runs along rather than re-parsing text
19
19
  * that has already been read.
20
20
  */
@@ -142,20 +142,29 @@ function tidy(lines) {
142
142
  }
143
143
  return kept.length > 0 ? kept : [[{ text: '' }]];
144
144
  }
145
- /** Fold one line onto several at word boundaries, never exceeding `columns`. */
146
- export function wrapLine(line, columns) {
145
+ /**
146
+ * Fold one line onto several at word boundaries, never exceeding `columns`.
147
+ *
148
+ * A column is a character of the text's own size (`base`). A marked run of
149
+ * another size counts each of its characters by the ratio of the two, so a
150
+ * small word takes less of the line than a normal one, as it does when drawn.
151
+ */
152
+ export function wrapLine(line, columns, base) {
147
153
  const lines = [];
148
154
  let current = [];
149
155
  let length = 0;
156
+ const weight = (run) => (run.size === undefined || base === undefined ? 1 : run.size / base);
150
157
  for (const word of words(line)) {
151
- const space = length === 0 ? 0 : 1;
152
- if (length > 0 && length + space + word.text.length > columns) {
158
+ const scale = weight(word);
159
+ const space = length === 0 ? 0 : scale;
160
+ const wide = word.text.length * scale;
161
+ if (length > 0 && length + space + wide > columns + 1e-9) {
153
162
  lines.push(current);
154
163
  current = [];
155
164
  length = 0;
156
165
  }
157
166
  append(current, length === 0 ? word : { ...word, text: ` ${word.text}` });
158
- length += (length === 0 ? 0 : 1) + word.text.length;
167
+ length += (length === 0 ? 0 : scale) + wide;
159
168
  }
160
169
  if (current.length > 0)
161
170
  lines.push(current);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reladraw",
3
- "version": "0.23.1",
3
+ "version": "0.25.0",
4
4
  "description": "A diagram language where you say where things go.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,7 +35,6 @@
35
35
  "scripts": {
36
36
  "build": "tsc && tsc -p tsconfig.element.json",
37
37
  "prepublishOnly": "npm run build",
38
- "preversion": "./dev.sh check-version \"$npm_new_version\"",
39
38
  "dev": "tsc --watch",
40
39
  "example": "npm run build && node dist/cli.js render examples/arch.reladraw -o examples/out/arch.svg"
41
40
  },