tsdraft 0.2.0 → 0.2.2
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/CHANGELOG.md +85 -0
- package/README.md +130 -7
- package/dist/drawing.d.ts +4 -0
- package/dist/drawing.js +40 -7
- package/dist/dxf-r12.d.ts +1 -0
- package/dist/dxf-r12.js +6 -4
- package/dist/dxf-r2018.js +17 -6
- package/dist/format.d.ts +32 -3
- package/dist/format.js +60 -6
- package/dist/layers.d.ts +66 -0
- package/dist/layers.js +255 -0
- package/dist/lint.d.ts +10 -1
- package/dist/lint.js +103 -18
- package/dist/rules.d.ts +89 -3
- package/dist/rules.js +112 -1
- package/dist/svg.js +9 -2
- package/package.json +1 -1
package/dist/layers.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Layer } from "./drawing.ts";
|
|
2
|
+
/** C1 controls and invisible formatting characters, such as the zero-width space. */
|
|
3
|
+
export declare const REJECTED_FORMAT = "\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u0600\u0601\u0602\u0603\u06DD\u070F\u17B4\u17B5\u200B\u200C\u200D\u200E\u200F\u202A\u202B\u202C\u202D\u202E\u2060\u2061\u2062\u2063\u2064\u206A\u206B\u206C\u206D\u206E\u206F\uFEFF\uFFF9\uFFFA\uFFFB";
|
|
4
|
+
/** Superscript and subscript letters, Roman numeral characters and letterlike symbols such as the ohm sign. */
|
|
5
|
+
export declare const REJECTED_LETTERLIKE = "\u2071\u207F\u2090\u2091\u2092\u2093\u2094\u2102\u2107\u210A\u210B\u210C\u210D\u210E\u210F\u2110\u2111\u2112\u2113\u2115\u2119\u211A\u211B\u211C\u211D\u2124\u2126\u2128\u212A\u212B\u212C\u212D\u212F\u2130\u2131\u2132\u2133\u2134\u2135\u2136\u2137\u2138\u2139\u213C\u213D\u213E\u213F\u2145\u2146\u2147\u2148\u2149\u214E\u2180\u2181\u2182\u2183\u2184\u2185\u2186\u2187\u2188";
|
|
6
|
+
/** Glagolitic letters and the Latin Extended-C block. */
|
|
7
|
+
export declare const REJECTED_GLAGOLITIC = "\u2C00\u2C01\u2C02\u2C03\u2C04\u2C05\u2C06\u2C07\u2C08\u2C09\u2C0A\u2C0B\u2C0C\u2C0D\u2C0E\u2C0F\u2C10\u2C11\u2C12\u2C13\u2C14\u2C15\u2C16\u2C17\u2C18\u2C19\u2C1A\u2C1B\u2C1C\u2C1D\u2C1E\u2C1F\u2C20\u2C21\u2C22\u2C23\u2C24\u2C25\u2C26\u2C27\u2C28\u2C29\u2C2A\u2C2B\u2C2C\u2C2D\u2C2E\u2C30\u2C31\u2C32\u2C33\u2C34\u2C35\u2C36\u2C37\u2C38\u2C39\u2C3A\u2C3B\u2C3C\u2C3D\u2C3E\u2C3F\u2C40\u2C41\u2C42\u2C43\u2C44\u2C45\u2C46\u2C47\u2C48\u2C49\u2C4A\u2C4B\u2C4C\u2C4D\u2C4E\u2C4F\u2C50\u2C51\u2C52\u2C53\u2C54\u2C55\u2C56\u2C57\u2C58\u2C59\u2C5A\u2C5B\u2C5C\u2C5D\u2C5E\u2C60\u2C61\u2C62\u2C63\u2C64\u2C65\u2C66\u2C67\u2C68\u2C69\u2C6A\u2C6B\u2C6C\u2C6D\u2C6E\u2C6F\u2C71\u2C72\u2C73\u2C74\u2C75\u2C76\u2C77\u2C78\u2C79\u2C7A\u2C7B\u2C7C\u2C7D";
|
|
8
|
+
/** Coptic letters. */
|
|
9
|
+
export declare const REJECTED_COPTIC = "\u2C80\u2C81\u2C82\u2C83\u2C84\u2C85\u2C86\u2C87\u2C88\u2C89\u2C8A\u2C8B\u2C8C\u2C8D\u2C8E\u2C8F\u2C90\u2C91\u2C92\u2C93\u2C94\u2C95\u2C96\u2C97\u2C98\u2C99\u2C9A\u2C9B\u2C9C\u2C9D\u2C9E\u2C9F\u2CA0\u2CA1\u2CA2\u2CA3\u2CA4\u2CA5\u2CA6\u2CA7\u2CA8\u2CA9\u2CAA\u2CAB\u2CAC\u2CAD\u2CAE\u2CAF\u2CB0\u2CB1\u2CB2\u2CB3\u2CB4\u2CB5\u2CB6\u2CB7\u2CB8\u2CB9\u2CBA\u2CBB\u2CBC\u2CBD\u2CBE\u2CBF\u2CC0\u2CC1\u2CC2\u2CC3\u2CC4\u2CC5\u2CC6\u2CC7\u2CC8\u2CC9\u2CCA\u2CCB\u2CCC\u2CCD\u2CCE\u2CCF\u2CD0\u2CD1\u2CD2\u2CD3\u2CD4\u2CD5\u2CD6\u2CD7\u2CD8\u2CD9\u2CDA\u2CDB\u2CDC\u2CDD\u2CDE\u2CDF\u2CE0\u2CE1\u2CE2\u2CE3\u2CE4";
|
|
10
|
+
/** Georgian Supplement, Tifinagh and Ethiopic Extended letters. */
|
|
11
|
+
export declare const REJECTED_GEORGIAN = "\u2D00\u2D01\u2D02\u2D03\u2D04\u2D05\u2D06\u2D07\u2D08\u2D09\u2D0A\u2D0B\u2D0C\u2D0D\u2D0E\u2D0F\u2D10\u2D11\u2D12\u2D13\u2D14\u2D15\u2D16\u2D17\u2D18\u2D19\u2D1A\u2D1B\u2D1C\u2D1D\u2D1E\u2D1F\u2D20\u2D21\u2D22\u2D23\u2D24\u2D25\u2D30\u2D31\u2D32\u2D33\u2D34\u2D35\u2D36\u2D37\u2D38\u2D39\u2D3A\u2D3B\u2D3C\u2D3D\u2D3E\u2D3F\u2D40\u2D41\u2D42\u2D43\u2D44\u2D45\u2D46\u2D47\u2D48\u2D49\u2D4A\u2D4B\u2D4C\u2D4D\u2D4E\u2D4F\u2D50\u2D51\u2D52\u2D53\u2D54\u2D55\u2D56\u2D57\u2D58\u2D59\u2D5A\u2D5B\u2D5C\u2D5D\u2D5E\u2D5F\u2D60\u2D61\u2D62\u2D63\u2D64\u2D65\u2D6F\u2D80\u2D81\u2D82\u2D83\u2D84\u2D85\u2D86\u2D87\u2D88\u2D89\u2D8A\u2D8B\u2D8C\u2D8D\u2D8E\u2D8F\u2D90\u2D91\u2D92\u2D93\u2D94\u2D95\u2D96\u2DA0\u2DA1\u2DA2\u2DA3\u2DA4\u2DA5\u2DA6\u2DA8\u2DA9\u2DAA\u2DAB\u2DAC\u2DAD\u2DAE\u2DB0\u2DB1\u2DB2\u2DB3\u2DB4\u2DB5\u2DB6\u2DB8\u2DB9\u2DBA\u2DBB\u2DBC\u2DBD\u2DBE\u2DC0\u2DC1\u2DC2\u2DC3\u2DC4\u2DC5\u2DC6\u2DC8\u2DC9\u2DCA\u2DCB\u2DCC\u2DCD\u2DCE\u2DD0\u2DD1\u2DD2\u2DD3\u2DD4\u2DD5\u2DD6\u2DD8\u2DD9\u2DDA\u2DDB\u2DDC\u2DDD\u2DDE";
|
|
12
|
+
/** The private-use characters U+E800 to U+E8FF. */
|
|
13
|
+
export declare const REJECTED_PRIVATE = "\uE800\uE801\uE802\uE803\uE804\uE805\uE806\uE807\uE808\uE809\uE80A\uE80B\uE80C\uE80D\uE80E\uE80F\uE810\uE811\uE812\uE813\uE814\uE815\uE816\uE817\uE818\uE819\uE81A\uE81B\uE81C\uE81D\uE81E\uE81F\uE820\uE821\uE822\uE823\uE824\uE825\uE826\uE827\uE828\uE829\uE82A\uE82B\uE82C\uE82D\uE82E\uE82F\uE830\uE831\uE832\uE833\uE834\uE835\uE836\uE837\uE838\uE839\uE83A\uE83B\uE83C\uE83D\uE83E\uE83F\uE840\uE841\uE842\uE843\uE844\uE845\uE846\uE847\uE848\uE849\uE84A\uE84B\uE84C\uE84D\uE84E\uE84F\uE850\uE851\uE852\uE853\uE854\uE855\uE856\uE857\uE858\uE859\uE85A\uE85B\uE85C\uE85D\uE85E\uE85F\uE860\uE861\uE862\uE863\uE864\uE865\uE866\uE867\uE868\uE869\uE86A\uE86B\uE86C\uE86D\uE86E\uE86F\uE870\uE871\uE872\uE873\uE874\uE875\uE876\uE877\uE878\uE879\uE87A\uE87B\uE87C\uE87D\uE87E\uE87F\uE880\uE881\uE882\uE883\uE884\uE885\uE886\uE887\uE888\uE889\uE88A\uE88B\uE88C\uE88D\uE88E\uE88F\uE890\uE891\uE892\uE893\uE894\uE895\uE896\uE897\uE898\uE899\uE89A\uE89B\uE89C\uE89D\uE89E\uE89F\uE8A0\uE8A1\uE8A2\uE8A3\uE8A4\uE8A5\uE8A6\uE8A7\uE8A8\uE8A9\uE8AA\uE8AB\uE8AC\uE8AD\uE8AE\uE8AF\uE8B0\uE8B1\uE8B2\uE8B3\uE8B4\uE8B5\uE8B6\uE8B7\uE8B8\uE8B9\uE8BA\uE8BB\uE8BC\uE8BD\uE8BE\uE8BF\uE8C0\uE8C1\uE8C2\uE8C3\uE8C4\uE8C5\uE8C6\uE8C7\uE8C8\uE8C9\uE8CA\uE8CB\uE8CC\uE8CD\uE8CE\uE8CF\uE8D0\uE8D1\uE8D2\uE8D3\uE8D4\uE8D5\uE8D6\uE8D7\uE8D8\uE8D9\uE8DA\uE8DB\uE8DC\uE8DD\uE8DE\uE8DF\uE8E0\uE8E1\uE8E2\uE8E3\uE8E4\uE8E5\uE8E6\uE8E7\uE8E8\uE8E9\uE8EA\uE8EB\uE8EC\uE8ED\uE8EE\uE8EF\uE8F0\uE8F1\uE8F2\uE8F3\uE8F4\uE8F5\uE8F6\uE8F7\uE8F8\uE8F9\uE8FA\uE8FB\uE8FC\uE8FD\uE8FE\uE8FF";
|
|
14
|
+
/**
|
|
15
|
+
* Finds the first character of a layer name that AutoCAD rejects, beyond the
|
|
16
|
+
* ASCII punctuation and control characters that `layerNameProblem` checks.
|
|
17
|
+
*
|
|
18
|
+
* @param name - The layer name.
|
|
19
|
+
* @returns The character, or undefined when the name has none.
|
|
20
|
+
*/
|
|
21
|
+
export declare function rejectedNameCharacter(name: string): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The form of a layer name that AutoCAD looks layers up by. Two names with the
|
|
24
|
+
* same key are one layer to AutoCAD: an entity on `wall` goes on a layer
|
|
25
|
+
* declared as `WALL`, and AutoCAD keeps only the first of two such layers.
|
|
26
|
+
*
|
|
27
|
+
* AutoCAD lower-cases one character at a time from its own table, so this is
|
|
28
|
+
* not `toLowerCase()`: a final sigma ς, a dotless ı and ß stay as they are.
|
|
29
|
+
*
|
|
30
|
+
* @param name - The layer name.
|
|
31
|
+
* @returns The key to compare layer names by.
|
|
32
|
+
*/
|
|
33
|
+
export declare function layerKey(name: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* The form of a layer name that AutoCAD's AUDIT compares when it looks for
|
|
36
|
+
* duplicate names. AUDIT takes more names for the same than the layer table
|
|
37
|
+
* does, so two layers can have different `layerKey`s and still be an AUDIT
|
|
38
|
+
* error. Two names with the same key here are such a pair, unless they have
|
|
39
|
+
* the same `layerKey` too.
|
|
40
|
+
*
|
|
41
|
+
* The key follows the kinds of pair measured in AutoCAD 2025:
|
|
42
|
+
*
|
|
43
|
+
* - A few single letters, such as σ and ς.
|
|
44
|
+
* - A letter and the letters it stands for, such as ß and `ss`.
|
|
45
|
+
* - A composed character and its decomposed form, such as é and `e` with a
|
|
46
|
+
* combining acute accent, with its marks in any order Unicode counts as the
|
|
47
|
+
* same, such as a dot below before or after a circumflex.
|
|
48
|
+
* - A name with a soft hyphen inside it or in front of it and the same name
|
|
49
|
+
* without.
|
|
50
|
+
*
|
|
51
|
+
* It is not AUDIT's whole comparison. AUDIT also takes some compatibility
|
|
52
|
+
* characters for their plain forms, and cannot tell two characters apart when
|
|
53
|
+
* it knows neither.
|
|
54
|
+
*
|
|
55
|
+
* @param name - The layer name.
|
|
56
|
+
* @returns The key to find AUDIT's duplicate names by.
|
|
57
|
+
*/
|
|
58
|
+
export declare function layerAuditKey(name: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Orders layers for a DXF layer table. AutoCAD's AUDIT reports "Layer zero name
|
|
61
|
+
* found at incorrect index" when layer `0` is not the first record.
|
|
62
|
+
*
|
|
63
|
+
* @param layers - The drawing's layers, in any order.
|
|
64
|
+
* @returns The layers with `0` first and the others in their given order.
|
|
65
|
+
*/
|
|
66
|
+
export declare function layerZeroFirst(layers: readonly Layer[]): readonly Layer[];
|
package/dist/layers.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// AutoCAD discards a drawing whose layer table has a name with one of the
|
|
2
|
+
// characters below, as it does for the ASCII punctuation `rules.ts` lists. The
|
|
3
|
+
// exception is the private-use block, which it replaces with `\M+00000`. They
|
|
4
|
+
// are string literals, with every character written as an escape, so that
|
|
5
|
+
// `rules.ts` can also reject them in a layer name written as a literal.
|
|
6
|
+
/** C1 controls and invisible formatting characters, such as the zero-width space. */
|
|
7
|
+
export const REJECTED_FORMAT = "\
|
|
8
|
+
\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\
|
|
9
|
+
\u008f\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\
|
|
10
|
+
\u009e\u009f\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\
|
|
11
|
+
\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u2064\u206a\u206b\u206c\u206d\u206e\
|
|
12
|
+
\u206f\ufeff\ufff9\ufffa\ufffb";
|
|
13
|
+
/** Superscript and subscript letters, Roman numeral characters and letterlike symbols such as the ohm sign. */
|
|
14
|
+
export const REJECTED_LETTERLIKE = "\
|
|
15
|
+
\u2071\u207f\u2090\u2091\u2092\u2093\u2094\u2102\u2107\u210a\u210b\u210c\u210d\u210e\u210f\
|
|
16
|
+
\u2110\u2111\u2112\u2113\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\
|
|
17
|
+
\u212c\u212d\u212f\u2130\u2131\u2132\u2133\u2134\u2135\u2136\u2137\u2138\u2139\u213c\u213d\
|
|
18
|
+
\u213e\u213f\u2145\u2146\u2147\u2148\u2149\u214e\u2180\u2181\u2182\u2183\u2184\u2185\u2186\
|
|
19
|
+
\u2187\u2188";
|
|
20
|
+
/** Glagolitic letters and the Latin Extended-C block. */
|
|
21
|
+
export const REJECTED_GLAGOLITIC = "\
|
|
22
|
+
\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\
|
|
23
|
+
\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\
|
|
24
|
+
\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\
|
|
25
|
+
\u2c2d\u2c2e\u2c30\u2c31\u2c32\u2c33\u2c34\u2c35\u2c36\u2c37\u2c38\u2c39\u2c3a\u2c3b\u2c3c\
|
|
26
|
+
\u2c3d\u2c3e\u2c3f\u2c40\u2c41\u2c42\u2c43\u2c44\u2c45\u2c46\u2c47\u2c48\u2c49\u2c4a\u2c4b\
|
|
27
|
+
\u2c4c\u2c4d\u2c4e\u2c4f\u2c50\u2c51\u2c52\u2c53\u2c54\u2c55\u2c56\u2c57\u2c58\u2c59\u2c5a\
|
|
28
|
+
\u2c5b\u2c5c\u2c5d\u2c5e\u2c60\u2c61\u2c62\u2c63\u2c64\u2c65\u2c66\u2c67\u2c68\u2c69\u2c6a\
|
|
29
|
+
\u2c6b\u2c6c\u2c6d\u2c6e\u2c6f\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\
|
|
30
|
+
\u2c7b\u2c7c\u2c7d";
|
|
31
|
+
/** Coptic letters. */
|
|
32
|
+
export const REJECTED_COPTIC = "\
|
|
33
|
+
\u2c80\u2c81\u2c82\u2c83\u2c84\u2c85\u2c86\u2c87\u2c88\u2c89\u2c8a\u2c8b\u2c8c\u2c8d\u2c8e\
|
|
34
|
+
\u2c8f\u2c90\u2c91\u2c92\u2c93\u2c94\u2c95\u2c96\u2c97\u2c98\u2c99\u2c9a\u2c9b\u2c9c\u2c9d\
|
|
35
|
+
\u2c9e\u2c9f\u2ca0\u2ca1\u2ca2\u2ca3\u2ca4\u2ca5\u2ca6\u2ca7\u2ca8\u2ca9\u2caa\u2cab\u2cac\
|
|
36
|
+
\u2cad\u2cae\u2caf\u2cb0\u2cb1\u2cb2\u2cb3\u2cb4\u2cb5\u2cb6\u2cb7\u2cb8\u2cb9\u2cba\u2cbb\
|
|
37
|
+
\u2cbc\u2cbd\u2cbe\u2cbf\u2cc0\u2cc1\u2cc2\u2cc3\u2cc4\u2cc5\u2cc6\u2cc7\u2cc8\u2cc9\u2cca\
|
|
38
|
+
\u2ccb\u2ccc\u2ccd\u2cce\u2ccf\u2cd0\u2cd1\u2cd2\u2cd3\u2cd4\u2cd5\u2cd6\u2cd7\u2cd8\u2cd9\
|
|
39
|
+
\u2cda\u2cdb\u2cdc\u2cdd\u2cde\u2cdf\u2ce0\u2ce1\u2ce2\u2ce3\u2ce4";
|
|
40
|
+
/** Georgian Supplement, Tifinagh and Ethiopic Extended letters. */
|
|
41
|
+
export const REJECTED_GEORGIAN = "\
|
|
42
|
+
\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\
|
|
43
|
+
\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\
|
|
44
|
+
\u2d1e\u2d1f\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\
|
|
45
|
+
\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\
|
|
46
|
+
\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d54\
|
|
47
|
+
\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\
|
|
48
|
+
\u2d64\u2d65\u2d6f\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\
|
|
49
|
+
\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2da0\u2da1\u2da2\u2da3\
|
|
50
|
+
\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\
|
|
51
|
+
\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\
|
|
52
|
+
\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\
|
|
53
|
+
\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde";
|
|
54
|
+
/** The private-use characters U+E800 to U+E8FF. */
|
|
55
|
+
export const REJECTED_PRIVATE = "\
|
|
56
|
+
\ue800\ue801\ue802\ue803\ue804\ue805\ue806\ue807\ue808\ue809\ue80a\ue80b\ue80c\ue80d\ue80e\
|
|
57
|
+
\ue80f\ue810\ue811\ue812\ue813\ue814\ue815\ue816\ue817\ue818\ue819\ue81a\ue81b\ue81c\ue81d\
|
|
58
|
+
\ue81e\ue81f\ue820\ue821\ue822\ue823\ue824\ue825\ue826\ue827\ue828\ue829\ue82a\ue82b\ue82c\
|
|
59
|
+
\ue82d\ue82e\ue82f\ue830\ue831\ue832\ue833\ue834\ue835\ue836\ue837\ue838\ue839\ue83a\ue83b\
|
|
60
|
+
\ue83c\ue83d\ue83e\ue83f\ue840\ue841\ue842\ue843\ue844\ue845\ue846\ue847\ue848\ue849\ue84a\
|
|
61
|
+
\ue84b\ue84c\ue84d\ue84e\ue84f\ue850\ue851\ue852\ue853\ue854\ue855\ue856\ue857\ue858\ue859\
|
|
62
|
+
\ue85a\ue85b\ue85c\ue85d\ue85e\ue85f\ue860\ue861\ue862\ue863\ue864\ue865\ue866\ue867\ue868\
|
|
63
|
+
\ue869\ue86a\ue86b\ue86c\ue86d\ue86e\ue86f\ue870\ue871\ue872\ue873\ue874\ue875\ue876\ue877\
|
|
64
|
+
\ue878\ue879\ue87a\ue87b\ue87c\ue87d\ue87e\ue87f\ue880\ue881\ue882\ue883\ue884\ue885\ue886\
|
|
65
|
+
\ue887\ue888\ue889\ue88a\ue88b\ue88c\ue88d\ue88e\ue88f\ue890\ue891\ue892\ue893\ue894\ue895\
|
|
66
|
+
\ue896\ue897\ue898\ue899\ue89a\ue89b\ue89c\ue89d\ue89e\ue89f\ue8a0\ue8a1\ue8a2\ue8a3\ue8a4\
|
|
67
|
+
\ue8a5\ue8a6\ue8a7\ue8a8\ue8a9\ue8aa\ue8ab\ue8ac\ue8ad\ue8ae\ue8af\ue8b0\ue8b1\ue8b2\ue8b3\
|
|
68
|
+
\ue8b4\ue8b5\ue8b6\ue8b7\ue8b8\ue8b9\ue8ba\ue8bb\ue8bc\ue8bd\ue8be\ue8bf\ue8c0\ue8c1\ue8c2\
|
|
69
|
+
\ue8c3\ue8c4\ue8c5\ue8c6\ue8c7\ue8c8\ue8c9\ue8ca\ue8cb\ue8cc\ue8cd\ue8ce\ue8cf\ue8d0\ue8d1\
|
|
70
|
+
\ue8d2\ue8d3\ue8d4\ue8d5\ue8d6\ue8d7\ue8d8\ue8d9\ue8da\ue8db\ue8dc\ue8dd\ue8de\ue8df\ue8e0\
|
|
71
|
+
\ue8e1\ue8e2\ue8e3\ue8e4\ue8e5\ue8e6\ue8e7\ue8e8\ue8e9\ue8ea\ue8eb\ue8ec\ue8ed\ue8ee\ue8ef\
|
|
72
|
+
\ue8f0\ue8f1\ue8f2\ue8f3\ue8f4\ue8f5\ue8f6\ue8f7\ue8f8\ue8f9\ue8fa\ue8fb\ue8fc\ue8fd\ue8fe\
|
|
73
|
+
\ue8ff";
|
|
74
|
+
const REJECTED = new Set(REJECTED_FORMAT +
|
|
75
|
+
REJECTED_LETTERLIKE +
|
|
76
|
+
REJECTED_GLAGOLITIC +
|
|
77
|
+
REJECTED_COPTIC +
|
|
78
|
+
REJECTED_GEORGIAN +
|
|
79
|
+
REJECTED_PRIVATE);
|
|
80
|
+
/**
|
|
81
|
+
* Finds the first character of a layer name that AutoCAD rejects, beyond the
|
|
82
|
+
* ASCII punctuation and control characters that `layerNameProblem` checks.
|
|
83
|
+
*
|
|
84
|
+
* @param name - The layer name.
|
|
85
|
+
* @returns The character, or undefined when the name has none.
|
|
86
|
+
*/
|
|
87
|
+
export function rejectedNameCharacter(name) {
|
|
88
|
+
for (const character of name)
|
|
89
|
+
if (REJECTED.has(character))
|
|
90
|
+
return character;
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The capital letters AutoCAD lower-cases when it looks a layer name up, as
|
|
95
|
+
* hexadecimal code point ranges. `/2` marks a range that takes every second
|
|
96
|
+
* code point. AutoCAD's table is older and smaller than Unicode's: it leaves
|
|
97
|
+
* out Ÿ (U+0178), İ (U+0130) and ϴ (U+03F4), the title-case digraphs such as
|
|
98
|
+
* Dž (U+01C5), and most letters added since Unicode 5.1.
|
|
99
|
+
*/
|
|
100
|
+
const FOLDED_RANGES = "41-5a c0-d6 d8-de 100-12e/2 132-136/2 139-147/2 14a-176/2 179-17d/2 181-182 184-186/2 " +
|
|
101
|
+
"187-189/2 18a-18b 18e-191 193-194 196-198 19c-19d 19f-1a0 1a2-1a6/2 1a7-1a9/2 1ac-1ae/2 " +
|
|
102
|
+
"1af-1b1/2 1b2-1b3 1b5-1b7/2 1b8 1bc 1c4 1c7 1ca 1cd-1db/2 1de-1ee/2 1f1 1f4-1f6/2 " +
|
|
103
|
+
"1f7-1f8 1fa-232/2 23b-23d/2 241-243/2 244-246 248-24e/2 370-372/2 376 386-388/2 389-38a " +
|
|
104
|
+
"38c-38e/2 38f-391/2 392-3a1 3a3-3ab 3cf 3d8-3ee/2 3f7-3f9/2 3fa 3fd-42f 460-480/2 " +
|
|
105
|
+
"48a-4c0/2 4c1-4cd/2 4d0-522/2 531-556 1e00-1e94/2 1ea0-1efe/2 1f08-1f0f 1f18-1f1d " +
|
|
106
|
+
"1f28-1f2f 1f38-1f3f 1f48-1f4d 1f59-1f5f/2 1f68-1f6f 1f88-1f8f 1f98-1f9f 1fa8-1faf " +
|
|
107
|
+
"1fb8-1fbc 1fc8-1fcc 1fd8-1fdb 1fe8-1fec 1ff8-1ffc 2160-216f 24b6-24cf a640-a65e/2 " +
|
|
108
|
+
"a662-a66c/2 a680-a696/2 a722-a72e/2 a732-a76e/2 a779-a77d/2 a77e-a786/2 a78b ff21-ff3a";
|
|
109
|
+
const FOLDED = new Set(FOLDED_RANGES.split(" ").flatMap((range) => {
|
|
110
|
+
const [span = "", step = "1"] = range.split("/");
|
|
111
|
+
const [first = "", last = first] = span.split("-");
|
|
112
|
+
const codes = [];
|
|
113
|
+
for (let code = parseInt(first, 16); code <= parseInt(last, 16); code += Number(step)) {
|
|
114
|
+
codes.push(code);
|
|
115
|
+
}
|
|
116
|
+
return codes;
|
|
117
|
+
}));
|
|
118
|
+
/**
|
|
119
|
+
* Pairs of letters that AutoCAD keeps as two layers but its AUDIT reports as a
|
|
120
|
+
* duplicate name, written `from>to` in hexadecimal after `layerKey` has
|
|
121
|
+
* lower-cased the name. They include the two lower-case sigmas σ and ς.
|
|
122
|
+
*/
|
|
123
|
+
const AUDIT_SAME_PAIRS = "178>ff 1c6>1c5 1c9>1c8 1cc>1cb 1f3>1f2 3c3>3c2 3f5>3b5 527>526 1e9e>df 1fbe>3b9 " +
|
|
124
|
+
"2c7e>23f 2c7f>240 2cec>2ceb 2cee>2ced a791>a790 a7a1>a7a0 a7a3>a7a2 a7a5>a7a4 " +
|
|
125
|
+
"a7a7>a7a6 a7a9>a7a8";
|
|
126
|
+
const AUDIT_SAME = new Map(AUDIT_SAME_PAIRS.split(" ").map((pair) => {
|
|
127
|
+
const [from = "", to = ""] = pair.split(">");
|
|
128
|
+
return [String.fromCharCode(parseInt(from, 16)), String.fromCharCode(parseInt(to, 16))];
|
|
129
|
+
}));
|
|
130
|
+
/**
|
|
131
|
+
* Letters that AUDIT takes for the two or three letters they stand for, in
|
|
132
|
+
* either letter case: it reports `straße` and `STRASSE` as a duplicate name,
|
|
133
|
+
* and `Þórr` and `Thórr`. Each is listed in lower case, as `layerKey` leaves
|
|
134
|
+
* it, with a title-case digraph beside its lower-case form.
|
|
135
|
+
*/
|
|
136
|
+
const AUDIT_SPELLED_OUT = new Map(Object.entries({
|
|
137
|
+
ß: "ss",
|
|
138
|
+
ẞ: "ss",
|
|
139
|
+
æ: "ae",
|
|
140
|
+
ᴂ: "ae",
|
|
141
|
+
œ: "oe",
|
|
142
|
+
ij: "ij",
|
|
143
|
+
þ: "th",
|
|
144
|
+
dž: "dž",
|
|
145
|
+
Dž: "dž",
|
|
146
|
+
lj: "lj",
|
|
147
|
+
Lj: "lj",
|
|
148
|
+
nj: "nj",
|
|
149
|
+
Nj: "nj",
|
|
150
|
+
dz: "dz",
|
|
151
|
+
Dz: "dz",
|
|
152
|
+
ff: "ff",
|
|
153
|
+
fi: "fi",
|
|
154
|
+
fl: "fl",
|
|
155
|
+
ffi: "ffi",
|
|
156
|
+
ffl: "ffl",
|
|
157
|
+
st: "st",
|
|
158
|
+
ȸ: "db",
|
|
159
|
+
ȹ: "qp",
|
|
160
|
+
ʪ: "ls",
|
|
161
|
+
ʫ: "lz",
|
|
162
|
+
ꜳ: "aa",
|
|
163
|
+
ꜵ: "ao",
|
|
164
|
+
ꜷ: "au",
|
|
165
|
+
ꜹ: "av",
|
|
166
|
+
ꜽ: "ay",
|
|
167
|
+
ꝏ: "oo",
|
|
168
|
+
// The key is compared in decomposed form, so `ž` is spelled as it decomposes.
|
|
169
|
+
}).map(([letter, spelling]) => [letter, spelling.normalize("NFD")]));
|
|
170
|
+
/**
|
|
171
|
+
* Composed characters that AUDIT does not take for their decomposed forms,
|
|
172
|
+
* though Unicode calls the two equivalent: Hangul syllables, and some Arabic,
|
|
173
|
+
* Indic, Myanmar, kana, Hebrew and CJK compatibility characters. AUDIT takes
|
|
174
|
+
* every other composed character below U+10000 for its decomposed form.
|
|
175
|
+
*/
|
|
176
|
+
const KEPT_COMPOSED = /([\u0622-\u0624\u0626\u06c0\u06c2\u06d3\u09cb\u09cc\u09df\u0b48\u0b4b\u0b4c\u0b94\u0bca-\u0bcc\u0c48\u0cc0\u0cc7\u0cc8\u0cca\u0ccb\u0d4a-\u0d4c\u0dda\u0ddc-\u0dde\u1026\u309e\u30fe\ufa2e\ufa2f\ufa6b\ufa6d\ufb1f\ufb2c\ufb2d\uac00-\ud7a3])/;
|
|
177
|
+
const SOFT_HYPHEN = "\u00ad";
|
|
178
|
+
/**
|
|
179
|
+
* Soft hyphens that AUDIT ignores. It does not ignore one in front of a
|
|
180
|
+
* combining mark or a Hangul jamo: `E`, a soft hyphen and a combining acute
|
|
181
|
+
* accent are not É to it.
|
|
182
|
+
*/
|
|
183
|
+
const IGNORED_SOFT_HYPHENS = /\u00ad+(?![\u00ad\p{M}\u1100-\u11ff\ua960-\ua97f\ud7b0-\ud7ff])/gu;
|
|
184
|
+
/**
|
|
185
|
+
* The form of a layer name that AutoCAD looks layers up by. Two names with the
|
|
186
|
+
* same key are one layer to AutoCAD: an entity on `wall` goes on a layer
|
|
187
|
+
* declared as `WALL`, and AutoCAD keeps only the first of two such layers.
|
|
188
|
+
*
|
|
189
|
+
* AutoCAD lower-cases one character at a time from its own table, so this is
|
|
190
|
+
* not `toLowerCase()`: a final sigma ς, a dotless ı and ß stay as they are.
|
|
191
|
+
*
|
|
192
|
+
* @param name - The layer name.
|
|
193
|
+
* @returns The key to compare layer names by.
|
|
194
|
+
*/
|
|
195
|
+
export function layerKey(name) {
|
|
196
|
+
let key = "";
|
|
197
|
+
for (let i = 0; i < name.length; i++) {
|
|
198
|
+
const character = name.charAt(i);
|
|
199
|
+
key += FOLDED.has(name.charCodeAt(i)) ? character.toLowerCase() : character;
|
|
200
|
+
}
|
|
201
|
+
return key;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The form of a layer name that AutoCAD's AUDIT compares when it looks for
|
|
205
|
+
* duplicate names. AUDIT takes more names for the same than the layer table
|
|
206
|
+
* does, so two layers can have different `layerKey`s and still be an AUDIT
|
|
207
|
+
* error. Two names with the same key here are such a pair, unless they have
|
|
208
|
+
* the same `layerKey` too.
|
|
209
|
+
*
|
|
210
|
+
* The key follows the kinds of pair measured in AutoCAD 2025:
|
|
211
|
+
*
|
|
212
|
+
* - A few single letters, such as σ and ς.
|
|
213
|
+
* - A letter and the letters it stands for, such as ß and `ss`.
|
|
214
|
+
* - A composed character and its decomposed form, such as é and `e` with a
|
|
215
|
+
* combining acute accent, with its marks in any order Unicode counts as the
|
|
216
|
+
* same, such as a dot below before or after a circumflex.
|
|
217
|
+
* - A name with a soft hyphen inside it or in front of it and the same name
|
|
218
|
+
* without.
|
|
219
|
+
*
|
|
220
|
+
* It is not AUDIT's whole comparison. AUDIT also takes some compatibility
|
|
221
|
+
* characters for their plain forms, and cannot tell two characters apart when
|
|
222
|
+
* it knows neither.
|
|
223
|
+
*
|
|
224
|
+
* @param name - The layer name.
|
|
225
|
+
* @returns The key to find AUDIT's duplicate names by.
|
|
226
|
+
*/
|
|
227
|
+
export function layerAuditKey(name) {
|
|
228
|
+
let key = "";
|
|
229
|
+
for (const character of layerKey(decomposed(name.replace(IGNORED_SOFT_HYPHENS, "")))) {
|
|
230
|
+
key += AUDIT_SPELLED_OUT.get(character) ?? AUDIT_SAME.get(character) ?? character;
|
|
231
|
+
}
|
|
232
|
+
// AUDIT does not report `coop` and `coop` with a soft hyphen after it as duplicates, though it
|
|
233
|
+
// does report some other pairs that differ by a soft hyphen at the end. A mark for one at the
|
|
234
|
+
// end keeps a name that ends with a soft hyphen apart from every name that does not. Two names
|
|
235
|
+
// that both end with one are compared like any others, as the pairs in `auditPairs` in
|
|
236
|
+
// `scripts/cad-check.ts` check.
|
|
237
|
+
return name.endsWith(SOFT_HYPHEN) ? key + SOFT_HYPHEN : key;
|
|
238
|
+
}
|
|
239
|
+
/** Decomposes every composed character that AUDIT takes for its decomposed form. */
|
|
240
|
+
function decomposed(name) {
|
|
241
|
+
return name
|
|
242
|
+
.split(KEPT_COMPOSED)
|
|
243
|
+
.map((part, i) => (i % 2 === 1 ? part : part.normalize("NFD")))
|
|
244
|
+
.join("");
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Orders layers for a DXF layer table. AutoCAD's AUDIT reports "Layer zero name
|
|
248
|
+
* found at incorrect index" when layer `0` is not the first record.
|
|
249
|
+
*
|
|
250
|
+
* @param layers - The drawing's layers, in any order.
|
|
251
|
+
* @returns The layers with `0` first and the others in their given order.
|
|
252
|
+
*/
|
|
253
|
+
export function layerZeroFirst(layers) {
|
|
254
|
+
return [...layers.filter((l) => l.name === "0"), ...layers.filter((l) => l.name !== "0")];
|
|
255
|
+
}
|
package/dist/lint.d.ts
CHANGED
|
@@ -23,8 +23,17 @@ export interface LintOptions {
|
|
|
23
23
|
* exported as a different version than it was built for, and for warnings.
|
|
24
24
|
*/
|
|
25
25
|
export declare function lint(drawing: Drawing, options?: LintOptions): Diagnostic[];
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* An error that refuses a drawing because of what `lint` found in it. `toDXF`
|
|
28
|
+
* throws it when a drawing has lint errors for the version being written, and
|
|
29
|
+
* other code can throw it to refuse a drawing for its own purpose. Its message
|
|
30
|
+
* lists the first 10 problems and names no file format.
|
|
31
|
+
*/
|
|
27
32
|
export declare class DrawingError extends Error {
|
|
33
|
+
/** Every problem the drawing was refused for, including those the message leaves out. */
|
|
28
34
|
readonly diagnostics: readonly Diagnostic[];
|
|
35
|
+
/**
|
|
36
|
+
* @param diagnostics - The problems to refuse the drawing for, as `lint` returns them.
|
|
37
|
+
*/
|
|
29
38
|
constructor(diagnostics: readonly Diagnostic[]);
|
|
30
39
|
}
|
package/dist/lint.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { written } from "./format.js";
|
|
2
|
+
import { layerAuditKey, layerKey } from "./layers.js";
|
|
3
|
+
import { arcIsWrittenAsPoint, arcProblem, layerColorProblem, layerNameProblem, layerTwinProblem, magnitudeProblem, sizeProblem, textProblem, textWarning, } from "./rules.js";
|
|
2
4
|
/**
|
|
3
5
|
* Checks a drawing against the rules AutoCAD enforces when it opens DXF.
|
|
4
6
|
*
|
|
@@ -17,7 +19,9 @@ export function lint(drawing, options = {}) {
|
|
|
17
19
|
message: `Unknown units: ${JSON.stringify(drawing.units)}`,
|
|
18
20
|
});
|
|
19
21
|
}
|
|
22
|
+
// AutoCAD looks layers up by one form of their names, and AUDIT compares another.
|
|
20
23
|
const seen = new Set();
|
|
24
|
+
const audited = new Map();
|
|
21
25
|
for (const layer of drawing.layers) {
|
|
22
26
|
const error = (rule, message) => out.push({ rule, severity: "error", message, layer: layer.name });
|
|
23
27
|
const nameProblem = layerNameProblem(layer.name, version);
|
|
@@ -26,40 +30,68 @@ export function lint(drawing, options = {}) {
|
|
|
26
30
|
const colorProblem = layerColorProblem(layer.color);
|
|
27
31
|
if (colorProblem)
|
|
28
32
|
error("layer-color", `Layer ${JSON.stringify(layer.name)}: ${colorProblem}`);
|
|
29
|
-
const key = layer.name
|
|
33
|
+
const key = layerKey(layer.name);
|
|
34
|
+
const auditKey = layerAuditKey(layer.name);
|
|
35
|
+
const twinProblem = layerTwinProblem(layer.name, audited.get(auditKey) ?? layer.name);
|
|
30
36
|
if (seen.has(key)) {
|
|
31
37
|
error("duplicate-layer", `Layer ${JSON.stringify(layer.name)} is defined twice. CAD ignores case in layer names`);
|
|
32
38
|
}
|
|
39
|
+
else if (twinProblem) {
|
|
40
|
+
error("duplicate-layer", `Layer ${JSON.stringify(layer.name)}: ${twinProblem}`);
|
|
41
|
+
}
|
|
33
42
|
seen.add(key);
|
|
43
|
+
if (!audited.has(auditKey))
|
|
44
|
+
audited.set(auditKey, layer.name);
|
|
34
45
|
}
|
|
35
46
|
if (!seen.has("0")) {
|
|
36
47
|
out.push({ rule: "undefined-layer", severity: "error", message: 'Layer "0" is missing' });
|
|
37
48
|
}
|
|
38
49
|
drawing.entities.forEach((entity, index) => {
|
|
39
50
|
const add = (rule, severity, problem) => out.push({ rule, severity, message: `${entity.kind} #${index}: ${problem}`, entity: index });
|
|
40
|
-
if (!seen.has(entity.layer
|
|
41
|
-
|
|
51
|
+
if (!seen.has(layerKey(entity.layer))) {
|
|
52
|
+
// AutoCAD adds a layer for a name it does not know. When AUDIT takes that name for a
|
|
53
|
+
// declared layer's, it then reports the new layer as a duplicate of it.
|
|
54
|
+
const twin = audited.get(layerAuditKey(entity.layer));
|
|
55
|
+
const hint = twin
|
|
56
|
+
? `. AutoCAD does not take it for layer ${JSON.stringify(twin)}: it would add a layer, and its AUDIT would report a duplicate name`
|
|
57
|
+
: "";
|
|
58
|
+
add("undefined-layer", "error", `layer ${JSON.stringify(entity.layer)} is not defined${hint}`);
|
|
42
59
|
}
|
|
43
|
-
|
|
60
|
+
const problems = geometryProblems(entity);
|
|
61
|
+
for (const problem of problems)
|
|
44
62
|
add("geometry", "error", problem);
|
|
45
63
|
if (entity.kind === "text") {
|
|
46
64
|
const problem = textProblem(entity.value);
|
|
47
65
|
if (problem)
|
|
48
66
|
add("text", "error", problem);
|
|
67
|
+
const warning = textWarning(entity.value);
|
|
68
|
+
if (warning)
|
|
69
|
+
add("text", "warning", warning);
|
|
49
70
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
// A shape with a geometry error has no length worth a second message.
|
|
72
|
+
const collapsed = problems.length === 0 ? zeroLength(entity) : undefined;
|
|
73
|
+
if (collapsed)
|
|
74
|
+
add("zero-length", "warning", collapsed);
|
|
53
75
|
});
|
|
54
76
|
return out;
|
|
55
77
|
}
|
|
56
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* An error that refuses a drawing because of what `lint` found in it. `toDXF`
|
|
80
|
+
* throws it when a drawing has lint errors for the version being written, and
|
|
81
|
+
* other code can throw it to refuse a drawing for its own purpose. Its message
|
|
82
|
+
* lists the first 10 problems and names no file format.
|
|
83
|
+
*/
|
|
57
84
|
export class DrawingError extends Error {
|
|
85
|
+
/** Every problem the drawing was refused for, including those the message leaves out. */
|
|
58
86
|
diagnostics;
|
|
87
|
+
/**
|
|
88
|
+
* @param diagnostics - The problems to refuse the drawing for, as `lint` returns them.
|
|
89
|
+
*/
|
|
59
90
|
constructor(diagnostics) {
|
|
91
|
+
const count = diagnostics.length === 1 ? "1 problem" : `${diagnostics.length} problems`;
|
|
60
92
|
const shown = diagnostics.slice(0, 10).map((d) => `- ${d.message}`);
|
|
61
93
|
const more = diagnostics.length > 10 ? [`- and ${diagnostics.length - 10} more`] : [];
|
|
62
|
-
super([
|
|
94
|
+
super([`The drawing has ${count}:`, ...shown, ...more].join("\n"));
|
|
63
95
|
this.name = "DrawingError";
|
|
64
96
|
this.diagnostics = diagnostics;
|
|
65
97
|
}
|
|
@@ -67,12 +99,18 @@ export class DrawingError extends Error {
|
|
|
67
99
|
function geometryProblems(e) {
|
|
68
100
|
const out = [];
|
|
69
101
|
const finite = (label, ...values) => {
|
|
70
|
-
if (!values.every(Number.isFinite))
|
|
102
|
+
if (!values.every(Number.isFinite)) {
|
|
71
103
|
out.push(`${label} must be finite numbers`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const problem = values.map(magnitudeProblem).find((p) => p !== undefined);
|
|
107
|
+
if (problem)
|
|
108
|
+
out.push(`${label}: ${problem}`);
|
|
72
109
|
};
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
const size = (label, value) => {
|
|
111
|
+
const problem = Number.isFinite(value) ? sizeProblem(value) : "This value must be finite";
|
|
112
|
+
if (problem)
|
|
113
|
+
out.push(`${label}: ${problem}`);
|
|
76
114
|
};
|
|
77
115
|
switch (e.kind) {
|
|
78
116
|
case "line":
|
|
@@ -81,29 +119,76 @@ function geometryProblems(e) {
|
|
|
81
119
|
case "polyline":
|
|
82
120
|
if (e.points.length < 2)
|
|
83
121
|
out.push("a polyline needs at least 2 points");
|
|
84
|
-
|
|
122
|
+
// A loop, because a polyline can have more points than a call can take arguments.
|
|
123
|
+
if (!e.points.every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
|
|
124
|
+
out.push("its points must be finite numbers");
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
for (const [x, y] of e.points) {
|
|
128
|
+
const problem = magnitudeProblem(x) ?? magnitudeProblem(y);
|
|
129
|
+
if (!problem)
|
|
130
|
+
continue;
|
|
131
|
+
out.push(`its points: ${problem}`);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
85
135
|
break;
|
|
86
136
|
case "circle":
|
|
87
137
|
finite("its center", ...e.center);
|
|
88
|
-
|
|
138
|
+
size("radius", e.radius);
|
|
89
139
|
break;
|
|
90
140
|
case "arc":
|
|
91
141
|
finite("its center", ...e.center);
|
|
92
|
-
|
|
142
|
+
size("radius", e.radius);
|
|
93
143
|
if (![e.startAngle, e.endAngle].every((a) => Number.isFinite(a) && a >= 0 && a < 360)) {
|
|
94
144
|
out.push("its angles must be from 0 up to 360");
|
|
95
145
|
}
|
|
96
146
|
else if (e.startAngle === e.endAngle) {
|
|
97
147
|
out.push("its start and end angles must differ");
|
|
98
148
|
}
|
|
149
|
+
else {
|
|
150
|
+
const problem = arcProblem(e.startAngle, e.endAngle);
|
|
151
|
+
if (problem)
|
|
152
|
+
out.push(problem);
|
|
153
|
+
}
|
|
99
154
|
break;
|
|
100
155
|
case "text":
|
|
101
156
|
finite("its position and rotation", ...e.at, e.rotation);
|
|
102
|
-
|
|
157
|
+
size("height", e.height);
|
|
103
158
|
break;
|
|
104
159
|
}
|
|
105
160
|
return out;
|
|
106
161
|
}
|
|
162
|
+
/** Says how a valid entity ends up with no length in the file, or returns undefined when it has one. */
|
|
163
|
+
function zeroLength(e) {
|
|
164
|
+
const rounded = "once it is rounded to the 6 decimals tsdraft writes";
|
|
165
|
+
switch (e.kind) {
|
|
166
|
+
case "line":
|
|
167
|
+
if (same(e.from, e.to))
|
|
168
|
+
return "the line has no length";
|
|
169
|
+
return sameWritten(e.from, e.to) ? `the line has no length ${rounded}` : undefined;
|
|
170
|
+
case "polyline": {
|
|
171
|
+
const [first] = e.points;
|
|
172
|
+
if (!first)
|
|
173
|
+
return undefined;
|
|
174
|
+
if (e.points.every((p) => same(p, first)))
|
|
175
|
+
return "the polyline has no length";
|
|
176
|
+
return e.points.every((p) => sameWritten(p, first))
|
|
177
|
+
? `the polyline has no length ${rounded}`
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
case "arc":
|
|
181
|
+
return arcIsWrittenAsPoint(e.startAngle, e.endAngle)
|
|
182
|
+
? `the arc has no length ${rounded}`
|
|
183
|
+
: undefined;
|
|
184
|
+
case "circle":
|
|
185
|
+
case "text":
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
107
189
|
function same(a, b) {
|
|
108
190
|
return a[0] === b[0] && a[1] === b[1];
|
|
109
191
|
}
|
|
192
|
+
function sameWritten(a, b) {
|
|
193
|
+
return written(a[0]) === written(b[0]) && written(a[1]) === written(b[1]);
|
|
194
|
+
}
|