@sutrata/editor 0.1.0 → 0.1.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.
Files changed (152) hide show
  1. package/README.md +87 -0
  2. package/dist/ai/generated-providers.json +1 -1
  3. package/package.json +36 -9
  4. package/scripts/copy-assets.mjs +0 -16
  5. package/scripts/generate-providers.mjs +0 -127
  6. package/src/ai/OnboardingDialog.tsx +0 -390
  7. package/src/ai/VoiceConfirmDialog.tsx +0 -137
  8. package/src/ai/VoiceToolbar.tsx +0 -321
  9. package/src/ai/ai-client.ts +0 -596
  10. package/src/ai/commands.ts +0 -164
  11. package/src/ai/generated-providers.json +0 -3696
  12. package/src/ai/prompts.ts +0 -62
  13. package/src/ai/voice-service.ts +0 -96
  14. package/src/autocomplete/AutocompletePlugin.ts +0 -21
  15. package/src/autocomplete/suggestions.ts +0 -147
  16. package/src/context/DocumentContext.tsx +0 -648
  17. package/src/desktop-api.d.ts +0 -40
  18. package/src/editor/EditorView.tsx +0 -82
  19. package/src/editor/SelectionMenu.tsx +0 -152
  20. package/src/editor/attr-pencil-plugin.ts +0 -172
  21. package/src/editor/character-decoration-plugin.ts +0 -104
  22. package/src/editor/editor-bus.ts +0 -51
  23. package/src/editor/find-highlight-plugin.ts +0 -68
  24. package/src/editor/frontmatter-field-view.ts +0 -52
  25. package/src/editor/frontmatter-field.ts +0 -115
  26. package/src/editor/grapheme-cursor.ts +0 -99
  27. package/src/editor/ime-plugin.ts +0 -85
  28. package/src/editor/insert-helper.ts +0 -43
  29. package/src/editor/keymap.ts +0 -124
  30. package/src/editor/language-class-plugin.ts +0 -91
  31. package/src/editor/plugins.ts +0 -26
  32. package/src/editor/prosemirror-to-sutra.ts +0 -192
  33. package/src/editor/scene-heading-view.ts +0 -147
  34. package/src/editor/schema.ts +0 -156
  35. package/src/editor/sutra-to-prosemirror.ts +0 -156
  36. package/src/extensions/storage-adapter.ts +0 -65
  37. package/src/extensions/types.ts +0 -60
  38. package/src/file/docx-exporter.ts +0 -585
  39. package/src/file/docx-importer.ts +0 -313
  40. package/src/file/print-paginator.ts +0 -301
  41. package/src/file/templates.ts +0 -73
  42. package/src/file/workflow-reports.ts +0 -820
  43. package/src/findreplace/FindReplace.tsx +0 -242
  44. package/src/findreplace/find-engine.ts +0 -97
  45. package/src/findreplace/scroll-to-match.ts +0 -89
  46. package/src/fonts/tofu-detect.ts +0 -53
  47. package/src/i18n/LanguageContext.tsx +0 -207
  48. package/src/i18n/language-detect.ts +0 -73
  49. package/src/i18n/useTranslation.tsx +0 -74
  50. package/src/index.ts +0 -27
  51. package/src/locales/bn.json +0 -143
  52. package/src/locales/en.json +0 -143
  53. package/src/locales/gu.json +0 -143
  54. package/src/locales/hi.json +0 -143
  55. package/src/locales/kn.json +0 -143
  56. package/src/locales/ml.json +0 -143
  57. package/src/locales/mr.json +0 -143
  58. package/src/locales/or.json +0 -143
  59. package/src/locales/pa.json +0 -143
  60. package/src/locales/si.json +0 -143
  61. package/src/locales/ta.json +0 -143
  62. package/src/locales/te.json +0 -143
  63. package/src/navigator/SceneNavigator.tsx +0 -844
  64. package/src/navigator/StatisticsDialog.tsx +0 -249
  65. package/src/navigator/scene-list.ts +0 -84
  66. package/src/settings/SettingsDialog.tsx +0 -303
  67. package/src/shell/AppBar.tsx +0 -354
  68. package/src/shell/AppShell.tsx +0 -71
  69. package/src/shell/ConfirmDialog.tsx +0 -71
  70. package/src/shell/ElementToolbar.tsx +0 -175
  71. package/src/shell/LanguageStatus.tsx +0 -101
  72. package/src/shell/StatusBar.tsx +0 -135
  73. package/src/shell/SutrataLogo.tsx +0 -89
  74. package/src/shell/Toast.tsx +0 -46
  75. package/src/shell/ToolbarButton.tsx +0 -30
  76. package/src/shell/VersionHistory.tsx +0 -212
  77. package/src/shell/Watermark.tsx +0 -9
  78. package/src/shell/fonts/fonts.css +0 -1133
  79. package/src/shell/icons.tsx +0 -335
  80. package/src/shell/toolbar-actions.ts +0 -59
  81. package/src/source/SourceView.tsx +0 -111
  82. package/src/source/find-highlight-source.ts +0 -57
  83. package/src/source/sutra-lang.ts +0 -121
  84. package/src/spellcheck/dictionaries.ts +0 -21
  85. package/src/spellcheck/hunspell.ts +0 -110
  86. package/src/spellcheck/spellcheck-plugin.ts +0 -83
  87. package/src/styles/StyleDialog.tsx +0 -148
  88. package/src/styles/builtin-styles.ts +0 -139
  89. package/src/styles/css-adapter.ts +0 -76
  90. package/src/styles/docx-adapter.ts +0 -109
  91. package/src/styles/embed-fonts.ts +0 -81
  92. package/src/styles/pagination.ts +0 -27
  93. package/src/styles/print-adapter.ts +0 -288
  94. package/src/styles/registry.ts +0 -20
  95. package/src/styles/screenplay.css +0 -3006
  96. package/src/styles/script-ranges.ts +0 -17
  97. package/src/styles/types.ts +0 -104
  98. package/src/styles/validate.ts +0 -114
  99. package/src/templates/templates.ts +0 -194
  100. package/src/titlepage/ExportDialog.tsx +0 -494
  101. package/src/titlepage/TitlePageDialog.tsx +0 -312
  102. package/src/titlepage/frontmatter-form.ts +0 -280
  103. package/src/titlepage/languages.ts +0 -37
  104. package/src/types.ts +0 -7
  105. package/tests/AppBar.test.tsx +0 -35
  106. package/tests/AppShell.test.tsx +0 -22
  107. package/tests/ElementToolbar.test.tsx +0 -36
  108. package/tests/LanguageStatus.test.tsx +0 -129
  109. package/tests/SelectionMenu.test.tsx +0 -71
  110. package/tests/ToolbarButton.test.tsx +0 -24
  111. package/tests/ai-onboarding.test.ts +0 -102
  112. package/tests/ai-status.test.tsx +0 -72
  113. package/tests/ai-structured.test.ts +0 -94
  114. package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
  115. package/tests/autocomplete/suggestions.test.ts +0 -106
  116. package/tests/document-context.test.tsx +0 -565
  117. package/tests/docx-importer.test.ts +0 -160
  118. package/tests/editor/character-decoration-plugin.test.ts +0 -101
  119. package/tests/editor/character-extension.test.ts +0 -44
  120. package/tests/editor/grapheme-cursor.test.ts +0 -118
  121. package/tests/editor/ime-plugin.test.ts +0 -72
  122. package/tests/editor/inline-note-bridge.test.ts +0 -32
  123. package/tests/editor/language-class-plugin.test.ts +0 -265
  124. package/tests/editor/title-page.test.ts +0 -124
  125. package/tests/embed-fonts.test.ts +0 -17
  126. package/tests/exporters.test.ts +0 -365
  127. package/tests/findreplace/FindReplace.test.tsx +0 -391
  128. package/tests/findreplace/find-engine.test.ts +0 -86
  129. package/tests/findreplace/scroll-to-match.test.ts +0 -50
  130. package/tests/fonts/tofu-detect.test.ts +0 -122
  131. package/tests/i18n/LanguageContext.test.tsx +0 -146
  132. package/tests/i18n/language-detect.test.ts +0 -54
  133. package/tests/i18n/locale-parity.test.ts +0 -25
  134. package/tests/i18n/useTranslation.test.tsx +0 -45
  135. package/tests/keymap.test.ts +0 -18
  136. package/tests/navigator/SceneNavigator.test.tsx +0 -177
  137. package/tests/navigator/StatisticsDialog.test.tsx +0 -63
  138. package/tests/navigator/scene-list.test.ts +0 -71
  139. package/tests/print-paginator.test.ts +0 -82
  140. package/tests/shell/VersionHistory.test.tsx +0 -129
  141. package/tests/spellcheck/hunspell.test.ts +0 -82
  142. package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
  143. package/tests/styles.test.ts +0 -315
  144. package/tests/templates/templates.test.ts +0 -83
  145. package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
  146. package/tests/titlepage/apply-title-page.test.ts +0 -61
  147. package/tests/titlepage/frontmatter-form.test.ts +0 -115
  148. package/tests/toolbar-actions.test.ts +0 -93
  149. package/tests/voice-commands.test.ts +0 -58
  150. package/tsconfig.json +0 -17
  151. package/vitest.config.ts +0 -11
  152. package/vitest.setup.ts +0 -65
