@knitting-tools/core 2.0.0 → 2.0.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/README.md CHANGED
@@ -9,12 +9,54 @@ A knitting pattern engine for expanding rows, applying shaping, and compiling pa
9
9
  - Apply physical shaping to stitch streams
10
10
  - Compile simple knitting DSL
11
11
 
12
+ ## Package boundaries
13
+
14
+ `@knitting-tools/core` is the pattern engine package.
15
+
16
+ - Keep here: parsing, shaping, row expansion, and compile pipeline.
17
+ - Use `@knitting-tools/math` for gauge and measurement utility functions.
18
+
12
19
  ## Install
13
20
 
14
21
  ```bash
15
22
  pnpm add @knitting-tools/core
16
23
  ```
17
24
 
25
+ ## Migration from @knitting-tools/math
26
+
27
+ The package was renamed in `2.0.0`:
28
+
29
+ - Old package: `@knitting-tools/math`
30
+ - New package: `@knitting-tools/core`
31
+
32
+ Update your project by changing both install and import paths.
33
+
34
+ Before:
35
+
36
+ ```ts
37
+ import { compilePattern } from "@knitting-tools/math";
38
+ ```
39
+
40
+ After:
41
+
42
+ ```ts
43
+ import { compilePattern } from "@knitting-tools/core";
44
+ ```
45
+
46
+ The runtime API surface is unchanged for this rename; migration is package-path only.
47
+
48
+ For split-package migration, prefer these utility imports from `@knitting-tools/math`:
49
+
50
+ - `rowsPerCm`
51
+ - `stitchesPerCm`
52
+ - `cmToRows`
53
+ - `cmToStitches`
54
+ - `convertLength`
55
+ - `normaliseMeasurement`
56
+ - `roundStitches`
57
+
58
+ These remain re-exported from `@knitting-tools/core` for compatibility.
59
+
18
60
  ## Quick example
19
61
 
