@patterkit/ops 0.16.1 → 0.16.2

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ian Thomas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -1040,6 +1040,102 @@ declare function scriptToDocx(doc: ScriptDoc): Promise<Buffer>;
1040
1040
  /** Render the readable screenplay as a PDF. Resolves once PDFKit has flushed the whole document. */
1041
1041
  declare function scriptToPdf(doc: ScriptDoc): Promise<Buffer>;
1042
1042
 
1043
+ var serif = [
1044
+ "@fontsource/newsreader/files/newsreader-latin-400-normal.woff",
1045
+ "@fontsource/newsreader/files/newsreader-latin-ext-400-normal.woff",
1046
+ "@fontsource/newsreader/files/newsreader-vietnamese-400-normal.woff"
1047
+ ];
1048
+ var serifBold = [
1049
+ "@fontsource/newsreader/files/newsreader-latin-600-normal.woff",
1050
+ "@fontsource/newsreader/files/newsreader-latin-ext-600-normal.woff",
1051
+ "@fontsource/newsreader/files/newsreader-vietnamese-600-normal.woff"
1052
+ ];
1053
+ var serifItalic = [
1054
+ "@fontsource/newsreader/files/newsreader-latin-400-italic.woff",
1055
+ "@fontsource/newsreader/files/newsreader-latin-ext-400-italic.woff",
1056
+ "@fontsource/newsreader/files/newsreader-vietnamese-400-italic.woff"
1057
+ ];
1058
+ var serifBoldItalic = [
1059
+ "@fontsource/newsreader/files/newsreader-latin-600-italic.woff",
1060
+ "@fontsource/newsreader/files/newsreader-latin-ext-600-italic.woff",
1061
+ "@fontsource/newsreader/files/newsreader-vietnamese-600-italic.woff"
1062
+ ];
1063
+ var sans = [
1064
+ "@fontsource/inter/files/inter-latin-400-normal.woff",
1065
+ "@fontsource/inter/files/inter-latin-ext-400-normal.woff",
1066
+ "@fontsource/inter/files/inter-greek-400-normal.woff",
1067
+ "@fontsource/inter/files/inter-greek-ext-400-normal.woff",
1068
+ "@fontsource/inter/files/inter-cyrillic-400-normal.woff",
1069
+ "@fontsource/inter/files/inter-cyrillic-ext-400-normal.woff",
1070
+ "@fontsource/inter/files/inter-vietnamese-400-normal.woff"
1071
+ ];
1072
+ var sansBold = [
1073
+ "@fontsource/inter/files/inter-latin-600-normal.woff",
1074
+ "@fontsource/inter/files/inter-latin-ext-600-normal.woff",
1075
+ "@fontsource/inter/files/inter-greek-600-normal.woff",
1076
+ "@fontsource/inter/files/inter-greek-ext-600-normal.woff",
1077
+ "@fontsource/inter/files/inter-cyrillic-600-normal.woff",
1078
+ "@fontsource/inter/files/inter-cyrillic-ext-600-normal.woff",
1079
+ "@fontsource/inter/files/inter-vietnamese-600-normal.woff"
1080
+ ];
1081
+ var mono = [
1082
+ "@ibm/plex-mono/fonts/complete/woff/IBMPlexMono-Regular.woff"
1083
+ ];
1084
+ var fallback = [
1085
+ "@fontsource/noto-sans/files/noto-sans-latin-400-normal.woff",
1086
+ "@fontsource/noto-sans/files/noto-sans-latin-ext-400-normal.woff",
1087
+ "@fontsource/noto-sans/files/noto-sans-greek-400-normal.woff",
1088
+ "@fontsource/noto-sans/files/noto-sans-greek-ext-400-normal.woff",
1089
+ "@fontsource/noto-sans/files/noto-sans-cyrillic-400-normal.woff",
1090
+ "@fontsource/noto-sans/files/noto-sans-cyrillic-ext-400-normal.woff",
1091
+ "@fontsource/noto-sans/files/noto-sans-vietnamese-400-normal.woff",
1092
+ "@fontsource/noto-sans/files/noto-sans-devanagari-400-normal.woff",
1093
+ "@fontsource/noto-sans-math/files/noto-sans-math-latin-400-normal.woff"
1094
+ ];
1095
+ var emoji = [
1096
+ "@fontsource/noto-emoji/files/noto-emoji-emoji-400-normal.woff",
1097
+ "@fontsource/noto-emoji/files/noto-emoji-0-400-normal.woff",
1098
+ "@fontsource/noto-emoji/files/noto-emoji-1-400-normal.woff",
1099
+ "@fontsource/noto-emoji/files/noto-emoji-2-400-normal.woff",
1100
+ "@fontsource/noto-emoji/files/noto-emoji-3-400-normal.woff",
1101
+ "@fontsource/noto-emoji/files/noto-emoji-4-400-normal.woff",
1102
+ "@fontsource/noto-emoji/files/noto-emoji-5-400-normal.woff",
1103
+ "@fontsource/noto-emoji/files/noto-emoji-6-400-normal.woff",
1104
+ "@fontsource/noto-emoji/files/noto-emoji-7-400-normal.woff",
1105
+ "@fontsource/noto-emoji/files/noto-emoji-8-400-normal.woff",
1106
+ "@fontsource/noto-emoji/files/noto-emoji-9-400-normal.woff",
1107
+ "@fontsource/noto-emoji/files/noto-emoji-10-400-normal.woff"
1108
+ ];
1109
+ var FACES = {
1110
+ serif: serif,
1111
+ serifBold: serifBold,
1112
+ serifItalic: serifItalic,
1113
+ serifBoldItalic: serifBoldItalic,
1114
+ sans: sans,
1115
+ sansBold: sansBold,
1116
+ mono: mono,
1117
+ fallback: fallback,
1118
+ emoji: emoji
1119
+ };
1120
+
1121
+ /** A face the PDF export registers: the design's three roles, plus the two fallbacks every role shares. */
1122
+ type ScriptFace = keyof typeof FACES;
1123
+ /** Each face's files, as module paths (`<package>/<file>`), in the order a character tries them. */
1124
+ declare const SCRIPT_FONT_FILES: Readonly<Record<ScriptFace, readonly string[]>>;
1125
+ /** The packages the faces come from, each of which carries the licence its fonts travel under. */
1126
+ declare const SCRIPT_FONT_PACKAGES: readonly string[];
1127
+ /** Read the files from these paths, keyed by module path, instead of resolving them from node_modules;
1128
+ * and the licences from `licencePaths`, keyed by package. For a bundle that embeds the files itself
1129
+ * (the standalone CLI, Patterpad). */
1130
+ declare function useScriptFontFiles(paths: Readonly<Record<string, string>>, licencePaths?: Readonly<Record<string, string>>): void;
1131
+ /** Where a font package's licence file is in node_modules (LICENSE, LICENSE.txt or OFL.txt). */
1132
+ declare function scriptFontLicencePath(pkg: string): string;
1133
+ /** Each font package's licence text, for a listing of what the PDF export embeds and under what terms. */
1134
+ declare function scriptFontLicences(): {
1135
+ package: string;
1136
+ text: string;
1137
+ }[];
1138
+
1043
1139
  type InitVcs = "git" | "perforce" | "plastic" | "svn";
