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.
@@ -2,26 +2,52 @@ import { ConditionState } from "./ConditionState.js";
2
2
  import { AnySymbol } from "./Symbol.js";
3
3
  import { TrackerBar } from "../systems/TrackerBar.js";
4
4
  import { TrackCalibration } from "./TrackCalibration.js";
5
- import { AnyFeature, FeatureOrPatch, Patch } from "./Feature.js";
5
+ import { AnyFeature, FeatureOrPatch, HorizontalSpan, Patch } from "./Feature.js";
6
6
  import { ActorAssignment, DateAssignment, ObjectAssumption, ReferenceAssumption } from "./Assumption.js";
7
7
  import { WithId, WithType } from "../shared/utils.js";
8
8
  import { Agent, Concept } from "./Agent.js";
9
9
  import { FeatureSource } from "./FeatureSource.js";
10
- import { Measure, Millimeters, Quantity, Track } from "./Quantity.js";
10
+ import { Measure, Millimeters, Percent, Quantity, Track } from "./Quantity.js";
11
11
  import { Perforator } from "./Perforator.js";
12
12
  /**
13
- * This condition state is used to describe the roll's
14
- * paper shrinkage or stretching. It might be calculated
15
- * on the basis of comparing the vertical or horizontal
16
- * extent with other witnesses of the same roll.
13
+ * How far the paper has stretched, or shrunk where it is negative, since
14
+ * it was perforated, as a share of its length then: the strain
15
+ * ΔL / L. It is a ratio of two lengths and so has no unit of its own,
16
+ * and it is stated in per cent because what paper does is a matter of
17
+ * tenths of one. It is taken in one direction, along the roll or across
18
+ * it, since machine-made paper does not stretch the same way in both.
19
+ * @see crm:E54 Dimension
20
+ */
21
+ export interface Strain extends Measure<'percent'> {
22
+ /**
23
+ * The standard uncertainty of the value, one standard deviation.
24
+ * Left out where the measurement gave none, which leaves it no more
25
+ * precise than paper is known to vary.
26
+ * @see reo:uncertainty
27
+ */
28
+ uncertainty?: Percent;
29
+ }
30
+ /**
31
+ * The paper of the copy stretched or shrunk, as measured on the copy:
32
+ * with a rule against a length the perforator fixed, or across the roll
33
+ * against the pitch of the punch block. What the alignments of the
34
+ * copies say about the paper is worked out from them (`paperOf`) and not
35
+ * stated here, so that a condition stands only where something was
36
+ * measured. A strain along the roll goes into that working out.
37
+ * @see crm:E3 Condition State
17
38
  */
18
39
  export interface PaperStretch extends ConditionState<'paper-stretch'> {
19
40
  /**
20
- * The stretch factor, e.g. 1.02 means the paper has
21
- * stretched by 2% compared to its original dimensions.
22
- * @see rdf:value
41
+ * The strain along the roll, the direction the alignment measures.
42
+ * @see reo:strainAlong
43
+ */
44
+ along?: Strain;
45
+ /**
46
+ * The strain across the roll. Paper stretches more across than
47
+ * along, so this is no measure of the other.
48
+ * @see reo:strainAcross
23
49
  */
24
- factor: number;
50
+ across?: Strain;
25
51
  }