20
62
  ```ts
package/dist/index.d.ts CHANGED
@@ -1,78 +1,13 @@
1
+ import { Gauge } from '@knitting-tools/math';
2
+ export { Gauge, ImperialGauge, MetricGauge, UnitSystem, cmToRows, cmToStitches, convertLength, normaliseMeasurement, roundStitches, rowsPerCm, stitchesPerCm } from '@knitting-tools/math';
3
+
1
4
  declare const INCH_TO_CM: number;
2
5
  declare const CM_TO_INCH: number;
3
6
  declare const MM_TO_CM: number;
4
7
  declare const CM_TO_MM: number;
5
8
 
6
- type UnitSystem = "imperial" | "metric";
7
- interface MetricGauge {
8
- rowsPer10cm: number;
9
- stitchesPer10cm: number;
10
- unit: "metric";
11
- }
12
- interface ImperialGauge {
13
- rowsPer4in: number;
14
- stitchesPer4in: number;
15
- unit: "imperial";
16
- }
17
- type Gauge = MetricGauge | ImperialGauge;
18
- /**
19
- * Get the number of stitches per centimeter based on the gauge.
20
- *
21
- * @param gauge - The gauge information.
22
- * @returns The number of stitches per centimeter.
23
- */
24
- declare function stitchesPerCm(gauge: Gauge): number;
25
- /**
26
- * Get the number of rows per centimeter based on the gauge.
27
- *
28
- * @param gauge - The gauge information.
29
- * @returns The number of rows per centimeter.
30
- */
31
- declare function rowsPerCm(gauge: Gauge): number;
32
-
33
- /**
34
- * Convert a centimetre measurement into a rounded stitch count for the given gauge.
35
- *
36
- * @param cm - The measurement in centimetres.
37
- * @param gauge - The gauge definition used for conversion.
38
- * @returns The nearest whole-number stitch count.
39
- */
40
- declare function cmToStitches(cm: number, gauge: Gauge): number;
41
- /**
42
- * Convert a centimetre measurement into a rounded row count for the given gauge.
43
- *
44
- * @param cm - The measurement in centimetres.
45
- * @param gauge - The gauge definition used for conversion.
46
- * @returns The nearest whole-number row count.
47
- */
48
- declare function cmToRows(cm: number, gauge: Gauge): number;
49
-
50
- /**
51
- * Convert a length value between centimeters and inches.
52
- *
53
- * @param value - The length value to convert.
54
- * @param from - The unit of the input value ("cm" or "in").
55
- * @param to - The unit to convert to ("cm" or "in").
56
- * @returns The converted length value.
57
- */
58
- declare function convertLength(value: number, from: "cm" | "in", to: "cm" | "in"): number;
59
- /**
60
- * Normalise a length measurement to centimeters.
61
- *
62
- * @param value - The length value to normalise.
63
- * @param unit - The unit of the input value ("cm" or "in").
64
- * @returns The normalised length value in centimeters.
65
- */
66
- declare function normaliseMeasurement(value: number, unit: "cm" | "in"): number;
67
- /**
68
- * Round a number of stitches to the nearest whole number.
69
- *
70
- * @param stitches - The number of stitches to round.
71
- * @returns The rounded number of stitches.
72
- */
73
- declare function roundStitches(stitches: number): number;
74
-
75
9
  type StitchType = "knit" | "purl" | "decrease" | "increase";
10
+ type OperationType = "decrease" | "increase";
76
11
  type StitchVariant = "inv-l" | "inv-r" | "k1-b" | "k2tog" | "kfb" | "kfbf" | "m1" | "p1-b" | "p2tog" | "pfb" | "psso" | "s2kp" | "sk2p" | "skp" | "sl" | "sl1" | "slip" | "ssp" | "ssk" | "tbl" | "yfon" | "yfrn" | "yo" | "yon" | "yrn";
77
12
  type StitchVariantParameterValue = boolean | number | string;
78
13
  type StitchVariantParameters = Record<string, StitchVariantParameterValue>;
@@ -94,6 +29,7 @@ type EdgeElement = ShapingElement | StitchInstruction;
94
29
  type RowElement = ShapingElement | StitchInstruction | RepeatBlock;
95
30
  interface StitchInstruction {
96
31
  count: number;
32
+ operation?: OperationType;
97
33
  type: StitchType;
98
34
  variant?: StitchVariant;
99
35
  variantParameters?: StitchVariantParameters;
@@ -338,6 +274,65 @@ declare function createDefaultStitchRegistry(): StitchRegistry;
338
274
  */
339
275
  declare function createEmptyStitchRegistry(): StitchRegistry;
340
276
 
277
+ type ShapingType = "decrease" | "increase";
278
+
279
+ interface ShapeRowInput {
280
+ alignment?: "left" | "center" | "right";
281
+ row: Row;
282
+ shaping: {
283
+ type: ShapingType;
284
+ count: number;
285
+ };
286
+ totalStitches: number;
287
+ }
288
+ interface ShapedStitch {
289
+ isShaping?: boolean;
290
+ operationIndex?: number;
291
+ shapingType?: ShapingType;
292
+ sourceIndex: number;
293
+ type: StitchType;
294
+ }
295
+ interface ShapeRowResult {
296
+ shapedStitches: ShapedStitch[];
297
+ stitches: StitchInstruction[];
298
+ totalStitches: number;
299
+ }
300
+ interface ShapingOperationGroup {
301
+ operationIndex: number;
302
+ outputIndices: number[];
303
+ shapingType: ShapingType;
304
+ sourceIndices: number[];
305
+ stitches: ShapedStitch[];
306
+ }
307
+ interface CompileRowInput {
308
+ alignment?: "left" | "center" | "right";
309
+ row: Row;
310
+ shaping?: {
311
+ count: number;
312
+ type: ShapingType;
313
+ };
314
+ totalStitches: number;
315
+ }
316
+ interface CompileRowResult {
317
+ shapedStitches?: ShapedStitch[];
318
+ stitches: StitchInstruction[];
319
+ totalStitches: number;
320
+ }
321
+
322
+ /**
323
+ * Options object for the shaping-aware overload of compileRowDsl /
324
+ * compileCompatibleRowDsl. Pass this instead of a plain registry when you
325
+ * want DSL parsing, expansion, and shaping in a single call.
326
+ */
327
+ interface CompileRowDslOptions {
328
+ alignment?: "left" | "center" | "right";
329
+ registry?: StitchRegistry;
330
+ shaping?: {
331
+ count: number;
332
+ type: ShapingType;
333
+ };
334
+ }
335
+
341
336
  interface RowDslAstEdgeStartElement {
342
337
  stitches: EdgeElement[];
343
338
  type: "edge-start";
@@ -366,42 +361,14 @@ declare function isRowDslAstWellFormed(ast: RowDslAst): boolean;
366
361
  declare function parseRowDsl(input: string, registry?: StitchRegistry): Row;
367
362
  declare function parseCompatibleRowDsl(input: string, registry?: StitchRegistry): Row;
368
363
  declare function compileRowDsl(input: string, totalStitches: number, registry?: StitchRegistry): StitchInstruction[];
364
+ declare function compileRowDsl(input: string, totalStitches: number, options: CompileRowDslOptions): CompileRowResult;
369
365
  declare function compileCompatibleRowDsl(input: string, totalStitches: number, registry?: StitchRegistry): StitchInstruction[];
366
+ declare function compileCompatibleRowDsl(input: string, totalStitches: number, options: CompileRowDslOptions): CompileRowResult;
370
367
  declare function rowToRowDsl(row: Row): string;
371
368
  declare function parseRowDslAst(input: string, registry?: StitchRegistry): RowDslAst;
372
369
  declare function parseCompatibleRowDslAst(input: string, registry?: StitchRegistry): RowDslAst;
373
370
  declare function resolveRowDslAst(ast: RowDslAst): Row;
374
371
 
375
- type ShapingType = "decrease" | "increase";
376
-
377
- interface ShapeRowInput {
378
- row: Row;
379
- shaping: {
380
- type: ShapingType;
381
- count: number;
382
- };
383
- totalStitches: number;
384
- }
385
- interface ShapedStitch {
386
- isShaping?: boolean;
387
- operationIndex?: number;
388
- shapingType?: ShapingType;
389
- sourceIndex: number;
390
- type: StitchType;
391
- }
392
- interface ShapeRowResult {
393
- shapedStitches: ShapedStitch[];
394
- stitches: StitchInstruction[];
395
- totalStitches: number;
396
- }
397
- interface ShapingOperationGroup {
398
- operationIndex: number;
399
- outputIndices: number[];
400
- shapingType: ShapingType;
401
- sourceIndices: number[];
402
- stitches: ShapedStitch[];
403
- }
404
-
405
372
  type ShapingDslParseResult = ShapeRowInput;
406
373
  type ShapingDslElement = {
407
374
  row: Row;
@@ -474,7 +441,8 @@ declare function printShapingDebug(shapedStitches: ShapedStitch[], label?: strin
474
441
  declare function parseShapingDslAst(input: string): ShapingDslAst;
475
442
  declare function parseShapingDsl(input: string): ShapingDslParseResult;
476
443
  declare function compileShapingDsl(input: string): ShapeRowResult;
444
+ declare function compileRow(input: CompileRowInput): CompileRowResult;
477
445
  declare function shapeRow(input: ShapeRowInput): ShapeRowResult;
478
446
  declare function groupShapedStitchesByOperation(shapedStitches: ShapedStitch[]): ShapingOperationGroup[];
479
447
 
480
- export { CM_TO_INCH, CM_TO_MM, type CastOnInput, type CompilePatternOptions, type CompiledPattern, type CompiledPatternRow, type EdgeElement, type Gauge, INCH_TO_CM, type ImperialGauge, KnittingMathError, type KnittingMathErrorCode, type KnittingMathErrorContext, KnittingMathExpansionError, KnittingMathParseError, KnittingMathValidationError, MM_TO_CM, type MetricGauge, type Pattern, type PatternCompileMode, type RepeatBlock, type ResolveOptions, type Row, type RowDslAst, type RowDslAstElement, type RowElement, type RowShapingOperation, type ShapeRowInput, type ShapeRowResult, type ShapedRowResult, type ShapedStitch, type ShapingDebugLine, type ShapingDebugOptions, type ShapingDslAst, type ShapingDslElement, type ShapingDslParseResult, type ShapingElement, type ShapingOperationGroup, type StitchInstruction, type StitchRegistry, type StitchType, type StitchVariant, type StitchVariantParameterValue, type StitchVariantParameters, type UnitSystem, calculateCastOn, calculateCastOnStitches, cmToRows, cmToStitches, compileCompatibleRowDsl, compilePattern, compileRowDsl, compileShapingDsl, convertLength, countStitchesInInstructions, createDefaultStitchRegistry, createEmptyStitchRegistry, createRepeatRow, ensureKnittingMathError, expandPattern, expandRow, expandRowToStitchTypes, expandRowToStitches, groupShapedStitchesByOperation, isRowDslAstWellFormed, normaliseMeasurement, parseCompatibleRowDsl, parseCompatibleRowDslAst, parseRowDsl, parseRowDslAst, parseShapingDsl, parseShapingDslAst, printPattern, printShapingDebug, renderShapingDebug, renderShapingDebugForInput, resolveRowDslAst, roundStitches, rowToRowDsl, rowsPerCm, shapeRow, shapedExpandPattern, stitchesPerCm };
448
+ export { CM_TO_INCH, CM_TO_MM, type CastOnInput, type CompilePatternOptions, type CompileRowDslOptions, type CompileRowInput, type CompileRowResult, type CompiledPattern, type CompiledPatternRow, type EdgeElement, INCH_TO_CM, KnittingMathError, type KnittingMathErrorCode, type KnittingMathErrorContext, KnittingMathExpansionError, KnittingMathParseError, KnittingMathValidationError, MM_TO_CM, type Pattern, type PatternCompileMode, type RepeatBlock, type ResolveOptions, type Row, type RowDslAst, type RowDslAstElement, type RowElement, type RowShapingOperation, type ShapeRowInput, type ShapeRowResult, type ShapedRowResult, type ShapedStitch, type ShapingDebugLine, type ShapingDebugOptions, type ShapingDslAst, type ShapingDslElement, type ShapingDslParseResult, type ShapingElement, type ShapingOperationGroup, type StitchInstruction, type StitchRegistry, type StitchType, type StitchVariant, type StitchVariantParameterValue, type StitchVariantParameters, calculateCastOn, calculateCastOnStitches, compileCompatibleRowDsl, compilePattern, compileRow, compileRowDsl, compileShapingDsl, countStitchesInInstructions, createDefaultStitchRegistry, createEmptyStitchRegistry, createRepeatRow, ensureKnittingMathError, expandPattern, expandRow, expandRowToStitchTypes, expandRowToStitches, groupShapedStitchesByOperation, isRowDslAstWellFormed, parseCompatibleRowDsl, parseCompatibleRowDslAst, parseRowDsl, parseRowDslAst, parseShapingDsl, parseShapingDslAst, printPattern, printShapingDebug, renderShapingDebug, renderShapingDebugForInput, resolveRowDslAst, rowToRowDsl, shapeRow, shapedExpandPattern };
package/dist/index.js CHANGED
@@ -4,58 +4,26 @@ var CM_TO_INCH = 1 / INCH_TO_CM;
4
4
  var MM_TO_CM = 0.1;
5
5
  var CM_TO_MM = 10;
6
6
 
7
- // src/gauge/gauge.ts
8
- var unitToCm = (value, unit) => {
9
- if (unit === "metric") return value / 10;
10
- else return value / 4 / INCH_TO_CM;
11
- };
12
- function stitchesPerCm(gauge) {
13
- if (gauge.unit === "metric") {
14
- return unitToCm(gauge.stitchesPer10cm, "metric");
15
- }
16
- return unitToCm(gauge.stitchesPer4in, "imperial");
17
- }
18
- function rowsPerCm(gauge) {
19
- if (gauge.unit === "metric") {
20
- return unitToCm(gauge.rowsPer10cm, "metric");
21
- }
22
- return unitToCm(gauge.rowsPer4in, "imperial");
23
- }
24
-
25
- // src/gauge/conversion.ts
26
- function cmToStitches(cm, gauge) {
27
- return Math.round(stitchesPerCm(gauge) * cm);
28
- }
29
- function cmToRows(cm, gauge) {
30
- return Math.round(rowsPerCm(gauge) * cm);
31
- }
32
-
33
- // src/measurement/measurement.ts
34
- function convertLength(value, from, to) {
35
- if (from === to) {
36
- return value;
37
- }
38
- if (from === "cm" && to === "in") {
39
- return value * CM_TO_INCH;
40
- }
41
- if (from === "in" && to === "cm") {
42
- return value * INCH_TO_CM;
43
- }
44
- throw new Error(`Unsupported conversion from ${from} \u2192 ${to}`);
45
- }
46
- function normaliseMeasurement(value, unit) {
47
- return convertLength(value, unit, "cm");
48
- }
49
- function roundStitches(stitches) {
50
- if (!Number.isFinite(stitches)) {
51
- throw new Error("Stitch count must be a finite number.");
52
- }
53
- return Math.round(stitches);
54
- }
7
+ // src/public/index.ts
8
+ import {
9
+ CM_TO_INCH as CM_TO_INCH2,
10
+ CM_TO_MM as CM_TO_MM2,
11
+ INCH_TO_CM as INCH_TO_CM2,
12
+ MM_TO_CM as MM_TO_CM2,
13
+ cmToRows,
14
+ cmToStitches,
15
+ convertLength,
16
+ normaliseMeasurement as normaliseMeasurement2,
17
+ roundStitches as roundStitches2,
18
+ rowsPerCm,
19
+ stitchesPerCm as stitchesPerCm2
20
+ } from "@knitting-tools/math";
55
21
 
56
22
  // src/pattern/row.ts
57
23
  var InstructionBuffer = class {
58
- items = [];
24
+ constructor() {
25
+ this.items = [];
26
+ }
59
27
  append(instruction, count = instruction.count) {
60
28
  this.items[this.items.length] = cloneInstruction(instruction, count);
61
29
  }
@@ -71,6 +39,15 @@ var SHAPING_PRODUCE = {
71
39
  decrease: 1,
72
40
  increase: 2
73
41
  };
42
+ function getInstructionOperation(instruction) {
43
+ if (instruction.operation !== void 0) {
44
+ return instruction.operation;
45
+ }
46
+ if (instruction.type === "decrease" || instruction.type === "increase") {
47
+ return instruction.type;
48
+ }
49
+ return void 0;
50
+ }
74
51
  function isRecord(value) {
75
52
  return typeof value === "object" && value !== null && !Array.isArray(value);
76
53
  }
@@ -87,10 +64,10 @@ function isStitchInstruction(element) {
87
64
  return !isRepeatBlock(element) && !isShapingElement(element);
88
65
  }
89
66
  function countEdgeSourceStitches(stitches) {
90
- return (stitches == null ? void 0 : stitches.reduce(
67
+ return stitches?.reduce(
91
68
  (sum, stitch) => sum + (isEdgeShapingElement(stitch) ? stitch.count * SHAPING_CONSUME[stitch.shapingType] : stitch.count),
92
69
  0
93
- )) ?? 0;
70
+ ) ?? 0;
94
71
  }
95
72
  function countSequenceStitches(elements) {
96
73
  let total = 0;
@@ -119,12 +96,21 @@ function cloneVariantParameters(variantParameters) {
119
96
  return variantParameters ? { ...variantParameters } : void 0;
120
97
  }
121
98
  function cloneInstruction(instruction, count = instruction.count) {
122
- return {
99
+ const clone = {
123
100
  count,
124
- type: instruction.type,
125
- variant: instruction.variant,
126
- variantParameters: cloneVariantParameters(instruction.variantParameters)
101
+ type: instruction.type
127
102
  };
103
+ if (instruction.operation !== void 0) {
104
+ clone.operation = instruction.operation;
105
+ }
106
+ if (instruction.variant !== void 0) {
107
+ clone.variant = instruction.variant;
108
+ }
109
+ const clonedVariantParameters = cloneVariantParameters(instruction.variantParameters);
110
+ if (clonedVariantParameters !== void 0) {
111
+ clone.variantParameters = clonedVariantParameters;
112
+ }
113
+ return clone;
128
114
  }
129
115
  function inferDecreaseVariant(element) {
130
116
  const isPurl = element.stitch === "purl";
@@ -155,12 +141,18 @@ function inferShapingVariant(element) {
155
141
  }
156
142
  function createInstructionFromShaping(element, operationCount) {
157
143
  const inferredVariant = element.variant ?? inferShapingVariant(element);
158
- return {
144
+ const instruction = {
159
145
  count: operationCount * SHAPING_PRODUCE[element.shapingType],
160
- type: element.shapingType,
161
- variant: inferredVariant,
162
- variantParameters: cloneVariantParameters(element.variantParameters)
146
+ type: element.shapingType
163
147
  };
148
+ if (inferredVariant !== void 0) {
149
+ instruction.variant = inferredVariant;
150
+ }
151
+ const clonedVariantParameters = cloneVariantParameters(element.variantParameters);
152
+ if (clonedVariantParameters !== void 0) {
153
+ instruction.variantParameters = clonedVariantParameters;
154
+ }
155
+ return instruction;
164
156
  }
165
157
  function areVariantParametersEqual(left, right) {
166
158
  if (left === right) {
@@ -182,7 +174,7 @@ function areVariantParametersEqual(left, right) {
182
174
  return true;
183
175
  }
184
176
  function canMergeInstructions(left, right) {
185
- return left.type === right.type && left.variant === right.variant && areVariantParametersEqual(left.variantParameters, right.variantParameters);
177
+ return left.operation === right.operation && left.type === right.type && left.variant === right.variant && areVariantParametersEqual(left.variantParameters, right.variantParameters);
186
178
  }
187
179
  function assertPositiveInteger(value, label) {
188
180
  if (!Number.isInteger(value) || value <= 0) {
@@ -214,6 +206,9 @@ function validateStitchInstruction(instruction, path) {
214
206
  if (!isRecord(instruction)) {
215
207
  throw new Error(`${path} must be an object.`);
216
208
  }
209
+ if (instruction.operation !== void 0 && instruction.operation !== "decrease" && instruction.operation !== "increase") {
210
+ throw new Error(`${path}.operation must be 'decrease' or 'increase' when provided.`);
211
+ }
217
212
  assertPositiveInteger(instruction.count, `${path}.count`);
218
213
  validateVariantParameters(instruction.variantParameters, `${path}.variantParameters`);
219
214
  }
@@ -861,7 +856,6 @@ function collectRowShaping(row) {
861
856
  return ops;
862
857
  }
863
858
  function compilePattern(pattern, initialStitches, options = {}) {
864
- var _a;
865
859
  const mode = options.mode ?? "shaped";
866
860
  const rows = [];
867
861
  let currentStitches = initialStitches;
@@ -893,7 +887,7 @@ function compilePattern(pattern, initialStitches, options = {}) {
893
887
  }
894
888
  }
895
889
  return {
896
- finalStitches: ((_a = rows.at(-1)) == null ? void 0 : _a.carriedStitches) ?? initialStitches,
890
+ finalStitches: rows.at(-1)?.carriedStitches ?? initialStitches,
897
891
  initialStitches,
898
892
  mode,
899
893
  rows
@@ -917,10 +911,11 @@ function printPattern(pattern, stitchesPerRow) {
917
911
  const expanded = expandPattern(pattern, stitchesPerRow);
918
912
  expanded.forEach((row, rowIndex) => {
919
913
  const rowStr = row.map((stitch) => {
920
- const symbol = stitchSymbols[stitch.type] ?? "?";
914
+ const operation = getInstructionOperation(stitch);
915
+ const symbol = operation ? stitchSymbols[operation] : stitchSymbols[stitch.type] ?? "?";
921
916
  return symbol.repeat(stitch.count);
922
917
  }).join(" ");
923
- patternDebugLogger == null ? void 0 : patternDebugLogger.log(`Row ${rowIndex + 1}: ${rowStr}`);
918
+ patternDebugLogger?.log(`Row ${rowIndex + 1}: ${rowStr}`);
924
919
  });
925
920
  }
926
921
  function createRepeatRow(sequence, times) {
@@ -937,8 +932,6 @@ function createRepeatRow(sequence, times) {
937
932
 
938
933
  // src/public/errors.ts
939
934
  var KnittingMathError = class extends Error {
940
- code;
941
- context;
942
935
  constructor(name, code, message, context, cause) {
943
936
  super(message);
944
937
  this.name = name;
@@ -1019,6 +1012,11 @@ function printPattern2(pattern, stitchesPerRow) {
1019
1012
  }
1020
1013
 
1021
1014
  // src/stitches/castOn.ts
1015
+ import {
1016
+ normaliseMeasurement,
1017
+ roundStitches,
1018
+ stitchesPerCm
1019
+ } from "@knitting-tools/math";
1022
1020
  function calculateCastOn(input) {
1023
1021
  return calculateCastOnStitches({ ...input, ease: 0 });
1024
1022
  }
@@ -1034,6 +1032,163 @@ function calculateCastOnStitches(input) {
1034
1032
  return roundStitches(stitches);
1035
1033
  }
1036
1034
 
1035
+ // src/shaping/distribution.ts
1036
+ function distributeEvenly(totalStitches, operations) {
1037
+ if (operations <= 0) {
1038
+ return { spacing: [] };
1039
+ }
1040
+ const baseSpacing = Math.floor(totalStitches / operations);
1041
+ const remainder = totalStitches % operations;
1042
+ const spacing = [];
1043
+ for (let i = 0; i < operations; i++) {
1044
+ const shouldAddExtra = Math.floor(i * remainder / operations) !== Math.floor((i + 1) * remainder / operations);
1045
+ spacing.push(baseSpacing + (shouldAddExtra ? 1 : 0));
1046
+ }
1047
+ return { spacing };
1048
+ }
1049
+
1050
+ // src/shaping/shapingRules.ts
1051
+ var SHAPING_RULES = {
1052
+ decrease: { consume: 2, produce: 1 },
1053
+ // k2tog - A decrease consumes 2 stitches and produces 1.
1054
+ increase: { consume: 1, produce: 2 }
1055
+ // M1 - An increase consumes 1 stitch and produces 2 stitches.
1056
+ };
1057
+
1058
+ // src/shaping/shaping.ts
1059
+ function getShapingPositions(length, count, alignment = "center") {
1060
+ if (count <= 0 || length <= 0) {
1061
+ return [];
1062
+ }
1063
+ const { spacing } = distributeEvenly(length, count);
1064
+ const positions = [];
1065
+ let current = 0;
1066
+ for (const gap of spacing) {
1067
+ let position;
1068
+ switch (alignment) {
1069
+ case "left":
1070
+ position = current;
1071
+ break;
1072
+ case "center":
1073
+ position = current + Math.floor(gap / 2);
1074
+ break;
1075
+ case "right":
1076
+ position = current + gap - 1;
1077
+ break;
1078
+ }
1079
+ positions.push(position);
1080
+ current += gap;
1081
+ }
1082
+ return positions.filter((position) => position >= 0 && position < length);
1083
+ }
1084
+ function applyPhysicalShaping(stitches, shapingType, positions) {
1085
+ const rule = SHAPING_RULES[shapingType];
1086
+ const result = [];
1087
+ let i = 0;
1088
+ let positionIndex = 0;
1089
+ while (i < stitches.length) {
1090
+ if (positionIndex < positions.length && i === positions[positionIndex]) {
1091
+ if (i + rule.consume > stitches.length) {
1092
+ throw new Error("Shaping operation exceeds available stitches at the selected position.");
1093
+ }
1094
+ for (let j = 0; j < rule.produce; j++) {
1095
+ result.push({
1096
+ isShaping: true,
1097
+ operationIndex: positionIndex,
1098
+ shapingType,
1099
+ sourceIndex: i,
1100
+ type: shapingType
1101
+ });
1102
+ }
1103
+ i += rule.consume;
1104
+ positionIndex++;
1105
+ } else {
1106
+ result.push(stitches[i]);
1107
+ i++;
1108
+ }
1109
+ }
1110
+ return result;
1111
+ }
1112
+ function compressStitches(stitches) {
1113
+ const result = [];
1114
+ for (const stitch of stitches) {
1115
+ const last = result.at(-1);
1116
+ if (last?.type === stitch.type) {
1117
+ last.count++;
1118
+ } else {
1119
+ result.push({ type: stitch.type, count: 1 });
1120
+ }
1121
+ }
1122
+ return result;
1123
+ }
1124
+ function shapeRow(input) {
1125
+ const { row, totalStitches, shaping } = input;
1126
+ if (shaping.count < 0) {
1127
+ throw new Error("Shaping count must be 0 or greater.");
1128
+ }
1129
+ const expanded = expandRowToStitchTypes(row, totalStitches).map((type, sourceIndex) => ({
1130
+ sourceIndex,
1131
+ type
1132
+ }));
1133
+ const rule = SHAPING_RULES[shaping.type];
1134
+ if (shaping.count * rule.consume > expanded.length) {
1135
+ throw new Error("Cannot apply more shaping operations than stitches in the row.");
1136
+ }
1137
+ const shapingPositions = getShapingPositions(
1138
+ expanded.length,
1139
+ shaping.count,
1140
+ input.alignment ?? "center"
1141
+ );
1142
+ const shapedStitches = applyPhysicalShaping(expanded, shaping.type, shapingPositions);
1143
+ const stitches = compressStitches(shapedStitches);
1144
+ const resultingStitches = totalStitches + shaping.count * (rule.produce - rule.consume);
1145
+ return { shapedStitches, stitches, totalStitches: resultingStitches };
1146
+ }
1147
+ function groupShapedStitchesByOperation(shapedStitches) {
1148
+ const groups = /* @__PURE__ */ new Map();
1149
+ for (let outputIndex = 0; outputIndex < shapedStitches.length; outputIndex += 1) {
1150
+ const stitch = shapedStitches[outputIndex];
1151
+ if (!stitch.isShaping || stitch.operationIndex === void 0 || stitch.shapingType === void 0) {
1152
+ continue;
1153
+ }
1154
+ const existing = groups.get(stitch.operationIndex);
1155
+ if (existing) {
1156
+ if (existing.shapingType !== stitch.shapingType) {
1157
+ throw new Error("Shaping operation contains mixed shaping types.");
1158
+ }
1159
+ existing.stitches.push(stitch);
1160
+ existing.outputIndices.push(outputIndex);
1161
+ existing.sourceIndices.push(stitch.sourceIndex);
1162
+ continue;
1163
+ }
1164
+ groups.set(stitch.operationIndex, {
1165
+ operationIndex: stitch.operationIndex,
1166
+ outputIndices: [outputIndex],
1167
+ shapingType: stitch.shapingType,
1168
+ sourceIndices: [stitch.sourceIndex],
1169
+ stitches: [stitch]
1170
+ });
1171
+ }
1172
+ return Array.from(groups.values());
1173
+ }
1174
+ function compileRow(input) {
1175
+ const { row, totalStitches, shaping, alignment } = input;
1176
+ if (!shaping || shaping.count === 0) {
1177
+ return { stitches: expandRow(row, totalStitches), totalStitches };
1178
+ }
1179
+ const {
1180
+ shapedStitches,
1181
+ stitches,
1182
+ totalStitches: resultingStitches
1183
+ } = shapeRow({
1184
+ alignment,
1185
+ row,
1186
+ shaping,
1187
+ totalStitches
1188
+ });
1189
+ return { shapedStitches, stitches, totalStitches: resultingStitches };
1190
+ }
1191
+
1037
1192
  // src/parser/compatibilityNormaliser.ts
1038
1193
  function isWordBoundaryCharacter(char) {
1039
1194
  return char === void 0 || /[^a-z]/i.test(char);
@@ -1346,10 +1501,12 @@ var ERR_DEFAULT_COUNT_INVALID = "Default count must be a positive integer.";
1346
1501
  var ERR_RESOLVE_OPTIONS_INVALID = "Resolve options must be an object when provided.";
1347
1502
  var ERR_RESOLVE_THROW_ON_UNKNOWN_INVALID = "Resolve option 'throwOnUnknown' must be a boolean when provided.";
1348
1503
  var InMemoryStitchRegistry = class {
1349
- typeMap = /* @__PURE__ */ new Map();
1350
- abbreviationMap = /* @__PURE__ */ new Map();
1351
- defaultCounts = /* @__PURE__ */ new Map();
1352
- lockedCountVariants = /* @__PURE__ */ new Set();
1504
+ constructor() {
1505
+ this.typeMap = /* @__PURE__ */ new Map();
1506
+ this.abbreviationMap = /* @__PURE__ */ new Map();
1507
+ this.defaultCounts = /* @__PURE__ */ new Map();
1508
+ this.lockedCountVariants = /* @__PURE__ */ new Set();
1509
+ }
1353
1510
  /**
1354
1511
  * Normalise a required token for storage.
1355
1512
  *
@@ -1524,10 +1681,8 @@ function createEmptyStitchRegistry() {
1524
1681
  var SHAPING_TOKEN_RE = /^shape-(dec|decrease|inc|increase)(?:-(l|left|r|right|c|centre|center))?(\d+)$/;
1525
1682
  var COMPACT_STITCH_TOKEN_RE = /^([a-z-]+)(\d+)$/i;
1526
1683
  var RowDslParser = class {
1527
- input;
1528
- registry;
1529
- index = 0;
1530
1684
  constructor(input, registry) {
1685
+ this.index = 0;
1531
1686
  this.input = input;
1532
1687
  this.registry = registry ?? createDefaultStitchRegistry();
1533
1688
  }
@@ -2067,6 +2222,9 @@ function resolveRowDslAst(ast) {
2067
2222
  }
2068
2223
 
2069
2224
  // src/parser/rowDsl.ts
2225
+ function isStitchRegistry(value) {
2226
+ return typeof value.isKnown === "function";
2227
+ }
2070
2228
  function parseWithAstParser(parser, input, registry) {
2071
2229
  return resolveRowDslAst(parser(input, registry));
2072
2230
  }
@@ -2079,11 +2237,24 @@ function parseRowDsl(input, registry) {
2079
2237
  function parseCompatibleRowDsl(input, registry) {
2080
2238
  return parseWithAstParser(parseCompatibleRowDslAst, input, registry);
2081
2239
  }
2082
- function compileRowDsl(input, totalStitches, registry) {
2083
- return compileWithParser(parseRowDsl, input, totalStitches, registry);
2084
- }
2085
- function compileCompatibleRowDsl(input, totalStitches, registry) {
2086
- return compileWithParser(parseCompatibleRowDsl, input, totalStitches, registry);
2240
+ function compileRowDsl(input, totalStitches, registryOrOptions) {
2241
+ if (registryOrOptions !== void 0 && !isStitchRegistry(registryOrOptions)) {
2242
+ const { registry, shaping, alignment } = registryOrOptions;
2243
+ return compileRow({ row: parseRowDsl(input, registry), totalStitches, shaping, alignment });
2244
+ }
2245
+ return compileWithParser(parseRowDsl, input, totalStitches, registryOrOptions);
2246
+ }
2247
+ function compileCompatibleRowDsl(input, totalStitches, registryOrOptions) {
2248
+ if (registryOrOptions !== void 0 && !isStitchRegistry(registryOrOptions)) {
2249
+ const { registry, shaping, alignment } = registryOrOptions;
2250
+ return compileRow({
2251
+ row: parseCompatibleRowDsl(input, registry),
2252
+ totalStitches,
2253
+ shaping,
2254
+ alignment
2255
+ });
2256
+ }
2257
+ return compileWithParser(parseCompatibleRowDsl, input, totalStitches, registryOrOptions);
2087
2258
  }
2088
2259
 
2089
2260
  // src/public/parser.ts
@@ -2119,16 +2290,34 @@ function parseCompatibleRowDsl2(input, registry) {
2119
2290
  throw wrapParseError(error, "parseCompatibleRowDsl");
2120
2291
  }
2121
2292
  }
2122
- function compileRowDsl2(input, totalStitches, registry) {
2293
+ function compileRowDsl2(input, totalStitches, registryOrOptions) {
2123
2294
  try {
2124
- return compileRowDsl(input, totalStitches, registry);
2295
+ if (registryOrOptions !== void 0 && typeof registryOrOptions.isKnown !== "function") {
2296
+ return compileRowDsl(input, totalStitches, registryOrOptions);
2297
+ }
2298
+ return compileRowDsl(
2299
+ input,
2300
+ totalStitches,
2301
+ registryOrOptions
2302
+ );
2125
2303
  } catch (error) {
2126
2304
  throw wrapExpansionError2(error, "compileRowDsl");
2127
2305
  }
2128
2306
  }
2129
- function compileCompatibleRowDsl2(input, totalStitches, registry) {
2307
+ function compileCompatibleRowDsl2(input, totalStitches, registryOrOptions) {
2130
2308
  try {
2131
- return compileCompatibleRowDsl(input, totalStitches, registry);
2309
+ if (registryOrOptions !== void 0 && typeof registryOrOptions.isKnown !== "function") {
2310
+ return compileCompatibleRowDsl(
2311
+ input,
2312
+ totalStitches,
2313
+ registryOrOptions
2314
+ );
2315
+ }
2316
+ return compileCompatibleRowDsl(
2317
+ input,
2318
+ totalStitches,
2319
+ registryOrOptions
2320
+ );
2132
2321
  } catch (error) {
2133
2322
  throw wrapExpansionError2(error, "compileCompatibleRowDsl");
2134
2323
  }
@@ -2162,127 +2351,6 @@ function resolveRowDslAst2(ast) {
2162
2351
  }
2163
2352
  }
2164
2353
 
2165
- // src/shaping/distribution.ts
2166
- function distributeEvenly(totalStitches, operations) {
2167
- if (operations <= 0) {
2168
- return { spacing: [] };
2169
- }
2170
- const baseSpacing = Math.floor(totalStitches / operations);
2171
- const remainder = totalStitches % operations;
2172
- const spacing = [];
2173
- for (let i = 0; i < operations; i++) {
2174
- const shouldAddExtra = Math.floor(i * remainder / operations) !== Math.floor((i + 1) * remainder / operations);
2175
- spacing.push(baseSpacing + (shouldAddExtra ? 1 : 0));
2176
- }
2177
- return { spacing };
2178
- }
2179
-
2180
- // src/shaping/shapingRules.ts
2181
- var SHAPING_RULES = {
2182
- decrease: { consume: 2, produce: 1 },
2183
- // k2tog - A decrease consumes 2 stitches and produces 1.
2184
- increase: { consume: 1, produce: 2 }
2185
- // M1 - An increase consumes 1 stitch and produces 2 stitches.
2186
- };
2187
-
2188
- // src/shaping/shaping.ts
2189
- function getShapingPositions(length, count) {
2190
- if (count <= 0 || length <= 0) {
2191
- return [];
2192
- }
2193
- const { spacing } = distributeEvenly(length, count);
2194
- const positions = [];
2195
- let current = 0;
2196
- for (const gap of spacing) {
2197
- positions.push(current);
2198
- current += gap;
2199
- }
2200
- return positions.filter((position) => position >= 0 && position < length);
2201
- }
2202
- function applyPhysicalShaping(stitches, shapingType, positions) {
2203
- const rule = SHAPING_RULES[shapingType];
2204
- const result = [];
2205
- let i = 0;
2206
- let positionIndex = 0;
2207
- while (i < stitches.length) {
2208
- if (positionIndex < positions.length && i === positions[positionIndex]) {
2209
- for (let j = 0; j < rule.produce; j++) {
2210
- result.push({
2211
- isShaping: true,
2212
- operationIndex: positionIndex,
2213
- shapingType,
2214
- sourceIndex: i,
2215
- type: shapingType
2216
- });
2217
- }
2218
- i += rule.consume;
2219
- positionIndex++;
2220
- } else {
2221
- result.push(stitches[i]);
2222
- i++;
2223
- }
2224
- }
2225
- return result;
2226
- }
2227
- function compressStitches(stitches) {
2228
- const result = [];
2229
- for (const stitch of stitches) {
2230
- const last = result.at(-1);
2231
- if ((last == null ? void 0 : last.type) === stitch.type) {
2232
- last.count++;
2233
- } else {
2234
- result.push({ type: stitch.type, count: 1 });
2235
- }
2236
- }
2237
- return result;
2238
- }
2239
- function shapeRow(input) {
2240
- const { row, totalStitches, shaping } = input;
2241
- if (shaping.count < 0) {
2242
- throw new Error("Shaping count must be 0 or greater.");
2243
- }
2244
- const expanded = expandRowToStitchTypes(row, totalStitches).map((type, sourceIndex) => ({
2245
- sourceIndex,
2246
- type
2247
- }));
2248
- const rule = SHAPING_RULES[shaping.type];
2249
- if (shaping.count * rule.consume > expanded.length) {
2250
- throw new Error("Cannot apply more shaping operations than stitches in the row.");
2251
- }
2252
- const shapingPositions = getShapingPositions(expanded.length, shaping.count);
2253
- const shapedStitches = applyPhysicalShaping(expanded, shaping.type, shapingPositions);
2254
- const stitches = compressStitches(shapedStitches);
2255
- const resultingStitches = totalStitches + shaping.count * (rule.produce - rule.consume);
2256
- return { shapedStitches, stitches, totalStitches: resultingStitches };
2257
- }
2258
- function groupShapedStitchesByOperation(shapedStitches) {
2259
- const groups = /* @__PURE__ */ new Map();
2260
- for (let outputIndex = 0; outputIndex < shapedStitches.length; outputIndex += 1) {
2261
- const stitch = shapedStitches[outputIndex];
2262
- if (!stitch.isShaping || stitch.operationIndex === void 0 || stitch.shapingType === void 0) {
2263
- continue;
2264
- }
2265
- const existing = groups.get(stitch.operationIndex);
2266
- if (existing) {
2267
- if (existing.shapingType !== stitch.shapingType) {
2268
- throw new Error("Shaping operation contains mixed shaping types.");
2269
- }
2270
- existing.stitches.push(stitch);
2271
- existing.outputIndices.push(outputIndex);
2272
- existing.sourceIndices.push(stitch.sourceIndex);
2273
- continue;
2274
- }
2275
- groups.set(stitch.operationIndex, {
2276
- operationIndex: stitch.operationIndex,
2277
- outputIndices: [outputIndex],
2278
- shapingType: stitch.shapingType,
2279
- sourceIndices: [stitch.sourceIndex],
2280
- stitches: [stitch]
2281
- });
2282
- }
2283
- return Array.from(groups.values());
2284
- }
2285
-
2286
2354
  // src/shaping/shapingDsl.ts
2287
2355
  var SHAPING_CLAUSE_RE = /^(?:shape|shaping)\s*:?\s*(increase|decrease)\s+(\d+)$/i;
2288
2356
  var TOTAL_CLAUSE_RE = /^(?:total|total-stitches|stitches)\s*:?\s*(\d+)$/i;
@@ -2405,8 +2473,8 @@ function resolveShapingDslAst(ast) {
2405
2473
  function parseShapingDsl(input) {
2406
2474
  return resolveShapingDslAst(parseShapingDslAst(input));
2407
2475
  }
2408
- function compileShapingDsl(input) {
2409
- return shapeRow(parseShapingDsl(input));
2476
+ function compileShapingDsl(input, alignment) {
2477
+ return shapeRow({ ...parseShapingDsl(input), alignment });
2410
2478
  }
2411
2479
 
2412
2480
  // src/shaping/shapingDebug.ts
@@ -2461,11 +2529,11 @@ function renderShapingDebugForInput(input, options = {}) {
2461
2529
  function printShapingDebug(shapedStitches, label, options = {}) {
2462
2530
  const { legend, markers, row } = renderShapingDebug(shapedStitches, options);
2463
2531
  if (label) {
2464
- debugLogger == null ? void 0 : debugLogger.log(label);
2532
+ debugLogger?.log(label);
2465
2533
  }
2466
- debugLogger == null ? void 0 : debugLogger.log(row);
2467
- debugLogger == null ? void 0 : debugLogger.log(markers);
2468
- debugLogger == null ? void 0 : debugLogger.log(legend);
2534
+ debugLogger?.log(row);
2535
+ debugLogger?.log(markers);
2536
+ debugLogger?.log(legend);
2469
2537
  }
2470
2538
 
2471
2539
  // src/public/shaping.ts
@@ -2508,6 +2576,13 @@ function compileShapingDsl2(input) {
2508
2576
  throw wrapExpansionError3(error, "compileShapingDsl");
2509
2577
  }
2510
2578
  }
2579
+ function compileRow2(input) {
2580
+ try {
2581
+ return compileRow(input);
2582
+ } catch (error) {
2583
+ throw wrapExpansionError3(error, "compileRow");
2584
+ }
2585
+ }
2511
2586
  function shapeRow2(input) {
2512
2587
  try {
2513
2588
  return shapeRow(input);
@@ -2537,6 +2612,7 @@ export {
2537
2612
  cmToStitches,
2538
2613
  compileCompatibleRowDsl2 as compileCompatibleRowDsl,
2539
2614
  compilePattern2 as compilePattern,
2615
+ compileRow2 as compileRow,
2540
2616
  compileRowDsl2 as compileRowDsl,
2541
2617
  compileShapingDsl2 as compileShapingDsl,
2542
2618
  convertLength,
@@ -2551,7 +2627,7 @@ export {
2551
2627
  expandRowToStitches,
2552
2628
  groupShapedStitchesByOperation2 as groupShapedStitchesByOperation,
2553
2629
  isRowDslAstWellFormed,
2554
- normaliseMeasurement,
2630
+ normaliseMeasurement2 as normaliseMeasurement,
2555
2631
  parseCompatibleRowDsl2 as parseCompatibleRowDsl,
2556
2632
  parseCompatibleRowDslAst2 as parseCompatibleRowDslAst,
2557
2633
  parseRowDsl2 as parseRowDsl,
@@ -2563,10 +2639,10 @@ export {
2563
2639
  renderShapingDebug,
2564
2640
  renderShapingDebugForInput,
2565
2641
  resolveRowDslAst2 as resolveRowDslAst,
2566
- roundStitches,
2642
+ roundStitches2 as roundStitches,
2567
2643
  rowToRowDsl2 as rowToRowDsl,
2568
2644
  rowsPerCm,
2569
2645
  shapeRow2 as shapeRow,
2570
2646
  shapedExpandPattern2 as shapedExpandPattern,
2571
- stitchesPerCm
2647
+ stitchesPerCm2 as stitchesPerCm
2572
2648
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knitting-tools/core",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Knitting maths, row parsing, shaping, and pattern compilation utilities.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -36,19 +36,21 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "scripts": {
40
- "build": "tsup src/index.ts --format esm --dts --clean",
41
- "test": "vitest run",
42
- "typecheck": "tsc -p tsconfig.src.json --noEmit",
43
- "test:watch": "vitest",
44
- "test:coverage": "vitest run --coverage",
45
- "check": "pnpm build && pnpm typecheck && pnpm test:coverage",
46
- "prepack": "pnpm build"
39
+ "dependencies": {
40
+ "@knitting-tools/math": "3.0.0"
47
41
  },
48
42
  "devDependencies": {
49
43
  "@vitest/coverage-v8": "4.1.0",
50
44
  "tsup": "^8.5.1",
51
45
  "typescript": "^5.9.3",
52
46
  "vitest": "^4.1.0"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup src/index.ts --format esm --dts --clean",
50
+ "test": "vitest run",
51
+ "typecheck": "tsc -p tsconfig.src.json --noEmit",
52
+ "test:watch": "vitest",
53
+ "test:coverage": "vitest run --coverage",
54
+ "check": "pnpm build && pnpm typecheck && pnpm test:coverage"
53
55
  }
54
- }
56
+ }