1044
1140
  /** Whether the compiled `.patterc` bundle is committed or built in CI (spec §11). */
1045
1141
  type BundlePosture = "commit" | "ignore";
@@ -1645,4 +1741,4 @@ declare const REFUSE_MISSING_SHARE = 0.25;
1645
1741
  /** Plan an import. Reads the handoff record named in the document from the project. */
1646
1742
  declare function planEditableImport(loaded: LoadedProject, returned: ReturnedDoc, opts: ImportOptions): ImportPlan;
1647
1743
 
1648
- export { AUDIO_MANIFEST_FILE, AUDIO_MANIFEST_SCHEMA, AUTHORING_HANDLED, type ApplyStats, type AudioEntry, type AudioRung, type AudioSnapshot, type BuildPlan, type BundlePosture, CONFLICT_SIDECAR, type CharacterReport, type Conflict, type ConflictKind, type ContentError, type CoverageAsyncHooks, type CoverageBeat, type CoverageHooks, type CoverageOptions, type CoverageOrder, type CoverageReport, type DecisionPlan, type DecisionResult, type DocumentManifest, type EditableScript, type EditableScriptOptions, type ExportOptions, type FormatResult, GAME_SCOPES_OWNER, type GameScopes, type GameScopesIssue, HANDOFF_DIR, type HandoffIssue, type HygieneIssue, type ImportOptions, type ImportPlan, type ImportProblem, type ImportReport, type InitOptions, type InitResult, type InitVcs, type LoadedProject, type LocCatalog, type LocEntry, type LocaleReport, type MergeFileType, type MergeResult, type MergedShard, NotAPackError, type OpenSuggestion, PATTER_SCOPES_FILE, PATTER_SCOPES_OWNER, type PinPlan, type PinnedName, type PlannedWrite, type PlayEvent, type PlayOptions, type PlayOutcome, type PlayResult, type ProvenanceCheck, RARE_REACH_PCT, REFUSE_MISSING_SHARE, type Random, type ReadBox, type ReadComment, type ReadItem, type ReadMarker, type ReadParagraph, type ReadText, type ReplaceHit, type ReplaceOptions, type ReplacePlan, type ReportData, type ReportTotals, type ResolveEntry, type ReturnedDoc, SHARD_EXTENSIONS, type SceneReport, type ScriptDoc, type ScriptElement, type SearchFocus, type SuggestionDecision, type TrackedChange, type UnpackMergeResult, type UnpackResult, UnsafeEntryError, UnsupportedMergeError, type ValidateResult, type VoiceLine, type VoiceScript, type VoicedCounts, type WebExport, type WrittenCounts, applyLoc, applySceneOrder, applySuggestionDecisions, applyWrites, audioFolderRungs, audioFoldersActive, audioManifest, audioManifestWrite, besideBundlePath, bundleOutputPath, catalogToJson, catalogToPo, catalogToXlsx, classesForChannel, compileLoaded, cueLabel, currentBundlePosture, defaultBundlePath, defaultGameScopesDir, detectMergeType, discoverGameScopes, drivableScopes, exportBlockers, exportEditableScript, extractLoc, findProjectFile, formatMarker, gameScopeTokens, gameScopesCatalogue, gameScopesSnapshot, handoffPath, handoffWrite, hostScopeTokens, issueMarkerCodes, jsonToCatalog, leastReachedFirst, listOpenSuggestions, listProjectTags, loadProject, loadProjectLanding, markerLength, newHandoffId, parseHandoff, patterScopesFile, patterScopesStale, patterScopesWrite, pickAudio, planBuild, planEditableImport, planPins, planReturnedWorld, planShareScopes, planWorldSave, poToCatalog, previewRegistry, proposeCoverageDrivers, qualifierNamer, reachabilityIssues, readEditableDocx, readGameScopes, readHandoff, readHandoffs, readMarker, renderCoverageText, renderPlay, renderReportText, resolveDocumentation, resolveStart, runCoverage, runCoverageAsync, runExport, runExportFull, runExportHtml, runExportWeb, runFormat, runInit, runMerge, runPack, runPlay, runPropertyUsage, runReplace, runReport, runReportXlsx, runResolve, runScriptDoc, runSearch, runStatusBrowse, runTagBrowse, runUnpack, runUnpackMerge, runValidate, runVoiceScript, scanAudio, scanAudioStatus, sceneIdForShard, scriptToDocx, scriptToPdf, serialiseHandoff, setCut, sidecarIssues, staleParts, vcsConfigWrites, voiceScriptToXlsx, worldSettingsScopes, xlsxToCatalog };
1744
+ export { AUDIO_MANIFEST_FILE, AUDIO_MANIFEST_SCHEMA, AUTHORING_HANDLED, type ApplyStats, type AudioEntry, type AudioRung, type AudioSnapshot, type BuildPlan, type BundlePosture, CONFLICT_SIDECAR, type CharacterReport, type Conflict, type ConflictKind, type ContentError, type CoverageAsyncHooks, type CoverageBeat, type CoverageHooks, type CoverageOptions, type CoverageOrder, type CoverageReport, type DecisionPlan, type DecisionResult, type DocumentManifest, type EditableScript, type EditableScriptOptions, type ExportOptions, type FormatResult, GAME_SCOPES_OWNER, type GameScopes, type GameScopesIssue, HANDOFF_DIR, type HandoffIssue, type HygieneIssue, type ImportOptions, type ImportPlan, type ImportProblem, type ImportReport, type InitOptions, type InitResult, type InitVcs, type LoadedProject, type LocCatalog, type LocEntry, type LocaleReport, type MergeFileType, type MergeResult, type MergedShard, NotAPackError, type OpenSuggestion, PATTER_SCOPES_FILE, PATTER_SCOPES_OWNER, type PinPlan, type PinnedName, type PlannedWrite, type PlayEvent, type PlayOptions, type PlayOutcome, type PlayResult, type ProvenanceCheck, RARE_REACH_PCT, REFUSE_MISSING_SHARE, type Random, type ReadBox, type ReadComment, type ReadItem, type ReadMarker, type ReadParagraph, type ReadText, type ReplaceHit, type ReplaceOptions, type ReplacePlan, type ReportData, type ReportTotals, type ResolveEntry, type ReturnedDoc, SCRIPT_FONT_FILES, SCRIPT_FONT_PACKAGES, SHARD_EXTENSIONS, type SceneReport, type ScriptDoc, type ScriptElement, type ScriptFace, type SearchFocus, type SuggestionDecision, type TrackedChange, type UnpackMergeResult, type UnpackResult, UnsafeEntryError, UnsupportedMergeError, type ValidateResult, type VoiceLine, type VoiceScript, type VoicedCounts, type WebExport, type WrittenCounts, applyLoc, applySceneOrder, applySuggestionDecisions, applyWrites, audioFolderRungs, audioFoldersActive, audioManifest, audioManifestWrite, besideBundlePath, bundleOutputPath, catalogToJson, catalogToPo, catalogToXlsx, classesForChannel, compileLoaded, cueLabel, currentBundlePosture, defaultBundlePath, defaultGameScopesDir, detectMergeType, discoverGameScopes, drivableScopes, exportBlockers, exportEditableScript, extractLoc, findProjectFile, formatMarker, gameScopeTokens, gameScopesCatalogue, gameScopesSnapshot, handoffPath, handoffWrite, hostScopeTokens, issueMarkerCodes, jsonToCatalog, leastReachedFirst, listOpenSuggestions, listProjectTags, loadProject, loadProjectLanding, markerLength, newHandoffId, parseHandoff, patterScopesFile, patterScopesStale, patterScopesWrite, pickAudio, planBuild, planEditableImport, planPins, planReturnedWorld, planShareScopes, planWorldSave, poToCatalog, previewRegistry, proposeCoverageDrivers, qualifierNamer, reachabilityIssues, readEditableDocx, readGameScopes, readHandoff, readHandoffs, readMarker, renderCoverageText, renderPlay, renderReportText, resolveDocumentation, resolveStart, runCoverage, runCoverageAsync, runExport, runExportFull, runExportHtml, runExportWeb, runFormat, runInit, runMerge, runPack, runPlay, runPropertyUsage, runReplace, runReport, runReportXlsx, runResolve, runScriptDoc, runSearch, runStatusBrowse, runTagBrowse, runUnpack, runUnpackMerge, runValidate, runVoiceScript, scanAudio, scanAudioStatus, sceneIdForShard, scriptFontLicencePath, scriptFontLicences, scriptToDocx, scriptToPdf, serialiseHandoff, setCut, sidecarIssues, staleParts, useScriptFontFiles, vcsConfigWrites, voiceScriptToXlsx, worldSettingsScopes, xlsxToCatalog };
package/dist/index.js CHANGED
@@ -1123,10 +1123,10 @@ function reachabilityIssues(loaded, compiled) {
1123
1123
  return bare.startsWith("@scene.");
1124
1124
  };