@@ -1,109 +0,0 @@
1
- import { AlignmentType } from 'docx'
2
- import type { IParagraphStyleOptions } from 'docx'
3
- import { BODY_ELEMENT_KEYS, COVER_ELEMENT_KEYS } from './types.js'
4
- import { KEEP_WITH_NEXT_ELEMENTS, KEEP_TOGETHER_ELEMENTS } from './pagination.js'
5
- import type { ScreenplayStyleDefinition, ElementStyle, BodyElementKey, CoverElementKey } from './types.js'
6
-
7
- /** Sutra element -> stable docx paragraph style id. Fixed forever: docx-importer.ts
8
- * matches re-imported .docx files against these exact ids, regardless of appearance. */
9
- const DOCX_STYLE_IDS: Record<BodyElementKey, string> = {
10
- sceneHeading: 'CineSceneHeading',
11
- action: 'CineAction',
12
- character: 'CineCharacter',
13
- parenthetical: 'CineParenthetical',
14
- dialogue: 'CineDialogue',
15
- transition: 'CineTransition',
16
- centered: 'CineCentered',
17
- lyrics: 'CineLyrics',
18
- section: 'CineSection',
19
- note: 'CineNote',
20
- comment: 'CineComment',
21
- }
22
-
23
- /** Cover-page element -> docx paragraph style id, referenced by docx-exporter.ts's
24
- * title page. These must be emitted alongside the body styles: a paragraph naming a
25
- * style the document doesn't define silently falls back to Word's Normal style
26
- * (left-aligned 11pt black), which is exactly how the title page used to render. */
27
- const DOCX_COVER_STYLE_IDS: Record<CoverElementKey, string> = {
28
- title: 'CineTitle',
29
- author: 'CineAuthor',
30
- logline: 'CineLogline',
31
- }
32
-
33
- const ALIGN_MAP = {
34
- left: AlignmentType.LEFT,
35
- center: AlignmentType.CENTER,
36
- right: AlignmentType.RIGHT,
37
- } as const
38
-
39
- function pctToTwips(pct: number | undefined, contentWidthTwips: number): number | undefined {
40
- if (pct === undefined) return undefined
41
- return Math.round((pct / 100) * contentWidthTwips)
42
- }
43
-
44
- /** docx's *style-level* paragraph options type omits `widowControl`, but the paragraph-
45
- * property serializer it shares with `Paragraph` does emit it (verified: `<w:widowControl/>`
46
- * lands in the generated word/styles.xml). Widen the type rather than drop the property. */
47
- type StyleParagraphOptions = NonNullable<IParagraphStyleOptions['paragraph']> & { widowControl?: boolean }
48
-
49
- /** `pagination` carries the screenplay page-break rules for body elements (see
50
- * pagination.ts); cover-page elements pass none — a cover page is one page by
51
- * construction. */
52
- function elementToDocxStyle(
53
- id: string,
54
- el: ElementStyle,
55
- fontFamily: string,
56
- contentWidthTwips: number,
57
- pagination: { keepNext?: boolean; keepLines?: boolean } = {},
58
- ): IParagraphStyleOptions {
59
- const left = pctToTwips(el.marginLeftPct, contentWidthTwips)
60
- const right = pctToTwips(el.marginRightPct, contentWidthTwips)
61
- const paragraph: StyleParagraphOptions = {
62
- alignment: el.align ? ALIGN_MAP[el.align] : undefined,
63
- indent: (left !== undefined || right !== undefined) ? { left, right } : undefined,
64
- spacing: {
65
- before: el.spaceBeforePt !== undefined ? Math.round(el.spaceBeforePt * 20) : undefined,
66
- after: el.spaceAfterPt !== undefined ? Math.round(el.spaceAfterPt * 20) : undefined,
67
- },
68
- keepNext: pagination.keepNext,
69
- keepLines: pagination.keepLines,
70
- // Stated rather than left to Word's default: action/dialogue stay breakable
71
- // (no keepLines), so widow control is what stops a page break from stranding a
72
- // single line of them on either side.
73
- widowControl: true,
74
- }
75
- return {
76
- id,
77
- name: id,
78
- run: {
79
- font: fontFamily,
80
- size: Math.round(el.fontSizePt * 2),
81
- bold: el.bold || undefined,
82
- italics: el.italic || undefined,
83
- underline: el.underline ? {} : undefined,
84
- color: el.color,
85
- },
86
- paragraph,
87
- }
88
- }
89
-
90
- /** Builds the `paragraphStyles` array for `new Document({ styles: { paragraphStyles } })`
91
- * from a style definition. `contentWidthTwips` is the page's printable width (page width
92
- * minus left+right margins) — percentages are resolved against it, so indentation scales
93
- * correctly with page size/margins instead of reusing one page format's fixed twips. */
94
- export function styleToDocxParagraphStyles(
95
- style: ScreenplayStyleDefinition,
96
- contentWidthTwips: number,
97
- ): IParagraphStyleOptions[] {
98
- return [
99
- ...BODY_ELEMENT_KEYS.map(key =>
100
- elementToDocxStyle(DOCX_STYLE_IDS[key], style.elements[key], style.fontFamily, contentWidthTwips, {
101
- keepNext: KEEP_WITH_NEXT_ELEMENTS.includes(key) || undefined,
102
- keepLines: KEEP_TOGETHER_ELEMENTS.includes(key) || undefined,
103
- })
104
- ),
105
- ...COVER_ELEMENT_KEYS.map(key =>
106
- elementToDocxStyle(DOCX_COVER_STYLE_IDS[key], style.elements[key], style.fontFamily, contentWidthTwips)
107
- ),
108
- ]
109
- }
@@ -1,81 +0,0 @@
1
- /**
2
- * Embeds self-hosted @font-face rules (from shell/fonts/fonts.css, already loaded into
3
- * the running app's own document) as data: URIs, for splicing into the PDF/print-preview
4
- * HTML — a separate, standalone document that never sees the app's own stylesheets.
5
- *
6
- * Introspects the CSSOM (`document.styleSheets`) rather than hardcoding filenames/unicode
7
- * ranges per family: whatever fonts.css declares for a family is exactly what gets embedded,
8
- * so this can never drift out of sync with the actual font files bundled in public/fonts.
9
- *
10
- * Why embed at all instead of just linking `/fonts/...` from the print HTML: the native
11
- * desktop PDF export writes the HTML to a temp file and loads it via a `file://` URL in a
12
- * hidden webview, which cannot resolve a root-relative `/fonts/...` path back to the running
13
- * app's own server/origin. A data: URI works regardless of the HTML's own location.
14
- */
15
-
16
- function urlToDataUri(url: string): Promise<string> {
17
- return fetch(url)
18
- .then(res => {
19
- if (!res.ok) throw new Error(`Failed to fetch font: ${url}`)
20
- return res.blob()
21
- })
22
- .then(blob => new Promise<string>((resolve, reject) => {
23
- const reader = new FileReader()
24
- reader.onload = () => resolve(reader.result as string)
25
- reader.onerror = () => reject(reader.error ?? new Error('Failed to read font file'))
26
- reader.readAsDataURL(blob)
27
- }))
28
- }
29
-
30
- function findFontFaceRules(familyName: string): CSSFontFaceRule[] {
31
- const rules: CSSFontFaceRule[] = []
32
- for (const sheet of Array.from(document.styleSheets)) {
33
- let cssRules: CSSRuleList
34
- try {
35
- cssRules = sheet.cssRules
36
- } catch {
37
- continue // cross-origin stylesheets throw on access; nothing we bundle is cross-origin
38
- }
39
- for (const rule of Array.from(cssRules)) {
40
- if (rule instanceof CSSFontFaceRule) {
41
- const family = rule.style.getPropertyValue('font-family').replace(/^['"]|['"]$/g, '')
42
- if (family === familyName) rules.push(rule)
43
- }
44
- }
45
- }
46
- return rules
47
- }
48
-
49
- /** Builds `@font-face` CSS text embedding every self-hosted rule for the given family
50
- * names, as data: URIs. Families with no matching rules (e.g. web-safe fonts that were
51
- * never in fonts.css) are silently skipped — the Google Fonts `<link>`/generic fallback
52
- * stack in print-adapter.ts's font stack still applies. Fetch failures for an individual
53
- * rule are likewise skipped rather than failing the whole export. */
54
- export async function embedFontFacesFor(familyNames: string[]): Promise<string> {
55
- const blocks: string[] = []
56
- for (const family of familyNames) {
57
- for (const rule of findFontFaceRules(family)) {
58
- const srcRaw = rule.style.getPropertyValue('src')
59
- const match = /url\(["']?([^"')]+)["']?\)/.exec(srcRaw)
60
- if (!match) continue
61
- try {
62
- const dataUri = await urlToDataUri(match[1]!)
63
- const weight = rule.style.getPropertyValue('font-weight') || '400'
64
- const style = rule.style.getPropertyValue('font-style') || 'normal'
65
- const unicodeRange = rule.style.getPropertyValue('unicode-range')
66
- blocks.push(
67
- '@font-face {\n' +
68
- ` font-family: '${family}';\n` +
69
- ` font-weight: ${weight};\n` +
70
- ` font-style: ${style};\n` +
71
- ` src: url(${dataUri}) format('woff2');\n` +
72
- (unicodeRange ? ` unicode-range: ${unicodeRange};\n` : '') +
73
- '}'
74
- )
75
- } catch {
76
- // Skip; the Google Fonts link / generic fallback stack still covers this family.
77
- }
78
- }
79
- }
80
- return blocks.join('\n')
81
- }
@@ -1,27 +0,0 @@
1
- import type { BodyElementKey } from './types.js'
2
-
3
- /** Screenplay pagination rules — format conventions, not per-style visual choices, so
4
- * they live here rather than in ScreenplayStyleDefinition: no style (built-in or
5
- * user-imported) gets to end a page on a character cue. Consumed by both export
6
- * paths, which express the same rule differently: DOCX via `keepNext`/`keepLines`
7
- * paragraph properties (docx-adapter.ts), PDF/print via `break-after`/`break-inside`
8
- * (print-adapter.ts). The live editor has no pagination, so it ignores these. */
9
-
10
- /** Elements that must not be the last thing on a page — a scene heading or character
11
- * cue stranded at the bottom with its action/dialogue overleaf is the classic
12
- * screenplay pagination error. A parenthetical is included for the same reason: it
13
- * belongs to the dialogue line that follows it. */
14
- export const KEEP_WITH_NEXT_ELEMENTS: readonly BodyElementKey[] = [
15
- 'sceneHeading', 'character', 'parenthetical', 'section',
16
- ]
17
-
18
- /** Elements short enough that splitting them across a page boundary is always wrong.
19
- * Deliberately excludes action/dialogue/lyrics/note — those can legitimately run
20
- * longer than a page, so they get orphan/widow control instead of an unbreakable box. */
21
- export const KEEP_TOGETHER_ELEMENTS: readonly BodyElementKey[] = [
22
- 'sceneHeading', 'character', 'parenthetical', 'transition', 'section',
23
- ]
24
-
25
- /** Minimum lines of a breakable element left on either side of a page boundary. Two is
26
- * the standard screenplay/typography minimum (a single stranded line reads as an error). */
27
- export const MIN_ORPHAN_WIDOW_LINES = 2
@@ -1,288 +0,0 @@
1
- import type { ScreenplayStyleDefinition, ElementStyle } from './types.js'
2
- import { MIN_ORPHAN_WIDOW_LINES } from './pagination.js'
3
-
4
- function color(el: ElementStyle): string {
5
- return el.color ? `#${el.color}` : 'black'
6
- }
7
-
8
- function fontProps(el: ElementStyle): string {
9
- const lines = [`font-size: ${el.fontSizePt}pt;`, `color: ${color(el)};`]
10
- if (el.bold) lines.push('font-weight: bold;')
11
- if (el.italic) lines.push('font-style: italic;')
12
- if (el.underline) lines.push('text-decoration: underline;')
13
- if (el.align) lines.push(`text-align: ${el.align};`)
14
- return lines.join('\n ')
15
- }
16
-
17
- /** Screenplay page-break rules for PDF/print — the CSS half of pagination.ts (DOCX gets
18
- * the same rules as keepNext/keepLines). `page-break-*` is emitted alongside the modern
19
- * `break-*` because print engines are uneven about which spelling they honor. */
20
- const KEEP_WITH_NEXT = `
21
- break-after: avoid;
22
- page-break-after: avoid;
23
- break-inside: avoid;
24
- page-break-inside: avoid;`
25
- const KEEP_TOGETHER = `
26
- break-inside: avoid;
27
- page-break-inside: avoid;`
28
- /** For elements that may legitimately run longer than a page: don't forbid the break,
29
- * just refuse to strand one line on either side of it. */
30
- const ORPHAN_WIDOW = `
31
- orphans: ${MIN_ORPHAN_WIDOW_LINES};
32
- widows: ${MIN_ORPHAN_WIDOW_LINES};`
33
-
34
- function marginProps(el: ElementStyle): string {
35
- const lines: string[] = []
36
- if (el.marginLeftPct !== undefined) lines.push(`margin-left: ${el.marginLeftPct}%;`)
37
- if (el.marginRightPct !== undefined) lines.push(`margin-right: ${el.marginRightPct}%;`)
38
- lines.push(`margin-top: ${el.spaceBeforePt ?? 0}pt;`)
39
- lines.push(`margin-bottom: ${el.spaceAfterPt ?? 0}pt;`)
40
- return lines.join('\n ')
41
- }
42
-
43
- /** Builds the screenplay-specific portion of the PDF/print-preview `<style>` block
44
- * (packages/app/src/file/workflow-reports.ts's buildScreenplayPrintHtml) from a style
45
- * definition. UI chrome (print button, page-break rule, dual-dialogue column layout)
46
- * stays hardcoded in workflow-reports.ts — only the parametrized visual properties live here. */
47
- export function styleToPrintCss(style: ScreenplayStyleDefinition, pageFormat: 'letter' | 'a4' = 'letter'): string {
48
- const e = style.elements
49
- const contentWidthIn = (pageFormat === 'a4' ? 8.27 : 8.5) - style.page.marginLeftIn - style.page.marginRightIn
50
- // The style's own font first, then its explicit per-script overrides (e.g. Traditional's
51
- // Noto Serif Tamil) so they win over the generic Indic fallback that always follows — this
52
- // HTML has no per-span language tagging, so one comprehensive stack has to do the work the
53
- // editor's .cs-lang-* rules split across classes. Overrides are style-specific, so unlike
54
- // the fixed Noto Sans fallback list they must come from the style itself, not be hardcoded —
55
- // otherwise every style would render Indic scripts in whatever the *last*-defined style's
56
- // overrides asked for.
57
- const overrideFonts = Object.values(style.complexScriptFontOverrides ?? {})
58
- // Noto Sans comes right after the style's own fonts: it is the Latin fallback (app spec
59
- // §4.4) and its bundled file carries every ISO 15919 letter and combining mark, which the
60
- // Indic families' Latin subsets do not. Its unicode-range stops at Latin, so Indic text
61
- // still falls through to the script-specific faces below.
62
- const fontStack = [...new Set([
63
- `'${style.fontFamily}'`,
64
- ...overrideFonts.map(f => `'${f}'`),
65
- "'Noto Sans'",
66
- "'Noto Sans Tamil'", "'Noto Sans Devanagari'", "'Noto Sans Telugu'", "'Noto Sans Kannada'",
67
- "'Noto Sans Malayalam'", "'Noto Sans Bengali'", "'Noto Sans Gujarati'", "'Noto Sans Gurmukhi'",
68
- "'Noto Sans Oriya'", 'sans-serif',
69
- ])].join(', ')
70
- return `
71
- @page {
72
- /* Stated, not left to the print dialog's default: print-paginator.ts fills
73
- fixed-height page boxes, so the paper it fills them for has to be the paper
74
- the browser actually prints on (and it's the same page: field the DOCX
75
- export already honors). */
76
- size: ${pageFormat === 'a4' ? 'A4' : 'letter'};
77
- margin: ${style.page.marginTopIn}in ${style.page.marginRightIn}in ${style.page.marginBottomIn}in ${style.page.marginLeftIn}in;
78
- }
79
- body {
80
- font-family: ${fontStack};
81
- margin: 0;
82
- padding: 0;
83
- background: white;
84
- color: black;
85
- font-size: 11pt;
86
- }
87
- /* Cover layouts — one of these two classes is on .print-cover-page, picked by
88
- the style's coverLayout (see types.ts). Both wrap the same markup: a
89
- .print-cover-top block (title / by-line / logline) and a .print-cover-bottom
90
- block (date / contact). */
91
- .print-cover-centered {
92
- height: 80vh;
93
- display: flex;
94
- flex-direction: column;
95
- align-items: center;
96
- justify-content: center;
97
- text-align: center;
98
- }
99
- .print-cover-corners {
100
- height: 88vh;
101
- display: flex;
102
- flex-direction: column;
103
- justify-content: space-between;
104
- text-align: left;
105
- }
106
- /* Down from the top edge, not glued to it — the reference script starts its
107
- title roughly a quarter of the way down the page. */
108
- .print-cover-corners .print-cover-top {
109
- padding-top: 20%;
110
- }
111
- .print-cover-corners .print-cover-bottom {
112
- align-self: flex-end;
113
- padding-bottom: 6%;
114
- max-width: 45%;
115
- }
116
- .print-cover-corners .print-cover-logline {
117
- margin-top: 12pt;
118
- max-width: 75%;
119
- }
120
- /* The corner block is its own visual group: the centered layout's big
121
- separating margins would push it off the page bottom. */
122
- .print-cover-corners .print-cover-date,
123
- .print-cover-corners .print-cover-contact {
124
- margin-top: 0;
125
- }
126
- .print-cover-title {
127
- ${fontProps(e.title)}
128
- text-transform: uppercase;
129
- margin-bottom: 20px;
130
- }
131
- .print-cover-by {
132
- font-size: 12pt;
133
- margin: 10px 0;
134
- }
135
- .print-cover-author {
136
- ${fontProps(e.author)}
137
- }
138
- .print-cover-logline {
139
- ${fontProps(e.logline)}
140
- margin-top: 40px;
141
- max-width: 600px;
142
- white-space: pre-line;
143
- }
144
- .print-cover-date {
145
- font-size: 10pt;
146
- margin-top: 50px;
147
- color: #777;
148
- }
149
- .print-cover-contact {
150
- font-size: 11pt;
151
- margin-top: 24px;
152
- color: #888;
153
- white-space: pre-line;
154
- }
155
-
156
- /* The preview lays out at exactly the printable width, on screen as well as on
157
- paper. print-paginator.ts measures where the page breaks fall in the live
158
- window, so if the on-screen line breaks differed from the printed ones (a
159
- 1280px-wide window wraps far later than a 6in column) every page it filled
160
- would overflow its sheet when printed. */
161
- #print-flow, #print-pages, .print-cover-page {
162
- width: ${contentWidthIn}in;
163
- margin-left: auto;
164
- margin-right: auto;
165
- }
166
-
167
- /* One printed sheet of body content, built by print-paginator.ts: an exact
168
- page-height box (set inline, from the paper size) so the browser puts exactly
169
- one on each sheet, with the running header at its top. */
170
- .print-page {
171
- break-after: page;
172
- page-break-after: always;
173
- box-sizing: border-box;
174
- overflow: visible;
175
- }
176
- .print-page:last-child {
177
- break-after: auto;
178
- page-break-after: auto;
179
- }
180
- .print-page-body {
181
- position: relative;
182
- display: flow-root; /* so a first child's margin-top counts inside the page */
183
- }
184
- .print-page-header {
185
- display: flex;
186
- justify-content: space-between;
187
- align-items: baseline;
188
- font-size: ${e.action.fontSizePt}pt;
189
- font-style: italic;
190
- margin-bottom: 24pt;
191
- }
192
- @media screen {
193
- .print-page {
194
- border-bottom: 1px dashed #ccc;
195
- margin-bottom: 24px;
196
- }
197
- }
198
-
199
- /* Screenplay items */
200
- .print-scene-heading {
201
- ${fontProps(e.sceneHeading)}
202
- text-transform: uppercase;
203
- ${marginProps(e.sceneHeading)}
204
- display: flex;
205
- justify-content: space-between;${KEEP_WITH_NEXT}
206
- }
207
- .print-action {
208
- ${fontProps(e.action)}
209
- ${marginProps(e.action)}
210
- line-height: 1.4;${ORPHAN_WIDOW}
211
- }
212
- .print-character-block {
213
- margin-top: ${e.character.spaceBeforePt ?? 0}pt;
214
- margin-bottom: ${e.character.spaceAfterPt ?? 0}pt;
215
- }
216
- .print-character {
217
- ${fontProps(e.character)}
218
- margin-left: ${e.character.marginLeftPct ?? 0}%;
219
- margin-bottom: 2pt;${KEEP_WITH_NEXT}
220
- }
221
- .print-parenthetical {
222
- ${fontProps(e.parenthetical)}
223
- margin-left: ${e.parenthetical.marginLeftPct ?? 0}%;
224
- margin-right: ${e.parenthetical.marginRightPct ?? 0}%;
225
- margin-bottom: 2pt;${KEEP_WITH_NEXT}
226
- }
227
- .print-dialogue {
228
- ${fontProps(e.dialogue)}
229
- ${marginProps(e.dialogue)}
230
- line-height: 1.3;${ORPHAN_WIDOW}
231
- }
232
- .print-transition {
233
- ${fontProps(e.transition)}
234
- ${marginProps(e.transition)}
235
- text-transform: uppercase;${KEEP_TOGETHER}
236
- }
237
- .print-centered {
238
- ${fontProps(e.centered)}
239
- ${marginProps(e.centered)}
240
- }
241
- .print-lyrics {
242
- ${fontProps(e.lyrics)}
243
- ${marginProps(e.lyrics)}${ORPHAN_WIDOW}
244
- }
245
- .print-note {
246
- background: #fffbeb;
247
- border-left: 2px solid #d97706;
248
- padding: 4px 8px;
249
- ${fontProps(e.note)}
250
- ${marginProps(e.note)}
251
- }
252
- .print-comment {
253
- ${fontProps(e.comment)}
254
- ${marginProps(e.comment)}
255
- }
256
- .print-section {
257
- ${fontProps(e.section)}
258
- ${marginProps(e.section)}${KEEP_WITH_NEXT}
259
- }
260
- .print-page-break {
261
- page-break-after: always;
262
- }
263
-
264
- /* Dual dialogue */
265
- .print-dual-dialogue {
266
- display: flex;
267
- margin-top: 6pt;
268
- margin-bottom: 6pt;
269
- }
270
- .print-dual-column {
271
- flex: 1;
272
- min-width: 0;
273
- }
274
- .print-left-column {
275
- margin-right: 20px;
276
- }
277
- .print-right-column {
278
- margin-left: 20px;
279
- }
280
- .print-dual-column .print-character {
281
- margin-left: 10%;
282
- }
283
- .print-dual-column .print-parenthetical,
284
- .print-dual-column .print-dialogue {
285
- margin-left: 10%;
286
- margin-right: 5%;
287
- }`
288
- }
@@ -1,20 +0,0 @@
1
- import type { ScreenplayStyleDefinition } from './types.js'
2
- import { DEFAULT_STYLE_ID } from './types.js'
3
- import { BUILTIN_STYLES, getBuiltinStyle } from './builtin-styles.js'
4
-
5
- /** Resolves a style id (usually from frontmatter's `style:` field) to a definition.
6
- * Falls back to the default style when the id is missing or unrecognized — e.g. a
7
- * document referencing a custom style the current machine/browser hasn't imported. */
8
- export function resolveStyle(
9
- id: string | undefined,
10
- customStyles: ScreenplayStyleDefinition[],
11
- ): ScreenplayStyleDefinition {
12
- const wanted = id ?? DEFAULT_STYLE_ID
13
- return getBuiltinStyle(wanted)
14
- ?? customStyles.find(s => s.id === wanted)
15
- ?? getBuiltinStyle(DEFAULT_STYLE_ID)!
16
- }
17
-
18
- export function allStyles(customStyles: ScreenplayStyleDefinition[]): ScreenplayStyleDefinition[] {
19
- return [...BUILTIN_STYLES, ...customStyles]
20
- }