linked-rolls 0.62.0 → 0.63.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/README.md +14 -3
- package/lib/analysis/constraints.js +16 -10
- package/lib/analysis/ownPaper.d.ts +9 -14
- package/lib/analysis/ownPaper.js +13 -31
- package/lib/analysis/paper.d.ts +129 -0
- package/lib/analysis/paper.js +260 -0
- package/lib/collation/alignment.d.ts +46 -8
- package/lib/collation/alignment.js +137 -47
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/io/asJsonLd.js +6 -1
- package/lib/io/importJsonLd.d.ts +5 -0
- package/lib/io/importJsonLd.js +7 -0
- package/lib/io/migrate.js +94 -2
- package/lib/model/Assumption.d.ts +21 -9
- package/lib/model/Edition.d.ts +19 -0
- package/lib/model/Edition.js +9 -1
- package/lib/model/Quantity.d.ts +4 -1
- package/lib/model/Quantity.js +1 -0
- package/lib/model/RollCopy.d.ts +144 -48
- package/lib/model/RollCopy.js +9 -6
- package/lib/ops/copies.d.ts +47 -10
- package/lib/ops/copies.js +92 -26
- package/lib/ops/index.d.ts +1 -1
- package/lib/ops/index.js +1 -1
- package/lib/readers/stanfordAton.js +28 -1
- package/lib/schema.json +183 -24
- package/lib/spec/context.json +33 -1
- package/lib/systems/ReproducingSystem.d.ts +7 -6
- package/package.json +1 -1
|
@@ -1,56 +1,126 @@
|
|
|
1
|
-
import { featuresOf } from "../model/RollCopy.js";
|
|
1
|
+
import { featuresOf, isTear, tearsOf } from "../model/RollCopy.js";
|
|
2
2
|
import { defaultTrackerBar } from "../systems/index.js";
|
|
3
|
-
import { add, mm,
|
|
4
|
-
/**
|
|
5
|
-
const
|
|
6
|
-
|
|
3
|
+
import { add, mm, subtract, track } from "../model/Quantity.js";
|
|
4
|
+
/** A place on the copy's own paper, on the edition's axis. */
|
|
5
|
+
export const toAxis = (alignment) => (place) => alignment ? mm((place + alignment.shift.horizontal) * alignment.scale) : place;
|
|
6
|
+
/** A place on the edition's axis, on the copy's own paper. */
|
|
7
|
+
export const fromAxis = (alignment) => (place) => alignment ? mm(place / alignment.scale - alignment.shift.horizontal) : place;
|
|
8
|
+
const acrossToAxis = (alignment) => (position) => alignment ? track(position + alignment.shift.vertical) : position;
|
|
9
|
+
const acrossFromAxis = (alignment) => (position) => alignment ? track(position - alignment.shift.vertical) : position;
|
|
10
|
+
/** Both ends of a span taken through the mapping, the far end only where the span has one. */
|
|
11
|
+
const mapped = (span, through) => {
|
|
12
|
+
span.from = through(span.from);
|
|
7
13
|
if (span.to !== undefined)
|
|
8
|
-
span.to =
|
|
14
|
+
span.to = through(span.to);
|
|
9
15
|
};
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Where along the roll the copy states anything: its features, and its
|
|
18
|
+
* tears, which are measured as the features are. A tear states no track,
|
|
19
|
+
* so the shift across the roll leaves it where it is.
|
|
20
|
+
*/
|
|
21
|
+
const spansAlong = (copy) => [...featuresOf(copy), ...tearsOf(copy)].map(placed => placed.horizontal);
|
|
22
|
+
/** Puts the copy's own places onto the axis in place. */
|
|
23
|
+
const moveOnto = (alignment, copy) => {
|
|
24
|
+
spansAlong(copy).forEach(span => mapped(span, toAxis(alignment)));
|
|
25
|
+
featuresOf(copy).forEach(feature => mapped(feature.vertical, acrossToAxis(alignment)));
|
|
15
26
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
featuresOf(copy).forEach(feature => {
|
|
21
|
-
move(feature.horizontal, shift.horizontal);
|
|
22
|
-
move(feature.vertical, shift.vertical);
|
|
23
|
-
});
|
|
24
|
-
copy.measurements.shift = shift;
|
|
27
|
+
/** Takes the copy's places on the axis back to its own paper in place. */
|
|
28
|
+
const moveOff = (alignment, copy) => {
|
|
29
|
+
spansAlong(copy).forEach(span => mapped(span, fromAxis(alignment)));
|
|
30
|
+
featuresOf(copy).forEach(feature => mapped(feature.vertical, acrossFromAxis(alignment)));
|
|
25
31
|
};
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Carries the copy's features and tears from its own places onto the
|
|
34
|
+
* axis, and records how, in place of any alignment it had: that one is
|
|
35
|
+
* taken off first.
|
|
36
|
+
*/
|
|
37
|
+
export const applyAlignment = (alignment, copy) => {
|
|
38
|
+
revertAlignment(copy);
|
|
39
|
+
moveOnto(alignment, copy);
|
|
40
|
+
copy.measurements.alignment = alignment;
|
|
32
41
|
};
|
|
33
|
-
/** Takes the
|
|
34
|
-
export const
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
42
|
+
/** Takes the copy's features and tears back to its own places, as far as it was aligned. */
|
|
43
|
+
export const revertAlignment = (copy) => {
|
|
44
|
+
const alignment = copy.measurements.alignment;
|
|
45
|
+
if (!alignment)
|
|
37
46
|
return;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
move(feature.horizontal, reversed.horizontal);
|
|
41
|
-
move(feature.vertical, reversed.vertical);
|
|
42
|
-
});
|
|
43
|
-
delete copy.measurements.shift;
|
|
47
|
+
moveOff(alignment, copy);
|
|
48
|
+
delete copy.measurements.alignment;
|
|
44
49
|
};
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Places are written to the nanometre. Carried onto the axis and back,
|
|
52
|
+
* a place comes back a rounding error away from where it was, and a
|
|
53
|
+
* document saved again would change in its last digits every time.
|
|
54
|
+
* A scan resolves a tenth of a millimetre at best.
|
|
55
|
+
*/
|
|
56
|
+
const PLACES_PER_MM = 1e6;
|
|
57
|
+
const written = (place) => mm(Math.round(place * PLACES_PER_MM) / PLACES_PER_MM);
|
|
58
|
+
/**
|
|
59
|
+
* The copy with its features and tears at its own places, as a document
|
|
60
|
+
* holds them, and its alignment kept to say how they go onto the axis.
|
|
61
|
+
* The copy given is left as it is. A copy that is not aligned is the
|
|
62
|
+
* very same copy.
|
|
63
|
+
*/
|
|
64
|
+
export const atOwnPlaces = (copy) => {
|
|
65
|
+
const alignment = copy.measurements.alignment;
|
|
66
|
+
if (!alignment)
|
|
67
|
+
return copy;
|
|
68
|
+
const along = (place) => written(fromAxis(alignment)(place));
|
|
69
|
+
const across = acrossFromAxis(alignment);
|
|
70
|
+
const ownSpan = (span, through) => {
|
|
71
|
+
const own = { ...span };
|
|
72
|
+
mapped(own, through);
|
|
73
|
+
return own;
|
|
74
|
+
};
|
|
75
|
+
const ownFeature = (feature) => ({ ...feature, horizontal: ownSpan(feature.horizontal, along), vertical: ownSpan(feature.vertical, across) });
|
|
76
|
+
return {
|
|
77
|
+
...copy,
|
|
78
|
+
...(copy.production && {
|
|
79
|
+
production: {
|
|
80
|
+
...copy.production,
|
|
81
|
+
...(copy.production.produced && { produced: copy.production.produced.map(ownFeature) })
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
modifications: copy.modifications.map(act => {
|
|
85
|
+
if (act.type === 'Alteration')
|
|
86
|
+
return { ...act, produced: act.produced.map(ownFeature) };
|
|
87
|
+
if (act.type === 'Attachment')
|
|
88
|
+
return { ...act, added: act.added.map(ownFeature) };
|
|
89
|
+
return act;
|
|
90
|
+
}),
|
|
91
|
+
conditions: copy.conditions.map(condition => isTear(condition)
|
|
92
|
+
? { ...condition, horizontal: ownSpan(condition.horizontal, along) }
|
|
93
|
+
: condition)
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Puts the features and tears of a copy read from a document, which
|
|
98
|
+
* holds them at the copy's own places, onto the axis. The copy given is
|
|
99
|
+
* changed.
|
|
100
|
+
*/
|
|
101
|
+
export const putOnAxis = (copy) => {
|
|
102
|
+
const alignment = copy.measurements.alignment;
|
|
103
|
+
if (alignment)
|
|
104
|
+
moveOnto(alignment, copy);
|
|
105
|
+
};
|
|
106
|
+
/** The copy's features at its own places, as it was read, whatever alignment it has. */
|
|
107
|
+
export const ownFeaturesOf = (copy) => {
|
|
108
|
+
const alignment = copy.measurements.alignment;
|
|
109
|
+
if (!alignment)
|
|
110
|
+
return featuresOf(copy);
|
|
111
|
+
return featuresOf(copy).map(feature => {
|
|
112
|
+
const own = { ...feature, horizontal: { ...feature.horizontal }, vertical: { ...feature.vertical } };
|
|
113
|
+
mapped(own.horizontal, fromAxis(alignment));
|
|
114
|
+
mapped(own.vertical, acrossFromAxis(alignment));
|
|
115
|
+
return own;
|
|
116
|
+
});
|
|
52
117
|
};
|
|
53
118
|
const chainsOf = (copy) => featuresOf(copy).filter(feature => feature.type === 'HoleChain');
|
|
119
|
+
/**
|
|
120
|
+
* The reader's extension as it lies on the axis. It is a length on the
|
|
121
|
+
* copy's own paper, which the alignment scales like any other.
|
|
122
|
+
*/
|
|
123
|
+
const extensionOnAxis = (extension, copy) => mm(extension * (copy.measurements.alignment?.scale ?? 1));
|
|
54
124
|
/**
|
|
55
125
|
* Takes the extension a pneumatic reader adds off the ends of the
|
|
56
126
|
* copy's chains of holes, and records how much was taken.
|
|
@@ -86,7 +156,10 @@ const chainsOf = (copy) => featuresOf(copy).filter(feature => feature.type === '
|
|
|
86
156
|
* on the feature, a reading of the chain, a smaller extension — and
|
|
87
157
|
* `shortenChains` throws rather than answer it.
|
|
88
158
|
*/
|
|
89
|
-
export const tooShortToShorten = (extension, copy, leaving = new Set()) =>
|
|
159
|
+
export const tooShortToShorten = (extension, copy, leaving = new Set()) => {
|
|
160
|
+
const onAxis = extensionOnAxis(extension, copy);
|
|
161
|
+
return chainsOf(copy).filter(chain => !leaving.has(chain.id) && chain.horizontal.to - chain.horizontal.from <= onAxis);
|
|
162
|
+
};
|
|
90
163
|
/**
|
|
91
164
|
* Takes the extension off, leaving the named chains as the reader gave
|
|
92
165
|
* them. Naming a chain is an editorial act and not a repair: it says
|
|
@@ -107,10 +180,11 @@ export const shortenChains = (extension, copy, leaving = new Set()) => {
|
|
|
107
180
|
+ `chain(s) of holes of copy ${copy.id}, which are no longer than it: `
|
|
108
181
|
+ `${tooShort.map(chain => chain.id).join(', ')}`);
|
|
109
182
|
}
|
|
183
|
+
const onAxis = extensionOnAxis(extension, copy);
|
|
110
184
|
const left = chainsOf(copy).filter(chain => leaving.has(chain.id)).map(chain => chain.id);
|
|
111
185
|
chainsOf(copy)
|
|
112
186
|
.filter(chain => !leaving.has(chain.id))
|
|
113
|
-
.forEach(chain => { chain.horizontal.to = subtract(chain.horizontal.to,
|
|
187
|
+
.forEach(chain => { chain.horizontal.to = subtract(chain.horizontal.to, onAxis); });
|
|
114
188
|
copy.measurements.readerExtension = { length: extension, ...(left.length > 0 && { leaving: left }) };
|
|
115
189
|
};
|
|
116
190
|
/** Puts the reader's extension back on the chains it was taken off, as far as one was taken off. */
|
|
@@ -118,12 +192,20 @@ export const revertShortening = (copy) => {
|
|
|
118
192
|
const taken = copy.measurements.readerExtension;
|
|
119
193
|
if (taken === undefined)
|
|
120
194
|
return;
|
|
195
|
+
const onAxis = extensionOnAxis(taken.length, copy);
|
|
121
196
|
const left = new Set(taken.leaving ?? []);
|
|
122
197
|
chainsOf(copy)
|
|
123
198
|
.filter(chain => !left.has(chain.id))
|
|
124
|
-
.forEach(chain => { chain.horizontal.to = add(chain.horizontal.to,
|
|
199
|
+
.forEach(chain => { chain.horizontal.to = add(chain.horizontal.to, onAxis); });
|
|
125
200
|
delete copy.measurements.readerExtension;
|
|
126
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* The method `alignFeatures` follows, as an alignment records it. Its
|
|
204
|
+
* revision changes when the method does and at no other release, so
|
|
205
|
+
* that an alignment found by an earlier method can be told from one
|
|
206
|
+
* that would be found again as it stands.
|
|
207
|
+
*/
|
|
208
|
+
export const ALIGNMENT_METHOD = { software: 'linked-rolls alignFeatures', version: '1' };
|
|
127
209
|
const byPlace = (x, y) => x.at - y.at || x.pitch - y.pitch;
|
|
128
210
|
/** The notes the bar reads off the features, in the order they pass it. */
|
|
129
211
|
const noteOnsets = (features, bar) => features
|
|
@@ -218,15 +300,23 @@ const settled = (a, b) => (fit, window) => {
|
|
|
218
300
|
const matches = nearestMatches(a, b, fit.line, window);
|
|
219
301
|
return { line: robustLine(matches) ?? fit.line, matches };
|
|
220
302
|
};
|
|
303
|
+
/** How far the places of the matches on the copy spread, as a standard deviation. */
|
|
304
|
+
const spreadOf = (matches) => {
|
|
305
|
+
const centre = matches.reduce((total, m) => total + m.a, 0) / matches.length;
|
|
306
|
+
return Math.sqrt(matches.reduce((total, m) => total + (m.a - centre) ** 2, 0) / matches.length);
|
|
307
|
+
};
|
|
221
308
|
const resultOf = ({ line, matches }) => {
|
|
222
309
|
if (matches.length < 2 || line.slope <= 0)
|
|
223
310
|
return undefined;
|
|
224
311
|
const squares = matches.reduce((total, m) => total + residualOf(line, m) ** 2, 0);
|
|
312
|
+
const residual = Math.sqrt(squares / matches.length);
|
|
313
|
+
const spread = spreadOf(matches);
|
|
225
314
|
return {
|
|
226
315
|
shift: mm(line.intercept / line.slope),
|
|
227
316
|
scale: line.slope,
|
|
228
317
|
matched: matches.length,
|
|
229
|
-
residual: mm(
|
|
318
|
+
residual: mm(residual),
|
|
319
|
+
scaleError: spread > 0 ? residual / (spread * Math.sqrt(matches.length)) : Infinity
|
|
230
320
|
};
|
|
231
321
|
};
|
|
232
322
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -28,11 +28,12 @@ export * from './model/procedures.js';
|
|
|
28
28
|
export * from './model/vocabulary.js';
|
|
29
29
|
export * from './analysis/reservations.js';
|
|
30
30
|
export * from './analysis/witnesses.js';
|
|
31
|
-
export { alignFeatures, tooShortToShorten, type AlignmentResult } from './collation/alignment.js';
|
|
31
|
+
export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten, type AlignmentResult } from './collation/alignment.js';
|
|
32
32
|
export * from './collation/statistics.js';
|
|
33
33
|
export * from './model/Edition.js';
|
|
34
34
|
export * from './view/EditionView.js';
|
|
35
35
|
export * from './analysis/ownPaper.js';
|
|
36
|
+
export * from './analysis/paper.js';
|
|
36
37
|
export * from './emulation/negotiation.js';
|
|
37
38
|
export * from './collation/scatter.js';
|
|
38
39
|
export * from './ops/index.js';
|
package/lib/index.js
CHANGED
|
@@ -21,11 +21,12 @@ export * from './model/procedures.js';
|
|
|
21
21
|
export * from './model/vocabulary.js';
|
|
22
22
|
export * from './analysis/reservations.js';
|
|
23
23
|
export * from './analysis/witnesses.js';
|
|
24
|
-
export { alignFeatures, tooShortToShorten } from './collation/alignment.js';
|
|
24
|
+
export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten } from './collation/alignment.js';
|
|
25
25
|
export * from './collation/statistics.js';
|
|
26
26
|
export * from './model/Edition.js';
|
|
27
27
|
export * from './view/EditionView.js';
|
|
28
28
|
export * from './analysis/ownPaper.js';
|
|
29
|
+
export * from './analysis/paper.js';
|
|
29
30
|
export * from './emulation/negotiation.js';
|
|
30
31
|
export * from './collation/scatter.js';
|
|
31
32
|
export * from './ops/index.js';
|
package/lib/io/asJsonLd.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { systemIdIn } from "../systems/TrackerBar.js";
|
|
2
2
|
import { certaintyOf, isAsserted } from "../model/Assumption.js";
|
|
3
3
|
import context from "../spec/context.json" with { type: 'json' };
|
|
4
|
+
import { atOwnPlaces } from "../collation/alignment.js";
|
|
4
5
|
/**
|
|
5
6
|
* The keys an export derives from the tree, which no node of the
|
|
6
7
|
* edition states itself. An import reads them off again.
|
|
@@ -151,8 +152,12 @@ const withDoubtedReferencesQuoted = (value) => {
|
|
|
151
152
|
quoted: entries.flatMap(({ quoted }) => quoted)
|
|
152
153
|
};
|
|
153
154
|
};
|
|
155
|
+
/** The edition with each copy's features and tears at the copy's own places, as a document holds them. */
|
|
156
|
+
const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurements.alignment)
|
|
157
|
+
? { ...edition, copies: edition.copies.map(atOwnPlaces) }
|
|
158
|
+
: edition;
|
|
154
159
|
export const asJsonLd = (edition) => {
|
|
155
|
-
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withChanges(asJsonLdEntity(edition))));
|
|
160
|
+
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withChanges(asJsonLdEntity(atOwnPlacesAll(edition)))));
|
|
156
161
|
// The context is the export's own; one carried in from an import must not override it.
|
|
157
162
|
const { base, '@context': carried, ...rest } = node;
|
|
158
163
|
return {
|
package/lib/io/importJsonLd.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Edition } from "../model/Edition.js";
|
|
2
2
|
export declare const importDate: (str: string) => Date;
|
|
3
3
|
type Json = any;
|
|
4
|
+
/**
|
|
5
|
+
* Reads an edition. A document holds each copy's features and tears at
|
|
6
|
+
* the copy's own places, as they were read; the edition holds them on
|
|
7
|
+
* its axis, which is where the copy's alignment carries them.
|
|
8
|
+
*/
|
|
4
9
|
export declare const importJsonLd: (json: Json) => Edition;
|
|
5
10
|
export {};
|
package/lib/io/importJsonLd.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { derivedKeys } from "./asJsonLd.js";
|
|
2
2
|
import { migrate, plainCopyId } from "./migrate.js";
|
|
3
3
|
import { isDateString } from "../shared/utils.js";
|
|
4
|
+
import { putOnAxis } from "../collation/alignment.js";
|
|
4
5
|
import schema from "../schema.json" with { type: 'json' };
|
|
5
6
|
export const importDate = (str) => {
|
|
6
7
|
const [y, m, d] = str.split('-').map(s => parseInt(s, 10));
|
|
@@ -118,11 +119,17 @@ const withQuotedStatementsInPlace = (edition) => {
|
|
|
118
119
|
const bySubject = Map.groupBy(statements.map(referenceOf), ({ subject }) => subject);
|
|
119
120
|
return withReferencesOn({ ...rest, ...(others.length > 0 && { '@included': others }) }, bySubject);
|
|
120
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* Reads an edition. A document holds each copy's features and tears at
|
|
124
|
+
* the copy's own places, as they were read; the edition holds them on
|
|
125
|
+
* its axis, which is where the copy's alignment carries them.
|
|
126
|
+
*/
|
|
121
127
|
export const importJsonLd = (json) => {
|
|
122
128
|
const { '@context': context, ...document } = withPlainCopyIds(migrate(withQuotedStatementsInPlace(json)));
|
|
123
129
|
const edition = fromJsonLdEntity(document);
|
|
124
130
|
edition.base = Array.isArray(context)
|
|
125
131
|
? context.find((c) => c['@base'])?.['@base'] || ''
|
|
126
132
|
: '';
|
|
133
|
+
(edition.copies ?? []).forEach(putOnAxis);
|
|
127
134
|
return edition;
|
|
128
135
|
};
|
package/lib/io/migrate.js
CHANGED
|
@@ -360,6 +360,78 @@ const withFeaturesInActs = (node) => {
|
|
|
360
360
|
modifications: [...acts, ...(loose.length > 0 ? [{ '@type': 'Attachment', added: loose }] : [])]
|
|
361
361
|
};
|
|
362
362
|
};
|
|
363
|
+
/** A place written to the nanometre, as the export writes places it has carried off the axis. */
|
|
364
|
+
const writtenPlace = (place) => Math.round(place * 1e6) / 1e6;
|
|
365
|
+
/** The span with both ends taken through the mapping, the far end only where it has one. */
|
|
366
|
+
const spanThrough = (span, through) => span && typeof span.from === 'number'
|
|
367
|
+
? { ...span, from: through(span.from), ...(typeof span.to === 'number' && { to: through(span.to) }) }
|
|
368
|
+
: span;
|
|
369
|
+
const isLegacyAligned = (copy) => copy?.measurements?.shift !== undefined || copy?.measurements?.scale !== undefined;
|
|
370
|
+
const isLegacyPaperStretch = (condition) => isPaperStretch(condition) && Object.hasOwn(condition, 'factor');
|
|
371
|
+
const conditionTypeOf = (condition) => condition?.conditionType ?? condition?.['@type'];
|
|
372
|
+
/**
|
|
373
|
+
* A copy's features once stood where its alignment had put them, on the
|
|
374
|
+
* edition's axis, with the shift and the scale that had done it recorded
|
|
375
|
+
* beside them. They stand at the copy's own places now, as they were
|
|
376
|
+
* read, and the alignment says how they go onto the axis; so they are
|
|
377
|
+
* taken back by it, and so are the tears, which were measured as the
|
|
378
|
+
* features are. The features are in their acts here, `withFeaturesInActs`
|
|
379
|
+
* having run.
|
|
380
|
+
*
|
|
381
|
+
* The factor of a paper-stretch condition was the alignment's scale
|
|
382
|
+
* stated again, which is how every such condition came about. It is
|
|
383
|
+
* dropped: the scale stays with the alignment, and what it says about the
|
|
384
|
+
* paper is worked out from all the alignments together.
|
|
385
|
+
*/
|
|
386
|
+
const withOwnPlaces = (node) => {
|
|
387
|
+
if (node['@type'] !== 'RollCopy' || !isLegacyAligned(node))
|
|
388
|
+
return node;
|
|
389
|
+
const { shift: legacyShift, scale: legacyScale, ...measurements } = node.measurements;
|
|
390
|
+
const shift = {
|
|
391
|
+
horizontal: legacyShift?.horizontal ?? 0,
|
|
392
|
+
vertical: legacyShift?.vertical ?? 0
|
|
393
|
+
};
|
|
394
|
+
const scale = legacyScale ?? 1;
|
|
395
|
+
const along = (place) => writtenPlace(place / scale - shift.horizontal);
|
|
396
|
+
const across = (position) => position - shift.vertical;
|
|
397
|
+
const ownFeature = (feature) => {
|
|
398
|
+
if (!feature || typeof feature !== 'object' || !feature.horizontal)
|
|
399
|
+
return feature;
|
|
400
|
+
return {
|
|
401
|
+
...feature,
|
|
402
|
+
horizontal: spanThrough(feature.horizontal, along),
|
|
403
|
+
...(feature.vertical?.unit === 'track' && shift.vertical !== 0 && { vertical: spanThrough(feature.vertical, across) })
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
const ownFeatures = (features) => Array.isArray(features) ? features.map(ownFeature) : features;
|
|
407
|
+
const production = node.production && {
|
|
408
|
+
...node.production,
|
|
409
|
+
...(Array.isArray(node.production.produced) && { produced: ownFeatures(node.production.produced) })
|
|
410
|
+
};
|
|
411
|
+
const modifications = Array.isArray(node.modifications)
|
|
412
|
+
? node.modifications.map((act) => act && typeof act === 'object'
|
|
413
|
+
? {
|
|
414
|
+
...act,
|
|
415
|
+
...(Array.isArray(act.produced) && { produced: ownFeatures(act.produced) }),
|
|
416
|
+
...(Array.isArray(act.added) && { added: ownFeatures(act.added) })
|
|
417
|
+
}
|
|
418
|
+
: act)
|
|
419
|
+
: node.modifications;
|
|
420
|
+
const conditions = Array.isArray(node.conditions)
|
|
421
|
+
? node.conditions
|
|
422
|
+
.filter((condition) => !isLegacyPaperStretch(condition))
|
|
423
|
+
.map((condition) => conditionTypeOf(condition) === 'torn' && condition.horizontal
|
|
424
|
+
? { ...condition, horizontal: spanThrough(condition.horizontal, along) }
|
|
425
|
+
: condition)
|
|
426
|
+
: node.conditions;
|
|
427
|
+
return {
|
|
428
|
+
...node,
|
|
429
|
+
measurements: { ...measurements, alignment: { shift, scale } },
|
|
430
|
+
...(production && { production }),
|
|
431
|
+
...(modifications && { modifications }),
|
|
432
|
+
...(conditions && { conditions })
|
|
433
|
+
};
|
|
434
|
+
};
|
|
363
435
|
/** A keeper nobody could name was written as an empty one before a copy could leave it out. */
|
|
364
436
|
const withoutEmptyKeeper = (node) => {
|
|
365
437
|
if (node.keeper?.name !== '' || node.keeper.sameAs?.length)
|
|
@@ -380,7 +452,7 @@ const withTimeSpanDates = (node) => {
|
|
|
380
452
|
};
|
|
381
453
|
const migrateNode = (node) => [withRenamedKeys, withRenamedEditType, withTypology, withRenamedType, withoutVersionType, withoutVersionSiglum, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
|
|
382
454
|
withPerforator, withProductionNodes, withTypedPerforator, withScale, withoutOps, withDerivationList, withReadingKind, withTimeSpanDates,
|
|
383
|
-
withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType]
|
|
455
|
+
withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withOwnPlaces, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType]
|
|
384
456
|
.reduce((result, step) => step(result), node);
|
|
385
457
|
/** The items each walked, or the very same list where the walk changed none. */
|
|
386
458
|
const walked = (items) => {
|
|
@@ -525,5 +597,25 @@ const withoutFormatVersion = (edition) => {
|
|
|
525
597
|
const { formatVersion: _stated, ...rest } = edition;
|
|
526
598
|
return rest;
|
|
527
599
|
};
|
|
528
|
-
|
|
600
|
+
/** Whether the copy states features, in either shape. */
|
|
601
|
+
const hasFeatures = (copy) => [copy?.features, copy?.production?.produced, copy?.productionEvent?.produced]
|
|
602
|
+
.some(features => Array.isArray(features) && features.length > 0);
|
|
603
|
+
const statesLegacyAlignment = (copy) => isLegacyAligned(copy)
|
|
604
|
+
|| (Array.isArray(copy?.ops) && (copy.ops.includes('shifted') || copy.ops.includes('stretched')));
|
|
605
|
+
/**
|
|
606
|
+
* The copy whose millimetres were the edition's axis went unnamed while
|
|
607
|
+
* the copies aligned onto it recorded only what had been done to them.
|
|
608
|
+
* It was the copy nothing had been done to, and of those the first with
|
|
609
|
+
* features, so that is the one named.
|
|
610
|
+
*/
|
|
611
|
+
const withReferenceCopy = (edition) => {
|
|
612
|
+
const copies = listAt(edition, 'copies');
|
|
613
|
+
if (edition.referenceCopy !== undefined || !copies?.some(statesLegacyAlignment))
|
|
614
|
+
return edition;
|
|
615
|
+
const reference = copies.find(copy => !statesLegacyAlignment(copy) && hasFeatures(copy));
|
|
616
|
+
return typeof reference?.['@id'] === 'string'
|
|
617
|
+
? { ...edition, referenceCopy: plainCopyId(reference['@id']) }
|
|
618
|
+
: edition;
|
|
619
|
+
};
|
|
620
|
+
const editionSteps = [withoutFormatVersion, withoutEditionType, withSystems, withEditors, withDerivationTolerance, withReferenceCopy];
|
|
529
621
|
export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
|
|
@@ -46,22 +46,35 @@ export interface Inference extends Argumentation<'inference'> {
|
|
|
46
46
|
/**
|
|
47
47
|
* References (by `@id`) to the beliefs
|
|
48
48
|
* from which the conclusion is drawn.
|
|
49
|
+
*
|
|
50
|
+
* A belief the editor holds and publishes elsewhere under an IRI of
|
|
51
|
+
* its own, such as a bound from a catalogue of premises the editor
|
|
52
|
+
* keeps, is named by that IRI. Someone else's belief is not: it is
|
|
53
|
+
* adopted first, and the belief of the edition's own that the
|
|
54
|
+
* adoption concludes is named instead.
|
|
49
55
|
* @see crminf:J1 used as premise
|
|
50
56
|
*/
|
|
51
57
|
premises: string[];
|
|
52
58
|
/**
|
|
53
59
|
* References (by `@id`) to what the inference worked on besides
|
|
54
|
-
* beliefs, such as the versions it compared
|
|
55
|
-
*
|
|
60
|
+
* beliefs, such as the versions it compared, the analysis output it
|
|
61
|
+
* cites, or the document holding a premise named by IRI, in the
|
|
62
|
+
* state it was read in, since a belief published elsewhere may move
|
|
63
|
+
* after the edition has used it.
|
|
56
64
|
* @see crm:P16 used specific object
|
|
57
65
|
*/
|
|
58
66
|
used?: string[];
|
|
59
67
|
}
|
|
60
68
|
/**
|
|
61
|
-
* A belief adoption
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
69
|
+
* A belief adoption takes over someone else's belief without checking it:
|
|
70
|
+
* knowledge through private communication, from the secondary
|
|
71
|
+
* literature, or from another edition. Like every reason, it concludes
|
|
72
|
+
* the belief it is a reason of, which is the edition's own and holds of
|
|
73
|
+
* the edition's statement what the adopted belief held. An inference that
|
|
74
|
+
* relies on it names that belief among its premises.
|
|
75
|
+
*
|
|
76
|
+
* The editor's own beliefs need no adoption, wherever they are
|
|
77
|
+
* published: an inference names them directly (see `Inference`).
|
|
65
78
|
* @see crminf:I7 Belief Adoption
|
|
66
79
|
*/
|
|
67
80
|
export interface BeliefAdoption extends Argumentation<'beliefAdoption'> {
|
|
@@ -76,9 +89,8 @@ export interface BeliefAdoption extends Argumentation<'beliefAdoption'> {
|
|
|
76
89
|
note: string;
|
|
77
90
|
/**
|
|
78
91
|
* The beliefs this adoption takes over, by IRI, where they are
|
|
79
|
-
* published with one.
|
|
80
|
-
*
|
|
81
|
-
* comes from.
|
|
92
|
+
* published with one. They are someone else's, and stay theirs: what
|
|
93
|
+
* the edition holds is the belief the adoption concludes.
|
|
82
94
|
* @see crminf:J6 adopted
|
|
83
95
|
*/
|
|
84
96
|
adopted?: string[];
|
package/lib/model/Edition.d.ts
CHANGED
|
@@ -140,6 +140,18 @@ export interface Edition {
|
|
|
140
140
|
* @see reo:witness
|
|
141
141
|
*/
|
|
142
142
|
copies: RollCopy[];
|
|
143
|
+
/**
|
|
144
|
+
* The copy whose millimetres are the edition's axis, by id: every
|
|
145
|
+
* place the edition gives along the roll is a place on this copy's
|
|
146
|
+
* paper, counted from where its scan begins, and every other copy is
|
|
147
|
+
* aligned onto it. Choosing it is the one editorial decision the
|
|
148
|
+
* alignment leaves; a copy scanned whole and without tears serves
|
|
149
|
+
* best, since its places are what notes and readers cite. Left out,
|
|
150
|
+
* it is the first copy with features that is not aligned
|
|
151
|
+
* (`referenceCopyOf`).
|
|
152
|
+
* Not exported to RDF.
|
|
153
|
+
*/
|
|
154
|
+
referenceCopy?: string;
|
|
143
155
|
/**
|
|
144
156
|
* The different versions of the roll on which
|
|
145
157
|
* this edition is based.
|
|
@@ -154,3 +166,10 @@ export interface Edition {
|
|
|
154
166
|
tempoAdjustment?: ObjectAssumption<RollTempo>;
|
|
155
167
|
}
|
|
156
168
|
export type EditionMetadata = Pick<Edition, 'base' | 'title' | 'license' | 'creation' | 'roll'>;
|
|
169
|
+
/**
|
|
170
|
+
* The copy whose millimetres are the edition's axis: the one it names,
|
|
171
|
+
* or where it names none or one it no longer has, the first copy with
|
|
172
|
+
* features that is not aligned, which is how an edition stood before it
|
|
173
|
+
* named one.
|
|
174
|
+
*/
|
|
175
|
+
export declare const referenceCopyOf: (edition: Pick<Edition, 'copies' | 'referenceCopy'>) => RollCopy | undefined;
|
package/lib/model/Edition.js
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { featuresOf } from "./RollCopy.js";
|
|
2
|
+
/**
|
|
3
|
+
* The copy whose millimetres are the edition's axis: the one it names,
|
|
4
|
+
* or where it names none or one it no longer has, the first copy with
|
|
5
|
+
* features that is not aligned, which is how an edition stood before it
|
|
6
|
+
* named one.
|
|
7
|
+
*/
|
|
8
|
+
export const referenceCopyOf = (edition) => edition.copies.find(copy => copy.id === edition.referenceCopy)
|
|
9
|
+
?? edition.copies.find(copy => !copy.measurements.alignment && featuresOf(copy).length > 0);
|
package/lib/model/Quantity.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ declare const unit: unique symbol;
|
|
|
11
11
|
* The units a record may state. A `Measure` is limited to these, since
|
|
12
12
|
* they are what the `unit` field is allowed to say.
|
|
13
13
|
*/
|
|
14
|
-
export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min' | 'px/in' | 'deg';
|
|
14
|
+
export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min' | 'px/in' | 'deg' | 'percent';
|
|
15
15
|
export type Quantity<U extends string> = number & {
|
|
16
16
|
readonly [unit]: U;
|
|
17
17
|
};
|
|
@@ -30,6 +30,8 @@ export type MetersPerMinute = Quantity<'m/min'>;
|
|
|
30
30
|
export type Resolution = Quantity<'px/in'>;
|
|
31
31
|
/** An angle on the roll, turning from the line across the paper towards the end of the roll. */
|
|
32
32
|
export type Degrees = Quantity<'deg'>;
|
|
33
|
+
/** A share of a length, such as how far the paper has stretched: dimensionless, in hundredths. */
|
|
34
|
+
export type Percent = Quantity<'percent'>;
|
|
33
35
|
/** Names a number in a unit. Partially apply it to make a constructor. */
|
|
34
36
|
export declare const quantity: <U extends string>(value: number) => Quantity<U>;
|
|
35
37
|
export declare const mm: (value: number) => Quantity<"mm">;
|
|
@@ -42,6 +44,7 @@ export declare const feetPerMinute: (value: number) => Quantity<"ft/min">;
|
|
|
42
44
|
export declare const metersPerMinute: (value: number) => Quantity<"m/min">;
|
|
43
45
|
export declare const pixelsPerInch: (value: number) => Quantity<"px/in">;
|
|
44
46
|
export declare const degrees: (value: number) => Quantity<"deg">;
|
|
47
|
+
export declare const percent: (value: number) => Quantity<"percent">;
|
|
45
48
|
/**
|
|
46
49
|
* A value together with the unit it was measured in, as a record
|
|
47
50
|
* states it.
|
package/lib/model/Quantity.js
CHANGED
|
@@ -10,6 +10,7 @@ export const feetPerMinute = quantity;
|
|
|
10
10
|
export const metersPerMinute = quantity;
|
|
11
11
|
export const pixelsPerInch = quantity;
|
|
12
12
|
export const degrees = quantity;
|
|
13
|
+
export const percent = quantity;
|
|
13
14
|
// The second operand takes its unit from the first. Inferred from both,
|
|
14
15
|
// TypeScript would unite two units rather than reject them.
|
|
15
16
|
export const add = (a, b) => quantity(a + b);
|