1125
1125
  const monotonic = (key) => latched.has(key) && !broken.has(key) && !preset.has(key) && projectOwned(key);
1126
- const cache = /* @__PURE__ */ new Map();
1126
+ const cache2 = /* @__PURE__ */ new Map();
1127
1127
  const inFlight = /* @__PURE__ */ new Set();
1128
1128
  const mustHold = (key) => {
1129
- const done = cache.get(key);
1129
+ const done = cache2.get(key);
1130
1130
  if (done !== void 0) return { need: done, cut: false };
1131
1131
  if (inFlight.has(key)) return { need: /* @__PURE__ */ new Set(), cut: true };
1132
1132
  const routes = writers.get(key);
@@ -1147,7 +1147,7 @@ function reachabilityIssues(loaded, compiled) {
1147
1147
  }
1148
1148
  inFlight.delete(key);
1149
1149
  const result = shared ?? /* @__PURE__ */ new Set();
1150
- if (!cut) cache.set(key, result);
1150
+ if (!cut) cache2.set(key, result);
1151
1151
  return { need: result, cut };
1152
1152
  };
1153
1153
  const refute = (gate) => {
@@ -3847,6 +3847,114 @@ async function scriptToDocx(doc) {
3847
3847
 
3848
3848
  // src/script-pdf.ts
3849
3849
  import PDFDocument from "pdfkit";
3850
+
3851
+ // src/script-fonts.ts
3852
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
3853
+ import { createRequire } from "module";
3854
+ import { join as join8 } from "path";
3855
+
3856
+ // src/script-fonts.json
3857
+ var script_fonts_default = {
3858
+ serif: [
3859
+ "@fontsource/newsreader/files/newsreader-latin-400-normal.woff",
3860
+ "@fontsource/newsreader/files/newsreader-latin-ext-400-normal.woff",
3861
+ "@fontsource/newsreader/files/newsreader-vietnamese-400-normal.woff"
3862
+ ],
3863
+ serifBold: [
3864
+ "@fontsource/newsreader/files/newsreader-latin-600-normal.woff",
3865
+ "@fontsource/newsreader/files/newsreader-latin-ext-600-normal.woff",
3866
+ "@fontsource/newsreader/files/newsreader-vietnamese-600-normal.woff"
3867
+ ],
3868
+ serifItalic: [
3869
+ "@fontsource/newsreader/files/newsreader-latin-400-italic.woff",
3870
+ "@fontsource/newsreader/files/newsreader-latin-ext-400-italic.woff",
3871
+ "@fontsource/newsreader/files/newsreader-vietnamese-400-italic.woff"
3872
+ ],
3873
+ serifBoldItalic: [
3874
+ "@fontsource/newsreader/files/newsreader-latin-600-italic.woff",
3875
+ "@fontsource/newsreader/files/newsreader-latin-ext-600-italic.woff",
3876
+ "@fontsource/newsreader/files/newsreader-vietnamese-600-italic.woff"
3877
+ ],
3878
+ sans: [
3879
+ "@fontsource/inter/files/inter-latin-400-normal.woff",
3880
+ "@fontsource/inter/files/inter-latin-ext-400-normal.woff",
3881
+ "@fontsource/inter/files/inter-greek-400-normal.woff",
3882
+ "@fontsource/inter/files/inter-greek-ext-400-normal.woff",
3883
+ "@fontsource/inter/files/inter-cyrillic-400-normal.woff",
3884
+ "@fontsource/inter/files/inter-cyrillic-ext-400-normal.woff",
3885
+ "@fontsource/inter/files/inter-vietnamese-400-normal.woff"
3886
+ ],
3887
+ sansBold: [
3888
+ "@fontsource/inter/files/inter-latin-600-normal.woff",
3889
+ "@fontsource/inter/files/inter-latin-ext-600-normal.woff",
3890
+ "@fontsource/inter/files/inter-greek-600-normal.woff",
3891
+ "@fontsource/inter/files/inter-greek-ext-600-normal.woff",
3892
+ "@fontsource/inter/files/inter-cyrillic-600-normal.woff",
3893
+ "@fontsource/inter/files/inter-cyrillic-ext-600-normal.woff",
3894
+ "@fontsource/inter/files/inter-vietnamese-600-normal.woff"
3895
+ ],
3896
+ mono: [
3897
+ "@ibm/plex-mono/fonts/complete/woff/IBMPlexMono-Regular.woff"
3898
+ ],
3899
+ fallback: [
3900
+ "@fontsource/noto-sans/files/noto-sans-latin-400-normal.woff",
3901
+ "@fontsource/noto-sans/files/noto-sans-latin-ext-400-normal.woff",
3902
+ "@fontsource/noto-sans/files/noto-sans-greek-400-normal.woff",
3903
+ "@fontsource/noto-sans/files/noto-sans-greek-ext-400-normal.woff",
3904
+ "@fontsource/noto-sans/files/noto-sans-cyrillic-400-normal.woff",
3905
+ "@fontsource/noto-sans/files/noto-sans-cyrillic-ext-400-normal.woff",
3906
+ "@fontsource/noto-sans/files/noto-sans-vietnamese-400-normal.woff",
3907
+ "@fontsource/noto-sans/files/noto-sans-devanagari-400-normal.woff",
3908
+ "@fontsource/noto-sans-math/files/noto-sans-math-latin-400-normal.woff"
3909
+ ],
3910
+ emoji: [
3911
+ "@fontsource/noto-emoji/files/noto-emoji-emoji-400-normal.woff",
3912
+ "@fontsource/noto-emoji/files/noto-emoji-0-400-normal.woff",
3913
+ "@fontsource/noto-emoji/files/noto-emoji-1-400-normal.woff",
3914
+ "@fontsource/noto-emoji/files/noto-emoji-2-400-normal.woff",
3915
+ "@fontsource/noto-emoji/files/noto-emoji-3-400-normal.woff",
3916
+ "@fontsource/noto-emoji/files/noto-emoji-4-400-normal.woff",
3917
+ "@fontsource/noto-emoji/files/noto-emoji-5-400-normal.woff",
3918
+ "@fontsource/noto-emoji/files/noto-emoji-6-400-normal.woff",
3919
+ "@fontsource/noto-emoji/files/noto-emoji-7-400-normal.woff",
3920
+ "@fontsource/noto-emoji/files/noto-emoji-8-400-normal.woff",
3921
+ "@fontsource/noto-emoji/files/noto-emoji-9-400-normal.woff",
3922
+ "@fontsource/noto-emoji/files/noto-emoji-10-400-normal.woff"
3923
+ ]
3924
+ };
3925
+
3926
+ // src/script-fonts.ts
3927
+ var SCRIPT_FONT_FILES = script_fonts_default;
3928
+ var SCRIPT_FONT_PACKAGES = [
3929
+ ...new Set(Object.values(script_fonts_default).flat().map((spec) => spec.split("/").slice(0, spec.startsWith("@") ? 2 : 1).join("/")))
3930
+ ];
3931
+ var require2 = createRequire(import.meta.url);
3932
+ var located = {};
3933
+ var licences = {};
3934
+ var cache = /* @__PURE__ */ new Map();
3935
+ function useScriptFontFiles(paths, licencePaths = {}) {
3936
+ located = { ...paths };
3937
+ licences = { ...licencePaths };
3938
+ cache.clear();
3939
+ }
3940
+ function scriptFontLicencePath(pkg) {
3941
+ const dir = (require2.resolve.paths(pkg) ?? []).map((p) => join8(p, pkg)).find((d) => existsSync5(d));
3942
+ const file = dir && readdirSync4(dir).find((f) => /^(licen[cs]e|ofl)/i.test(f));
3943
+ if (!dir || !file) throw new Error(`no licence file found for ${pkg}`);
3944
+ return join8(dir, file);
3945
+ }
3946
+ function scriptFontLicences() {
3947
+ return SCRIPT_FONT_PACKAGES.map((pkg) => ({ package: pkg, text: readFileSync7(licences[pkg] ?? scriptFontLicencePath(pkg), "utf8") }));
3948
+ }
3949
+ function scriptFontFiles(face) {
3950
+ return script_fonts_default[face].map((spec) => {
3951
+ let bytes = cache.get(spec);
3952
+ if (!bytes) cache.set(spec, bytes = readFileSync7(located[spec] ?? require2.resolve(spec)));
3953
+ return bytes;
3954
+ });
3955
+ }
3956
+
3957
+ // src/script-pdf.ts
3850
3958
  var MARGIN = 64;
3851
3959
  var INDENT_STEP = 20;
3852
3960
  var EDGE_INSET = 12;
@@ -3871,36 +3979,47 @@ var LINE = hex(TOKENS.line);
3871
3979
  var EDGE = "#d7d0c3";
3872
3980
  var SERIF2 = (bold, italic) => bold && italic ? "Serif-BoldItalic" : bold ? "Serif-Bold" : italic ? "Serif-Italic" : "Serif";
3873
3981
  var isEmoji = (cp) => cp >= 126976 || cp >= 9728 && cp <= 10175;
3982
+ var ROLES = {
3983
+ "Serif": "serif",
3984
+ "Serif-Bold": "serifBold",
3985
+ "Serif-Italic": "serifItalic",
3986
+ "Serif-BoldItalic": "serifBoldItalic",
3987
+ "Sans": "sans",
3988
+ "Sans-Bold": "sansBold",
3989
+ "Mono": "mono"
3990
+ };
3874
3991
  async function scriptToPdf(doc) {
3875
- const { scriptFont } = await import("./script-fonts-AQNQOADI.js");
3876
3992
  return new Promise((resolve7, reject) => {
3877
3993
  const pdf = new PDFDocument({ size: "A4", margins: { top: MARGIN, bottom: MARGIN, left: MARGIN, right: MARGIN }, info: { Title: doc.project, Author: "Patter" }, font: null });
3878
- pdf.registerFont("Serif", scriptFont("serif"));
3879
- pdf.registerFont("Serif-Bold", scriptFont("serifBold"));
3880
- pdf.registerFont("Serif-Italic", scriptFont("serifItalic"));
3881
- pdf.registerFont("Serif-BoldItalic", scriptFont("serifBoldItalic"));
3882
- pdf.registerFont("Sans", scriptFont("sans"));
3883
- pdf.registerFont("Sans-Bold", scriptFont("sansBold"));
3884
- pdf.registerFont("Mono", scriptFont("mono"));
3885
- pdf.registerFont("Symbol", scriptFont("symbol"));
3886
- pdf.registerFont("Emoji", scriptFont("emoji"));
3994
+ const register = (prefix, face, firstIsPrefix) => scriptFontFiles(face).map((bytes, i) => {
3995
+ const name = firstIsPrefix && i === 0 ? prefix : `${prefix}~${i}`;
3996
+ pdf.registerFont(name, bytes);
3997
+ return name;
3998
+ });
3999
+ const roleFiles = {};
4000
+ for (const [role, face] of Object.entries(ROLES)) roleFiles[role] = register(role, face, true);
4001
+ const fallback = register("Fallback", "fallback", false);
4002
+ const emoji = register("Emoji", "emoji", false);
3887
4003
  const coverage = {};
3888
4004
  const ascender = {};
3889
- for (const name of ["Serif", "Serif-Bold", "Serif-Italic", "Serif-BoldItalic", "Sans", "Sans-Bold", "Mono"]) {
4005
+ for (const name of [...Object.values(roleFiles).flat(), ...fallback, ...emoji]) {
3890
4006
  pdf.font(name);
3891
4007
  const f = pdf._font;
3892
4008
  coverage[name] = (cp) => f.font.hasGlyphForCodePoint(cp);
3893
4009
  ascender[name] = f.ascender;
3894
4010
  }
3895
4011
  const ascentPt = (name, size) => ascender[name] / 1e3 * size;
4012
+ const chains = {};
4013
+ for (const role of Object.keys(ROLES)) chains[role] = [...roleFiles[role], ...fallback];
4014
+ const pick = (chain, cp) => chain.find((n) => coverage[n](cp));
3896
4015
  const split = (text, primary, color) => {
3897
- const cov = coverage[primary];
4016
+ const chain = chains[primary];
3898
4017
  const out = [];
3899
4018
  let buf = "";
3900
4019
  let cur = primary;
3901
4020
  for (const ch of text) {
3902
4021
  const cp = ch.codePointAt(0);
3903
- const f = isEmoji(cp) ? "Emoji" : cov(cp) ? primary : "Symbol";
4022
+ const f = (isEmoji(cp) ? pick(emoji, cp) : void 0) ?? pick(chain, cp) ?? primary;
3904
4023
  if (f !== cur) {
3905
4024
  if (buf) out.push({ text: buf, font: cur, color });
3906
4025
  buf = "";
@@ -4066,8 +4185,8 @@ async function scriptToPdf(doc) {
4066
4185
  }
4067
4186
 
4068
4187
  // src/init.ts
4069
- import { join as join8, basename as basename6, resolve as resolve5 } from "path";
4070
- import { readdirSync as readdirSync4, existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
4188
+ import { join as join9, basename as basename6, resolve as resolve5 } from "path";
4189
+ import { readdirSync as readdirSync5, existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
4071
4190
  import { newId, slug, canonicalStringify as canonicalStringify7 } from "@patterkit/core";
4072
4191
  import { FLOW_SCHEMA, PROJECT_SCHEMA, STRINGS_SCHEMA as STRINGS_SCHEMA2 } from "@patterkit/model";
4073
4192
  function runInit(opts) {
@@ -4076,7 +4195,7 @@ function runInit(opts) {
4076
4195
  const fileSlug = slug(name);
4077
4196
  let existing = [];
4078
4197
  try {
4079
- existing = readdirSync4(dir).filter((f) => f.endsWith(".patterproj"));
4198
+ existing = readdirSync5(dir).filter((f) => f.endsWith(".patterproj"));
4080
4199
  } catch {
4081
4200
  }
4082
4201
  if (existing.length > 0) throw new Error(`a project already exists here: ${existing[0]}`);
@@ -4120,31 +4239,31 @@ function runInit(opts) {
4120
4239
  strings: { [beatId]: `Welcome to ${name}. This is the first line of your story - replace it with your own.` }
4121
4240
  };
4122
4241
  const bundle = opts.bundle ?? "commit";
4123
- const projectFile = join8(dir, `${fileSlug}.patterproj`);
4242
+ const projectFile = join9(dir, `${fileSlug}.patterproj`);
4124
4243
  const writes = [
4125
- { path: join8(dir, "scenes", "start.patterflow"), content: canonicalStringify7(flow) },
4126
- { path: join8(dir, "loc", "en", "start.patterloc"), content: canonicalStringify7(locale) },
4127
- { path: join8(dir, ".editorconfig"), content: EDITORCONFIG },
4244
+ { path: join9(dir, "scenes", "start.patterflow"), content: canonicalStringify7(flow) },
4245
+ { path: join9(dir, "loc", "en", "start.patterloc"), content: canonicalStringify7(locale) },
4246
+ { path: join9(dir, ".editorconfig"), content: EDITORCONFIG },
4128
4247
  ...vcsConfigWrites(dir, opts.vcs, bundle),
4129
4248
  { path: projectFile, content: canonicalStringify7(project) }
4130
4249
  ];
4131
- const collisions = writes.map((w) => w.path).filter((p) => existsSync5(p));
4250
+ const collisions = writes.map((w) => w.path).filter((p) => existsSync6(p));
4132
4251
  if (collisions.length > 0) {
4133
4252
  throw new Error(`refusing to overwrite existing file(s): ${collisions.join(", ")}`);
4134
4253
  }
4135
4254
  return { writes, projectFile, name };
4136
4255
  }
4137
4256
  function vcsConfigWrites(dir, vcs, bundle = "commit") {
4138
- const writes = [{ path: join8(dir, "vcs-setup.md"), content: vcsSetup(vcs, bundle) }];
4139
- if (vcs === "git") writes.push({ path: join8(dir, ".gitattributes"), content: GITATTRIBUTES });
4257
+ const writes = [{ path: join9(dir, "vcs-setup.md"), content: vcsSetup(vcs, bundle) }];
4258
+ if (vcs === "git") writes.push({ path: join9(dir, ".gitattributes"), content: GITATTRIBUTES });
4140
4259
  const ignore = ignoreFileFor(vcs);
4141
- if (ignore) writes.push({ path: join8(dir, ignore), content: ignoreContent(bundle) });
4260
+ if (ignore) writes.push({ path: join9(dir, ignore), content: ignoreContent(bundle) });
4142
4261
  return writes;
4143
4262
  }
4144
4263
  function currentBundlePosture(dir, vcs) {
4145
4264
  const read = (name) => {
4146
4265
  try {
4147
- return readFileSync7(join8(dir, name), "utf8");
4266
+ return readFileSync8(join9(dir, name), "utf8");
4148
4267
  } catch {
4149
4268
  return "";
4150
4269
  }
@@ -4640,7 +4759,7 @@ function runReplace(loaded, opts) {
4640
4759
  }
4641
4760
 
4642
4761
  // src/pin.ts
4643
- import { readFileSync as readFileSync8 } from "fs";
4762
+ import { readFileSync as readFileSync9 } from "fs";
4644
4763
  import { canonicalStringify as canonicalStringify9, parseSource as parseSource5 } from "@patterkit/core";
4645
4764
  import { effectiveGameId as effectiveGameId3, gameIdify } from "@patterkit/model";
4646
4765
  function toPin(node) {
@@ -4668,7 +4787,7 @@ function planPins(loaded) {
4668
4787
  });
4669
4788
  if (!changed) continue;
4670
4789
  const next = { ...scene, ...sceneGameId !== void 0 ? { gameId: sceneGameId } : {}, blocks };
4671
- const file = parseSource5(readFileSync8(path, "utf8"));
4790
+ const file = parseSource5(readFileSync9(path, "utf8"));
4672
4791
  plan.writes.push({ path, content: canonicalStringify9({ ...file, scene: next }) });
4673
4792
  plan.scenes.push(next);
4674
4793
  }
@@ -4677,8 +4796,8 @@ function planPins(loaded) {
4677
4796
 
4678
4797
  // src/unpack.ts
4679
4798
  import JSZip2 from "jszip";
4680
- import { join as join9, resolve as resolve6, sep as sep4 } from "path";
4681
- import { existsSync as existsSync6, readFileSync as readFileSync9, readdirSync as readdirSync5 } from "fs";
4799
+ import { join as join10, resolve as resolve6, sep as sep4 } from "path";
4800
+ import { existsSync as existsSync7, readFileSync as readFileSync10, readdirSync as readdirSync6 } from "fs";
4682
4801
  import { parseSource as parseSource6, canonicalStringify as canonicalStringify10 } from "@patterkit/core";
4683
4802
  import { isUnsafeEntry } from "@wildwinter/toolkit/archive";
4684
4803
  import { SCOPES_FILE_SUFFIX as SCOPES_FILE_SUFFIX2 } from "@wildwinter/scoperegistry/scopes";
@@ -4746,13 +4865,13 @@ async function runUnpack(bytes, targetDir) {
4746
4865
  function projectIdIn(dir) {
4747
4866
  let names;
4748
4867
  try {
4749
- names = readdirSync5(dir).filter((n) => n.endsWith(".patterproj"));
4868
+ names = readdirSync6(dir).filter((n) => n.endsWith(".patterproj"));
4750
4869
  } catch {
4751
4870
  return void 0;
4752
4871
  }
4753
4872
  for (const n of names) {
4754
4873
  try {
4755
- const id = parseSource6(readFileSync9(join9(dir, n), "utf8")).project?.id;
4874
+ const id = parseSource6(readFileSync10(join10(dir, n), "utf8")).project?.id;
4756
4875
  if (id) return id;
4757
4876
  } catch {
4758
4877
  }
@@ -4779,7 +4898,7 @@ function checkProvenance(returned, base, target) {
4779
4898
  }
4780
4899
  function targetProjectId(projectDir) {
4781
4900
  try {
4782
- const pf = parseSource6(readFileSync9(findProjectFile(projectDir), "utf8"));
4901
+ const pf = parseSource6(readFileSync10(findProjectFile(projectDir), "utf8"));
4783
4902
  return pf.project?.id;
4784
4903
  } catch {
4785
4904
  return void 0;
@@ -4800,13 +4919,13 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
4800
4919
  for (const [rel, theirText] of [...theirs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
4801
4920
  const outPath = containedWrite(projectDir, rel);
4802
4921
  const baseText = base.get(rel);
4803
- if (!existsSync6(outPath)) {
4922
+ if (!existsSync7(outPath)) {
4804
4923
  if (baseText === theirText) continue;
4805
4924
  writes.push({ path: outPath, content: theirText });
4806
4925
  shards.push({ path: rel, added: true, changed: true });
4807
4926
  continue;
4808
4927
  }
4809
- const oursText = readFileSync9(outPath, "utf8");
4928
+ const oursText = readFileSync10(outPath, "utf8");
4810
4929
  if (theirText === baseText || theirText === oursText) {
4811
4930
  if (rel === projectRel && baseText !== void 0) {
4812
4931
  const read = (t) => parseSource6(t);
@@ -4847,17 +4966,17 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
4847
4966
  if (world.write) {
4848
4967
  writes.push(world.write);
4849
4968
  gameScopes = { path: world.write.path };
4850
- } else if (world.error) gameScopes = { path: join9(local2.dir, GAME_SCOPES_FILE), error: world.error };
4969
+ } else if (world.error) gameScopes = { path: join10(local2.dir, GAME_SCOPES_FILE), error: world.error };
4851
4970
  }
4852
4971
  const handoffs = [];
4853
4972
  for (const [rel, theirText] of [...returnedDoc.handoffs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
4854
4973
  const outPath = containedWrite(projectDir, rel);
4855
- if (!existsSync6(outPath)) {
4974
+ if (!existsSync7(outPath)) {
4856
4975
  writes.push({ path: outPath, content: theirText });
4857
4976
  handoffs.push(rel);
4858
4977
  continue;
4859
4978
  }
4860
- const merged = mergeHandoff(parseHandoff(readFileSync9(outPath, "utf8")), parseHandoff(theirText));
4979
+ const merged = mergeHandoff(parseHandoff(readFileSync10(outPath, "utf8")), parseHandoff(theirText));
4861
4980
  if (merged) {
4862
4981
  writes.push({ path: outPath, content: serialiseHandoff(merged) });
4863
4982
  handoffs.push(rel);
@@ -4880,7 +4999,7 @@ function projectEntry(shards) {
4880
4999
  }
4881
5000
  function containedWrite(dir, name) {
4882
5001
  const root = resolve6(dir);
4883
- const full = resolve6(join9(dir, name));
5002
+ const full = resolve6(join10(dir, name));
4884
5003
  if (full !== root && !full.startsWith(root + sep4)) {
4885
5004
  throw new UnsafeEntryError(`document entry escapes the target directory: ${name}`);
4886
5005
  }
@@ -4888,7 +5007,7 @@ function containedWrite(dir, name) {
4888
5007
  }
4889
5008
 
4890
5009
  // src/suggestions.ts
4891
- import { readFileSync as readFileSync10 } from "fs";
5010
+ import { readFileSync as readFileSync11 } from "fs";
4892
5011
  import { canonicalStringify as canonicalStringify11, parseSource as parseSource7 } from "@patterkit/core";
4893
5012
  import { walkNodes as walkNodes5, AUTHORING_SCHEMA as AUTHORING_SCHEMA3 } from "@patterkit/model";
4894
5013
  function staleParts(s, liveText, line) {
@@ -4975,7 +5094,7 @@ var Working = class {
4975
5094
  if (!path) return void 0;
4976
5095
  let file = this.flows.get(path);
4977
5096
  if (!file) {
4978
- const onDisk = parseSource7(readFileSync10(path, "utf8"));
5097
+ const onDisk = parseSource7(readFileSync11(path, "utf8"));
4979
5098
  const scene = this.loaded.scenes.find((s) => s.id === sceneId);
4980
5099
  file = { ...onDisk, scene: structuredClone(scene ?? onDisk.scene) };
4981
5100
  this.flows.set(path, file);
@@ -5452,11 +5571,11 @@ function readParagraphs(ps, joiner) {
5452
5571
  c.walk(p, "both");
5453
5572
  return c;
5454
5573
  });
5455
- const join10 = (view) => parts.map((c) => c.text(view)).filter((t) => t !== "").join(joiner);
5574
+ const join11 = (view) => parts.map((c) => c.text(view)).filter((t) => t !== "").join(joiner);
5456
5575
  return {
5457
5576
  text: {
5458
- proposed: join10("proposed"),
5459
- original: join10("original"),
5577
+ proposed: join11("proposed"),
5578
+ original: join11("original"),
5460
5579
  changes: parts.flatMap((c) => c.changes),
5461
5580
  comments: [...new Set(parts.flatMap((c) => c.comments))],
5462
5581
  droppedFormatting: parts.reduce((n, c) => n + c.dropped, 0)
@@ -6027,6 +6146,8 @@ export {
6027
6146
  PATTER_SCOPES_OWNER,
6028
6147
  RARE_REACH_PCT,
6029
6148
  REFUSE_MISSING_SHARE,
6149
+ SCRIPT_FONT_FILES,
6150
+ SCRIPT_FONT_PACKAGES,
6030
6151
  SHARD_EXTENSIONS,
6031
6152
  UnsafeEntryError,
6032
6153
  UnsupportedMergeError,
@@ -6126,6 +6247,8 @@ export {
6126
6247
  scanAudio,
6127
6248
  scanAudioStatus,
6128
6249
  sceneIdForShard,
6250
+ scriptFontLicencePath,
6251
+ scriptFontLicences,
6129
6252
  scriptToDocx,
6130
6253
  scriptToPdf,
6131
6254
  serialiseHandoff,
@@ -6133,6 +6256,7 @@ export {
6133
6256
  setCut,
6134
6257
  sidecarIssues,
6135
6258
  staleParts,
6259
+ useScriptFontFiles,
6136
6260
  vcsConfigWrites,
6137
6261
  voiceScriptToXlsx,
6138
6262
  worldSettingsScopes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/ops",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "The shared operations layer: every project operation (load, validate, export, format, play, ...) as a pure function, consumed by the patter CLI and Patterpad alike.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,9 +27,15 @@
27
27
  "build": "tsup src/index.ts --format esm --dts --clean"
28
28
  },
29
29
  "dependencies": {
30
- "@patterkit/compiler": "0.6.1",
31
- "@patterkit/core": "0.6.1",
32
- "@patterkit/model": "0.13.0",
30
+ "@fontsource/inter": "^5.3.0",
31
+ "@fontsource/newsreader": "^5.3.0",
32
+ "@fontsource/noto-emoji": "^5.3.2",
33
+ "@fontsource/noto-sans": "^5.3.0",
34
+ "@fontsource/noto-sans-math": "^5.3.0",
35
+ "@ibm/plex-mono": "1.0.0",
36
+ "@patterkit/compiler": "0.6.2",
37
+ "@patterkit/core": "0.6.2",
38
+ "@patterkit/model": "0.13.1",
33
39
  "@patterkit/runtime": "0.24.0",
34
40
  "@wildwinter/toolkit": "^0.1.0",
35
41
  "@xmldom/xmldom": "^0.8.15",