rapier-html 1.2.45 → 1.2.46
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/BUILD.json +16 -16
- package/README.md +11 -11
- package/dist/ledger/LICENSE +12 -5
- package/dist/ledger/authored-origin.mjs +2 -2
- package/dist/ledger/authorship.mjs +5 -5
- package/dist/ledger/carried.mjs +5 -5
- package/dist/ledger/diff/concerns.mjs +1 -1
- package/dist/ledger/diff/diff.mjs +6 -6
- package/dist/ledger/diff/index.mjs +2 -2
- package/dist/ledger/diff/merge.mjs +3 -3
- package/dist/ledger/diff/moves.mjs +1 -1
- package/dist/ledger/diff/walk.mjs +1 -1
- package/dist/ledger/document-fonts.mjs +242 -0
- package/dist/ledger/document-settings.mjs +2 -2
- package/dist/ledger/format.mjs +14 -12
- package/dist/ledger/history.mjs +7 -7
- package/dist/ledger/inverse-source.mjs +5 -5
- package/dist/ledger/journal-records.mjs +3 -3
- package/dist/ledger/math/delimiters.mjs +38 -0
- package/dist/ledger/math/markdown-rules.mjs +103 -0
- package/dist/ledger/md-assets.mjs +3 -2
- package/dist/ledger/md-comments.mjs +384 -0
- package/dist/ledger/md-engine.mjs +157 -0
- package/dist/ledger/md-marks.mjs +10 -1
- package/dist/ledger/md-parser-config.mjs +463 -0
- package/dist/ledger/md-parser-runtime.mjs +23 -0
- package/dist/ledger/md-source.mjs +1 -1
- package/dist/ledger/template-page.mjs +1 -1
- package/dist/ledger/transport.mjs +66 -7
- package/dist/ledger/vendor/BROWSER-ENTITIES.json +36 -0
- package/dist/ledger/vendor/MARKDOWN-RUNTIME.json +136 -0
- package/dist/ledger/vendor/MARKDOWNIT.json +582 -0
- package/dist/ledger/vendor/NOTICE.txt +60 -0
- package/dist/ledger/vendor/markdown-licenses/entities-LICENSE.txt +11 -0
- package/dist/ledger/vendor/markdown-licenses/linkify-it-LICENSE.txt +22 -0
- package/dist/ledger/vendor/markdown-licenses/markdown-it-LICENSE.txt +22 -0
- package/dist/ledger/vendor/markdown-licenses/mdurl-LICENSE.txt +45 -0
- package/dist/ledger/vendor/markdown-licenses/punycode.js-LICENSE-MIT.txt +20 -0
- package/dist/ledger/vendor/markdown-licenses/uc.micro-LICENSE.txt +20 -0
- package/dist/ledger/vendor/markdown-plugins.json +46 -0
- package/dist/ledger/vendor/markdown-plugins.mjs +27 -0
- package/dist/ledger/vendor/markdown-runtime.mjs +13 -0
- package/dist/rapier.html +59 -67
- package/package.json +1 -1
package/BUILD.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "rapier-html",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.46",
|
|
4
4
|
"path": "dist/rapier.html",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
7
|
-
"builtAt": "2026-10-
|
|
5
|
+
"bytes": 5934524,
|
|
6
|
+
"sha256": "f517fef79e14aa898b4bfaf519c679032f6df7538128892e6374280cb9511432",
|
|
7
|
+
"builtAt": "2026-10-27T10:37:55.443Z",
|
|
8
8
|
"node": "v22.23.4",
|
|
9
9
|
"canonical": true,
|
|
10
10
|
"packing": "zopfli",
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
"delivery": "built-in",
|
|
32
32
|
"kind": "files",
|
|
33
33
|
"element": "rapier-plugin-mermaid",
|
|
34
|
-
"bytes":
|
|
35
|
-
"packedBytes":
|
|
34
|
+
"bytes": 722820,
|
|
35
|
+
"packedBytes": 241155,
|
|
36
36
|
"files": [
|
|
37
37
|
{
|
|
38
38
|
"id": "rapier-mermaid-core",
|
|
39
|
-
"name": "pele-1.9.0-native-
|
|
40
|
-
"version": "1.9.0-native-
|
|
41
|
-
"bytes":
|
|
42
|
-
"sri": "
|
|
39
|
+
"name": "pele-1.9.0-native-2.js",
|
|
40
|
+
"version": "1.9.0-native-2",
|
|
41
|
+
"bytes": 722820,
|
|
42
|
+
"sri": "vPm/+HAI9fOqGc1GFl8wFvzMcZjXMhIUXWSdd2qAAVW6fBqjmK6Np6MVprxE8DFK"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
45
45
|
},
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"delivery": "built-in",
|
|
49
49
|
"kind": "files",
|
|
50
50
|
"element": "rapier-plugin-mermaid-zenuml",
|
|
51
|
-
"bytes":
|
|
52
|
-
"packedBytes":
|
|
51
|
+
"bytes": 421169,
|
|
52
|
+
"packedBytes": 111122,
|
|
53
53
|
"files": [
|
|
54
54
|
{
|
|
55
55
|
"id": "rapier-mermaid-zenuml",
|
|
56
|
-
"name": "rapier-zenuml-svg-4.5.0-
|
|
57
|
-
"version": "4.5.0-svg-
|
|
58
|
-
"bytes":
|
|
59
|
-
"sri": "
|
|
56
|
+
"name": "rapier-zenuml-svg-4.5.0-2.js",
|
|
57
|
+
"version": "4.5.0-svg-2",
|
|
58
|
+
"bytes": 421169,
|
|
59
|
+
"sri": "g5vdITVAGyvxD4BJdaFJjNZKvlTUXxXlJkqeFyfXREsJVsopr/iH4DC8gPhuEjDK"
|
|
60
60
|
}
|
|
61
61
|
]
|
|
62
62
|
},
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ Turn Markdown into one offline HTML file that contains the full Rapier editor.
|
|
|
7
7
|
<img src="https://raw.githubusercontent.com/jackskip22/rapier-plugins/main/assets/write-390-light.png" alt="The Rapier editor on a phone: a launch plan with an agent's change marked" width="320">
|
|
8
8
|
</picture>
|
|
9
9
|
|
|
10
|
-
Try it: `npx rapier-html@1.2.
|
|
11
|
-
Install: `npm install rapier-html@1.2.
|
|
10
|
+
Try it: `npx rapier-html@1.2.46 notes.md`, then open `notes.rapier.html` in any browser.
|
|
11
|
+
Install: `npm install rapier-html@1.2.46`.
|
|
12
12
|
|
|
13
13
|
It opens on a phone or computer, with no account. People read, edit, draw and paint; agents hand over documents, show
|
|
14
14
|
what changed and get the edited copy back.
|
|
@@ -26,14 +26,14 @@ The file needs no hosted workspace.
|
|
|
26
26
|
Needs Node 22 or newer.
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
npx -- rapier-html@1.2.
|
|
30
|
-
npx -- rapier-html@1.2.
|
|
31
|
-
npx -- rapier-html@1.2.
|
|
32
|
-
npx -- rapier-html@1.2.
|
|
33
|
-
npx -- rapier-html@1.2.
|
|
34
|
-
npx -- rapier-html@1.2.
|
|
35
|
-
npx -- rapier-html@1.2.
|
|
36
|
-
npm install rapier-html@1.2.
|
|
29
|
+
npx -- rapier-html@1.2.46 notes.md # the editor, notes.md inside it, written beside the file
|
|
30
|
+
npx -- rapier-html@1.2.46 notes.md --view draw # opens on Draw: sketch and paint, the document behind it
|
|
31
|
+
npx -- rapier-html@1.2.46 notes.md --include draw,math # carries Draw and Math (default: what the document needs)
|
|
32
|
+
npx -- rapier-html@1.2.46 notes.md --drawing sketch.svg # carries a drawing, opened on Draw over the document
|
|
33
|
+
npx -- rapier-html@1.2.46 revised.md --compare original.md # opens showing what changed
|
|
34
|
+
npx -- rapier-html@1.2.46 notes.md --return "$RETURN_URL" --return-expires-at "$RETURN_EXPIRES_AT" # Send back
|
|
35
|
+
npx -- rapier-html@1.2.46 notes.md out.html # a named output
|
|
36
|
+
npm install rapier-html@1.2.46 # as a library: writePage, unwrap
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
The first `--` is for npm, so `--help` reaches the command. It never overwrites a file.
|
|
@@ -117,7 +117,7 @@ objects, and `unwrap(page)` returns the checked parts beside the text. Both are
|
|
|
117
117
|
history.
|
|
118
118
|
- Markdown saved with the kit's `ledger/carried.writeDocument` is read automatically when wrapping. Rewrapping a page
|
|
119
119
|
without parts drops the old parts.
|
|
120
|
-
- The published package carries
|
|
120
|
+
- The published package carries the portable MIT validator and canonical Markdown parser support, with the parser vendors' original MIT, ISC and BSD-2-Clause notices. Accepted comment history reopens without a source checkout or a parser supplied by the caller.
|
|
121
121
|
|
|
122
122
|
## License
|
|
123
123
|
|
package/dist/ledger/LICENSE
CHANGED
|
@@ -21,14 +21,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
21
21
|
SOFTWARE.
|
|
22
22
|
|
|
23
23
|
Files under this license carry `SPDX-License-Identifier: MIT` in their first lines: the standard's
|
|
24
|
-
grammar, marks and reference style (`spec/md-layout.mjs`, `spec/md-marks.mjs`, `spec/md-source.mjs`, `spec/flowchart.mjs`, `spec/markdown-style.css`),
|
|
25
|
-
(`
|
|
24
|
+
grammar, marks and reference style (`spec/md-layout.mjs`, `spec/md-marks.mjs`, `spec/md-source.mjs`, `spec/flowchart.mjs`, `spec/markdown-style.css`),
|
|
25
|
+
the reusable diagram source, layout and raising helpers (`draw/flowchart-mermaid.mjs`, `draw/flowchart-layout.mjs`,
|
|
26
|
+
`draw/flowchart-raise.mjs`) and diagram-core export facade (`shell/mermaid/api.mjs`),
|
|
27
|
+
ink geometry (`spec/ink.mjs`),
|
|
26
28
|
the layout model and line planner (`layout/model.mjs`, `layout/line-plan.mjs`, `layout/typography.mjs`, `layout/preparation.mjs`,
|
|
27
29
|
`layout/prepared-paragraph.mjs`, `layout/hyphenation.mjs`, `layout/paragraph-fit.mjs`), the animated-picture reader and playback
|
|
28
30
|
the HTML blocks use (`images/gif.mjs`, `images/playback.mjs`),
|
|
29
31
|
the portable HTML and document metadata helpers (`spec/html-blocks.mjs`, `spec/frontmatter.mjs`,
|
|
30
32
|
`spec/document-settings.mjs`, `spec/document-fonts.mjs`), the Will grammar (`agent/will.mjs`), the picture appendix reader and writer
|
|
31
|
-
(`spec/md-assets.mjs`), the
|
|
33
|
+
(`spec/md-assets.mjs`), the portable comment and canonical parser support owners
|
|
34
|
+
(`spec/md-comments.mjs`, `spec/md-parser-config.mjs`, `spec/md-parser-runtime.mjs`),
|
|
35
|
+
the neutral math boundary and parser adapters (`math/delimiters.mjs`, `math/markdown-rules.mjs`), the two shared-page reference readers (`tools/read-shared-page.mjs`,
|
|
32
36
|
`tools/read-shared-page.py`), and the host helper `packages/rapier-embed/embed.mjs` with its
|
|
33
37
|
contract owner `packages/rapier-embed/contract.mjs`. The vendored Pretext (`agent/vendor/pretext`) and micromark carry their
|
|
34
38
|
own MIT notices. The edit-ledger module (`kit/rapier-ledger.mjs`, `kit/ledger/*.mjs`) and
|
|
@@ -39,11 +43,14 @@ AGPL-3.0-only. The page wrapper (`skills/rapier-html/wrap.mjs`, `skills/rapier-h
|
|
|
39
43
|
`skills/rapier-html/return-address.mjs`) and its carrier-staging helper
|
|
40
44
|
(`skills/rapier-html/package-files.mjs`) are MIT. The kit's MIT-marked facades,
|
|
41
45
|
`kit/render-entry.mjs`, `kit/render*.mjs`, `kit/export-fonts.mjs`, `kit/print-capitals.mjs`,
|
|
42
|
-
`kit/pdf-vectors.mjs`, `kit/pdf-glyphs.mjs`, `kit/stage.mjs` and MIT-marked conformance
|
|
46
|
+
`kit/pdf-vectors.mjs`, `kit/pdf-glyphs.mjs`, `kit/stage.mjs`, `kit/markdown-vendor.mjs` and MIT-marked conformance
|
|
43
47
|
modules are covered too. These names describe the Rapier-owned files carrying the
|
|
44
48
|
MIT notice, not a grant over the editor or third-party code. Other Rapier-owned
|
|
45
49
|
files retain their stated license; the editor is AGPL-3.0-only (see `LICENSE`).
|
|
46
|
-
Third-party components retain their own copyright and license notices.
|
|
50
|
+
Third-party components retain their own copyright and license notices. The published Markdown parser,
|
|
51
|
+
its plugins and their dependencies retain their original MIT, ISC and BSD-2-Clause notices in `agent/vendor/NOTICE.txt` and
|
|
52
|
+
`agent/vendor/markdown-licenses/`; this inventory does not replace them. The Feather callout paths
|
|
53
|
+
in the canonical configuration retain the Feather copyright and MIT notice there.
|
|
47
54
|
|
|
48
55
|
The portable font helpers read document definitions and accept explicit host validation and
|
|
49
56
|
subsetting functions. The application binding (`editor/export-fonts.mjs`), captured-font
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {_rapierTransformSplices, _rapierValidAuthored} from
|
|
3
|
-
import {sha256} from
|
|
2
|
+
import {_rapierTransformSplices, _rapierValidAuthored} from "./journal-records.mjs";
|
|
3
|
+
import {sha256} from "./hash.mjs";
|
|
4
4
|
|
|
5
5
|
function authoredFrontier(records, visit = null) {
|
|
6
6
|
const dependencies = new Map(), frontier = new Set();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {readLedger, exportLedger, textRoot, rootAfter} from
|
|
3
|
-
import {_rapierTransformSplices, _rapierHistoryEffects} from
|
|
4
|
-
import {projectInverseSource} from
|
|
5
|
-
import {sha256} from
|
|
6
|
-
import {plainData, documentText, nonnegative, exactKeys, fault} from
|
|
2
|
+
import {readLedger, exportLedger, textRoot, rootAfter} from "./format.mjs";
|
|
3
|
+
import {_rapierTransformSplices, _rapierHistoryEffects} from "./journal-records.mjs";
|
|
4
|
+
import {projectInverseSource} from "./inverse-source.mjs";
|
|
5
|
+
import {sha256} from "./hash.mjs";
|
|
6
|
+
import {plainData, documentText, nonnegative, exactKeys, fault} from "./data.mjs";
|
|
7
7
|
function coalesce(runs) {
|
|
8
8
|
const out = [];
|
|
9
9
|
for (const run of runs) {
|
package/dist/ledger/carried.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {readLedger} from
|
|
3
|
-
import {sha256} from
|
|
4
|
-
import {authorship as projectAuthorship, readAuthorship} from
|
|
5
|
-
import {canonicalJSON, fault} from
|
|
2
|
+
import {readLedger} from "./format.mjs";
|
|
3
|
+
import {sha256} from "./hash.mjs";
|
|
4
|
+
import {authorship as projectAuthorship, readAuthorship} from "./authorship.mjs";
|
|
5
|
+
import {canonicalJSON, fault} from "./data.mjs";
|
|
6
6
|
export function encodeCarried(text) {
|
|
7
7
|
return String(text).replace(/[\\\r\0]|<[/!]/g, m => m === '\\' ? '\\\\' : m === '\r' ? '\\r' : m === '\0' ? '\\0' : m === '</' ? '\\/' : '\\!');
|
|
8
8
|
}
|
|
@@ -57,8 +57,8 @@ export function writeDocument(text, options) {
|
|
|
57
57
|
return text + documentTrailer(text, options);
|
|
58
58
|
}
|
|
59
59
|
function unfinishedTrailer(source) {
|
|
60
|
+
if (!source.includes(OPEN)) return false;
|
|
60
61
|
const at = source.lastIndexOf(OPEN);
|
|
61
|
-
if (at < 0) return false;
|
|
62
62
|
let fence = null;
|
|
63
63
|
for (const line of source.slice(0, at).split('\n')) {
|
|
64
64
|
const found = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {letter, within, prev, next} from
|
|
2
|
+
import {letter, within, prev, next} from "./tokens.mjs";
|
|
3
3
|
|
|
4
4
|
export function separate(c, endA = c.before.length, endB = c.after.length) {
|
|
5
5
|
const {before, after, safe, raw, spend} = c;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {observe} from
|
|
3
|
-
import {boundaries, tokenizer, wordSegmenter} from
|
|
4
|
-
import {matcher} from
|
|
5
|
-
import {walker} from
|
|
6
|
-
import {moves} from
|
|
7
|
-
import {separate, concerns} from
|
|
2
|
+
import {observe} from "./structure.mjs";
|
|
3
|
+
import {boundaries, tokenizer, wordSegmenter} from "./tokens.mjs";
|
|
4
|
+
import {matcher} from "./matching.mjs";
|
|
5
|
+
import {walker} from "./walk.mjs";
|
|
6
|
+
import {moves} from "./moves.mjs";
|
|
7
|
+
import {separate, concerns} from "./concerns.mjs";
|
|
8
8
|
|
|
9
9
|
export function diff(before, after, options = {}) {
|
|
10
10
|
if (before === after) return {edits: [], changes: []};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {diff} from
|
|
3
|
-
import {observe} from
|
|
4
|
-
import {boundaries, words, wordSegmenter} from
|
|
2
|
+
import {diff} from "./diff.mjs";
|
|
3
|
+
import {observe} from "./structure.mjs";
|
|
4
|
+
import {boundaries, words, wordSegmenter} from "./tokens.mjs";
|
|
5
5
|
|
|
6
6
|
export function merge(base, mine, theirs, options = {}) {
|
|
7
7
|
if (mine === theirs || base === theirs || base === mine) return {text: base === mine ? theirs : mine, conflicts: []};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {separate, phrases} from
|
|
2
|
+
import {separate, phrases} from "./concerns.mjs";
|
|
3
3
|
|
|
4
4
|
export function moves(c) {
|
|
5
5
|
const {before, after, spend, safe, budget, raw, code, tokens, matches, walk, structures, pa, pb, la, aliases} = c;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {words, letter, within, prev, next} from
|
|
2
|
+
import {words, letter, within, prev, next} from "./tokens.mjs";
|
|
3
3
|
|
|
4
4
|
export function walker(c) {
|
|
5
5
|
const {before, after, spend, safe, budget, raw, chars, code, segmenter, tokens, matches} = c;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {markdownParser, installMarkdownImages, normalizeLabel} from "./md-assets.mjs";
|
|
3
|
+
import {splitOpeningFrontmatter} from "./frontmatter.mjs";
|
|
4
|
+
import {readDocumentSettings, documentFont} from "./document-settings.mjs";
|
|
5
|
+
import {normalizeFontFamily, fontFamilyCss, FONT_STACKS, typography, fontDataUrl} from "./md-marks.mjs";
|
|
6
|
+
export {fontDataUrl};
|
|
7
|
+
|
|
8
|
+
export const CSS_GENERIC_FONT_FAMILIES = Object.freeze(['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui',
|
|
9
|
+
'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'emoji', 'math', 'fangsong', 'initial', 'revert', 'revert-layer']);
|
|
10
|
+
|
|
11
|
+
function parserFor(parser) {
|
|
12
|
+
const md = installMarkdownImages(markdownParser(parser));
|
|
13
|
+
if (!md.__rapierFontsInstalled) {
|
|
14
|
+
const validate = md.validateLink;
|
|
15
|
+
md.validateLink = url => !!fontDataUrl(url) || validate(url);
|
|
16
|
+
md.__rapierFontsInstalled = true;
|
|
17
|
+
}
|
|
18
|
+
return md;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function faceOf(value) {
|
|
22
|
+
const family = normalizeFontFamily(value?.family), style = value?.style || 'normal';
|
|
23
|
+
const weight = value?.weight ?? 400, stretch = value?.stretch ?? 100;
|
|
24
|
+
if (!family || !['normal', 'italic', 'oblique'].includes(style) || !Number.isFinite(weight) || weight < 1 || weight > 1000 ||
|
|
25
|
+
!Number.isFinite(stretch) || stretch <= 0 || stretch > 1000) return null;
|
|
26
|
+
const result = {family, style, weight, stretch};
|
|
27
|
+
if (value.weightRange != null) {
|
|
28
|
+
const range = value.weightRange;
|
|
29
|
+
if (!Array.isArray(range) || range.length !== 2 || range.some(number => !Number.isFinite(number) || number < 1 || number > 1000) || range[0] > range[1]) return null;
|
|
30
|
+
result.weightRange = range.slice();
|
|
31
|
+
}
|
|
32
|
+
if (value.axes != null) {
|
|
33
|
+
if (!Array.isArray(value.axes) || value.axes.length > 64) return null;
|
|
34
|
+
const seen = new Set(), axes = [];
|
|
35
|
+
for (const axis of value.axes) {
|
|
36
|
+
if (!axis || !/^[ -~]{4}$/.test(axis.tag) || seen.has(axis.tag) ||
|
|
37
|
+
![axis.min, axis.default, axis.max].every(Number.isFinite) || axis.min > axis.default || axis.default > axis.max) return null;
|
|
38
|
+
seen.add(axis.tag);
|
|
39
|
+
axes.push({tag: axis.tag, min: axis.min, default: axis.default, max: axis.max, ...(typeof axis.name === 'string' ? {name: axis.name} : {})});
|
|
40
|
+
}
|
|
41
|
+
if (axes.length) result.axes = axes;
|
|
42
|
+
}
|
|
43
|
+
if (value.sourceId != null) {
|
|
44
|
+
if (!/^f[0-9a-f]{24}$/.test(value.sourceId)) return null;
|
|
45
|
+
result.sourceId = value.sourceId;
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function fontAssetKey(font) {
|
|
51
|
+
const face = faceOf(font);
|
|
52
|
+
if (!face) return '';
|
|
53
|
+
return [face.family.toLowerCase(), face.style, face.weight, face.stretch,
|
|
54
|
+
(face.axes || []).map(axis => [axis.tag, axis.min, axis.max].join(':')).sort().join(',')].join('/');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function readFontAssets(markdown, parser) {
|
|
58
|
+
const source = String(markdown ?? ''), assets = [], blocks = [], refused = [];
|
|
59
|
+
if (!/\[font:/i.test(source)) return {assets, blocks, refused, references: Object.create(null)};
|
|
60
|
+
const md = parserFor(parser), frame = splitOpeningFrontmatter(source), env = {}, tokens = [], starts = [frame.bodyOffset];
|
|
61
|
+
for (let at = frame.bodyOffset; at < source.length; at++) {
|
|
62
|
+
if (source[at] === '\r') { if (source[at + 1] === '\n') at++; starts.push(at + 1); }
|
|
63
|
+
else if (source[at] === '\n') starts.push(at + 1);
|
|
64
|
+
}
|
|
65
|
+
md.block.parse(frame.body.replace(/\r\n?/g, '\n').replace(/\0/g, '\ufffd'), md, env, tokens);
|
|
66
|
+
const labels = new Set(), faces = new Set();
|
|
67
|
+
for (const token of tokens) {
|
|
68
|
+
if (token.type !== 'reference_definition' || !token.map) continue;
|
|
69
|
+
const id = token.meta.label, first = !labels.has(id);
|
|
70
|
+
labels.add(id);
|
|
71
|
+
const definition = token.meta.mdImageDefinition || env.references?.[id];
|
|
72
|
+
const url = definition?.href, data = fontDataUrl(url);
|
|
73
|
+
if (!/^FONT:/i.test(id)) continue;
|
|
74
|
+
const start = starts[token.map[0]], next = starts[token.map[1]] ?? source.length;
|
|
75
|
+
let end = next;
|
|
76
|
+
if (source[end - 1] === '\n') end--;
|
|
77
|
+
if (source[end - 1] === '\r') end--;
|
|
78
|
+
const raw = source.slice(start, end), match = /^[ \t]{0,3}\[((?:\\.|[^\]\\\r\n])+)\]:/.exec(raw);
|
|
79
|
+
if (!match) continue;
|
|
80
|
+
const label = md.utils.unescapeAll(match[1]);
|
|
81
|
+
if (!data) { refused.push({name: label, reason: 'The font reference must contain an embedded font.'}); continue; }
|
|
82
|
+
let metadata = {family: label.slice(5)};
|
|
83
|
+
if (definition.title) {
|
|
84
|
+
try { metadata = JSON.parse(definition.title); }
|
|
85
|
+
catch (_) { refused.push({name: label, reason: 'The font reference has invalid metadata.'}); continue; }
|
|
86
|
+
}
|
|
87
|
+
const face = faceOf(metadata);
|
|
88
|
+
if (!face) { refused.push({name: label, reason: 'The font reference has invalid metadata.'}); continue; }
|
|
89
|
+
const key = fontAssetKey(face), active = first && !faces.has(key);
|
|
90
|
+
const row = {...face, ...data, url, label, id, key, source: raw, start, end, next, active, topLevel: token.level === 0};
|
|
91
|
+
blocks.push(row);
|
|
92
|
+
if (active) { assets.push(row); faces.add(key); }
|
|
93
|
+
}
|
|
94
|
+
return {assets, blocks, refused, references: env.references || Object.create(null)};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function fontLabel(face) {
|
|
98
|
+
const variants = face.style !== 'normal' || face.weight !== 400 || face.stretch !== 100 || face.axes?.length;
|
|
99
|
+
return 'font:' + face.family + (variants ? '/' + face.style + '/' + face.weight + (face.stretch !== 100 ? '/' + face.stretch : '')
|
|
100
|
+
+ (face.axes?.length ? '/' + face.axes.map(axis => axis.tag + '=' + axis.min + ':' + axis.max).join(',') : '') : '');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function serializeFontAsset(asset, label = null) {
|
|
104
|
+
const face = faceOf(asset), data = fontDataUrl(asset?.url);
|
|
105
|
+
if (!face || !data) throw new TypeError('font: use an embedded font with valid family metadata');
|
|
106
|
+
const escaped = escapeFontLabel(label || fontLabel(face));
|
|
107
|
+
const title = JSON.stringify(face).replace(/&/g, '&').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
108
|
+
return '[' + escaped + ']: ' + asset.url + ' "' + title + '"';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function escapeFontLabel(label) {
|
|
112
|
+
return String(label).replace(/[\\[\]]/g, '\\$&').replace(/&/g, '&');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function writeFontAssets(markdown, assets, parser) {
|
|
116
|
+
const source = String(markdown ?? ''), md = parserFor(parser), index = readFontAssets(source, md), wanted = new Map();
|
|
117
|
+
for (const asset of assets || []) {
|
|
118
|
+
const key = fontAssetKey(asset);
|
|
119
|
+
if (!key || !fontDataUrl(asset?.url)) throw new TypeError('font: invalid embedded asset');
|
|
120
|
+
if (wanted.has(key) && wanted.get(key).url !== asset.url) throw new TypeError('font: one file owns each family face');
|
|
121
|
+
wanted.set(key, asset);
|
|
122
|
+
}
|
|
123
|
+
const edits = [], consumed = new Set(), labels = new Set(Object.keys(index.references));
|
|
124
|
+
const retired = index.assets.filter(row => !wanted.has(row.key));
|
|
125
|
+
for (const row of index.blocks) {
|
|
126
|
+
if (!row.active) {
|
|
127
|
+
if (retired.some(active => active.key === row.key || active.id === row.id)) edits.push({start: row.start, end: row.next, text: ''});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const asset = wanted.get(row.key);
|
|
131
|
+
if (!asset) { edits.push({start: row.start, end: row.next, text: ''}); continue; }
|
|
132
|
+
consumed.add(row.key);
|
|
133
|
+
if (asset.url === row.url && JSON.stringify(faceOf(asset)) === JSON.stringify(faceOf(row))) continue;
|
|
134
|
+
edits.push({start: row.start, end: row.end, text: serializeFontAsset(asset, row.label)});
|
|
135
|
+
}
|
|
136
|
+
let result = source;
|
|
137
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
138
|
+
const pending = [];
|
|
139
|
+
for (const [key, asset] of wanted) {
|
|
140
|
+
if (consumed.has(key)) continue;
|
|
141
|
+
const base = fontLabel(faceOf(asset));
|
|
142
|
+
let label = base, suffix = 2;
|
|
143
|
+
while (labels.has(normalizeLabel(escapeFontLabel(label)))) label = base + '/' + suffix++;
|
|
144
|
+
labels.add(normalizeLabel(escapeFontLabel(label)));
|
|
145
|
+
pending.push(serializeFontAsset(asset, label));
|
|
146
|
+
}
|
|
147
|
+
if (pending.length) {
|
|
148
|
+
const eol = /\r\n|\r|\n/.exec(source)?.[0] || '\n';
|
|
149
|
+
result += (result ? (/(?:\r\n|\r(?!\n)|\n)[ \t]*(?:\r\n|\r(?!\n)|\n)$/.test(result) ? '' : /[\r\n]$/.test(result) ? eol : eol + eol) : '') + pending.join(eol) + eol;
|
|
150
|
+
const written = readFontAssets(result, md).assets;
|
|
151
|
+
for (const [key, asset] of wanted) if (!written.some(row => row.key === key && row.url === asset.url)) throw new Error('font: close the code or HTML block before embedding a font');
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function documentFontsCss(assets) {
|
|
157
|
+
const rules = [];
|
|
158
|
+
for (const asset of assets || []) {
|
|
159
|
+
const face = faceOf(asset);
|
|
160
|
+
if (!face || !fontDataUrl(asset?.url)) continue;
|
|
161
|
+
const weight = face.axes?.find(axis => axis.tag === 'wght');
|
|
162
|
+
const width = face.axes?.find(axis => axis.tag === 'wdth');
|
|
163
|
+
const range = weight ? [weight.min, weight.max] : face.weightRange || [face.weight, face.weight];
|
|
164
|
+
const weightCss = range[0] === range[1] ? String(range[0]) : range.join(' ');
|
|
165
|
+
const stretchCss = width ? width.min + '% ' + width.max + '%' : face.stretch + '%';
|
|
166
|
+
rules.push('@font-face{font-family:' + fontFamilyCss(face.family) + ';font-style:' + face.style + ';font-weight:' + weightCss +
|
|
167
|
+
';font-stretch:' + stretchCss + ';src:url("' + asset.url + '");font-display:swap}');
|
|
168
|
+
}
|
|
169
|
+
return rules.join('');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function htmlFontFamilies(html, md) {
|
|
173
|
+
const families = new Set();
|
|
174
|
+
const decode = value => md.utils.unescapeAll(value.replace(/\\/g, '\\\\'));
|
|
175
|
+
for (const tag of html.matchAll(/<[A-Za-z][A-Za-z0-9:-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g)) {
|
|
176
|
+
for (const attr of tag[0].matchAll(/\s(data-md-font|style)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi)) {
|
|
177
|
+
const value = decode(attr[2] ?? attr[3] ?? attr[4]), names = [];
|
|
178
|
+
if (/^<span(?=[\s>])/i.test(tag[0])) {
|
|
179
|
+
const family = attr[1].toLowerCase() === 'style' ? typography.parseStyle(value).family : null;
|
|
180
|
+
if (family?.custom) families.add(family.name);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (attr[1].toLowerCase() === 'data-md-font') names.push(value);
|
|
184
|
+
else for (const declared of value.matchAll(/(?:^|;)\s*font-family\s*:\s*((?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^;])*)/gi)) {
|
|
185
|
+
const match = /^\s*(?:"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'|([^,!]+))/.exec(declared[1]);
|
|
186
|
+
if (!match) continue;
|
|
187
|
+
const name = (match[1] ?? match[2] ?? match[3]).replace(/\\(?:([0-9a-f]{1,6})\s?|([^\r\n\f]))/gi, (_whole, hex, escaped) => {
|
|
188
|
+
const code = hex ? parseInt(hex, 16) : 0;
|
|
189
|
+
return hex ? code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : '\ufffd' : escaped;
|
|
190
|
+
});
|
|
191
|
+
if (match[3] != null && CSS_GENERIC_FONT_FAMILIES.includes(name.trim().toLowerCase())) continue;
|
|
192
|
+
names.push(name);
|
|
193
|
+
}
|
|
194
|
+
for (const name of names) { const family = normalizeFontFamily(name); if (family) families.add(family); }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return families;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function documentFontUsage(markdown, parser, defaultFont) {
|
|
201
|
+
const source = String(markdown ?? ''), md = parserFor(parser), settings = readDocumentSettings(source), use = new Map();
|
|
202
|
+
const family = setting => setting?.stack === 'named' ? setting.family : null;
|
|
203
|
+
const inherited = defaultFont === undefined ? settings.defaultfont : documentFont(defaultFont);
|
|
204
|
+
const main = family(settings.mainfont || inherited || settings.sansfont), sans = family(settings.sansfont || settings.mainfont || inherited), mono = family(settings.monofont);
|
|
205
|
+
const add = (name, text) => { if (name && !Object.hasOwn(FONT_STACKS, name.toLowerCase()) && text) use.set(name, (use.get(name) || '') + text + '\n'); };
|
|
206
|
+
if (!main && !sans && !mono && !source.includes('<')) return use;
|
|
207
|
+
const plain = html => md.utils.unescapeAll(String(html).replace(/<!--[^]*?-->/g, '').replace(/<(script|style)\b[^>]*>[^]*?<\/\1\s*>/gi, '').replace(/<[^>]*>/g, '').replace(/\\/g, '\\\\'));
|
|
208
|
+
let blockFamily = main;
|
|
209
|
+
for (const token of md.parse(splitOpeningFrontmatter(source).body, {})) {
|
|
210
|
+
if (token.type === 'heading_open') blockFamily = sans;
|
|
211
|
+
else if (token.type === 'heading_close' || token.type === 'paragraph_open') blockFamily = main;
|
|
212
|
+
else if (token.type === 'fence' && /^html(?:\s+\{[^{}]*\})?\s*$/i.test(token.info)) {
|
|
213
|
+
const text = plain(token.content);
|
|
214
|
+
add(blockFamily, text);
|
|
215
|
+
for (const name of htmlFontFamilies(token.content, md)) add(name, text);
|
|
216
|
+
}
|
|
217
|
+
else if (token.type === 'fence' || token.type === 'code_block') add(mono, token.content);
|
|
218
|
+
else if (token.type === 'html_block') {
|
|
219
|
+
const text = plain(token.content);
|
|
220
|
+
add(blockFamily, text);
|
|
221
|
+
for (const name of htmlFontFamilies(token.content, md)) add(name, text);
|
|
222
|
+
}
|
|
223
|
+
else if (token.type === 'inline') {
|
|
224
|
+
const stack = [blockFamily];
|
|
225
|
+
let html = '', text = '';
|
|
226
|
+
for (const child of token.children || []) {
|
|
227
|
+
if (child.type === 'html_inline') html += child.content;
|
|
228
|
+
if (['text', 'text_special', 'emoji', 'image', 'code_inline'].includes(child.type)) text += child.content;
|
|
229
|
+
const typed = child.meta?.typography;
|
|
230
|
+
if (typed && child.nesting === 1) {
|
|
231
|
+
stack.push(typed.style.family ? typed.style.family.custom ? typed.style.family.name : null : stack.at(-1)); continue;
|
|
232
|
+
}
|
|
233
|
+
if (typed && child.nesting === -1) { if (stack.length > 1) stack.pop(); continue; }
|
|
234
|
+
if (child.type === 'code_inline') add(mono, child.content);
|
|
235
|
+
else if (['text', 'text_special', 'emoji', 'image'].includes(child.type)) add(stack.at(-1), child.content);
|
|
236
|
+
}
|
|
237
|
+
for (const name of htmlFontFamilies(html, md)) add(name, text);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
add(main, settings.title); add(main, settings.subtitle);
|
|
241
|
+
return use;
|
|
242
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {frontMatterFrame, frontMatterLine} from
|
|
3
|
-
import {normalizeFontFamily, fontFamilyCss, FONT_STACKS} from
|
|
2
|
+
import {frontMatterFrame, frontMatterLine} from "./frontmatter.mjs";
|
|
3
|
+
import {normalizeFontFamily, fontFamilyCss, FONT_STACKS} from "./md-marks.mjs";
|
|
4
4
|
|
|
5
5
|
const NUMBER = /^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6
6
|
export const DOCUMENT_LEADING = 1.75 / 1.1;
|
package/dist/ledger/format.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {_rapierPrepareHistoryRecords, _rapierValidMetadata, _rapierTransformMetadata} from
|
|
3
|
-
import {pictureHistorySplices} from
|
|
4
|
-
import {HistorySource} from
|
|
5
|
-
import {authoredHistory} from
|
|
6
|
-
import {textRoot, rootAfter, sha256, sha256PartsAsync, createSHA256} from
|
|
7
|
-
import {canonicalJSON, canonicalParts, canonicalRowsParts, ownRow, ownLedger, isOwnedLedger, plainData, documentText, utf8Length, exactKeys, nonnegative, fault} from
|
|
8
|
-
import {diff} from
|
|
9
|
-
import {minimalHistorySplice} from
|
|
10
|
-
import {evaluateWill} from
|
|
2
|
+
import {_rapierPrepareHistoryRecords, _rapierValidMetadata, _rapierTransformMetadata} from "./journal-records.mjs";
|
|
3
|
+
import {pictureHistorySplices} from "./history.mjs";
|
|
4
|
+
import {HistorySource} from "./source.mjs";
|
|
5
|
+
import {authoredHistory} from "./transport.mjs";
|
|
6
|
+
import {textRoot, rootAfter, sha256, sha256PartsAsync, createSHA256} from "./hash.mjs";
|
|
7
|
+
import {canonicalJSON, canonicalParts, canonicalRowsParts, ownRow, ownLedger, isOwnedLedger, plainData, documentText, utf8Length, exactKeys, nonnegative, fault} from "./data.mjs";
|
|
8
|
+
import {diff} from "./diff/diff.mjs";
|
|
9
|
+
import {minimalHistorySplice} from "./inverse-source.mjs";
|
|
10
|
+
import {evaluateWill} from "./will.mjs";
|
|
11
11
|
export {textRoot, rootAfter};
|
|
12
12
|
const rootShape = value => typeof value === 'string' && /^\d+:[a-z0-9]+:[a-z0-9]+$/.test(value);
|
|
13
13
|
function* checkedRecords(ledger, prepared, from = null) {
|
|
@@ -170,6 +170,7 @@ const expandedRows = new WeakMap();
|
|
|
170
170
|
const sameSplices = (a, b) => a === b || a.length === b.length &&
|
|
171
171
|
a.every((row, i) => row.pos === b[i].pos && row.removed === b[i].removed && row.inserted === b[i].inserted);
|
|
172
172
|
const sameEffect = (a, b) => a === b || !!a && !!b && canonicalJSON(a) === canonicalJSON(b);
|
|
173
|
+
const RECENT_TEXT_LIMIT = 1 << 19;
|
|
173
174
|
let recentLedgers = [];
|
|
174
175
|
function* produceLedger({text, metadata, records, documentAuthority, revision, root, complete = false, earlier = records} = {}, native = false) {
|
|
175
176
|
if (!_rapierValidMetadata(metadata)) throw fault('document metadata required');
|
|
@@ -239,9 +240,10 @@ function* produceLedger({text, metadata, records, documentAuthority, revision, r
|
|
|
239
240
|
const replayedMetadata = prepared.replayMetadata(startMetadata);
|
|
240
241
|
if (!replayedMetadata || canonicalJSON(replayedMetadata) !== canonicalJSON(metadata)) throw fault('metadata does not describe this document');
|
|
241
242
|
const ledger = {...payload, sha256: yield* checksum(envelopeParts(payload), native)};
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
const others = recentLedgers.filter(row => row.rows[0] !== expanded[0] && row.documentAuthority === documentAuthority);
|
|
244
|
+
recentLedgers = text.length + startText.length > RECENT_TEXT_LIMIT ? [] : [{rows: expanded, headText: text, headMetadata: canonicalJSON(metadata),
|
|
245
|
+
headBytes, startText, startMetadata: {...startMetadata}, startFacts, startBytes: bytes, documentAuthority, complete: payload.complete,
|
|
246
|
+
startRoot, startRevision, chainRoot: payload.head.root, chainRevision: payload.head.revision}, ...others].slice(0, 4);
|
|
245
247
|
ownLedger(ledger);
|
|
246
248
|
proofs.set(ledger, {shell: Array.from(envelopeParts({...ledger, start: {...ledger.start, text: ''}})).join(''), startText,
|
|
247
249
|
text, metadata: {...metadata}, root, revision, prepared: _rapierPrepareHistoryRecords(ledger.records),
|
package/dist/ledger/history.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {_rapierTransformSplices, _rapierValidMetadata, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierPrepareHistoryRecords} from
|
|
3
|
-
import {projectInverseSource} from
|
|
4
|
-
import {sourceView, HistorySource} from
|
|
5
|
-
import {textRootParts, rootAfter} from
|
|
6
|
-
import {authoredHistory, authoredPlacements} from
|
|
7
|
-
import {plainData, canonicalJSON} from
|
|
8
|
-
import {evaluateWill} from
|
|
2
|
+
import {_rapierTransformSplices, _rapierValidMetadata, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierPrepareHistoryRecords} from "./journal-records.mjs";
|
|
3
|
+
import {projectInverseSource} from "./inverse-source.mjs";
|
|
4
|
+
import {sourceView, HistorySource} from "./source.mjs";
|
|
5
|
+
import {textRootParts, rootAfter} from "./hash.mjs";
|
|
6
|
+
import {authoredHistory, authoredPlacements} from "./transport.mjs";
|
|
7
|
+
import {plainData, canonicalJSON} from "./data.mjs";
|
|
8
|
+
import {evaluateWill} from "./will.mjs";
|
|
9
9
|
|
|
10
10
|
const historyFailure = reason => ({ok: false, reason});
|
|
11
11
|
const historyInteger = value => Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {_rapierTransformSplices, _rapierHistoryEffects} from
|
|
3
|
-
import {HistorySource} from
|
|
4
|
-
import {authoredPlacements} from
|
|
5
|
-
import {canonicalJSON} from
|
|
6
|
-
import {diff} from
|
|
2
|
+
import {_rapierTransformSplices, _rapierHistoryEffects} from "./journal-records.mjs";
|
|
3
|
+
import {HistorySource} from "./source.mjs";
|
|
4
|
+
import {authoredPlacements} from "./authored-origin.mjs";
|
|
5
|
+
import {canonicalJSON} from "./data.mjs";
|
|
6
|
+
import {diff} from "./diff/index.mjs";
|
|
7
7
|
|
|
8
8
|
const historyFailure = reason => ({ok: false, reason});
|
|
9
9
|
const historyBoundary = (source, at) => Number.isSafeInteger(at) && at >= 0 && !Object.is(at, -0) && at <= source.length &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {fields, scalar, plainPrototype} from
|
|
3
|
-
import {transformText} from
|
|
4
|
-
import {dataImage} from
|
|
2
|
+
import {fields, scalar, plainPrototype} from "./data.mjs";
|
|
3
|
+
import {transformText} from "./source.mjs";
|
|
4
|
+
import {dataImage} from "./md-assets.mjs";
|
|
5
5
|
|
|
6
6
|
const _RAPIER_TRANSACTION_ACTOR_LIMIT = 160;
|
|
7
7
|
const _RAPIER_TRANSACTION_OPERATION_LIMIT = 96;
|