wolfy-reader 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +277 -0
  3. package/dist/core/book.d.ts +75 -0
  4. package/dist/core/book.d.ts.map +1 -0
  5. package/dist/core/book.js +2 -0
  6. package/dist/core/book.js.map +1 -0
  7. package/dist/core/errors.d.ts +17 -0
  8. package/dist/core/errors.d.ts.map +1 -0
  9. package/dist/core/errors.js +29 -0
  10. package/dist/core/errors.js.map +1 -0
  11. package/dist/core/format.d.ts +13 -0
  12. package/dist/core/format.d.ts.map +1 -0
  13. package/dist/core/format.js +2 -0
  14. package/dist/core/format.js.map +1 -0
  15. package/dist/core/graphemes.d.ts +36 -0
  16. package/dist/core/graphemes.d.ts.map +1 -0
  17. package/dist/core/graphemes.js +62 -0
  18. package/dist/core/graphemes.js.map +1 -0
  19. package/dist/core/index.d.ts +11 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +6 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/lookup.d.ts +10 -0
  24. package/dist/core/lookup.d.ts.map +1 -0
  25. package/dist/core/lookup.js +16 -0
  26. package/dist/core/lookup.js.map +1 -0
  27. package/dist/core/open.d.ts +12 -0
  28. package/dist/core/open.d.ts.map +1 -0
  29. package/dist/core/open.js +23 -0
  30. package/dist/core/open.js.map +1 -0
  31. package/dist/core/position.d.ts +121 -0
  32. package/dist/core/position.d.ts.map +1 -0
  33. package/dist/core/position.js +327 -0
  34. package/dist/core/position.js.map +1 -0
  35. package/dist/core/reading-text.d.ts +77 -0
  36. package/dist/core/reading-text.d.ts.map +1 -0
  37. package/dist/core/reading-text.js +144 -0
  38. package/dist/core/reading-text.js.map +1 -0
  39. package/dist/core/source.d.ts +17 -0
  40. package/dist/core/source.d.ts.map +1 -0
  41. package/dist/core/source.js +39 -0
  42. package/dist/core/source.js.map +1 -0
  43. package/dist/core/storage.d.ts +7 -0
  44. package/dist/core/storage.d.ts.map +1 -0
  45. package/dist/core/storage.js +2 -0
  46. package/dist/core/storage.js.map +1 -0
  47. package/dist/core/text.d.ts +18 -0
  48. package/dist/core/text.d.ts.map +1 -0
  49. package/dist/core/text.js +32 -0
  50. package/dist/core/text.js.map +1 -0
  51. package/dist/formats/epub/href.d.ts +14 -0
  52. package/dist/formats/epub/href.d.ts.map +1 -0
  53. package/dist/formats/epub/href.js +52 -0
  54. package/dist/formats/epub/href.js.map +1 -0
  55. package/dist/formats/epub/index.d.ts +3 -0
  56. package/dist/formats/epub/index.d.ts.map +1 -0
  57. package/dist/formats/epub/index.js +223 -0
  58. package/dist/formats/epub/index.js.map +1 -0
  59. package/dist/formats/epub/opf.d.ts +27 -0
  60. package/dist/formats/epub/opf.d.ts.map +1 -0
  61. package/dist/formats/epub/opf.js +110 -0
  62. package/dist/formats/epub/opf.js.map +1 -0
  63. package/dist/formats/epub/toc.d.ts +6 -0
  64. package/dist/formats/epub/toc.d.ts.map +1 -0
  65. package/dist/formats/epub/toc.js +88 -0
  66. package/dist/formats/epub/toc.js.map +1 -0
  67. package/dist/formats/fb2/index.d.ts +13 -0
  68. package/dist/formats/fb2/index.d.ts.map +1 -0
  69. package/dist/formats/fb2/index.js +317 -0
  70. package/dist/formats/fb2/index.js.map +1 -0
  71. package/dist/formats/index.d.ts +4 -0
  72. package/dist/formats/index.d.ts.map +1 -0
  73. package/dist/formats/index.js +4 -0
  74. package/dist/formats/index.js.map +1 -0
  75. package/dist/formats/text/index.d.ts +11 -0
  76. package/dist/formats/text/index.d.ts.map +1 -0
  77. package/dist/formats/text/index.js +201 -0
  78. package/dist/formats/text/index.js.map +1 -0
  79. package/dist/formats/xml.d.ts +33 -0
  80. package/dist/formats/xml.d.ts.map +1 -0
  81. package/dist/formats/xml.js +321 -0
  82. package/dist/formats/xml.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +28 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/layout/chunk.d.ts +51 -0
  88. package/dist/layout/chunk.d.ts.map +1 -0
  89. package/dist/layout/chunk.js +131 -0
  90. package/dist/layout/chunk.js.map +1 -0
  91. package/dist/layout/index.d.ts +238 -0
  92. package/dist/layout/index.d.ts.map +1 -0
  93. package/dist/layout/index.js +476 -0
  94. package/dist/layout/index.js.map +1 -0
  95. package/dist/reader/index.d.ts +231 -0
  96. package/dist/reader/index.d.ts.map +1 -0
  97. package/dist/reader/index.js +958 -0
  98. package/dist/reader/index.js.map +1 -0
  99. package/dist/reader/input.d.ts +69 -0
  100. package/dist/reader/input.d.ts.map +1 -0
  101. package/dist/reader/input.js +75 -0
  102. package/dist/reader/input.js.map +1 -0
  103. package/dist/search/extract.d.ts +51 -0
  104. package/dist/search/extract.d.ts.map +1 -0
  105. package/dist/search/extract.js +401 -0
  106. package/dist/search/extract.js.map +1 -0
  107. package/dist/search/index.d.ts +11 -0
  108. package/dist/search/index.d.ts.map +1 -0
  109. package/dist/search/index.js +11 -0
  110. package/dist/search/index.js.map +1 -0
  111. package/dist/search/matcher.d.ts +56 -0
  112. package/dist/search/matcher.d.ts.map +1 -0
  113. package/dist/search/matcher.js +128 -0
  114. package/dist/search/matcher.js.map +1 -0
  115. package/dist/search/normalize.d.ts +48 -0
  116. package/dist/search/normalize.d.ts.map +1 -0
  117. package/dist/search/normalize.js +103 -0
  118. package/dist/search/normalize.js.map +1 -0
  119. package/dist/view/allowlist.d.ts +59 -0
  120. package/dist/view/allowlist.d.ts.map +1 -0
  121. package/dist/view/allowlist.js +198 -0
  122. package/dist/view/allowlist.js.map +1 -0
  123. package/dist/view/appearance.d.ts +137 -0
  124. package/dist/view/appearance.d.ts.map +1 -0
  125. package/dist/view/appearance.js +274 -0
  126. package/dist/view/appearance.js.map +1 -0
  127. package/dist/view/css.d.ts +24 -0
  128. package/dist/view/css.d.ts.map +1 -0
  129. package/dist/view/css.js +153 -0
  130. package/dist/view/css.js.map +1 -0
  131. package/dist/view/frame.d.ts +54 -0
  132. package/dist/view/frame.d.ts.map +1 -0
  133. package/dist/view/frame.js +980 -0
  134. package/dist/view/frame.js.map +1 -0
  135. package/dist/view/host.d.ts +160 -0
  136. package/dist/view/host.d.ts.map +1 -0
  137. package/dist/view/host.js +385 -0
  138. package/dist/view/host.js.map +1 -0
  139. package/dist/view/index.d.ts +12 -0
  140. package/dist/view/index.d.ts.map +1 -0
  141. package/dist/view/index.js +7 -0
  142. package/dist/view/index.js.map +1 -0
  143. package/dist/view/protocol.d.ts +220 -0
  144. package/dist/view/protocol.d.ts.map +1 -0
  145. package/dist/view/protocol.js +246 -0
  146. package/dist/view/protocol.js.map +1 -0
  147. package/dist/view/reference.d.ts +18 -0
  148. package/dist/view/reference.d.ts.map +1 -0
  149. package/dist/view/reference.js +25 -0
  150. package/dist/view/reference.js.map +1 -0
  151. package/dist/view/resources.d.ts +51 -0
  152. package/dist/view/resources.d.ts.map +1 -0
  153. package/dist/view/resources.js +242 -0
  154. package/dist/view/resources.js.map +1 -0
  155. package/dist/view/sanitize.d.ts +32 -0
  156. package/dist/view/sanitize.d.ts.map +1 -0
  157. package/dist/view/sanitize.js +226 -0
  158. package/dist/view/sanitize.js.map +1 -0
  159. package/dist/zip/central.d.ts +12 -0
  160. package/dist/zip/central.d.ts.map +1 -0
  161. package/dist/zip/central.js +72 -0
  162. package/dist/zip/central.js.map +1 -0
  163. package/dist/zip/crc32.d.ts +3 -0
  164. package/dist/zip/crc32.d.ts.map +1 -0
  165. package/dist/zip/crc32.js +28 -0
  166. package/dist/zip/crc32.js.map +1 -0
  167. package/dist/zip/eocd.d.ts +9 -0
  168. package/dist/zip/eocd.d.ts.map +1 -0
  169. package/dist/zip/eocd.js +84 -0
  170. package/dist/zip/eocd.js.map +1 -0
  171. package/dist/zip/errors.d.ts +19 -0
  172. package/dist/zip/errors.d.ts.map +1 -0
  173. package/dist/zip/errors.js +37 -0
  174. package/dist/zip/errors.js.map +1 -0
  175. package/dist/zip/index.d.ts +18 -0
  176. package/dist/zip/index.d.ts.map +1 -0
  177. package/dist/zip/index.js +78 -0
  178. package/dist/zip/index.js.map +1 -0
  179. package/dist/zip/inflate.d.ts +2 -0
  180. package/dist/zip/inflate.d.ts.map +1 -0
  181. package/dist/zip/inflate.js +43 -0
  182. package/dist/zip/inflate.js.map +1 -0
  183. package/dist/zip/source.d.ts +15 -0
  184. package/dist/zip/source.d.ts.map +1 -0
  185. package/dist/zip/source.js +34 -0
  186. package/dist/zip/source.js.map +1 -0
  187. package/package.json +75 -0
  188. package/src/core/book.ts +80 -0
  189. package/src/core/errors.ts +31 -0
  190. package/src/core/format.ts +14 -0
  191. package/src/core/graphemes.ts +73 -0
  192. package/src/core/index.ts +27 -0
  193. package/src/core/lookup.ts +16 -0
  194. package/src/core/open.ts +31 -0
  195. package/src/core/position.ts +485 -0
  196. package/src/core/reading-text.ts +200 -0
  197. package/src/core/source.ts +61 -0
  198. package/src/core/storage.ts +6 -0
  199. package/src/core/text.ts +35 -0
  200. package/src/formats/epub/href.ts +54 -0
  201. package/src/formats/epub/index.ts +235 -0
  202. package/src/formats/epub/opf.ts +143 -0
  203. package/src/formats/epub/toc.ts +120 -0
  204. package/src/formats/fb2/index.ts +348 -0
  205. package/src/formats/index.ts +3 -0
  206. package/src/formats/text/index.ts +208 -0
  207. package/src/formats/xml.ts +326 -0
  208. package/src/index.ts +26 -0
  209. package/src/layout/chunk.ts +169 -0
  210. package/src/layout/index.ts +573 -0
  211. package/src/reader/index.ts +1206 -0
  212. package/src/reader/input.ts +122 -0
  213. package/src/search/extract.ts +403 -0
  214. package/src/search/index.ts +10 -0
  215. package/src/search/matcher.ts +187 -0
  216. package/src/search/normalize.ts +122 -0
  217. package/src/view/allowlist.ts +273 -0
  218. package/src/view/appearance.ts +380 -0
  219. package/src/view/css.ts +172 -0
  220. package/src/view/frame.ts +1018 -0
  221. package/src/view/host.ts +527 -0
  222. package/src/view/index.ts +24 -0
  223. package/src/view/protocol.ts +412 -0
  224. package/src/view/reference.ts +30 -0
  225. package/src/view/resources.ts +265 -0
  226. package/src/view/sanitize.ts +266 -0
  227. package/src/zip/central.ts +85 -0
  228. package/src/zip/crc32.ts +30 -0
  229. package/src/zip/eocd.ts +97 -0
  230. package/src/zip/errors.ts +41 -0
  231. package/src/zip/index.ts +114 -0
  232. package/src/zip/inflate.ts +41 -0
  233. package/src/zip/source.ts +51 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Search normalization — the folding policy that decides when two strings "match"