26
52
  /**
27
53
  * A general condition description for a roll copy, e.g.
@@ -31,20 +57,53 @@ export interface PaperStretch extends ConditionState<'paper-stretch'> {
31
57
  export interface GeneralRollCondition extends ConditionState<'general'> {
32
58
  }
33
59
  /**
34
- * An assignment of a condition (general or paper-stretch)
60
+ * The paper of the copy torn at one place, most often a tear running in
61
+ * from one of its edges. Each tear is a condition of its own, so that
62
+ * each can carry the belief it rests on and serve as a premise. A tear
63
+ * that runs into a chain of holes damages the chain as well, which is
64
+ * then stated of the chain as `partially-torn`.
65
+ * @see crm:E3 Condition State
66
+ */
67
+ export interface Tear extends ConditionState<'torn'> {
68
+ /**
69
+ * Where along the roll the tear lies, measured as the copy's
70
+ * features are, so that it moves with them when the copy is aligned.
71
+ * @see reo:horizontal
72
+ */
73
+ horizontal: HorizontalSpan;
74
+ /**
75
+ * The edge of the paper the tear runs in from, where it runs in
76
+ * from one.
77
+ * @see reo:edge
78
+ */
79
+ edge?: 'bass' | 'treble';
80
+ /**
81
+ * How far the tear reaches into the paper, taken across the roll
82
+ * from the edge it runs in from.
83
+ * @see reo:depth
84
+ */
85
+ depth?: Measure<'mm'>;
86
+ /**
87
+ * IIIF region pointing to a depiction of the tear in the scan.
88
+ * @see crm:P138i has representation
89
+ */
90
+ depiction?: string;
91
+ }
92
+ /**
93
+ * An assignment of a condition (general, paper-stretch or a tear)
35
94
  * to a roll copy, annotatable with a belief about its certainty.
36
95
  */
37
- export type RollConditionAssignment = ObjectAssumption<GeneralRollCondition | PaperStretch>;
96
+ export type RollConditionAssignment = ObjectAssumption<GeneralRollCondition | PaperStretch | Tear>;
38
97
  /**
39
98
  * The keeper a copy is held by, as an object assumption, so that the
40
99
  * statement can carry the belief it rests on and the reasons for it.
41
100
  */
42
101
  export type KeeperAssignment = ObjectAssumption<Agent>;
43
- export declare const rollConditions: readonly ['general', 'paper-stretch'];
102
+ export declare const rollConditions: readonly ['general', 'paper-stretch', 'torn'];
44
103
  /**
45
- * A shift correction applied to a roll copy to align it
46
- * with other copies. The shift is defined as horizontal
47
- * (along the roll length, in mm) and vertical (across tracks).
104
+ * How far an alignment moves a copy's places before it scales them:
105
+ * along the roll, in the copy's own millimetres, and across it, in
106
+ * tracks.
48
107
  */
