@altnovatrail/canvas 0.0.0-stage → 0.1.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/LICENSE +201 -0
- package/README.md +59 -2
- package/lib/core/boxes.d.ts +54 -0
- package/lib/core/boxes.js +116 -0
- package/lib/core/clock.d.ts +25 -0
- package/lib/core/clock.js +39 -0
- package/lib/core/crop.d.ts +87 -0
- package/lib/core/crop.js +227 -0
- package/lib/core/eraser.d.ts +42 -0
- package/lib/core/eraser.js +127 -0
- package/lib/core/example.d.ts +66 -0
- package/lib/core/example.js +116 -0
- package/lib/core/fields.d.ts +29 -0
- package/lib/core/fields.js +78 -0
- package/lib/core/geometry.d.ts +38 -0
- package/lib/core/geometry.js +107 -0
- package/lib/core/grid.d.ts +30 -0
- package/lib/core/grid.js +47 -0
- package/lib/core/groups.d.ts +55 -0
- package/lib/core/groups.js +172 -0
- package/lib/core/hands.d.ts +120 -0
- package/lib/core/hands.js +273 -0
- package/lib/core/history.d.ts +42 -0
- package/lib/core/history.js +228 -0
- package/lib/core/index.d.ts +12 -0
- package/lib/core/index.js +15 -0
- package/lib/core/ink.d.ts +1 -0
- package/lib/core/ink.js +5 -0
- package/lib/core/manifest.d.ts +22 -0
- package/lib/core/manifest.js +50 -0
- package/lib/core/marks.d.ts +145 -0
- package/lib/core/marks.js +454 -0
- package/lib/core/outline.d.ts +22 -0
- package/lib/core/outline.js +50 -0
- package/lib/core/page.d.ts +62 -0
- package/lib/core/page.js +309 -0
- package/lib/core/png.d.ts +13 -0
- package/lib/core/png.js +231 -0
- package/lib/core/raster.d.ts +14 -0
- package/lib/core/raster.js +72 -0
- package/lib/core/recorder.d.ts +73 -0
- package/lib/core/recorder.js +127 -0
- package/lib/core/screen.d.ts +35 -0
- package/lib/core/screen.js +36 -0
- package/lib/core/sets.d.ts +10 -0
- package/lib/core/sets.js +31 -0
- package/lib/core/sheet.d.ts +9 -0
- package/lib/core/sheet.js +6 -0
- package/lib/core/submission.d.ts +41 -0
- package/lib/core/submission.js +63 -0
- package/lib/core/task.d.ts +214 -0
- package/lib/core/task.js +343 -0
- package/lib/core/widths.d.ts +57 -0
- package/lib/core/widths.js +218 -0
- package/lib/core/work.d.ts +77 -0
- package/lib/core/work.js +293 -0
- package/lib/element/attributes.d.ts +11 -0
- package/lib/element/attributes.js +22 -0
- package/lib/element/index.d.ts +219 -0
- package/lib/element/index.js +1898 -0
- package/lib/element/labels.d.ts +41 -0
- package/lib/element/labels.js +74 -0
- package/lib/element/parts.d.ts +63 -0
- package/lib/element/parts.js +191 -0
- package/lib/element/pictures.d.ts +45 -0
- package/lib/element/pictures.js +167 -0
- package/lib/element/probe.d.ts +33 -0
- package/lib/element/probe.js +18 -0
- package/lib/element/session.d.ts +3 -0
- package/lib/element/session.js +7 -0
- package/lib/element/statement.d.ts +35 -0
- package/lib/element/statement.js +116 -0
- package/lib/element/tools.d.ts +64 -0
- package/lib/element/tools.js +254 -0
- package/package.json +41 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# canvas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/altnovatrail/canvas/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/altnovatrail/canvas/actions/workflows/release.yml)
|
|
5
|
+
[](https://github.com/altnovatrail/canvas/actions/workflows/codeql.yml)
|
|
6
|
+
[](https://github.com/altnovatrail/canvas/releases/latest)
|
|
7
|
+
[](package.json)
|
|
8
|
+
[](https://codecov.io/gh/altnovatrail/canvas)
|
|
9
|
+
[](https://scorecard.dev/viewer/?uri=github.com/altnovatrail/canvas)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
[](https://sonarcloud.io/summary/new_code?id=altnovatrail_canvas)
|
|
13
|
+
[](https://sonarcloud.io/summary/new_code?id=altnovatrail_canvas)
|
|
14
|
+
[](https://sonarcloud.io/summary/new_code?id=altnovatrail_canvas)
|
|
15
|
+
[](https://sonarcloud.io/summary/new_code?id=altnovatrail_canvas)
|
|
16
|
+
[](https://sonarcloud.io/summary/new_code?id=altnovatrail_canvas)
|
|
17
|
+
|
|
18
|
+
The NovaTrail canvases. A canvas is the workplace of a task, where the learner writes, draws and types; this UI package holds the canvas kinds — for now two, the handwriting sheet and the workbook — with their layers and tools, captures the learner's strokes with time and pressure, draws the picture of the work that goes to be checked and shows the tutor's marks tied to the learner's strokes, and the app builds it in at an exact version. A separate build of it is the samples page, where adult volunteers make samples for eval on the same canvas that learners use: a sample is saved as a file on the volunteer's device and reaches a module's eval set by pull request, and nothing goes to the platform's server.
|
|
19
|
+
|
|
20
|
+
A component of [NovaTrail](https://github.com/altnovatrail/spec/tree/v0.6.2), a universal open platform for adaptive learning, interactive lessons and AI tutoring. It belongs to stage 2, First slice with learners, with tutor, api, app, skill-map and infra; with tutor it is the stage's first code: first the handwriting sheet, while the samples page waits until a measurement needs volunteers' or children's samples.
|
|
21
|
+
|
|
22
|
+
## Status
|
|
23
|
+
|
|
24
|
+
Code started. The detailing is done — epic E1, archived in [docs/plan/E1.md](docs/plan/E1.md). Epic E2, replanned for the MVP on 2026-10-22, built the handwriting sheet as the element `<novatrail-canvas>`, for tutor's check board to take in place of its own sheet; the board has decided to keep its own. Its last task was the author's try of the sheet by hand on the demo page, which found nothing to fix. E2 is done and archived in [docs/plan/E2.md](docs/plan/E2.md). The text canvas, epic E3, was dropped on 2026-10-25: canvas builds no text canvas. Epic E4, taken the same day, built the sheet for the product: the eraser and the history of writing, the layers and the grid, the groups of strokes and the crop for checking, the tutor's marks tied to the strokes, the package's full interface and the input on tablets. It was done on 2026-10-26 and is archived in [docs/plan/E4.md](docs/plan/E4.md). Epic E6, taken the same day, detailed and built the workbook, the first slice's second canvas kind, for school maths in grade 3, and was done on 2026-10-27; it is archived in [docs/plan/E6.md](docs/plan/E6.md). Epic E7, taken the same day, took what spec v0.2.42 brought: the task's new open fields, the exit condition that every answer slot holds something — `filled` on a sheet too — and the lesson's worked example, shown on a sheet the learner does not write on. On the way canvas moved to spec v0.4.1, whose npm package gives the types of the module format. E7 was done on 2026-11-01 and is archived in [docs/plan/E7.md](docs/plan/E7.md). Epic E8, taken the same day, is the first release of the package, which spec's cross-repository plan asks for before the app's code; the tests' coverage and the release's build come first. Then canvas moved to spec v0.6.2, of spec's 0.6 line, and its CI now checks the signature of each package of `@altnovatrail` in its lock file, as spec's rules ask; then the element took the module format 0.2 and the types of its contract with the app from spec's npm package, and the package came out in its first release, v0.1.0. The samples page is epic E5 for later; it waits until a measurement needs volunteers' or children's samples. So far there is the package, `@altnovatrail/canvas`, with its tools and the sheet's input: the element takes a pen, a finger or a mouse, one pointer at a time, and draws the strokes, and its methods `undo()` and `clear()` undo the last action and clear the sheet. Its eraser, E4's first task, takes strokes off whole along the path of a press: the element's `tool` switches to it, and a pen's eraser erases whatever the tool, if the task allows the eraser. The sheet keeps the history of writing — every stroke, erasing, clearing and undo, at the sheet's time — for the app to keep. The sheet is a stack of layers, in the order of spec's canvas layers: its task, the element's `task` property, rules it in squares, lines or dots, and the task's statement stands above the sheet, its pictures from the consumer's `media`. The task and the module's manifest come in the types of spec's npm package, `@altnovatrail/spec`; the element reads the module format 0.2, which spec v0.6.0 brought — a manifest of another version is a TypeError — and the task is checked as its schema checks it, a field it does not know a TypeError; what the statement gives and asks, the fields `givens` and `unknown`, the element takes and leaves for the app to show; a workbook's task the element shows too: its lines laid out on a page the size of the sheet — numbers, text fitted to the layout's own widths, and boxes, up to ten rows — which a screen reader reads as a list. On a workbook each stroke falls in one box or in none, by a rule in integers on the middle of its points' box; what falls in no box is draft, which stays on the page and goes to no check, and the property `filled` tells which boxes hold writing, and on a sheet whether its one answer anywhere on it, or the whole sheet, does. The module's manifest, the `manifest` property, gives the height of a digit on the crop, 24 px unless it gives another, as without a manifest, and the language of the statement. The sheet is at most 1000 CSS pixels wide, and the consumer themes it by custom properties. The DOM-free part parts the strokes into groups by place and time — two strokes no more than 2 seconds apart whose boxes come within a cell of each other are neighbours, and a chain of neighbours is a group — labels the groups A, B, C… by place and gives each one's rectangle on the sheet, for the crop, the tutor's marks and the samples to come; the rule's test vectors are a proposal for the eval protocol. It also draws the crop for checking, with its own raster and PNG encoder: the ink of the visible strokes, scaled so that the writing comes out at the digit height the module's manifest asks for, within the size every Claude model takes as it is, with the groups' frames in its fractions. Its test vectors go to the eval protocol too. The element takes the tutor's marks of a submission back by its property `marks`, with the context it is to give with the crop: a mark holds on to the learner's strokes — a group's, or those a model's box on the crop takes, two thirds of their points within a quarter of a digit of it — and a frame stands round those of them the sheet shows, over the work, read by its name and fading while the learner writes into it. The history goes out by the property `history` and the event `historychange`, which tells of every action with the events it adds, and comes back by the same property: the same history changes nothing, and another, one the sheet could have written, replaces the sheet, undo and all. `exportWork()` gives the work for checking: the PNG of the crop and the context of its submission, which the app keeps and gives back with the tutor's marks, its digest telling the sheet's own context from another sheet's. On a workbook it gives the crop of each filled box, with its item and slot, and the context of the table of boxes; a tutor's mark there stands on an answer cell by its place and frames the box with its ink. Above the statement and the sheet, outside their scrolling, stands the element's own row of buttons — the pen and the eraser the task allows, undo and clear — a toolbar worked by pointer and by keyboard, labelled in English or Russian by the element's own `lang`; a `toolchange` event tells of the learner's choice of tool. On a tablet a pen's touch puts the whole page in pen mode, where a finger writes nothing until the row's Draw with finger lets it write again; a touch wider or taller than 100 CSS pixels is a palm and writes nothing, and two fingers scroll the sheet. The row's Up and Down scroll it the same way with a single pointer, on any device, by four fifths of what is in sight. A lesson's worked example comes by the property `workedExample`, in place of a task: its statement stands above the sheet and its grid on it, but the learner does not write there — a finger or a pen scrolls the sheet as the browser scrolls the statement, and the row, named Worked example, has Next, Demo, Up and Down. Next opens the parts of the author's demo one at a time, into a numbered list above the sheet that a polite region tells a screen reader of, and Demo hides the whole demo and shows it again; the property `demoState` and the event `demochange` tell the app how far the learner has gone, and the app may give it back to a new element after the example — in React's JSX `workedExample` comes before `demoState`, and `ondemochange` listens. The pictures of the demo come from the consumer's `media` with the statement's, one file for each reference, all of them as the example comes, so that a part opened shows its picture at once; a picture on an area of the sheet stands on the sheet itself, stretched over its area, and the sheet scrolls to it as its part opens. The element now waits for the app and the samples page; a test plays the app's part, and `just demo` serves a page to try the element on by hand. A release comes out by itself after CI passes on `main`, when a path it is built from has changed: the package's archive, LICENSE and their sums, built, signed and checked by .github's shared release, and the same archive goes to npm: the first version by hand, while the release's own npm step, through trusted publishing, is being set up. A pull request from a branch of this repository tries the release and publishes nothing.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
The app takes the package from npm at an exact version, and spec's npm package, whose types the element's contract is in, at the version the package has in its `dependencies`:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install --save-exact @altnovatrail/canvas@<version>
|
|
32
|
+
npm install --save-exact --save-dev @altnovatrail/spec@<its version>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A pull request that changes the lock file has spec's recipe check it, as NovaTrail's rules ask of every package of `@altnovatrail`: `just -f spec/justfile release-verify-npm package-lock.json`, from a checkout of spec at a tag. It takes the archive from the registry only if it is the same bytes as the file of canvas's release, signed by .github's shared release at the call of canvas's `release.yml` on `main`.
|
|
36
|
+
|
|
37
|
+
## Code
|
|
38
|
+
|
|
39
|
+
- `src/core/` — the DOM-free part, `@altnovatrail/canvas/core`: it runs the same in a browser and in Node, and its entry point gives the sheet's size, the types of the data and the fold of a history into its strokes.
|
|
40
|
+
- `src/element/` — the element `<novatrail-canvas>`, `@altnovatrail/canvas`: importing it registers the element. The types of its contract with the app — the element as `CanvasElement` has it, its events, the history of writing, the work for checking and its context — are spec's, from `@altnovatrail/spec/canvas`, and this entry point gives them under spec's names, the same types; the DOM-free part gives the data's among them, the history of writing and the context of a submission. The app takes them from spec's package, `import type { CanvasElement } from "@altnovatrail/spec/canvas"`, at the version canvas has in its `dependencies`; canvas's own are only what the contract does not name: the tutor's marks, the media and the task's temporary tools.
|
|
41
|
+
- `scripts/` — the check of the package as a consumer takes it, and of the archive a release puts out.
|
|
42
|
+
- `demo/` — a page to try the element on by hand, as the app is to have it: it gives the element a language and its task the tools allowed, keeps the history of writing as the element tells it and opens the sheet again from it, draws the groups of the strokes and the crop for checking under the sheet, gives the tutor's marks back over the crop Check sent, and shows a workbook's pages in the font and the language it gives, how many boxes are filled, the crop of each filled box Check sent and a mark given back on a box, and a lesson's worked example on a new element, on which nothing is written, whose demo opens part by part — a picture on an area of the sheet on the sheet itself — the page keeping how far it has gone and giving it back to the next element. `just demo` serves it on this machine.
|
|
43
|
+
- `vectors/` — the test vectors of the rule of stroke groups, of the crop for checking, of the snapping of a model's box to the strokes, of the workbook's page layout and of the rule of boxes, which their tests write and keep current.
|
|
44
|
+
- `licenses.json` — the packages the license check accepts by name, each with why.
|
|
45
|
+
|
|
46
|
+
`just build` builds the package into `lib/`, and `just release-files v0.1.0` writes the files of that release into `dist/` — the package's archive, LICENSE and SHA256SUMS — and prints the sums, which a build of the same commit gives again. `just ci` runs the checks CI runs: format, lint and types; the tests in Node and in Chromium; advisories; licenses. They run in the devcontainer, whose toolchain stage holds the browser the tests run in. The license check is spec's recipe, run from the `spec/` submodule with the exceptions in `licenses.json`, so it needs `spec/` checked out: see [CLAUDE.md](CLAUDE.md), Environment.
|
|
47
|
+
|
|
48
|
+
## Documents
|
|
49
|
+
|
|
50
|
+
The documents are in Russian.
|
|
51
|
+
|
|
52
|
+
- [docs/architecture.md](docs/architecture.md) — the component: what it does and does not do, its data and links, and what is still to decide.
|
|
53
|
+
- [docs/decisions.md](docs/decisions.md) — the decision log: what was decided, why, and what was rejected.
|
|
54
|
+
- [RUN.md](RUN.md) — the work plan, in epics.
|
|
55
|
+
|
|
56
|
+
The working rules, for people and coding agents alike, are in [CLAUDE.md](CLAUDE.md) and [AGENTS.md](AGENTS.md). The platform's shared vocabulary, formats, contracts and decisions are in [spec](https://github.com/altnovatrail/spec/tree/v0.6.2), checked out here as a git submodule at `v0.6.2`: clone this repository with `git clone --recurse-submodules`.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
[Apache-2.0](LICENSE).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Filled, Filling } from "@altnovatrail/spec/canvas";
|
|
2
|
+
import type { Stroke } from "./ink.js";
|
|
3
|
+
import type { Page, PageBox } from "./page.js";
|
|
4
|
+
/** A box of a page: its item and its slot in the item's line, each from 1, and where it stands. */
|
|
5
|
+
export interface Cell {
|
|
6
|
+
readonly item: number;
|
|
7
|
+
readonly slot: number;
|
|
8
|
+
readonly box: PageBox;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The rule of boxes (R37), in thousandths of a box's width across and of its height down: how far
|
|
12
|
+
* the box is widened for the middle of a stroke's points, and how wide and how tall the box of its
|
|
13
|
+
* points may be.
|
|
14
|
+
*/
|
|
15
|
+
export interface BoxRule {
|
|
16
|
+
readonly margin: number;
|
|
17
|
+
readonly limit: number;
|
|
18
|
+
}
|
|
19
|
+
/** The rule the workbook has (R37): the sweep of T48 chose it. */
|
|
20
|
+
export declare const BOX_RULE: BoxRule;
|
|
21
|
+
/**
|
|
22
|
+
* Which answer slots the learner's strokes fill, per item, per slot, in the task's order: a
|
|
23
|
+
* workbook's boxes, each whether it holds a stroke (R34, R37), or a sheet's one answer anywhere on
|
|
24
|
+
* it or the whole sheet, whether the sheet holds one (R42). spec's type (R74 spec; R07).
|
|
25
|
+
*/
|
|
26
|
+
export type { Filled };
|
|
27
|
+
/**
|
|
28
|
+
* What a sheet's one answer slot — anywhere on it, or the whole sheet — is filled with (R42): a
|
|
29
|
+
* stroke on the sheet, or none. The same array each time, as a workbook's filling stays the same
|
|
30
|
+
* until a box changes.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sheetFilledOf(stroked: boolean): Filled;
|
|
33
|
+
/** The boxes of a page in the task's order: by item, then by slot. */
|
|
34
|
+
export declare function cellsOf(page: Page): readonly Cell[];
|
|
35
|
+
/**
|
|
36
|
+
* The cell a stroke is in, by its place in `cells`, or −1 for the draft. Of the boxes it may be in,
|
|
37
|
+
* the one that holds the more of its points, within the widened box, and of those the earlier, in
|
|
38
|
+
* the order of `cells`: the task's order on a page. A stroke with no points is a TypeError, and
|
|
39
|
+
* one with a point off the sheet a RangeError, as the crop has them.
|
|
40
|
+
*/
|
|
41
|
+
export declare function cellOf(stroke: Stroke, cells: readonly Cell[], rule?: BoxRule): number;
|
|
42
|
+
/** A filled box and the IDs of its strokes, in ascending order: spec's type (R74 spec; R07). */
|
|
43
|
+
export type { Filling };
|
|
44
|
+
/**
|
|
45
|
+
* The table of the boxes the strokes fill (R34): each box that holds a stroke, in the order of
|
|
46
|
+
* `cells`, with the IDs of its strokes. A stroke ID twice is a TypeError.
|
|
47
|
+
*/
|
|
48
|
+
export declare function tableOf(strokes: readonly Stroke[], cells: readonly Cell[], rule?: BoxRule): readonly Filling[];
|
|
49
|
+
/**
|
|
50
|
+
* Which boxes of a page are filled, per item, per slot, in the task's order: the cells `taken`
|
|
51
|
+
* names by their place in `cells` — the page's, from `cellsOf` — each from `cellOf`; −1, the
|
|
52
|
+
* draft, fills none.
|
|
53
|
+
*/
|
|
54
|
+
export declare function filledOf(page: Page, cells: readonly Cell[], taken: Iterable<number>): Filled;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// The boxes of a workbook's page and the strokes in them (R34, R37). Each visible stroke is in one
|
|
2
|
+
// box or in none: the rule takes it alone, in whole numbers, with no DOM and no time, and what is in
|
|
3
|
+
// no box is the draft. A stroke is a box's if the middle of the box of its points lies in the box
|
|
4
|
+
// widened by 120 thousandths of its size — 12 units of a box of 100, short of the 16 to halfway to
|
|
5
|
+
// the next box — and it is no wider and no taller than the box and 30 units on each side: a
|
|
6
|
+
// fraction's bar wider than the box is its own, and a line through the whole line is not. The
|
|
7
|
+
// core's entry point does not give the rule, and vectors/boxes.json pins it: the eval protocol
|
|
8
|
+
// takes it as it is (В47 in RUN.md).
|
|
9
|
+
import { extentOf, tenths } from "./geometry.js";
|
|
10
|
+
/** The rule the workbook has (R37): the sweep of T48 chose it. */
|
|
11
|
+
export const BOX_RULE = Object.freeze({ margin: 120, limit: 1600 });
|
|
12
|
+
/** A sheet's one answer slot, filled and not: the same frozen arrays for every sheet (R42). */
|
|
13
|
+
const STROKED = Object.freeze([Object.freeze([true])]);
|
|
14
|
+
const BLANK = Object.freeze([Object.freeze([false])]);
|
|
15
|
+
/**
|
|
16
|
+
* What a sheet's one answer slot — anywhere on it, or the whole sheet — is filled with (R42): a
|
|
17
|
+
* stroke on the sheet, or none. The same array each time, as a workbook's filling stays the same
|
|
18
|
+
* until a box changes.
|
|
19
|
+
*/
|
|
20
|
+
export function sheetFilledOf(stroked) {
|
|
21
|
+
return stroked ? STROKED : BLANK;
|
|
22
|
+
}
|
|
23
|
+
/** The boxes of a page in the task's order: by item, then by slot. */
|
|
24
|
+
export function cellsOf(page) {
|
|
25
|
+
return Object.freeze(page.items.flatMap((item, i) => item.boxes.map((box, j) => Object.freeze({ item: i + 1, slot: j + 1, box }))));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The cell a stroke is in, by its place in `cells`, or −1 for the draft. Of the boxes it may be in,
|
|
29
|
+
* the one that holds the more of its points, within the widened box, and of those the earlier, in
|
|
30
|
+
* the order of `cells`: the task's order on a page. A stroke with no points is a TypeError, and
|
|
31
|
+
* one with a point off the sheet a RangeError, as the crop has them.
|
|
32
|
+
*/
|
|
33
|
+
export function cellOf(stroke, cells, rule = BOX_RULE) {
|
|
34
|
+
if (rule !== BOX_RULE)
|
|
35
|
+
checkRule(rule);
|
|
36
|
+
const { margin, limit } = rule;
|
|
37
|
+
const { minX, minY, maxX, maxY } = extentOf(stroke);
|
|
38
|
+
const points = stroke.points.map(({ x, y }) => [tenths(x), tenths(y)]);
|
|
39
|
+
let chosen = -1;
|
|
40
|
+
let most = 0;
|
|
41
|
+
for (const [c, { box }] of cells.entries()) {
|
|
42
|
+
const left = 10 * box.left;
|
|
43
|
+
const top = 10 * box.top;
|
|
44
|
+
const width = 10 * box.width;
|
|
45
|
+
const height = 10 * box.height;
|
|
46
|
+
if (1000 * (maxX - minX) > limit * width || 1000 * (maxY - minY) > limit * height)
|
|
47
|
+
continue;
|
|
48
|
+
// The middle, doubled to stay whole: 2·left − 2·margin·width ≤ minX + maxX, and so on.
|
|
49
|
+
const middle = 1000 * (minX + maxX - 2 * left) >= -2 * margin * width &&
|
|
50
|
+
1000 * (minX + maxX - 2 * left - 2 * width) <= 2 * margin * width &&
|
|
51
|
+
1000 * (minY + maxY - 2 * top) >= -2 * margin * height &&
|
|
52
|
+
1000 * (minY + maxY - 2 * top - 2 * height) <= 2 * margin * height;
|
|
53
|
+
if (!middle)
|
|
54
|
+
continue;
|
|
55
|
+
let held = 0;
|
|
56
|
+
for (const [x, y] of points) {
|
|
57
|
+
if (1000 * (x - left) >= -margin * width &&
|
|
58
|
+
1000 * (x - left - width) <= margin * width &&
|
|
59
|
+
1000 * (y - top) >= -margin * height &&
|
|
60
|
+
1000 * (y - top - height) <= margin * height) {
|
|
61
|
+
held += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (chosen < 0 || held > most) {
|
|
65
|
+
chosen = c;
|
|
66
|
+
most = held;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return chosen;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The table of the boxes the strokes fill (R34): each box that holds a stroke, in the order of
|
|
73
|
+
* `cells`, with the IDs of its strokes. A stroke ID twice is a TypeError.
|
|
74
|
+
*/
|
|
75
|
+
export function tableOf(strokes, cells, rule = BOX_RULE) {
|
|
76
|
+
const ids = new Set();
|
|
77
|
+
const held = cells.map(() => []);
|
|
78
|
+
for (const stroke of strokes) {
|
|
79
|
+
if (ids.has(stroke.id))
|
|
80
|
+
throw new TypeError(`stroke ${stroke.id} is given twice`);
|
|
81
|
+
ids.add(stroke.id);
|
|
82
|
+
const c = cellOf(stroke, cells, rule);
|
|
83
|
+
if (c >= 0)
|
|
84
|
+
held[c]?.push(stroke.id);
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze(cells.flatMap(({ item, slot }, c) => {
|
|
87
|
+
const own = held[c] ?? [];
|
|
88
|
+
return own.length === 0
|
|
89
|
+
? []
|
|
90
|
+
: [Object.freeze({ item, slot, ids: Object.freeze(own.sort((a, b) => a - b)) })];
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Which boxes of a page are filled, per item, per slot, in the task's order: the cells `taken`
|
|
95
|
+
* names by their place in `cells` — the page's, from `cellsOf` — each from `cellOf`; −1, the
|
|
96
|
+
* draft, fills none.
|
|
97
|
+
*/
|
|
98
|
+
export function filledOf(page, cells, taken) {
|
|
99
|
+
const rows = page.items.map(({ boxes }) => boxes.map(() => false));
|
|
100
|
+
for (const c of taken) {
|
|
101
|
+
const cell = cells[c];
|
|
102
|
+
const row = cell === undefined ? undefined : rows[cell.item - 1];
|
|
103
|
+
if (cell !== undefined && row !== undefined)
|
|
104
|
+
row[cell.slot - 1] = true;
|
|
105
|
+
}
|
|
106
|
+
return Object.freeze(rows.map((row) => Object.freeze(row)));
|
|
107
|
+
}
|
|
108
|
+
/** A rule of whole thousandths, the margin up to the box itself and the limit at least nothing. */
|
|
109
|
+
function checkRule({ margin, limit }) {
|
|
110
|
+
if (!Number.isInteger(margin) || margin < 0 || margin > 1000) {
|
|
111
|
+
throw new RangeError(`the margin of the rule of boxes is a whole 0 to 1000, not ${margin}`);
|
|
112
|
+
}
|
|
113
|
+
if (!Number.isInteger(limit) || limit < 0 || limit > 100_000) {
|
|
114
|
+
throw new RangeError(`the limit of the rule of boxes is a whole 0 to 100000, not ${limit}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sheet's clock: milliseconds of the sheet, never going back (R09, R18). The page's time it is
|
|
3
|
+
* given — a pointer event's `timeStamp`, or `performance.now()` for a method the consumer calls —
|
|
4
|
+
* both count from the page's time origin (DOM Standard; High Resolution Time), so one clock serves
|
|
5
|
+
* the strokes, the eraser and the actions of the sheet alike (R22). A new sheet's time starts at
|
|
6
|
+
* its first event; a sheet given a history of writing goes on from where that history leaves off
|
|
7
|
+
* (R27): the sheet's time is its history's.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Clock {
|
|
10
|
+
#private;
|
|
11
|
+
/**
|
|
12
|
+
* The sheet's time at a moment of the page's clock. The first moment asked about is the sheet's
|
|
13
|
+
* 0, or the time the clock resumed at, and a moment earlier than one asked about before takes
|
|
14
|
+
* that one's time.
|
|
15
|
+
*/
|
|
16
|
+
at(time: number): number;
|
|
17
|
+
/** The latest time the clock has given, or the one it resumed at, or 0 before it is first asked. */
|
|
18
|
+
get latest(): number;
|
|
19
|
+
/**
|
|
20
|
+
* The clock goes on at `t`: the next moment asked about is the sheet's `t`, whatever the page's
|
|
21
|
+
* time, and the clock counts on from there (R27). It may go back, when the sheet takes a history
|
|
22
|
+
* shorter than the one it had; `resume(0)` makes it a new sheet's clock.
|
|
23
|
+
*/
|
|
24
|
+
resume(t: number): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sheet's clock: milliseconds of the sheet, never going back (R09, R18). The page's time it is
|
|
3
|
+
* given — a pointer event's `timeStamp`, or `performance.now()` for a method the consumer calls —
|
|
4
|
+
* both count from the page's time origin (DOM Standard; High Resolution Time), so one clock serves
|
|
5
|
+
* the strokes, the eraser and the actions of the sheet alike (R22). A new sheet's time starts at
|
|
6
|
+
* its first event; a sheet given a history of writing goes on from where that history leaves off
|
|
7
|
+
* (R27): the sheet's time is its history's.
|
|
8
|
+
*/
|
|
9
|
+
export class Clock {
|
|
10
|
+
#origin;
|
|
11
|
+
/** The sheet's time at the origin: 0, or where a history given to the sheet goes on. */
|
|
12
|
+
#base = 0;
|
|
13
|
+
#last = 0;
|
|
14
|
+
/**
|
|
15
|
+
* The sheet's time at a moment of the page's clock. The first moment asked about is the sheet's
|
|
16
|
+
* 0, or the time the clock resumed at, and a moment earlier than one asked about before takes
|
|
17
|
+
* that one's time.
|
|
18
|
+
*/
|
|
19
|
+
at(time) {
|
|
20
|
+
this.#origin ??= time;
|
|
21
|
+
const t = Math.max(this.#base + (time - this.#origin), this.#last);
|
|
22
|
+
this.#last = t;
|
|
23
|
+
return t;
|
|
24
|
+
}
|
|
25
|
+
/** The latest time the clock has given, or the one it resumed at, or 0 before it is first asked. */
|
|
26
|
+
get latest() {
|
|
27
|
+
return this.#last;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The clock goes on at `t`: the next moment asked about is the sheet's `t`, whatever the page's
|
|
31
|
+
* time, and the clock counts on from there (R27). It may go back, when the sheet takes a history
|
|
32
|
+
* shorter than the one it had; `resume(0)` makes it a new sheet's clock.
|
|
33
|
+
*/
|
|
34
|
+
resume(t) {
|
|
35
|
+
this.#origin = undefined;
|
|
36
|
+
this.#base = t;
|
|
37
|
+
this.#last = t;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CropNumbers, Frame } from "@altnovatrail/spec/canvas";
|
|
2
|
+
import type { Group } from "./groups.js";
|
|
3
|
+
import type { Stroke } from "./ink.js";
|
|
4
|
+
/**
|
|
5
|
+
* The crop's rule in its numbers (R25): sizes on the crop in pixels, heights on the sheet in units.
|
|
6
|
+
* The rule counts in whole numbers — places in tenths of a unit, as the groups do (R24) — and each
|
|
7
|
+
* number it hands out that is not whole is one division of two whole numbers, so that tutor's
|
|
8
|
+
* runner, which crops by the same rule, gets the same numbers to the bit.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CROP: Readonly<{
|
|
11
|
+
/** The margin around the work, px (R06). */
|
|
12
|
+
margin: 12;
|
|
13
|
+
/**
|
|
14
|
+
* A stroke lower than this, in units, is a line, not writing — a dot, a minus, a bar, an
|
|
15
|
+
* underline — and is in no bundle, nor joins any.
|
|
16
|
+
*/
|
|
17
|
+
flat: 15;
|
|
18
|
+
/** The scale takes the median no higher than this, in units: a drawing alone stays a drawing. */
|
|
19
|
+
ceiling: 100;
|
|
20
|
+
/** The least width and height of a crop, px: Claude may err on images under 200 pixels. */
|
|
21
|
+
side: 200;
|
|
22
|
+
/**
|
|
23
|
+
* The longest side, px, and the most visual tokens, ⌈w / 28⌉ × ⌈h / 28⌉: what every model of
|
|
24
|
+
* Claude takes without scaling the image down, its standard tier.
|
|
25
|
+
*/
|
|
26
|
+
edge: 1568;
|
|
27
|
+
tokens: 1568;
|
|
28
|
+
patch: 28;
|
|
29
|
+
/**
|
|
30
|
+
* The height of a digit a crop is drawn at unless the module's manifest says another, px: 24
|
|
31
|
+
* since the module format 0.2, with or without a manifest.
|
|
32
|
+
*/
|
|
33
|
+
digit: 24;
|
|
34
|
+
/**
|
|
35
|
+
* The heights of digits a sample's crops are drawn at, px, until the eval protocol names its own
|
|
36
|
+
* (R09; R08 tutor).
|
|
37
|
+
*/
|
|
38
|
+
heights: readonly [16, 24, 32, 48];
|
|
39
|
+
/** The tallest digit a crop takes, px: its ink then still stays within the margin. */
|
|
40
|
+
tallest: 256;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* A group's frame on a crop: its label, and its ink in fractions 0–1000 of the crop, [ymin, xmin,
|
|
44
|
+
* ymax, xmax] — every pixel the ink of its strokes touches lies within it. spec's type (R74 spec).
|
|
45
|
+
*/
|
|
46
|
+
export type { Frame };
|
|
47
|
+
/**
|
|
48
|
+
* The crop of a sheet for checking (R25), all but its pixels: what lies where, at what scale. Its
|
|
49
|
+
* numbers the marks are placed by — D′, the size, the margins, `sheet_box`, the frames and the
|
|
50
|
+
* whole numbers they come from — are spec's CropNumbers (R74 spec), and the rest the crop's own.
|
|
51
|
+
*/
|
|
52
|
+
export interface Crop extends CropNumbers {
|
|
53
|
+
/** D, the height of a digit asked for, px. */
|
|
54
|
+
readonly digit: number;
|
|
55
|
+
/** h_med, the median height of the bundles, in units: the floor if there are none. */
|
|
56
|
+
readonly median: number;
|
|
57
|
+
/** s, the crop's px to a sheet unit: D′ over h_med, or over the ceiling if h_med is above it. */
|
|
58
|
+
readonly scale: number;
|
|
59
|
+
/** The width of the line, px: D′ / 12, and 1 at least. */
|
|
60
|
+
readonly line: number;
|
|
61
|
+
}
|
|
62
|
+
/** A stroke as the crop sees it: the box of its points in tenths. */
|
|
63
|
+
/**
|
|
64
|
+
* The bundles of the strokes on a sheet (R25): strokes no lower than `CROP.flat`, joined by a chain
|
|
65
|
+
* of boxes that touch or overlap, whenever each was written. Each bundle by its IDs ascending, the
|
|
66
|
+
* bundles by their smallest ID.
|
|
67
|
+
*/
|
|
68
|
+
export declare function bundlesOf(strokes: readonly Stroke[]): number[][];
|
|
69
|
+
/** h_med (R25): the lower median of the heights of the bundles, in units; `CROP.flat` if none. */
|
|
70
|
+
export declare function medianOf(strokes: readonly Stroke[]): number;
|
|
71
|
+
/**
|
|
72
|
+
* The crop of the strokes on a sheet with its digits `digit` px high, and the frames of their
|
|
73
|
+
* groups (R25): the strokes the sheet shows, and their groups by the sheet's rule (R24). A sheet
|
|
74
|
+
* without strokes has no crop: it goes to no check (H3).
|
|
75
|
+
*/
|
|
76
|
+
export declare function cropOf(strokes: readonly Stroke[], groups: readonly Group[], digit?: number): Crop | null;
|
|
77
|
+
/**
|
|
78
|
+
* Where a crop with its margins lies on the sheet, its `sheet_box` (R25): [ymin, xmin, ymax, xmax]
|
|
79
|
+
* in units, each one division of two whole numbers, from the whole numbers of the crop. A
|
|
80
|
+
* submission's `sheet_box` is checked by it when the tutor's marks come back with it (R26).
|
|
81
|
+
*/
|
|
82
|
+
export declare function sheetBoxOf(crop: Pick<Crop, "drawn" | "width" | "height" | "margins" | "exact">): readonly [number, number, number, number];
|
|
83
|
+
/** What Claude counts an image as: ⌈w / 28⌉ × ⌈h / 28⌉ visual tokens, each a patch 28 px square. */
|
|
84
|
+
export declare function tokensOf({ width, height }: {
|
|
85
|
+
width: number;
|
|
86
|
+
height: number;
|
|
87
|
+
}): number;
|