3
+ * for the purpose of full-text search. Applied to both the query and the extracted
4
+ * section text before literal substring matching.
5
+ *
6
+ * Policy (see docs/domains/search.md ## Key decisions):
7
+ * - **Case-insensitive.** Folded with `toLowerCase()`.
8
+ * - **Unicode NFC.** Each grapheme is canonically composed, so a pre-composed `é`
9
+ * and a decomposed `e`+combining-acute match.
10
+ * - **Diacritics are preserved.** `café` does not match `cafe`. Stripping marks is a
11
+ * locale-sensitive judgement (German ä, Turkish dotless ı) better left to a later,
12
+ * opt-in dimension than baked into the default.
13
+ * - **Smart punctuation folded to ASCII.** Curly quotes → straight, en/em dashes and
14
+ * the minus sign → hyphen-minus, non-breaking and other Unicode spaces → a normal
15
+ * space. A reader types `don't`, the book prints `don’t`.
16
+ * - **Whitespace collapsed.** Every run of whitespace (including the newlines that
17
+ * land between block elements during extraction) becomes a single space, and
18
+ * leading/trailing space folds off, so a query never fails on invisible layout
19
+ * whitespace.
20
+ *
21
+ * The fold must stay anchorable: the matcher works on the normalized string but each
22
+ * hit's text and `Position` anchor into the **raw extracted** text. So the fold is
23
+ * built grapheme by grapheme alongside an offset map from every normalized index
24
+ * back to the raw UTF-16 offset it came from. The raw text handed to
25
+ * `capturePosition` is never NFC-rewritten (it must stay byte-identical to what the
26
+ * paginator measures against), so composition happens only inside the folded copy.
27
+ */
28
+ /** A normalized string plus the map from its indices back to raw UTF-16 offsets. */
29
+ export interface NormalizedText {
30
+ /** The folded, whitespace-collapsed text the matcher searches. */
31
+ readonly text: string;
32
+ /**
33
+ * `map[i]` is the raw UTF-16 offset of the character that produced normalized
34
+ * code unit `i`; `map[text.length]` is the raw text length. Monotonic
35
+ * non-decreasing, so a normalized span `[a, b)` maps to the raw span
36
+ * `[map[a], map[b])`.
37
+ */
38
+ readonly map: readonly number[];
39
+ }
40
+ /**
41
+ * Folds `raw` under the search policy and returns the normalized text plus the
42
+ * index map back to raw UTF-16 offsets. A match found in `text` maps cleanly onto a
43
+ * raw span for `capturePosition`.
44
+ */
45
+ export declare function normalizeText(raw: string): NormalizedText;
46
+ /** The query folded to the same space as {@link normalizeText}'s `text`. */
47
+ export declare function normalizeQuery(query: string): string;
48
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/search/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AA6CD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CA2BzD;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Search normalization — the folding policy that decides when two strings "match"
3
+ * for the purpose of full-text search. Applied to both the query and the extracted
4
+ * section text before literal substring matching.
5
+ *
6
+ * Policy (see docs/domains/search.md ## Key decisions):
7
+ * - **Case-insensitive.** Folded with `toLowerCase()`.
8
+ * - **Unicode NFC.** Each grapheme is canonically composed, so a pre-composed `é`
9
+ * and a decomposed `e`+combining-acute match.
10
+ * - **Diacritics are preserved.** `café` does not match `cafe`. Stripping marks is a
11
+ * locale-sensitive judgement (German ä, Turkish dotless ı) better left to a later,
12
+ * opt-in dimension than baked into the default.
13
+ * - **Smart punctuation folded to ASCII.** Curly quotes → straight, en/em dashes and
14
+ * the minus sign → hyphen-minus, non-breaking and other Unicode spaces → a normal
15
+ * space. A reader types `don't`, the book prints `don’t`.
16
+ * - **Whitespace collapsed.** Every run of whitespace (including the newlines that
17
+ * land between block elements during extraction) becomes a single space, and
18
+ * leading/trailing space folds off, so a query never fails on invisible layout
19
+ * whitespace.
20
+ *
21
+ * The fold must stay anchorable: the matcher works on the normalized string but each
22
+ * hit's text and `Position` anchor into the **raw extracted** text. So the fold is
23
+ * built grapheme by grapheme alongside an offset map from every normalized index
24
+ * back to the raw UTF-16 offset it came from. The raw text handed to
25
+ * `capturePosition` is never NFC-rewritten (it must stay byte-identical to what the
26
+ * paginator measures against), so composition happens only inside the folded copy.
27
+ */
28
+ const SMART = new Map([
29
+ ['‘', "'"], // ‘ left single quote
30
+ ['’', "'"], // ’ right single quote / apostrophe
31
+ ['‚', "'"], // ‚ single low-9 quote
32
+ ['‛', "'"], // ‛ single high-reversed-9 quote
33
+ ['“', '"'], // “ left double quote
34
+ ['”', '"'], // ” right double quote
35
+ ['„', '"'], // „ double low-9 quote
36
+ ['‟', '"'], // ‟ double high-reversed-9 quote
37
+ ['′', "'"], // ′ prime
38
+ ['″', '"'], // ″ double prime
39
+ ['‐', '-'], // ‐ hyphen
40
+ ['‑', '-'], // ‑ non-breaking hyphen
41
+ ['‒', '-'], // ‒ figure dash
42
+ ['–', '-'], // – en dash
43
+ ['—', '-'], // — em dash
44
+ ['―', '-'], // ― horizontal bar
45
+ ['−', '-'], // − minus sign
46
+ ]);
47
+ const WHITESPACE = /^\s$/;
48
+ let graphemeSegmenter;
49
+ function segmenter() {
50
+ graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });
51
+ return graphemeSegmenter;
52
+ }
53
+ /** True when every code point in a grapheme is whitespace. */
54
+ function isWhitespace(grapheme) {
55
+ for (const ch of grapheme) {
56
+ if (!WHITESPACE.test(ch) && ch !== '​' && ch !== '')
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ /** Folds a single grapheme under the punctuation/NFC/case policy. */
62
+ function foldGrapheme(grapheme) {
63
+ const mapped = grapheme.length === 1 ? (SMART.get(grapheme) ?? grapheme) : grapheme;
64
+ return mapped.normalize('NFC').toLowerCase();
65
+ }
66
+ /**
67
+ * Folds `raw` under the search policy and returns the normalized text plus the
68
+ * index map back to raw UTF-16 offsets. A match found in `text` maps cleanly onto a
69
+ * raw span for `capturePosition`.
70
+ */
71
+ export function normalizeText(raw) {
72
+ let text = '';
73
+ const map = [];
74
+ let pendingSpace = false;
75
+ let sawNonSpace = false;
76
+ let rawOffset = 0;
77
+ for (const { segment } of segmenter().segment(raw)) {
78
+ if (isWhitespace(segment)) {
79
+ if (sawNonSpace)
80
+ pendingSpace = true;
81
+ rawOffset += segment.length;
82
+ continue;
83
+ }
84
+ if (pendingSpace) {
85
+ map.push(rawOffset); // the collapsed space anchors at the start of the text it precedes
86
+ text += ' ';
87
+ pendingSpace = false;
88
+ }
89
+ const folded = foldGrapheme(segment);
90
+ for (let unit = 0; unit < folded.length; unit += 1)
91
+ map.push(rawOffset);
92
+ text += folded;
93
+ sawNonSpace = true;
94
+ rawOffset += segment.length;
95
+ }
96
+ map.push(raw.length); // trailing whitespace folds off; the terminal maps to raw end
97
+ return { text, map };
98
+ }
99
+ /** The query folded to the same space as {@link normalizeText}'s `text`. */
100
+ export function normalizeQuery(query) {
101
+ return normalizeText(query).text;
102
+ }
103
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/search/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What is allowed to survive sanitization, stated as allowlists.
3
+ *
4
+ * A blocklist loses to the next vector the platform invents; an allowlist only
5
+ * ever loses formatting. Anything absent from these tables is either unwrapped
6
+ * (the element goes, its text stays — a book is text before it is markup) or,
7
+ * when its children are not readable content, discarded whole.
8
+ */
9
+ export declare const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
10
+ export declare const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
11
+ export declare const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
12
+ export declare const HTML_GLOBAL_ATTRIBUTES: ReadonlySet<string>;
13
+ export declare const HTML_ELEMENTS: ReadonlyMap<string, ReadonlySet<string>>;
14
+ /**
15
+ * Elements that go with everything inside them, because their children are not
16
+ * prose: raw-text elements whose content is source rather than text, form
17
+ * controls, and void elements with no children to keep.
18
+ *
19
+ * Everything else absent from the allowlist is *unwrapped* instead — the
20
+ * element goes, its text stays. That distinction is load-bearing twice over. A
21
+ * book is text before it is markup, and a malformed one nests half a chapter
22
+ * inside a stray element: an </noscript> end tag after an open <p> is ignored
23
+ * by the HTML parser, so an unclosed <noscript> swallows everything after it,
24
+ * and discarding that element would delete the rest of the chapter.
25
+ *
26
+ * <noscript> is also why unwrapping is the safer option and not merely the
27
+ * kinder one. Its content is markup when a document is parsed with scripting
28
+ * off and raw text when it is parsed with scripting on, so an element that
29
+ * survives into the frame is re-read in the other mode and attribute
30
+ * boundaries move under it — the mutation-XSS shape this pipeline exists to
31
+ * defeat. Removing the element removes the reinterpretation.
32
+ *
33
+ * The table itself lives in `src/core/reading-text.ts` (re-exported here under
34
+ * its frozen name): discarding decides not only what renders but what the
35
+ * frame's *text* is, and the headless search extractor must mirror it exactly
36
+ * so a hit's anchor exists in the text the frame resolves against. One table,
37
+ * two consumers — never fork it.
38
+ */
39
+ export { DISCARDED_HTML_ELEMENTS as HTML_DISCARDED } from '../core/reading-text.ts';
40
+ export declare const SVG_GLOBAL_ATTRIBUTES: ReadonlySet<string>;
41
+ export declare const SVG_ELEMENTS: ReadonlyMap<string, ReadonlySet<string>>;
42
+ /**
43
+ * SMIL animation is on this list, not merely absent from the allowlist, because
44
+ * `<animate attributeName="href" to="javascript:...">` turns an inert element
45
+ * into a live one after sanitization has already run.
46
+ *
47
+ * Shared from `src/core/reading-text.ts` for the same reason as HTML_DISCARDED:
48
+ * the search extractor mirrors what discarding removes from the frame's text.
49
+ */
50
+ export { DISCARDED_SVG_ELEMENTS as SVG_DISCARDED } from '../core/reading-text.ts';
51
+ /** Attributes carrying a reference, by element local name. */
52
+ export declare const URL_ATTRIBUTES: ReadonlyMap<string, ReadonlySet<string>>;
53
+ /**
54
+ * Whether a reference may stay on the attribute at all. Relative references are
55
+ * kept so they can be resolved to blob URLs; remote ones are kept so CSP is the
56
+ * layer that refuses them, and refuses them observably.
57
+ */
58
+ export declare function isAllowedReference(element: string, attribute: string, kind: string, scheme: string | undefined): boolean;
59
+ //# sourceMappingURL=allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../src/view/allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,eAAO,MAAM,cAAc,iCAAiC,CAAC;AAC7D,eAAO,MAAM,aAAa,+BAA+B,CAAC;AAC1D,eAAO,MAAM,eAAe,iCAAiC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,qBAYlC,CAAC;AAIF,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CA8EjE,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,uBAAuB,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEpF,eAAO,MAAM,qBAAqB,qBAuCjC,CAAC;AAoBF,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CA2BhE,CAAC;AAEH;;;;;;;GAOG;AACH,OAAO,EAAE,sBAAsB,IAAI,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAElF,8DAA8D;AAC9D,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAUlE,CAAC;AAKH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAWxH"}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * What is allowed to survive sanitization, stated as allowlists.
3
+ *
4
+ * A blocklist loses to the next vector the platform invents; an allowlist only
5
+ * ever loses formatting. Anything absent from these tables is either unwrapped
6
+ * (the element goes, its text stays — a book is text before it is markup) or,
7
+ * when its children are not readable content, discarded whole.
8
+ */
9
+ function set(...names) {
10
+ return new Set(names);
11
+ }
12
+ const NONE = new Set();
13
+ export const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
14
+ export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
15
+ export const XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink';
16
+ export const HTML_GLOBAL_ATTRIBUTES = set('class', 'dir', 'epub:type', 'hidden', 'id', 'lang', 'role', 'style', 'title', 'translate', 'xml:lang');
17
+ const CELL = set('abbr', 'align', 'colspan', 'headers', 'rowspan', 'scope', 'valign', 'width');
18
+ export const HTML_ELEMENTS = new Map([
19
+ ['a', set('href')],
20
+ ['abbr', NONE],
21
+ ['address', NONE],
22
+ ['article', NONE],
23
+ ['aside', NONE],
24
+ ['b', NONE],
25
+ ['bdi', NONE],
26
+ ['bdo', NONE],
27
+ ['big', NONE],
28
+ ['blockquote', set('cite')],
29
+ ['br', NONE],
30
+ ['caption', set('align')],
31
+ ['center', NONE],
32
+ ['cite', NONE],
33
+ ['code', NONE],
34
+ ['col', set('align', 'span', 'valign', 'width')],
35
+ ['colgroup', set('align', 'span', 'valign', 'width')],
36
+ ['dd', NONE],
37
+ ['del', set('cite', 'datetime')],
38
+ ['details', set('open')],
39
+ ['dfn', NONE],
40
+ ['div', NONE],
41
+ ['dl', NONE],
42
+ ['dt', NONE],
43
+ ['em', NONE],
44
+ ['figcaption', NONE],
45
+ ['figure', NONE],
46
+ ['footer', NONE],
47
+ ['h1', NONE],
48
+ ['h2', NONE],
49
+ ['h3', NONE],
50
+ ['h4', NONE],
51
+ ['h5', NONE],
52
+ ['h6', NONE],
53
+ ['header', NONE],
54
+ ['hgroup', NONE],
55
+ ['hr', NONE],
56
+ ['i', NONE],
57
+ ['img', set('alt', 'height', 'src', 'width')],
58
+ ['ins', set('cite', 'datetime')],
59
+ ['kbd', NONE],
60
+ ['li', set('value')],
61
+ ['link', set('href', 'media', 'rel', 'type')],
62
+ ['main', NONE],
63
+ ['mark', NONE],
64
+ ['nav', NONE],
65
+ ['ol', set('reversed', 'start', 'type')],
66
+ ['p', NONE],
67
+ ['pre', NONE],
68
+ ['q', set('cite')],
69
+ ['rp', NONE],
70
+ ['rt', NONE],
71
+ ['ruby', NONE],
72
+ ['s', NONE],
73
+ ['samp', NONE],
74
+ ['section', NONE],
75
+ ['small', NONE],
76
+ ['span', NONE],
77
+ ['strike', NONE],
78
+ ['strong', NONE],
79
+ ['style', set('media', 'type')],
80
+ ['sub', NONE],
81
+ ['summary', NONE],
82
+ ['sup', NONE],
83
+ ['table', set('align', 'border', 'cellpadding', 'cellspacing', 'summary', 'width')],
84
+ ['tbody', set('align', 'valign')],
85
+ ['td', CELL],
86
+ ['tfoot', set('align', 'valign')],
87
+ ['th', CELL],
88
+ ['thead', set('align', 'valign')],
89
+ ['time', set('datetime')],
90
+ ['tr', set('align', 'valign')],
91
+ ['tt', NONE],
92
+ ['u', NONE],
93
+ ['ul', NONE],
94
+ ['var', NONE],
95
+ ['wbr', NONE],
96
+ ]);
97
+ /**
98
+ * Elements that go with everything inside them, because their children are not
99
+ * prose: raw-text elements whose content is source rather than text, form
100
+ * controls, and void elements with no children to keep.
101
+ *
102
+ * Everything else absent from the allowlist is *unwrapped* instead — the
103
+ * element goes, its text stays. That distinction is load-bearing twice over. A
104
+ * book is text before it is markup, and a malformed one nests half a chapter
105
+ * inside a stray element: an </noscript> end tag after an open <p> is ignored
106
+ * by the HTML parser, so an unclosed <noscript> swallows everything after it,
107
+ * and discarding that element would delete the rest of the chapter.
108
+ *
109
+ * <noscript> is also why unwrapping is the safer option and not merely the
110
+ * kinder one. Its content is markup when a document is parsed with scripting
111
+ * off and raw text when it is parsed with scripting on, so an element that
112
+ * survives into the frame is re-read in the other mode and attribute
113
+ * boundaries move under it — the mutation-XSS shape this pipeline exists to
114
+ * defeat. Removing the element removes the reinterpretation.
115
+ *
116
+ * The table itself lives in `src/core/reading-text.ts` (re-exported here under
117
+ * its frozen name): discarding decides not only what renders but what the
118
+ * frame's *text* is, and the headless search extractor must mirror it exactly
119
+ * so a hit's anchor exists in the text the frame resolves against. One table,
120
+ * two consumers — never fork it.
121
+ */
122
+ export { DISCARDED_HTML_ELEMENTS as HTML_DISCARDED } from '../core/reading-text.js';
123
+ export const SVG_GLOBAL_ATTRIBUTES = set('class', 'clip-path', 'clip-rule', 'color', 'display', 'dominant-baseline', 'fill', 'fill-opacity', 'fill-rule', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'lang', 'letter-spacing', 'mask', 'opacity', 'overflow', 'paint-order', 'role', 'shape-rendering', 'space', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-opacity', 'stroke-width', 'style', 'text-anchor', 'transform', 'vector-effect', 'visibility', 'word-spacing');
124
+ const GRADIENT = set('cx', 'cy', 'fr', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'spreadMethod', 'x1', 'x2', 'y1', 'y2');
125
+ const TEXT_POSITION = set('dx', 'dy', 'rotate', 'textLength', 'x', 'y');
126
+ export const SVG_ELEMENTS = new Map([
127
+ ['a', set('href')],
128
+ ['circle', set('cx', 'cy', 'r')],
129
+ ['clipPath', set('clipPathUnits')],
130
+ ['defs', NONE],
131
+ ['desc', NONE],
132
+ ['ellipse', set('cx', 'cy', 'rx', 'ry')],
133
+ ['g', NONE],
134
+ ['image', set('height', 'href', 'preserveAspectRatio', 'width', 'x', 'y')],
135
+ ['line', set('x1', 'x2', 'y1', 'y2')],
136
+ ['linearGradient', GRADIENT],
137
+ ['marker', set('markerHeight', 'markerUnits', 'markerWidth', 'orient', 'refX', 'refY', 'viewBox')],
138
+ ['mask', set('height', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y')],
139
+ ['path', set('d', 'pathLength')],
140
+ ['pattern', set('height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'viewBox', 'width', 'x', 'y')],
141
+ ['polygon', set('points')],
142
+ ['polyline', set('points')],
143
+ ['radialGradient', GRADIENT],
144
+ ['rect', set('height', 'rx', 'ry', 'width', 'x', 'y')],
145
+ ['stop', set('offset')],
146
+ ['style', set('media', 'type')],
147
+ ['svg', set('height', 'preserveAspectRatio', 'version', 'viewBox', 'width', 'x', 'y')],
148
+ ['symbol', set('preserveAspectRatio', 'viewBox')],
149
+ ['text', TEXT_POSITION],
150
+ ['title', NONE],
151
+ ['tspan', TEXT_POSITION],
152
+ ['use', set('height', 'href', 'width', 'x', 'y')],
153
+ ]);
154
+ /**
155
+ * SMIL animation is on this list, not merely absent from the allowlist, because
156
+ * `<animate attributeName="href" to="javascript:...">` turns an inert element
157
+ * into a live one after sanitization has already run.
158
+ *
159
+ * Shared from `src/core/reading-text.ts` for the same reason as HTML_DISCARDED:
160
+ * the search extractor mirrors what discarding removes from the frame's text.
161
+ */
162
+ export { DISCARDED_SVG_ELEMENTS as SVG_DISCARDED } from '../core/reading-text.js';
163
+ /** Attributes carrying a reference, by element local name. */
164
+ export const URL_ATTRIBUTES = new Map([
165
+ ['a', set('href')],
166
+ ['blockquote', set('cite')],
167
+ ['del', set('cite')],
168
+ ['image', set('href')],
169
+ ['img', set('src')],
170
+ ['ins', set('cite')],
171
+ ['link', set('href')],
172
+ ['q', set('cite')],
173
+ ['use', set('href')],
174
+ ]);
175
+ const LINK_SCHEMES = set('http', 'https', 'mailto', 'tel');
176
+ const MEDIA_SCHEMES = set('data', 'http', 'https');
177
+ /**
178
+ * Whether a reference may stay on the attribute at all. Relative references are
179
+ * kept so they can be resolved to blob URLs; remote ones are kept so CSP is the
180
+ * layer that refuses them, and refuses them observably.
181
+ */
182
+ export function isAllowedReference(element, attribute, kind, scheme) {
183
+ // <use href> reaches into the same document and nowhere else: a data: URI
184
+ // there is a document-injection vector, not an image reference.
185
+ if (element === 'use')
186
+ return kind === 'fragment';
187
+ if (kind === 'fragment')
188
+ return element === 'a';
189
+ if (kind === 'relative')
190
+ return true;
191
+ if (kind !== 'scheme' || scheme === undefined)
192
+ return false;
193
+ if (attribute === 'src' || (element === 'image' && attribute === 'href') || element === 'link') {
194
+ return MEDIA_SCHEMES.has(scheme);
195
+ }
196
+ return LINK_SCHEMES.has(scheme);
197
+ }
198
+ //# sourceMappingURL=allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../src/view/allowlist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /** A built-in theme name, or `'custom'` for a caller-supplied variable set. */
2
+ export type ThemeName = 'light' | 'dark' | 'sepia' | 'custom';
3
+ /** Text alignment for body prose: publisher default, ragged left, or justified. */
4
+ export type TextAlign = 'start' | 'justify';
5
+ /**
6
+ * The live appearance state: the theme half (content colours) plus the typography
7
+ * half (font, size, line height, margin, alignment, hyphenation, columns). Both
8
+ * flow through the same `--wr-*` namespace and the same cascade, so publisher CSS
9
+ * can restyle its own content but can never reach an appearance variable.
10
+ * `customProperties` supplies bespoke `--wr-*` values that merge over the resolved
11
+ * theme (they win), so a `'custom'` theme is any override set and the built-in
12
+ * themes are equally overridable.
13
+ */
14
+ export interface Appearance {
15
+ /**
16
+ * Which theme drives content colours. Omitted (or `undefined`) means "do not
17
+ * force a theme" — the frame follows `prefers-color-scheme`. A named theme
18
+ * overrides the media query.
19
+ */
20
+ readonly theme?: ThemeName;
21
+ /**
22
+ * Caller-supplied `--wr-*` custom properties, merged over the resolved theme.
23
+ * Keys may be given with or without the leading `--`. Values are emitted
24
+ * verbatim into an inline `<style>` (CSP allows `style-src 'unsafe-inline'`),
25
+ * so a caller controls exactly what its own book renders.
26
+ */
27
+ readonly customProperties?: Readonly<Record<string, string>>;
28
+ /** Body font family (a CSS `font-family` value). Reflows. */
29
+ readonly fontFamily?: string;
30
+ /** Body font size in CSS px. Reflows. */
31
+ readonly fontSize?: number;
32
+ /** Body line height, a unitless multiplier. Reflows. */
33
+ readonly lineHeight?: number;
34
+ /**
35
+ * Page margin in CSS px — the whitespace framing the text on both page edges,
36
+ * which also serves as the gutter between columns (a page reads
37
+ * margin/col/margin/col/margin). It is not a `--wr-*` variable: page geometry
38
+ * belongs to the paginator, so it rides `PaginateOptions` (as `columnGap`) and
39
+ * reflows the layout. Reflows.
40
+ */
41
+ readonly margin?: number;
42
+ /** Text alignment: `'start'` (publisher default) or `'justify'`. May reflow. */
43
+ readonly textAlign?: TextAlign;
44
+ /** Shorthand for `textAlign: 'justify'` when `true`, `'start'` when `false`. */
45
+ readonly justify?: boolean;
46
+ /** Whether the content root hyphenates. May reflow line breaks. */
47
+ readonly hyphenate?: boolean;
48
+ /** Number of text columns per page: 1 or 2. Reflows. */
49
+ readonly columns?: 1 | 2;
50
+ }
51
+ /**
52
+ * The theme portion of the `--wr-*` contract. These names are the public,
53
+ * frozen-ish vocabulary a custom theme supplies; content colours flow through
54
+ * them so a theme change repaints without touching layout.
55
+ */
56
+ export interface ThemeVariables {
57
+ /** Reading surface background. */
58
+ readonly '--wr-background': string;
59
+ /** Body text colour. */
60
+ readonly '--wr-color': string;
61
+ /** Hyperlink colour. */
62
+ readonly '--wr-link-color': string;
63
+ /** Selection background. */
64
+ readonly '--wr-selection-background': string;
65
+ /** Selection text colour. */
66
+ readonly '--wr-selection-color': string;
67
+ }
68
+ /**
69
+ * The built-in themes. Each is a full {@link ThemeVariables} set so a forced
70
+ * theme is self-contained — it needs no media query to be complete.
71
+ */
72
+ export declare const THEMES: Readonly<Record<Exclude<ThemeName, 'custom'>, ThemeVariables>>;
73
+ /**
74
+ * The typography portion of the `--wr-*` contract. Each maps a live appearance
75
+ * knob to a custom property the content stylesheet consumes; publisher CSS never
76
+ * names these, so it cannot clobber them, exactly like the theme variables. All
77
+ * are optional: an unset knob leaves the publisher's own value in place.
78
+ */
79
+ export interface TypographyVariables {
80
+ /** Body font family. */
81
+ readonly '--wr-font-family'?: string;
82
+ /** Body font size (a CSS length, e.g. `18px`). */
83
+ readonly '--wr-font-size'?: string;
84
+ /** Body line height (unitless multiplier). */
85
+ readonly '--wr-line-height'?: string;
86
+ /** Body text alignment. */
87
+ readonly '--wr-text-align'?: string;
88
+ /** Content-root hyphenation (`auto` or `manual`). */
89
+ readonly '--wr-hyphens'?: string;
90
+ /** Number of text columns per page. */
91
+ readonly '--wr-column-count'?: string;
92
+ }
93
+ /**
94
+ * Merge two partial `Appearance`s. `update` wins field-by-field; `customProperties`
95
+ * shallow-merges (an update's keys override the base's, and only the base's other
96
+ * keys survive). Passing `theme: undefined` in `update` does not clear the base
97
+ * theme — omit the key to keep it, since `exactOptionalPropertyTypes` distinguishes
98
+ * absent from `undefined`.
99
+ */
100
+ export declare function mergeAppearance(base: Appearance, update: Appearance): Appearance;
101
+ /**
102
+ * The resolved `--wr-*` variables for a forced theme, or `null` when no theme is
103
+ * forced (the frame follows `prefers-color-scheme` instead). Custom properties
104
+ * merge over the theme's base and win; for `theme: 'custom'` the base is the light
105
+ * theme so an incomplete custom set still yields a complete, readable variable set.
106
+ */
107
+ export declare function resolveThemeProperties(appearance: Appearance): Record<string, string> | null;
108
+ /**
109
+ * The resolved typography `--wr-*` variables for an appearance, or `null` when no
110
+ * typography knob is set. `columns` maps to `--wr-column-count`; `justify` folds
111
+ * into `--wr-text-align` (an explicit `textAlign` wins over `justify`). Only the
112
+ * knobs the caller supplied are emitted — an unset knob leaves the publisher's own
113
+ * value in place rather than forcing a default.
114
+ */
115
+ export declare function resolveTypographyProperties(appearance: Appearance): TypographyVariables | null;
116
+ /**
117
+ * The full appearance stylesheet the frame injects at document assembly, before
118
+ * the publisher's `headHtml`. It carries the theme half (colours) and the
119
+ * typography half (font/size/line-height/alignment/hyphenation) through one
120
+ * `--wr-*` namespace. It declares the theme layer first (so its ordering is fixed
121
+ * regardless of any `@layer` a book declares later), emits the themeable defaults
122
+ * and the unlayered surface guarantee, then the `prefers-color-scheme` defaults,
123
+ * then — if a theme is forced — an unlayered `:root` block that overrides the
124
+ * media query, and finally the unlayered typography guarantee. `null` from
125
+ * {@link resolveThemeProperties} means "follow the OS"; a variable set means
126
+ * "force it".
127
+ */
128
+ export declare function themeStyleSheet(appearance: Appearance): string;
129
+ /**
130
+ * The typography knobs that change chunk geometry and therefore require a re-layout
131
+ * (a `paginate`), not just a variable poke: font family, font size, line height,
132
+ * page margin, and column count. Alignment and hyphenation can shift line breaks
133
+ * too, so they are treated as reflowing to be safe. Whether an `update` touches any
134
+ * of these decides which path {@link Reader.setAppearance} takes.
135
+ */
136
+ export declare function isReflowingUpdate(update: Appearance): boolean;
137
+ //# sourceMappingURL=appearance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../../src/view/appearance.ts"],"names":[],"mappings":"AAkBA,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE9D,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE5C;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,wBAAwB;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,wBAAwB;IACxB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,4BAA4B;IAC5B,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,6BAA6B;IAC7B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC;AAED;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,cAAc,CAAC,CAsBxE,CAAC;AAEX;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,kDAAkD;IAClD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,8CAA8C;IAC9C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,2BAA2B;IAC3B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,qDAAqD;IACrD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,uCAAuC;IACvC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAeD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,GAAG,UAAU,CAkChF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAgB5F;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,UAAU,GACrB,mBAAmB,GAAG,IAAI,CAY5B;AAiGD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAW9D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAW7D"}