49
108
  export interface Shift {
50
109
  /** Along the roll. */
@@ -52,6 +111,56 @@ export interface Shift {
52
111
  /** Across the tracker bar. */
53
112
  vertical: Track;
54
113
  }
114
+ /**
115
+ * How the copy's own places are carried onto the edition's axis, which
116
+ * is the millimetres of its reference copy (`Edition.referenceCopy`):
117
+ *
118
+ * x_axis = (x + shift.horizontal) · scale
119
+ * track_axis = track + shift.vertical
120
+ *
121
+ * It is found, not given: `alignCopy` matches the copy's notes with the
122
+ * reference copy's and records what it found, and can find it again.
123
+ * The document holds the copy's features and tears at its own places,
124
+ * as they were read, and this beside them; `importJsonLd` puts them
125
+ * onto the axis and `asJsonLd` takes them back, so that within the
126
+ * library every place along the roll is a place on the axis.
127
+ *
128
+ * What the scale is put down to, the paper or the speed the copy was
129
+ * cut for, is worked out from all the alignments together (`paperOf`).
130
+ * Not exported to RDF.
131
+ */
132
+ export interface Alignment {
133
+ /**
134
+ * The copy it was found against, by id: the edition's reference
135
+ * copy at the time. Left out by alignments recorded before this was.
136
+ */
137
+ against?: string;
138
+ /** Applied before the scale. */
139
+ shift: Shift;
140
+ /** Own millimetres to the axis's, applied after the shift. */
141
+ scale: number;
142
+ /** How many notes of the copy found their counterpart on the reference copy. */
143
+ matched?: number;
144
+ /** How far the counterparts still lie apart, as a root mean square in millimetres of the axis. */
145
+ residual?: Millimeters;
146
+ /**
147
+ * The standard error of the scale: the residual over the spread of
148
+ * the matched notes along the roll, as for a line of least squares.
149
+ */
150
+ scaleError?: number;
151
+ /** The software that found it, and when. */
152
+ foundBy?: {
153
+ /** The name of the method. */
154
+ software: string;
155
+ /** Its revision (`ALIGNMENT_METHOD`). */
156
+ version: string;
157
+ /**
158
+ * The day it was found.
159
+ * @format date
160
+ */
161
+ date: Date;
162
+ };
163
+ }
55
164
  /** The margins on the treble and bass sides of the roll, in the unit the scan was measured in. */
56
165
  export interface Margins<U extends 'px' | 'mm'> {
57
166
  treble: Quantity<U>;
@@ -64,18 +173,6 @@ export interface Margins<U extends 'px' | 'mm'> {
64
173
  * @see crm:E54 Dimension
65
174
  */
66
175
  export type PaperSpeed = Measure<'ft/min'> | Measure<'m/min'>;
67
- /**
68
- * What the scale an alignment found is put down to: the paper of the
69
- * copy having stretched or shrunk, or the copy having been cut for
70
- * another paper speed than the roll it is aligned with.
71
- */
72
- export type ScaleReading = {
73
- cause: 'paper';
74
- condition: ObjectAssumption<PaperStretch>;
75
- } | {
76
- cause: 'speed';
77
- speed: ObjectAssumption<PaperSpeed>;
78
- };
79
176
  /**
80
177
  * Describes the production of a roll copy: the manufacturer,
81
178
  * the paper used, the date, and the system and paper speed the
@@ -114,8 +211,10 @@ export interface ProductionEvent {
114
211
  /**
115
212
  * The paper speed the copy was cut for. A copy cut from the same
116
213
  * master for another speed comes out longer or shorter than the
117
- * roll it is aligned with by the ratio of the speeds, which is
118
- * what the alignment then measures.
214
+ * roll it is aligned with by the ratio of the speeds. The alignment
215
+ * measures that ratio far more closely than a label states a speed,
216
+ * so the speed is held against what the alignments give
217
+ * (`alignmentProblems`) rather than used to find it.
119
218
  * @see reo:paperSpeed
120
219
  */
121
220
  speed?: ObjectAssumption<PaperSpeed>;
@@ -209,8 +308,8 @@ export type Modification = Partial<{
209
308
  export interface RollCopy extends WithType<'RollCopy'>, WithId {
210
309
  /**
211
310
  * Physical measurements of this roll copy, including
212
- * dimensions, hole separation, margins, shift corrections,
213
- * and information about the measuring software. What the
311
+ * dimensions, hole separation, margins, the alignment onto the
312
+ * edition's axis, and information about the measuring software. What the
214
313
  * perforations tell about the machine that cut them is stated
215
314
  * with the production.
216
315
  * @see crm:P39i was measured by
@@ -252,17 +351,12 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
252
351
  */
253
352
  margins: Margins<'px'> | Margins<'mm'>;
254
353
  /**
255
- * The shift applied to align this copy with the others.
354
+ * How the copy's places are carried onto the edition's axis.
355
+ * Left out for the reference copy, whose places are the axis,
356
+ * and for a copy not aligned yet.
256
357
  * Not exported to RDF.
257
358
  */
258
- shift: Shift;
259
- /**
260
- * The factor this copy's features were scaled by to align them
261
- * with the others. What it is put down to is stated apart: a
262
- * paper-stretch condition, or the speed the copy was cut for.
263
- * Not exported to RDF.
264
- */
265
- scale: number;
359
+ alignment: Alignment;
266
360
  /**
267
361
  * What a pneumatic reader added to this copy's chains of holes,
268
362
  * where it was taken off again. Such a reader reports how long a
@@ -332,7 +426,7 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
332
426
  production?: ProductionEvent;
333
427
  /**
334
428
  * Condition assessments of this roll copy (e.g. paper stretch,
335
- * general wear). Each condition is an assumption annotatable
429
+ * general wear, a tear). Each condition is an assumption annotatable
336
430
  * with a belief.
337
431
  * @see crm:P44 has condition
338
432
  */
@@ -402,12 +496,14 @@ export declare const statesNothing: (modification: Modification) => boolean;
402
496
  export declare const featuresByAct: (copy: Pick<RollCopy, 'production' | 'modifications'>) => FeatureOrPatch[][];
403
497
  /** Every feature the copy states at a place of its own, whichever act made it. */
404
498
  export declare const featuresOf: (copy: Pick<RollCopy, 'production' | 'modifications'>) => FeatureOrPatch[];
405
- /**
406
- * Whether the condition is the stretch or shrinkage of the paper. A
407
- * scale put down to this belongs to the one exemplar and says nothing
408
- * about the speed its system's rolls were cut at.
409
- */
410
- export declare const isPaperStretch: (condition: RollConditionAssignment) => boolean;
499
+ /** Whether the condition is the stretch or shrinkage of the paper, as measured on the copy. */
500
+ export declare const isPaperStretch: (condition: RollConditionAssignment) => condition is ObjectAssumption<PaperStretch>;
501
+ /** What the copy's paper was measured to have done, where anything was measured. */
502
+ export declare const paperStretchOf: (copy: Pick<RollCopy, 'conditions'>) => ObjectAssumption<PaperStretch> | undefined;
503
+ /** Whether the condition is a tear in the paper. */
504
+ export declare const isTear: (condition: RollConditionAssignment) => condition is ObjectAssumption<Tear>;
505
+ /** The tears the copy is stated to have. */
506
+ export declare const tearsOf: (copy: Pick<RollCopy, 'conditions'>) => ObjectAssumption<Tear>[];
411
507
  /**
412
508
  * The bar a copy is read by, which is the one it was cut for. A copy
413
509
  * that names no system falls back to `defaultTrackerBar`.
@@ -4,7 +4,8 @@ import { assignReference, certaintyOf, isAsserted } from "./Assumption.js";
4
4
  import { px, track } from "./Quantity.js";
5
5
  export const rollConditions = [
6
6
  'general',
7
- 'paper-stretch'
7
+ 'paper-stretch',
8
+ 'torn'
8
9
  ];
9
10
  export const modificationPurposes = [
10
11
  'musical-improvement',
@@ -34,12 +35,14 @@ export const statesNothing = (modification) => modification.type === 'Removal' ?
34
35
  export const featuresByAct = (copy) => [copy.production?.produced ?? [], ...copy.modifications.map(featuresMadeBy)];
35
36
  /** Every feature the copy states at a place of its own, whichever act made it. */
36
37
  export const featuresOf = (copy) => featuresByAct(copy).flat();
37
- /**
38
- * Whether the condition is the stretch or shrinkage of the paper. A
39
- * scale put down to this belongs to the one exemplar and says nothing
40
- * about the speed its system's rolls were cut at.
41
- */
38
+ /** Whether the condition is the stretch or shrinkage of the paper, as measured on the copy. */
42
39
  export const isPaperStretch = (condition) => condition.conditionType === 'paper-stretch';
40
+ /** What the copy's paper was measured to have done, where anything was measured. */
41
+ export const paperStretchOf = (copy) => copy.conditions.find(isPaperStretch);
42
+ /** Whether the condition is a tear in the paper. */
43
+ export const isTear = (condition) => condition.conditionType === 'torn';
44
+ /** The tears the copy is stated to have. */
45
+ export const tearsOf = (copy) => copy.conditions.filter(isTear);
43
46
  /**
44
47
  * The bar a copy is read by, which is the one it was cut for. A copy
45
48
  * that names no system falls back to `defaultTrackerBar`.
@@ -2,7 +2,7 @@
2
2
  import { Draft } from "immer";
3
3
  import { Edition } from "../model/Edition.js";
4
4
  import { AnySymbol } from "../model/Symbol.js";
5
- import { GeneralRollCondition, RollCopy, ScaleReading, Shift } from "../model/RollCopy.js";
5
+ import { Alignment, GeneralRollCondition, PaperStretch, RollCopy, Tear } from "../model/RollCopy.js";
6
6
  import { FeatureSource } from "../model/FeatureSource.js";
7
7
  import { Belief, ObjectAssumption, ReferenceAssumption } from "../model/Assumption.js";
8
8
  import { Millimeters } from "../model/Quantity.js";
@@ -21,15 +21,38 @@ export declare const createVersion: (copy: RollCopy) => EditionOp;
21
21
  */
22
22
  export declare const addCopy: (copy: RollCopy) => EditionOp;
23
23
  /**
24
- * Shifts and then scales the copy's features into line with another
25
- * copy's, and puts the scale down to what the reading says: the paper,
26
- * or the speed the copy was cut for.
24
+ * What aligning the copy with the reference copy finds, or nothing
25
+ * where the two share no run of notes, where there is no reference
26
+ * copy, or where the copy is the reference itself. The copy is read at
27
+ * its own places whatever alignment it has, and through its own bar,
28
+ * which is what lets a copy cut for another system be aligned at all.
27
29
  */
28
- export declare const alignCopy: (copyId: string, shift: Shift, scale: number, reading?: ScaleReading) => EditionOp;
30
+ export declare const alignmentFor: (edition: Edition, copy: RollCopy, date?: Date) => Alignment | undefined;
29
31
  /**
30
- * Puts the copy's features back where they were measured. A paper
31
- * stretch read off the alignment goes with it; a speed stated stays,
32
- * being a fact about the copy.
32
+ * Aligns the copy with the edition's reference copy: finds the shift
33
+ * and scale that carry its notes onto the reference copy's, and puts
34
+ * its features and tears onto the axis by them, in place of any
35
+ * alignment it had. A copy that shares no run of notes with the
36
+ * reference copy is left as it was.
37
+ */
38
+ export declare const alignCopy: (copyId: string, date?: Date) => EditionOp;
39
+ /**
40
+ * Aligns every copy that has features with the reference copy, as
41
+ * `alignCopy` does one. The reference copy itself is taken off any
42
+ * alignment it had, its places being the axis.
43
+ */
44
+ export declare const alignCopies: (date?: Date) => EditionOp;
45
+ /**
46
+ * Makes the copy the one whose millimetres are the edition's axis, and
47
+ * aligns every other copy onto it. Every place the edition gives along
48
+ * the roll moves with this, which is why it is a choice and not
49
+ * something worked out.
50
+ */
51
+ export declare const chooseReferenceCopy: (copyId: string, date?: Date) => EditionOp;
52
+ /**
53
+ * Puts the copy's features back where they were measured, off the
54
+ * axis. What the alignments say about the paper goes with it, since
55
+ * that is worked out from them.
33
56
  */
34
57
  export declare const unalignCopy: (copyId: string) => EditionOp;
35
58
  /**
@@ -60,10 +83,24 @@ export declare const stateCarriage: (copyId: string, versionId: string, belief?:
60
83
  export declare const clearCarriage: (copyId: string, versionId: string) => EditionOp;
61
84
  /**
62
85
  * Adds a general condition to the copy, beside whatever is stated of it
63
- * already. The other condition a copy may be in, a paper stretch, is
64
- * read off an alignment and stated by `alignCopy`.
86
+ * already. A paper stretch is added the same way, where one was
87
+ * measured; what the alignments say of the paper is worked out from
88
+ * them and stated nowhere.
65
89
  */
66
90
  export declare const addGeneralCondition: (copyId: string, condition: ObjectAssumption<GeneralRollCondition>) => EditionOp;
91
+ /**
92
+ * States what the copy's paper was measured to have done, in place of
93
+ * what was stated before; given nothing, it takes the statement away. A
94
+ * copy has one paper, so it has one such condition, which may give a
95
+ * strain in either direction.
96
+ */
97
+ export declare const statePaperStretch: (copyId: string, stretch?: ObjectAssumption<PaperStretch>) => EditionOp;
98
+ /**
99
+ * Adds a tear to the copy, beside whatever is stated of it already. The
100
+ * tear is given on the axis, where the copy's features stand, and moves
101
+ * with them from then on.
102
+ */
103
+ export declare const addTear: (copyId: string, tear: ObjectAssumption<Tear>) => EditionOp;
67
104
  /** The symbols of the versions that no other copy carries. */
68
105
  export declare const symbolsCarriedOnlyBy: (edition: Edition, copyId: string) => AnySymbol[];
69
106
  /**
package/lib/ops/copies.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { v4 } from "uuid";
2
+ import { referenceCopyOf } from "../model/Edition.js";
2
3
  import { asSymbols, barOf, featuresOf, isPaperStretch } from "../model/RollCopy.js";
3
4
  import { systemOf } from "../systems/TrackerBar.js";
4
- import { applyShift, applyScale, revertShift, revertScale, revertShortening, shortenChains } from "../collation/alignment.js";
5
+ import { alignFeatures, ALIGNMENT_METHOD, applyAlignment, ownFeaturesOf, revertAlignment, revertShortening, shortenChains } from "../collation/alignment.js";
5
6
  import { idOf } from "../model/Assumption.js";
7
+ import { track } from "../model/Quantity.js";
6
8
  import { onCopy, stateOf, insertion, insertedIn, referenceHeld, withoutReferences } from "./draft.js";
7
9
  import { without } from "./immutable.js";
8
10
  import { featureIdsOf, carriedOnlyOn, forgetFeatures } from "./forget.js";
@@ -30,36 +32,80 @@ export const createVersion = (copy) => draft => {
30
32
  export const addCopy = (copy) => draft => {
31
33
  draft.copies.push(copy);
32
34
  };
33
- /** States what the scale is put down to, in place of an earlier reading. */
34
- const readScale = (copy, reading) => {
35
- if (reading.cause === 'paper') {
36
- copy.conditions = [...copy.conditions.filter(condition => !isPaperStretch(condition)), reading.condition];
35
+ /**
36
+ * What aligning the copy with the reference copy finds, or nothing
37
+ * where the two share no run of notes, where there is no reference
38
+ * copy, or where the copy is the reference itself. The copy is read at
39
+ * its own places whatever alignment it has, and through its own bar,
40
+ * which is what lets a copy cut for another system be aligned at all.
41
+ */
42
+ export const alignmentFor = (edition, copy, date = new Date()) => {
43
+ const reference = referenceCopyOf(edition);
44
+ if (!reference || reference.id === copy.id)
45
+ return undefined;
46
+ const found = alignFeatures(ownFeaturesOf(copy), featuresOf(reference), barOf(copy), barOf(reference));
47
+ if (!found)
48
+ return undefined;
49
+ return {
50
+ against: reference.id,
51
+ shift: { horizontal: found.shift, vertical: track(0) },
52
+ scale: found.scale,
53
+ matched: found.matched,
54
+ residual: found.residual,
55
+ scaleError: found.scaleError,
56
+ foundBy: { ...ALIGNMENT_METHOD, date }
57
+ };
58
+ };
59
+ /**
60
+ * Aligns the copy with the edition's reference copy: finds the shift
61
+ * and scale that carry its notes onto the reference copy's, and puts
62
+ * its features and tears onto the axis by them, in place of any
63
+ * alignment it had. A copy that shares no run of notes with the
64
+ * reference copy is left as it was.
65
+ */
66
+ export const alignCopy = (copyId, date = new Date()) => draft => {
67
+ const state = stateOf(draft);
68
+ const copy = state.copies.find(c => c.id === copyId);
69
+ const alignment = copy && alignmentFor(state, copy, date);
70
+ const target = draft.copies.find(c => c.id === copyId);
71
+ if (alignment && target)
72
+ applyAlignment(alignment, target);
73
+ };
74
+ /**
75
+ * Aligns every copy that has features with the reference copy, as
76
+ * `alignCopy` does one. The reference copy itself is taken off any
77
+ * alignment it had, its places being the axis.
78
+ */
79
+ export const alignCopies = (date = new Date()) => draft => {
80
+ const reference = referenceCopyOf(stateOf(draft));
81
+ if (!reference)
37
82
  return;
38
- }
39
- if (!copy.production)
40
- copy.production = {};
41
- copy.production.speed = reading.speed;
83
+ const own = draft.copies.find(c => c.id === reference.id);
84
+ if (own)
85
+ revertAlignment(own);
86
+ stateOf(draft).copies
87
+ .filter(copy => copy.id !== reference.id && featuresOf(copy).length > 0)
88
+ .forEach(copy => alignCopy(copy.id, date)(draft));
42
89
  };
43
90
  /**
44
- * Shifts and then scales the copy's features into line with another
45
- * copy's, and puts the scale down to what the reading says: the paper,
46
- * or the speed the copy was cut for.
91
+ * Makes the copy the one whose millimetres are the edition's axis, and
92
+ * aligns every other copy onto it. Every place the edition gives along
93
+ * the roll moves with this, which is why it is a choice and not
94
+ * something worked out.
47
95
  */
48
- export const alignCopy = (copyId, shift, scale, reading) => onCopy(copyId, copy => {
49
- applyShift(shift, copy);
50
- applyScale(scale, copy);
51
- if (reading)
52
- readScale(copy, reading);
53
- });
96
+ export const chooseReferenceCopy = (copyId, date = new Date()) => draft => {
97
+ if (!draft.copies.some(copy => copy.id === copyId))
98
+ return;
99
+ draft.referenceCopy = copyId;
100
+ alignCopies(date)(draft);
101
+ };
54
102
  /**
55
- * Puts the copy's features back where they were measured. A paper
56
- * stretch read off the alignment goes with it; a speed stated stays,
57
- * being a fact about the copy.
103
+ * Puts the copy's features back where they were measured, off the
104
+ * axis. What the alignments say about the paper goes with it, since
105
+ * that is worked out from them.
58
106
  */
59
107
  export const unalignCopy = (copyId) => onCopy(copyId, copy => {
60
- revertScale(copy);
61
- revertShift(copy);
62
- copy.conditions = without(copy.conditions, isPaperStretch);
108
+ revertAlignment(copy);
63
109
  });
64
110
  /**
65
111
  * Takes the extension a pneumatic reader adds off the ends of the
@@ -113,12 +159,30 @@ export const stateCarriage = (copyId, versionId, belief) => onCopy(copyId, copy
113
159
  export const clearCarriage = (copyId, versionId) => onCopy(copyId, copy => dropStatements(copy, statement => idOf(statement) === versionId));
114
160
  /**
115
161
  * Adds a general condition to the copy, beside whatever is stated of it
116
- * already. The other condition a copy may be in, a paper stretch, is
117
- * read off an alignment and stated by `alignCopy`.
162
+ * already. A paper stretch is added the same way, where one was
163
+ * measured; what the alignments say of the paper is worked out from
164
+ * them and stated nowhere.
118
165
  */
119
166
  export const addGeneralCondition = (copyId, condition) => onCopy(copyId, copy => {
120
167
  copy.conditions.push(condition);
121
168
  });
169
+ /**
170
+ * States what the copy's paper was measured to have done, in place of
171
+ * what was stated before; given nothing, it takes the statement away. A
172
+ * copy has one paper, so it has one such condition, which may give a
173
+ * strain in either direction.
174
+ */
175
+ export const statePaperStretch = (copyId, stretch) => onCopy(copyId, copy => {
176
+ copy.conditions = [...without(copy.conditions, isPaperStretch), ...(stretch ? [stretch] : [])];
177
+ });
178
+ /**
179
+ * Adds a tear to the copy, beside whatever is stated of it already. The
180
+ * tear is given on the axis, where the copy's features stand, and moves
181
+ * with them from then on.
182
+ */
183
+ export const addTear = (copyId, tear) => onCopy(copyId, copy => {
184
+ copy.conditions.push(tear);
185
+ });
122
186
  /** The symbols of the versions that no other copy carries. */
123
187
  export const symbolsCarriedOnlyBy = (edition, copyId) => {
124
188
  const copy = edition.copies.find(c => c.id === copyId);
@@ -132,4 +196,6 @@ export const symbolsCarriedOnlyBy = (edition, copyId) => {
132
196
  export const removeCopy = (copyId) => onCopy(copyId, (copy, draft) => {
133
197
  forgetFeatures(draft, featureIdsOf(copy));
134
198
  draft.copies = draft.copies.filter(c => c.id !== copyId);
199
+ if (draft.referenceCopy === copyId)
200
+ delete draft.referenceCopy;
135
201
  });
@@ -4,7 +4,7 @@
4
4
  * the ones the library offers.
5
5
  */
6
6
  export type { EditionOp } from "./draft.js";
7
- export { createVersion, addCopy, alignCopy, unalignCopy, shortenCopy, unshortenCopy, stateSource, clearSource, nameCopy, stateCarriage, clearCarriage, addGeneralCondition, symbolsCarriedOnlyBy, removeCopy } from "./copies.js";
7
+ export { createVersion, addCopy, alignmentFor, alignCopy, alignCopies, chooseReferenceCopy, unalignCopy, shortenCopy, unshortenCopy, stateSource, clearSource, nameCopy, stateCarriage, clearCarriage, addGeneralCondition, statePaperStretch, addTear, symbolsCarriedOnlyBy, removeCopy } from "./copies.js";
8
8
  export { stateFeatureCondition, addFeature, addBorneFeature, removeFeatures, mergeObstacle, mergeObstacleIn, mergeFeatures, type FeatureAct, type MergeObstacle } from "./features.js";
9
9
  export { detachVersion, removeVersion, removeSymbols, deriveVersion, stateDerivation, clearDerivation } from "./versions.js";
10
10
  export { connectVersions, collateSymbols, separateReadings } from "./collation.js";
package/lib/ops/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { createVersion, addCopy, alignCopy, unalignCopy, shortenCopy, unshortenCopy, stateSource, clearSource, nameCopy, stateCarriage, clearCarriage, addGeneralCondition, symbolsCarriedOnlyBy, removeCopy } from "./copies.js";
1
+ export { createVersion, addCopy, alignmentFor, alignCopy, alignCopies, chooseReferenceCopy, unalignCopy, shortenCopy, unshortenCopy, stateSource, clearSource, nameCopy, stateCarriage, clearCarriage, addGeneralCondition, statePaperStretch, addTear, symbolsCarriedOnlyBy, removeCopy } from "./copies.js";
2
2
  export { stateFeatureCondition, addFeature, addBorneFeature, removeFeatures, mergeObstacle, mergeObstacleIn, mergeFeatures } from "./features.js";
3
3
  export { detachVersion, removeVersion, removeSymbols, deriveVersion, stateDerivation, clearDerivation } from "./versions.js";
4
4
  export { connectVersions, collateSymbols, separateReadings } from "./collation.js";
@@ -96,6 +96,33 @@ const stanfordScan = (druid) => ({
96
96
  scan: `https://stacks.stanford.edu/image/iiif/${druid}%2F${druid}_0001/`,
97
97
  depictionOf: (column, row, width, height) => `https://stacks.stanford.edu/image/iiif/${druid}/${druid}_0001/${column},${row},${width},${height}/128,/270/default.jpg`
98
98
  });
99
+ /**
100
+ * The edge tears the analysis found, one condition each. The analysis
101
+ * lists only those that reach deeper into the paper than a tenth of an
102
+ * inch, and measures each by the box around it, so the tear runs across
103
+ * the roll as far as the box is wide.
104
+ */
105
+ const tearsIn = (tears, dpi, depictionOf) => {
106
+ const onEdge = (edge, listed) => listOf(listed).map((tear) => {
107
+ const row = readPx(tear.ORIGIN_ROW);
108
+ const length = readPx(tear.WIDTH_ROW);
109
+ const column = readPx(tear.ORIGIN_COL);
110
+ const depth = readPx(tear.WIDTH_COL);
111
+ return {
112
+ conditionType: 'torn',
113
+ horizontal: {
114
+ unit: 'mm',
115
+ from: inMillimeters(row, dpi),
116
+ to: inMillimeters(px(row + length), dpi)
117
+ },
118
+ edge,
119
+ depth: { value: inMillimeters(depth, dpi), unit: 'mm' },
120
+ ...(depictionOf && { depiction: depictionOf(column, row, depth, length) })
121
+ };
122
+ });
123
+ return [...onEdge('bass', tears?.BASS_TEARS?.TEAR), ...onEdge('treble', tears?.TREBLE_TEARS?.TEAR)]
124
+ .sort((a, b) => a.horizontal.from - b.horizontal.from);
125
+ };
99
126
  /**
100
127
  * The software behind an analysis and when it was run, as the
101
128
  * analysis file states them.
@@ -155,7 +182,7 @@ export function readFromStanfordAton(atonString, { trackShift, system = welteT10
155
182
  return {
156
183
  type: 'RollCopy',
157
184
  id: v4(),
158
- conditions: [],
185
+ conditions: tearsIn(json.ROLLINFO.TEARS, dpi, stanford?.depictionOf),
159
186
  production: {
160
187
  system: systemOf(system),
161
188
  ...(punchDiameter !== undefined && {